@nathapp/nax-agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/LICENSE +21 -0
- package/README.md +49 -0
- package/dist/coding-tools/coding-tool-bash.d.ts +46 -0
- package/dist/coding-tools/coding-tool-bash.js +44 -0
- package/dist/coding-tools/coding-tool-extras.d.ts +35 -0
- package/dist/coding-tools/coding-tool-extras.js +56 -0
- package/dist/coding-tools/coding-tool-sandbox.d.ts +74 -0
- package/dist/coding-tools/coding-tool-sandbox.js +215 -0
- package/dist/coding-tools/coding-tool-support.d.ts +132 -0
- package/dist/coding-tools/coding-tool-support.js +159 -0
- package/dist/coding-tools/universal-coding-tools.d.ts +11 -0
- package/dist/coding-tools/universal-coding-tools.js +5 -0
- package/dist/command-interceptor/index.d.ts +55 -0
- package/dist/command-interceptor/index.js +60 -0
- package/dist/command-interceptor/shell.d.ts +36 -0
- package/dist/command-interceptor/shell.js +137 -0
- package/dist/command-safety/build.d.ts +19 -0
- package/dist/command-safety/build.js +47 -0
- package/dist/command-safety/guard.d.ts +32 -0
- package/dist/command-safety/guard.js +179 -0
- package/dist/command-safety/identifiers.d.ts +24 -0
- package/dist/command-safety/identifiers.js +20 -0
- package/dist/command-safety/index.d.ts +17 -0
- package/dist/command-safety/index.js +11 -0
- package/dist/command-safety/questions.d.ts +19 -0
- package/dist/command-safety/questions.js +60 -0
- package/dist/command-safety/row.d.ts +2 -0
- package/dist/command-safety/row.js +14 -0
- package/dist/command-safety/rule-scorer.d.ts +9 -0
- package/dist/command-safety/rule-scorer.js +391 -0
- package/dist/command-safety/shadow.d.ts +33 -0
- package/dist/command-safety/shadow.js +188 -0
- package/dist/command-safety/systemone-client.d.ts +16 -0
- package/dist/command-safety/systemone-client.js +92 -0
- package/dist/command-safety/tap.d.ts +37 -0
- package/dist/command-safety/tap.js +77 -0
- package/dist/command-safety/temp-only.d.ts +6 -0
- package/dist/command-safety/temp-only.js +215 -0
- package/dist/command-safety/tmp-write.d.ts +2 -0
- package/dist/command-safety/tmp-write.js +112 -0
- package/dist/command-safety/types.d.ts +185 -0
- package/dist/command-safety/types.js +21 -0
- package/dist/config/bash-approval.d.ts +33 -0
- package/dist/config/bash-approval.js +30 -0
- package/dist/config/catalog-overrides.d.ts +94 -0
- package/dist/config/catalog-overrides.js +7 -0
- package/dist/config/native-agent/index.d.ts +8 -0
- package/dist/config/native-agent/index.js +8 -0
- package/dist/config/schemas-sandbox.d.ts +31 -0
- package/dist/config/schemas-sandbox.js +45 -0
- package/dist/cost/core/index.d.ts +8 -0
- package/dist/cost/core/index.js +7 -0
- package/dist/cost/estimate.d.ts +34 -0
- package/dist/cost/estimate.js +65 -0
- package/dist/cost/model-spec.d.ts +28 -0
- package/dist/cost/model-spec.js +29 -0
- package/dist/cost/standard-types.d.ts +9 -0
- package/dist/cost/standard-types.js +1 -0
- package/dist/cost/usage-math.d.ts +18 -0
- package/dist/cost/usage-math.js +46 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +21 -0
- package/dist/infra/agent-logger.d.ts +22 -0
- package/dist/infra/agent-logger.js +19 -0
- package/dist/infra/credentials-config.d.ts +20 -0
- package/dist/infra/credentials-config.js +15 -0
- package/dist/infra/errors.d.ts +9 -0
- package/dist/infra/errors.js +11 -0
- package/dist/infra/index.d.ts +7 -0
- package/dist/infra/index.js +7 -0
- package/dist/infra/nax-error.d.ts +10 -0
- package/dist/infra/nax-error.js +16 -0
- package/dist/infra/spin-breaker/hash.d.ts +2 -0
- package/dist/infra/spin-breaker/hash.js +5 -0
- package/dist/infra/spin-breaker/index.d.ts +120 -0
- package/dist/infra/spin-breaker/index.js +340 -0
- package/dist/internal/agent-output-env.d.ts +44 -0
- package/dist/internal/agent-output-env.js +57 -0
- package/dist/internal/argv-exec.d.ts +58 -0
- package/dist/internal/argv-exec.js +224 -0
- package/dist/internal/bounded-io.d.ts +24 -0
- package/dist/internal/bounded-io.js +51 -0
- package/dist/internal/command-spec/index.d.ts +29 -0
- package/dist/internal/command-spec/index.js +36 -0
- package/dist/internal/describe-value-type.d.ts +8 -0
- package/dist/internal/describe-value-type.js +14 -0
- package/dist/internal/exec-framing.d.ts +18 -0
- package/dist/internal/exec-framing.js +22 -0
- package/dist/internal/file-lock.d.ts +91 -0
- package/dist/internal/file-lock.js +212 -0
- package/dist/internal/file-size.d.ts +2 -0
- package/dist/internal/file-size.js +12 -0
- package/dist/internal/git-add.d.ts +43 -0
- package/dist/internal/git-add.js +59 -0
- package/dist/internal/git-env.d.ts +59 -0
- package/dist/internal/git-env.js +93 -0
- package/dist/internal/git-exec.d.ts +58 -0
- package/dist/internal/git-exec.js +115 -0
- package/dist/internal/path-file-lock.d.ts +23 -0
- package/dist/internal/path-file-lock.js +25 -0
- package/dist/internal/process-alive.d.ts +42 -0
- package/dist/internal/process-alive.js +56 -0
- package/dist/internal/process-kill.d.ts +27 -0
- package/dist/internal/process-kill.js +54 -0
- package/dist/internal/realpath.d.ts +29 -0
- package/dist/internal/realpath.js +52 -0
- package/dist/internal/redact.d.ts +40 -0
- package/dist/internal/redact.js +161 -0
- package/dist/internal/shell-quote.d.ts +2 -0
- package/dist/internal/shell-quote.js +4 -0
- package/dist/internal/sort.d.ts +20 -0
- package/dist/internal/sort.js +20 -0
- package/dist/internal/strip-control-chars.d.ts +19 -0
- package/dist/internal/strip-control-chars.js +38 -0
- package/dist/internal/thenable.d.ts +10 -0
- package/dist/internal/thenable.js +15 -0
- package/dist/internal.d.ts +91 -0
- package/dist/internal.js +91 -0
- package/dist/native/adapter-deps.d.ts +46 -0
- package/dist/native/adapter-deps.js +43 -0
- package/dist/native/auth-types.d.ts +70 -0
- package/dist/native/auth-types.js +13 -0
- package/dist/native/auth.d.ts +133 -0
- package/dist/native/auth.js +362 -0
- package/dist/native/client.d.ts +50 -0
- package/dist/native/client.js +174 -0
- package/dist/native/complete.d.ts +33 -0
- package/dist/native/complete.js +88 -0
- package/dist/native/credentials/chained-store.d.ts +30 -0
- package/dist/native/credentials/chained-store.js +68 -0
- package/dist/native/credentials/change-guard.d.ts +51 -0
- package/dist/native/credentials/change-guard.js +136 -0
- package/dist/native/credentials/exec-source.d.ts +50 -0
- package/dist/native/credentials/exec-source.js +222 -0
- package/dist/native/credentials/fingerprint.d.ts +26 -0
- package/dist/native/credentials/fingerprint.js +174 -0
- package/dist/native/credentials/helper-process.d.ts +79 -0
- package/dist/native/credentials/helper-process.js +259 -0
- package/dist/native/credentials/helper-protocol.d.ts +36 -0
- package/dist/native/credentials/helper-protocol.js +79 -0
- package/dist/native/credentials/index.d.ts +60 -0
- package/dist/native/credentials/index.js +184 -0
- package/dist/native/errors.d.ts +34 -0
- package/dist/native/errors.js +109 -0
- package/dist/native/index.d.ts +25 -0
- package/dist/native/index.js +23 -0
- package/dist/native/model-resolver.d.ts +57 -0
- package/dist/native/model-resolver.js +73 -0
- package/dist/native/models.d.ts +116 -0
- package/dist/native/models.js +183 -0
- package/dist/native/session/ask-human.d.ts +15 -0
- package/dist/native/session/ask-human.js +22 -0
- package/dist/native/session/compaction.d.ts +94 -0
- package/dist/native/session/compaction.js +170 -0
- package/dist/native/session/handle-invalid-tool-call.d.ts +111 -0
- package/dist/native/session/handle-invalid-tool-call.js +159 -0
- package/dist/native/session/loop-events/cache-boundary.d.ts +11 -0
- package/dist/native/session/loop-events/cache-boundary.js +29 -0
- package/dist/native/session/loop-events/external-handler.d.ts +38 -0
- package/dist/native/session/loop-events/external-handler.js +193 -0
- package/dist/native/session/loop-events/index.d.ts +16 -0
- package/dist/native/session/loop-events/index.js +15 -0
- package/dist/native/session/loop-events/payload-guard.d.ts +9 -0
- package/dist/native/session/loop-events/payload-guard.js +31 -0
- package/dist/native/session/loop-events/registry.d.ts +84 -0
- package/dist/native/session/loop-events/registry.js +305 -0
- package/dist/native/session/loop-events/types.d.ts +251 -0
- package/dist/native/session/loop-events/types.js +14 -0
- package/dist/native/session/loop-handlers.d.ts +73 -0
- package/dist/native/session/loop-handlers.js +137 -0
- package/dist/native/session/nudge.d.ts +29 -0
- package/dist/native/session/nudge.js +35 -0
- package/dist/native/session/rate-provenance.d.ts +17 -0
- package/dist/native/session/rate-provenance.js +39 -0
- package/dist/native/session/session.d.ts +139 -0
- package/dist/native/session/session.js +208 -0
- package/dist/native/session/tool-input-exemplar.d.ts +2 -0
- package/dist/native/session/tool-input-exemplar.js +66 -0
- package/dist/native/session/tool-input-validation.d.ts +15 -0
- package/dist/native/session/tool-input-validation.js +146 -0
- package/dist/native/session/tool-mapping.d.ts +18 -0
- package/dist/native/session/tool-mapping.js +27 -0
- package/dist/native/session/tool-result.d.ts +37 -0
- package/dist/native/session/tool-result.js +25 -0
- package/dist/native/session/transcript-store.d.ts +77 -0
- package/dist/native/session/transcript-store.js +200 -0
- package/dist/native/session/truncation-handler.d.ts +47 -0
- package/dist/native/session/truncation-handler.js +83 -0
- package/dist/native/session/turn-accumulator.d.ts +45 -0
- package/dist/native/session/turn-accumulator.js +67 -0
- package/dist/native/session/turn-ask-human.d.ts +23 -0
- package/dist/native/session/turn-ask-human.js +41 -0
- package/dist/native/session/turn-compaction-step.d.ts +74 -0
- package/dist/native/session/turn-compaction-step.js +128 -0
- package/dist/native/session/turn-complete-step.d.ts +77 -0
- package/dist/native/session/turn-complete-step.js +159 -0
- package/dist/native/session/turn-end-event.d.ts +3 -0
- package/dist/native/session/turn-end-event.js +17 -0
- package/dist/native/session/turn-events.d.ts +63 -0
- package/dist/native/session/turn-events.js +48 -0
- package/dist/native/session/turn-loop-round-trip.d.ts +116 -0
- package/dist/native/session/turn-loop-round-trip.js +318 -0
- package/dist/native/session/turn-loop.d.ts +23 -0
- package/dist/native/session/turn-loop.js +250 -0
- package/dist/native/session/turn-result.d.ts +39 -0
- package/dist/native/session/turn-result.js +64 -0
- package/dist/native/session/turn-retry.d.ts +120 -0
- package/dist/native/session/turn-retry.js +182 -0
- package/dist/native/session/turn-tool-batch.d.ts +61 -0
- package/dist/native/session/turn-tool-batch.js +259 -0
- package/dist/native/session/turn-types.d.ts +164 -0
- package/dist/native/session/turn-types.js +36 -0
- package/dist/native/session-adapter.d.ts +56 -0
- package/dist/native/session-adapter.js +369 -0
- package/dist/native/session-affinity.d.ts +36 -0
- package/dist/native/session-affinity.js +43 -0
- package/dist/native/tier-providers.d.ts +18 -0
- package/dist/native/tier-providers.js +20 -0
- package/dist/permissions/approval-audit.d.ts +10 -0
- package/dist/permissions/approval-audit.js +22 -0
- package/dist/permissions/approvals-link.d.ts +20 -0
- package/dist/permissions/approvals-link.js +62 -0
- package/dist/permissions/approvals-store.d.ts +168 -0
- package/dist/permissions/approvals-store.js +259 -0
- package/dist/permissions/approvals-taint.d.ts +33 -0
- package/dist/permissions/approvals-taint.js +138 -0
- package/dist/permissions/ask-chain.d.ts +69 -0
- package/dist/permissions/ask-chain.js +32 -0
- package/dist/permissions/ask.d.ts +23 -0
- package/dist/permissions/ask.js +25 -0
- package/dist/permissions/bash-lex.d.ts +59 -0
- package/dist/permissions/bash-lex.js +279 -0
- package/dist/permissions/grammar.d.ts +12 -0
- package/dist/permissions/grammar.js +24 -0
- package/dist/permissions/index.d.ts +11 -0
- package/dist/permissions/index.js +9 -0
- package/dist/permissions/secret-spans.d.ts +19 -0
- package/dist/permissions/secret-spans.js +88 -0
- package/dist/permissions/types.d.ts +85 -0
- package/dist/permissions/types.js +1 -0
- package/dist/runtime/index.d.ts +3 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/node-glob.d.ts +3 -0
- package/dist/runtime/node-glob.js +83 -0
- package/dist/runtime/node-runtime.d.ts +3 -0
- package/dist/runtime/node-runtime.js +126 -0
- package/dist/runtime/slot.d.ts +11 -0
- package/dist/runtime/slot.js +17 -0
- package/dist/runtime/types.d.ts +51 -0
- package/dist/runtime/types.js +1 -0
- package/dist/runtime/which.d.ts +8 -0
- package/dist/runtime/which.js +32 -0
- package/dist/sandbox/argv-quote.d.ts +11 -0
- package/dist/sandbox/argv-quote.js +13 -0
- package/dist/sandbox/defaults.d.ts +27 -0
- package/dist/sandbox/defaults.js +47 -0
- package/dist/sandbox/git-guards.d.ts +64 -0
- package/dist/sandbox/git-guards.js +161 -0
- package/dist/sandbox/index.d.ts +11 -0
- package/dist/sandbox/index.js +10 -0
- package/dist/sandbox/launcher.d.ts +37 -0
- package/dist/sandbox/launcher.js +160 -0
- package/dist/sandbox/messages.d.ts +17 -0
- package/dist/sandbox/messages.js +46 -0
- package/dist/sandbox/policy-builder.d.ts +44 -0
- package/dist/sandbox/policy-builder.js +149 -0
- package/dist/sandbox/policy-inputs.d.ts +53 -0
- package/dist/sandbox/policy-inputs.js +76 -0
- package/dist/sandbox/probe.d.ts +8 -0
- package/dist/sandbox/probe.js +66 -0
- package/dist/sandbox/registry.d.ts +17 -0
- package/dist/sandbox/registry.js +57 -0
- package/dist/sandbox/session-tmp.d.ts +20 -0
- package/dist/sandbox/session-tmp.js +95 -0
- package/dist/sandbox/srt-backend.d.ts +21 -0
- package/dist/sandbox/srt-backend.js +99 -0
- package/dist/sandbox/types.d.ts +102 -0
- package/dist/sandbox/types.js +1 -0
- package/dist/session/adapter-failure.d.ts +60 -0
- package/dist/session/adapter-failure.js +11 -0
- package/dist/session/agent-stream-events.d.ts +103 -0
- package/dist/session/agent-stream-events.js +6 -0
- package/dist/session/interaction-handler.d.ts +53 -0
- package/dist/session/interaction-handler.js +2 -0
- package/dist/session/no-op-interaction-handler.d.ts +9 -0
- package/dist/session/no-op-interaction-handler.js +11 -0
- package/dist/session/protocol-types.d.ts +12 -0
- package/dist/session/protocol-types.js +1 -0
- package/dist/session/session-types.d.ts +456 -0
- package/dist/session/session-types.js +64 -0
- package/dist/session/tool-descriptor.d.ts +23 -0
- package/dist/session/tool-descriptor.js +5 -0
- package/dist/session/turn-deadline.d.ts +17 -0
- package/dist/session/turn-deadline.js +23 -0
- package/dist/tools/ask-request.d.ts +35 -0
- package/dist/tools/ask-request.js +57 -0
- package/dist/tools/bash-cwd.d.ts +82 -0
- package/dist/tools/bash-cwd.js +55 -0
- package/dist/tools/bash.d.ts +69 -0
- package/dist/tools/bash.js +283 -0
- package/dist/tools/delete.d.ts +57 -0
- package/dist/tools/delete.js +154 -0
- package/dist/tools/denial-redirect.d.ts +42 -0
- package/dist/tools/denial-redirect.js +266 -0
- package/dist/tools/deny-paths.d.ts +35 -0
- package/dist/tools/deny-paths.js +76 -0
- package/dist/tools/edit-region.d.ts +50 -0
- package/dist/tools/edit-region.js +90 -0
- package/dist/tools/edit.d.ts +25 -0
- package/dist/tools/edit.js +114 -0
- package/dist/tools/exec-allowlist-text.d.ts +18 -0
- package/dist/tools/exec-allowlist-text.js +24 -0
- package/dist/tools/exec-guard.d.ts +69 -0
- package/dist/tools/exec-guard.js +151 -0
- package/dist/tools/git-commit.d.ts +59 -0
- package/dist/tools/git-commit.js +220 -0
- package/dist/tools/git-flags/index.d.ts +10 -0
- package/dist/tools/git-flags/index.js +10 -0
- package/dist/tools/git.d.ts +88 -0
- package/dist/tools/git.js +397 -0
- package/dist/tools/glob.d.ts +29 -0
- package/dist/tools/glob.js +153 -0
- package/dist/tools/grep.d.ts +21 -0
- package/dist/tools/grep.js +162 -0
- package/dist/tools/index.d.ts +36 -0
- package/dist/tools/index.js +28 -0
- package/dist/tools/narrow-grants.d.ts +19 -0
- package/dist/tools/narrow-grants.js +54 -0
- package/dist/tools/nax-owned-writes.d.ts +135 -0
- package/dist/tools/nax-owned-writes.js +259 -0
- package/dist/tools/package-managers-table.d.ts +146 -0
- package/dist/tools/package-managers-table.js +325 -0
- package/dist/tools/package-managers-types.d.ts +36 -0
- package/dist/tools/package-managers-types.js +6 -0
- package/dist/tools/package-managers.d.ts +85 -0
- package/dist/tools/package-managers.js +391 -0
- package/dist/tools/path-list.d.ts +38 -0
- package/dist/tools/path-list.js +62 -0
- package/dist/tools/policy-bash-raw.d.ts +19 -0
- package/dist/tools/policy-bash-raw.js +210 -0
- package/dist/tools/policy-bash.d.ts +64 -0
- package/dist/tools/policy-bash.js +232 -0
- package/dist/tools/policy-command-branch.d.ts +32 -0
- package/dist/tools/policy-command-branch.js +63 -0
- package/dist/tools/policy-input.d.ts +2 -0
- package/dist/tools/policy-input.js +10 -0
- package/dist/tools/policy-match.d.ts +75 -0
- package/dist/tools/policy-match.js +117 -0
- package/dist/tools/policy-paths-branch.d.ts +97 -0
- package/dist/tools/policy-paths-branch.js +314 -0
- package/dist/tools/policy.d.ts +65 -0
- package/dist/tools/policy.js +232 -0
- package/dist/tools/protected-paths.d.ts +26 -0
- package/dist/tools/protected-paths.js +8 -0
- package/dist/tools/provider-adapt.d.ts +4 -0
- package/dist/tools/provider-adapt.js +32 -0
- package/dist/tools/provider-advertise.d.ts +39 -0
- package/dist/tools/provider-advertise.js +50 -0
- package/dist/tools/provider-grants.d.ts +36 -0
- package/dist/tools/provider-grants.js +72 -0
- package/dist/tools/provider-sanitize.d.ts +19 -0
- package/dist/tools/provider-sanitize.js +53 -0
- package/dist/tools/provider-types.d.ts +41 -0
- package/dist/tools/provider-types.js +26 -0
- package/dist/tools/read-continuation.d.ts +147 -0
- package/dist/tools/read-continuation.js +176 -0
- package/dist/tools/read-file.d.ts +32 -0
- package/dist/tools/read-file.js +85 -0
- package/dist/tools/read.d.ts +25 -0
- package/dist/tools/read.js +182 -0
- package/dist/tools/registry.d.ts +125 -0
- package/dist/tools/registry.js +67 -0
- package/dist/tools/request-capability.d.ts +13 -0
- package/dist/tools/request-capability.js +23 -0
- package/dist/tools/run-command-exec.d.ts +14 -0
- package/dist/tools/run-command-exec.js +161 -0
- package/dist/tools/run-command.d.ts +107 -0
- package/dist/tools/run-command.js +398 -0
- package/dist/tools/runtime-calltool.d.ts +146 -0
- package/dist/tools/runtime-calltool.js +223 -0
- package/dist/tools/runtime.d.ts +149 -0
- package/dist/tools/runtime.js +345 -0
- package/dist/tools/scratchpad.d.ts +20 -0
- package/dist/tools/scratchpad.js +197 -0
- package/dist/tools/spill.d.ts +81 -0
- package/dist/tools/spill.js +251 -0
- package/dist/tools/tool-audit.d.ts +171 -0
- package/dist/tools/tool-audit.js +183 -0
- package/dist/tools/truncate.d.ts +98 -0
- package/dist/tools/truncate.js +297 -0
- package/dist/tools/types.d.ts +145 -0
- package/dist/tools/types.js +24 -0
- package/dist/tools/verb-denial-argv-hint.d.ts +35 -0
- package/dist/tools/verb-denial-argv-hint.js +40 -0
- package/dist/tools/write.d.ts +9 -0
- package/dist/tools/write.js +45 -0
- package/package.json +54 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Classification and normalization for the install-shaped exec branch.
|
|
3
|
+
*
|
|
4
|
+
* A call is install-shaped only when `argv[0]` is a known manager AND the
|
|
5
|
+
* verb is one of that manager's install verbs; such calls are hardened (a
|
|
6
|
+
* no-scripts mechanism, when one is known) and normalized (scoped to the
|
|
7
|
+
* story's own workspace member) by `normalizeExec`. Everything else is
|
|
8
|
+
* generic: it runs exactly as given, at the target cwd, with no rewriting
|
|
9
|
+
* and no mechanism — reachable only through an explicit `Exec(...)` grant
|
|
10
|
+
* elsewhere in the policy.
|
|
11
|
+
*
|
|
12
|
+
* Both directions matter. Without the generic class, something like
|
|
13
|
+
* `bun x tsc --noEmit` could never be permitted by any grant. Without the
|
|
14
|
+
* "known manager plus install verb" test, a loosely classified `bun add`
|
|
15
|
+
* could reach the generic path and skip its no-scripts mechanism entirely.
|
|
16
|
+
* Neither class may borrow the other's treatment — this module is pure
|
|
17
|
+
* classification/data lookup, no spawning, no policy, no I/O.
|
|
18
|
+
*
|
|
19
|
+
* Classification is FAIL-CLOSED (fix round 1, Critical finding 1). A global
|
|
20
|
+
* flag ahead of the verb (`npm --loglevel=silent install x`) must not push
|
|
21
|
+
* a real install onto the unhardened generic path, but a flag's own VALUE
|
|
22
|
+
* token must not be mistaken for the verb either. Three branches, in order:
|
|
23
|
+
*
|
|
24
|
+
* 1. Find the first token after argv[0] that does not start with "-". If
|
|
25
|
+
* it (or the token sequence starting there, for multi-token verbs
|
|
26
|
+
* like `go mod download`) matches one of this manager's install verbs
|
|
27
|
+
* (aliases included), the call is install-shaped.
|
|
28
|
+
* 2. Otherwise, if ANY token anywhere in argv equals one of this
|
|
29
|
+
* manager's install verb tokens, DENY, naming the token. This is the
|
|
30
|
+
* fail-closed branch: `npm --loglevel silent install x` has "silent"
|
|
31
|
+
* as its first non-flag token (branch 1 misses, because a flag's
|
|
32
|
+
* value is not the verb), so the "install" token later in argv trips
|
|
33
|
+
* this branch instead. That is deliberate — a visible refusal the
|
|
34
|
+
* model can retry without the global flag beats a silent unhardened
|
|
35
|
+
* install. Do not "improve" this into a fall-through to generic.
|
|
36
|
+
* 3. Otherwise the call is generic.
|
|
37
|
+
*
|
|
38
|
+
* A manager invocation can also be DISGUISED rather than merely flag-led
|
|
39
|
+
* (addendum finding B): `npx`/`bunx`/`pnpx` run an arbitrary package outside
|
|
40
|
+
* this table's model entirely, and `pnpm dlx npm install x` / `yarn dlx bun
|
|
41
|
+
* add x` launder a real manager invocation through a dlx-style verb. Both
|
|
42
|
+
* shapes are denied before verb classification even runs — there is no
|
|
43
|
+
* argv shape here this table can normalize safely, so neither may fall
|
|
44
|
+
* through to generic. `argv[0]` is also case- and executable-suffix-
|
|
45
|
+
* normalized (`NPM`, `npm.cmd`, `npm.exe` all resolve to the `npm` entry)
|
|
46
|
+
* before every manager lookup; the path-separator form (`./npm`) is already
|
|
47
|
+
* refused upstream by `validateArgv` in `exec-guard.ts` and is not
|
|
48
|
+
* duplicated here.
|
|
49
|
+
*
|
|
50
|
+
* A directory-redirect flag (`--cwd`, `--dir`, `--manifest-path`, ...) is
|
|
51
|
+
* denied on the same basis (fix round 2): `target` is a closed enum
|
|
52
|
+
* precisely so no path can arrive from the model, and a flag like this
|
|
53
|
+
* hands that choice straight back. Manager-global redirect flags are
|
|
54
|
+
* screened for install-shaped AND generic calls alike — see
|
|
55
|
+
* `package-managers-table.ts`'s file header for the full per-manager table
|
|
56
|
+
* of which flags are global versus install-only.
|
|
57
|
+
*
|
|
58
|
+
* `uv pip <anything>` is also denied at classification time (fix round 3,
|
|
59
|
+
* finding 3) — see `isNestedPipInvocation` for why nesting is rejected in
|
|
60
|
+
* favor of a flat denial.
|
|
61
|
+
*
|
|
62
|
+
* None of the fix-round-3 gaps (pip's `-t` alias, yarn's `-C`, `uv pip`
|
|
63
|
+
* nesting) is reachable under the built-in default allowlist, which is
|
|
64
|
+
* install-only and contains neither `uv pip *`, a bare `pip install`, nor
|
|
65
|
+
* a yarn form with `-C`. They become reachable only when a project writes
|
|
66
|
+
* a widening `Exec(...)` grant by hand — defense-in-depth, not a live hole
|
|
67
|
+
* under the shipped default.
|
|
68
|
+
*/
|
|
69
|
+
import { resolve } from "node:path";
|
|
70
|
+
import { normalizeFlagToken } from "./exec-guard.js";
|
|
71
|
+
import { MANAGER_TABLE } from "./package-managers-table.js";
|
|
72
|
+
import { resolveWithin } from "./policy.js";
|
|
73
|
+
/** Package runners that execute an arbitrary, model-named package outside
|
|
74
|
+
* this table's model entirely — always denied, never generic. */
|
|
75
|
+
const RUNNER_BINARIES = new Set(["npx", "bunx", "pnpx"]);
|
|
76
|
+
const DLX_VERB = "dlx";
|
|
77
|
+
/**
|
|
78
|
+
* Lowercases and strips a trailing executable suffix so `NPM`, `npm.cmd`
|
|
79
|
+
* and `npm.exe` all resolve to the same `MANAGER_TABLE` entry as `npm`.
|
|
80
|
+
* Path-separator forms (`./npm`, `..\npm.exe`) are refused upstream by
|
|
81
|
+
* `validateArgv` in `exec-guard.ts` and are not re-checked here.
|
|
82
|
+
*/
|
|
83
|
+
export function normalizeManagerBinary(token) {
|
|
84
|
+
return token.toLowerCase().replace(/\.(cmd|exe|ps1|bat)$/, "");
|
|
85
|
+
}
|
|
86
|
+
export function isKnownManager(binary) {
|
|
87
|
+
return Object.hasOwn(MANAGER_TABLE, normalizeManagerBinary(binary));
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* `pnpm dlx npm install x` / `yarn dlx bun add x`: a manager plus a
|
|
91
|
+
* dlx-style verb whose remaining tokens name another known manager. That is
|
|
92
|
+
* a manager invocation wearing a disguise, not an ordinary dlx package run
|
|
93
|
+
* (`pnpm dlx cowsay hello` is untouched — "cowsay" is not a known manager).
|
|
94
|
+
* Returns the disguised manager's token, or undefined when there is none.
|
|
95
|
+
*/
|
|
96
|
+
function disguisedManagerToken(argv) {
|
|
97
|
+
const second = argv[1];
|
|
98
|
+
if (second === undefined || second.toLowerCase() !== DLX_VERB)
|
|
99
|
+
return undefined;
|
|
100
|
+
return argv.slice(2).find((token) => isKnownManager(token));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* `uv pip <anything>` (fix round 3, finding 3): uv's install verbs are
|
|
104
|
+
* `sync`/`add`, so `["uv","pip","install","--target","/elsewhere","x"]`
|
|
105
|
+
* matches no uv verb and contains no bare token equal to one — it would
|
|
106
|
+
* otherwise fall through to generic, with no directory screen and no
|
|
107
|
+
* no-scripts mechanism, even though `pip install` is exactly the
|
|
108
|
+
* install-shaped call this table exists to harden.
|
|
109
|
+
*
|
|
110
|
+
* Ruling: DENY, don't recognize `uv pip install` as install-shaped and
|
|
111
|
+
* borrow pip's screens for it. That would make one manager entry reach
|
|
112
|
+
* into another's semantics — a general nesting mechanism this table does
|
|
113
|
+
* not otherwise have — for a shape the built-in allowlist never permits
|
|
114
|
+
* anyway (its patterns are `uv sync*`/`uv add*`, not `uv pip *`). Denying
|
|
115
|
+
* costs a capability that already requires a hand-written `Exec(...)`
|
|
116
|
+
* grant to reach at all, and keeps every entry independent. If a project
|
|
117
|
+
* ever needs `uv pip` normalized instead of denied, that is the tradeoff
|
|
118
|
+
* to revisit — recorded here so the choice is visible.
|
|
119
|
+
*
|
|
120
|
+
* Uses the same first-non-flag-token search as branch 1 of `classify`, so
|
|
121
|
+
* a global flag ahead of `pip` (`uv --quiet pip install x`) does not
|
|
122
|
+
* defeat this either.
|
|
123
|
+
*/
|
|
124
|
+
function isNestedPipInvocation(rawBinary, argv) {
|
|
125
|
+
if (normalizeManagerBinary(rawBinary) !== "uv")
|
|
126
|
+
return false;
|
|
127
|
+
const firstNonFlagIndex = argv.findIndex((token, i) => i > 0 && !token.startsWith("-"));
|
|
128
|
+
return firstNonFlagIndex !== -1 && argv[firstNonFlagIndex].toLowerCase() === "pip";
|
|
129
|
+
}
|
|
130
|
+
/** The first token after argv[0] that is not a flag — where a manager's verb
|
|
131
|
+
* (or the fail-closed scan's target) is expected to start. */
|
|
132
|
+
function firstNonFlagIndexAfterBinary(argv) {
|
|
133
|
+
return argv.findIndex((token, i) => i > 0 && !token.startsWith("-"));
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Longest-sequence-first so a multi-token verb like `["mod", "download"]`
|
|
137
|
+
* matches as one verb rather than being pre-empted by a shorter prefix.
|
|
138
|
+
* Returns the matched verb (so its length is known to the caller), or
|
|
139
|
+
* `undefined` when nothing at `pos` matches.
|
|
140
|
+
*/
|
|
141
|
+
function matchedVerb(argv, pos, installVerbs) {
|
|
142
|
+
const sorted = [...installVerbs].sort((a, b) => b.length - a.length);
|
|
143
|
+
for (const verb of sorted) {
|
|
144
|
+
const candidate = argv.slice(pos, pos + verb.length);
|
|
145
|
+
if (candidate.length === verb.length && candidate.every((token, i) => token === verb[i])) {
|
|
146
|
+
return verb;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
function matchesVerbAt(argv, pos, installVerbs) {
|
|
152
|
+
return matchedVerb(argv, pos, installVerbs) !== undefined;
|
|
153
|
+
}
|
|
154
|
+
function classify(argv) {
|
|
155
|
+
const rawBinary = argv[0];
|
|
156
|
+
if (rawBinary === undefined)
|
|
157
|
+
return { kind: "generic" };
|
|
158
|
+
if (RUNNER_BINARIES.has(normalizeManagerBinary(rawBinary))) {
|
|
159
|
+
return {
|
|
160
|
+
kind: "deny",
|
|
161
|
+
reason: `"${rawBinary}" is a package runner that executes an arbitrary package; it cannot be classified as install-shaped or safely treated as generic`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (isKnownManager(rawBinary)) {
|
|
165
|
+
const disguised = disguisedManagerToken(argv);
|
|
166
|
+
if (disguised !== undefined) {
|
|
167
|
+
return {
|
|
168
|
+
kind: "deny",
|
|
169
|
+
reason: `"${rawBinary} ${argv[1]}" runs "${disguised}" — a disguised manager invocation via a dlx-style verb`,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (isNestedPipInvocation(rawBinary, argv)) {
|
|
173
|
+
return {
|
|
174
|
+
kind: "deny",
|
|
175
|
+
reason: `"${rawBinary} pip" nests pip inside another manager; not classifiable as install-shaped (uv's install verbs are sync/add, not pip) and not safely generic (pip's own directory/scripts screens would not apply)`,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const normalizedBinary = normalizeManagerBinary(rawBinary);
|
|
180
|
+
const entry = MANAGER_TABLE[normalizedBinary];
|
|
181
|
+
if (!entry)
|
|
182
|
+
return { kind: "generic" };
|
|
183
|
+
// Branch 1: the first non-flag token after argv[0] (or the sequence
|
|
184
|
+
// starting there) matches an install verb.
|
|
185
|
+
const firstNonFlagIndex = firstNonFlagIndexAfterBinary(argv);
|
|
186
|
+
if (firstNonFlagIndex !== -1 && matchesVerbAt(argv, firstNonFlagIndex, entry.installVerbs)) {
|
|
187
|
+
return { kind: "install" };
|
|
188
|
+
}
|
|
189
|
+
// Branch 2 (fail-closed): an install verb token appears somewhere else in
|
|
190
|
+
// argv. The call looks like an install but is not in a position this
|
|
191
|
+
// policy can normalize safely — deny, do not guess generic.
|
|
192
|
+
const flatVerbTokens = new Set(entry.installVerbs.flat());
|
|
193
|
+
for (let i = 1; i < argv.length; i++) {
|
|
194
|
+
if (flatVerbTokens.has(argv[i])) {
|
|
195
|
+
return {
|
|
196
|
+
kind: "deny",
|
|
197
|
+
reason: `argv contains the install verb "${argv[i]}" but not in a position this policy can classify safely: ${argv.join(" ")}`,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Branch 3: generic.
|
|
202
|
+
return { kind: "generic" };
|
|
203
|
+
}
|
|
204
|
+
export function classifyExec(argv) {
|
|
205
|
+
return classify(argv).kind;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* A scripts-control flag the model added itself (`--ignore-scripts`, or its
|
|
209
|
+
* negation `--no-ignore-scripts`) is not trusted, in either direction: the
|
|
210
|
+
* no-scripts mechanism this table appends must be the only thing deciding
|
|
211
|
+
* that, driven by `install.allowScripts` in project config — a human
|
|
212
|
+
* decision — never by a flag in the model-authored argv. Addendum finding
|
|
213
|
+
* C. Matches case- and `=`-normalized, mirroring `deniedFlag` in
|
|
214
|
+
* `exec-guard.ts`.
|
|
215
|
+
*/
|
|
216
|
+
const SCRIPTS_CONTROL_FLAG = /^--(no-)?ignore-scripts$/;
|
|
217
|
+
function scriptsControlConflict(argv) {
|
|
218
|
+
for (let i = 1; i < argv.length; i++) {
|
|
219
|
+
const token = argv[i];
|
|
220
|
+
if (SCRIPTS_CONTROL_FLAG.test(normalizeFlagToken(token)))
|
|
221
|
+
return token;
|
|
222
|
+
}
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
function applyNoScripts(result, mechanism) {
|
|
226
|
+
if ("error" in result)
|
|
227
|
+
return result;
|
|
228
|
+
if ("none" in mechanism)
|
|
229
|
+
return result;
|
|
230
|
+
if ("flag" in mechanism) {
|
|
231
|
+
return { ...result, argv: [...result.argv, mechanism.flag] };
|
|
232
|
+
}
|
|
233
|
+
return { ...result, env: { ...result.env, ...mechanism.env } };
|
|
234
|
+
}
|
|
235
|
+
/** Archive suffixes that make a pip/uv token path-shaped even without a
|
|
236
|
+
* leading path separator — a built sdist/wheel tarball path, e.g.
|
|
237
|
+
* `pip install ./dist/pkg-1.0.tar.gz` or a bare `pkg-1.0.whl` sitting in
|
|
238
|
+
* cwd. Not extended to every manager: an npm/bun/yarn tarball install is
|
|
239
|
+
* already path-shaped via a leading path segment in every case this table
|
|
240
|
+
* has to cover, and widening this check to every manager risks flagging a
|
|
241
|
+
* bare filename that merely happens to share an extension. */
|
|
242
|
+
const ARCHIVE_SUFFIXES = [".tgz", ".tar.gz", ".whl", ".zip"];
|
|
243
|
+
/**
|
|
244
|
+
* A token "looks like a path" when it starts with a path-relative or
|
|
245
|
+
* absolute prefix, or — for pip/uv only — ends in a known archive suffix.
|
|
246
|
+
* A bare package name (`httpx`), a version specifier (`pkg@1.2.3`,
|
|
247
|
+
* `pkg>=1.0`), and a Python extras spec (`httpx[http2]`) are none of these
|
|
248
|
+
* and must NOT be treated as path-shaped (finding 1, fix round 4).
|
|
249
|
+
*/
|
|
250
|
+
function isPathShapedToken(token, manager) {
|
|
251
|
+
if (token.startsWith("./") || token.startsWith("../") || token.startsWith("/") || token.startsWith("~")) {
|
|
252
|
+
return true;
|
|
253
|
+
}
|
|
254
|
+
if ((manager === "pip" || manager === "uv") && ARCHIVE_SUFFIXES.some((suffix) => token.endsWith(suffix))) {
|
|
255
|
+
return true;
|
|
256
|
+
}
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Finding 1 (fix round 4, Important/security): every screen in this table
|
|
261
|
+
* covers FLAGS only (registry/directory redirects) — a positional argument
|
|
262
|
+
* to an install verb was never containment-checked at all, so
|
|
263
|
+
* `["pip","install","/etc/passwd"]` or
|
|
264
|
+
* `["npm","install","../../../elsewhere/x.tgz"]` builds/installs from an
|
|
265
|
+
* arbitrary filesystem path. This matters most for pip: it ships in the
|
|
266
|
+
* default built-in allowlist and has no confirmed no-scripts mechanism (see
|
|
267
|
+
* `package-managers-table.ts`'s file header), so a `setup.py` from outside
|
|
268
|
+
* the permitted root would execute.
|
|
269
|
+
*
|
|
270
|
+
* Ruling: do not ban positional paths — a local path install
|
|
271
|
+
* (`bun add ./vendor/local-types`) is legitimate and the integration test
|
|
272
|
+
* depends on it. Containment-check any positional token that LOOKS like a
|
|
273
|
+
* path instead, resolved against `resolvedCwd` — the cwd this call will
|
|
274
|
+
* actually run from once normalized, taken from `packageForm`/`rootForm`'s
|
|
275
|
+
* own result rather than re-derived here, because which cwd an install
|
|
276
|
+
* actually runs from is manager-specific: npm/pnpm/yarn/cargo/uv always run
|
|
277
|
+
* from the repo root via their own scoping flag/subcommand, while
|
|
278
|
+
* bun/pip/go run from the package workdir when target is "package".
|
|
279
|
+
*
|
|
280
|
+
* Flags (leading `-`) are skipped outright when scanning for candidates —
|
|
281
|
+
* every flag that could redirect a path is already denied earlier in
|
|
282
|
+
* `normalizeExec` (`globalDirectoryConflict`/`installDirectoryConflict`), so
|
|
283
|
+
* only a boolean/enum flag or its non-path value can still be present here.
|
|
284
|
+
* Treating a flag's own value as a candidate anyway is harmless, not merely
|
|
285
|
+
* tolerated: a non-path-shaped value is ignored by `isPathShapedToken`, and
|
|
286
|
+
* a path-shaped one (pip's `-e ./local-pkg`, `-r ./requirements.txt`) is
|
|
287
|
+
* exactly the same containment concern as a bare positional path and
|
|
288
|
+
* deserves the same treatment.
|
|
289
|
+
*
|
|
290
|
+
* Reuses `resolveWithin` (`src/tools/policy.ts`) for the actual containment
|
|
291
|
+
* comparison rather than a second path-comparison implementation: this
|
|
292
|
+
* function only computes the absolute candidate itself (a relative token
|
|
293
|
+
* resolves against `resolvedCwd`, not the repo root, so `resolveWithin`
|
|
294
|
+
* cannot be asked to do that resolution step) and then hands it off already
|
|
295
|
+
* absolute, so it is `resolveWithin`'s own `isAbsolute` branch that runs the
|
|
296
|
+
* actual containment check.
|
|
297
|
+
*/
|
|
298
|
+
function findPositionalPathConflict(argv, entry, repoRoot, resolvedCwd) {
|
|
299
|
+
const manager = normalizeManagerBinary(argv[0]);
|
|
300
|
+
const verbStart = firstNonFlagIndexAfterBinary(argv);
|
|
301
|
+
if (verbStart === -1)
|
|
302
|
+
return undefined; // unreachable for an install-shaped call
|
|
303
|
+
const verb = matchedVerb(argv, verbStart, entry.installVerbs);
|
|
304
|
+
const afterVerb = verbStart + (verb?.length ?? 0);
|
|
305
|
+
for (let i = afterVerb; i < argv.length; i++) {
|
|
306
|
+
const token = argv[i];
|
|
307
|
+
if (token.startsWith("-"))
|
|
308
|
+
continue;
|
|
309
|
+
if (!isPathShapedToken(token, manager))
|
|
310
|
+
continue;
|
|
311
|
+
const absolute = token.startsWith("/") ? token : resolve(resolvedCwd, token);
|
|
312
|
+
if (resolveWithin(repoRoot, absolute) === null) {
|
|
313
|
+
return `argv contains a path-shaped argument "${token}" that resolves outside the permitted root`;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return undefined;
|
|
317
|
+
}
|
|
318
|
+
export function normalizeExec(input) {
|
|
319
|
+
const { argv, target, packageRelPath, allowScripts } = input;
|
|
320
|
+
// Rule: packageRelPath === "" means the story IS the repo root — both
|
|
321
|
+
// targets collapse to repoRoot, and no scoping flag naming a member is
|
|
322
|
+
// ever added.
|
|
323
|
+
const effectiveTarget = packageRelPath === "" ? "repoRoot" : target;
|
|
324
|
+
const classification = classify(argv);
|
|
325
|
+
if (classification.kind === "deny") {
|
|
326
|
+
return { error: classification.reason };
|
|
327
|
+
}
|
|
328
|
+
const binary = argv[0];
|
|
329
|
+
const entry = MANAGER_TABLE[normalizeManagerBinary(binary)];
|
|
330
|
+
// Fix round 2: a directory-redirect flag global to the manager hands cwd
|
|
331
|
+
// control back to the model regardless of whether the call is
|
|
332
|
+
// install-shaped or generic — `target` is a closed enum precisely so no
|
|
333
|
+
// path can arrive from the model, and this is the containment property
|
|
334
|
+
// itself. Checked before branching on classification kind so a generic
|
|
335
|
+
// call (`bun x tsc --cwd /elsewhere`) cannot escape it either.
|
|
336
|
+
if (entry) {
|
|
337
|
+
const globalDirConflict = entry.globalDirectoryConflict(argv);
|
|
338
|
+
if (globalDirConflict !== undefined) {
|
|
339
|
+
return {
|
|
340
|
+
error: `argv already redirects the working directory via "${globalDirConflict}"; this policy computes and controls cwd, never the model-authored argv`,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (classification.kind === "generic") {
|
|
345
|
+
// Generic calls get cwd from the target and nothing else — no
|
|
346
|
+
// mechanism, no scoping, no rewriting of any argument.
|
|
347
|
+
return { argv, cwd: effectiveTarget === "package" ? input.packageWorkdir : input.repoRoot };
|
|
348
|
+
}
|
|
349
|
+
if (!entry) {
|
|
350
|
+
// Unreachable: classify() only returns "install" for a binary that
|
|
351
|
+
// resolved to a MANAGER_TABLE entry. Kept as a defensive fallback
|
|
352
|
+
// rather than a non-null assertion (postfix `!` is banned in this repo).
|
|
353
|
+
return { argv, cwd: effectiveTarget === "package" ? input.packageWorkdir : input.repoRoot };
|
|
354
|
+
}
|
|
355
|
+
const installDirConflict = entry.installDirectoryConflict(argv);
|
|
356
|
+
if (installDirConflict !== undefined) {
|
|
357
|
+
return {
|
|
358
|
+
error: `argv already redirects the install destination via "${installDirConflict}"; this policy computes and controls cwd, never the model-authored argv`,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
// Fix round 1, Critical finding 2 + addendum C: DENY rather than strip
|
|
362
|
+
// when the incoming argv already carries a flag that would smuggle in a
|
|
363
|
+
// second workspace member, or a scripts-control flag the model is not
|
|
364
|
+
// entitled to set. Stripping either would change what the model asked
|
|
365
|
+
// for while reporting success.
|
|
366
|
+
const scopeConflict = entry.scopingConflict(argv);
|
|
367
|
+
if (scopeConflict !== undefined) {
|
|
368
|
+
return {
|
|
369
|
+
error: `argv already scopes this call via "${scopeConflict}"; refusing rather than combining it with the story's own package scoping`,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
const scriptsConflict = scriptsControlConflict(argv);
|
|
373
|
+
if (scriptsConflict !== undefined) {
|
|
374
|
+
return {
|
|
375
|
+
error: `argv already carries the scripts-control flag "${scriptsConflict}"; the no-scripts mechanism is decided by project config (install.allowScripts), never by the model-authored argv`,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
if (effectiveTarget === "package" && entry.needsPackageName && !input.packageName) {
|
|
379
|
+
return { error: `${binary} workspace scoping requires the member package's manifest name` };
|
|
380
|
+
}
|
|
381
|
+
const base = effectiveTarget === "package" ? entry.packageForm(argv, input) : entry.rootForm(argv, input);
|
|
382
|
+
if ("error" in base)
|
|
383
|
+
return base;
|
|
384
|
+
const positionalPathConflict = findPositionalPathConflict(argv, entry, input.repoRoot, base.cwd);
|
|
385
|
+
if (positionalPathConflict !== undefined) {
|
|
386
|
+
return { error: positionalPathConflict };
|
|
387
|
+
}
|
|
388
|
+
if (allowScripts)
|
|
389
|
+
return base;
|
|
390
|
+
return applyNoScripts(base, entry.noScripts(input));
|
|
391
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Split one string field into the path elements it really names.
|
|
3
|
+
*
|
|
4
|
+
* Lives on its own because two layers must agree on the answer exactly: the
|
|
5
|
+
* policy resolves and grant-checks each element (`ToolScope.listPathFields`),
|
|
6
|
+
* and the tool substitutes each element as its own shell argument. Were they
|
|
7
|
+
* to disagree, the policy would approve one set of paths and the shell would
|
|
8
|
+
* receive another (nax#1998).
|
|
9
|
+
*
|
|
10
|
+
* Whitespace is the separator, but only for a value that is really a LIST.
|
|
11
|
+
* `{{files}}` is a project-declared placeholder and a project may mean a NAME
|
|
12
|
+
* filter by it (`pytest -k {{files}}`, `jest -t {{files}}`), while a path may
|
|
13
|
+
* itself contain a space. Splitting unconditionally would turn one filter into
|
|
14
|
+
* several arguments and break any repo under `/tmp/my dir/`. So a multi-token
|
|
15
|
+
* value decides by ordered evidence tiers (nax#1998 + US-002):
|
|
16
|
+
*
|
|
17
|
+
* 1. One token (or none) -> the whole value. Never an empty list: an empty
|
|
18
|
+
* list would leave a caller's per-element loop with nothing to check, and
|
|
19
|
+
* the field would be approved by falling off the end of it.
|
|
20
|
+
* 2. The WHOLE value is an existing file -> the whole value. A path under
|
|
21
|
+
* `/tmp/my dir/` contains a space; its tokens are not separate paths. This
|
|
22
|
+
* is checked before every token-level test, so a space-bearing path can
|
|
23
|
+
* never be re-split.
|
|
24
|
+
* 3. ANY token is an existing file or an existing DIRECTORY -> the tokens.
|
|
25
|
+
* The directory case is what gives `test/unit test/integration` two
|
|
26
|
+
* arguments today.
|
|
27
|
+
* 4. EVERY token looks like a path -- contains a path separator, or ends in
|
|
28
|
+
* a file extension -> the tokens. Both slash spellings count, so a path
|
|
29
|
+
* list keeps its meaning across operating systems. This is deliberately
|
|
30
|
+
* language-neutral:
|
|
31
|
+
* it keys on path SYNTAX, never on a test-file naming convention, so it
|
|
32
|
+
* admits not-yet-created `test_foo.py` / `foo_test.go` / `Foo.test.ts`
|
|
33
|
+
* equally, without nax knowing the host project's language. It requires
|
|
34
|
+
* EVERY token to qualify, so a mixed name-filter-plus-path value where
|
|
35
|
+
* nothing exists keeps today's whole-value behaviour rather than guessing.
|
|
36
|
+
* 5. Otherwise -> the whole value. A multi-token name filter lands here.
|
|
37
|
+
*/
|
|
38
|
+
export declare function pathListElements(value: string, root: string): string[];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import { isAbsolute, resolve } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Split one string field into the path elements it really names.
|
|
5
|
+
*
|
|
6
|
+
* Lives on its own because two layers must agree on the answer exactly: the
|
|
7
|
+
* policy resolves and grant-checks each element (`ToolScope.listPathFields`),
|
|
8
|
+
* and the tool substitutes each element as its own shell argument. Were they
|
|
9
|
+
* to disagree, the policy would approve one set of paths and the shell would
|
|
10
|
+
* receive another (nax#1998).
|
|
11
|
+
*
|
|
12
|
+
* Whitespace is the separator, but only for a value that is really a LIST.
|
|
13
|
+
* `{{files}}` is a project-declared placeholder and a project may mean a NAME
|
|
14
|
+
* filter by it (`pytest -k {{files}}`, `jest -t {{files}}`), while a path may
|
|
15
|
+
* itself contain a space. Splitting unconditionally would turn one filter into
|
|
16
|
+
* several arguments and break any repo under `/tmp/my dir/`. So a multi-token
|
|
17
|
+
* value decides by ordered evidence tiers (nax#1998 + US-002):
|
|
18
|
+
*
|
|
19
|
+
* 1. One token (or none) -> the whole value. Never an empty list: an empty
|
|
20
|
+
* list would leave a caller's per-element loop with nothing to check, and
|
|
21
|
+
* the field would be approved by falling off the end of it.
|
|
22
|
+
* 2. The WHOLE value is an existing file -> the whole value. A path under
|
|
23
|
+
* `/tmp/my dir/` contains a space; its tokens are not separate paths. This
|
|
24
|
+
* is checked before every token-level test, so a space-bearing path can
|
|
25
|
+
* never be re-split.
|
|
26
|
+
* 3. ANY token is an existing file or an existing DIRECTORY -> the tokens.
|
|
27
|
+
* The directory case is what gives `test/unit test/integration` two
|
|
28
|
+
* arguments today.
|
|
29
|
+
* 4. EVERY token looks like a path -- contains a path separator, or ends in
|
|
30
|
+
* a file extension -> the tokens. Both slash spellings count, so a path
|
|
31
|
+
* list keeps its meaning across operating systems. This is deliberately
|
|
32
|
+
* language-neutral:
|
|
33
|
+
* it keys on path SYNTAX, never on a test-file naming convention, so it
|
|
34
|
+
* admits not-yet-created `test_foo.py` / `foo_test.go` / `Foo.test.ts`
|
|
35
|
+
* equally, without nax knowing the host project's language. It requires
|
|
36
|
+
* EVERY token to qualify, so a mixed name-filter-plus-path value where
|
|
37
|
+
* nothing exists keeps today's whole-value behaviour rather than guessing.
|
|
38
|
+
* 5. Otherwise -> the whole value. A multi-token name filter lands here.
|
|
39
|
+
*/
|
|
40
|
+
export function pathListElements(value, root) {
|
|
41
|
+
const tokens = value.split(/\s+/).filter((token) => token.length > 0);
|
|
42
|
+
if (tokens.length <= 1)
|
|
43
|
+
return [value];
|
|
44
|
+
if (isExistingFile(value, root))
|
|
45
|
+
return [value];
|
|
46
|
+
if (tokens.some((token) => isExistingFile(token, root) || isExistingDir(token, root)))
|
|
47
|
+
return tokens;
|
|
48
|
+
if (tokens.every(looksLikePath))
|
|
49
|
+
return tokens;
|
|
50
|
+
return [value];
|
|
51
|
+
}
|
|
52
|
+
function isExistingFile(candidate, root) {
|
|
53
|
+
const absolute = isAbsolute(candidate) ? candidate : resolve(root, candidate);
|
|
54
|
+
return statSync(absolute, { throwIfNoEntry: false })?.isFile() === true;
|
|
55
|
+
}
|
|
56
|
+
function isExistingDir(candidate, root) {
|
|
57
|
+
const absolute = isAbsolute(candidate) ? candidate : resolve(root, candidate);
|
|
58
|
+
return statSync(absolute, { throwIfNoEntry: false })?.isDirectory() === true;
|
|
59
|
+
}
|
|
60
|
+
function looksLikePath(token) {
|
|
61
|
+
return /[\\/]/.test(token) || /\.[A-Za-z0-9]+$/.test(token);
|
|
62
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BashCheck } from "./policy-bash.ts";
|
|
2
|
+
export interface RawScreenArgs {
|
|
3
|
+
readonly tool: string;
|
|
4
|
+
readonly command: unknown;
|
|
5
|
+
/** The shell's initial working directory. */
|
|
6
|
+
readonly initialPath: string;
|
|
7
|
+
/** Resolves a candidate from an effective shell working directory. */
|
|
8
|
+
readonly resolvePath: (candidate: string, cwd: string) => string | null;
|
|
9
|
+
/** The permitted root, used to relativise a resolved path. */
|
|
10
|
+
readonly root: string;
|
|
11
|
+
/**
|
|
12
|
+
* US-002: true when the command runs inside the OS sandbox (an available
|
|
13
|
+
* launcher). A token that only NAMES a feature PRD is then no longer refused
|
|
14
|
+
* -- reading it through Bash costs nothing, and the sandbox still blocks a
|
|
15
|
+
* write. Absent or false screens exactly as before.
|
|
16
|
+
*/
|
|
17
|
+
readonly sandboxWrapped?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function screenRawBashCommand(args: RawScreenArgs): BashCheck;
|