feat(config): single-command bash whitelist + env-var leak guard#618
Open
edenreich wants to merge 15 commits into
Open
feat(config): single-command bash whitelist + env-var leak guard#618edenreich wants to merge 15 commits into
edenreich wants to merge 15 commits into
Conversation
Add INFER_TOOLS_BASH_WHITELIST_COMMANDS_APPEND and INFER_TOOLS_BASH_WHITELIST_PATTERNS_APPEND. Unlike the existing INFER_TOOLS_BASH_WHITELIST_COMMANDS / _PATTERNS vars (which replace the resolved list), the _APPEND siblings merge onto the fully-resolved default/config/replace list, so callers can add per-repo entries without clobbering the built-in default. Applied after ReadInConfig so the merge sees the final base. The CLI default stays the single source of truth for the whitelist; consumers (infer-action, the org reusable workflow) are pass-throughs that only forward these vars. Also extract the shared comma/newline list parsing into parseDelimitedList, removing the duplicated loop across the five INFER_* list env vars and keeping initConfig under the gocyclo ceiling.
… dir
config.DefaultLogsPath is relative (".infer/logs"), so every test that
calls initConfig() (which calls logger.Init) created a real
cmd/.infer/logs/ directory in the working tree. Add a package TestMain
that points INFER_LOGGING_DIR at a throwaway temp dir for the run, so
the logger writes there instead. No test asserts on logging.dir, and
the tests that clear INFER_* env vars already chdir into their own
temp dir, so the override is safe and self-cleaning.
9241cb5 to
f382e4a
Compare
Expose the four bash-whitelist env vars as persistent flags: --tools-bash-whitelist-commands / --tools-bash-whitelist-patterns and their -append siblings. For each list the replace source overrides the resolved value and the append source merges onto it; the env var takes precedence over the matching flag, consistent with the documented flags < env layering. Consolidate all whitelist resolution into applyBashWhitelistOverrides (run after ReadInConfig so appends see config-file values), replacing the inline env-only blocks in initConfig.
392c5f1 to
629edf5
Compare
Tighten what the Bash tool auto-approves so it cannot exfiltrate secrets or smuggle an arbitrary tail: - Only a single command is auto-approved; any top-level operator (|, &&, ||, ;, &) falls through to approval (operators inside quotes still count as one command). Closes the "echo x | xargs rm" prefix hole. - $VAR may be used in commands, but expanding one in a command that prints (echo/printf) or publishes (gh issue/pr create|comment|edit) is rejected, so the agent cannot echo or post an environment variable's value. - git push is never auto-approved by the default list. - Command substitution, file-write redirects and dangerous find actions stay blocked; each rejection now returns an actionable hint to the model. Behavior change: commands that previously auto-approved (compound/piped commands, echo $VAR) now require approval. CI callers can re-add specific safe commands via INFER_TOOLS_BASH_WHITELIST_COMMANDS_APPEND.
62a7a3d to
b8a0fc4
Compare
edenreich
commented
Jun 7, 2026
Co-authored-by: Eden Reich <eden.reich@gmail.com>
edenreich
commented
Jun 7, 2026
edenreich
commented
Jun 7, 2026
edenreich
commented
Jun 7, 2026
edenreich
commented
Jun 7, 2026
Co-authored-by: Eden Reich <eden.reich@gmail.com>
Rework PR #618's bash auto-approval into a single config-driven, per-mode allowed-list. A bash command auto-runs only if it matches the allowed-list for the active agent mode; anything unmatched is denied - an approval prompt in chat, a hard reject with an actionable hint in headless agent mode. There is no separate deny list. - Config: tools.bash.mode.{all,plan,standard,auto}.allow. mode.all is the every-mode baseline, unioned with the active mode's own list (bashAllowFor). mode.auto defaults to ".*" (unrestricted) so headless `infer agent` commits and pushes without approval; mode.plan is empty. - Matching is full-command (\A(?:entry)\z): a bare token matches only itself, entries opt into arguments via ( .*)?. The ".*" sentinel means unrestricted and skips the clean-command guard. - Clean-command guard (every non-".*" mode) rejects command substitution, multi-command chains/pipelines, surviving file-write redirects, dangerous find actions, and env-var leaks (echo/printf/gh publish expanding $VAR). - One matcher, IsBashCommandAllowed(command, mode), is shared by the Bash tool gate, the approval policy, and agent auto-approval. Mode flows via domain.WithAgentMode context + AgentMode.AllowedlistKey(). - The per-mode allowed-list is injected into the system prompt and rebuilt each turn so a chat mode toggle re-injects it. Renames the bash auto-approval vocabulary from "whitelist" to "allowed" across identifiers, messages, comments, and docs. tools.bash.whitelist.commands is removed; configure tools.bash.mode.<mode>.allow instead. The bespoke INFER_TOOLS_BASH_WHITELIST_COMMANDS[_APPEND] env vars and --tools-bash-whitelist-commands* flags are removed - the allowed-list is config-file driven only.
… statements should be lowercase
- drop the raw `gh api` GET wildcard from defaults; the baseline now enumerates explicit non-destructive gh subcommands (incl. gh project reads), with gh api opt-in per repo. - move read-only `gh project (list|view|item-list|field-list)` into the mode.all baseline; gh project writes (item-add/item-edit) are no longer auto-approved and fall through to approval. - stop hardcoding the gh api regex in tests; allow-patterns come from DefaultConfig only. - add `prompts.agent.system_prompt_auto`: a destructive-action policy for auto-accept mode (confirm/avoid irreversible ops), wired in getSystemPromptForMode. - buildBashAllowInfo tells the model not to retry a denied command but to stop and ask the user or use an allowed alternative.
Headless `infer agent` previously defaulted to auto mode (`.*`), so CI, heartbeat, and any run without --require-approval could execute ANY command unattended. This makes the restricted path the default. - add tools.safety.approval_behaviour (prompt|ipc|block, default prompt) deciding HOW a needed approval is delivered, alongside the existing require_approval which decides WHETHER. config.ResolveApprovalDelivery is the shared resolver; Config.Validate rejects unknown values. - headless executor now runs in standard mode and routes approval- requiring tools per behaviour: IPC when a broker is attached (--require-approval, e.g. the channel manager), else blocked with a reason. No more `.*` default; full autonomy is now an explicit opt-in. - chat honours block (rejects without prompting); prompt/ipc still prompt. - docs, CLAUDE.md, and the seeded config document the field and the controlled-autonomy CI profile.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Hardens what the Bash tool auto-approves (the whitelist consulted by the Bash tool, the approval policy, and agent auto-approval) and completes the unified commands model.
Auto-approval policy (
config/bash_whitelist.go)|,&&,||,;,&, newline) drops the whole command to approval; operators inside quotes (jq'… | …',--title "a && b") still count as one command. Closes theecho x | xargs rmprefix hole.$VARmay be used in commands, but expanding one in a command that prints (echo/printf) or publishes (gh issue/pr create|comment|edit) is rejected, so the agent can't echo or post a secret's value. A single-quoted or backslash-escaped$stays literal.git pushis never in the default list, so it always requires approval.findactions are rejected. Each rejection now returns an actionable hint to the model.Unified list + flags (earlier commits in this PR)
commandsregex list (replaces the old commands+patterns split),INFER_TOOLS_BASH_WHITELIST_COMMANDS[_APPEND], and matching persistent flags.Behavior change
Commands that previously auto-approved (compound/piped commands,
echo $VAR) now require approval. CI callers can re-add specific safe commands viaINFER_TOOLS_BASH_WHITELIST_COMMANDS_APPEND(follow-up:infer-actionwill appendgit commit/git push).Verification
go test ./...,golangci-lint,gofmt, andmarkdownlintall pass. Also fixed pre-existing stale test configs left by the unified-model commit.