clinkx 1.0.0 → 1.1.1
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 +95 -0
- package/README.md +184 -112
- package/clinkx-workflows/dist/chairman-knobs.d.ts +24 -0
- package/clinkx-workflows/dist/chairman-knobs.js +27 -0
- package/clinkx-workflows/dist/chairman-knobs.js.map +1 -0
- package/clinkx-workflows/dist/clink-client.d.ts +10 -0
- package/clinkx-workflows/dist/clink-client.js +39 -1
- package/clinkx-workflows/dist/clink-client.js.map +1 -1
- package/clinkx-workflows/dist/config.d.ts +29 -1
- package/clinkx-workflows/dist/config.js +35 -2
- package/clinkx-workflows/dist/config.js.map +1 -1
- package/clinkx-workflows/dist/context-preflight.d.ts +5 -0
- package/clinkx-workflows/dist/context-preflight.js.map +1 -1
- package/clinkx-workflows/dist/council-execution-policy.d.ts +68 -0
- package/clinkx-workflows/dist/council-execution-policy.js +110 -0
- package/clinkx-workflows/dist/council-execution-policy.js.map +1 -0
- package/clinkx-workflows/dist/council-expander.d.ts +9 -8
- package/clinkx-workflows/dist/council-expander.js +18 -9
- package/clinkx-workflows/dist/council-expander.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +5557 -4
- package/clinkx-workflows/dist/dashboard/activity-contract.js +55 -3
- package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +9 -5
- package/clinkx-workflows/dist/dashboard/activity-reader.js +15 -2
- package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/api-routes.d.ts +4 -0
- package/clinkx-workflows/dist/dashboard/api-routes.js +133 -25
- package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.d.ts +3865 -0
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js +69 -0
- package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +36 -2
- package/clinkx-workflows/dist/dashboard/context-run-reader.js +142 -19
- package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/contracts.d.ts +20027 -480
- package/clinkx-workflows/dist/dashboard/contracts.js +133 -6
- package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/index.d.ts +4 -0
- package/clinkx-workflows/dist/dashboard/index.js +4 -0
- package/clinkx-workflows/dist/dashboard/index.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/invocation-contract.d.ts +786 -0
- package/clinkx-workflows/dist/dashboard/invocation-contract.js +201 -0
- package/clinkx-workflows/dist/dashboard/invocation-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +14 -1
- package/clinkx-workflows/dist/dashboard/lazy-content.js +53 -2
- package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.d.ts +90 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.js +31 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.d.ts +12 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.js +286 -0
- package/clinkx-workflows/dist/dashboard/native-transcript/projection.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +147 -4
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js +11 -11
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js +4 -56
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +2 -1
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +6 -5
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.d.ts +194 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js +89 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.d.ts +26 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js +102 -0
- package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js +4 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/scanner.d.ts +3 -0
- package/clinkx-workflows/dist/dashboard/scanner.js +52 -8
- package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/server-factory.d.ts +14 -1
- package/clinkx-workflows/dist/dashboard/server-factory.js +73 -21
- package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +223 -56
- package/clinkx-workflows/dist/dashboard/session-log-contract.js +26 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/session-log-journal.js +10 -3
- package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +9 -1
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js +793 -59
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -1
- package/clinkx-workflows/dist/dashboard-ui/assets/index-JPHQVtn9.js +106 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/style-DOMHk-j6.css +1 -0
- package/clinkx-workflows/dist/dashboard-ui/index.html +2 -2
- package/clinkx-workflows/dist/delegate-finalizer-policy.d.ts +26 -0
- package/clinkx-workflows/dist/delegate-finalizer-policy.js +72 -0
- package/clinkx-workflows/dist/delegate-finalizer-policy.js.map +1 -0
- package/clinkx-workflows/dist/delegate-signature.d.ts +14 -0
- package/clinkx-workflows/dist/delegate-signature.js +14 -0
- package/clinkx-workflows/dist/delegate-signature.js.map +1 -0
- package/clinkx-workflows/dist/delegate-validate.d.ts +32 -2
- package/clinkx-workflows/dist/delegate-validate.js +51 -0
- package/clinkx-workflows/dist/delegate-validate.js.map +1 -1
- package/clinkx-workflows/dist/engine.d.ts +113 -3
- package/clinkx-workflows/dist/engine.js +476 -80
- package/clinkx-workflows/dist/engine.js.map +1 -1
- package/clinkx-workflows/dist/errors.d.ts +22 -2
- package/clinkx-workflows/dist/errors.js +30 -2
- package/clinkx-workflows/dist/errors.js.map +1 -1
- package/clinkx-workflows/dist/fidelity.d.ts +47 -1
- package/clinkx-workflows/dist/fidelity.js +54 -1
- package/clinkx-workflows/dist/fidelity.js.map +1 -1
- package/clinkx-workflows/dist/fingerprint.d.ts +24 -6
- package/clinkx-workflows/dist/fingerprint.js +32 -9
- package/clinkx-workflows/dist/fingerprint.js.map +1 -1
- package/clinkx-workflows/dist/index.js +53 -1
- package/clinkx-workflows/dist/index.js.map +1 -1
- package/clinkx-workflows/dist/logger.d.ts +1 -1
- package/clinkx-workflows/dist/logger.js +17 -3
- package/clinkx-workflows/dist/logger.js.map +1 -1
- package/clinkx-workflows/dist/monotonic-clock.d.ts +19 -0
- package/clinkx-workflows/dist/monotonic-clock.js +22 -0
- package/clinkx-workflows/dist/monotonic-clock.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/contract.d.ts +52 -52
- package/clinkx-workflows/dist/plan-review/publication.d.ts +4 -4
- package/clinkx-workflows/dist/plan-review/run-state.d.ts +16 -16
- package/clinkx-workflows/dist/roster-contract.d.ts +889 -0
- package/clinkx-workflows/dist/roster-contract.js +180 -0
- package/clinkx-workflows/dist/roster-contract.js.map +1 -0
- package/clinkx-workflows/dist/rosters.d.ts +14 -0
- package/clinkx-workflows/dist/rosters.js +169 -0
- package/clinkx-workflows/dist/rosters.js.map +1 -0
- package/clinkx-workflows/dist/run-health.d.ts +7 -0
- package/clinkx-workflows/dist/run-health.js +25 -0
- package/clinkx-workflows/dist/run-health.js.map +1 -0
- package/clinkx-workflows/dist/schema.d.ts +64 -0
- package/clinkx-workflows/dist/schema.js +24 -0
- package/clinkx-workflows/dist/schema.js.map +1 -1
- package/clinkx-workflows/dist/server.js +2 -0
- package/clinkx-workflows/dist/server.js.map +1 -1
- package/clinkx-workflows/dist/shutdown.d.ts +15 -2
- package/clinkx-workflows/dist/shutdown.js +60 -16
- package/clinkx-workflows/dist/shutdown.js.map +1 -1
- package/clinkx-workflows/dist/state-schema.d.ts +23 -0
- package/clinkx-workflows/dist/state.d.ts +5 -0
- package/clinkx-workflows/dist/state.js +128 -21
- package/clinkx-workflows/dist/state.js.map +1 -1
- package/clinkx-workflows/dist/stdio-watchdog.d.ts +52 -0
- package/clinkx-workflows/dist/stdio-watchdog.js +212 -0
- package/clinkx-workflows/dist/stdio-watchdog.js.map +1 -0
- package/clinkx-workflows/dist/types.d.ts +6 -0
- package/clinkx-workflows/dist/validators/council.d.ts +50 -50
- package/clinkx-workflows/dist/validators/plan-review.d.ts +3 -3
- package/clinkx-workflows/dist/workflow-tools.d.ts +70 -0
- package/clinkx-workflows/dist/workflow-tools.js +760 -168
- package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
- package/clinkx-workflows/templates/council/answer-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/answer-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/answer-specialist.txt +3 -0
- package/clinkx-workflows/templates/council/chairman.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/debug-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/debug-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/debug-debugger.txt +3 -0
- package/clinkx-workflows/templates/council/discover-analyst.txt +3 -0
- package/clinkx-workflows/templates/council/discover-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/discover-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/member.txt +3 -0
- package/clinkx-workflows/templates/council/plan-review-chairman.txt +3 -0
- package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/plan-review-reviewer.txt +3 -0
- package/clinkx-workflows/templates/council/reviewer.txt +3 -0
- package/clinkx-workflows/workflows/council-answer.yaml +3 -0
- package/clinkx-workflows/workflows/council-code-review.yaml +3 -0
- package/clinkx-workflows/workflows/council-debug.yaml +3 -0
- package/clinkx-workflows/workflows/council-default.yaml +7 -0
- package/clinkx-workflows/workflows/council-discover.yaml +3 -0
- package/clinkx-workflows/workflows/council-plan-review.yaml +3 -0
- package/conf/adapters/agy.json +9 -2
- package/conf/adapters/claude.json +7 -0
- package/conf/adapters/codex.json +7 -0
- package/conf/adapters/glm.json +19 -3
- package/conf/adapters/grok.json +9 -2
- package/conf/adapters/hapi/agy.json +9 -2
- package/conf/adapters/hapi/claude.json +7 -0
- package/conf/adapters/hapi/codex.json +7 -0
- package/conf/adapters/hapi/glm.json +20 -4
- package/conf/rosters/default-council.json +21 -0
- package/dist/activity-journal.d.ts +17 -3
- package/dist/activity-journal.js +404 -79
- package/dist/activity-journal.js.map +1 -1
- package/dist/codex/agent-tools.js +36 -0
- package/dist/codex/agent-tools.js.map +1 -1
- package/dist/codex/app-server-client.d.ts +3 -0
- package/dist/codex/app-server-client.js +11 -0
- package/dist/codex/app-server-client.js.map +1 -1
- package/dist/codex/app-server-registry.d.ts +18 -0
- package/dist/codex/app-server-registry.js +88 -2
- package/dist/codex/app-server-registry.js.map +1 -1
- package/dist/codex/client-types.d.ts +7 -1
- package/dist/codex/protocol.d.ts +4 -4
- package/dist/config.d.ts +220 -23
- package/dist/config.js +104 -1
- package/dist/config.js.map +1 -1
- package/dist/context-builder/bundle.d.ts +13 -0
- package/dist/context-builder/bundle.js +46 -0
- package/dist/context-builder/bundle.js.map +1 -1
- package/dist/context-builder/delegate-finalizer-effective.d.ts +33 -0
- package/dist/context-builder/delegate-finalizer-effective.js +82 -0
- package/dist/context-builder/delegate-finalizer-effective.js.map +1 -0
- package/dist/context-builder/delegate-packet.d.ts +18 -0
- package/dist/context-builder/delegate-packet.js +73 -9
- package/dist/context-builder/delegate-packet.js.map +1 -1
- package/dist/context-builder/delegate-reconstruction.d.ts +29 -0
- package/dist/context-builder/delegate-reconstruction.js +110 -41
- package/dist/context-builder/delegate-reconstruction.js.map +1 -1
- package/dist/context-builder/delegate-validation.d.ts +31 -0
- package/dist/context-builder/delegate-validation.js +87 -10
- package/dist/context-builder/delegate-validation.js.map +1 -1
- package/dist/context-builder/delegate.d.ts +9 -0
- package/dist/context-builder/delegate.js +210 -59
- package/dist/context-builder/delegate.js.map +1 -1
- package/dist/context-builder/diagnostics.d.ts +1 -1
- package/dist/context-builder/diagnostics.js.map +1 -1
- package/dist/context-builder/discovery-progress.d.ts +25 -0
- package/dist/context-builder/discovery-progress.js +64 -0
- package/dist/context-builder/discovery-progress.js.map +1 -0
- package/dist/context-builder/discovery-watchdog.d.ts +15 -0
- package/dist/context-builder/discovery-watchdog.js +39 -0
- package/dist/context-builder/discovery-watchdog.js.map +1 -0
- package/dist/context-builder/discovery.d.ts +7 -0
- package/dist/context-builder/discovery.js +119 -7
- package/dist/context-builder/discovery.js.map +1 -1
- package/dist/context-builder/external-gaps.d.ts +73 -0
- package/dist/context-builder/external-gaps.js +248 -0
- package/dist/context-builder/external-gaps.js.map +1 -0
- package/dist/context-builder/globs.d.ts +1 -0
- package/dist/context-builder/globs.js +15 -0
- package/dist/context-builder/globs.js.map +1 -1
- package/dist/context-builder/handler.d.ts +42 -1
- package/dist/context-builder/handler.js +690 -62
- package/dist/context-builder/handler.js.map +1 -1
- package/dist/context-builder/invocation-sidecar.d.ts +41 -0
- package/dist/context-builder/invocation-sidecar.js +189 -0
- package/dist/context-builder/invocation-sidecar.js.map +1 -0
- package/dist/context-builder/lifecycle-contract.d.ts +51 -51
- package/dist/context-builder/mutation-lock.d.ts +32 -0
- package/dist/context-builder/mutation-lock.js +321 -0
- package/dist/context-builder/mutation-lock.js.map +1 -0
- package/dist/context-builder/review-coverage.d.ts +32 -0
- package/dist/context-builder/review-coverage.js +130 -23
- package/dist/context-builder/review-coverage.js.map +1 -1
- package/dist/context-builder/schema.d.ts +76 -1
- package/dist/context-builder/schema.js +21 -1
- package/dist/context-builder/schema.js.map +1 -1
- package/dist/context-builder/secrets.d.ts +31 -0
- package/dist/context-builder/secrets.js +871 -84
- package/dist/context-builder/secrets.js.map +1 -1
- package/dist/context-builder/status.d.ts +1 -1
- package/dist/context-builder/status.js +1 -0
- package/dist/context-builder/status.js.map +1 -1
- package/dist/context-builder/storage.d.ts +29 -5
- package/dist/context-builder/storage.js +311 -236
- package/dist/context-builder/storage.js.map +1 -1
- package/dist/context-builder/versions.d.ts +19 -4
- package/dist/context-builder/versions.js +19 -4
- package/dist/context-builder/versions.js.map +1 -1
- package/dist/context-builder/workflow-runner.d.ts +6 -0
- package/dist/context-builder/workflow-runner.js +363 -173
- package/dist/context-builder/workflow-runner.js.map +1 -1
- package/dist/dashboard-adapter-catalog.js +7 -0
- package/dist/dashboard-adapter-catalog.js.map +1 -1
- package/dist/dashboard-cli.d.ts +2 -0
- package/dist/dashboard-cli.js +40 -2
- package/dist/dashboard-cli.js.map +1 -1
- package/dist/dashboard-companion/client.d.ts +1 -0
- package/dist/dashboard-companion/client.js +22 -3
- package/dist/dashboard-companion/client.js.map +1 -1
- package/dist/dashboard-companion/protocol.js +4 -1
- package/dist/dashboard-companion/protocol.js.map +1 -1
- package/dist/dashboard-gateway/index.d.ts +1 -0
- package/dist/dashboard-gateway/index.js +33 -2
- package/dist/dashboard-gateway/index.js.map +1 -1
- package/dist/handler.js +5 -1
- package/dist/handler.js.map +1 -1
- package/dist/index.d.ts +18 -1
- package/dist/index.js +145 -7
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +18 -3
- package/dist/logger.js.map +1 -1
- package/dist/parsers/agy-stream-json.d.ts +14 -0
- package/dist/parsers/agy-stream-json.js +172 -0
- package/dist/parsers/agy-stream-json.js.map +1 -0
- package/dist/parsers/extract.js +5 -1
- package/dist/parsers/extract.js.map +1 -1
- package/dist/parsers/observed-invocation.d.ts +20 -0
- package/dist/parsers/observed-invocation.js +42 -0
- package/dist/parsers/observed-invocation.js.map +1 -0
- package/dist/pipeline.d.ts +13 -0
- package/dist/pipeline.js +60 -10
- package/dist/pipeline.js.map +1 -1
- package/dist/progress-report/lock.d.ts +74 -0
- package/dist/progress-report/lock.js +343 -0
- package/dist/progress-report/lock.js.map +1 -0
- package/dist/progress-report/schema.d.ts +808 -0
- package/dist/progress-report/schema.js +119 -0
- package/dist/progress-report/schema.js.map +1 -0
- package/dist/progress-report/store.d.ts +66 -0
- package/dist/progress-report/store.js +766 -0
- package/dist/progress-report/store.js.map +1 -0
- package/dist/progress-report/tools.d.ts +20 -0
- package/dist/progress-report/tools.js +141 -0
- package/dist/progress-report/tools.js.map +1 -0
- package/dist/prompt-mode.d.ts +1 -1
- package/dist/prompt-mode.js +7 -2
- package/dist/prompt-mode.js.map +1 -1
- package/dist/runner.d.ts +7 -0
- package/dist/runner.js +10 -3
- package/dist/runner.js.map +1 -1
- package/dist/safety.d.ts +50 -0
- package/dist/safety.js +182 -1
- package/dist/safety.js.map +1 -1
- package/dist/schema.d.ts +8 -0
- package/dist/schema.js +20 -0
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +6 -1
- package/dist/server.js +94 -8
- package/dist/server.js.map +1 -1
- package/dist/session-log-stream.d.ts +1 -1
- package/dist/session-log-stream.js +280 -33
- package/dist/session-log-stream.js.map +1 -1
- package/dist/stdio-activity.d.ts +28 -0
- package/dist/stdio-activity.js +72 -0
- package/dist/stdio-activity.js.map +1 -0
- package/docs/configuration.md +426 -0
- package/docs/security.md +293 -0
- package/package.json +5 -5
- package/clinkx-workflows/dist/dashboard-ui/assets/index-DOkMXTfB.js +0 -25
- package/clinkx-workflows/dist/dashboard-ui/assets/style-ChJsn0KD.css +0 -1
|
@@ -3,28 +3,208 @@ const REDACTION_MARKER = "[REDACTED]";
|
|
|
3
3
|
const MAX_EVENT_TEXT_CHARS = 32_768;
|
|
4
4
|
const MAX_EVENT_SUMMARY_CHARS = 4_096;
|
|
5
5
|
const MAX_REDACTIONS = 10_000;
|
|
6
|
-
const
|
|
7
|
-
const
|
|
6
|
+
const MAX_ARGUMENT_BYTES = 32_768;
|
|
7
|
+
const MAX_ARGUMENT_INPUT_BYTES = 1024 * 1024;
|
|
8
|
+
const MAX_ARGUMENT_DEPTH = 64;
|
|
9
|
+
const MAX_ARGUMENT_NODES = 10_000;
|
|
10
|
+
const SENSITIVE_KEY_SOURCE = "api[-_]?key|apikey|access[-_]?token|auth[-_]?token|token|secret|client[-_]?secret|password|authorization|proxy[-_]?authorization|cookie|set[-_]?cookie|private[-_]?key";
|
|
11
|
+
const SENSITIVE_KEY_PATTERN = new RegExp(`^(?:${SENSITIVE_KEY_SOURCE}|pgpassword)$`, "i");
|
|
12
|
+
const BEARER_PATTERN = /\bBearer\s+(?!\[REDACTED\])[^\s,;"']+/gi;
|
|
8
13
|
const PRIVATE_KEY_SIGNAL_PATTERN = /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----/i;
|
|
9
|
-
const SENSITIVE_QUERY_PATTERN = /([?&](?:api[-_]?key|access[-_]?token|auth[-_]?token|token|secret|password|client[-_]?secret|signature|sig|key)=)(?!\[REDACTED\])([^&#\s]+)/gi;
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
14
|
+
const SENSITIVE_QUERY_PATTERN = /([?&](?:api[-_]?key|access[-_]?token|auth[-_]?token|token|secret|password|client[-_]?secret|signature|sig|key)=)(?!\[REDACTED\])([^&#\s"']+)/gi;
|
|
15
|
+
const ASSIGNMENT_PATTERN = /(^|[^A-Za-z0-9_])([A-Za-z_][A-Za-z0-9_]{0,127})([ \t]*[=:][ \t]*)/g;
|
|
16
|
+
const VENDOR_TOKEN_PATTERN = /\b(?:sk-(?:ant-|proj-|svcacct-)?[A-Za-z0-9_-]{12,}|(?:sk|pk|github_pat|cxel|cxmsg|npm)_[A-Za-z0-9_:-]{12,}|gh[pousr]_[A-Za-z0-9]{12,}|xox[baprs]-[A-Za-z0-9-]{10,}|glpat-[A-Za-z0-9_-]{12,}|(?:AKIA|ASIA)[0-9A-Z]{16}|AIza[0-9A-Za-z_-]{35})\b/g;
|
|
17
|
+
const JWT_PATTERN = /\beyJ[A-Za-z0-9_-]{1,4096}\.[A-Za-z0-9_-]{1,4096}\.[A-Za-z0-9_-]{1,4096}(?![A-Za-z0-9_.-])/g;
|
|
18
|
+
const HEADER_PATTERN = /^(Proxy-Authorization|Authorization|Set-Cookie|Cookie)([ \t]*:[ \t]*)(.*)$/i;
|
|
19
|
+
const SHELL_DELIMITER_PATTERN = /[\s;|&]/;
|
|
13
20
|
const POSIX_USER_HOME_PATTERN = /\/(?:Users|home)\/[^/\s"'`<>]+/g;
|
|
14
21
|
const ROOT_USER_HOME_PATTERN = /\/root(?=\/|[\s"'`<>]|$)/g;
|
|
15
22
|
const WINDOWS_USER_HOME_PATTERN = /\b[A-Za-z]:\\Users\\[^\\/\s"'`<>]+/gi;
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
function isSensitiveKey(key) {
|
|
24
|
+
const normalized = key.toLowerCase().replace(/[-_ ]+/g, "_");
|
|
25
|
+
return SENSITIVE_KEY_PATTERN.test(normalized) || /(?:^|_)(?:token|secret|password|api_key|secret_access_key)$/.test(normalized);
|
|
26
|
+
}
|
|
27
|
+
const preparationHistory = new WeakMap();
|
|
28
|
+
export function prepareSessionLogEventV1(input, options) {
|
|
29
|
+
const prior = input !== null && typeof input === "object"
|
|
30
|
+
? preparationHistory.get(input)
|
|
31
|
+
: undefined;
|
|
32
|
+
if (prior !== undefined) {
|
|
33
|
+
if (options?.coverage === undefined || options.coverage === prior.redaction.coverage)
|
|
34
|
+
return prior;
|
|
35
|
+
const result = Object.freeze({ ...prior, redaction: Object.freeze({ ...prior.redaction, coverage: options.coverage }) });
|
|
36
|
+
preparationHistory.set(prior.event, result);
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
18
39
|
const normalized = validateNormalizedEvent(input);
|
|
19
40
|
const prepared = redactEvent(normalized);
|
|
20
41
|
assertTextWithinContract(prepared.event);
|
|
42
|
+
if (prepared.event.kind === "tool_call" && prepared.event.arguments !== undefined) {
|
|
43
|
+
Object.freeze(prepared.event.arguments);
|
|
44
|
+
}
|
|
21
45
|
const event = Object.freeze(prepared.event);
|
|
46
|
+
const count = Math.min(MAX_REDACTIONS, prepared.count);
|
|
22
47
|
const redaction = Object.freeze({
|
|
23
48
|
ran: true,
|
|
24
|
-
status:
|
|
25
|
-
count
|
|
49
|
+
status: count === 0 ? "no_matches" : "redacted",
|
|
50
|
+
count,
|
|
51
|
+
...(options?.coverage === undefined ? {} : { coverage: options.coverage }),
|
|
52
|
+
});
|
|
53
|
+
const result = Object.freeze({
|
|
54
|
+
event,
|
|
55
|
+
redaction,
|
|
56
|
+
...(prepared.truncated ? { truncated: true } : {}),
|
|
57
|
+
...(event.kind === "tool_call" && event.arguments !== undefined && event.arguments.omission !== "none"
|
|
58
|
+
? { omitted: true } : {}),
|
|
59
|
+
});
|
|
60
|
+
preparationHistory.set(event, result);
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
class ArgumentAdmissionError extends Error {
|
|
64
|
+
reason;
|
|
65
|
+
constructor(reason) {
|
|
66
|
+
super("argument admission failed");
|
|
67
|
+
this.reason = reason;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function prepareSessionLogToolCallV1(event, value, options) {
|
|
71
|
+
const args = sanitizeArguments(value);
|
|
72
|
+
const candidate = { ...event, summary: args.summary ?? event.summary, arguments: args.detail };
|
|
73
|
+
const prepared = prepareSessionLogEventV1(candidate, options);
|
|
74
|
+
const count = Math.min(MAX_REDACTIONS, args.count + prepared.redaction.count);
|
|
75
|
+
const result = Object.freeze({
|
|
76
|
+
...prepared,
|
|
77
|
+
redaction: Object.freeze({ ...prepared.redaction, status: count > 0 ? "redacted" : "no_matches", count }),
|
|
26
78
|
});
|
|
27
|
-
|
|
79
|
+
preparationHistory.set(result.event, result);
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
function sanitizeArgumentDetail(detail) {
|
|
83
|
+
if (detail.omission !== "none")
|
|
84
|
+
return { detail: { ...detail }, count: 0 };
|
|
85
|
+
if (detail.content.length > MAX_ARGUMENT_INPUT_BYTES || Buffer.byteLength(detail.content, "utf8") > MAX_ARGUMENT_INPUT_BYTES) {
|
|
86
|
+
return { detail: { content: "", omission: "size_limit" }, count: 0 };
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
return sanitizeArguments(JSON.parse(detail.content));
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return { detail: { content: "", omission: "invalid" }, count: 0 };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function sanitizeArguments(input, baseDepth = 0) {
|
|
96
|
+
let nodes = 0;
|
|
97
|
+
let bytes = 0;
|
|
98
|
+
let count = 0;
|
|
99
|
+
const active = new Set();
|
|
100
|
+
const charge = (text) => {
|
|
101
|
+
if (text.length > MAX_ARGUMENT_INPUT_BYTES)
|
|
102
|
+
throw new ArgumentAdmissionError("size_limit");
|
|
103
|
+
bytes += Buffer.byteLength(JSON.stringify(text), "utf8");
|
|
104
|
+
if (bytes > MAX_ARGUMENT_INPUT_BYTES)
|
|
105
|
+
throw new ArgumentAdmissionError("size_limit");
|
|
106
|
+
};
|
|
107
|
+
const visit = (value, depth) => {
|
|
108
|
+
if (++nodes > MAX_ARGUMENT_NODES || depth > MAX_ARGUMENT_DEPTH)
|
|
109
|
+
throw new ArgumentAdmissionError("structure_limit");
|
|
110
|
+
if (value === null || typeof value === "boolean")
|
|
111
|
+
return value;
|
|
112
|
+
if (typeof value === "number") {
|
|
113
|
+
if (!Number.isFinite(value))
|
|
114
|
+
throw new ArgumentAdmissionError("invalid");
|
|
115
|
+
return value;
|
|
116
|
+
}
|
|
117
|
+
if (typeof value === "string") {
|
|
118
|
+
charge(value);
|
|
119
|
+
const trimmed = value.trimStart();
|
|
120
|
+
if (/^[{["]/.test(trimmed)) {
|
|
121
|
+
let decoded;
|
|
122
|
+
let valid = false;
|
|
123
|
+
try {
|
|
124
|
+
decoded = JSON.parse(value);
|
|
125
|
+
valid = true;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
valid = false;
|
|
129
|
+
}
|
|
130
|
+
if (valid)
|
|
131
|
+
return JSON.stringify(visit(decoded, depth + 1));
|
|
132
|
+
}
|
|
133
|
+
const redacted = redactText(value, Number.POSITIVE_INFINITY, depth + 1);
|
|
134
|
+
count += redacted.count;
|
|
135
|
+
return redacted.text;
|
|
136
|
+
}
|
|
137
|
+
if (typeof value !== "object" || value === undefined)
|
|
138
|
+
throw new ArgumentAdmissionError("invalid");
|
|
139
|
+
if (active.has(value))
|
|
140
|
+
throw new ArgumentAdmissionError("invalid");
|
|
141
|
+
active.add(value);
|
|
142
|
+
try {
|
|
143
|
+
if (Array.isArray(value)) {
|
|
144
|
+
if (value.length + nodes > MAX_ARGUMENT_NODES)
|
|
145
|
+
throw new ArgumentAdmissionError("structure_limit");
|
|
146
|
+
const result = [];
|
|
147
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
148
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
|
|
149
|
+
if (descriptor === undefined || !("value" in descriptor))
|
|
150
|
+
throw new ArgumentAdmissionError("invalid");
|
|
151
|
+
result.push(visit(descriptor.value, depth + 1));
|
|
152
|
+
}
|
|
153
|
+
return result;
|
|
154
|
+
}
|
|
155
|
+
if (!isPlainRecord(value))
|
|
156
|
+
throw new ArgumentAdmissionError("invalid");
|
|
157
|
+
const keys = Object.keys(value);
|
|
158
|
+
if (keys.length + nodes > MAX_ARGUMENT_NODES)
|
|
159
|
+
throw new ArgumentAdmissionError("structure_limit");
|
|
160
|
+
const result = Object.create(null);
|
|
161
|
+
const sensitiveHeaderPair = typeof value["name"] === "string" && isSensitiveKey(value["name"]);
|
|
162
|
+
for (const key of keys) {
|
|
163
|
+
charge(key);
|
|
164
|
+
const child = visit(value[key], depth + 1);
|
|
165
|
+
const redactedKey = redactText(key);
|
|
166
|
+
count += redactedKey.count;
|
|
167
|
+
const retainedKey = redactedKey.count === 0 ? key
|
|
168
|
+
: `redacted_key_${createHash("sha256").update(key).digest("hex")}`;
|
|
169
|
+
if (isSensitiveKey(key) || (key === "value" && sensitiveHeaderPair)) {
|
|
170
|
+
if (value[key] !== REDACTION_MARKER)
|
|
171
|
+
count += 1;
|
|
172
|
+
result[retainedKey] = REDACTION_MARKER;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
if (Object.hasOwn(result, retainedKey))
|
|
176
|
+
throw new ArgumentAdmissionError("invalid");
|
|
177
|
+
result[retainedKey] = child;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return result;
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
active.delete(value);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
try {
|
|
187
|
+
const value = visit(input, baseDepth);
|
|
188
|
+
const content = JSON.stringify(value);
|
|
189
|
+
let summary;
|
|
190
|
+
if (isPlainRecord(value)) {
|
|
191
|
+
for (const key of ["command", "cmd", "CommandLine", "file_path", "path", "pattern", "query"]) {
|
|
192
|
+
if (typeof value[key] === "string" && value[key].length > 0) {
|
|
193
|
+
summary = value[key];
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
return {
|
|
199
|
+
detail: Buffer.byteLength(content, "utf8") > MAX_ARGUMENT_BYTES
|
|
200
|
+
? { content: "", omission: "size_limit" } : { content, omission: "none" },
|
|
201
|
+
...(summary === undefined ? {} : { summary }),
|
|
202
|
+
count,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
return { detail: { content: "", omission: error instanceof ArgumentAdmissionError ? error.reason : "invalid" }, count };
|
|
207
|
+
}
|
|
28
208
|
}
|
|
29
209
|
function assertTextWithinContract(event) {
|
|
30
210
|
let text;
|
|
@@ -60,27 +240,31 @@ function redactEvent(event) {
|
|
|
60
240
|
switch (event.kind) {
|
|
61
241
|
case "user":
|
|
62
242
|
case "assistant": {
|
|
63
|
-
const redacted = redactText(event.content, MAX_EVENT_TEXT_CHARS);
|
|
64
|
-
return { event: { ...event, content: redacted.text }, count: redacted.count
|
|
243
|
+
const redacted = redactText(event.content, MAX_EVENT_TEXT_CHARS, 0, "prose");
|
|
244
|
+
return { event: { ...event, content: redacted.text }, count: redacted.count,
|
|
245
|
+
...(redacted.truncated ? { truncated: true } : {}) };
|
|
65
246
|
}
|
|
66
247
|
case "tool_call": {
|
|
67
248
|
const toolName = redactIdentifier(event.toolName);
|
|
68
249
|
const callId = redactIdentifier(event.callId);
|
|
69
|
-
const
|
|
250
|
+
const args = event.arguments === undefined ? undefined : sanitizeArgumentDetail(event.arguments);
|
|
251
|
+
const redacted = redactText(args?.summary ?? event.summary, MAX_EVENT_SUMMARY_CHARS);
|
|
70
252
|
return {
|
|
71
253
|
event: {
|
|
72
254
|
...event,
|
|
73
255
|
toolName: toolName.value,
|
|
74
256
|
callId: callId.value,
|
|
75
257
|
summary: redacted.text,
|
|
258
|
+
...(args === undefined ? {} : { arguments: args.detail }),
|
|
76
259
|
},
|
|
77
|
-
count: toolName.count + callId.count + redacted.count,
|
|
260
|
+
count: toolName.count + callId.count + redacted.count + (args?.count ?? 0),
|
|
261
|
+
...(redacted.truncated ? { truncated: true } : {}),
|
|
78
262
|
};
|
|
79
263
|
}
|
|
80
264
|
case "tool_result": {
|
|
81
265
|
const toolName = redactIdentifier(event.toolName);
|
|
82
266
|
const callId = redactIdentifier(event.callId);
|
|
83
|
-
const redacted = redactText(event.summary, MAX_EVENT_SUMMARY_CHARS);
|
|
267
|
+
const redacted = redactText(event.summary, MAX_EVENT_SUMMARY_CHARS, 0, "prose");
|
|
84
268
|
return {
|
|
85
269
|
event: {
|
|
86
270
|
...event,
|
|
@@ -89,6 +273,7 @@ function redactEvent(event) {
|
|
|
89
273
|
summary: redacted.text,
|
|
90
274
|
},
|
|
91
275
|
count: toolName.count + callId.count + redacted.count,
|
|
276
|
+
...(redacted.truncated ? { truncated: true } : {}),
|
|
92
277
|
};
|
|
93
278
|
}
|
|
94
279
|
case "approval_request": {
|
|
@@ -97,6 +282,7 @@ function redactEvent(event) {
|
|
|
97
282
|
return {
|
|
98
283
|
event: { ...event, approvalId: approvalId.value, action: redacted.text },
|
|
99
284
|
count: approvalId.count + redacted.count,
|
|
285
|
+
...(redacted.truncated ? { truncated: true } : {}),
|
|
100
286
|
};
|
|
101
287
|
}
|
|
102
288
|
case "approval_resolution": {
|
|
@@ -108,22 +294,25 @@ function redactEvent(event) {
|
|
|
108
294
|
}
|
|
109
295
|
case "error": {
|
|
110
296
|
const code = redactIdentifier(event.code);
|
|
111
|
-
const redacted = redactText(event.message, MAX_EVENT_SUMMARY_CHARS);
|
|
297
|
+
const redacted = redactText(event.message, MAX_EVENT_SUMMARY_CHARS, 0, "prose");
|
|
112
298
|
return {
|
|
113
299
|
event: { ...event, code: code.value, message: redacted.text },
|
|
114
300
|
count: code.count + redacted.count,
|
|
301
|
+
...(redacted.truncated ? { truncated: true } : {}),
|
|
115
302
|
};
|
|
116
303
|
}
|
|
117
304
|
case "reasoning": {
|
|
118
|
-
const redacted = redactText(event.summary, MAX_EVENT_SUMMARY_CHARS);
|
|
119
|
-
return { event: { ...event, summary: redacted.text }, count: redacted.count
|
|
305
|
+
const redacted = redactText(event.summary, MAX_EVENT_SUMMARY_CHARS, 0, "prose");
|
|
306
|
+
return { event: { ...event, summary: redacted.text }, count: redacted.count,
|
|
307
|
+
...(redacted.truncated ? { truncated: true } : {}) };
|
|
120
308
|
}
|
|
121
309
|
case "unknown": {
|
|
122
310
|
const originalKind = redactIdentifier(event.originalKind);
|
|
123
|
-
const redacted = redactText(event.summary, MAX_EVENT_SUMMARY_CHARS);
|
|
311
|
+
const redacted = redactText(event.summary, MAX_EVENT_SUMMARY_CHARS, 0, "prose");
|
|
124
312
|
return {
|
|
125
313
|
event: { ...event, originalKind: originalKind.value, summary: redacted.text },
|
|
126
314
|
count: originalKind.count + redacted.count,
|
|
315
|
+
...(redacted.truncated ? { truncated: true } : {}),
|
|
127
316
|
};
|
|
128
317
|
}
|
|
129
318
|
case "lifecycle":
|
|
@@ -141,58 +330,594 @@ function redactIdentifier(value) {
|
|
|
141
330
|
.slice(0, 16);
|
|
142
331
|
return { value: `redacted_${digest}`, count: 1 };
|
|
143
332
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
148
|
-
let text = value;
|
|
333
|
+
function applyTextEdits(text, edits) {
|
|
334
|
+
const parts = [];
|
|
335
|
+
let end = 0;
|
|
149
336
|
let count = 0;
|
|
337
|
+
for (const edit of edits) {
|
|
338
|
+
parts.push(text.slice(end, edit.start), edit.text);
|
|
339
|
+
end = edit.end;
|
|
340
|
+
count += edit.count;
|
|
341
|
+
}
|
|
342
|
+
parts.push(text.slice(end));
|
|
343
|
+
return { text: parts.join(""), count };
|
|
344
|
+
}
|
|
345
|
+
function redactEmbeddedJson(text, depth) {
|
|
346
|
+
const edits = [];
|
|
347
|
+
for (let start = 0; start < text.length; start += 1) {
|
|
348
|
+
if (text[start] !== "{" && text[start] !== "[")
|
|
349
|
+
continue;
|
|
350
|
+
let shellEscaped = false;
|
|
351
|
+
const stack = [];
|
|
352
|
+
const decoded = [];
|
|
353
|
+
let quoted = false;
|
|
354
|
+
let escaped = false;
|
|
355
|
+
let end = start;
|
|
356
|
+
for (; end < text.length; end += 1) {
|
|
357
|
+
let char = text[end];
|
|
358
|
+
if (!quoted && char === "\\" && text[end + 1] === '"')
|
|
359
|
+
shellEscaped = true;
|
|
360
|
+
if (shellEscaped && char === "\\" && (text[end + 1] === "\\" || text[end + 1] === '"'))
|
|
361
|
+
char = text[++end];
|
|
362
|
+
decoded.push(char);
|
|
363
|
+
if (quoted) {
|
|
364
|
+
if (escaped)
|
|
365
|
+
escaped = false;
|
|
366
|
+
else if (char === "\\")
|
|
367
|
+
escaped = true;
|
|
368
|
+
else if (char === '"')
|
|
369
|
+
quoted = false;
|
|
370
|
+
}
|
|
371
|
+
else if (char === '"')
|
|
372
|
+
quoted = true;
|
|
373
|
+
else if (char === "{" || char === "[")
|
|
374
|
+
stack.push(char);
|
|
375
|
+
else if (char === "}" || char === "]") {
|
|
376
|
+
const open = stack.pop();
|
|
377
|
+
if ((char === "}" && open !== "{") || (char === "]" && open !== "["))
|
|
378
|
+
break;
|
|
379
|
+
if (stack.length === 0) {
|
|
380
|
+
end += 1;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
const source = decoded.join("");
|
|
386
|
+
try {
|
|
387
|
+
const parsed = JSON.parse(source);
|
|
388
|
+
const sanitized = sanitizeArguments(parsed, depth + 1);
|
|
389
|
+
if (sanitized.detail.omission !== "none") {
|
|
390
|
+
edits.push({ start, end, text: REDACTION_MARKER, count: Math.max(1, sanitized.count) });
|
|
391
|
+
}
|
|
392
|
+
else if (sanitized.count > 0) {
|
|
393
|
+
const replacement = shellEscaped ? sanitized.detail.content.replace(/[\\"]/g, "\\$&") : sanitized.detail.content;
|
|
394
|
+
edits.push({ start, end, text: replacement, count: sanitized.count });
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
const keyPattern = /"((?:\\.|[^"\\]){1,512})"\s*:/g;
|
|
399
|
+
let match;
|
|
400
|
+
while ((match = keyPattern.exec(source)) !== null) {
|
|
401
|
+
let key;
|
|
402
|
+
try {
|
|
403
|
+
key = JSON.parse(`"${match[1]}"`);
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
if (typeof key === "string" && isSensitiveKey(key)) {
|
|
409
|
+
return { text: REDACTION_MARKER, count: 1 };
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
start = Math.max(start, end - 1);
|
|
414
|
+
}
|
|
415
|
+
return applyTextEdits(text, edits);
|
|
416
|
+
}
|
|
417
|
+
function isWordApostrophe(text, index) {
|
|
418
|
+
const attachedFlag = index >= 2 && /^-[uab]$/.test(text.slice(index - 2, index))
|
|
419
|
+
&& (index === 2 || SHELL_DELIMITER_PATTERN.test(text[index - 3]));
|
|
420
|
+
return text[index] === "'"
|
|
421
|
+
&& !attachedFlag
|
|
422
|
+
&& /(?:\p{L}|\p{N})$/u.test(text.slice(Math.max(0, index - 2), index))
|
|
423
|
+
&& /^\p{L}/u.test(text.slice(index + 1, index + 3));
|
|
424
|
+
}
|
|
425
|
+
function shellTokens(text, mode = "command") {
|
|
426
|
+
const tokens = [];
|
|
427
|
+
for (let index = 0; index < text.length;) {
|
|
428
|
+
const start = index;
|
|
429
|
+
if (SHELL_DELIMITER_PATTERN.test(text[index])) {
|
|
430
|
+
index += 1;
|
|
431
|
+
if (/[;|&\n]/.test(text[start]))
|
|
432
|
+
tokens.push({ start, end: index, value: text[start], quoted: false });
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
let quote;
|
|
436
|
+
let quoted = false;
|
|
437
|
+
const parts = [];
|
|
438
|
+
while (index < text.length) {
|
|
439
|
+
const char = text[index];
|
|
440
|
+
if (quote === undefined && SHELL_DELIMITER_PATTERN.test(char))
|
|
441
|
+
break;
|
|
442
|
+
const wordApostrophe = mode === "prose" && quote === undefined && isWordApostrophe(text, index);
|
|
443
|
+
index += 1;
|
|
444
|
+
if (wordApostrophe)
|
|
445
|
+
parts.push(char);
|
|
446
|
+
else if (quote === undefined && (char === "'" || char === '"')) {
|
|
447
|
+
quote = char;
|
|
448
|
+
quoted = true;
|
|
449
|
+
}
|
|
450
|
+
else if (char === quote)
|
|
451
|
+
quote = undefined;
|
|
452
|
+
else if (char === "\\" && quote !== "'" && index < text.length) {
|
|
453
|
+
if (text[index] === "\n")
|
|
454
|
+
index += 1;
|
|
455
|
+
else if (text[index] === "\r" && text[index + 1] === "\n")
|
|
456
|
+
index += 2;
|
|
457
|
+
else
|
|
458
|
+
parts.push(text[index++]);
|
|
459
|
+
}
|
|
460
|
+
else
|
|
461
|
+
parts.push(char);
|
|
462
|
+
}
|
|
463
|
+
if (index <= start) {
|
|
464
|
+
index = start + 1;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (parts.length > 0 || quoted)
|
|
468
|
+
tokens.push({ start, end: index, value: parts.join(""), quoted });
|
|
469
|
+
}
|
|
470
|
+
return tokens;
|
|
471
|
+
}
|
|
472
|
+
function redactHeader(value) {
|
|
473
|
+
const match = HEADER_PATTERN.exec(value);
|
|
474
|
+
if (match === null)
|
|
475
|
+
return { text: value, count: 0 };
|
|
476
|
+
const body = match[3].trim();
|
|
477
|
+
const scheme = /authorization/i.test(match[1]) ? /^(?:Bearer|Basic)[ \t]+/i.exec(body)?.[0] ?? "" : "";
|
|
478
|
+
if (body === "" || body === REDACTION_MARKER || body === `${scheme}${REDACTION_MARKER}`)
|
|
479
|
+
return { text: value, count: 0 };
|
|
480
|
+
return { text: `${match[1]}${match[2]}${scheme}${REDACTION_MARKER}`, count: 1 };
|
|
481
|
+
}
|
|
482
|
+
function isMysqlCredential(value) {
|
|
483
|
+
return value.startsWith("-p") && value.length > 2
|
|
484
|
+
&& !/^(?:(?:\d{1,3}\.){3}\d{1,3}:|\[[0-9A-Fa-f:]+\]:)?\d+(?::\d+)?(?:\/\w+)?$/.test(value.slice(2));
|
|
485
|
+
}
|
|
486
|
+
function credentialOption(tokens, index) {
|
|
487
|
+
const token = tokens[index];
|
|
488
|
+
const match = /^(--[\w-]+)(?:=([\s\S]*))?$|^(-[uab])=?([\s\S]*)$/.exec(token.value);
|
|
489
|
+
if (match === null)
|
|
490
|
+
return undefined;
|
|
491
|
+
const flag = match[1] ?? match[3];
|
|
492
|
+
const attached = match[2] !== undefined || (match[3] !== undefined && token.value.length > 2);
|
|
493
|
+
const operand = attached ? token : tokens[index + 1];
|
|
494
|
+
const value = attached ? match[2] ?? match[4] : operand?.value ?? "";
|
|
495
|
+
const colon = value.indexOf(":");
|
|
496
|
+
const sensitive = ["-u", "--user", "--proxy-user", "--http-user", "-a", "--auth"].includes(flag) ? colon >= 0 && colon < value.length - 1
|
|
497
|
+
: ["-b", "--cookie"].includes(flag) ? value.includes("=")
|
|
498
|
+
: ["--password", "--http-password", "--proxy-password", "--passwd"].includes(flag);
|
|
499
|
+
return sensitive && operand !== undefined && !(!operand.quoted && /^[;|&\n]$/.test(value))
|
|
500
|
+
? { flag, attached, operand, value } : undefined;
|
|
501
|
+
}
|
|
502
|
+
function proseOperandWord(line, start, enclosingEnd) {
|
|
503
|
+
if (start >= line.length || SHELL_DELIMITER_PATTERN.test(line[start]))
|
|
504
|
+
return undefined;
|
|
505
|
+
let quote;
|
|
506
|
+
let quoted = false;
|
|
507
|
+
const parts = [];
|
|
508
|
+
let end = start;
|
|
509
|
+
while (end < line.length) {
|
|
510
|
+
if (quote === undefined && SHELL_DELIMITER_PATTERN.test(line[end]))
|
|
511
|
+
break;
|
|
512
|
+
const char = line[end++];
|
|
513
|
+
if (quote === undefined && (char === "'" || char === '"')) {
|
|
514
|
+
quote = char;
|
|
515
|
+
quoted = true;
|
|
516
|
+
}
|
|
517
|
+
else if (char === quote)
|
|
518
|
+
quote = undefined;
|
|
519
|
+
else if (char === "\\" && quote !== "'" && end < line.length)
|
|
520
|
+
parts.push(line[end++]);
|
|
521
|
+
else
|
|
522
|
+
parts.push(char);
|
|
523
|
+
}
|
|
524
|
+
if (quote !== undefined && enclosingEnd !== undefined && enclosingEnd > start && enclosingEnd < end) {
|
|
525
|
+
return proseOperandWord(line.slice(0, enclosingEnd), start);
|
|
526
|
+
}
|
|
527
|
+
return { start, end, value: parts.join(""), quoted };
|
|
528
|
+
}
|
|
529
|
+
function recoveredProseCredentialOption(line, tokens, index, enclosingEnd) {
|
|
530
|
+
const token = tokens[index];
|
|
531
|
+
const attached = /^(--[\w-]+=|-[uab]=?)(?=\S)/.exec(line.slice(token.start, token.end));
|
|
532
|
+
if (attached === null && !/^(?:--[\w-]+|-[uab])$/.test(token.value))
|
|
533
|
+
return undefined;
|
|
534
|
+
const start = attached === null ? tokens[index + 1]?.start : token.start + attached[0].length;
|
|
535
|
+
if (start === undefined)
|
|
536
|
+
return undefined;
|
|
537
|
+
const operand = proseOperandWord(line, start, enclosingEnd);
|
|
538
|
+
if (operand === undefined)
|
|
539
|
+
return undefined;
|
|
540
|
+
const flag = attached === null ? token : { ...token, end: operand.end, value: attached[0] + operand.value };
|
|
541
|
+
const credential = credentialOption([flag, operand], 0);
|
|
542
|
+
return credential?.value === REDACTION_MARKER ? undefined : credential;
|
|
543
|
+
}
|
|
544
|
+
function proseEvidenceTokens(text) {
|
|
545
|
+
return Array.from(text.matchAll(/[^\s;|&]+|[;|&\n]/g), (word) => ({
|
|
546
|
+
start: word.index, end: word.index + word[0].length, value: word[0].replace(/["']/g, ""), quoted: false,
|
|
547
|
+
}));
|
|
548
|
+
}
|
|
549
|
+
function redactMultilineProseTokens(text) {
|
|
550
|
+
if (!text.includes("\n"))
|
|
551
|
+
return { text, count: 0 };
|
|
552
|
+
// Recover only credential-owned words; preceding prose quotes cannot own later lines.
|
|
553
|
+
const tokens = proseEvidenceTokens(text);
|
|
554
|
+
const edits = [];
|
|
555
|
+
let mysql = false;
|
|
556
|
+
let enclosingEnd;
|
|
557
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
558
|
+
const token = tokens[index];
|
|
559
|
+
if (enclosingEnd !== undefined && token.start >= enclosingEnd)
|
|
560
|
+
enclosingEnd = undefined;
|
|
561
|
+
if (/^[;|&\n]$/.test(token.value)) {
|
|
562
|
+
mysql = false;
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
if (["mysql", "mariadb"].includes(token.value.split(/[\\/]/).at(-1)))
|
|
566
|
+
mysql = true;
|
|
567
|
+
const assignment = /^["']?([A-Za-z_][A-Za-z0-9_]{0,127})([ \t]*[=:][ \t]*)/.exec(text.slice(token.start));
|
|
568
|
+
const sensitiveAssignment = assignment !== null && isSensitiveKey(assignment[1])
|
|
569
|
+
&& !(assignment[2].includes(":") && /authorization|cookie/i.test(assignment[1]));
|
|
570
|
+
const spacedAssignment = sensitiveAssignment && /[ \t]/.test(assignment[2]);
|
|
571
|
+
const credential = recoveredProseCredentialOption(text, tokens, index, enclosingEnd);
|
|
572
|
+
const mysqlOperand = mysql && isMysqlCredential(token.value) && token.value !== `-p${REDACTION_MARKER}`;
|
|
573
|
+
const headerPrefix = token.value.startsWith("--header=") ? "--header=" : token.value.startsWith("-H") ? "-H" : "";
|
|
574
|
+
const headerOperand = headerPrefix !== "" || ["-H", "--header"].includes(tokens[index - 1]?.value ?? "");
|
|
575
|
+
const wrapper = tokens[index - 1]?.value.split(/[\\/]/).at(-1);
|
|
576
|
+
const commandFlag = (["sh", "bash", "zsh"].includes(wrapper ?? "") && /^-[A-Za-z]*c[A-Za-z]*$/.test(token.value))
|
|
577
|
+
|| (wrapper === "env" && ["-S", "--split-string"].includes(token.value));
|
|
578
|
+
const operand = credential?.operand ?? (commandFlag ? proseOperandWord(text, tokens[index + 1]?.start ?? text.length, enclosingEnd)
|
|
579
|
+
: sensitiveAssignment || mysqlOperand || headerOperand ? proseOperandWord(text, spacedAssignment ? token.start + assignment[0].length : token.start + headerPrefix.length, enclosingEnd) : undefined);
|
|
580
|
+
if (operand === undefined) {
|
|
581
|
+
const quote = text[token.start];
|
|
582
|
+
if (quote === "'" || quote === '"') {
|
|
583
|
+
const enclosing = proseOperandWord(text, token.start);
|
|
584
|
+
if (text.slice(token.start, enclosing.end).includes("\n") && text[enclosing.end - 1] === quote)
|
|
585
|
+
enclosingEnd = enclosing.end - 1;
|
|
586
|
+
}
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
const source = text.slice(operand.start, operand.end);
|
|
590
|
+
if (source.includes("\n") || operand.end === enclosingEnd) {
|
|
591
|
+
let replacement;
|
|
592
|
+
if (spacedAssignment) {
|
|
593
|
+
const quote = source[0];
|
|
594
|
+
replacement = (quote === "'" || quote === '"') && source.endsWith(quote)
|
|
595
|
+
? `${quote}${REDACTION_MARKER}${quote}` : REDACTION_MARKER;
|
|
596
|
+
}
|
|
597
|
+
else if (sensitiveAssignment) {
|
|
598
|
+
const prefix = `${assignment[1]}${assignment[2]}`;
|
|
599
|
+
if (operand.value.slice(prefix.length) !== REDACTION_MARKER) {
|
|
600
|
+
replacement = REDACTION_MARKER;
|
|
601
|
+
const quote = source[0];
|
|
602
|
+
if ((quote === "'" || quote === '"') && source === `${quote}${operand.value}${quote}`) {
|
|
603
|
+
replacement = `${quote}${prefix}${REDACTION_MARKER}${quote}`;
|
|
604
|
+
}
|
|
605
|
+
else if (source.startsWith(prefix)) {
|
|
606
|
+
const rawValue = source.slice(prefix.length);
|
|
607
|
+
const quote = rawValue[0];
|
|
608
|
+
if (quote !== "'" && quote !== '"')
|
|
609
|
+
replacement = prefix + REDACTION_MARKER;
|
|
610
|
+
else if (rawValue === `${quote}${operand.value.slice(prefix.length)}${quote}`) {
|
|
611
|
+
replacement = `${prefix}${quote}${REDACTION_MARKER}${quote}`;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
else if (credential !== undefined) {
|
|
617
|
+
replacement = (credential.attached ? credential.flag + (token.value[credential.flag.length] === "=" ? "=" : "") : "") + REDACTION_MARKER;
|
|
618
|
+
}
|
|
619
|
+
else if (mysqlOperand)
|
|
620
|
+
replacement = `-p${REDACTION_MARKER}`;
|
|
621
|
+
else if (headerOperand && redactHeader(operand.value.replace(/\r?\n/g, " ")).count > 0)
|
|
622
|
+
replacement = REDACTION_MARKER;
|
|
623
|
+
else if (commandFlag && firstProseCredentialEvidence(operand.value.replace(/\r?\n/g, " ")) !== undefined)
|
|
624
|
+
replacement = REDACTION_MARKER;
|
|
625
|
+
if (replacement !== undefined)
|
|
626
|
+
edits.push({
|
|
627
|
+
start: operand.start, end: operand.end + (sensitiveAssignment && operand.end === enclosingEnd ? 1 : 0), text: replacement, count: 1,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
// A locally decoded word owns its quote fragments, even when no edit is needed.
|
|
631
|
+
while (tokens[index + 1] !== undefined && tokens[index + 1].start < operand.end)
|
|
632
|
+
index += 1;
|
|
633
|
+
}
|
|
634
|
+
return applyTextEdits(text, edits);
|
|
635
|
+
}
|
|
636
|
+
function firstProseCredentialEvidence(line) {
|
|
637
|
+
let first = Number.POSITIVE_INFINITY;
|
|
638
|
+
ASSIGNMENT_PATTERN.lastIndex = 0;
|
|
639
|
+
let match;
|
|
640
|
+
while ((match = ASSIGNMENT_PATTERN.exec(line)) !== null) {
|
|
641
|
+
const valueStart = ASSIGNMENT_PATTERN.lastIndex;
|
|
642
|
+
// Leave trailing space available as the boundary of a following assignment.
|
|
643
|
+
ASSIGNMENT_PATTERN.lastIndex -= /[ \t]+$/.exec(match[3])?.[0].length ?? 0;
|
|
644
|
+
if (match[1] === "-" && line[match.index - 1] === "-" && match[3].startsWith("=")
|
|
645
|
+
&& /^(?:user|proxy-user|http-user|auth|cookie|password|http-password|proxy-password|passwd)$/.test(match[2])
|
|
646
|
+
&& (match.index === 1 || /[\s;|&"']/.test(line[match.index - 2])))
|
|
647
|
+
continue;
|
|
648
|
+
if (isSensitiveKey(match[2]) && !(match[3].includes(":") && /authorization|cookie/i.test(match[2]))) {
|
|
649
|
+
if (/^["']?\[REDACTED\]["']?(?=$|[\s;|&,])/.test(line.slice(valueStart)))
|
|
650
|
+
continue;
|
|
651
|
+
const operand = proseOperandWord(line, valueStart);
|
|
652
|
+
if (operand !== undefined && operand.value !== REDACTION_MARKER) {
|
|
653
|
+
first = match.index + match[1].length;
|
|
654
|
+
break;
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
// Ignore quote grouping only for locating evidence in an already ambiguous line.
|
|
659
|
+
const tokens = proseEvidenceTokens(line);
|
|
660
|
+
let mysql = false;
|
|
661
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
662
|
+
const token = tokens[index];
|
|
663
|
+
if (token.start >= first)
|
|
664
|
+
break;
|
|
665
|
+
if (/^[;|&]$/.test(token.value)) {
|
|
666
|
+
mysql = false;
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
const credential = credentialOption(tokens, index);
|
|
670
|
+
if ((credential !== undefined && credential.value !== REDACTION_MARKER) || recoveredProseCredentialOption(line, tokens, index) !== undefined
|
|
671
|
+
|| (mysql && isMysqlCredential(token.value) && token.value !== `-p${REDACTION_MARKER}`)) {
|
|
672
|
+
first = Math.min(first, token.start);
|
|
673
|
+
}
|
|
674
|
+
const headerPrefix = token.value.startsWith("--header=") ? "--header=" : token.value.startsWith("-H") ? "-H" : "";
|
|
675
|
+
if (HEADER_PATTERN.test(token.value.slice(headerPrefix.length))) {
|
|
676
|
+
const headerStart = token.start + headerPrefix.length;
|
|
677
|
+
const quoted = line[headerStart] === "'" || line[headerStart] === '"';
|
|
678
|
+
const value = (quoted ? proseOperandWord(line, headerStart)?.value : undefined) ?? line.slice(headerStart).replace(/["']/g, "");
|
|
679
|
+
const header = HEADER_PATTERN.exec(value);
|
|
680
|
+
if (header !== null && redactHeader(value).count > 0) {
|
|
681
|
+
const previous = tokens[index - 1];
|
|
682
|
+
const start = previous !== undefined && ["-H", "--header"].includes(previous.value) ? previous.start : token.start;
|
|
683
|
+
first = Math.min(first, start);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
if (["mysql", "mariadb"].includes(token.value.split(/[\\/]/).at(-1)))
|
|
687
|
+
mysql = true;
|
|
688
|
+
}
|
|
689
|
+
const url = redactUrlUserinfo(line);
|
|
690
|
+
if (url.firstStart !== undefined)
|
|
691
|
+
first = Math.min(first, url.firstStart);
|
|
692
|
+
for (const pattern of [VENDOR_TOKEN_PATTERN, JWT_PATTERN, BEARER_PATTERN]) {
|
|
693
|
+
pattern.lastIndex = 0;
|
|
694
|
+
const evidence = pattern.exec(line);
|
|
695
|
+
if (evidence !== null)
|
|
696
|
+
first = Math.min(first, evidence.index);
|
|
697
|
+
}
|
|
698
|
+
return Number.isFinite(first) ? first : undefined;
|
|
699
|
+
}
|
|
700
|
+
function redactProseCommandsAndHeaders(text) {
|
|
701
|
+
const multiline = redactMultilineProseTokens(text);
|
|
702
|
+
let count = multiline.count;
|
|
703
|
+
const lines = multiline.text.split("\n").map((source) => {
|
|
704
|
+
const cr = source.endsWith("\r") ? "\r" : "";
|
|
705
|
+
const line = cr === "" ? source : source.slice(0, -1);
|
|
706
|
+
let single = 0;
|
|
707
|
+
let double = 0;
|
|
708
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
709
|
+
if (line[index] === "\\") {
|
|
710
|
+
index += 1;
|
|
711
|
+
continue;
|
|
712
|
+
}
|
|
713
|
+
if (line[index] === "'" && !isWordApostrophe(line, index))
|
|
714
|
+
single += 1;
|
|
715
|
+
if (line[index] === '"')
|
|
716
|
+
double += 1;
|
|
717
|
+
}
|
|
718
|
+
const assignments = redactAssignments(line);
|
|
719
|
+
if (single % 2 !== 0 || double % 2 !== 0 || (assignments.text === REDACTION_MARKER && assignments.count > 0)) {
|
|
720
|
+
const first = firstProseCredentialEvidence(line);
|
|
721
|
+
if (first === undefined)
|
|
722
|
+
return source;
|
|
723
|
+
count += 1;
|
|
724
|
+
return line.slice(0, first) + REDACTION_MARKER + cr;
|
|
725
|
+
}
|
|
726
|
+
const redacted = redactCommandsAndHeaders(line, 0, false, "prose");
|
|
727
|
+
count += redacted.count;
|
|
728
|
+
return redacted.text + cr;
|
|
729
|
+
});
|
|
730
|
+
return { text: lines.join("\n"), count };
|
|
731
|
+
}
|
|
732
|
+
function redactCommandsAndHeaders(text, depth = 0, evidenceOnly = false, mode = "command") {
|
|
733
|
+
const lines = text.split("\n");
|
|
734
|
+
let lineCount = 0;
|
|
735
|
+
text = lines.map((line) => {
|
|
736
|
+
const indent = /^[ \t]*/.exec(line)[0];
|
|
737
|
+
const redacted = redactHeader(line.slice(indent.length));
|
|
738
|
+
lineCount += redacted.count;
|
|
739
|
+
return indent + redacted.text;
|
|
740
|
+
}).join("\n");
|
|
741
|
+
const tokens = shellTokens(text, mode);
|
|
742
|
+
const edits = [];
|
|
743
|
+
let mysql = false;
|
|
744
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
745
|
+
const token = tokens[index];
|
|
746
|
+
if (/^[;|&\n]$/.test(token.value) && !token.quoted) {
|
|
747
|
+
mysql = false;
|
|
748
|
+
continue;
|
|
749
|
+
}
|
|
750
|
+
const source = text.slice(token.start, token.end);
|
|
751
|
+
const previous = tokens[index - 1];
|
|
752
|
+
const wrapper = tokens[index - 2]?.value.split(/[\\/]/).at(-1);
|
|
753
|
+
const quotedCommand = token.quoted && previous !== undefined && !previous.quoted && ((["sh", "bash", "zsh"].includes(wrapper ?? "") && /^-[A-Za-z]*c[A-Za-z]*$/.test(previous.value))
|
|
754
|
+
|| (wrapper === "env" && ["-S", "--split-string"].includes(previous.value)));
|
|
755
|
+
const assignment = /^([A-Za-z_][A-Za-z0-9_]*)=/.exec(token.value);
|
|
756
|
+
if (assignment !== null && isSensitiveKey(assignment[1]) && !quotedCommand) {
|
|
757
|
+
// Spaced prose values are handled by redactAssignments, including existing markers.
|
|
758
|
+
if (mode === "prose" && token.value === assignment[0] && /[ \t]/.test(text[token.end] ?? ""))
|
|
759
|
+
continue;
|
|
760
|
+
if (!source.startsWith(assignment[0])) {
|
|
761
|
+
const quote = source[0];
|
|
762
|
+
const mapped = (quote === '"' || quote === "'") && source === `${quote}${token.value}${quote}`;
|
|
763
|
+
const replacement = mapped ? `${quote}${assignment[0]}${REDACTION_MARKER}${quote}` : REDACTION_MARKER;
|
|
764
|
+
if (source !== replacement)
|
|
765
|
+
edits.push({ start: token.start, end: token.end, text: replacement, count: 1 });
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
const start = token.start + assignment[0].length;
|
|
769
|
+
const quote = text[start] === '"' || text[start] === "'" ? text[start] : "";
|
|
770
|
+
if (quote !== "" && (text.slice(start, token.end).includes("\n") || text[token.end - 1] !== quote)) {
|
|
771
|
+
return { text: REDACTION_MARKER, count: 1 };
|
|
772
|
+
}
|
|
773
|
+
const replacement = `${quote}${REDACTION_MARKER}${quote}${quote === "" && source.endsWith(",") ? "," : ""}`;
|
|
774
|
+
if (text.slice(start, token.end) !== replacement)
|
|
775
|
+
edits.push({ start, end: token.end, text: replacement, count: 1 });
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
const headerPrefix = token.value.startsWith("--header=") ? "--header=" : token.value.startsWith("-H") ? "-H" : "";
|
|
779
|
+
const header = headerPrefix !== "" || token.quoted || tokens[index - 1]?.value === "-H" || tokens[index - 1]?.value === "--header"
|
|
780
|
+
? redactHeader(token.value.slice(headerPrefix.length)) : { text: token.value, count: 0 };
|
|
781
|
+
if (header.count > 0) {
|
|
782
|
+
edits.push({ start: token.start, end: token.end, text: headerPrefix + JSON.stringify(header.text), count: 1 });
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
785
|
+
if (mysql && isMysqlCredential(token.value)) {
|
|
786
|
+
if (token.value.slice(2) !== REDACTION_MARKER)
|
|
787
|
+
edits.push({ start: token.start, end: token.end, text: `-p${REDACTION_MARKER}`, count: 1 });
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
if (!token.quoted && ["mysql", "mariadb"].includes(token.value.split(/[\\/]/).at(-1)))
|
|
791
|
+
mysql = true;
|
|
792
|
+
const credential = credentialOption(tokens, index);
|
|
793
|
+
if (credential !== undefined) {
|
|
794
|
+
const { flag, attached, operand, value } = credential;
|
|
795
|
+
const prefix = attached ? flag + (token.value[flag.length] === "=" ? "=" : "") : "";
|
|
796
|
+
if (value !== REDACTION_MARKER)
|
|
797
|
+
edits.push({ start: operand.start, end: operand.end, text: prefix + REDACTION_MARKER, count: 1 });
|
|
798
|
+
if (!attached)
|
|
799
|
+
index += 1;
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
if (token.quoted && (quotedCommand || /\s/.test(token.value)) && !evidenceOnly) {
|
|
803
|
+
const nested = redactCommandsAndHeaders(token.value, 1, depth > 0);
|
|
804
|
+
const count = nested.count + (depth === 0 ? 0 : redactAssignments(token.value).count + redactUrlUserinfo(token.value).count
|
|
805
|
+
+ Number(Boolean(token.value.match(VENDOR_TOKEN_PATTERN) || token.value.match(JWT_PATTERN) || token.value.match(BEARER_PATTERN))));
|
|
806
|
+
if (count > 0) {
|
|
807
|
+
const quote = source[0];
|
|
808
|
+
const mapped = (quote === '"' || quote === "'") && source === `${quote}${token.value}${quote}`;
|
|
809
|
+
edits.push({ start: token.start, end: token.end, text: mapped && depth === 0 ? `${quote}${nested.text}${quote}` : REDACTION_MARKER, count: depth === 0 ? count : 1 });
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
const result = applyTextEdits(text, edits);
|
|
814
|
+
return { text: result.text, count: result.count + lineCount };
|
|
815
|
+
}
|
|
816
|
+
function redactAssignments(text) {
|
|
817
|
+
const edits = [];
|
|
818
|
+
ASSIGNMENT_PATTERN.lastIndex = 0;
|
|
819
|
+
let match;
|
|
820
|
+
while ((match = ASSIGNMENT_PATTERN.exec(text)) !== null) {
|
|
821
|
+
if (!isSensitiveKey(match[2]))
|
|
822
|
+
continue;
|
|
823
|
+
if (match[1] === "-" && text[match.index - 1] === "-" && match[3].startsWith("=")
|
|
824
|
+
&& /^(?:user|proxy-user|http-user|auth|cookie|password|http-password|proxy-password|passwd)$/.test(match[2])
|
|
825
|
+
&& (match.index === 1 || /[\s;|&"']/.test(text[match.index - 2])))
|
|
826
|
+
continue;
|
|
827
|
+
if (match[3].includes(":") && /authorization|cookie/i.test(match[2]))
|
|
828
|
+
continue;
|
|
829
|
+
const start = ASSIGNMENT_PATTERN.lastIndex;
|
|
830
|
+
const quote = text[start] === '"' || text[start] === "'" ? text[start] : undefined;
|
|
831
|
+
let end = start + (quote === undefined ? 0 : 1);
|
|
832
|
+
if (quote === undefined && text.startsWith(REDACTION_MARKER, start))
|
|
833
|
+
end += REDACTION_MARKER.length;
|
|
834
|
+
for (; end < text.length; end += 1) {
|
|
835
|
+
const char = text[end];
|
|
836
|
+
if (quote === undefined && (/[\s,;&#}\]]/.test(char) || ((match[1] === '"' || match[1] === "'") && char === match[1])))
|
|
837
|
+
break;
|
|
838
|
+
if (quote !== undefined && char === "\n")
|
|
839
|
+
return { text: REDACTION_MARKER, count: 1 };
|
|
840
|
+
if (quote !== undefined && char === "\\")
|
|
841
|
+
end += 1;
|
|
842
|
+
else if (quote !== undefined && char === quote) {
|
|
843
|
+
end += 1;
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
const replacement = quote === undefined ? REDACTION_MARKER : `${quote}${REDACTION_MARKER}${quote}`;
|
|
848
|
+
if (text.slice(start, end) === replacement || end === start)
|
|
849
|
+
continue;
|
|
850
|
+
if (quote !== undefined && text[end - 1] !== quote)
|
|
851
|
+
return { text: REDACTION_MARKER, count: 1 };
|
|
852
|
+
if (quote !== undefined && end < text.length && !/[\s,;&#}\]]/.test(text[end]))
|
|
853
|
+
return { text: REDACTION_MARKER, count: 1 };
|
|
854
|
+
edits.push({ start, end, text: replacement, count: 1 });
|
|
855
|
+
ASSIGNMENT_PATTERN.lastIndex = end;
|
|
856
|
+
}
|
|
857
|
+
return applyTextEdits(text, edits);
|
|
858
|
+
}
|
|
859
|
+
function redactUrlUserinfo(text) {
|
|
860
|
+
const schemePattern = /\b[A-Za-z][A-Za-z0-9+.-]{0,63}:\/\//g;
|
|
861
|
+
const edits = [];
|
|
862
|
+
let firstStart;
|
|
863
|
+
let match;
|
|
864
|
+
while ((match = schemePattern.exec(text)) !== null) {
|
|
865
|
+
const start = schemePattern.lastIndex;
|
|
866
|
+
let end = start;
|
|
867
|
+
let colon = -1;
|
|
868
|
+
let at = -1;
|
|
869
|
+
for (; end < text.length && !/[\s/?#"'<>`]/.test(text[end]); end += 1) {
|
|
870
|
+
if (text[end] === ":" && colon < 0)
|
|
871
|
+
colon = end;
|
|
872
|
+
if (text[end] === "@")
|
|
873
|
+
at = end;
|
|
874
|
+
}
|
|
875
|
+
if (colon >= start && at > colon) {
|
|
876
|
+
firstStart ??= match.index;
|
|
877
|
+
edits.push({ start, end: at, text: REDACTION_MARKER, count: 1 });
|
|
878
|
+
}
|
|
879
|
+
schemePattern.lastIndex = end;
|
|
880
|
+
}
|
|
881
|
+
return { ...applyTextEdits(text, edits), firstStart };
|
|
882
|
+
}
|
|
883
|
+
function redactText(value, maxChars = Number.POSITIVE_INFINITY, depth = 0, mode = "command") {
|
|
884
|
+
if (PRIVATE_KEY_SIGNAL_PATTERN.test(value))
|
|
885
|
+
return { text: REDACTION_MARKER, count: 1 };
|
|
886
|
+
const embedded = redactEmbeddedJson(value, depth);
|
|
887
|
+
const commands = mode === "prose" ? redactProseCommandsAndHeaders(embedded.text) : redactCommandsAndHeaders(embedded.text);
|
|
888
|
+
let text = commands.text;
|
|
889
|
+
let count = embedded.count + commands.count;
|
|
150
890
|
const replace = (pattern, replacement) => {
|
|
151
891
|
text = text.replace(pattern, (...args) => {
|
|
152
892
|
count += 1;
|
|
153
893
|
return replacement(...args.slice(0, -2).map(String));
|
|
154
894
|
});
|
|
155
895
|
};
|
|
156
|
-
replace(BASIC_AUTHORIZATION_PATTERN, (_match, prefix) => `${prefix}${REDACTION_MARKER}`);
|
|
157
896
|
replace(BEARER_PATTERN, (match) => `${match.slice(0, match.search(/\s/))} ${REDACTION_MARKER}`);
|
|
158
897
|
replace(SENSITIVE_QUERY_PATTERN, (_match, prefix) => `${prefix}${REDACTION_MARKER}`);
|
|
159
|
-
|
|
160
|
-
|
|
898
|
+
const assignments = redactAssignments(text);
|
|
899
|
+
text = assignments.text;
|
|
900
|
+
count += assignments.count;
|
|
901
|
+
const urls = redactUrlUserinfo(text);
|
|
902
|
+
text = urls.text;
|
|
903
|
+
count += urls.count;
|
|
161
904
|
replace(VENDOR_TOKEN_PATTERN, () => REDACTION_MARKER);
|
|
905
|
+
replace(JWT_PATTERN, () => REDACTION_MARKER);
|
|
162
906
|
replace(POSIX_USER_HOME_PATTERN, () => REDACTION_MARKER);
|
|
163
907
|
replace(ROOT_USER_HOME_PATTERN, () => REDACTION_MARKER);
|
|
164
908
|
replace(WINDOWS_USER_HOME_PATTERN, () => REDACTION_MARKER);
|
|
165
|
-
if (count > MAX_REDACTIONS
|
|
166
|
-
text.length > maxChars ||
|
|
167
|
-
hasSensitiveAssignmentResidual(text)) {
|
|
909
|
+
if (count > MAX_REDACTIONS)
|
|
168
910
|
return { text: REDACTION_MARKER, count: 1 };
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if (tail.startsWith(REDACTION_MARKER)) {
|
|
180
|
-
const next = tail[REDACTION_MARKER.length];
|
|
181
|
-
if (next === undefined || /[\s&;,}\]]/.test(next))
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
const quote = tail[0];
|
|
185
|
-
if (quote === "\"" || quote === "'") {
|
|
186
|
-
const safeQuoted = `${quote}${REDACTION_MARKER}${quote}`;
|
|
187
|
-
if (tail.startsWith(safeQuoted)) {
|
|
188
|
-
const next = tail[safeQuoted.length];
|
|
189
|
-
if (next === undefined || /[\s&;,}\]]/.test(next))
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
return false;
|
|
911
|
+
if (text.length <= maxChars)
|
|
912
|
+
return { text, count };
|
|
913
|
+
let end = maxChars;
|
|
914
|
+
const markerStart = text.lastIndexOf(REDACTION_MARKER, end - 1);
|
|
915
|
+
if (markerStart >= 0 && markerStart + REDACTION_MARKER.length > end)
|
|
916
|
+
end = markerStart;
|
|
917
|
+
const code = text.charCodeAt(end - 1);
|
|
918
|
+
if (code >= 0xd800 && code <= 0xdbff)
|
|
919
|
+
end -= 1;
|
|
920
|
+
return { text: text.slice(0, end), count, truncated: true };
|
|
196
921
|
}
|
|
197
922
|
function validateNormalizedEvent(value) {
|
|
198
923
|
if (!isPlainRecord(value) || typeof value["kind"] !== "string")
|
|
@@ -205,7 +930,7 @@ function validateNormalizedEvent(value) {
|
|
|
205
930
|
}
|
|
206
931
|
return { kind: value["kind"], content: value["content"] };
|
|
207
932
|
case "tool_call":
|
|
208
|
-
if (!hasExactKeys(value, ["kind", "toolName", "callId", "summary"]) ||
|
|
933
|
+
if (!hasExactKeys(value, ["kind", "toolName", "callId", "summary", ...(Object.hasOwn(value, "arguments") ? ["arguments"] : [])]) ||
|
|
209
934
|
!isAdmissibleIdentifier(value["toolName"]) ||
|
|
210
935
|
!isAdmissibleIdentifier(value["callId"]) ||
|
|
211
936
|
typeof value["summary"] !== "string")
|
|
@@ -215,6 +940,7 @@ function validateNormalizedEvent(value) {
|
|
|
215
940
|
toolName: value["toolName"],
|
|
216
941
|
callId: value["callId"],
|
|
217
942
|
summary: value["summary"],
|
|
943
|
+
...(Object.hasOwn(value, "arguments") ? { arguments: validateArgumentDetail(value["arguments"]) } : {}),
|
|
218
944
|
};
|
|
219
945
|
case "tool_result":
|
|
220
946
|
if (!hasExactKeys(value, ["kind", "toolName", "callId", "outcome", "summary"]) ||
|
|
@@ -325,6 +1051,14 @@ function validateNormalizedEvent(value) {
|
|
|
325
1051
|
return invalidEvent();
|
|
326
1052
|
}
|
|
327
1053
|
}
|
|
1054
|
+
function validateArgumentDetail(value) {
|
|
1055
|
+
if (!isPlainRecord(value) || !hasExactKeys(value, ["content", "omission"]) ||
|
|
1056
|
+
typeof value["content"] !== "string" ||
|
|
1057
|
+
!isOneOf(value["omission"], ["none", "size_limit", "structure_limit", "invalid"]) ||
|
|
1058
|
+
(value["omission"] !== "none" && value["content"] !== ""))
|
|
1059
|
+
return invalidEvent();
|
|
1060
|
+
return { content: value["content"], omission: value["omission"] };
|
|
1061
|
+
}
|
|
328
1062
|
function invalidEvent() {
|
|
329
1063
|
throw new TypeError("unsupported session-log-v1 normalized event");
|
|
330
1064
|
}
|