fix(codersdk): reject trailing data after closing single quote in env import (#27474)

This commit is contained in:
dylanhuff-at-coder
2026-07-24 01:20:02 -04:00
committed by GitHub
parent b8727d9c23
commit f96338110b
2 changed files with 35 additions and 11 deletions
+15 -10
View File
@@ -191,11 +191,7 @@ func parseEnvValue(rhs string, lineNum int) (string, error) {
}
return unescapeDoubleQuoted(inner), nil
case '\'':
inner, ok := quotedInner(v, '\'')
if !ok {
return "", xerrors.Errorf("line %d: missing closing single quote", lineNum)
}
return inner, nil
return singleQuotedInner(v, lineNum)
default:
return strings.TrimSpace(v), nil
}
@@ -222,12 +218,21 @@ func hasOddBackslashRun(s string, before int) bool {
return count%2 == 1
}
func quotedInner(v string, quote byte) (string, bool) {
trimmed := strings.TrimRight(v, " \t")
if len(trimmed) < 2 || trimmed[len(trimmed)-1] != quote {
return "", false
// singleQuotedInner returns the content between the opening single
// quote at index 0 and the first closing single quote. Single quotes
// have no escape sequences, so the first quote after the opener always
// closes the value. Only whitespace may follow the closing quote.
func singleQuotedInner(v string, lineNum int) (string, error) {
for i := 1; i < len(v); i++ {
if v[i] != '\'' {
continue
}
if strings.Trim(v[i+1:], " \t") != "" {
return "", xerrors.Errorf("line %d: unexpected data after closing single quote", lineNum)
}
return v[1:i], nil
}
return trimmed[1 : len(trimmed)-1], true
return "", xerrors.Errorf("line %d: missing closing single quote", lineNum)
}
func unescapeDoubleQuoted(s string) string {
+20 -1
View File
@@ -29,6 +29,11 @@ func TestParseSecretsFileEnv(t *testing.T) {
`DQ_CARRIAGE="a\rb"`,
`DQ_UNKNOWN="x\zy"`,
`SQUOTED='literal \n no escape'`,
"SQ_TRAILING_WS='abc' \t",
"SQ_EMPTY=''",
`SQ_DOUBLE_INSIDE='a"b'`,
`SQ_HASH='#not a comment'`,
"SQ_UNICODE='héllo 世界'",
"EQ_IN_VALUE=a=b=c",
"HASH=value # kept literal",
"UNICODE=héllo 世界 café",
@@ -54,6 +59,11 @@ func TestParseSecretsFileEnv(t *testing.T) {
{Name: "DQ_CARRIAGE", EnvName: "DQ_CARRIAGE", Value: "a\rb"},
{Name: "DQ_UNKNOWN", EnvName: "DQ_UNKNOWN", Value: `x\zy`},
{Name: "SQUOTED", EnvName: "SQUOTED", Value: `literal \n no escape`},
{Name: "SQ_TRAILING_WS", EnvName: "SQ_TRAILING_WS", Value: "abc"},
{Name: "SQ_EMPTY", EnvName: "SQ_EMPTY", Value: ""},
{Name: "SQ_DOUBLE_INSIDE", EnvName: "SQ_DOUBLE_INSIDE", Value: `a"b`},
{Name: "SQ_HASH", EnvName: "SQ_HASH", Value: "#not a comment"},
{Name: "SQ_UNICODE", EnvName: "SQ_UNICODE", Value: "héllo 世界"},
{Name: "EQ_IN_VALUE", EnvName: "EQ_IN_VALUE", Value: "a=b=c"},
{Name: "HASH", EnvName: "HASH", Value: "value # kept literal"},
{Name: "UNICODE", EnvName: "UNICODE", Value: "héllo 世界 café"},
@@ -92,7 +102,15 @@ func TestParseSecretsFileEnvErrors(t *testing.T) {
{name: "UnterminatedDouble", content: `KEY="oops`, errMsgs: []string{"missing closing double quote"}},
{name: "EscapedDoubleQuoteNotClosing", content: `KEY="oops\"`, errMsgs: []string{"missing closing double quote"}},
{name: "DoubleQuoteTrailingData", content: `KEY="ok" # comment`, errMsgs: []string{"unexpected data after closing double quote"}},
{name: "UnterminatedSingle", content: `KEY='oops`, errMsgs: []string{"missing closing single quote"}},
{name: "UnterminatedSingle", content: `KEY='oops`, errMsgs: []string{"missing closing single quote", "line 1"}},
{name: "SingleQuoteOnly", content: `KEY='`, errMsgs: []string{"missing closing single quote", "line 1"}},
{name: "SingleQuoteTrailingComment", content: `KEY='abc' # 'note'`, errMsgs: []string{"unexpected data after closing single quote", "line 1"}},
{name: "SingleQuoteEmbeddedQuote", content: `KEY='a'b'`, errMsgs: []string{"unexpected data after closing single quote", "line 1"}},
{name: "SingleQuotePairAfterClose", content: `KEY='a' 'b'`, errMsgs: []string{"unexpected data after closing single quote", "line 1"}},
{name: "SingleQuoteTrailingData", content: `KEY='abc' extra`, errMsgs: []string{"unexpected data after closing single quote", "line 1"}},
{name: "SingleQuoteAdjacentQuoted", content: `KEY='it''s'`, errMsgs: []string{"unexpected data after closing single quote", "line 1"}},
{name: "SingleQuoteTrailingDataNoSpace", content: `KEY=''extra`, errMsgs: []string{"unexpected data after closing single quote", "line 1"}},
{name: "SingleQuoteErrorOnLaterLine", content: "OK=fine\nKEY='abc' extra", errMsgs: []string{"unexpected data after closing single quote", "line 2"}},
{name: "DuplicateKey", content: "DUP=a\nDUP=b", errMsgs: []string{"duplicate key", "line 2"}},
}
for _, tt := range tests {
@@ -254,6 +272,7 @@ func FuzzParseSecretsFile(f *testing.F) {
f.Add("env", "NOEQUALS")
f.Add("env", "=value")
f.Add("env", `KEY="ok" # trailing`)
f.Add("env", `KEY='ok' extra`)
f.Add("env", "DUP=a\nDUP=b")
// json - valid
f.Add("json", `{"A":"1","B":"two"}`)