Skip to content

fix(runtime): tighten run_command metachar reject to ContainsAny set#67

Merged
tzone85 merged 1 commit into
mainfrom
fix/cmd-allowlist-metachar-coverage
Jun 11, 2026
Merged

fix(runtime): tighten run_command metachar reject to ContainsAny set#67
tzone85 merged 1 commit into
mainfrom
fix/cmd-allowlist-metachar-coverage

Conversation

@tzone85

@tzone85 tzone85 commented Jun 11, 2026

Copy link
Copy Markdown
Owner

Summary

The ad-hoc substring loop in isCommandAllowed missed several dangerous characters:

  • bare tab (\t) — only \t& was rejected; \t alone passed the metachar check then failed the prefix match. Inconsistent rejection-as-denial.
  • NUL byte (\x00) — "go build\x00evil" slipped past the metachar list.
  • bare > and <$ / ``` caught command substitution but redirection had no entry.
  • \\ (backslash) — could escape characters past the prefix match.

Changes

Replace the loop with one strings.ContainsAny(command, ";&|$\<>\n\r\t\x00\\"). Faster (single linear scan), and closes the gaps. ; & | $ `already covered chaining + expansion + command substitution;> <add redirection;\n \r \t \x00cover control chars;\` catches escapes.

Test plan

  • 7 new table-driven cases in TestIsCommandAllowed_RejectsExtendedMetachars: bare tab, NUL, redirection out/in, newline, carriage return, backslash, bare $.
  • go build ./..., go vet ./..., go test ./... -count=1 -timeout 240s all green locally.

Audit traceability

Security finding SEC-M3 (2026-06-11 sweep).

The ad-hoc substring loop in isCommandAllowed missed:
- bare tab (\t) — only "\t&" was rejected; a tab on its own (e.g.
  "go\ttest ./...") passed the metachar check then failed the prefix
  match, but the inconsistency was a footgun;
- NUL byte (\x00) — "go build\x00evil" passed metachar and failed
  prefix, again silent rejection-as-denial rather than rejection-as-
  intent;
- bare > and < — only "$" / "$(" caught command substitution but
  redirection had no entry;
- \ (backslash) — could be used to escape characters.

Replace the substring loop with one strings.ContainsAny over the
canonical forbidden set: `;&|$`<>\n\r\t\x00\\`. Faster (single linear
scan), and closes the gaps. ; & | $ ` already covered chaining +
expansion + command substitution; > < add redirection; \n \r \t \x00
cover control chars; \\ catches escapes.

7 new table-driven cases in TestIsCommandAllowed_RejectsExtendedMetachars
cover bare tab, NUL, > / <, newline, carriage return, backslash, and
bare $.

Surfaced by the 2026-06-11 security audit (SEC-M3).
@tzone85 tzone85 merged commit 333c7b8 into main Jun 11, 2026
9 of 10 checks passed
@tzone85 tzone85 deleted the fix/cmd-allowlist-metachar-coverage branch June 11, 2026 11:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant