@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,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic path-keyed file lock.
|
|
3
|
+
*
|
|
4
|
+
* Keyed on an arbitrary file path (e.g. `metrics.json`) instead of the queue
|
|
5
|
+
* file. Used to serialize read-modify-write sequences across processes so a
|
|
6
|
+
* concurrent `saveRunMetrics` cannot race with another and drop one run's
|
|
7
|
+
* append (BUG-6).
|
|
8
|
+
*
|
|
9
|
+
* Implementation is the shared single-file exclusive-create lock in
|
|
10
|
+
* `file-lock.ts` (see its module doc for the #1731 correctness argument and
|
|
11
|
+
* the BUG-10 / BUG-25 staleness rules). The lock file lives next to the
|
|
12
|
+
* target as `<target>.lock`.
|
|
13
|
+
*
|
|
14
|
+
* Failure mode: the default 5s timeout aborts with a descriptive error —
|
|
15
|
+
* it never enters over a possible holder (fail closed).
|
|
16
|
+
*/
|
|
17
|
+
export interface PathFileLockOptions {
|
|
18
|
+
/** Milliseconds between acquisition retries. Default 10. */
|
|
19
|
+
retryMs?: number;
|
|
20
|
+
/** Maximum time to wait for the lock before throwing. Default 5000. */
|
|
21
|
+
timeoutMs?: number;
|
|
22
|
+
}
|
|
23
|
+
export declare function withPathFileLock<T>(targetPath: string, operation: () => Promise<T>, options?: PathFileLockOptions): Promise<T>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic path-keyed file lock.
|
|
3
|
+
*
|
|
4
|
+
* Keyed on an arbitrary file path (e.g. `metrics.json`) instead of the queue
|
|
5
|
+
* file. Used to serialize read-modify-write sequences across processes so a
|
|
6
|
+
* concurrent `saveRunMetrics` cannot race with another and drop one run's
|
|
7
|
+
* append (BUG-6).
|
|
8
|
+
*
|
|
9
|
+
* Implementation is the shared single-file exclusive-create lock in
|
|
10
|
+
* `file-lock.ts` (see its module doc for the #1731 correctness argument and
|
|
11
|
+
* the BUG-10 / BUG-25 staleness rules). The lock file lives next to the
|
|
12
|
+
* target as `<target>.lock`.
|
|
13
|
+
*
|
|
14
|
+
* Failure mode: the default 5s timeout aborts with a descriptive error —
|
|
15
|
+
* it never enters over a possible holder (fail closed).
|
|
16
|
+
*/
|
|
17
|
+
import { withFileLock } from "./file-lock.js";
|
|
18
|
+
export async function withPathFileLock(targetPath, operation, options = {}) {
|
|
19
|
+
return withFileLock(`${targetPath}.lock`, operation, {
|
|
20
|
+
retryMs: options.retryMs,
|
|
21
|
+
timeoutMs: options.timeoutMs,
|
|
22
|
+
lockName: "path",
|
|
23
|
+
errorPrefix: "[path-lock]",
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process Liveness Probe — SSOT
|
|
3
|
+
*
|
|
4
|
+
* Answers "is this PID still running?" for lock holders, queue holders, and
|
|
5
|
+
* spawned children. This is the only place in the codebase that interprets a
|
|
6
|
+
* `process.kill(pid, 0)` probe.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Check whether a process with the given PID is still alive.
|
|
10
|
+
*
|
|
11
|
+
* `process.kill(pid, 0)` sends no signal — it only performs the permission and
|
|
12
|
+
* existence checks the kernel would run for a real signal, then throws on
|
|
13
|
+
* failure. Interpreting that throw is the whole subtlety:
|
|
14
|
+
*
|
|
15
|
+
* | Outcome | Meaning | Alive? |
|
|
16
|
+
* |:---------------|:-------------------------------------------|:-------|
|
|
17
|
+
* | no throw | Probe succeeded | yes |
|
|
18
|
+
* | `ESRCH` | No such process | no |
|
|
19
|
+
* | `EPERM` | Process EXISTS, caller may not signal it | yes |
|
|
20
|
+
* | any other/none | Probe inconclusive | yes |
|
|
21
|
+
*
|
|
22
|
+
* Only `ESRCH` proves absence. A bare `catch { return false }` — the shape this
|
|
23
|
+
* helper replaces — misreads `EPERM` as "dead", so a live run owned by another
|
|
24
|
+
* user or another privilege level looks stale. That is a correctness bug for
|
|
25
|
+
* every caller that then reclaims the resource: `nax unlock` deletes a running
|
|
26
|
+
* process's lock, and lock acquisition lets a second run start on top of the
|
|
27
|
+
* first. Inconclusive errors therefore fail **safe** (report alive) rather than
|
|
28
|
+
* fail convenient.
|
|
29
|
+
*
|
|
30
|
+
* @param pid - Process ID to probe. Non-positive and non-finite values are
|
|
31
|
+
* never valid process IDs and report dead without probing — passing a
|
|
32
|
+
* negative PID to `process.kill` would address a process *group*.
|
|
33
|
+
* @returns true if the process exists (or its existence cannot be ruled out)
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* if (isProcessAlive(lockPid)) {
|
|
38
|
+
* throw new NaxError(`nax is already running (PID ${lockPid})`, "LOCK_ACQUISITION_FAILED", { stage: "file-lock" });
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function isProcessAlive(pid: number): boolean;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process Liveness Probe — SSOT
|
|
3
|
+
*
|
|
4
|
+
* Answers "is this PID still running?" for lock holders, queue holders, and
|
|
5
|
+
* spawned children. This is the only place in the codebase that interprets a
|
|
6
|
+
* `process.kill(pid, 0)` probe.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Check whether a process with the given PID is still alive.
|
|
10
|
+
*
|
|
11
|
+
* `process.kill(pid, 0)` sends no signal — it only performs the permission and
|
|
12
|
+
* existence checks the kernel would run for a real signal, then throws on
|
|
13
|
+
* failure. Interpreting that throw is the whole subtlety:
|
|
14
|
+
*
|
|
15
|
+
* | Outcome | Meaning | Alive? |
|
|
16
|
+
* |:---------------|:-------------------------------------------|:-------|
|
|
17
|
+
* | no throw | Probe succeeded | yes |
|
|
18
|
+
* | `ESRCH` | No such process | no |
|
|
19
|
+
* | `EPERM` | Process EXISTS, caller may not signal it | yes |
|
|
20
|
+
* | any other/none | Probe inconclusive | yes |
|
|
21
|
+
*
|
|
22
|
+
* Only `ESRCH` proves absence. A bare `catch { return false }` — the shape this
|
|
23
|
+
* helper replaces — misreads `EPERM` as "dead", so a live run owned by another
|
|
24
|
+
* user or another privilege level looks stale. That is a correctness bug for
|
|
25
|
+
* every caller that then reclaims the resource: `nax unlock` deletes a running
|
|
26
|
+
* process's lock, and lock acquisition lets a second run start on top of the
|
|
27
|
+
* first. Inconclusive errors therefore fail **safe** (report alive) rather than
|
|
28
|
+
* fail convenient.
|
|
29
|
+
*
|
|
30
|
+
* @param pid - Process ID to probe. Non-positive and non-finite values are
|
|
31
|
+
* never valid process IDs and report dead without probing — passing a
|
|
32
|
+
* negative PID to `process.kill` would address a process *group*.
|
|
33
|
+
* @returns true if the process exists (or its existence cannot be ruled out)
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* if (isProcessAlive(lockPid)) {
|
|
38
|
+
* throw new NaxError(`nax is already running (PID ${lockPid})`, "LOCK_ACQUISITION_FAILED", { stage: "file-lock" });
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function isProcessAlive(pid) {
|
|
43
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
44
|
+
return false;
|
|
45
|
+
try {
|
|
46
|
+
process.kill(pid, 0);
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
// Only ESRCH proves absence. EPERM, any other errno, and a throw carrying
|
|
51
|
+
// no errno at all all mean the same thing — the process was not proven
|
|
52
|
+
// gone — so they report alive. Reclaiming callers (lock steal, unlock
|
|
53
|
+
// delete) must never act on an inconclusive probe.
|
|
54
|
+
return error.code !== "ESRCH";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process Group Kill Utility
|
|
3
|
+
*
|
|
4
|
+
* Shared helper for killing process groups, preventing orphaned child processes.
|
|
5
|
+
* Uses process.kill(-pid, signal) to kill the entire process group.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Kill a process group by PID, falling back to single process if group kill fails.
|
|
9
|
+
*
|
|
10
|
+
* Process groups are spawned with the negative PID. This function:
|
|
11
|
+
* 1. Attempts to kill the entire process group (negative PID)
|
|
12
|
+
* 2. Falls back to killing the single process if group kill fails
|
|
13
|
+
*
|
|
14
|
+
* @param pid - Process ID (positive, not negative)
|
|
15
|
+
* @param signal - Signal to send (SIGTERM, SIGKILL, etc.)
|
|
16
|
+
* @returns true if kill was sent successfully, false if process already exited
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* if (killProcessGroup(proc.pid, "SIGTERM")) {
|
|
21
|
+
* console.log("Process group killed");
|
|
22
|
+
* } else {
|
|
23
|
+
* console.log("Process already exited");
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function killProcessGroup(pid: number, signal: NodeJS.Signals | number): boolean;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process Group Kill Utility
|
|
3
|
+
*
|
|
4
|
+
* Shared helper for killing process groups, preventing orphaned child processes.
|
|
5
|
+
* Uses process.kill(-pid, signal) to kill the entire process group.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Kill a process group by PID, falling back to single process if group kill fails.
|
|
9
|
+
*
|
|
10
|
+
* Process groups are spawned with the negative PID. This function:
|
|
11
|
+
* 1. Attempts to kill the entire process group (negative PID)
|
|
12
|
+
* 2. Falls back to killing the single process if group kill fails
|
|
13
|
+
*
|
|
14
|
+
* @param pid - Process ID (positive, not negative)
|
|
15
|
+
* @param signal - Signal to send (SIGTERM, SIGKILL, etc.)
|
|
16
|
+
* @returns true if kill was sent successfully, false if process already exited
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* if (killProcessGroup(proc.pid, "SIGTERM")) {
|
|
21
|
+
* console.log("Process group killed");
|
|
22
|
+
* } else {
|
|
23
|
+
* console.log("Process already exited");
|
|
24
|
+
* }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function killProcessGroup(pid, signal) {
|
|
28
|
+
// A pid that is not a real child reaches other processes: 0 is our own group,
|
|
29
|
+
// 1 (as -1) is every process we may signal, and a negative pid is init or a
|
|
30
|
+
// foreign group. A spawn that failed has no pid, so refuse rather than signal.
|
|
31
|
+
if (!Number.isInteger(pid) || pid <= 1)
|
|
32
|
+
return false;
|
|
33
|
+
// Try process group kill first (negative PID)
|
|
34
|
+
try {
|
|
35
|
+
process.kill(-pid, signal);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
const err = error;
|
|
40
|
+
// ESRCH = no such process — process group doesn't exist, try single process
|
|
41
|
+
if (err.code === "ESRCH") {
|
|
42
|
+
try {
|
|
43
|
+
process.kill(pid, signal);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// Process already exited
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Other errors (EPERM, etc.) — signal was likely sent, return true
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symlink-tolerant path normalisation for comparing paths from different sources.
|
|
3
|
+
*
|
|
4
|
+
* `getGitRoot` returns git's realpath — `git rev-parse --show-toplevel` run from
|
|
5
|
+
* `/tmp/repo` answers `/private/tmp/repo` — while paths threaded through config,
|
|
6
|
+
* pipeline context, or a story record keep whatever spelling the caller supplied.
|
|
7
|
+
* On macOS `/tmp` is a symlink to `/private/tmp` and worktrees are created under
|
|
8
|
+
* temp directories, so the two spellings meet constantly. Comparing them
|
|
9
|
+
* unresolved makes equal paths look different, and every containment check
|
|
10
|
+
* (`startsWith(root)`) silently fails closed.
|
|
11
|
+
*
|
|
12
|
+
* Pure apart from the `realpathSync` probe, and never throws.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Absolute, symlink-resolved form of `p`.
|
|
16
|
+
*
|
|
17
|
+
* A path that does not exist cannot be resolved directly, so this walks up to
|
|
18
|
+
* the nearest ancestor that DOES exist, resolves that, and re-attaches the
|
|
19
|
+
* remaining segments. Resolving only the immediate parent is not enough: a
|
|
20
|
+
* changed file that has since been deleted, or one under a directory that was
|
|
21
|
+
* never created, leaves several missing segments — and a half-resolved answer
|
|
22
|
+
* compares unequal to a fully-resolved root, turning every containment check
|
|
23
|
+
* into a silent false negative.
|
|
24
|
+
*
|
|
25
|
+
* Falls back to the lexical absolute form when no ancestor resolves.
|
|
26
|
+
*/
|
|
27
|
+
export declare function realOrRaw(p: string): string;
|
|
28
|
+
/** Is `filePath` inside `root`'s subtree, symlinks resolved on both sides? */
|
|
29
|
+
export declare function isInside(root: string, filePath: string): boolean;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Symlink-tolerant path normalisation for comparing paths from different sources.
|
|
3
|
+
*
|
|
4
|
+
* `getGitRoot` returns git's realpath — `git rev-parse --show-toplevel` run from
|
|
5
|
+
* `/tmp/repo` answers `/private/tmp/repo` — while paths threaded through config,
|
|
6
|
+
* pipeline context, or a story record keep whatever spelling the caller supplied.
|
|
7
|
+
* On macOS `/tmp` is a symlink to `/private/tmp` and worktrees are created under
|
|
8
|
+
* temp directories, so the two spellings meet constantly. Comparing them
|
|
9
|
+
* unresolved makes equal paths look different, and every containment check
|
|
10
|
+
* (`startsWith(root)`) silently fails closed.
|
|
11
|
+
*
|
|
12
|
+
* Pure apart from the `realpathSync` probe, and never throws.
|
|
13
|
+
*/
|
|
14
|
+
import { realpathSync } from "node:fs";
|
|
15
|
+
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
16
|
+
/**
|
|
17
|
+
* Absolute, symlink-resolved form of `p`.
|
|
18
|
+
*
|
|
19
|
+
* A path that does not exist cannot be resolved directly, so this walks up to
|
|
20
|
+
* the nearest ancestor that DOES exist, resolves that, and re-attaches the
|
|
21
|
+
* remaining segments. Resolving only the immediate parent is not enough: a
|
|
22
|
+
* changed file that has since been deleted, or one under a directory that was
|
|
23
|
+
* never created, leaves several missing segments — and a half-resolved answer
|
|
24
|
+
* compares unequal to a fully-resolved root, turning every containment check
|
|
25
|
+
* into a silent false negative.
|
|
26
|
+
*
|
|
27
|
+
* Falls back to the lexical absolute form when no ancestor resolves.
|
|
28
|
+
*/
|
|
29
|
+
export function realOrRaw(p) {
|
|
30
|
+
const abs = resolve(p);
|
|
31
|
+
const trailing = [];
|
|
32
|
+
let current = abs;
|
|
33
|
+
for (;;) {
|
|
34
|
+
try {
|
|
35
|
+
return trailing.length === 0 ? realpathSync(current) : join(realpathSync(current), ...trailing);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
const parent = dirname(current);
|
|
39
|
+
// `dirname` is a fixed point at the filesystem root — nothing resolved.
|
|
40
|
+
if (parent === current)
|
|
41
|
+
return abs;
|
|
42
|
+
trailing.unshift(basename(current));
|
|
43
|
+
current = parent;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Is `filePath` inside `root`'s subtree, symlinks resolved on both sides? */
|
|
48
|
+
export function isInside(root, filePath) {
|
|
49
|
+
const base = realOrRaw(root);
|
|
50
|
+
const target = realOrRaw(filePath);
|
|
51
|
+
return target === base || target.startsWith(`${base}${sep}`);
|
|
52
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret-redaction helper for the JSONL logger write path.
|
|
3
|
+
*
|
|
4
|
+
* Two layers of protection:
|
|
5
|
+
* 1. Key-based: any field whose name matches SECRET_KEY_PATTERN gets its value
|
|
6
|
+
* replaced with "[REDACTED]" regardless of content.
|
|
7
|
+
* 2. Pattern-based: free-text strings (in values that did NOT trigger layer 1)
|
|
8
|
+
* are scanned for token-shaped substrings (API keys, PATs, etc.).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Patterns are reset via `re.lastIndex = 0` before every call because they carry
|
|
12
|
+
* the `/g` flag (required for `String.replace`). Resetting prevents the stale
|
|
13
|
+
* `lastIndex` bug that skips matches on subsequent calls.
|
|
14
|
+
*/
|
|
15
|
+
/** One secret-value pattern and the kind label a masked span shows. */
|
|
16
|
+
export interface SecretValuePattern {
|
|
17
|
+
readonly kind: string;
|
|
18
|
+
readonly re: RegExp;
|
|
19
|
+
}
|
|
20
|
+
export declare const SECRET_VALUE_PATTERNS: readonly SecretValuePattern[];
|
|
21
|
+
/**
|
|
22
|
+
* Recursively walk `input` and return a new value with secrets removed.
|
|
23
|
+
* - Primitive non-strings are returned as-is.
|
|
24
|
+
* - Object keys matching SECRET_KEY_PATTERN → value replaced with "[REDACTED]".
|
|
25
|
+
* - String values (not key-redacted) → token-pattern scan.
|
|
26
|
+
* - Arrays and nested objects are walked recursively.
|
|
27
|
+
*/
|
|
28
|
+
export declare function redactSecrets<T>(input: T): T;
|
|
29
|
+
/**
|
|
30
|
+
* Redact a whole log entry — both the free-text `message` and the structured
|
|
31
|
+
* `data` payload.
|
|
32
|
+
*
|
|
33
|
+
* `message` matters as much as `data`: callers routinely interpolate shell
|
|
34
|
+
* commands, agent stderr, and error text into it, any of which can carry a
|
|
35
|
+
* credential. Redacting only `data` left those in cleartext.
|
|
36
|
+
*/
|
|
37
|
+
export declare function redactEntry<T extends {
|
|
38
|
+
message: string;
|
|
39
|
+
data?: Record<string, unknown>;
|
|
40
|
+
}>(entry: T): T;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret-redaction helper for the JSONL logger write path.
|
|
3
|
+
*
|
|
4
|
+
* Two layers of protection:
|
|
5
|
+
* 1. Key-based: any field whose name matches SECRET_KEY_PATTERN gets its value
|
|
6
|
+
* replaced with "[REDACTED]" regardless of content.
|
|
7
|
+
* 2. Pattern-based: free-text strings (in values that did NOT trigger layer 1)
|
|
8
|
+
* are scanned for token-shaped substrings (API keys, PATs, etc.).
|
|
9
|
+
*/
|
|
10
|
+
// TOKEN(?!s\b) prevents matching plural metric keys like "tokens", "inputTokens",
|
|
11
|
+
// "totalTokens" (which are counts, not credentials) while still matching "token",
|
|
12
|
+
// "GITHUB_TOKEN", "accessToken", etc.
|
|
13
|
+
// SEC-1 (Round 2 review): URL/URI/DSN/_URL/CONNECTIONSTRING were missing —
|
|
14
|
+
// DATABASE_URL=postgres://admin:s3cret@db/prod passed through both redaction
|
|
15
|
+
// layers (the KEY=value regex required SECRET|TOKEN|... before `=`, which
|
|
16
|
+
// DATABASE_URL doesn't satisfy; the key pattern didn't match either).
|
|
17
|
+
// SEC-3: AUTHORIZATION/COOKIE/CREDENTIAL/PASSWD were missing. Deliberately
|
|
18
|
+
// narrowed — this is an unanchored substring test, so AUTH(?:ORIZATION)?
|
|
19
|
+
// would also match author/authorName, and SESSION would match
|
|
20
|
+
// sessionName/sessionId/sessionScratchDir, the run log's primary correlation
|
|
21
|
+
// key (see prompt-auditor.ts:300-302). COOKIE already covers SET-COOKIE and
|
|
22
|
+
// setCookie; sessionToken stays covered by the TOKEN branch.
|
|
23
|
+
const SECRET_KEY_PATTERN = /(SECRET|TOKEN(?!s\b)|API_?KEY|PASSWORD|PRIVATE_?KEY|ACCESS_?KEY|WEBHOOK|AUTHORIZATION|COOKIE|CREDENTIAL|PASSWD|(?:\w+)?_URL|\w+_URI|\w+_DSN|CONNECTION\s*STRING)/i;
|
|
24
|
+
export const SECRET_VALUE_PATTERNS = [
|
|
25
|
+
{ kind: "openai", re: /sk-[A-Za-z0-9_-]{16,}/g },
|
|
26
|
+
{ kind: "github", re: /ghp_[A-Za-z0-9]{16,}/g },
|
|
27
|
+
{ kind: "github", re: /gh[opsu]_[A-Za-z0-9]{16,}/g },
|
|
28
|
+
// LOG-1: GitHub fine-grained PATs — gh[opsu]_ above never matches "github_pat_"
|
|
29
|
+
// ("gh" + "i" is not one of [opsu]).
|
|
30
|
+
{ kind: "github-pat", re: /github_pat_[A-Za-z0-9_]{20,}/g },
|
|
31
|
+
{ kind: "npm", re: /npm_[A-Za-z0-9]{8,}/g },
|
|
32
|
+
{ kind: "aws", re: /AKIA[0-9A-Z]{16}/g },
|
|
33
|
+
{ kind: "slack", re: /xox[baprs]-[A-Za-z0-9-]{10,}/g },
|
|
34
|
+
// LOG-1: Telegram bot tokens ("<bot-id>:<35-char secret>").
|
|
35
|
+
{ kind: "telegram", re: /\b\d{6,}:[A-Za-z0-9_-]{30,}\b/g },
|
|
36
|
+
// KEY=value assignments inside strings (e.g. "NPM_TOKEN=somevalue")
|
|
37
|
+
{ kind: "assignment", re: /(?:SECRET|TOKEN|API_?KEY|PASSWORD|PRIVATE_?KEY|ACCESS_?KEY|WEBHOOK)=[^\s"',]+/gi },
|
|
38
|
+
// MED-01: PEM-encoded key/cert blocks (private keys, certificates, incl.
|
|
39
|
+
// PGP's " ... BLOCK" suffix). The gap between BEGIN/END is bounded to
|
|
40
|
+
// 64KB — generous enough for any realistic single key or bundled
|
|
41
|
+
// certificate chain, while still capping the rescan cost of an
|
|
42
|
+
// unterminated "BEGIN" marker in a pathologically large payload on this
|
|
43
|
+
// synchronous logger write path (a small bound like a few KB risks
|
|
44
|
+
// missing legitimate multi-cert chain bundles entirely).
|
|
45
|
+
{
|
|
46
|
+
kind: "pem",
|
|
47
|
+
re: /-----BEGIN [A-Z ]*(?:PRIVATE KEY|CERTIFICATE)(?: BLOCK)?-----[\s\S]{0,65536}?-----END [A-Z ]*(?:PRIVATE KEY|CERTIFICATE)(?: BLOCK)?-----/g,
|
|
48
|
+
},
|
|
49
|
+
// MED-01: JWTs (header.payload.signature, base64url segments).
|
|
50
|
+
{ kind: "jwt", re: /eyJ[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}\.[A-Za-z0-9_-]{4,}/g },
|
|
51
|
+
// MED-01: Authorization header values (Bearer/Basic schemes). Requires a
|
|
52
|
+
// credential-shaped value (>=8 chars, at least one digit/symbol) so
|
|
53
|
+
// ordinary prose like "Basic authentication failed" or "Bearer token
|
|
54
|
+
// scheme" isn't swallowed — real bearer tokens and base64 basic creds
|
|
55
|
+
// almost always contain a non-alphabetic character; English words
|
|
56
|
+
// essentially never do. Length floor kept at 8 (not raised to 16) so
|
|
57
|
+
// short-but-real base64 credentials (e.g. "user:pass" -> ~16 raw chars,
|
|
58
|
+
// shorter inputs shorter still) aren't under-redacted.
|
|
59
|
+
{ kind: "bearer", re: /\bBearer\s+(?=[A-Za-z0-9\-._~+/]*[0-9+/_-])[A-Za-z0-9\-._~+/]{8,}={0,2}/gi },
|
|
60
|
+
// LOG-1: Basic creds are base64(user:pass) and are frequently pure-alphabetic
|
|
61
|
+
// (no digit/symbol), which the digit-requiring lookahead above misses. Require
|
|
62
|
+
// mixed case instead — real base64 output almost always mixes upper/lower,
|
|
63
|
+
// while English prose like "Basic authentication" does not. No `i` flag here
|
|
64
|
+
// (unlike Bearer): case-insensitivity would make [A-Z]/[a-z] equivalent,
|
|
65
|
+
// defeating the mixed-case check — so the "basic" keyword is spelled out
|
|
66
|
+
// char-by-char to stay case-insensitive for the scheme name only (the
|
|
67
|
+
// scheme is case-insensitive per RFC 7617; "basic"/"BASIC" are as valid as
|
|
68
|
+
// "Basic") while the payload lookaheads remain case-sensitive.
|
|
69
|
+
{
|
|
70
|
+
kind: "basic",
|
|
71
|
+
re: /\b[Bb][Aa][Ss][Ii][Cc]\s+(?=[A-Za-z0-9+/]*[A-Z])(?=[A-Za-z0-9+/]*[a-z])[A-Za-z0-9+/]{8,}={0,2}/g,
|
|
72
|
+
},
|
|
73
|
+
// MED-01: header-style key:value / key=value pairs for api-key headers
|
|
74
|
+
// that SECRET_KEY_PATTERN's object-key check can't reach because the
|
|
75
|
+
// key/value are both embedded in one free-text string (e.g. raw HTTP logs).
|
|
76
|
+
{ kind: "api-key-header", re: /(?:x-api-key|api[_-]?key)\s*[:=]\s*[^\s"',]+/gi },
|
|
77
|
+
// SEC-3: Cookie / Set-Cookie headers interpolated into a free-text string
|
|
78
|
+
// (agent stderr is the common path) — SECRET_KEY_PATTERN only sees object
|
|
79
|
+
// keys, not header text. Consumes the rest of the line; cookie values are
|
|
80
|
+
// opaque and line-delimited in header dumps, so a narrower character class
|
|
81
|
+
// would only risk under-redacting the tail of a multi-cookie header.
|
|
82
|
+
{ kind: "cookie", re: /\b(?:Set-)?Cookie\s*:\s*[^\r\n]+/gi },
|
|
83
|
+
// SEC-1 (Round 2 review): URL-embedded credentials (scheme://user:pass@host).
|
|
84
|
+
// Matches a scheme name (lowercase letters, digits, +, ., -), then ://, then
|
|
85
|
+
// optional user:password (no /, whitespace, or @), then @. The colon + @
|
|
86
|
+
// is the load-bearing signal that a credential is present; credential-less
|
|
87
|
+
// URLs like "https://example.com/foo" are intentionally left alone because
|
|
88
|
+
// they have no colon between :// and @.
|
|
89
|
+
// Examples:
|
|
90
|
+
// postgres://admin:s3cret@db.internal:5432/prod → "postgres://admin:s3cret@"
|
|
91
|
+
// redis://:hunter2@cache.internal:6379/0 → "redis://:hunter2@" (empty user)
|
|
92
|
+
// mongodb://root:mongoPwd@mongo.internal:27017 → "mongodb://root:mongoPwd@"
|
|
93
|
+
{ kind: "url-credentials", re: /\b[a-z][a-z0-9+.-]*:\/\/(?:[^/\s@]*:[^/\s@]+)@/gi },
|
|
94
|
+
];
|
|
95
|
+
const REDACTED = "[REDACTED]";
|
|
96
|
+
function redactString(value) {
|
|
97
|
+
let out = value;
|
|
98
|
+
for (const { re } of SECRET_VALUE_PATTERNS) {
|
|
99
|
+
re.lastIndex = 0;
|
|
100
|
+
out = out.replace(re, REDACTED);
|
|
101
|
+
}
|
|
102
|
+
return out;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Recursively walk `input` and return a new value with secrets removed.
|
|
106
|
+
* - Primitive non-strings are returned as-is.
|
|
107
|
+
* - Object keys matching SECRET_KEY_PATTERN → value replaced with "[REDACTED]".
|
|
108
|
+
* - String values (not key-redacted) → token-pattern scan.
|
|
109
|
+
* - Arrays and nested objects are walked recursively.
|
|
110
|
+
*/
|
|
111
|
+
export function redactSecrets(input) {
|
|
112
|
+
return redactValue(input);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Redact a whole log entry — both the free-text `message` and the structured
|
|
116
|
+
* `data` payload.
|
|
117
|
+
*
|
|
118
|
+
* `message` matters as much as `data`: callers routinely interpolate shell
|
|
119
|
+
* commands, agent stderr, and error text into it, any of which can carry a
|
|
120
|
+
* credential. Redacting only `data` left those in cleartext.
|
|
121
|
+
*/
|
|
122
|
+
export function redactEntry(entry) {
|
|
123
|
+
return {
|
|
124
|
+
...entry,
|
|
125
|
+
message: redactString(entry.message),
|
|
126
|
+
...(entry.data ? { data: redactValue(entry.data) } : {}),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
/** Depth guard is a backstop against pathological (non-circular but very deep) input; the WeakSet is what actually stops cycles. */
|
|
130
|
+
const MAX_REDACT_DEPTH = 100;
|
|
131
|
+
const CIRCULAR_REF_MARKER = "[Circular]";
|
|
132
|
+
/**
|
|
133
|
+
* MED-02: unguarded recursion here threw a RangeError (stack overflow) out
|
|
134
|
+
* of every logger call whenever a data payload contained a circular
|
|
135
|
+
* reference — a single bad log call could crash whatever code path was
|
|
136
|
+
* trying to log. `seen` tracks objects/arrays currently on the recursion
|
|
137
|
+
* stack (removed on the way back out, so the same object appearing twice at
|
|
138
|
+
* different, non-nested positions is still redacted normally) and
|
|
139
|
+
* `MAX_REDACT_DEPTH` bounds pathologically deep-but-acyclic input.
|
|
140
|
+
*/
|
|
141
|
+
function redactValue(input, seen = new WeakSet(), depth = 0) {
|
|
142
|
+
if (typeof input === "string")
|
|
143
|
+
return redactString(input);
|
|
144
|
+
if (input === null || typeof input !== "object")
|
|
145
|
+
return input;
|
|
146
|
+
if (depth >= MAX_REDACT_DEPTH || seen.has(input))
|
|
147
|
+
return CIRCULAR_REF_MARKER;
|
|
148
|
+
seen.add(input);
|
|
149
|
+
try {
|
|
150
|
+
if (Array.isArray(input))
|
|
151
|
+
return input.map((item) => redactValue(item, seen, depth + 1));
|
|
152
|
+
const out = {};
|
|
153
|
+
for (const [key, value] of Object.entries(input)) {
|
|
154
|
+
out[key] = SECRET_KEY_PATTERN.test(key) ? REDACTED : redactValue(value, seen, depth + 1);
|
|
155
|
+
}
|
|
156
|
+
return out;
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
seen.delete(input);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparators for `Array.prototype.sort`.
|
|
3
|
+
*
|
|
4
|
+
* A bare `.sort()` coerces every element to a string and orders by UTF-16 code
|
|
5
|
+
* unit. That is correct for a `string[]` and silently wrong for a `number[]`
|
|
6
|
+
* (`[2, 10]` sorts to `[10, 2]`), so `suspicious/useArraySortCompare` bans the
|
|
7
|
+
* bare form outright. These are the comparators it should resolve to.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Code-point ordering for strings.
|
|
11
|
+
*
|
|
12
|
+
* Byte-identical to what a bare `.sort()` already does on a `string[]`, so
|
|
13
|
+
* adopting it never changes an existing ordering. Preferred over
|
|
14
|
+
* `localeCompare` wherever the order is part of a contract: locale-aware
|
|
15
|
+
* collation is not stable across locales or ICU versions (CTX-5 — see
|
|
16
|
+
* `src/context/engine/digest.ts`).
|
|
17
|
+
*/
|
|
18
|
+
export declare const byCodePoint: (a: string, b: string) => number;
|
|
19
|
+
/** Ascending numeric ordering — the comparison a bare `.sort()` does *not* do. */
|
|
20
|
+
export declare const byNumber: (a: number, b: number) => number;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comparators for `Array.prototype.sort`.
|
|
3
|
+
*
|
|
4
|
+
* A bare `.sort()` coerces every element to a string and orders by UTF-16 code
|
|
5
|
+
* unit. That is correct for a `string[]` and silently wrong for a `number[]`
|
|
6
|
+
* (`[2, 10]` sorts to `[10, 2]`), so `suspicious/useArraySortCompare` bans the
|
|
7
|
+
* bare form outright. These are the comparators it should resolve to.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Code-point ordering for strings.
|
|
11
|
+
*
|
|
12
|
+
* Byte-identical to what a bare `.sort()` already does on a `string[]`, so
|
|
13
|
+
* adopting it never changes an existing ordering. Preferred over
|
|
14
|
+
* `localeCompare` wherever the order is part of a contract: locale-aware
|
|
15
|
+
* collation is not stable across locales or ICU versions (CTX-5 — see
|
|
16
|
+
* `src/context/engine/digest.ts`).
|
|
17
|
+
*/
|
|
18
|
+
export const byCodePoint = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
19
|
+
/** Ascending numeric ordering — the comparison a bare `.sort()` does *not* do. */
|
|
20
|
+
export const byNumber = (a, b) => a - b;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEC-09: strip ANSI/escape sequences and other C0 control characters from
|
|
3
|
+
* agent-controlled or PRD-authored display strings before they reach a
|
|
4
|
+
* terminal renderer.
|
|
5
|
+
*
|
|
6
|
+
* Ink's <Text> does not sanitize its children, and the headless console
|
|
7
|
+
* formatter writes raw strings straight to stdout. A crafted prd.json field
|
|
8
|
+
* (story title, failure reason) or agent output containing ESC (\x1b)
|
|
9
|
+
* sequences can move the cursor, clear the screen, or (via OSC 52) write to
|
|
10
|
+
* the clipboard in a terminal showing the user's session — defense-in-depth
|
|
11
|
+
* against a prompt-injected repo or a malicious PRD, not a primary trust
|
|
12
|
+
* boundary (the LLM already writes code that runs).
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Removes ESC-initiated sequences (CSI, OSC, and other ANSI escape forms)
|
|
16
|
+
* and other C0 control characters, while leaving normal whitespace
|
|
17
|
+
* (tab, newline, carriage return) untouched.
|
|
18
|
+
*/
|
|
19
|
+
export declare function stripControlChars(input: string): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEC-09: strip ANSI/escape sequences and other C0 control characters from
|
|
3
|
+
* agent-controlled or PRD-authored display strings before they reach a
|
|
4
|
+
* terminal renderer.
|
|
5
|
+
*
|
|
6
|
+
* Ink's <Text> does not sanitize its children, and the headless console
|
|
7
|
+
* formatter writes raw strings straight to stdout. A crafted prd.json field
|
|
8
|
+
* (story title, failure reason) or agent output containing ESC (\x1b)
|
|
9
|
+
* sequences can move the cursor, clear the screen, or (via OSC 52) write to
|
|
10
|
+
* the clipboard in a terminal showing the user's session — defense-in-depth
|
|
11
|
+
* against a prompt-injected repo or a malicious PRD, not a primary trust
|
|
12
|
+
* boundary (the LLM already writes code that runs).
|
|
13
|
+
*/
|
|
14
|
+
// C0 control range 0x00-0x1F and DEL (0x7F), excluding tab (\x09), newline
|
|
15
|
+
// (\x0A), and carriage return (\x0D) — display strings are frequently
|
|
16
|
+
// multi-line and stripping those would corrupt legitimate formatting for no
|
|
17
|
+
// security benefit.
|
|
18
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: this IS the control-character stripper
|
|
19
|
+
const CONTROL_CHARS_RE = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
|
|
20
|
+
/**
|
|
21
|
+
* Removes ESC-initiated sequences (CSI, OSC, and other ANSI escape forms)
|
|
22
|
+
* and other C0 control characters, while leaving normal whitespace
|
|
23
|
+
* (tab, newline, carriage return) untouched.
|
|
24
|
+
*/
|
|
25
|
+
export function stripControlChars(input) {
|
|
26
|
+
if (!input)
|
|
27
|
+
return input;
|
|
28
|
+
// ESC-initiated sequences: OSC "\x1b]...BEL-or-ST", CSI "\x1b[...<final-byte>",
|
|
29
|
+
// and any other "\x1b" + single byte (short escapes like \x1bc, \x1b=).
|
|
30
|
+
const withoutEscapes = input
|
|
31
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: matching a real ESC/BEL/ST sequence to strip
|
|
32
|
+
.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "") // OSC ... BEL | OSC ... ST
|
|
33
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: matching a real ESC (CSI) sequence to strip
|
|
34
|
+
.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "") // CSI ... final byte
|
|
35
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: matching a real ESC sequence to strip
|
|
36
|
+
.replace(/\x1b[ -~]/g, ""); // other short two-byte escapes (ESC + one printable byte, e.g. ESC c, ESC =, ESC 7)
|
|
37
|
+
return withoutEscapes.replace(CONTROL_CHARS_RE, "");
|
|
38
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is `value` a promise-like — something `await` would suspend on?
|
|
3
|
+
*
|
|
4
|
+
* Two callers need the SAME answer rather than two near-copies: the loop-event
|
|
5
|
+
* dispatcher decides with it whether an `await` is warranted at all (a
|
|
6
|
+
* synchronous answer must not pay a needless microtask yield), and the plugin
|
|
7
|
+
* stager uses it to detect an `async register()` contract violation. A single
|
|
8
|
+
* predicate keeps those from diverging on an exotic function-thenable.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isThenable(value: unknown): value is PromiseLike<unknown>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Is `value` a promise-like — something `await` would suspend on?
|
|
3
|
+
*
|
|
4
|
+
* Two callers need the SAME answer rather than two near-copies: the loop-event
|
|
5
|
+
* dispatcher decides with it whether an `await` is warranted at all (a
|
|
6
|
+
* synchronous answer must not pay a needless microtask yield), and the plugin
|
|
7
|
+
* stager uses it to detect an `async register()` contract violation. A single
|
|
8
|
+
* predicate keeps those from diverging on an exotic function-thenable.
|
|
9
|
+
*/
|
|
10
|
+
export function isThenable(value) {
|
|
11
|
+
if (value === null)
|
|
12
|
+
return false;
|
|
13
|
+
const type = typeof value;
|
|
14
|
+
return (type === "object" || type === "function") && typeof value.then === "function";
|
|
15
|
+
}
|