Skip to content

fix(coding-agent): allow quoted metacharacters in bash patterns - #7553

Merged
can1357 merged 4 commits into
mainfrom
farm/b07f5593/fix-quoted-bash-approval-patterns
Aug 3, 2026
Merged

fix(coding-agent): allow quoted metacharacters in bash patterns#7553
can1357 merged 4 commits into
mainfrom
farm/b07f5593/fix-quoted-bash-approval-patterns

Conversation

@roboomp

@roboomp roboomp commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator

Repro

With bash.patterns: [{ match: "cargo *", approval: "allow" }], evaluate cargo bench --manifest-path layers/layer3/Cargo.toml --bench standardized_criterion -- --full '^layer3/write/file-wal/batch-(10|1000|10000)$'; current main returns the Bash approval decision "exec" instead of { tier: "write", policy: "allow" }.

Cause

BashTool.approval() in packages/coding-agent/src/tools/bash.ts called BASH_APPROVAL_SHELL_CONTROL_RE against the raw command, so shell metacharacters inside single-quoted literal arguments were indistinguishable from executable compound-command syntax.

Fix

  • Replace the raw regex guard with quote-aware shell-control scanning while preserving unquoted controls, double-quoted expansion, command substitution, redirection, and compound-command protections.
  • Cover the reported Cargo benchmark regex filter through the observable BashTool.approval() decision.
  • Record the fix under the coding-agent Unreleased changelog.

Verification

bun test packages/coding-agent/test/tools/approval.test.ts passes 33 tests; the exact reported command now returns {"tier":"write","policy":"allow"}. Skipped the pre-publish gate after verifying on a clean origin/main checkout that bun run fix rewrites unrelated crates/pi-shell/src/shell.rs and bun check fails identically in unrelated omptype migration paths (openai-responses-server-schema.ts and task/types.ts). Fixes #7552

- Replaced the raw character guard with quote-aware scanning while retaining command substitution and unquoted shell-control protections.
- Added regression coverage for the reported Cargo benchmark filter.

Fixes #7552

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 54b2e38564

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +79 to +81
if (quote === "'") {
if (ch === "'") quote = undefined;
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Block shell-interpreted quoted payloads from allow rules

When a user has a common allow rule such as bash.patterns: [{ match: "git *", approval: "allow" }], this branch treats all metacharacters inside single quotes as harmless, so git -c alias.x='!touch /tmp/pwn; printf ok' x now matches the allow rule and runs without approval. I confirmed the Git behavior with git -c alias.omp-review-test='!printf first; printf second' ...; ! aliases run through a shell, so the semicolon becomes executable compound syntax rather than literal argument text. The raw guard in the parent blocked this case, and it breaks the repo-documented allow contract that patterns cannot approve compound commands (docs/bash-tool-runtime.md:36).

Useful? React with 👍 / 👎.

- Kept quoted shell controls prompt-gated when code-evaluation options can reinterpret the argument.
- Covered the reported git inline shell-alias bypass while retaining Cargo regex approval.

Fixes #7552
@roboomp

roboomp commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author
  • Kept single-quoted shell controls prompt-gated when -c/-e/--command/--eval options can reinterpret the argument, covering the reported Git inline shell alias.
  • Added the exact git -c alias.x='!touch /tmp/pwn; printf ok' x regression case while retaining auto-approval for the Cargo benchmark regex.
  • Verified the focused approval suite (33 pass), with the Git payload resolving to exec and the Cargo command resolving to { tier: "write", policy: "allow" }.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9bcd31fcd2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}
// Expansion remains active inside double quotes; other control-looking
// characters are literal argument text.
if (ch === "`" || ch === "$") return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Treat double-quoted -c payloads as executable

When the reinterpreted payload is double-quoted rather than single-quoted, this branch never sets hasQuotedShellControl, so the -c/-e guard at the end is skipped. With a common allow rule like bash.patterns: [{ match: "git *", approval: "allow" }], git -c alias.x="!touch /tmp/pwn; printf ok" x now matches as a simple allowed command even though Git runs ! aliases through a shell; I verified this exact double-quoted form executes the semicolon-separated payload. This is the remaining gap after the single-quoted alias case was covered.

Useful? React with 👍 / 👎.

