forked from molecule-ai/molecule-core
fix(security): sanitize body.Name before YAML interpolation in generateDefaultConfig
A crafted workspace name containing a newline (e.g. "x\nmodel: evil") could inject arbitrary YAML keys into the auto-generated config.yaml. Strip \n and \r from the name before interpolation. YAML key injection requires a newline to start a new mapping entry; other characters such as `:` are safe in unquoted scalar values. Adds TestGenerateDefaultConfig_YAMLInjection with three adversarial inputs: bare \n injection, CRLF injection, and multi-key injection. Closes #221 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
519d478ea2
commit
afea61ae52
@ -54,6 +54,13 @@ func generateDefaultConfig(name string, files map[string]string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// Sanitize: strip newlines and carriage returns to prevent YAML key
|
||||
// injection. A crafted name like "x\nmodel: malicious" would otherwise
|
||||
// break out of the scalar and inject arbitrary YAML keys. Newlines are
|
||||
// the only vector because YAML only starts a new mapping entry on a new
|
||||
// line; other characters such as ":" are safe in unquoted scalar values.
|
||||
name = strings.NewReplacer("\n", "", "\r", "").Replace(name)
|
||||
|
||||
var cfg strings.Builder
|
||||
cfg.WriteString("name: " + name + "\n")
|
||||
cfg.WriteString("description: Imported agent\n")
|
||||
|
||||
@ -94,6 +94,44 @@ func TestGenerateDefaultConfig_Empty(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateDefaultConfig_YAMLInjection verifies that a crafted workspace
|
||||
// name containing a newline cannot inject arbitrary YAML keys into the
|
||||
// generated config. This is the regression test for issue #221.
|
||||
func TestGenerateDefaultConfig_YAMLInjection(t *testing.T) {
|
||||
adversarialCases := []struct {
|
||||
desc string
|
||||
name string
|
||||
bannedLines []string // lines that must NOT appear in the output
|
||||
}{
|
||||
{
|
||||
desc: "newline followed by new key",
|
||||
name: "legit-agent\nmodel: malicious:model",
|
||||
bannedLines: []string{"model: malicious:model"},
|
||||
},
|
||||
{
|
||||
desc: "CRLF injection",
|
||||
name: "legit-agent\r\nmalicious_key: value",
|
||||
bannedLines: []string{"malicious_key: value"},
|
||||
},
|
||||
{
|
||||
desc: "multiple newlines with multiple keys",
|
||||
name: "x\nfoo: bar\nbaz: qux",
|
||||
bannedLines: []string{"foo: bar", "baz: qux"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range adversarialCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
cfg := generateDefaultConfig(tc.name, map[string]string{})
|
||||
for _, banned := range tc.bannedLines {
|
||||
if strings.Contains(cfg, banned) {
|
||||
t.Errorf("YAML injection: output contains injected line %q\nfull config:\n%s", banned, cfg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== writeFiles ====================
|
||||
|
||||
func TestWriteFiles_Success(t *testing.T) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user