Home

mweinbach / agent-coworker

publicmweinbach/agent-coworker
Overview Code History Branches Pull requestsIssuesInsights
main
HomeOverview Code PRsIssues

fix: enforce read-only shell policy (#72)

6 months ago

9ff222d
Authored
Max Weinbach4/13/2026, 5:22:44 PM
* fix: enforce read-only shell policy

Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>

* test: tighten readonly shell policy coverage

Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>

* fix: address readonly shell policy review feedback

Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>

* Harden read-only shell policy and add bypass regression tests

* Harden read-only shell command policy checks

* fix: close readonly shell policy review gaps

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>

* Fix shell policy parser bypasses and tee false positives

* fix: close remaining shell policy review gaps

The PR picked up two newer shell-policy review findings after the first
follow-up push. The shared parser now skips launcher options with values
before looking for -c payloads and walks nested shell-command payloads
through a bounded queue so deeper shells still get inspected.

Constraint: Read-only shell enforcement must stay at the shared harness policy layer
Rejected: Patch only the specific bash examples from review | leaves the option-value and nesting bugs in the generic parser
Confidence: high
Scope-risk: narrow
Directive: Keep shell-command detection launcher-aware and candidate traversal bounded; do not reintroduce shallow depth cutoffs around nested -c parsing
Tested: ~/.bun/bin/bun test test/bash.readonly-policy.test.ts test/tools.test.ts; ~/.bun/bin/bun run typecheck
Not-tested: Full bun test suite

Co-authored-by: Codex <noreply@openai.com>

* fix: address latest shell policy review gaps

New review comments on the PR exposed two more read-only shell bypasses:
newline-delimited commands were flattened before raw-rule parsing, and
standalone brace groups were not split before executable detection.
The tokenizer now preserves those shell boundaries while still treating
escaped line continuations as continuations rather than separators.

Constraint: Read-only enforcement must remain centralized in the shared shell policy parser
Rejected: Add point fixes for just the reported examples | would leave newline and brace semantics inconsistent across the parser
Confidence: high
Scope-risk: narrow
Directive: Preserve real shell command boundaries for raw-rule checks; do not collapse newlines or standalone grouping tokens before tokenizer-based mutation detection
Tested: ~/.bun/bin/bun test test/bash.readonly-policy.test.ts test/tools.test.ts; ~/.bun/bin/bun run typecheck
Not-tested: Full bun test suite

Co-authored-by: Codex <noreply@openai.com>

* fix: address quoted shell redirect review gap

A fresh review on the latest PR commit showed that quoted redirect
targets still bypassed the write detector because the old regex ran on
quote-stripped input. The shell policy now detects redirections from raw
input with quote-aware scanning, so quoted filenames are still blocked
without regressing harmless quoted literals containing >.

Constraint: Quoted shell literals that merely mention > must remain allowed for read-only inspection commands
Rejected: Expand the stripped regex to cover quotes | still loses shell structure and keeps redirection parsing brittle
Confidence: high
Scope-risk: narrow
Directive: Keep redirection detection on raw shell input with quote-aware scanning; do not move it back onto quote-stripped text
Tested: ~/.bun/bin/bun test test/bash.readonly-policy.test.ts test/tools.test.ts; ~/.bun/bin/bun run typecheck
Not-tested: Full bun test suite

Co-authored-by: Codex <noreply@openai.com>

* fix: address shell reserved-word review gap

A new review on the latest PR SHA showed that shell reserved words like
if/for/while and the ! negation operator could still mask later
mutating commands from executable detection. The parser now treats those
control-flow tokens as segment boundaries so the real executable remains
visible to the shared read-only policy checks.

Constraint: Compound shell forms must still be evaluated through the shared harness policy instead of ad hoc command allowlists
Rejected: Only special-case if and ! from the current examples | leaves the same control-flow blind spot for other reserved-word forms
Confidence: high
Scope-risk: narrow
Directive: Keep shell control-flow tokens out of executable resolution; if new shell forms appear, extend the shared separator/control-token set rather than layering one-off bypass checks
Tested: ~/.bun/bin/bun test test/bash.readonly-policy.test.ts test/tools.test.ts; ~/.bun/bin/bun run typecheck
Not-tested: Full bun test suite

Co-authored-by: Codex <noreply@openai.com>

* fix: harden remaining shell policy mutation paths

The newest PR review found three more bypass families in the shared
shell policy: mutating git subcommands outside the small write set,
package-manager global flags placed before install verbs, and long
nested shell chains that could exceed the fixed candidate scan cap.
This pass tightens all three at the harness policy boundary.

Constraint: Read-only shell enforcement must stay conservative even when command syntax varies across git and package manager CLIs
Rejected: Patch only the specific review examples | would leave the same classification gaps for adjacent mutating subcommands and option forms
Confidence: high
Scope-risk: moderate
Directive: Keep git/package mutation detection syntax-aware and avoid fixed scan caps that can drop later executed payloads
Tested: ~/.bun/bin/bun test test/bash.readonly-policy.test.ts test/tools.test.ts; ~/.bun/bin/bun run typecheck
Not-tested: Full bun test suite

Co-authored-by: Codex <noreply@openai.com>

* fix: catch remaining env and pip shell policy bypasses

The latest PR review exposed two more syntax variants that bypassed the
shared read-only shell guard: GNU env short options with attached values
and pip global flags placed before install. This pass extends the
existing parser and install classifier so those common forms stay within
the same centralized write-detection boundary.

Constraint: Read-only shell enforcement must cover common CLI syntax variations without moving logic into the UI or prompt layer
Rejected: Special-case only env -C. and pip --quiet | would leave the same parser gap for adjacent flag variants
Confidence: high
Scope-risk: narrow
Directive: When CLI flags can legally appear before a mutating verb, extend the shared argument scanner instead of assuming fixed positions
Tested: ~/.bun/bin/bun test test/bash.readonly-policy.test.ts test/tools.test.ts; ~/.bun/bin/bun run typecheck
Not-tested: Full bun test suite

Co-authored-by: Codex <noreply@openai.com>

* fix: cover remaining env redirection and pip policy gaps

The latest review cycle surfaced three more shell-policy gaps: GNU env
attached short-option values, package-manager -C case normalization, and
shell syntax forms that still bypassed the shared write detector (<>
redirections and nested escaped backticks). This pass closes those
variants while keeping the harness-level policy as the single enforcement
boundary.

Constraint: The read-only shell guard must stay syntax-aware across common CLI and shell variants without regressing benign quoted inspection commands
Rejected: Patch only the exact comment examples | would leave adjacent flag and substitution variants outside the shared parser contracts
Confidence: high
Scope-risk: narrow
Directive: Normalize option matching to the parser's lowercase view and keep recursive substitution scanning faithful to executable shell syntax, not string-literal approximations
Tested: ~/.bun/bin/bun test test/bash.readonly-policy.test.ts test/tools.test.ts; ~/.bun/bin/bun run typecheck
Not-tested: Full bun test suite

Co-authored-by: Codex <noreply@openai.com>

* test: harden package manifest CI timeout budget

GitHub's ubuntu runner hit the package-manifest dry-run assertion's
default 5s limit even though the underlying pack manifest assertions
still pass locally. The test now carries an explicit higher timeout so
CI evaluates the packaging contract instead of flaking on runner speed.

Constraint: CI runner timing is authoritative for this lane, even when local repro is faster
Rejected: Retry the failed job without code changes | leaves the same timeout budget in place for the next runner
Confidence: high
Scope-risk: narrow
Directive: Keep packaging-manifest checks focused on contract assertions and give runner-bound npm pack dry-runs an explicit timeout budget
Tested: ~/.bun/bin/bun test test/package-manifest.test.ts; ~/.bun/bin/bun test --max-concurrency 1 -t 'packs runtime assets and excludes repo-only baggage'; ~/.bun/bin/bun run typecheck
Not-tested: Full bun test suite

Co-authored-by: Codex <noreply@openai.com>

* fix: close fish shell policy parsing gap

A new review on the latest PR SHA showed that Fish's uppercase -C
init-command flag was being lowercased into -c before option-value
matching, so the parser mistook it for the real command payload flag
and skipped the later mutating -c command. This pass restores
case-aware shell option matching for launcher-specific flags and keeps
the dead thirdArg cleanup in the package-install classifier.

Constraint: Shell option parsing must preserve case where the underlying shell treats -C and -c as different flags
Rejected: Lowercase every launcher option key globally | breaks shells whose uppercase and lowercase short flags have different meanings
Confidence: high
Scope-risk: narrow
Directive: For shell-specific launcher flags, preserve the shell's own case semantics before falling back to generic normalized flag handling
Tested: ~/.bun/bin/bun test test/bash.readonly-policy.test.ts test/tools.test.ts; ~/.bun/bin/bun run typecheck
Not-tested: Full bun test suite

Co-authored-by: Codex <noreply@openai.com>

* fix: handle env split-string and eval shell payloads

The last practical review findings were both string-executing shell
surfaces: GNU env -S and eval. This pass teaches recursive candidate
extraction to inspect those payloads through the same shared harness
policy path, rather than leaving them as blind spots in read-only mode.

Constraint: Embedded shell command strings must be enforced at the shared parser boundary rather than by ad hoc command-specific blocks in callers
Rejected: Mark env -S and eval as globally forbidden tokens | would hide the real executed payload and reduce future extensibility for adjacent executor forms
Confidence: high
Scope-risk: narrow
Directive: When a shell primitive executes string content, feed that content back through recursive policy scanning instead of treating the primitive itself as the final executable
Tested: ~/.bun/bin/bun test test/bash.readonly-policy.test.ts test/tools.test.ts; ~/.bun/bin/bun run typecheck
Not-tested: Full bun test suite

Co-authored-by: Codex <noreply@openai.com>

* Harden shell policy against inline interpreter and python install bypass

* Update commandPolicy.ts

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Max Weinbach <mweinbach@users.noreply.github.com>
Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
Co-authored-by: Codex <noreply@openai.com>

Parenta8e471b

12 files changed
  • src/agent.ts+4−1
  • src/server/agents/DelegateRunner.ts+2−1
  • src/server/agents/commandPolicy.ts+1002−0
  • src/server/agents/roles.ts+13−0
  • src/server/session/SkillManager.ts+1−0
  • src/server/session/TurnExecutionManager.ts+2−0
  • src/tools/bash.ts+14−0
  • src/tools/context.ts+4−0
  • tasks/todo.md+50−0
  • test/bash.readonly-policy.test.ts+248−0
  • test/package-manifest.test.ts+1−1
  • test/tools.test.ts+16−0