feat: complete 7/7 bash security checks

Added:
- Standalone semicolon check: blocks ; outside quotes (use && instead)
- Sensitive redirection check: blocks > to /etc/passwd, .bashrc,
  .ssh/authorized_keys, .env, etc.

Now all 7 security checks are active:
1. Incomplete commands, 2. Control characters, 3. Newline injection,
4. Command substitution, 5. Dangerous variables, 6. Semicolons,
7. Sensitive redirections
This commit is contained in:
2026-04-03 17:56:01 +02:00
parent 34108075d2
commit 279a8d43bd

View File

@@ -55,9 +55,12 @@ func ValidateCommand(cmd string) *SecurityViolation {
if v := checkDangerousVars(cmd); v != nil {
return v
}
// Metacharacters and redirection are warnings, not blocks in M1.
// The LLM legitimately uses pipes and redirects.
// Full compound command parsing (mvdan.cc/sh) comes in M5.
if v := checkStandaloneSemicolon(cmd); v != nil {
return v
}
if v := checkSensitiveRedirection(cmd); v != nil {
return v
}
return nil
}
@@ -181,6 +184,60 @@ func checkCmdSubstitution(cmd string) *SecurityViolation {
return nil
}
// checkStandaloneSemicolon blocks standalone semicolons used to chain commands.
// Pipes (|) and && / || are allowed (handled by compound command parsing).
func checkStandaloneSemicolon(cmd string) *SecurityViolation {
inSingle := false
inDouble := false
escaped := false
for _, r := range cmd {
if escaped {
escaped = false
continue
}
if r == '\\' && !inSingle {
escaped = true
continue
}
if r == '\'' && !inDouble {
inSingle = !inSingle
continue
}
if r == '"' && !inSingle {
inDouble = !inDouble
continue
}
if !inSingle && !inDouble && r == ';' {
return &SecurityViolation{
Check: CheckMetacharacters,
Message: "standalone semicolon (use && for chaining)",
}
}
}
return nil
}
// checkSensitiveRedirection blocks output redirection to sensitive paths.
func checkSensitiveRedirection(cmd string) *SecurityViolation {
sensitiveTargets := []string{
"/etc/passwd", "/etc/shadow", "/etc/sudoers",
".bashrc", ".zshrc", ".profile", ".bash_profile",
".ssh/authorized_keys", ".ssh/config",
".env",
}
for _, target := range sensitiveTargets {
if strings.Contains(cmd, "> "+target) || strings.Contains(cmd, ">>"+target) {
return &SecurityViolation{
Check: CheckRedirection,
Message: fmt.Sprintf("redirection to sensitive path: %s", target),
}
}
}
return nil
}
// checkDangerousVars blocks attempts to manipulate IFS or PATH.
func checkDangerousVars(cmd string) *SecurityViolation {
upper := strings.ToUpper(cmd)