{"record":{"id":"dc09d517e9914e99","repo":"restic/restic","slug":"double-quoted-string-not-terminated","errorCode":null,"errorMessage":"double-quoted string not terminated","messagePattern":"double-quoted string not terminated","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/backend/shell_split.go","lineNumber":68,"sourceCode":"\tfor i, r := range data {\n\t\tif s.isSplitChar(r) {\n\t\t\tif fieldStart >= 0 {\n\t\t\t\tstrs = append(strs, data[fieldStart:i])\n\t\t\t\tfieldStart = -1\n\t\t\t}\n\t\t} else if fieldStart == -1 {\n\t\t\tfieldStart = i\n\t\t}\n\t}\n\tif fieldStart >= 0 { // Last field might end at EOF.\n\t\tstrs = append(strs, data[fieldStart:])\n\t}\n\n\tswitch s.quote {\n\tcase '\\'':\n\t\treturn nil, errors.New(\"single-quoted string not terminated\")\n\tcase '\"':\n\t\treturn nil, errors.New(\"double-quoted string not terminated\")\n\t}\n\n\tif len(strs) == 0 {\n\t\treturn nil, errors.New(\"command string is empty\")\n\t}\n\n\treturn strs, nil\n}\n","sourceCodeStart":50,"sourceCodeEnd":77,"githubUrl":"https://github.com/restic/restic/blob/a80be1478a4c537f8396e0db2b05120aa78f11e0/internal/backend/shell_split.go#L50-L77","documentation":"Companion of the single-quote check in SplitShellStrings: after end of input the tokenizer still has a double quote open, so the string cannot be split into well-formed arguments and the parse fails.","triggerScenarios":"sftp.command values such as 'ssh -o ProxyCommand=\"nc host 22' (opening double quote never closed), or values mangled by YAML/JSON/config escaping that strips one quote of a pair.","commonSituations":"Nested quoting through multiple layers (shell -> restic option -> ssh argument); CI pipelines where YAML folding removes quotes; editing options in a UI that trims trailing quote characters.","solutions":["Close the double quote: ensure every opening '\"' has a matching '\"'.","Prefer single quotes at the outer layer and double quotes inside, or vice versa, to halve the nesting.","Paste the value into 'printf %s' / a validator to confirm the character counts match."],"exampleFix":"# before\nrestic -o sftp.command='ssh -o ProxyCommand=\"nc jump 22' -r sftp:host:/repo snapshots\n# -> double-quoted string not terminated\n\n# after\nrestic -o sftp.command='ssh -o ProxyCommand=\"nc jump 22\"' -r sftp:host:/repo snapshots","handlingStrategy":"validation","validationCode":"func doubleQuotesBalanced(s string) bool {\n\tvar inSingle bool\n\tcount := 0\n\tfor _, r := range s {\n\t\tif r == '\\'' { inSingle = !inSingle }\n\t\tif r == '\"' && !inSingle { count++ }\n\t}\n\treturn count%2 == 0\n}","typeGuard":null,"tryCatchPattern":"args, err := backend.SplitShellStrings(cmd)\nif err != nil {\n\treturn nil, fmt.Errorf(\"invalid sftp.command %q: %w\", cmd, err) // covers unterminated double quotes\n}","preventionTips":["Minimize nesting layers; prefer single-quoted outer strings in YAML.","Run config through a YAML linter that flags unbalanced quotes."],"tags":["sftp","shell","quoting","configuration","parsing"],"backgroundTag":null,"analyzedSha":"a80be1478a4c537f8396e0db2b05120aa78f11e0","analyzedAt":"2026-08-15T15:30:29.928Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}