Pular para conteúdo

AUR-481: Rust idiomatic security coverage

Documento histórico do board. Pode descrever componentes removidos; para uso atual, consulte o guia do produto.

The measurement

The 2026-08-26 measurement planted the same four defects (hardcoded secret, SQL injection, command injection, XSS) in eight languages and ran the deterministic --seguranca pass. Rust was the worst cell in the table: zero of three applicable rules fired, for a language the tool has a native documentation extractor for (AUR-427) and no external toolchain dependency. Three causes, each in security.yml's existing patterns:

  1. security/hardcoded-secret never matched const ACCESS_KEY: &str = "AKIA..."; -- the pattern expects a quote immediately after :=/:/=, and Rust's typed declarations put a TYPE (&str, String) between the identifier and =.
  2. security/sql-injection never matched conn.query(&("SELECT ... '".to_owned() + name)); -- the pattern requires ["']\s*\+ and Rust's idiomatic concatenation puts a method call (.to_owned()/.to_string()) between the closing quote and the +. Rust's more common shape, format!("SELECT ...{}", name), has no + at all.
  3. security/command-injection never matched Command::new("sh").arg("-c").arg("ping ".to_owned() + h).spawn(); -- none of the existing branches (exec[lv]p?e?, exec(Sync)?, spawn(...shell:true)) recognize Command::new.

What this card delivers

internal/review/rules/security.yml:

  • security/hardcoded-secret gains two new alternatives: a typed const/static/let declaration (IDENT: &str|String = "value") assigned a quoted literal directly, and the String::from("value") wrapper (type annotation optional there). Both reuse the same case-insensitive keyword group and the same digit+letter VALUE class the existing quoted branch uses -- no weakening, same false-positive guard.
  • security/sql-injection gains two new alternatives: the .to_owned()/.to_string()-then-+ concatenation shape, and a format!(...) call whose format string carries a SQL verb and a {} placeholder followed by a comma-separated identifier argument.
  • security/command-injection gains one new alternative: Command::new("sh"|"bash") plus a .arg("-c") call, flagged on that combination alone (the same "dangerous regardless of what follows" reasoning the existing spawn(...shell:true) branch already uses), regardless of whether the later .arg(...) carries .to_owned() + h concatenation or a format!(...) call.

Both extensions were probed against every positive and negative shape (see tests/unit/AUR-481.go) with a throwaway regex harness before touching the catalog, then proven against the real loader (internal/review.SecurityScan) and the real CLI (aurumcode review --seguranca).

Rust command-injection: first reverted, now restored

A branch analogous to the existing spawn(...shell:true) one -- Command::new("sh"|"bash") plus a .arg("-c") call, flagged on that combination alone regardless of what a later .arg(...) carries (.to_owned() + h concatenation or a format!(...) call) -- was written and proven correct (matches both vulnerable forms, excludes Command::new("ping").arg(host)). It was first reverted: extending security/command-injection's pattern broke tests/acceptance/AUR-462.sh's own MUT-001, which hardcoded that pattern's exact bytes, verbatim, as a literal grep-and-rewrite anchor (AUR-462.sh:266), and AUR-462.sh was not in this card's paths at the time. Measured directly: tests/acceptance/AUR-462.sh AC-001 produced AUR-462/AC-001/MUT-001/anchor-not-found, exit 1.

tests/acceptance/AUR-462.sh was then added to this card's paths (the coordinator's own call, after independently verifying the anchor-fragility claim), with instructions to reanchor MUT-001 stably rather than weaken its assertion. It now arms on the rule's - id: security/command-injection line -- a structurally stable, content-independent marker -- and rewrites the next pattern: line wholesale to an always-empty (pattern: ''), always-non-matching value, rather than string-replacing inside the pattern's own text. An awk END block makes the mutation impossible to silently no-op: if the anchor is never armed-and-matched, awk exits 1, mv never runs, and an explicit follow-up grep catches the untouched file and fails loudly. This is immune to any future card widening the pattern's text, which is exactly the failure this card hit (and the same failure mode AUR-448's own MUT-001 hit the same day, when AUR-467 added a second print of the same shape it anchored on). With that fix in place, the Rust command-injection branch is restored and shipped.

Go, C#, PowerShell, and bash command-injection coverage (also named by this card's own outcome measurement) remain out for the ordinary reason: this card's declared priority, set by the dispatcher, is Rust delivered completely and proven, over partial coverage of every language. Nothing in security.yml was touched for these four languages.

XSS was not in scope for any of the eight languages this card targeted (the outcome table shows XSS covered only for JavaScript, already fixed by AUR-462).

Fixture

tests/fixtures/review/vuln/rust-secret-sql-injection/ is a bare, loose-object git repository built by tests/fixtures/repos/git-demo/build-fixture.sh from that directory's history.spec (the sealed acceptance profile carries no git binary, so every fixture in this repository is committed pre-built rather than generated at test time). Its second commit adds src/main.rs carrying every AC-001 true-positive shape (typed const secret, String::from secret, .to_owned() SQL concat, format! SQL, Command::new("sh") with both a .to_owned() and a format! argument) beside every AC-002 safe form this card names (a numeric constant, a digit-free string constant, a $1-parametrized query, an argv-form Command with no shell).

Validation

  • Unit: tests/unit/AUR-481.go::TestAUR481 -- package-boundary proof over synthetic diffs, including every AC-001 positive, every AC-002 negative, three AC-003 regression shapes, and a fixture-line-for-line end-to-end/determinism check.
  • Integration: tests/integration/AUR-481.go::IntegrationAUR481 -- CLI-boundary proof: builds the real binary, runs it against the committed Rust fixture and the pre-existing Python/secret/Node fixtures.
  • E2E: tests/e2e/AUR-481.sh::E2EAUR481 -- full-process proof, including the --fail-on high gate closing on the six real Rust findings.
  • Acceptance: tests/acceptance/AUR-481.sh -- runs all three selectors plus two mutations: MUT-001 removes both new SQL alternatives and proves the two Rust SQL true positives vanish; MUT-002 widens the format! branch past its SQL-verb requirement and proves the AC-002 safe form (format!("user {}", name)) is wrongly matched.
  • Regression: tests/acceptance/AUR-462.sh AC-001 and tests/acceptance/AUR-466.sh AC-001 were run before and after every edit in this card and both stayed exit 0.