- Treated double-quoted shell-control chars like single-quoted ones so a -c/-e reinterpretation option still gates them.
- Covered the double-quoted git inline shell-alias bypass.

Fixes #7552
@roboomp

roboomp commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author
  • Treated double-quoted shell-control characters the same as single-quoted ones, so a -c/-e/--command/--eval reinterpretation option still gates them (backtick/$ expansion inside double quotes stays an immediate block).
  • Added the double-quoted git -c alias.x="!touch /tmp/pwn; printf ok" x regression case.
  • Verified the focused approval suite (33 pass); both quote styles now resolve to exec, Cargo regex still { tier: "write", policy: "allow" }.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b621a9a637

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +89 to +91
if (ch === "\\") {
i++;
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Block escaped shell controls in reinterpreted args

When a user has a common allow rule like bash.patterns: [{ match: "git *", approval: "allow" }], this backslash skip treats escaped metacharacters as literal and leaves hasQuotedShellControl false. The outer shell removes those escapes before Git sees the value; I checked git --help, which documents -c <name>=<value>, and verified git -c alias.x=!touch\ /tmp/pwn\;\ printf\ ok x runs the semicolon-separated ! alias through a shell. Fresh evidence relative to the quoted-alias threads: the payload uses backslash escapes rather than quotes, so this new scanner now approves shell control that the parent raw guard rejected.

Useful? React with 👍 / 👎.

- Preserved escaped control characters for the downstream reinterpretation safety decision.
- Covered the backslash-escaped git inline shell-alias bypass.

Fixes #7552
@roboomp

roboomp commented Aug 3, 2026

Copy link
Copy Markdown
Collaborator Author
  • Recorded backslash-escaped shell-control characters for the downstream -c/-e/--command/--eval reinterpretation check instead of discarding them as permanently literal.
  • Added the exact git -c alias.x=!touch\ /tmp/pwn\;\ printf\ ok x regression case.
  • Verified the focused approval suite (33 pass); the escaped Git alias resolves to exec while the Cargo regex remains { tier: "write", policy: "allow" }.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98a65ffbdf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

"(": true,
")": true,
};
const BASH_APPROVAL_REINTERPRETED_ARGUMENT_RE = /(?:^|[ \t])(?:-[^-]*[ce]|--(?:command|eval))(?:[= \t]|$)/u;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Recognize attached -c/-e payloads before allowing

When users configure an allow rule such as bash.patterns: [{ match: "python3 *", approval: "allow" }], this regex only treats -c/-e as reinterpreting code when the option is followed by whitespace, =, or end-of-line. Several interpreters accept the payload attached to the same argv word (I checked python3 -c'...', ruby -e'...', and perl -e'...' execute), so a command like python3 -c'import os; os.system("rm -rf build")' records the quoted semicolon as reinterpretable but then returns false here and is auto-allowed, whereas the previous raw shell-control guard blocked it. Please treat attached short-option payloads as reinterpreted too, or parse argv before applying the allow rule.

Useful? React with 👍 / 👎.

@can1357
can1357 merged commit ad37de9 into main Aug 3, 2026
17 of 20 checks passed
teknium1 added a commit to NousResearch/hermes-agent that referenced this pull request Aug 17, 2026
… allowlist matching

command_allowlist glob rules (e.g. 'cargo *') rejected any command whose
quoted arguments contained shell metacharacters — a cargo benchmark
regex filter like '^layer3/write/(a|b)$' disqualified the whole command
even though those characters are literal to the shell.

_has_allowlist_shell_operator is now quote-aware:
- metacharacters inside single/double quotes or behind a backslash are
  treated as literal arguments;
- $ and backtick inside DOUBLE quotes still disqualify (expansion is
  active there);
- quoted/escaped control characters still disqualify when the command
  carries a -c/-e/--command/--eval-style option that hands the payload
  to another interpreter (sh -c '...', git -c alias.x='!...' x);
