You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Harden the opt-in portable shell permission scanner so uncertain shell input cannot execute under a narrower saved approval.
This PR builds on the behavior-preserving scanner relocation merged in #44026.
Before / After
Before: effectful syntax such as assignment-only redirects and lone PowerShell carriage returns could produce no shell resources. Opaque input could inherit a prefix approval such as echo *, and unknown directory changes could bypass scoped external-directory denies or be resolved against the wrong environment.
After: unsupported or uncertain input requires a one-time exact command approval plus conservative unknown-directory authorization. Scoped wildcard allows cannot authorize opaque input, scoped denies still apply, and opaque approvals cannot persist wildcard-shaped grants. Static directory analysis now uses the invocation environment and fails closed for disguised, sequential, wrapped, state-mutating, startup-hook, or otherwise unresolved changes.
How
packages/core/src/shell/scan.ts rejects side-effecting assignment statements, compound redirects, runtime PowerShell alias mutation, unsupported parameter syntax, unsafe quoting, and other under-reported forms.
packages/core/src/shell/parse.ts carries analysis certainty, uses the actual invocation environment, recognizes additional directory command forms, preserves lexical save prefixes, and marks startup hooks or same-command environment mutation uncertain.
packages/core/src/permission.ts adds an internal exact-resource evaluation mode that ignores scoped wildcard allows while conservatively retaining configured denies.
packages/core/src/tool/plugin/shell.ts makes opaque shell and unknown-directory prompts non-persistable.
Permission documentation describes supported shell families and fail-closed behavior.
AI code review — automated review for reference; please use your judgment.
packages/core/src/permission.ts:186 — In evaluated() exact mode, the condition rule.resource === resource || rule.resource === "*" || rule.effect !== "allow" returns any ask/deny rule whose action matches, even when the rule's resource pattern doesn't cover the requested resource. Why it matters: a scoped config like deny external_directory /secrets/** becomes an action-wide deny for all exact-mode requests (["*"]), diverging from wildcard-mode semantics where resource patterns gate the effect. Suggestion: only return a rule when rule.resource === resource || rule.resource === "*"; otherwise continue for every effect, falling through to the ask default — i.e. drop the || rule.effect !== "allow" arm.
packages/core/tool/plugin/shell.ts (behavioral) — After a user approves an unknown-directory prompt with "always allow" (saved against a concrete path), later exact-mode asserts use resources: ["*"], which no saved concrete-path allow can ever match (and evaluated skips wildcard-allow rules). Why it matters: "don't ask again" silently degrades to ask-every-time for subsequent opaque commands. Possibly intended ("unknown dirs can't inherit narrower approvals"), but it should be stated in the changeset/permissions.mdx so users understand the persistent prompting.
packages/core/src/permission.ts:186 (test gap) — evaluated() is a pure function carrying the subtlest logic in this PR, yet it's only exercised indirectly. Suggestion: add a direct matrix test: exact-vs-wildcard allow, "*" allow hit, wildcard-allow skip, non-matching deny (pins item 1's chosen semantics), non-matching ask, and empty-ruleset default.
packages/core/src/shell/parse.ts:384 — popd/pushd now unconditionally report unknown: true, and any second directory change flips directoryUnknown — correct and conservative, but note the whole command string then goes out as a single opaque resource with save: [] (shell.ts), losing the per-command breakdown in approval history/audit. Suggestion: keep the strictness, but consider still resolving/publishing the known directories for observability alongside the blanket prompt.
packages/core/src/shell/parse.ts:795 — ~ now expands via $HOME only; on typical Windows setups (no HOME, PowerShell default), cd ~ becomes unknown ⇒ prompt. Fail-closed is right, but mapping USERPROFILE as a fallback for the portable path would avoid a noisy UX regression on Windows while staying deterministic.
packages/core/src/shell/scan.ts:498 (nit) — The curly-quote blocklist covers U+2018–U+201D; other confusables (fullwidth "', angle quotes «»‹›) pass through to word parsing. Low risk since they'd rarely execute, but a broader "non-ASCII quote-like" class would be cheaper than enumerating.
Nice work overall — the BASH_ENV/BASH_FUNC_* startup gating, alias-command opacity in PowerShell, and the adversarial/generated test suites close real bypass classes.
— automated review (ox-alpha, round2)
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
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.
What
Harden the opt-in portable shell permission scanner so uncertain shell input cannot execute under a narrower saved approval.
This PR builds on the behavior-preserving scanner relocation merged in #44026.
Before / After
Before: effectful syntax such as assignment-only redirects and lone PowerShell carriage returns could produce no shell resources. Opaque input could inherit a prefix approval such as
echo *, and unknown directory changes could bypass scoped external-directory denies or be resolved against the wrong environment.After: unsupported or uncertain input requires a one-time exact command approval plus conservative unknown-directory authorization. Scoped wildcard allows cannot authorize opaque input, scoped denies still apply, and opaque approvals cannot persist wildcard-shaped grants. Static directory analysis now uses the invocation environment and fails closed for disguised, sequential, wrapped, state-mutating, startup-hook, or otherwise unresolved changes.
How
packages/core/src/shell/scan.tsrejects side-effecting assignment statements, compound redirects, runtime PowerShell alias mutation, unsupported parameter syntax, unsafe quoting, and other under-reported forms.packages/core/src/shell/parse.tscarries analysis certainty, uses the actual invocation environment, recognizes additional directory command forms, preserves lexical save prefixes, and marks startup hooks or same-command environment mutation uncertain.packages/core/src/permission.tsadds an internal exact-resource evaluation mode that ignores scoped wildcard allows while conservatively retaining configured denies.packages/core/src/tool/plugin/shell.tsmakes opaque shell and unknown-directory prompts non-persistable.@opencode-ai/coreonly; the scanner is no longer a package after refactor(core): inline portable shell scanner #44026.Scope
experimental.portable_shell_scannerremains opt-in and default-false.Testing
packages/core: focused scanner, parser, permission, and shell-tool suite (381 passed)packages/core:bun typecheck,bun run buildpackages/www:bun typecheck,bun validate,bun run buildbun run lint(0 errors, existing warnings only),git diff --checkFlow
flowchart TD Input[Shell input] --> Scan{Portable analysis complete?} Scan -->|yes| Resources[Derived command and directory resources] Scan -->|no| Exact[Exact raw command, no saved approval] Exact --> Unknown[Unknown external directory, no saved approval] Resources --> Policy[Normal wildcard policy evaluation] Unknown --> Strict[Conservative exact-mode policy evaluation] Exact --> Strict Policy --> Execute{Allowed?} Strict --> Execute Execute -->|yes| Run[Spawn shell] Execute -->|no| Stop[Reject before spawn]