Pular para conteúdo

AUR-486 -- Go, C#, PowerShell and bash in their idiomatic spelling

Outcome

Command injection is found in the idiomatic spelling of Go, C#, PowerShell and bash, and SQL injection built by shell interpolation is found too. This is the remainder of AUR-481, which delivered Rust and was reduced in scope to what it proved.

Measured finding (2026-08-26)

The same four defects (secret, sql, cmd, xss) were planted in eight languages and the deterministic security pass was run over them. The command-injection cell was missed in all four languages this card covers:

Language secret sql cmd
Go sim sim NAO
C# sim sim NAO
PowerShell sim sim NAO
Bash sim NAO NAO

Causes, read from the pattern at the time:

  • Go: exec.Command("sh", "-c", "ping "+host). The branch was exec[lv]p?e?, which needs an l or v right after exec (the C spellings execl/execv/execve); the dot in exec.Command also breaks the sequence. Go is this project's own primary language.
  • C#: Process.Start(...) was not in the pattern at all.
  • PowerShell: Invoke-Expression was not in the pattern at all.
  • Bash: eval "ping $1" and sh -c "ping $1" were not in the pattern; and SQL assembled by shell interpolation (query="... WHERE name = '$1'") has no + for the SQL rule to anchor on.

The fix

internal/review/rules/security.yml gains branches, all of them deterministic (no LLM, no AST):

  • security/command-injection:
  • Go: exec.Command("sh"|"bash"|"cmd"|"powershell", "-c"|"/c"|"-Command", ...). The safe argv form exec.Command("ping", host) names no shell and is excluded structurally.
  • C#: Process.Start(...) reaching a shell by quote-then-+ concatenation, or a literal cmd/powershell file name. Process.Start("ping", host) and a ProcessStartInfo with separated ArgumentList are excluded.
  • PowerShell: Invoke-Expression / iex.
  • bash: sh -c / bash -c, and eval anchored at the start of the line (^\s*eval) so the word inside a # comment or a string literal does not fire.
  • security/sql-injection: a SQL verb followed later by a quote immediately before a shell sigil ('$1', "$name", '${ID}'). The quote/$ adjacency is what separates shell interpolation from a database placeholder ("SELECT ... = $1" in Rust, ? in Python), so AUR-481's parametrized-query AC-002 stays green.

Non-goals

  • No existing pattern is loosened. False positives in a merge gate teach the team to disable the gate (AUR-462, AUR-466).
  • No semantic analysis or AST: the pass stays a deterministic matcher over added lines.
  • No full language parity is promised; what is not covered is declared here.

Acceptance

  • AC-001: over a fixture with Go exec.Command(..., "sh","-c", ...), C# Process.Start, PowerShell Invoke-Expression, bash eval/sh -c and SQL-by-shell, the pass reports each with the correct rule citation.
  • AC-002: the Go argv form, C# separated arguments, eval inside a comment or a string literal, and parametrized SQL produce no finding.
  • AC-003 (regression): the Rust (AUR-481), Node (AUR-462), Python (AUR-435) and C++ (AUR-442/AUR-461) shapes still produce exactly the findings they produced before. C++ is asserted at the unit boundary: the system("ping " + host) true positive still fires and its benign neighbour system("ping example.com") does not. The Rust/Node/Python fixtures are additionally counted at the CLI boundary, scoped to the security section so the separate deterministic-analysis section cannot skew the count.

Selectors: unit tests/unit/AUR-486.go::TestAUR486; integration tests/integration/AUR-486.go::IntegrationAUR486; acceptance tests/acceptance/AUR-486.sh; e2e tests/e2e/AUR-486.sh::E2EAUR486.

Mutations

  • MUT-001: remove the Go branch (exec\.Command -> non-matching). The Go command-injection finding must vanish while C#/bash stay; AC-001 RED.
  • MUT-002: widen the Go branch to a bare exec\.Command\s*\(. The safe argv form must wrongly match; AC-002 RED.

Runtime note

The sealed profile bootstrap-readonly-v1 has bash and Go but no git, and this card's paths do not include tests/fixtures. The e2e script and the integration test therefore write a bare repository in the loose-object format tests/fixtures/repos/git-demo/build-fixture.sh emits; nothing shells out to git.

Known limitation, accepted deliberately

The bash eval anchor is the start of the line, so an eval reached through a command substitution or a wrapper on the same line (foo; eval "...") is not matched. This is the trade that keeps eval in a comment or inside a string literal from firing, which is this card's AC-002.