- unterminated quotes disqualify (shape can't be reasoned about).

Compound commands (unquoted ; & | < > backtick $( newline) are rejected
exactly as before. hermes_cli/approvals_suggest.derive_glob picks up the
same semantics via its existing import.
teknium1 added a commit to NousResearch/hermes-agent that referenced this pull request Aug 17, 2026
… allowlist matching

command_allowlist glob rules (e.g. 'cargo *') rejected any command whose
quoted arguments contained shell metacharacters — a cargo benchmark
regex filter like '^layer3/write/(a|b)$' disqualified the whole command
even though those characters are literal to the shell.

_has_allowlist_shell_operator is now quote-aware:
- metacharacters inside single/double quotes or behind a backslash are
  treated as literal arguments;
- $ and backtick inside DOUBLE quotes still disqualify (expansion is
  active there);
- quoted/escaped control characters still disqualify when the command
  carries a -c/-e/--command/--eval-style option that hands the payload
  to another interpreter (sh -c '...', git -c alias.x='!...' x);
- unterminated quotes disqualify (shape can't be reasoned about).

Compound commands (unquoted ; & | < > backtick $( newline) are rejected
exactly as before. hermes_cli/approvals_suggest.derive_glob picks up the
same semantics via its existing import.
mikeholownych added a commit to mikeholownych/charterforge that referenced this pull request Aug 17, 2026
…ite (#8)

* fix(config): parse list/mapping literals in hermes config set

Fold the list/mapping parser INSIDE the existing string-typed-value coercion guard (the `not isinstance(_default_value_for_key(key), str)` block from e4ea0a0) instead of running it unconditionally, so a genuinely string-typed setting whose value merely starts with '[' or '{' is left untouched while non-string keys get JSON/YAML flow literals parsed to real lists/dicts.

Update website/docs/user-guide/configuring-models.md: the `config set only writes scalar values` note is no longer accurate; document the list/mapping support with a quoted example.

Fixes NousResearch#40545 NousResearch#50168

* fix(config): extend structured-value parsing to multi-line YAML blocks with a conservative trigger

Consolidation follow-up on top of NousResearch#59182's cherry-picked base:

- Add _looks_structured_value(): triggers a yaml.safe_load structured
  parse only when the value starts with '[' / '{' or spans multiple
  lines with YAML list-item ('- x') or mapping-entry ('key: v') shaped
  lines. Deliberately avoids the over-broad leading '-' trigger from
  NousResearch#88066 so '-5' and '--flag' stay strings.
- Stays folded INSIDE the string-typed-key guard: keys whose
  DEFAULT_CONFIG type is str (e.g. approvals.mode) are never coerced.
- Tests: multi-line YAML list/dict, string-typed key given '[x]' and
  '-5' stays string, dash-prefixed scalars stay strings, plain
  multi-line prose stays a string, load_config round-trip.
  Sabotage-verified: 7 of the suite's tests fail on main without the fix.

* Port from can1357/oh-my-pi#7553: allow quoted shell metacharacters in allowlist matching

command_allowlist glob rules (e.g. 'cargo *') rejected any command whose
quoted arguments contained shell metacharacters — a cargo benchmark
regex filter like '^layer3/write/(a|b)$' disqualified the whole command
even though those characters are literal to the shell.

_has_allowlist_shell_operator is now quote-aware:
- metacharacters inside single/double quotes or behind a backslash are
  treated as literal arguments;
- $ and backtick inside DOUBLE quotes still disqualify (expansion is
  active there);
- quoted/escaped control characters still disqualify when the command
  carries a -c/-e/--command/--eval-style option that hands the payload
  to another interpreter (sh -c '...', git -c alias.x='!...' x);
- unterminated quotes disqualify (shape can't be reasoned about).

Compound commands (unquoted ; & | < > backtick $( newline) are rejected
exactly as before. hermes_cli/approvals_suggest.derive_glob picks up the
same semantics via its existing import.

* feat(terminal): interpret signal-termination exit codes for the model

Port from Kilo-Org/kilocode#12698: report signal-terminated commands with
a human-readable note instead of a bare numeric exit code.

Kilo's fix settles a signal-killed process as the conventional 128+signum
exit code so its bash tool stops hanging. Hermes already produces numeric
codes for signal deaths (subprocess -signum, or the shell's 128+signum),
but the model saw a bare exit_code=-9 or 137 and burned turns
mis-diagnosing (137 = OOM kill being the most common). This adapts the
idea to Hermes' existing exit-code semantics tier:

- _interpret_signal_exit(): maps negative codes (definite signal death)
  and the 128+signum band (hedged with 'usually') to a note naming the
  signal and its likely cause, wired into _interpret_exit_code() ahead of
  the per-command semantics table.
- Curated signal table (SIGKILL/SIGSEGV/SIGTERM/SIGABRT/...) so ambiguous
  application exit codes are never mislabeled; uncurated 128+N codes stay
  silent, SIGINT is excluded (executor's interrupt-marker path owns
  rc=130).
- Notes surface via the existing exit_code_meaning result field.

E2E verified against real SIGSEGV/SIGKILL processes.

* Port from MoonshotAI/kimi-code#2596/NousResearch#2600: surface MCP tool-result _meta to the model, minus protocol-reserved keys

MCP tool results carry a server _meta mapping (exposed as .meta by the
Python SDK) alongside structuredContent. Servers return namespaced
machine-readable contracts there (validated payloads, browser-handoff
URLs); Hermes previously dropped the field entirely, so that data was
invisible to the agent.

Now _meta is included in the JSON tool output, after filtering
protocol-reserved keys per the MCP spec's key-name rules: a prefix is
reserved when a modelcontextprotocol or mcp label is followed by at
least one more label (modelcontextprotocol.io/..., tools.mcp.com/...).
Vendor namespaces with a trailing reserved word (com.example.mcp/...)
and unprefixed keys pass through. Non-serializable metadata drops the
extras rather than failing the call.

* fix(telegram): rebind TypeHandler in the deferred SDK import

`check_telegram_requirements()` re-imports python-telegram-bot after a
lazy install and rebinds the module-level aliases that the top-level
`except ImportError` block set to `typing.Any`. TypeHandler was left out
of all three places: the `global` declaration, the
`from telegram.ext import (...)` list, and the assignments.

So whenever the top-level import fails and the deferred path runs, every
other alias is restored and TELEGRAM_AVAILABLE flips to True, while
TypeHandler stays `Any`. Handler registration then raises
`TypeError: Any cannot be instantiated` and the gateway reports:

    [Telegram] Failed to connect to Telegram: Any cannot be instantiated
    Gateway started with no connected platforms

The 22.6 -> 22.8 pin bump named in NousResearch#85272 is the trigger rather than the
defect: it makes the top-level import fail, which is what routes the
module through the deferred path where the omission has always been.

* Port from aaif-goose/goose#10746: strip invisible Unicode TAG chars from MCP content

Unicode TAG characters (U+E0000-U+E007F) render as nothing in terminals
and chat UIs but are fully visible to LLM tokenizers, making them an
ASCII-smuggling prompt-injection channel for untrusted MCP servers.

- tools/ansi_strip.py: new strip_unicode_tags() with fast path; unlike
  goose we preserve valid emoji tag sequences (U+1F3F4 base + tag spec +
  U+E007F cancel), so regional flags survive.
- tools/mcp_tool.py: applied at every MCP text ingestion point — tool
  result text blocks, embedded resource text, read_resource contents,
  get_prompt message content, and tool descriptions entering the schema.
- tests/tools/test_unicode_tag_strip.py: smuggled-instruction vectors,
  goose's test vector, emoji-tag-sequence preservation, ZWJ untouched.

* feat(business-os): implement Waves 1-33 strategic enhancements and full E2E QA suite

- Add Waves 1-33 capabilities across finance, governance, GTM, SEO, and top-tier web/graphic design.
- Implement Design System Tokens, WebGL Shaders, Motion Architecture, and Skeleton Shimmer Loaders.
- Add WCAG 2.1 AAA Accessible Keyboard Focus Rings, Adaptive Breakpoints, and Fluid Typography Scalers.
- Fix pre-existing unit test timing issues and model catalog mocking in test_models, test_objective_worker, and authority_integrity.
- Create comprehensive E2E functional test suite (test_full_qa_e2e_workflow.py) verified across 381 unit & integration tests.

* chore(contributors): add contributor mapping for paul.lesyuk@gmail.com

---------

Co-authored-by: Sam Liu <sam7894604@gmail.com>
Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
Co-authored-by: Pavel Lesyuk <paul.lesyuk@gmail.com>
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.

bash approval pattern "cargo *" does not auto-approve cargo regex benchmark command

2 participants