fix(bugseti): sanitize shell metacharacters in seeder env vars
SanitizeEnv() only removed control characters but not shell metacharacters. A malicious repo name could execute arbitrary commands via environment variable injection (e.g. backticks, $(), semicolons). Add stripShellMeta() to strip backticks, dollar signs, semicolons, pipes, ampersands, and other shell-significant characters from values passed to the bash seed script environment. Fixes #59 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
903fd79454
commit
8852dff8de
2 changed files with 64 additions and 2 deletions
|
|
@ -106,7 +106,23 @@ func loadEthicsGuard(ctx context.Context, rootHint string) *EthicsGuard {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *EthicsGuard) SanitizeEnv(value string) string {
|
func (g *EthicsGuard) SanitizeEnv(value string) string {
|
||||||
return sanitizeInline(value, maxEnvRunes)
|
return stripShellMeta(sanitizeInline(value, maxEnvRunes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// stripShellMeta removes shell metacharacters that could allow command
|
||||||
|
// injection when a value is interpolated inside a shell environment variable.
|
||||||
|
func stripShellMeta(s string) string {
|
||||||
|
var b strings.Builder
|
||||||
|
b.Grow(len(s))
|
||||||
|
for _, r := range s {
|
||||||
|
switch r {
|
||||||
|
case '`', '$', ';', '|', '&', '(', ')', '{', '}', '<', '>', '!', '\\', '\'', '"', '\n', '\r':
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
b.WriteRune(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *EthicsGuard) SanitizeTitle(value string) string {
|
func (g *EthicsGuard) SanitizeTitle(value string) string {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package bugseti
|
package bugseti
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
func TestSanitizeInline_Good(t *testing.T) {
|
func TestSanitizeInline_Good(t *testing.T) {
|
||||||
input := "Hello world"
|
input := "Hello world"
|
||||||
|
|
@ -26,3 +28,47 @@ func TestSanitizeMultiline_Ugly(t *testing.T) {
|
||||||
t.Fatalf("expected %q, got %q", "ab\ncd", output)
|
t.Fatalf("expected %q, got %q", "ab\ncd", output)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSanitizeEnv_Good(t *testing.T) {
|
||||||
|
g := &EthicsGuard{}
|
||||||
|
input := "owner/repo-name"
|
||||||
|
output := g.SanitizeEnv(input)
|
||||||
|
if output != input {
|
||||||
|
t.Fatalf("expected %q, got %q", input, output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSanitizeEnv_Bad(t *testing.T) {
|
||||||
|
g := &EthicsGuard{}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"backtick", "owner/repo`whoami`", "owner/repowhoami"},
|
||||||
|
{"dollar", "owner/repo$(id)", "owner/repoid"},
|
||||||
|
{"semicolon", "owner/repo;rm -rf /", "owner/reporm -rf /"},
|
||||||
|
{"pipe", "owner/repo|cat /etc/passwd", "owner/repocat /etc/passwd"},
|
||||||
|
{"ampersand", "owner/repo&&echo pwned", "owner/repoecho pwned"},
|
||||||
|
{"mixed", "`$;|&(){}<>!\\'\"\n\r", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
output := g.SanitizeEnv(tc.input)
|
||||||
|
if output != tc.expected {
|
||||||
|
t.Fatalf("expected %q, got %q", tc.expected, output)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStripShellMeta_Ugly(t *testing.T) {
|
||||||
|
// All metacharacters should be stripped, leaving empty string
|
||||||
|
input := "`$;|&(){}<>!\\'\""
|
||||||
|
output := stripShellMeta(input)
|
||||||
|
if output != "" {
|
||||||
|
t.Fatalf("expected empty string, got %q", output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue