clinkx 0.3.0 → 1.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 +119 -0
- package/LICENSE +10 -0
- package/README.md +154 -26
- package/clinkx-workflows/dist/allowed-roots.d.ts +1 -0
- package/clinkx-workflows/dist/allowed-roots.js +5 -0
- package/clinkx-workflows/dist/allowed-roots.js.map +1 -0
- package/clinkx-workflows/dist/artifacts.d.ts +20 -1
- package/clinkx-workflows/dist/artifacts.js +38 -9
- package/clinkx-workflows/dist/artifacts.js.map +1 -1
- package/clinkx-workflows/dist/backend.d.ts +4 -0
- 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/citation-check.d.ts +40 -0
- package/clinkx-workflows/dist/citation-check.js +348 -0
- package/clinkx-workflows/dist/citation-check.js.map +1 -0
- package/clinkx-workflows/dist/clink-client.d.ts +12 -1
- package/clinkx-workflows/dist/clink-client.js +56 -5
- package/clinkx-workflows/dist/clink-client.js.map +1 -1
- package/clinkx-workflows/dist/config.d.ts +173 -16
- package/clinkx-workflows/dist/config.js +417 -26
- package/clinkx-workflows/dist/config.js.map +1 -1
- package/clinkx-workflows/dist/contained-file.d.ts +172 -0
- package/clinkx-workflows/dist/contained-file.js +483 -0
- package/clinkx-workflows/dist/contained-file.js.map +1 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-contract.d.ts +416 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-contract.js +328 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-contract.js.map +1 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-router.d.ts +24 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-router.js +39 -0
- package/clinkx-workflows/dist/context-builder-lifecycle-router.js.map +1 -0
- package/clinkx-workflows/dist/context-cache-key.d.ts +53 -0
- package/clinkx-workflows/dist/context-cache-key.js +91 -0
- package/clinkx-workflows/dist/context-cache-key.js.map +1 -0
- package/clinkx-workflows/dist/context-mode-resolver.d.ts +27 -0
- package/clinkx-workflows/dist/context-mode-resolver.js +126 -0
- package/clinkx-workflows/dist/context-mode-resolver.js.map +1 -0
- package/clinkx-workflows/dist/context-preflight.d.ts +189 -0
- package/clinkx-workflows/dist/context-preflight.js +91 -0
- package/clinkx-workflows/dist/context-preflight.js.map +1 -0
- 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 +10 -9
- package/clinkx-workflows/dist/council-expander.js +20 -10
- package/clinkx-workflows/dist/council-expander.js.map +1 -1
- package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +5718 -0
- package/clinkx-workflows/dist/dashboard/activity-contract.js +157 -0
- package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/activity-lease.d.ts +25 -0
- package/clinkx-workflows/dist/dashboard/activity-lease.js +34 -0
- package/clinkx-workflows/dist/dashboard/activity-lease.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +90 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.js +493 -0
- package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/api-routes.d.ts +40 -0
- package/clinkx-workflows/dist/dashboard/api-routes.js +1155 -0
- package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/artifact-index.d.ts +37 -0
- package/clinkx-workflows/dist/dashboard/artifact-index.js +145 -0
- package/clinkx-workflows/dist/dashboard/artifact-index.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/challenge-store.d.ts +31 -0
- package/clinkx-workflows/dist/dashboard/auth/challenge-store.js +65 -0
- package/clinkx-workflows/dist/dashboard/auth/challenge-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/controller.d.ts +26 -0
- package/clinkx-workflows/dist/dashboard/auth/controller.js +596 -0
- package/clinkx-workflows/dist/dashboard/auth/controller.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/credential-store.d.ts +60 -0
- package/clinkx-workflows/dist/dashboard/auth/credential-store.js +776 -0
- package/clinkx-workflows/dist/dashboard/auth/credential-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.d.ts +9 -0
- package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js +44 -0
- package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/session-store.d.ts +17 -0
- package/clinkx-workflows/dist/dashboard/auth/session-store.js +77 -0
- package/clinkx-workflows/dist/dashboard/auth/session-store.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/auth/webauthn-service.d.ts +25 -0
- package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js +71 -0
- package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js.map +1 -0
- 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-liveness.d.ts +37 -0
- package/clinkx-workflows/dist/dashboard/context-run-liveness.js +49 -0
- package/clinkx-workflows/dist/dashboard/context-run-liveness.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +256 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.js +1301 -0
- package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/contracts.d.ts +28483 -0
- package/clinkx-workflows/dist/dashboard/contracts.js +1284 -0
- package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/generation.d.ts +14 -0
- package/clinkx-workflows/dist/dashboard/generation.js +41 -0
- package/clinkx-workflows/dist/dashboard/generation.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/index.d.ts +32 -0
- package/clinkx-workflows/dist/dashboard/index.js +20 -0
- package/clinkx-workflows/dist/dashboard/index.js.map +1 -0
- 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 +114 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.js +799 -0
- package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -0
- 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 +446 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js +221 -0
- package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-reader.d.ts +50 -0
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js +529 -0
- package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.d.ts +89 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js +71 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +29 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +111 -0
- package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -0
- 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/read-model.d.ts +86 -0
- package/clinkx-workflows/dist/dashboard/read-model.js +440 -0
- package/clinkx-workflows/dist/dashboard/read-model.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/request-admission.d.ts +31 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js +130 -0
- package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/scanner.d.ts +78 -0
- package/clinkx-workflows/dist/dashboard/scanner.js +1154 -0
- package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/server-factory.d.ts +63 -0
- package/clinkx-workflows/dist/dashboard/server-factory.js +483 -0
- package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +2778 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.js +506 -0
- package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-identity.d.ts +21 -0
- package/clinkx-workflows/dist/dashboard/session-log-identity.js +101 -0
- package/clinkx-workflows/dist/dashboard/session-log-identity.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-journal.d.ts +43 -0
- package/clinkx-workflows/dist/dashboard/session-log-journal.js +529 -0
- package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-reader.d.ts +61 -0
- package/clinkx-workflows/dist/dashboard/session-log-reader.js +378 -0
- package/clinkx-workflows/dist/dashboard/session-log-reader.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +14 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js +1101 -0
- package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/session-log-retention.d.ts +23 -0
- package/clinkx-workflows/dist/dashboard/session-log-retention.js +212 -0
- package/clinkx-workflows/dist/dashboard/session-log-retention.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/static-responses.d.ts +10 -0
- package/clinkx-workflows/dist/dashboard/static-responses.js +94 -0
- package/clinkx-workflows/dist/dashboard/static-responses.js.map +1 -0
- package/clinkx-workflows/dist/dashboard/worker-lock-observer.d.ts +28 -0
- package/clinkx-workflows/dist/dashboard/worker-lock-observer.js +241 -0
- package/clinkx-workflows/dist/dashboard/worker-lock-observer.js.map +1 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/index-JPHQVtn9.js +106 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
- package/clinkx-workflows/dist/dashboard-ui/assets/style-DOMHk-j6.css +1 -0
- package/clinkx-workflows/dist/dashboard-ui/contracts/council-plan-review-input-v1.json +158 -0
- package/clinkx-workflows/dist/dashboard-ui/index.html +15 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Mono-OFL.txt +93 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Sans-OFL.txt +93 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/JetBrains-Mono-OFL.txt +93 -0
- package/clinkx-workflows/dist/dashboard-ui/licenses/NOTICE.md +32 -0
- package/clinkx-workflows/dist/dashboard-ui/theme-init.js +8 -0
- package/clinkx-workflows/dist/definition-normalizer.d.ts +19 -0
- package/clinkx-workflows/dist/definition-normalizer.js.map +1 -1
- 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 +108 -0
- package/clinkx-workflows/dist/delegate-validate.js +148 -0
- package/clinkx-workflows/dist/delegate-validate.js.map +1 -0
- package/clinkx-workflows/dist/engine.d.ts +251 -0
- package/clinkx-workflows/dist/engine.js +1342 -92
- package/clinkx-workflows/dist/engine.js.map +1 -1
- package/clinkx-workflows/dist/errors.d.ts +24 -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 +25 -7
- package/clinkx-workflows/dist/fingerprint.js +33 -10
- package/clinkx-workflows/dist/fingerprint.js.map +1 -1
- package/clinkx-workflows/dist/index.d.ts +19 -0
- package/clinkx-workflows/dist/index.js +68 -1
- package/clinkx-workflows/dist/index.js.map +1 -1
- package/clinkx-workflows/dist/loader.d.ts +9 -3
- package/clinkx-workflows/dist/loader.js +92 -21
- package/clinkx-workflows/dist/loader.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/admission.d.ts +160 -0
- package/clinkx-workflows/dist/plan-review/admission.js +290 -0
- package/clinkx-workflows/dist/plan-review/admission.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/context-policy.d.ts +55 -0
- package/clinkx-workflows/dist/plan-review/context-policy.js +146 -0
- package/clinkx-workflows/dist/plan-review/context-policy.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/contract.d.ts +763 -0
- package/clinkx-workflows/dist/plan-review/contract.js +289 -0
- package/clinkx-workflows/dist/plan-review/contract.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/digest.d.ts +68 -0
- package/clinkx-workflows/dist/plan-review/digest.js +241 -0
- package/clinkx-workflows/dist/plan-review/digest.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/discovery.d.ts +45 -0
- package/clinkx-workflows/dist/plan-review/discovery.js +36 -0
- package/clinkx-workflows/dist/plan-review/discovery.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/execution-contract.d.ts +298 -0
- package/clinkx-workflows/dist/plan-review/execution-contract.js +194 -0
- package/clinkx-workflows/dist/plan-review/execution-contract.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/expander.d.ts +48 -0
- package/clinkx-workflows/dist/plan-review/expander.js +243 -0
- package/clinkx-workflows/dist/plan-review/expander.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/projector.d.ts +126 -0
- package/clinkx-workflows/dist/plan-review/projector.js +437 -0
- package/clinkx-workflows/dist/plan-review/projector.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/publication.d.ts +95 -0
- package/clinkx-workflows/dist/plan-review/publication.js +182 -0
- package/clinkx-workflows/dist/plan-review/publication.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/ranking-reducer.d.ts +34 -0
- package/clinkx-workflows/dist/plan-review/ranking-reducer.js +139 -0
- package/clinkx-workflows/dist/plan-review/ranking-reducer.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/resume.d.ts +38 -0
- package/clinkx-workflows/dist/plan-review/resume.js +122 -0
- package/clinkx-workflows/dist/plan-review/resume.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/rubric.d.ts +7 -0
- package/clinkx-workflows/dist/plan-review/rubric.js +24 -0
- package/clinkx-workflows/dist/plan-review/rubric.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/run-state.d.ts +398 -0
- package/clinkx-workflows/dist/plan-review/run-state.js +183 -0
- package/clinkx-workflows/dist/plan-review/run-state.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/runtime-policy.d.ts +74 -0
- package/clinkx-workflows/dist/plan-review/runtime-policy.js +186 -0
- package/clinkx-workflows/dist/plan-review/runtime-policy.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/snapshot.d.ts +81 -0
- package/clinkx-workflows/dist/plan-review/snapshot.js +170 -0
- package/clinkx-workflows/dist/plan-review/snapshot.js.map +1 -0
- package/clinkx-workflows/dist/plan-review/stage-error.d.ts +19 -0
- package/clinkx-workflows/dist/plan-review/stage-error.js +20 -0
- package/clinkx-workflows/dist/plan-review/stage-error.js.map +1 -0
- package/clinkx-workflows/dist/post-processors/index.d.ts +20 -0
- package/clinkx-workflows/dist/post-processors/index.js +21 -0
- package/clinkx-workflows/dist/post-processors/index.js.map +1 -0
- package/clinkx-workflows/dist/prompt-budget.d.ts +7 -0
- package/clinkx-workflows/dist/prompt-budget.js +7 -1
- package/clinkx-workflows/dist/prompt-budget.js.map +1 -1
- package/clinkx-workflows/dist/reducers/index.js +2 -0
- package/clinkx-workflows/dist/reducers/index.js.map +1 -1
- 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/run-lock-heartbeat.d.ts +2 -0
- package/clinkx-workflows/dist/run-lock-heartbeat.js +3 -0
- package/clinkx-workflows/dist/run-lock-heartbeat.js.map +1 -0
- package/clinkx-workflows/dist/run-summary/cards/council-plan-review.d.ts +34 -0
- package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js +74 -0
- package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js.map +1 -0
- package/clinkx-workflows/dist/run-summary/cards/index.js +5 -0
- package/clinkx-workflows/dist/run-summary/cards/index.js.map +1 -1
- package/clinkx-workflows/dist/run-summary.d.ts +13 -0
- package/clinkx-workflows/dist/run-summary.js +29 -1
- package/clinkx-workflows/dist/run-summary.js.map +1 -1
- package/clinkx-workflows/dist/schema.d.ts +140 -21
- package/clinkx-workflows/dist/schema.js +34 -1
- package/clinkx-workflows/dist/schema.js.map +1 -1
- package/clinkx-workflows/dist/seed-terms.d.ts +30 -0
- package/clinkx-workflows/dist/seed-terms.js +176 -0
- package/clinkx-workflows/dist/seed-terms.js.map +1 -0
- package/clinkx-workflows/dist/server.d.ts +5 -2
- package/clinkx-workflows/dist/server.js +84 -52
- package/clinkx-workflows/dist/server.js.map +1 -1
- package/clinkx-workflows/dist/shutdown.d.ts +29 -18
- package/clinkx-workflows/dist/shutdown.js +90 -64
- package/clinkx-workflows/dist/shutdown.js.map +1 -1
- package/clinkx-workflows/dist/state-schema.d.ts +61 -0
- package/clinkx-workflows/dist/state.d.ts +175 -3
- package/clinkx-workflows/dist/state.js +1398 -44
- 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/transport.d.ts +15 -0
- package/clinkx-workflows/dist/transport.js +19 -10
- package/clinkx-workflows/dist/transport.js.map +1 -1
- package/clinkx-workflows/dist/types.d.ts +20 -2
- package/clinkx-workflows/dist/validators/council.d.ts +334 -101
- package/clinkx-workflows/dist/validators/council.js +52 -13
- package/clinkx-workflows/dist/validators/council.js.map +1 -1
- package/clinkx-workflows/dist/validators/index.d.ts +1 -0
- package/clinkx-workflows/dist/validators/index.js +3 -0
- package/clinkx-workflows/dist/validators/index.js.map +1 -1
- package/clinkx-workflows/dist/validators/plan-review.d.ts +398 -0
- package/clinkx-workflows/dist/validators/plan-review.js +316 -0
- package/clinkx-workflows/dist/validators/plan-review.js.map +1 -0
- package/clinkx-workflows/dist/workflow-receipt.js +188 -4
- package/clinkx-workflows/dist/workflow-receipt.js.map +1 -1
- package/clinkx-workflows/dist/workflow-tools.d.ts +118 -3
- package/clinkx-workflows/dist/workflow-tools.js +2110 -175
- package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
- package/clinkx-workflows/dist/workspace-signature.d.ts +16 -0
- package/clinkx-workflows/dist/workspace-signature.js +388 -0
- package/clinkx-workflows/dist/workspace-signature.js.map +1 -0
- 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 +5 -1
- package/clinkx-workflows/templates/council/chairman.txt +3 -0
- package/clinkx-workflows/templates/council/code-review-chairman.txt +16 -0
- package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +7 -1
- package/clinkx-workflows/templates/council/code-review-reviewer.txt +22 -3
- 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 +5 -1
- package/clinkx-workflows/templates/council/discover-analyst.txt +5 -1
- 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 +5 -1
- package/clinkx-workflows/templates/council/plan-review-chairman.txt +71 -0
- package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +54 -0
- package/clinkx-workflows/templates/council/plan-review-reviewer.txt +63 -0
- package/clinkx-workflows/templates/council/reviewer.txt +3 -0
- package/clinkx-workflows/templates/plan-review/chairman.txt +48 -0
- package/clinkx-workflows/templates/plan-review/cross-review.txt +52 -0
- package/clinkx-workflows/templates/plan-review/ranking.txt +16 -0
- package/clinkx-workflows/templates/plan-review/reviewer.txt +57 -0
- package/clinkx-workflows/workflows/council-answer.yaml +100 -8
- package/clinkx-workflows/workflows/council-code-review.yaml +106 -8
- package/clinkx-workflows/workflows/council-debug.yaml +100 -8
- package/clinkx-workflows/workflows/council-default.yaml +104 -8
- package/clinkx-workflows/workflows/council-discover.yaml +100 -8
- package/clinkx-workflows/workflows/council-plan-review.yaml +253 -0
- package/clinkx-workflows/workflows/plan-review-receipts.yaml +230 -0
- package/conf/adapters/agy.json +56 -0
- package/conf/adapters/claude.json +12 -0
- package/conf/adapters/codex-app-server.json +44 -0
- package/conf/adapters/codex.json +14 -2
- package/conf/adapters/glm.json +24 -3
- package/conf/adapters/grok.json +67 -0
- package/conf/adapters/hapi/agy.json +59 -0
- package/conf/adapters/hapi/claude.json +12 -0
- package/conf/adapters/hapi/codex.json +14 -2
- package/conf/adapters/hapi/glm.json +25 -4
- package/conf/rosters/default-council.json +21 -0
- package/dist/activity-journal.d.ts +56 -0
- package/dist/activity-journal.js +706 -0
- package/dist/activity-journal.js.map +1 -0
- package/dist/allowed-roots.d.ts +41 -0
- package/dist/allowed-roots.js +125 -3
- package/dist/allowed-roots.js.map +1 -1
- package/dist/codex/activity-session-log.d.ts +14 -0
- package/dist/codex/activity-session-log.js +206 -0
- package/dist/codex/activity-session-log.js.map +1 -0
- package/dist/codex/agent-tools.d.ts +23 -0
- package/dist/codex/agent-tools.js +1306 -0
- package/dist/codex/agent-tools.js.map +1 -0
- package/dist/codex/app-server-client.d.ts +79 -0
- package/dist/codex/app-server-client.js +593 -0
- package/dist/codex/app-server-client.js.map +1 -0
- package/dist/codex/app-server-registry.d.ts +407 -0
- package/dist/codex/app-server-registry.js +1493 -0
- package/dist/codex/app-server-registry.js.map +1 -0
- package/dist/codex/client-types.d.ts +115 -0
- package/dist/codex/client-types.js +2 -0
- package/dist/codex/client-types.js.map +1 -0
- package/dist/codex/protocol.d.ts +5375 -0
- package/dist/codex/protocol.js +614 -0
- package/dist/codex/protocol.js.map +1 -0
- package/dist/codex/session.d.ts +263 -0
- package/dist/codex/session.js +466 -0
- package/dist/codex/session.js.map +1 -0
- package/dist/config.d.ts +386 -23
- package/dist/config.js +184 -15
- package/dist/config.js.map +1 -1
- package/dist/context-builder/anchors.d.ts +1 -0
- package/dist/context-builder/anchors.js +3 -0
- package/dist/context-builder/anchors.js.map +1 -1
- package/dist/context-builder/bundle.d.ts +48 -2
- package/dist/context-builder/bundle.js +388 -2417
- package/dist/context-builder/bundle.js.map +1 -1
- package/dist/context-builder/codemap.d.ts +5 -0
- package/dist/context-builder/codemap.js +387 -13
- package/dist/context-builder/codemap.js.map +1 -1
- package/dist/context-builder/config.d.ts +1 -0
- package/dist/context-builder/config.js +12 -0
- package/dist/context-builder/config.js.map +1 -1
- package/dist/context-builder/context-run-catalog.d.ts +17 -0
- package/dist/context-builder/context-run-catalog.js +99 -0
- package/dist/context-builder/context-run-catalog.js.map +1 -0
- 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 +55 -0
- package/dist/context-builder/delegate-packet.js +430 -74
- 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 +41 -1
- package/dist/context-builder/delegate-validation.js +237 -48
- package/dist/context-builder/delegate-validation.js.map +1 -1
- package/dist/context-builder/delegate.d.ts +11 -0
- package/dist/context-builder/delegate.js +210 -58
- package/dist/context-builder/delegate.js.map +1 -1
- package/dist/context-builder/diagnostics.d.ts +11 -2
- package/dist/context-builder/diagnostics.js +39 -6
- 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 +36 -1
- package/dist/context-builder/discovery.js +584 -172
- 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 +25 -10
- package/dist/context-builder/globs.js.map +1 -1
- package/dist/context-builder/handler.d.ts +67 -2
- package/dist/context-builder/handler.js +1810 -2440
- 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 +424 -0
- package/dist/context-builder/lifecycle-contract.js +523 -0
- package/dist/context-builder/lifecycle-contract.js.map +1 -0
- package/dist/context-builder/lifecycle-journal.d.ts +62 -0
- package/dist/context-builder/lifecycle-journal.js +88 -0
- package/dist/context-builder/lifecycle-journal.js.map +1 -0
- package/dist/context-builder/lifecycle-observer.d.ts +102 -0
- package/dist/context-builder/lifecycle-observer.js +258 -0
- package/dist/context-builder/lifecycle-observer.js.map +1 -0
- package/dist/context-builder/lifecycle-reconcile.d.ts +32 -0
- package/dist/context-builder/lifecycle-reconcile.js +191 -0
- package/dist/context-builder/lifecycle-reconcile.js.map +1 -0
- package/dist/context-builder/lifecycle-recovery.d.ts +2 -0
- package/dist/context-builder/lifecycle-recovery.js +14 -0
- package/dist/context-builder/lifecycle-recovery.js.map +1 -0
- 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/path-signals.js +9 -5
- package/dist/context-builder/path-signals.js.map +1 -1
- package/dist/context-builder/progress.d.ts +3 -1
- package/dist/context-builder/progress.js +23 -0
- package/dist/context-builder/progress.js.map +1 -1
- package/dist/context-builder/receipt.d.ts +6 -4
- package/dist/context-builder/receipt.js +12 -1
- package/dist/context-builder/receipt.js.map +1 -1
- package/dist/context-builder/refine-handler.d.ts +14 -0
- package/dist/context-builder/refine-handler.js +790 -0
- package/dist/context-builder/refine-handler.js.map +1 -0
- package/dist/context-builder/review-coverage.d.ts +116 -0
- package/dist/context-builder/review-coverage.js +544 -0
- package/dist/context-builder/review-coverage.js.map +1 -0
- package/dist/context-builder/schema.d.ts +315 -2
- package/dist/context-builder/schema.js +133 -7
- package/dist/context-builder/schema.js.map +1 -1
- package/dist/context-builder/secrets.d.ts +59 -0
- package/dist/context-builder/secrets.js +2255 -44
- package/dist/context-builder/secrets.js.map +1 -1
- package/dist/context-builder/selection.d.ts +7 -3
- package/dist/context-builder/selection.js +200 -738
- package/dist/context-builder/selection.js.map +1 -1
- package/dist/context-builder/session-log-identity.d.ts +1 -0
- package/dist/context-builder/session-log-identity.js +66 -0
- package/dist/context-builder/session-log-identity.js.map +1 -0
- package/dist/context-builder/slice-range-math.d.ts +16 -0
- package/dist/context-builder/slice-range-math.js +137 -0
- package/dist/context-builder/slice-range-math.js.map +1 -0
- package/dist/context-builder/status.d.ts +23 -2
- package/dist/context-builder/status.js +60 -4
- package/dist/context-builder/status.js.map +1 -1
- package/dist/context-builder/storage.d.ts +59 -2
- package/dist/context-builder/storage.js +500 -168
- package/dist/context-builder/storage.js.map +1 -1
- package/dist/context-builder/token-estimate.d.ts +13 -2
- package/dist/context-builder/token-estimate.js +77 -4
- package/dist/context-builder/token-estimate.js.map +1 -1
- package/dist/context-builder/versions.d.ts +44 -0
- package/dist/context-builder/versions.js +45 -0
- package/dist/context-builder/versions.js.map +1 -0
- package/dist/context-builder/workflow-runner.d.ts +75 -0
- package/dist/context-builder/workflow-runner.js +1241 -0
- package/dist/context-builder/workflow-runner.js.map +1 -0
- package/dist/context-builder/write-watchdog.d.ts +14 -0
- package/dist/context-builder/write-watchdog.js +38 -0
- package/dist/context-builder/write-watchdog.js.map +1 -0
- package/dist/dashboard-adapter-catalog.d.ts +9 -0
- package/dist/dashboard-adapter-catalog.js +53 -0
- package/dist/dashboard-adapter-catalog.js.map +1 -0
- package/dist/dashboard-cli.d.ts +81 -0
- package/dist/dashboard-cli.js +623 -0
- package/dist/dashboard-cli.js.map +1 -0
- package/dist/dashboard-companion/client.d.ts +37 -0
- package/dist/dashboard-companion/client.js +229 -0
- package/dist/dashboard-companion/client.js.map +1 -0
- package/dist/dashboard-companion/config.d.ts +8 -0
- package/dist/dashboard-companion/config.js +52 -0
- package/dist/dashboard-companion/config.js.map +1 -0
- package/dist/dashboard-companion/http-proxy.d.ts +10 -0
- package/dist/dashboard-companion/http-proxy.js +234 -0
- package/dist/dashboard-companion/http-proxy.js.map +1 -0
- package/dist/dashboard-companion/protocol.d.ts +80 -0
- package/dist/dashboard-companion/protocol.js +458 -0
- package/dist/dashboard-companion/protocol.js.map +1 -0
- package/dist/dashboard-companion/standalone-assets.d.ts +12 -0
- package/dist/dashboard-companion/standalone-assets.js +72 -0
- package/dist/dashboard-companion/standalone-assets.js.map +1 -0
- package/dist/dashboard-companion/standalone-entry.d.ts +1 -0
- package/dist/dashboard-companion/standalone-entry.js +17 -0
- package/dist/dashboard-companion/standalone-entry.js.map +1 -0
- package/dist/dashboard-gateway/config.d.ts +9 -0
- package/dist/dashboard-gateway/config.js +44 -0
- package/dist/dashboard-gateway/config.js.map +1 -0
- package/dist/dashboard-gateway/index.d.ts +12 -0
- package/dist/dashboard-gateway/index.js +786 -0
- package/dist/dashboard-gateway/index.js.map +1 -0
- package/dist/dashboard-gateway/main.d.ts +1 -0
- package/dist/dashboard-gateway/main.js +25 -0
- package/dist/dashboard-gateway/main.js.map +1 -0
- package/dist/dashboard-pairing-token.d.ts +1 -0
- package/dist/dashboard-pairing-token.js +5 -0
- package/dist/dashboard-pairing-token.js.map +1 -0
- package/dist/elaris/client.d.ts +73 -0
- package/dist/elaris/client.js +825 -0
- package/dist/elaris/client.js.map +1 -0
- package/dist/elaris/config.d.ts +18 -0
- package/dist/elaris/config.js +75 -0
- package/dist/elaris/config.js.map +1 -0
- package/dist/elaris/errors.d.ts +41 -0
- package/dist/elaris/errors.js +205 -0
- package/dist/elaris/errors.js.map +1 -0
- package/dist/elaris/github-repo.d.ts +18 -0
- package/dist/elaris/github-repo.js +89 -0
- package/dist/elaris/github-repo.js.map +1 -0
- package/dist/elaris/index.d.ts +10 -0
- package/dist/elaris/index.js +11 -0
- package/dist/elaris/index.js.map +1 -0
- package/dist/elaris/redact.d.ts +89 -0
- package/dist/elaris/redact.js +423 -0
- package/dist/elaris/redact.js.map +1 -0
- package/dist/elaris/run-id.d.ts +5 -0
- package/dist/elaris/run-id.js +20 -0
- package/dist/elaris/run-id.js.map +1 -0
- package/dist/elaris/status.d.ts +45 -0
- package/dist/elaris/status.js +122 -0
- package/dist/elaris/status.js.map +1 -0
- package/dist/elaris/tool-schemas.d.ts +314 -0
- package/dist/elaris/tool-schemas.js +208 -0
- package/dist/elaris/tool-schemas.js.map +1 -0
- package/dist/elaris/tools.d.ts +73 -0
- package/dist/elaris/tools.js +1065 -0
- package/dist/elaris/tools.js.map +1 -0
- package/dist/elaris/types.d.ts +1541 -0
- package/dist/elaris/types.js +252 -0
- package/dist/elaris/types.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +19 -0
- package/dist/errors.js.map +1 -1
- package/dist/handler.d.ts +4 -0
- package/dist/handler.js +15 -2
- package/dist/handler.js.map +1 -1
- package/dist/index.d.ts +20 -2
- package/dist/index.js +154 -6
- package/dist/index.js.map +1 -1
- package/dist/local-clink-backend.d.ts +3 -0
- package/dist/local-clink-backend.js +4 -0
- package/dist/local-clink-backend.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/package-info.d.ts +2 -0
- package/dist/package-info.js +18 -0
- package/dist/package-info.js.map +1 -0
- 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/codex-jsonl.js +48 -3
- package/dist/parsers/codex-jsonl.js.map +1 -1
- package/dist/parsers/extract.d.ts +10 -1
- package/dist/parsers/extract.js +16 -4
- 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 +17 -0
- package/dist/pipeline.js +181 -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/run-dir.d.ts +24 -0
- package/dist/run-dir.js +201 -2
- package/dist/run-dir.js.map +1 -1
- package/dist/runner.d.ts +20 -0
- package/dist/runner.js +192 -16
- package/dist/runner.js.map +1 -1
- package/dist/safety.d.ts +54 -4
- package/dist/safety.js +186 -5
- package/dist/safety.js.map +1 -1
- package/dist/schema.d.ts +15 -7
- package/dist/schema.js +20 -0
- package/dist/schema.js.map +1 -1
- package/dist/server.d.ts +7 -1
- package/dist/server.js +451 -23
- package/dist/server.js.map +1 -1
- package/dist/session-log-stream.d.ts +18 -0
- package/dist/session-log-stream.js +566 -0
- package/dist/session-log-stream.js.map +1 -0
- package/dist/stdio-activity.d.ts +28 -0
- package/dist/stdio-activity.js +72 -0
- package/dist/stdio-activity.js.map +1 -0
- package/package.json +47 -5
- package/conf/adapters/gemini.json +0 -44
- package/conf/adapters/hapi/gemini.json +0 -47
- package/dist/parsers/gemini-json.d.ts +0 -2
- package/dist/parsers/gemini-json.js +0 -72
- package/dist/parsers/gemini-json.js.map +0 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InvalidParamsError } from "../errors.js";
|
|
2
|
-
|
|
2
|
+
import { isAlias, isMap, isScalar, isSeq, parseAllDocuments, } from "yaml";
|
|
3
|
+
const PRIVATE_KEY_BLOCK_PATTERN = /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY(?: BLOCK)?-----/;
|
|
3
4
|
const WELL_KNOWN_TOKEN_PATTERNS = [
|
|
4
5
|
{ provenance: "aws_access_key_id", pattern: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/ },
|
|
5
6
|
{ provenance: "github_token", pattern: /\bgh[opusr]_[A-Za-z0-9_]{30,}\b/ },
|
|
@@ -10,7 +11,668 @@ const WELL_KNOWN_TOKEN_PATTERNS = [
|
|
|
10
11
|
{ provenance: "npm_token", pattern: /\bnpm_[A-Za-z0-9]{30,}\b/ },
|
|
11
12
|
{ provenance: "slack_token", pattern: /\bxox[baprs]-[A-Za-z0-9-]{20,}\b/ },
|
|
12
13
|
];
|
|
13
|
-
const
|
|
14
|
+
const ASSIGNMENT_KEY_PATTERN = /(?<![A-Za-z0-9_$-])(?:(["'])([A-Za-z_][A-Za-z0-9_-]*)\1|((?:[A-Za-z_$][A-Za-z0-9_$]*(?:[ \t]*(?:\.|::)[ \t]*[A-Za-z_$][A-Za-z0-9_$]*)*[ \t]*)?\[[ \t]*(["'])([A-Za-z_][A-Za-z0-9_-]*)\4[ \t]*\])|([A-Za-z_$][A-Za-z0-9_$-]*(?:[ \t]*(?:\.|::)[ \t]*[A-Za-z_$][A-Za-z0-9_$-]*)*))(?=[ \t]*[:=])/gi;
|
|
15
|
+
const SENSITIVE_ASSIGNMENT_KEY_PATTERN = /(?:^|[_-])(?:password|passwd|api[_-]?key|secret|token|access[_-]?token|private[_-]?key|client[_-]?secret)$/i;
|
|
16
|
+
const REDACTION_MARKER = "[redacted-sensitive]";
|
|
17
|
+
const YAML_BLOCK_HEADER_PATTERN_SOURCE = String.raw `(?:(?:&[A-Za-z_][A-Za-z0-9_.-]*|!<[^>\r\n]+>|!!?[A-Za-z_][A-Za-z0-9_.:/-]*)[ \t]+)*[|>](?:[+-][1-9]?|[1-9][+-]?)?`;
|
|
18
|
+
const MAX_YAML_DOCUMENTS = 32;
|
|
19
|
+
const MAX_YAML_NODES = 20_000;
|
|
20
|
+
const MAX_YAML_DEPTH = 128;
|
|
21
|
+
const MAX_YAML_SOURCE_CHARS = 5_000_000;
|
|
22
|
+
const MAX_JS_KEY_LOOKBACK = 32_768;
|
|
23
|
+
const MAX_TS_TYPE_CHARS = 4_096;
|
|
24
|
+
const MAX_TS_TYPE_DEPTH = 32;
|
|
25
|
+
/**
|
|
26
|
+
* Fail-closed provenance for a body whose deterministic scan work exceeded
|
|
27
|
+
* its budget. The budget is a backstop, not the performance mechanism: the
|
|
28
|
+
* type-body header scan is a deterministic delimiter-aware linear scan, and
|
|
29
|
+
* the other index-backed detectors are linear and memoized per text, so
|
|
30
|
+
* realistic and generated corpora never trip it. Known residuals: the
|
|
31
|
+
* dense_fields shape stays super-linear but bounded (tracked follow-up), and
|
|
32
|
+
* the value-span/redaction regexes still have catastrophic-backtracking
|
|
33
|
+
* shapes (member-call-chain, backslash runs; see the scan-wedge issue doc)
|
|
34
|
+
* that can wedge inside a single regex match before the budget trips. Work
|
|
35
|
+
* is charged at real interrupt points (per header candidate, mid index
|
|
36
|
+
* build), so exhaustion unwinds and fails closed. Revisit the calibration
|
|
37
|
+
* if a legitimate body ever reports this reason.
|
|
38
|
+
*/
|
|
39
|
+
export const SANITIZE_BUDGET_EXHAUSTED_PROVENANCE = "sanitize_budget_exhausted";
|
|
40
|
+
// Calibrated at >= 20x the linearized realistic-shape cost, with headroom
|
|
41
|
+
// for the dense_fields adversarial shape (measured ~4_000 work units per
|
|
42
|
+
// char at 256 KiB; see tests/context-builder/secrets-perf.test.ts). Larger
|
|
43
|
+
// or denser pathological bodies exhaust it by design and fail closed.
|
|
44
|
+
const SECRET_SCAN_WORK_PER_CHAR = 6_000;
|
|
45
|
+
const SECRET_SCAN_WORK_FLOOR = 4_000_000;
|
|
46
|
+
class SecretScanBudgetExhausted extends Error {
|
|
47
|
+
}
|
|
48
|
+
let activeSecretScanSession;
|
|
49
|
+
let secretScanWorkTotal = 0;
|
|
50
|
+
let secretScanBudgetOverride;
|
|
51
|
+
function chargeWork(units) {
|
|
52
|
+
secretScanWorkTotal += units;
|
|
53
|
+
const session = activeSecretScanSession;
|
|
54
|
+
if (session != null) {
|
|
55
|
+
session.work += units;
|
|
56
|
+
if (session.work > session.limit) {
|
|
57
|
+
throw new SecretScanBudgetExhausted();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Runs one public detector entry point inside a per-body session. Nested
|
|
63
|
+
* entry points (sanitize -> scan/redact/residual) share the outer budget and
|
|
64
|
+
* memo; exhaustion unwinds to the outermost entry, which fails closed.
|
|
65
|
+
*/
|
|
66
|
+
function withSecretScanSession(text, work, onExhausted) {
|
|
67
|
+
if (activeSecretScanSession != null) {
|
|
68
|
+
return work();
|
|
69
|
+
}
|
|
70
|
+
activeSecretScanSession = {
|
|
71
|
+
limit: secretScanBudgetOverride ??
|
|
72
|
+
SECRET_SCAN_WORK_FLOOR + SECRET_SCAN_WORK_PER_CHAR * text.length,
|
|
73
|
+
work: 0,
|
|
74
|
+
texts: new Map(),
|
|
75
|
+
};
|
|
76
|
+
try {
|
|
77
|
+
return work();
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error instanceof SecretScanBudgetExhausted) {
|
|
81
|
+
return onExhausted();
|
|
82
|
+
}
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
finally {
|
|
86
|
+
activeSecretScanSession = undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function analysisFor(text) {
|
|
90
|
+
const session = activeSecretScanSession;
|
|
91
|
+
if (session == null) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
let analysis = session.texts.get(text);
|
|
95
|
+
if (analysis == null) {
|
|
96
|
+
analysis = {};
|
|
97
|
+
session.texts.set(text, analysis);
|
|
98
|
+
}
|
|
99
|
+
return analysis;
|
|
100
|
+
}
|
|
101
|
+
/** Test-only: deterministic work units consumed by `work`. */
|
|
102
|
+
export function measureSecretScanWorkForTests(work) {
|
|
103
|
+
const before = secretScanWorkTotal;
|
|
104
|
+
const result = work();
|
|
105
|
+
return { result, work: secretScanWorkTotal - before };
|
|
106
|
+
}
|
|
107
|
+
/** Test-only: run `work` with a fixed per-body work budget. */
|
|
108
|
+
export function withSecretScanBudgetForTests(limit, work) {
|
|
109
|
+
const previous = secretScanBudgetOverride;
|
|
110
|
+
secretScanBudgetOverride = limit;
|
|
111
|
+
try {
|
|
112
|
+
return work();
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
secretScanBudgetOverride = previous;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Test-only: (start, brace) offset of every type-body header match. */
|
|
119
|
+
export function typeBodyHeaderMatchesForTests(text) {
|
|
120
|
+
const headers = collectTypeBodyHeaders(text);
|
|
121
|
+
const pairs = [];
|
|
122
|
+
for (let slot = 0; slot < headers.starts.length; slot++) {
|
|
123
|
+
const start = headers.starts[slot];
|
|
124
|
+
const brace = headers.braces[slot];
|
|
125
|
+
if (start != null && brace != null) {
|
|
126
|
+
pairs.push({ start, brace });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return pairs;
|
|
130
|
+
}
|
|
131
|
+
function budgetExhaustedFinding() {
|
|
132
|
+
return contentFinding(SANITIZE_BUDGET_EXHAUSTED_PROVENANCE, 1);
|
|
133
|
+
}
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Linear per-text indexes.
|
|
136
|
+
//
|
|
137
|
+
// The detectors below used to rescan a fresh 32 KiB lookback slice per '='
|
|
138
|
+
// (regex matchAll over the window plus a brace scan, per query). Each index is
|
|
139
|
+
// built once per distinct body-level string per scan session, memoized in the
|
|
140
|
+
// SecretTextAnalysis slots, and queried with binary searches / O(1) lookups.
|
|
141
|
+
// Query semantics replicate the legacy slice behavior exactly, including the
|
|
142
|
+
// slice-relative `^`/`\b` window edge (a header or typed field starting
|
|
143
|
+
// exactly at the window start counts) — see the per-query comments.
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
const TYPED_FIELD_PATTERN = /(?:^|[;{}\r\n])[ \t]*(?:(?:public|private|protected|static|readonly|declare|abstract|override)[ \t]+)*([A-Za-z_$][A-Za-z0-9_$-]*)(?:[?!])?[ \t]*:[ \t]*/g;
|
|
146
|
+
const TYPED_FIELD_MODIFIERS = [
|
|
147
|
+
"public",
|
|
148
|
+
"private",
|
|
149
|
+
"protected",
|
|
150
|
+
"static",
|
|
151
|
+
"readonly",
|
|
152
|
+
"declare",
|
|
153
|
+
"abstract",
|
|
154
|
+
"override",
|
|
155
|
+
];
|
|
156
|
+
const MAX_TS_TYPE_ONLY_FIELD_CHARS = 4_096;
|
|
157
|
+
// Same composition as the per-call `new RegExp` the malformed-YAML span walk
|
|
158
|
+
// used to compile for every candidate value start.
|
|
159
|
+
const MALFORMED_YAML_CONTINUES_PATTERN = new RegExp(`^(?:&[A-Za-z0-9_.-]+[ \\t]+|!!?[^ \\t\\r\\n]+[ \\t]+|!<[^>\\r\\n]+>[ \\t]+)*${YAML_BLOCK_HEADER_PATTERN_SOURCE}(?:[ \\t]+#.*)?$`);
|
|
160
|
+
/**
|
|
161
|
+
* (start, brace) offset of every `class`/`interface`/`type` header, from a
|
|
162
|
+
* delimiter-aware linear scan equivalent to matchAll of
|
|
163
|
+
* /\b(?:class|interface|type)\s+[A-Za-z_$][A-Za-z0-9_$]*[^{};]*\{/g (the
|
|
164
|
+
* pattern's greedy `[^{};]*` backtracks one char at a time per candidate, so
|
|
165
|
+
* a header succeeds exactly when the first `{`/`}`/`;` after the ident is
|
|
166
|
+
* `{`; resolving that stopper through an amortized forward scan removed the
|
|
167
|
+
* quadratic blowup on dense failed-header bodies). Deliberately not
|
|
168
|
+
* quote-aware: matches inside
|
|
169
|
+
* strings and comments are golden-pinned semantics. `charge` gates the
|
|
170
|
+
* budget charges: the whole-text index build charges here, while the bounded
|
|
171
|
+
* lookback-slice fallback does not (its caller already charges the slice
|
|
172
|
+
* length, and the slice volume per body is a pre-existing property).
|
|
173
|
+
*/
|
|
174
|
+
function collectTypeBodyHeaders(text, charge = true) {
|
|
175
|
+
const length = text.length;
|
|
176
|
+
const starts = [];
|
|
177
|
+
const braces = [];
|
|
178
|
+
if (charge) {
|
|
179
|
+
chargeWork(length);
|
|
180
|
+
}
|
|
181
|
+
// Stopper lookup (first `{`/`}`/`;` at or after a cursor) via one amortized
|
|
182
|
+
// forward scan: region [regionStart, regionStopper) provably holds no
|
|
183
|
+
// stopper, so every candidate cursor inside it reuses regionStopper — a
|
|
184
|
+
// dense run of failed candidates pays one scan, not one per candidate.
|
|
185
|
+
// The absent-stopper sentinel is regionStopper === length, so the
|
|
186
|
+
// text[stopper] === "{" success test fails naturally at EOF. No index
|
|
187
|
+
// array is allocated: the lookback-slice fallback below runs thousands of
|
|
188
|
+
// times per body on the dense shapes, and a per-call Int32Array plus full
|
|
189
|
+
// backward pass dominated the wall clock there.
|
|
190
|
+
let regionStart = 0;
|
|
191
|
+
let regionStopper = -1;
|
|
192
|
+
const stopperAfter = (from) => {
|
|
193
|
+
if (regionStart <= from && from <= regionStopper) {
|
|
194
|
+
return regionStopper;
|
|
195
|
+
}
|
|
196
|
+
let index = from;
|
|
197
|
+
while (index < length) {
|
|
198
|
+
const code = text.charCodeAt(index);
|
|
199
|
+
if (code === 0x7b || code === 0x7d || code === 0x3b) {
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
index++;
|
|
203
|
+
}
|
|
204
|
+
regionStart = from;
|
|
205
|
+
regionStopper = index;
|
|
206
|
+
return index;
|
|
207
|
+
};
|
|
208
|
+
// Fresh locator per invocation: literal alternation plus lookahead, no
|
|
209
|
+
// backtracking structure. The regex \b stays the boundary authority (word
|
|
210
|
+
// chars are [A-Za-z0-9_], so `$class` matches while `xclass` and `Class`
|
|
211
|
+
// do not); lastIndex is managed by hand so a successful header can
|
|
212
|
+
// swallow its span the way matchAll's lastIndex jump did.
|
|
213
|
+
const locator = /\b(?:class|interface|type)(?=\s)/g;
|
|
214
|
+
let keyword;
|
|
215
|
+
while ((keyword = locator.exec(text)) != null) {
|
|
216
|
+
const start = keyword.index;
|
|
217
|
+
let cursor = start + keyword[0].length;
|
|
218
|
+
while (cursor < length && isJsWhitespaceChar(text[cursor] ?? "")) {
|
|
219
|
+
cursor++;
|
|
220
|
+
}
|
|
221
|
+
if (cursor >= length || !isIdentStartCode(text.charCodeAt(cursor))) {
|
|
222
|
+
if (charge) {
|
|
223
|
+
chargeWork(8);
|
|
224
|
+
}
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const identStart = cursor;
|
|
228
|
+
while (cursor < length && isIdentCharCode(text.charCodeAt(cursor))) {
|
|
229
|
+
cursor++;
|
|
230
|
+
}
|
|
231
|
+
if (charge) {
|
|
232
|
+
chargeWork(8 + (cursor - identStart));
|
|
233
|
+
}
|
|
234
|
+
const stopper = stopperAfter(cursor);
|
|
235
|
+
// Failed candidates never jump the locator: it is already past the
|
|
236
|
+
// keyword, and the very next keyword may still succeed
|
|
237
|
+
// (`class A; type B {`). Jumping to the failure-stopper + 1 would also
|
|
238
|
+
// be safe; this scan commits to no-jump.
|
|
239
|
+
if (text.charCodeAt(stopper) === 0x7b) {
|
|
240
|
+
starts.push(start);
|
|
241
|
+
braces.push(stopper);
|
|
242
|
+
locator.lastIndex = stopper + 1;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return { starts, braces };
|
|
246
|
+
}
|
|
247
|
+
function typeBodyIndexFor(text) {
|
|
248
|
+
const analysis = analysisFor(text);
|
|
249
|
+
const cached = analysis?.typeBody;
|
|
250
|
+
if (cached != null) {
|
|
251
|
+
return cached;
|
|
252
|
+
}
|
|
253
|
+
// collectTypeBodyHeaders charges the amortized stopper scan and, per
|
|
254
|
+
// candidate, 8 + identLength — real work with honest budget interrupt
|
|
255
|
+
// points between candidates (plain JS checks, not event-loop yields). This
|
|
256
|
+
// charge covers the stack pass below; mid-enumeration exhaustion unwinds
|
|
257
|
+
// and fails closed instead of leaving a partial index in the session memo.
|
|
258
|
+
const headers = collectTypeBodyHeaders(text);
|
|
259
|
+
chargeWork(text.length);
|
|
260
|
+
const headerStarts = headers.starts;
|
|
261
|
+
const headerBraces = headers.braces;
|
|
262
|
+
const pastEnd = text.length + 1;
|
|
263
|
+
// Per-position close/exceed facts for every `{`, from one stack pass. The
|
|
264
|
+
// stack bases are strictly increasing (unique per open frame), so the frame
|
|
265
|
+
// that newly exceeds MAX_TS_TYPE_DEPTH when depth becomes d is exactly the
|
|
266
|
+
// one with base d - 33, found by binary search.
|
|
267
|
+
const closeAt = new Int32Array(text.length);
|
|
268
|
+
const exceedAt = new Int32Array(text.length);
|
|
269
|
+
closeAt.fill(pastEnd);
|
|
270
|
+
exceedAt.fill(pastEnd);
|
|
271
|
+
const stackPos = [];
|
|
272
|
+
const stackBase = [];
|
|
273
|
+
const stackExceed = [];
|
|
274
|
+
const stackHeaderSlot = [];
|
|
275
|
+
let depth = 0;
|
|
276
|
+
const headerSlots = new Map();
|
|
277
|
+
for (let slot = 0; slot < headerBraces.length; slot++) {
|
|
278
|
+
headerSlots.set(headerBraces[slot] ?? -1, slot);
|
|
279
|
+
}
|
|
280
|
+
for (let index = 0; index < text.length; index++) {
|
|
281
|
+
const code = text.charCodeAt(index);
|
|
282
|
+
if (code === 0x7b) {
|
|
283
|
+
stackPos.push(index);
|
|
284
|
+
stackBase.push(depth);
|
|
285
|
+
stackExceed.push(pastEnd);
|
|
286
|
+
stackHeaderSlot.push(headerSlots.get(index) ?? -1);
|
|
287
|
+
depth++;
|
|
288
|
+
const exceedBase = depth - (MAX_TS_TYPE_DEPTH + 1);
|
|
289
|
+
const frame = binarySearchExact(stackBase, exceedBase);
|
|
290
|
+
if (frame >= 0 && (stackExceed[frame] ?? pastEnd) === pastEnd) {
|
|
291
|
+
stackExceed[frame] = index + 1;
|
|
292
|
+
}
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
if (code === 0x7d) {
|
|
296
|
+
const pos = stackPos.pop();
|
|
297
|
+
const base = stackBase.pop();
|
|
298
|
+
const exceed = stackExceed.pop();
|
|
299
|
+
const headerSlot = stackHeaderSlot.pop();
|
|
300
|
+
depth--;
|
|
301
|
+
if (pos == null || base == null || exceed == null || headerSlot == null) {
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
closeAt[pos] = index + 1;
|
|
305
|
+
exceedAt[pos] = exceed;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// Frames never popped: closeAt stays pastEnd; keep any recorded exceed.
|
|
310
|
+
for (let frame = 0; frame < stackPos.length; frame++) {
|
|
311
|
+
const pos = stackPos[frame];
|
|
312
|
+
const exceed = stackExceed[frame];
|
|
313
|
+
if (pos == null || exceed == null) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
exceedAt[pos] = exceed;
|
|
317
|
+
}
|
|
318
|
+
const index = {
|
|
319
|
+
starts: Int32Array.from(headerStarts),
|
|
320
|
+
braces: Int32Array.from(headerBraces),
|
|
321
|
+
closeAt,
|
|
322
|
+
exceedAt,
|
|
323
|
+
pastEnd,
|
|
324
|
+
};
|
|
325
|
+
if (analysis != null) {
|
|
326
|
+
analysis.typeBody = index;
|
|
327
|
+
}
|
|
328
|
+
return index;
|
|
329
|
+
}
|
|
330
|
+
function typedFieldCandidatesFor(text) {
|
|
331
|
+
const analysis = analysisFor(text);
|
|
332
|
+
const cached = analysis?.typedFields;
|
|
333
|
+
if (cached != null) {
|
|
334
|
+
return cached;
|
|
335
|
+
}
|
|
336
|
+
chargeWork(2 * text.length);
|
|
337
|
+
const matchStarts = [];
|
|
338
|
+
const keyStarts = [];
|
|
339
|
+
const keyEnds = [];
|
|
340
|
+
const typeStarts = [];
|
|
341
|
+
for (const match of text.matchAll(TYPED_FIELD_PATTERN)) {
|
|
342
|
+
const rawKey = match[1] ?? "";
|
|
343
|
+
const matchStart = match.index ?? 0;
|
|
344
|
+
const keyStart = matchStart + match[0].lastIndexOf(rawKey);
|
|
345
|
+
matchStarts.push(matchStart);
|
|
346
|
+
keyStarts.push(keyStart);
|
|
347
|
+
keyEnds.push(keyStart + rawKey.length);
|
|
348
|
+
typeStarts.push(matchStart + match[0].length);
|
|
349
|
+
}
|
|
350
|
+
const candidates = {
|
|
351
|
+
matchStarts: Int32Array.from(matchStarts),
|
|
352
|
+
keyStarts: Int32Array.from(keyStarts),
|
|
353
|
+
keyEnds: Int32Array.from(keyEnds),
|
|
354
|
+
typeStarts: Int32Array.from(typeStarts),
|
|
355
|
+
count: matchStarts.length,
|
|
356
|
+
};
|
|
357
|
+
if (analysis != null) {
|
|
358
|
+
analysis.typedFields = candidates;
|
|
359
|
+
}
|
|
360
|
+
return candidates;
|
|
361
|
+
}
|
|
362
|
+
function newlineOffsetsFor(text) {
|
|
363
|
+
const analysis = analysisFor(text);
|
|
364
|
+
const cached = analysis?.newlineOffsets;
|
|
365
|
+
if (cached != null) {
|
|
366
|
+
return cached;
|
|
367
|
+
}
|
|
368
|
+
chargeWork(text.length);
|
|
369
|
+
const offsets = [];
|
|
370
|
+
for (let index = 0; index < text.length; index++) {
|
|
371
|
+
if (text.charCodeAt(index) === 0x0a) {
|
|
372
|
+
offsets.push(index);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
const result = Int32Array.from(offsets);
|
|
376
|
+
if (analysis != null) {
|
|
377
|
+
analysis.newlineOffsets = result;
|
|
378
|
+
}
|
|
379
|
+
return result;
|
|
380
|
+
}
|
|
381
|
+
function nextSemicolonFor(text) {
|
|
382
|
+
const analysis = analysisFor(text);
|
|
383
|
+
const cached = analysis?.nextSemicolon;
|
|
384
|
+
if (cached != null) {
|
|
385
|
+
return cached;
|
|
386
|
+
}
|
|
387
|
+
chargeWork(text.length);
|
|
388
|
+
const result = new Int32Array(text.length + 1);
|
|
389
|
+
result.fill(-1);
|
|
390
|
+
for (let index = text.length - 1; index >= 0; index--) {
|
|
391
|
+
result[index] =
|
|
392
|
+
text.charCodeAt(index) === 0x3b ? index : result[index + 1] ?? -1;
|
|
393
|
+
}
|
|
394
|
+
if (analysis != null) {
|
|
395
|
+
analysis.nextSemicolon = result;
|
|
396
|
+
}
|
|
397
|
+
return result;
|
|
398
|
+
}
|
|
399
|
+
function yamlBlockHeaderIndexFor(text) {
|
|
400
|
+
const analysis = analysisFor(text);
|
|
401
|
+
const cached = analysis?.yamlBlockHeaders;
|
|
402
|
+
if (cached != null) {
|
|
403
|
+
return cached;
|
|
404
|
+
}
|
|
405
|
+
chargeWork(text.length);
|
|
406
|
+
const starts = [0];
|
|
407
|
+
for (let index = 0; index < text.length; index++) {
|
|
408
|
+
if (text.charCodeAt(index) === 0x0a) {
|
|
409
|
+
starts.push(index + 1);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const result = { lineStarts: Int32Array.from(starts) };
|
|
413
|
+
if (analysis != null) {
|
|
414
|
+
analysis.yamlBlockHeaders = result;
|
|
415
|
+
}
|
|
416
|
+
return result;
|
|
417
|
+
}
|
|
418
|
+
/** Rightmost index with values[index] < target (-1 when none). */
|
|
419
|
+
function binarySearchLastLessThan(values, target) {
|
|
420
|
+
let low = 0;
|
|
421
|
+
let high = values.length - 1;
|
|
422
|
+
let result = -1;
|
|
423
|
+
while (low <= high) {
|
|
424
|
+
const mid = (low + high) >>> 1;
|
|
425
|
+
if ((values[mid] ?? 0) < target) {
|
|
426
|
+
result = mid;
|
|
427
|
+
low = mid + 1;
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
high = mid - 1;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return result;
|
|
434
|
+
}
|
|
435
|
+
/** Exact-match index in a sorted-ascending array (-1 when absent). */
|
|
436
|
+
function binarySearchExact(values, target) {
|
|
437
|
+
let low = 0;
|
|
438
|
+
let high = values.length - 1;
|
|
439
|
+
while (low <= high) {
|
|
440
|
+
const mid = (low + high) >>> 1;
|
|
441
|
+
const value = values[mid] ?? 0;
|
|
442
|
+
if (value === target) {
|
|
443
|
+
return mid;
|
|
444
|
+
}
|
|
445
|
+
if (value < target) {
|
|
446
|
+
low = mid + 1;
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
high = mid - 1;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return -1;
|
|
453
|
+
}
|
|
454
|
+
function isJsWordCode(code) {
|
|
455
|
+
return ((code >= 0x30 && code <= 0x39) ||
|
|
456
|
+
(code >= 0x41 && code <= 0x5a) ||
|
|
457
|
+
(code >= 0x61 && code <= 0x7a) ||
|
|
458
|
+
code === 0x5f ||
|
|
459
|
+
code === 0x24);
|
|
460
|
+
}
|
|
461
|
+
function isIdentStartCode(code) {
|
|
462
|
+
return ((code >= 0x41 && code <= 0x5a) ||
|
|
463
|
+
(code >= 0x61 && code <= 0x7a) ||
|
|
464
|
+
code === 0x5f ||
|
|
465
|
+
code === 0x24);
|
|
466
|
+
}
|
|
467
|
+
function isIdentCharCode(code) {
|
|
468
|
+
return isJsWordCode(code);
|
|
469
|
+
}
|
|
470
|
+
function isJsWhitespaceChar(char) {
|
|
471
|
+
return /\s/.test(char);
|
|
472
|
+
}
|
|
473
|
+
function isLineTerminatorCode(code) {
|
|
474
|
+
return (code === 0x0a || code === 0x0d || code === 0x2028 || code === 0x2029);
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* Brace offset of a `class`/`interface`/`type` header starting exactly at
|
|
478
|
+
* `start` (the slice-relative `\b` window edge: a header beginning exactly at
|
|
479
|
+
* the lookback window start counts even when the preceding character would
|
|
480
|
+
* block the word boundary in the full text). `-1` when no header completes
|
|
481
|
+
* before `limit`.
|
|
482
|
+
*/
|
|
483
|
+
function typeBodyHeaderBraceAt(text, start, limit) {
|
|
484
|
+
const keywordLength = text.startsWith("interface", start)
|
|
485
|
+
? "interface".length
|
|
486
|
+
: text.startsWith("class", start)
|
|
487
|
+
? "class".length
|
|
488
|
+
: text.startsWith("type", start)
|
|
489
|
+
? "type".length
|
|
490
|
+
: -1;
|
|
491
|
+
if (keywordLength < 0) {
|
|
492
|
+
return -1;
|
|
493
|
+
}
|
|
494
|
+
let cursor = start + keywordLength;
|
|
495
|
+
if (cursor >= limit || !isJsWhitespaceChar(text[cursor] ?? "")) {
|
|
496
|
+
return -1;
|
|
497
|
+
}
|
|
498
|
+
while (cursor < limit && isJsWhitespaceChar(text[cursor] ?? "")) {
|
|
499
|
+
cursor++;
|
|
500
|
+
}
|
|
501
|
+
if (cursor >= limit || !isIdentStartCode(text.charCodeAt(cursor))) {
|
|
502
|
+
return -1;
|
|
503
|
+
}
|
|
504
|
+
cursor++;
|
|
505
|
+
while (cursor < limit && isIdentCharCode(text.charCodeAt(cursor))) {
|
|
506
|
+
cursor++;
|
|
507
|
+
}
|
|
508
|
+
// `[^{};]*` runs to the first stopper; only `{` can complete the header.
|
|
509
|
+
while (cursor < limit) {
|
|
510
|
+
const code = text.charCodeAt(cursor);
|
|
511
|
+
if (code === 0x7b) {
|
|
512
|
+
return cursor;
|
|
513
|
+
}
|
|
514
|
+
if (code === 0x7d || code === 0x3b) {
|
|
515
|
+
return -1;
|
|
516
|
+
}
|
|
517
|
+
cursor++;
|
|
518
|
+
}
|
|
519
|
+
return -1;
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Index-backed equivalent of the legacy isInsideBoundedTypeBody(prefix,
|
|
523
|
+
* offset) where `prefix` was the caller's window slice starting at
|
|
524
|
+
* `windowStart` (0 for full-text callers). Falls back to the exact legacy
|
|
525
|
+
* slice scan when a header match straddles the window start — the one
|
|
526
|
+
* configuration where slice sub-matches exist that a whole-text matchAll
|
|
527
|
+
* never records.
|
|
528
|
+
*/
|
|
529
|
+
function isInsideBoundedTypeBodyAt(text, windowStart, offset) {
|
|
530
|
+
const lo = Math.max(0, windowStart, offset - MAX_JS_KEY_LOOKBACK);
|
|
531
|
+
if (lo >= offset) {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
const index = typeBodyIndexFor(text);
|
|
535
|
+
chargeWork(8);
|
|
536
|
+
const straddling = binarySearchLastLessThan(index.starts, lo);
|
|
537
|
+
if (straddling >= 0 &&
|
|
538
|
+
(index.braces[straddling] ?? -1) >= lo) {
|
|
539
|
+
chargeWork(offset - lo);
|
|
540
|
+
return isInsideBoundedTypeBodyBefore(text.slice(lo, offset));
|
|
541
|
+
}
|
|
542
|
+
let brace = -1;
|
|
543
|
+
const lastVisible = binarySearchLastLessThan(index.braces, offset);
|
|
544
|
+
if (lastVisible >= 0 &&
|
|
545
|
+
(index.starts[lastVisible] ?? -1) >= lo) {
|
|
546
|
+
brace = index.braces[lastVisible] ?? -1;
|
|
547
|
+
}
|
|
548
|
+
if (lo > 0 && isJsWordCode(text.charCodeAt(lo - 1))) {
|
|
549
|
+
const synthetic = typeBodyHeaderBraceAt(text, lo, offset);
|
|
550
|
+
if (synthetic > brace) {
|
|
551
|
+
brace = synthetic;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (brace < 0) {
|
|
555
|
+
return false;
|
|
556
|
+
}
|
|
557
|
+
return ((index.closeAt[brace] ?? index.pastEnd) > offset &&
|
|
558
|
+
(index.exceedAt[brace] ?? index.pastEnd) > offset);
|
|
559
|
+
}
|
|
560
|
+
/** Exact legacy scan over an already-sliced lookback `before`. */
|
|
561
|
+
function isInsideBoundedTypeBodyBefore(before) {
|
|
562
|
+
// Shared header scan on the slice: offset 0 counts as a boundary, exactly
|
|
563
|
+
// the slice-relative \b semantics the slice's own matchAll had. Only the
|
|
564
|
+
// last header's brace feeds the depth walk (legacy last-match semantics).
|
|
565
|
+
// Uncharged: the caller already charges the slice length, and this fallback
|
|
566
|
+
// runs per straddling lookback query (thousands of 32 KiB slices on the
|
|
567
|
+
// dense shapes), so double-charging here would exhaust the budget.
|
|
568
|
+
const classBrace = collectTypeBodyHeaders(before, false).braces.at(-1) ?? -1;
|
|
569
|
+
if (classBrace < 0) {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
let depth = 0;
|
|
573
|
+
for (let index = classBrace; index < before.length; index++) {
|
|
574
|
+
if (before[index] === "{") {
|
|
575
|
+
depth++;
|
|
576
|
+
}
|
|
577
|
+
else if (before[index] === "}") {
|
|
578
|
+
depth--;
|
|
579
|
+
if (depth === 0) {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
if (depth > MAX_TS_TYPE_DEPTH) {
|
|
584
|
+
return false;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return depth > 0;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Parses `[ \t]*(modifier[ \t]+)*key[?!]?[ \t]*:[ \t]*` starting exactly at
|
|
591
|
+
* `start` — the typed-field pattern with the boundary alternative elided.
|
|
592
|
+
* This reproduces the regex's greedy modifier consumption with rightmost
|
|
593
|
+
* backtracking (e.g. `readonly : T` parses with key `readonly`), which is
|
|
594
|
+
* what a window slice's slice-relative `^` anchoring would see at position 0.
|
|
595
|
+
*/
|
|
596
|
+
function matchTypedFieldContinuationAt(text, start, limit) {
|
|
597
|
+
let cursor = start;
|
|
598
|
+
while (cursor < limit &&
|
|
599
|
+
(text.charCodeAt(cursor) === 0x20 || text.charCodeAt(cursor) === 0x09)) {
|
|
600
|
+
cursor++;
|
|
601
|
+
}
|
|
602
|
+
const modifierStarts = [];
|
|
603
|
+
for (;;) {
|
|
604
|
+
let matched = -1;
|
|
605
|
+
for (const modifier of TYPED_FIELD_MODIFIERS) {
|
|
606
|
+
if (text.startsWith(modifier, cursor)) {
|
|
607
|
+
matched = modifier.length;
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
if (matched < 0 ||
|
|
612
|
+
cursor + matched >= limit ||
|
|
613
|
+
(text.charCodeAt(cursor + matched) !== 0x20 &&
|
|
614
|
+
text.charCodeAt(cursor + matched) !== 0x09)) {
|
|
615
|
+
break;
|
|
616
|
+
}
|
|
617
|
+
modifierStarts.push(cursor);
|
|
618
|
+
cursor += matched;
|
|
619
|
+
while (cursor < limit &&
|
|
620
|
+
(text.charCodeAt(cursor) === 0x20 || text.charCodeAt(cursor) === 0x09)) {
|
|
621
|
+
cursor++;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
for (;;) {
|
|
625
|
+
const keyStart = cursor;
|
|
626
|
+
if (keyStart < limit && isIdentStartCode(text.charCodeAt(keyStart))) {
|
|
627
|
+
let keyEnd = keyStart + 1;
|
|
628
|
+
// The regex key class is [A-Za-z0-9_$-]*: hyphens are legal key
|
|
629
|
+
// continuations (e.g. `api-key: string;`) even though they are not
|
|
630
|
+
// word characters.
|
|
631
|
+
while (keyEnd < limit &&
|
|
632
|
+
(isIdentCharCode(text.charCodeAt(keyEnd)) ||
|
|
633
|
+
text.charCodeAt(keyEnd) === 0x2d)) {
|
|
634
|
+
keyEnd++;
|
|
635
|
+
}
|
|
636
|
+
let afterKey = keyEnd;
|
|
637
|
+
const maybeOptional = text.charCodeAt(afterKey);
|
|
638
|
+
if (maybeOptional === 0x3f || maybeOptional === 0x21) {
|
|
639
|
+
afterKey++;
|
|
640
|
+
}
|
|
641
|
+
while (afterKey < limit &&
|
|
642
|
+
(text.charCodeAt(afterKey) === 0x20 ||
|
|
643
|
+
text.charCodeAt(afterKey) === 0x09)) {
|
|
644
|
+
afterKey++;
|
|
645
|
+
}
|
|
646
|
+
if (afterKey < limit && text.charCodeAt(afterKey) === 0x3a) {
|
|
647
|
+
let typeStart = afterKey + 1;
|
|
648
|
+
while (typeStart < limit &&
|
|
649
|
+
(text.charCodeAt(typeStart) === 0x20 ||
|
|
650
|
+
text.charCodeAt(typeStart) === 0x09)) {
|
|
651
|
+
typeStart++;
|
|
652
|
+
}
|
|
653
|
+
return { keyStart, keyEnd, typeStart };
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
if (modifierStarts.length === 0) {
|
|
657
|
+
return undefined;
|
|
658
|
+
}
|
|
659
|
+
cursor = modifierStarts.pop() ?? start;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
/** Memoized whole-text comment mask; only called on body-level strings. */
|
|
663
|
+
function commentMaskedFor(text) {
|
|
664
|
+
const analysis = analysisFor(text);
|
|
665
|
+
const cached = analysis?.commentMasked;
|
|
666
|
+
if (cached != null) {
|
|
667
|
+
return cached;
|
|
668
|
+
}
|
|
669
|
+
chargeWork(2 * text.length);
|
|
670
|
+
const masked = maskJsCommentTrivia(text);
|
|
671
|
+
if (analysis != null) {
|
|
672
|
+
analysis.commentMasked = masked;
|
|
673
|
+
}
|
|
674
|
+
return masked;
|
|
675
|
+
}
|
|
14
676
|
const PLACEHOLDER_VALUES = new Set([
|
|
15
677
|
"changeme",
|
|
16
678
|
"example",
|
|
@@ -66,6 +728,9 @@ export function scanSecretPath(rootRelativePath) {
|
|
|
66
728
|
return dedupeFindings(findings);
|
|
67
729
|
}
|
|
68
730
|
export function scanHighConfidenceSecretContent(text) {
|
|
731
|
+
return withSecretScanSession(text, () => scanHighConfidenceSecretContentInSession(text), () => [budgetExhaustedFinding()]);
|
|
732
|
+
}
|
|
733
|
+
function scanHighConfidenceSecretContentInSession(text) {
|
|
69
734
|
const findings = [];
|
|
70
735
|
const privateKeyLine = findPatternLine(text, PRIVATE_KEY_BLOCK_PATTERN);
|
|
71
736
|
if (privateKeyLine != null) {
|
|
@@ -77,20 +742,9 @@ export function scanHighConfidenceSecretContent(text) {
|
|
|
77
742
|
findings.push(contentFinding(provenance, line));
|
|
78
743
|
}
|
|
79
744
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (match == null) {
|
|
84
|
-
continue;
|
|
85
|
-
}
|
|
86
|
-
const value = match[3] ?? "";
|
|
87
|
-
const quote = match[2] ?? "";
|
|
88
|
-
if (quote.length === 0 && looksLikeCodeExpression(value)) {
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
if (isHighConfidenceAssignedSecret(value)) {
|
|
92
|
-
findings.push(contentFinding("assignment_secret", index + 1));
|
|
93
|
-
break;
|
|
745
|
+
for (const assignment of iterateAssignmentSecrets(text)) {
|
|
746
|
+
if (assignment.confidence === "high") {
|
|
747
|
+
findings.push(contentFinding("assignment_secret", assignment.line));
|
|
94
748
|
}
|
|
95
749
|
}
|
|
96
750
|
return dedupeFindings(findings);
|
|
@@ -98,25 +752,135 @@ export function scanHighConfidenceSecretContent(text) {
|
|
|
98
752
|
export function isBlockingSecretFinding(finding) {
|
|
99
753
|
return finding.provenance !== "assignment_secret";
|
|
100
754
|
}
|
|
755
|
+
/**
|
|
756
|
+
* Budget exhaustion here fails closed by removing the whole body: a partial
|
|
757
|
+
* redaction cannot be proven complete.
|
|
758
|
+
*/
|
|
101
759
|
export function redactHighConfidenceAssignmentSecrets(text) {
|
|
102
|
-
return text
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
760
|
+
return withSecretScanSession(text, () => redactHighConfidenceAssignmentSecretsInSession(text), () => "");
|
|
761
|
+
}
|
|
762
|
+
function redactHighConfidenceAssignmentSecretsInSession(text) {
|
|
763
|
+
const secretAssignments = [...iterateAssignmentSecrets(text)].filter((assignment) => assignment.confidence === "high").sort((left, right) => left.valueStart - right.valueStart);
|
|
764
|
+
let redacted = text;
|
|
765
|
+
for (let index = secretAssignments.length - 1; index >= 0; index--) {
|
|
766
|
+
const assignment = secretAssignments[index];
|
|
767
|
+
if (assignment == null) {
|
|
768
|
+
continue;
|
|
107
769
|
}
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
770
|
+
redacted = `${redacted.slice(0, assignment.valueStart)}${assignment.replacement ?? REDACTION_MARKER}${redacted.slice(assignment.valueEnd)}`;
|
|
771
|
+
}
|
|
772
|
+
return redacted;
|
|
111
773
|
}
|
|
112
|
-
|
|
113
|
-
|
|
774
|
+
/**
|
|
775
|
+
* Sanitizes one bounded text body. The residual callback is deliberately a
|
|
776
|
+
* separate lexical detector and runs for every opt-out sanitization, including
|
|
777
|
+
* content the primary assignment grammar did not classify.
|
|
778
|
+
*/
|
|
779
|
+
export function sanitizeHighConfidenceSecretContent(text, residualDetector = hasConservativeResidualSecret) {
|
|
780
|
+
return withSecretScanSession(text, () => sanitizeHighConfidenceSecretContentInSession(text, residualDetector), () => ({ status: "blocked", content: "", findings: [budgetExhaustedFinding()] }));
|
|
781
|
+
}
|
|
782
|
+
function sanitizeHighConfidenceSecretContentInSession(text, residualDetector) {
|
|
783
|
+
const findings = scanHighConfidenceSecretContent(text);
|
|
784
|
+
if (findings.some(isBlockingSecretFinding)) {
|
|
785
|
+
return { status: "blocked", content: "", findings };
|
|
786
|
+
}
|
|
114
787
|
if (findings.length === 0) {
|
|
788
|
+
if (residualDetector(text)) {
|
|
789
|
+
return {
|
|
790
|
+
status: "blocked",
|
|
791
|
+
content: "",
|
|
792
|
+
findings: [contentFinding("residual_secret", 1)],
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
return { status: "unchanged", content: text, findings };
|
|
796
|
+
}
|
|
797
|
+
const content = redactHighConfidenceAssignmentSecrets(text);
|
|
798
|
+
if (residualDetector(content)) {
|
|
799
|
+
return {
|
|
800
|
+
status: "blocked",
|
|
801
|
+
content: "",
|
|
802
|
+
findings: [contentFinding("residual_secret", 1)],
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
return { status: "redacted", content, findings };
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Independent fail-closed backstop for every opt-out content body. This does
|
|
809
|
+
* not call the assignment iterator: it recognizes sensitive key contexts and
|
|
810
|
+
* YAML block continuations with a separate, intentionally conservative lexer.
|
|
811
|
+
*/
|
|
812
|
+
export function hasConservativeResidualSecret(text) {
|
|
813
|
+
return withSecretScanSession(text, () => hasConservativeResidualSecretInSession(text), () => true);
|
|
814
|
+
}
|
|
815
|
+
function hasConservativeResidualSecretInSession(text) {
|
|
816
|
+
chargeWork(text.length * (1 + WELL_KNOWN_TOKEN_PATTERNS.length));
|
|
817
|
+
if (PRIVATE_KEY_BLOCK_PATTERN.test(text)) {
|
|
818
|
+
return true;
|
|
819
|
+
}
|
|
820
|
+
for (const { pattern } of WELL_KNOWN_TOKEN_PATTERNS) {
|
|
821
|
+
pattern.lastIndex = 0;
|
|
822
|
+
if (pattern.test(text)) {
|
|
823
|
+
return true;
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
const yaml = analyzeYamlStructuralSecrets(text);
|
|
827
|
+
if (yaml.unsafeSecretRange) {
|
|
828
|
+
return true;
|
|
829
|
+
}
|
|
830
|
+
if (yaml.parseFailed && hasMalformedYamlSensitiveSecret(text)) {
|
|
831
|
+
return true;
|
|
832
|
+
}
|
|
833
|
+
if (yaml.recognized) {
|
|
834
|
+
return yaml.matches.some((match) => match.confidence === "high");
|
|
835
|
+
}
|
|
836
|
+
const js = analyzeJsStructuralSecrets(text, false);
|
|
837
|
+
if (js.unsafeSensitiveContext ||
|
|
838
|
+
js.matches.some((match) => match.confidence === "high")) {
|
|
839
|
+
return true;
|
|
840
|
+
}
|
|
841
|
+
return [...iterateAssignmentSecrets(text)].some((match) => match.confidence === "high");
|
|
842
|
+
}
|
|
843
|
+
export function hasDisallowedSecretMaterial(text, allowSecrets) {
|
|
844
|
+
if (allowSecrets === true) {
|
|
845
|
+
return false;
|
|
846
|
+
}
|
|
847
|
+
return withSecretScanSession(text, () => hasHighConfidenceSecret(text) || hasConservativeResidualSecret(text), () => true);
|
|
848
|
+
}
|
|
849
|
+
export function assertNoHighConfidenceSecretsInTask(task) {
|
|
850
|
+
if (!hasDisallowedSecretMaterial(task, false)) {
|
|
115
851
|
return;
|
|
116
852
|
}
|
|
117
|
-
const
|
|
853
|
+
const findings = scanHighConfidenceSecretContent(task);
|
|
854
|
+
const provenances = findings.length === 0
|
|
855
|
+
? "residual_secret"
|
|
856
|
+
: findings.map((finding) => finding.provenance).join(", ");
|
|
118
857
|
throw new InvalidParamsError(`context_builder task contains high-confidence secret material (${provenances})`);
|
|
119
858
|
}
|
|
859
|
+
export function assertTaskSecretsGated(task, allowSecrets) {
|
|
860
|
+
if (hasDisallowedSecretMaterial(task, allowSecrets)) {
|
|
861
|
+
assertNoHighConfidenceSecretsInTask(task);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
export function resolveAllowSecrets(explicit) {
|
|
865
|
+
if (typeof explicit === "boolean") {
|
|
866
|
+
return explicit;
|
|
867
|
+
}
|
|
868
|
+
const raw = process.env.CLINKX_CONTEXT_ALLOW_SECRETS;
|
|
869
|
+
if (raw == null) {
|
|
870
|
+
return true;
|
|
871
|
+
}
|
|
872
|
+
const value = raw.trim().toLowerCase();
|
|
873
|
+
if (value === "") {
|
|
874
|
+
return true;
|
|
875
|
+
}
|
|
876
|
+
if (value === "false" || value === "0" || value === "no" || value === "off") {
|
|
877
|
+
return false;
|
|
878
|
+
}
|
|
879
|
+
if (value === "true" || value === "1" || value === "yes" || value === "on") {
|
|
880
|
+
return true;
|
|
881
|
+
}
|
|
882
|
+
throw new InvalidParamsError("CLINKX_CONTEXT_ALLOW_SECRETS must be one of true/false/1/0/yes/no/on/off (case-insensitive)");
|
|
883
|
+
}
|
|
120
884
|
export function hasHighConfidenceSecret(text) {
|
|
121
885
|
return scanHighConfidenceSecretContent(text).length > 0;
|
|
122
886
|
}
|
|
@@ -148,6 +912,7 @@ function isSecretDirectorySegment(segment) {
|
|
|
148
912
|
segment === "credentials");
|
|
149
913
|
}
|
|
150
914
|
function findPatternLine(text, pattern) {
|
|
915
|
+
chargeWork(2 * text.length);
|
|
151
916
|
const lines = text.split(/\r?\n/);
|
|
152
917
|
for (let index = 0; index < lines.length; index++) {
|
|
153
918
|
if (pattern.test(lines[index] ?? "")) {
|
|
@@ -157,9 +922,86 @@ function findPatternLine(text, pattern) {
|
|
|
157
922
|
return undefined;
|
|
158
923
|
}
|
|
159
924
|
function looksLikeCodeExpression(value) {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
925
|
+
const expression = unwrapReferenceExpression(value);
|
|
926
|
+
const plainReference = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(expression);
|
|
927
|
+
const explicitlyNamedReference = /(?:reference|ref|source|provider)[0-9]*$/i.test(expression);
|
|
928
|
+
const memberReference = /^[A-Za-z_$][A-Za-z0-9_$]*(?:(?:::|\.)[A-Za-z_$][A-Za-z0-9_$]*)+$/.test(expression);
|
|
929
|
+
return (looksLikeTsTypeExpression(expression) ||
|
|
930
|
+
/^[A-Za-z_$][A-Za-z0-9_$]*(?:(?:::|\.)[A-Za-z_$][A-Za-z0-9_$]*)*\([^"'`\r\n]*\)$/.test(expression) ||
|
|
931
|
+
(plainReference &&
|
|
932
|
+
!SENSITIVE_ASSIGNMENT_KEY_PATTERN.test(expression) &&
|
|
933
|
+
(explicitlyNamedReference ||
|
|
934
|
+
!isHighConfidenceAssignedSecret(expression))) ||
|
|
935
|
+
(memberReference && !looksLikeCredentialShapedDottedLiteral(expression)) ||
|
|
936
|
+
(/^[A-Za-z_$][A-Za-z0-9_$]*(?:(?:\?\.|\.)[A-Za-z_$][A-Za-z0-9_$]*(?:\([^"'`\r\n]*\))?|\[[A-Za-z_$][A-Za-z0-9_$]*\])+(?:\?\.[A-Za-z_$][A-Za-z0-9_$]*\([^"'`\r\n]*\))*$/.test(expression) && !looksLikeCredentialShapedDottedLiteral(expression)) ||
|
|
937
|
+
/^[A-Za-z_$][A-Za-z0-9_$]*(?:(?:\?\.|\.)[A-Za-z_$][A-Za-z0-9_$]*(?:\([^"'`\r\n]*\))?|\[[A-Za-z_$][A-Za-z0-9_$]*\])*(?:\?\.[A-Za-z_$][A-Za-z0-9_$]*\([^"'`\r\n]*\))*\s*(?:\?\?|\|\|)\s*(?:["']{2}|undefined|null);?$/.test(expression) ||
|
|
938
|
+
/^(?:\$\{[A-Za-z_][A-Za-z0-9_]*(?::-[^}\r\n]*)?\}|\$[A-Za-z_][A-Za-z0-9_]*)$/.test(expression));
|
|
939
|
+
}
|
|
940
|
+
function looksLikeTsTypeExpression(value, allowNamedType = false) {
|
|
941
|
+
const type = value.trim().replace(/;\s*$/, "").trim();
|
|
942
|
+
if (type.length === 0 ||
|
|
943
|
+
/["'`=]/.test(type) ||
|
|
944
|
+
classifyTsTypeAnnotation(type) !== "valid") {
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
return (/\b(?:any|bigint|boolean|never|null|number|object|string|symbol|undefined|unknown|void)\b/.test(type) ||
|
|
948
|
+
/\b(?:keyof|readonly|typeof)\b/.test(type) ||
|
|
949
|
+
/[<>{}\[\]|&?]/.test(type) ||
|
|
950
|
+
(allowNamedType &&
|
|
951
|
+
/^[A-Za-z_$][A-Za-z0-9_$]*(?:(?:\.|::)[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(type)));
|
|
952
|
+
}
|
|
953
|
+
function unwrapReferenceExpression(value) {
|
|
954
|
+
let expression = value.trim();
|
|
955
|
+
for (let count = 0; count < 16; count++) {
|
|
956
|
+
const before = expression;
|
|
957
|
+
expression = expression
|
|
958
|
+
.replace(/\s+(?:as|satisfies)\s+(?:const|[A-Za-z_$][A-Za-z0-9_$]*(?:\s*<[^<>\r\n]+>)?)$/, "")
|
|
959
|
+
.trim();
|
|
960
|
+
if (hasBalancedOuterParentheses(expression)) {
|
|
961
|
+
expression = expression.slice(1, -1).trim();
|
|
962
|
+
}
|
|
963
|
+
expression = expression
|
|
964
|
+
.replace(/^<[A-Za-z_$][A-Za-z0-9_$]*(?:<[^<>\r\n]+>)?>\s*/, "")
|
|
965
|
+
.trim();
|
|
966
|
+
if (expression === before) {
|
|
967
|
+
break;
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return expression;
|
|
971
|
+
}
|
|
972
|
+
function hasBalancedOuterParentheses(value) {
|
|
973
|
+
if (!value.startsWith("(") || !value.endsWith(")")) {
|
|
974
|
+
return false;
|
|
975
|
+
}
|
|
976
|
+
let depth = 0;
|
|
977
|
+
for (let index = 0; index < value.length; index++) {
|
|
978
|
+
if (value[index] === "(") {
|
|
979
|
+
depth++;
|
|
980
|
+
}
|
|
981
|
+
else if (value[index] === ")") {
|
|
982
|
+
depth--;
|
|
983
|
+
if (depth === 0 && index < value.length - 1) {
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
986
|
+
if (depth < 0) {
|
|
987
|
+
return false;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return depth === 0;
|
|
992
|
+
}
|
|
993
|
+
function looksLikeCredentialShapedDottedLiteral(value) {
|
|
994
|
+
const segments = value.split(".");
|
|
995
|
+
if (segments.length < 2) {
|
|
996
|
+
return false;
|
|
997
|
+
}
|
|
998
|
+
if (/^(?:sk|pk|rk|pat)$/i.test(segments[0] ?? "")) {
|
|
999
|
+
return true;
|
|
1000
|
+
}
|
|
1001
|
+
const terminal = segments.at(-1) ?? "";
|
|
1002
|
+
return (terminal.length >= 16 &&
|
|
1003
|
+
!terminal.includes("_") &&
|
|
1004
|
+
isHighConfidenceAssignedSecret(terminal));
|
|
163
1005
|
}
|
|
164
1006
|
function isHighConfidenceAssignedSecret(value) {
|
|
165
1007
|
const normalized = value.trim().toLowerCase();
|
|
@@ -169,6 +1011,12 @@ function isHighConfidenceAssignedSecret(value) {
|
|
|
169
1011
|
if (PLACEHOLDER_VALUES.has(normalized)) {
|
|
170
1012
|
return false;
|
|
171
1013
|
}
|
|
1014
|
+
if (normalized === REDACTION_MARKER) {
|
|
1015
|
+
return false;
|
|
1016
|
+
}
|
|
1017
|
+
if (normalized === REDACTION_MARKER.slice(1, -1)) {
|
|
1018
|
+
return false;
|
|
1019
|
+
}
|
|
172
1020
|
if (/^(?:x+|\*+|0+|1+|a+)$/.test(normalized)) {
|
|
173
1021
|
return false;
|
|
174
1022
|
}
|
|
@@ -177,24 +1025,1387 @@ function isHighConfidenceAssignedSecret(value) {
|
|
|
177
1025
|
}
|
|
178
1026
|
return shannonEntropy(value) >= 3;
|
|
179
1027
|
}
|
|
180
|
-
function
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
1028
|
+
function isSensitiveDecodedKey(value) {
|
|
1029
|
+
const key = value.trim();
|
|
1030
|
+
const terminal = key.split(".").at(-1) ?? key;
|
|
1031
|
+
return [key, terminal].some((candidate) => /^[A-Za-z_$][A-Za-z0-9_$-]*$/.test(candidate) &&
|
|
1032
|
+
SENSITIVE_ASSIGNMENT_KEY_PATTERN.test(candidate));
|
|
1033
|
+
}
|
|
1034
|
+
function isRedactionMarkerValue(value) {
|
|
1035
|
+
const normalized = value.trim();
|
|
1036
|
+
return (normalized.length > 0 &&
|
|
1037
|
+
(normalized.replaceAll(REDACTION_MARKER, "").trim().length === 0 ||
|
|
1038
|
+
normalized
|
|
1039
|
+
.replaceAll(REDACTION_MARKER.slice(1, -1), "")
|
|
1040
|
+
.trim().length === 0));
|
|
1041
|
+
}
|
|
1042
|
+
function analyzeYamlStructuralSecrets(text) {
|
|
1043
|
+
const analysis = analysisFor(text);
|
|
1044
|
+
const cached = analysis?.yaml;
|
|
1045
|
+
if (cached != null) {
|
|
1046
|
+
return cached;
|
|
1047
|
+
}
|
|
1048
|
+
const result = analyzeYamlStructuralSecretsUncached(text);
|
|
1049
|
+
if (analysis != null) {
|
|
1050
|
+
analysis.yaml = result;
|
|
1051
|
+
}
|
|
1052
|
+
return result;
|
|
1053
|
+
}
|
|
1054
|
+
function analyzeYamlStructuralSecretsUncached(text) {
|
|
1055
|
+
chargeWork(text.length);
|
|
1056
|
+
if (text.length > MAX_YAML_SOURCE_CHARS) {
|
|
1057
|
+
return { ...emptyYamlStructuralAnalysis(), recognized: true, exhausted: true };
|
|
1058
|
+
}
|
|
1059
|
+
let documents;
|
|
1060
|
+
try {
|
|
1061
|
+
documents = parseAllDocuments(text, {
|
|
1062
|
+
prettyErrors: false,
|
|
1063
|
+
strict: false,
|
|
1064
|
+
uniqueKeys: false,
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
catch {
|
|
1068
|
+
return { ...emptyYamlStructuralAnalysis(), parseFailed: true };
|
|
1069
|
+
}
|
|
1070
|
+
if (documents.some((document) => document.errors.length > 0)) {
|
|
1071
|
+
return {
|
|
1072
|
+
...emptyYamlStructuralAnalysis(),
|
|
1073
|
+
parseFailed: true,
|
|
1074
|
+
};
|
|
1075
|
+
}
|
|
1076
|
+
if (documents.length === 0 || documents.length > MAX_YAML_DOCUMENTS) {
|
|
1077
|
+
return {
|
|
1078
|
+
...emptyYamlStructuralAnalysis(),
|
|
1079
|
+
exhausted: documents.length > MAX_YAML_DOCUMENTS,
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
1082
|
+
const collectionDocument = documents.some((document) => isMap(document.contents) || isSeq(document.contents));
|
|
1083
|
+
if (!collectionDocument) {
|
|
1084
|
+
return emptyYamlStructuralAnalysis();
|
|
1085
|
+
}
|
|
1086
|
+
const analysis = {
|
|
1087
|
+
recognized: false,
|
|
1088
|
+
exhausted: false,
|
|
1089
|
+
unsafeSecretRange: false,
|
|
1090
|
+
parseFailed: false,
|
|
1091
|
+
matches: [],
|
|
1092
|
+
};
|
|
1093
|
+
let hasSensitiveKey = false;
|
|
1094
|
+
let nodesSeen = 0;
|
|
1095
|
+
let currentDocument = documents[0];
|
|
1096
|
+
const resolveMappingKeyScalar = (node) => {
|
|
1097
|
+
let current = node;
|
|
1098
|
+
const active = new Set();
|
|
1099
|
+
for (let depth = 0; depth <= MAX_YAML_DEPTH; depth++) {
|
|
1100
|
+
chargeWork(1);
|
|
1101
|
+
if (current == null || ++nodesSeen > MAX_YAML_NODES) {
|
|
1102
|
+
analysis.exhausted = true;
|
|
1103
|
+
return undefined;
|
|
1104
|
+
}
|
|
1105
|
+
if (isScalar(current)) {
|
|
1106
|
+
return current;
|
|
1107
|
+
}
|
|
1108
|
+
if (!isAlias(current) || active.has(current) || currentDocument == null) {
|
|
1109
|
+
analysis.exhausted = true;
|
|
1110
|
+
return undefined;
|
|
1111
|
+
}
|
|
1112
|
+
active.add(current);
|
|
1113
|
+
current = current.resolve(currentDocument);
|
|
1114
|
+
}
|
|
1115
|
+
analysis.exhausted = true;
|
|
1116
|
+
return undefined;
|
|
1117
|
+
};
|
|
1118
|
+
const visit = (node, sensitiveAncestor, depth, unsafeScalarRange) => {
|
|
1119
|
+
if (node == null || analysis.exhausted) {
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
chargeWork(1);
|
|
1123
|
+
nodesSeen++;
|
|
1124
|
+
if (nodesSeen > MAX_YAML_NODES || depth > MAX_YAML_DEPTH) {
|
|
1125
|
+
analysis.exhausted = true;
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
1128
|
+
if (isMap(node)) {
|
|
1129
|
+
for (const pair of node.items) {
|
|
1130
|
+
const pairKey = pair.key;
|
|
1131
|
+
if (sensitiveAncestor) {
|
|
1132
|
+
visit(pairKey, true, depth + 1, false);
|
|
1133
|
+
}
|
|
1134
|
+
const keyNode = isScalar(pairKey)
|
|
1135
|
+
? pairKey
|
|
1136
|
+
: isAlias(pairKey)
|
|
1137
|
+
? resolveMappingKeyScalar(pairKey)
|
|
1138
|
+
: undefined;
|
|
1139
|
+
if (isAlias(pairKey) && keyNode == null) {
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
const decodedKey = isScalar(keyNode)
|
|
1143
|
+
? String(keyNode.value ?? "")
|
|
1144
|
+
: undefined;
|
|
1145
|
+
const sensitiveKey = decodedKey != null && isSensitiveDecodedKey(decodedKey);
|
|
1146
|
+
hasSensitiveKey ||= sensitiveKey;
|
|
1147
|
+
const pairValue = pair.value;
|
|
1148
|
+
const keyEnd = pairKey?.range?.[1];
|
|
1149
|
+
const valueStart = pairValue?.range?.[0];
|
|
1150
|
+
const valueGap = keyEnd == null || valueStart == null
|
|
1151
|
+
? ""
|
|
1152
|
+
: text.slice(keyEnd, valueStart);
|
|
1153
|
+
if (sensitiveKey &&
|
|
1154
|
+
(valueGap.match(/\n/g)?.length ?? 0) > 256) {
|
|
1155
|
+
if (isScalar(pairValue)) {
|
|
1156
|
+
visit(pairValue, true, depth + 1, true);
|
|
1157
|
+
}
|
|
1158
|
+
analysis.exhausted = true;
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
visit(pairValue, sensitiveAncestor || sensitiveKey, depth + 1, sensitiveKey &&
|
|
1162
|
+
isScalar(pairValue) &&
|
|
1163
|
+
pairValue.type === "PLAIN" &&
|
|
1164
|
+
/\r|\n/.test(valueGap));
|
|
1165
|
+
}
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
if (isSeq(node)) {
|
|
1169
|
+
for (const item of node.items) {
|
|
1170
|
+
visit(item, sensitiveAncestor, depth + 1, false);
|
|
1171
|
+
}
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
if (!sensitiveAncestor) {
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
if (isAlias(node)) {
|
|
1178
|
+
if (currentDocument == null) {
|
|
1179
|
+
analysis.exhausted = true;
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
const referenceStatus = yamlReferenceTreeStatus(node, currentDocument);
|
|
1183
|
+
if (referenceStatus === "safe") {
|
|
1184
|
+
return;
|
|
1185
|
+
}
|
|
1186
|
+
if (referenceStatus === "secret") {
|
|
1187
|
+
analysis.unsafeSecretRange = true;
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
analysis.exhausted = true;
|
|
1191
|
+
}
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
if (!isScalar(node)) {
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
let value;
|
|
1198
|
+
if (typeof node.value === "string") {
|
|
1199
|
+
value = node.value;
|
|
1200
|
+
}
|
|
1201
|
+
else if (node.value instanceof Uint8Array) {
|
|
1202
|
+
const decoded = new TextDecoder("utf-8", { fatal: false }).decode(node.value);
|
|
1203
|
+
const encoded = typeof node.source === "string" ? node.source : "";
|
|
1204
|
+
value = isHighConfidenceAssignedSecret(decoded) ? decoded : encoded;
|
|
1205
|
+
}
|
|
1206
|
+
else if (node.value == null ||
|
|
1207
|
+
typeof node.value === "number" ||
|
|
1208
|
+
typeof node.value === "boolean") {
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
else {
|
|
1212
|
+
analysis.exhausted = true;
|
|
1213
|
+
return;
|
|
1214
|
+
}
|
|
1215
|
+
if (isRedactionMarkerValue(value) ||
|
|
1216
|
+
(node.type === "PLAIN" && looksLikeCodeExpression(value.trim())) ||
|
|
1217
|
+
!isHighConfidenceAssignedSecret(value)) {
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
const range = node.range;
|
|
1221
|
+
const valueStart = range?.[0];
|
|
1222
|
+
const valueEnd = range?.[1];
|
|
1223
|
+
if (valueStart == null ||
|
|
1224
|
+
valueEnd == null ||
|
|
1225
|
+
valueStart < 0 ||
|
|
1226
|
+
valueEnd <= valueStart ||
|
|
1227
|
+
valueEnd > text.length) {
|
|
1228
|
+
analysis.unsafeSecretRange = true;
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
if (unsafeScalarRange) {
|
|
1232
|
+
analysis.unsafeSecretRange = true;
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
analysis.matches.push({
|
|
1236
|
+
keyStart: valueStart,
|
|
1237
|
+
keyEnd: valueStart,
|
|
1238
|
+
valueStart,
|
|
1239
|
+
valueEnd,
|
|
1240
|
+
line: lineNumberAtOffset(text, valueStart),
|
|
1241
|
+
confidence: "high",
|
|
1242
|
+
replacement: redactYamlScalarSource(text.slice(valueStart, valueEnd)),
|
|
1243
|
+
});
|
|
1244
|
+
};
|
|
1245
|
+
for (const document of documents) {
|
|
1246
|
+
currentDocument = document;
|
|
1247
|
+
visit(document.contents, false, 0, false);
|
|
1248
|
+
}
|
|
1249
|
+
analysis.recognized = hasSensitiveKey;
|
|
1250
|
+
return analysis;
|
|
1251
|
+
}
|
|
1252
|
+
function yamlReferenceTreeStatus(node, document) {
|
|
1253
|
+
const active = new Set();
|
|
1254
|
+
const memoizedSafe = new Set();
|
|
1255
|
+
let remaining = MAX_YAML_NODES;
|
|
1256
|
+
const visit = (current, depth) => {
|
|
1257
|
+
if (current == null || depth > MAX_YAML_DEPTH) {
|
|
1258
|
+
return "incomplete";
|
|
1259
|
+
}
|
|
1260
|
+
if (memoizedSafe.has(current)) {
|
|
1261
|
+
return "safe";
|
|
1262
|
+
}
|
|
1263
|
+
if (--remaining < 0 || active.has(current)) {
|
|
1264
|
+
return "incomplete";
|
|
1265
|
+
}
|
|
1266
|
+
active.add(current);
|
|
1267
|
+
let status;
|
|
1268
|
+
if (isAlias(current)) {
|
|
1269
|
+
const resolved = current.resolve(document);
|
|
1270
|
+
status = visit(resolved, depth + 1);
|
|
1271
|
+
}
|
|
1272
|
+
else if (isSeq(current)) {
|
|
1273
|
+
status = combineYamlReferenceStatuses(current.items.map((item) => visit(item, depth + 1)));
|
|
1274
|
+
}
|
|
1275
|
+
else if (isMap(current)) {
|
|
1276
|
+
status = combineYamlReferenceStatuses(current.items.flatMap((pair) => [
|
|
1277
|
+
visit(pair.key, depth + 1),
|
|
1278
|
+
visit(pair.value, depth + 1),
|
|
1279
|
+
]));
|
|
1280
|
+
}
|
|
1281
|
+
else if (!isScalar(current)) {
|
|
1282
|
+
status = "incomplete";
|
|
1283
|
+
}
|
|
1284
|
+
else if (current.value == null ||
|
|
1285
|
+
typeof current.value === "number" ||
|
|
1286
|
+
typeof current.value === "boolean") {
|
|
1287
|
+
status = "safe";
|
|
1288
|
+
}
|
|
1289
|
+
else if (current.value instanceof Uint8Array) {
|
|
1290
|
+
const decoded = new TextDecoder("utf-8", { fatal: false }).decode(current.value);
|
|
1291
|
+
const encoded = typeof current.source === "string" ? current.source : "";
|
|
1292
|
+
status = isHighConfidenceAssignedSecret(decoded) ||
|
|
1293
|
+
isHighConfidenceAssignedSecret(encoded)
|
|
1294
|
+
? "secret"
|
|
1295
|
+
: "safe";
|
|
1296
|
+
}
|
|
1297
|
+
else if (typeof current.value !== "string") {
|
|
1298
|
+
status = "incomplete";
|
|
1299
|
+
}
|
|
1300
|
+
else {
|
|
1301
|
+
const safe = isRedactionMarkerValue(current.value) ||
|
|
1302
|
+
!isHighConfidenceAssignedSecret(current.value) ||
|
|
1303
|
+
(current.type === "PLAIN" &&
|
|
1304
|
+
looksLikeCodeExpression(current.value.trim()));
|
|
1305
|
+
status = safe ? "safe" : "secret";
|
|
1306
|
+
}
|
|
1307
|
+
active.delete(current);
|
|
1308
|
+
if (status === "safe") {
|
|
1309
|
+
memoizedSafe.add(current);
|
|
1310
|
+
}
|
|
1311
|
+
return status;
|
|
1312
|
+
};
|
|
1313
|
+
return visit(node, 0);
|
|
1314
|
+
}
|
|
1315
|
+
function combineYamlReferenceStatuses(statuses) {
|
|
1316
|
+
if (statuses.includes("secret")) {
|
|
1317
|
+
return "secret";
|
|
1318
|
+
}
|
|
1319
|
+
return statuses.includes("incomplete") ? "incomplete" : "safe";
|
|
1320
|
+
}
|
|
1321
|
+
function emptyYamlStructuralAnalysis() {
|
|
1322
|
+
return {
|
|
1323
|
+
recognized: false,
|
|
1324
|
+
exhausted: false,
|
|
1325
|
+
parseFailed: false,
|
|
1326
|
+
unsafeSecretRange: false,
|
|
1327
|
+
matches: [],
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function hasMalformedYamlSensitiveSecret(text) {
|
|
1331
|
+
const analysis = analysisFor(text);
|
|
1332
|
+
const cached = analysis?.malformedYamlSecret;
|
|
1333
|
+
if (cached != null) {
|
|
1334
|
+
return cached;
|
|
1335
|
+
}
|
|
1336
|
+
let result = false;
|
|
1337
|
+
// Valid TypeScript class fields are not YAML merely because a nested type
|
|
1338
|
+
// property has a sensitive name. Remove only declarations whose bounded RHS
|
|
1339
|
+
// is an unambiguous reference; literal/object RHS values remain visible to
|
|
1340
|
+
// the fail-closed malformed-content check below.
|
|
1341
|
+
const withoutSafeTypedFields = maskConfidentlySafeTypeOnlyFields(maskConfidentlySafeTypedFields(text));
|
|
1342
|
+
for (const valueStart of malformedYamlSensitiveValueStarts(withoutSafeTypedFields)) {
|
|
1343
|
+
const span = malformedYamlValueSpan(withoutSafeTypedFields, valueStart);
|
|
1344
|
+
const expression = span.trim().replace(/,$/, "").trim();
|
|
1345
|
+
if (looksLikeCodeExpression(expression)) {
|
|
1346
|
+
continue;
|
|
1347
|
+
}
|
|
1348
|
+
if (spanContainsHighEntropyLiteral(span)) {
|
|
1349
|
+
result = true;
|
|
1350
|
+
break;
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
if (analysis != null) {
|
|
1354
|
+
analysis.malformedYamlSecret = result;
|
|
1355
|
+
}
|
|
1356
|
+
return result;
|
|
1357
|
+
}
|
|
1358
|
+
function malformedYamlValueSpan(text, valueStart) {
|
|
1359
|
+
const { lineStarts } = yamlBlockHeaderIndexFor(text);
|
|
1360
|
+
const lineSlot = binarySearchLastLessThan(lineStarts, valueStart + 1);
|
|
1361
|
+
let lineIndex = lineSlot < 0 ? 0 : lineSlot;
|
|
1362
|
+
const firstLineEnd = lineEndViaStarts(text, lineStarts, lineIndex);
|
|
1363
|
+
const firstLine = text.slice(valueStart, firstLineEnd);
|
|
1364
|
+
const firstValue = firstLine.replace(/^[ \t]*/, "");
|
|
1365
|
+
const continues = firstValue.length === 0 ||
|
|
1366
|
+
firstValue.startsWith("#") ||
|
|
1367
|
+
MALFORMED_YAML_CONTINUES_PATTERN.test(firstValue);
|
|
1368
|
+
if (!continues) {
|
|
1369
|
+
return firstLine;
|
|
1370
|
+
}
|
|
1371
|
+
const keyLineStart = lineStarts[lineIndex] ?? 0;
|
|
1372
|
+
const keyIndent = leadingIndentWidth(text.slice(keyLineStart, valueStart));
|
|
1373
|
+
let cursor = firstLineEnd;
|
|
1374
|
+
let end = firstLineEnd;
|
|
1375
|
+
let lines = 0;
|
|
1376
|
+
while (cursor < text.length && lines <= 512 && cursor - valueStart < 32_768) {
|
|
1377
|
+
chargeWork(1);
|
|
1378
|
+
if (lineIndex + 1 >= lineStarts.length) {
|
|
1379
|
+
break;
|
|
1380
|
+
}
|
|
1381
|
+
const lineStart = lineStarts[lineIndex + 1] ?? text.length;
|
|
1382
|
+
if (text.charCodeAt(cursor) === 0x0d) {
|
|
1383
|
+
if (text.charCodeAt(cursor + 1) !== 0x0a) {
|
|
1384
|
+
break;
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
else if (text.charCodeAt(cursor) !== 0x0a) {
|
|
1388
|
+
break;
|
|
1389
|
+
}
|
|
1390
|
+
const lineEnd = lineEndViaStarts(text, lineStarts, lineIndex + 1);
|
|
1391
|
+
const line = text.slice(lineStart, lineEnd);
|
|
1392
|
+
if (line.trim().length > 0 && leadingIndentWidth(line) <= keyIndent) {
|
|
1393
|
+
break;
|
|
1394
|
+
}
|
|
1395
|
+
end = lineEnd;
|
|
1396
|
+
cursor = lineEnd;
|
|
1397
|
+
lines++;
|
|
1398
|
+
lineIndex++;
|
|
1399
|
+
}
|
|
1400
|
+
return text.slice(valueStart, end);
|
|
1401
|
+
}
|
|
1402
|
+
/** End offset (exclusive of the line terminator) of line `lineIndex`. */
|
|
1403
|
+
function lineEndViaStarts(text, lineStarts, lineIndex) {
|
|
1404
|
+
if (lineIndex + 1 >= lineStarts.length) {
|
|
1405
|
+
return text.length;
|
|
1406
|
+
}
|
|
1407
|
+
const nextStart = lineStarts[lineIndex + 1] ?? text.length;
|
|
1408
|
+
const newline = nextStart - 1;
|
|
1409
|
+
return newline > 0 && text.charCodeAt(newline - 1) === 0x0d
|
|
1410
|
+
? newline - 1
|
|
1411
|
+
: newline;
|
|
1412
|
+
}
|
|
1413
|
+
function maskConfidentlySafeTypeOnlyFields(text) {
|
|
1414
|
+
// Manual linear scan reproducing
|
|
1415
|
+
// /(^|[;{}\r\n])(prefix)([\s\S]{1,4096}?);/gm.replace semantics exactly:
|
|
1416
|
+
// `^` only at line starts, `;`-terminated lazy type bodies resolved through
|
|
1417
|
+
// the next-semicolon index, and the scan resuming after each match's `;`
|
|
1418
|
+
// whether or not the match was rewritten.
|
|
1419
|
+
const nextSemicolon = nextSemicolonFor(text);
|
|
1420
|
+
const isTypeBodyBoundaryChar = (code) => code === 0x3b ||
|
|
1421
|
+
code === 0x7b ||
|
|
1422
|
+
code === 0x7d ||
|
|
1423
|
+
code === 0x0d ||
|
|
1424
|
+
code === 0x0a;
|
|
1425
|
+
let output = "";
|
|
1426
|
+
let copied = 0;
|
|
1427
|
+
let position = 0;
|
|
1428
|
+
while (position < text.length) {
|
|
1429
|
+
chargeWork(1);
|
|
1430
|
+
const atLineStart = position === 0 || isLineTerminatorCode(text.charCodeAt(position - 1));
|
|
1431
|
+
let boundaryLength = -1;
|
|
1432
|
+
let continuation;
|
|
1433
|
+
if (atLineStart) {
|
|
1434
|
+
continuation = matchTypedFieldContinuationAt(text, position, text.length);
|
|
1435
|
+
if (continuation != null) {
|
|
1436
|
+
boundaryLength = 0;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
if (boundaryLength < 0 && isTypeBodyBoundaryChar(text.charCodeAt(position))) {
|
|
1440
|
+
const afterBoundary = matchTypedFieldContinuationAt(text, position + 1, text.length);
|
|
1441
|
+
if (afterBoundary != null) {
|
|
1442
|
+
boundaryLength = 1;
|
|
1443
|
+
continuation = afterBoundary;
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
if (boundaryLength < 0 || continuation == null) {
|
|
1447
|
+
position++;
|
|
1448
|
+
continue;
|
|
1449
|
+
}
|
|
1450
|
+
const typeStart = continuation.typeStart;
|
|
1451
|
+
const terminator = typeStart + 1 <= text.length ? nextSemicolon[typeStart + 1] ?? -1 : -1;
|
|
1452
|
+
if (terminator < 0 || terminator - typeStart > MAX_TS_TYPE_ONLY_FIELD_CHARS) {
|
|
1453
|
+
// The lazy group cannot reach a `;` in range. Prefix backtracking never
|
|
1454
|
+
// rescues the match: a shorter modifier run leaves a keyword followed by
|
|
1455
|
+
// whitespace, which the key production cannot re-parse.
|
|
1456
|
+
position++;
|
|
1457
|
+
continue;
|
|
1458
|
+
}
|
|
1459
|
+
const type = text.slice(typeStart, terminator);
|
|
1460
|
+
if (isInsideBoundedTypeBodyAt(text, 0, position + boundaryLength) &&
|
|
1461
|
+
looksLikeTsTypeExpression(type, true)) {
|
|
1462
|
+
output += text.slice(copied, position);
|
|
1463
|
+
output += text.slice(position, position + boundaryLength);
|
|
1464
|
+
for (let index = position + boundaryLength; index <= terminator; index++) {
|
|
1465
|
+
const code = text.charCodeAt(index);
|
|
1466
|
+
output += code === 0x0a || code === 0x0d ? text[index] ?? "" : " ";
|
|
1467
|
+
}
|
|
1468
|
+
copied = terminator + 1;
|
|
1469
|
+
}
|
|
1470
|
+
position = terminator + 1;
|
|
1471
|
+
}
|
|
1472
|
+
return output + text.slice(copied);
|
|
1473
|
+
}
|
|
1474
|
+
function maskConfidentlySafeTypedFields(text) {
|
|
1475
|
+
const masked = commentMaskedFor(text);
|
|
1476
|
+
const safeSpans = [];
|
|
1477
|
+
let quote;
|
|
1478
|
+
let escaped = false;
|
|
1479
|
+
for (let equalsAt = 0; equalsAt < masked.length; equalsAt++) {
|
|
1480
|
+
chargeWork(1);
|
|
1481
|
+
const character = masked[equalsAt] ?? "";
|
|
1482
|
+
if (quote != null) {
|
|
1483
|
+
if (!escaped && character === quote) {
|
|
1484
|
+
quote = undefined;
|
|
1485
|
+
}
|
|
1486
|
+
escaped = !escaped && character === "\\";
|
|
1487
|
+
continue;
|
|
1488
|
+
}
|
|
1489
|
+
if (character === '"' || character === "'" || character === "`") {
|
|
1490
|
+
quote = character;
|
|
1491
|
+
escaped = false;
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
if (character !== "=" ||
|
|
1495
|
+
/[=!<>]/.test(masked[equalsAt - 1] ?? "") ||
|
|
1496
|
+
/[=>]/.test(masked[equalsAt + 1] ?? "")) {
|
|
1497
|
+
continue;
|
|
1498
|
+
}
|
|
1499
|
+
chargeWork(32);
|
|
1500
|
+
const windowStart = Math.max(0, equalsAt - MAX_JS_KEY_LOOKBACK);
|
|
1501
|
+
const field = findBoundedTypedField(masked, windowStart, equalsAt);
|
|
1502
|
+
if (field == null || field === "exhausted") {
|
|
1503
|
+
continue;
|
|
1504
|
+
}
|
|
1505
|
+
const rhs = residualJsValueSpan(text, masked, equalsAt + 1);
|
|
1506
|
+
if (rhs.exhausted) {
|
|
1507
|
+
continue;
|
|
1508
|
+
}
|
|
1509
|
+
const value = reconstructStaticJsValue(rhs.content)?.trim();
|
|
1510
|
+
if (value == null || !isSafeTypedFieldReference(rhs.content, value)) {
|
|
1511
|
+
continue;
|
|
1512
|
+
}
|
|
1513
|
+
safeSpans.push({
|
|
1514
|
+
start: field.start,
|
|
1515
|
+
end: equalsAt + 1 + rhs.content.length,
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
if (safeSpans.length === 0) {
|
|
1519
|
+
return text;
|
|
1520
|
+
}
|
|
1521
|
+
// Parser/lexer ranges are UTF-16 offsets, so preserve code-unit indexing.
|
|
1522
|
+
const characters = text.split("");
|
|
1523
|
+
for (const span of safeSpans) {
|
|
1524
|
+
for (let index = span.start; index < span.end; index++) {
|
|
1525
|
+
if (characters[index] !== "\n" && characters[index] !== "\r") {
|
|
1526
|
+
characters[index] = " ";
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
return characters.join("");
|
|
1531
|
+
}
|
|
1532
|
+
function isSafeTypedFieldReference(source, value) {
|
|
1533
|
+
const expression = unwrapReferenceExpression(value);
|
|
1534
|
+
const sourceExpression = unwrapReferenceExpression(maskJsCommentTrivia(source));
|
|
1535
|
+
const staticEnvReference = /^(?:process\.env\[["'][A-Za-z_][A-Za-z0-9_]*["']\]|Deno\.env\.get\(["'][A-Za-z_][A-Za-z0-9_]*["']\))$/.test(sourceExpression);
|
|
1536
|
+
return (staticEnvReference ||
|
|
1537
|
+
(!/["'`]/.test(maskJsCommentTrivia(source)) &&
|
|
1538
|
+
(looksLikeCodeExpression(expression) ||
|
|
1539
|
+
/^keyof\s+typeof\s+[A-Za-z_$][A-Za-z0-9_$]*(?:(?:\.|::)[A-Za-z_$][A-Za-z0-9_$]*)*$/.test(expression))));
|
|
1540
|
+
}
|
|
1541
|
+
function malformedYamlSensitiveValueStarts(text) {
|
|
1542
|
+
chargeWork(4 * text.length);
|
|
1543
|
+
const starts = [];
|
|
1544
|
+
const keyToken = String.raw `[A-Za-z_$][A-Za-z0-9_$-]*(?:\.[A-Za-z_$][A-Za-z0-9_$-]*)*`;
|
|
1545
|
+
for (const match of text.matchAll(/^[ \t]*(["'])((?:\\.|(?!\1)[^\r\n])*)\1[ \t]*:/gm)) {
|
|
1546
|
+
const decoded = match[1] === '"'
|
|
1547
|
+
? decodeJsString(match[2] ?? "")
|
|
1548
|
+
: (match[2] ?? "").replaceAll("''", "'");
|
|
1549
|
+
if (decoded != null && isSensitiveDecodedKey(decoded)) {
|
|
1550
|
+
starts.push((match.index ?? 0) + match[0].length);
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
for (const match of text.matchAll(new RegExp(String.raw `^[ \t]*(${keyToken})[ \t]*:`, "gm"))) {
|
|
1554
|
+
if (isSensitiveDecodedKey(match[1] ?? "")) {
|
|
1555
|
+
starts.push((match.index ?? 0) + match[0].length);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
const sensitiveAliases = new Set();
|
|
1559
|
+
for (const match of text.matchAll(new RegExp(String.raw `&([A-Za-z0-9_.-]+)(?:[ \t]+(?:!!?[^ \t\r\n]+|!<[^>\r\n]+>))*[ \t]+(${keyToken})`, "g"))) {
|
|
1560
|
+
if (isSensitiveDecodedKey(match[2] ?? "")) {
|
|
1561
|
+
sensitiveAliases.add(match[1] ?? "");
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
const explicitKey = new RegExp(String.raw `^[ \t]*\?[ \t]+(?:(${keyToken})|\*([A-Za-z0-9_.-]+))[ \t]*(?:\r?\n)[ \t]*:`, "gm");
|
|
1565
|
+
for (const match of text.matchAll(explicitKey)) {
|
|
1566
|
+
if (isSensitiveDecodedKey(match[1] ?? "") ||
|
|
1567
|
+
sensitiveAliases.has(match[2] ?? "")) {
|
|
1568
|
+
starts.push((match.index ?? 0) + match[0].length);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
return starts;
|
|
1572
|
+
}
|
|
1573
|
+
function redactYamlScalarSource(source) {
|
|
1574
|
+
if (!/\r|\n/.test(source)) {
|
|
1575
|
+
const leading = /^[ \t]*/.exec(source)?.[0] ?? "";
|
|
1576
|
+
const trailing = /[ \t]*$/.exec(source)?.[0] ?? "";
|
|
1577
|
+
const body = source.slice(leading.length, source.length - trailing.length);
|
|
1578
|
+
const quote = body[0] === '"' || body[0] === "'" ? body[0] : undefined;
|
|
1579
|
+
return `${leading}${quote ?? ""}${REDACTION_MARKER}${quote ?? ""}${trailing}`;
|
|
1580
|
+
}
|
|
1581
|
+
const firstLineEnd = lineEndIndex(source, 0);
|
|
1582
|
+
const firstLine = source.slice(0, firstLineEnd);
|
|
1583
|
+
if (new RegExp(`^[ \\t]*${YAML_BLOCK_HEADER_PATTERN_SOURCE}(?:[ \\t]+#.*)?$`).test(firstLine)) {
|
|
1584
|
+
const newlineLength = source.startsWith("\r\n", firstLineEnd) ? 2 : 1;
|
|
1585
|
+
return `${source.slice(0, firstLineEnd + newlineLength)}${redactYamlBlockPayload(source.slice(firstLineEnd + newlineLength))}`;
|
|
1586
|
+
}
|
|
1587
|
+
const trimmed = source.trim();
|
|
1588
|
+
const quote = trimmed[0] === '"' || trimmed[0] === "'" ? trimmed[0] : undefined;
|
|
1589
|
+
if (quote != null && trimmed.endsWith(quote)) {
|
|
1590
|
+
const leading = /^[ \t]*/.exec(source)?.[0] ?? "";
|
|
1591
|
+
const lineBreaks = source.match(/\r\n|\n/g) ?? [];
|
|
1592
|
+
return `${leading}${quote}${REDACTION_MARKER}${quote}${lineBreaks.join("")}`;
|
|
1593
|
+
}
|
|
1594
|
+
return redactYamlBlockPayload(source);
|
|
1595
|
+
}
|
|
1596
|
+
function lineNumberAtOffset(text, offset) {
|
|
1597
|
+
const newlineOffsets = newlineOffsetsFor(text);
|
|
1598
|
+
return 1 + binarySearchLastLessThan(newlineOffsets, offset) + 1;
|
|
1599
|
+
}
|
|
1600
|
+
function analyzeJsStructuralSecrets(text, primaryRedaction = true) {
|
|
1601
|
+
const analysis = analysisFor(text);
|
|
1602
|
+
const slot = primaryRedaction ? "jsPrimary" : "jsResidual";
|
|
1603
|
+
const cached = analysis?.[slot];
|
|
1604
|
+
if (cached != null) {
|
|
1605
|
+
return cached;
|
|
1606
|
+
}
|
|
1607
|
+
const result = analyzeJsStructuralSecretsUncached(text, primaryRedaction);
|
|
1608
|
+
if (analysis != null) {
|
|
1609
|
+
analysis[slot] = result;
|
|
1610
|
+
}
|
|
1611
|
+
return result;
|
|
1612
|
+
}
|
|
1613
|
+
function analyzeJsStructuralSecretsUncached(text, primaryRedaction) {
|
|
1614
|
+
const masked = commentMaskedFor(text);
|
|
1615
|
+
const matches = [];
|
|
1616
|
+
let unsafeSensitiveContext = false;
|
|
1617
|
+
let quote;
|
|
1618
|
+
let escaped = false;
|
|
1619
|
+
let scanned = 0;
|
|
1620
|
+
for (let cursor = 0; cursor < masked.length; cursor++) {
|
|
1621
|
+
chargeWork(1);
|
|
1622
|
+
if (++scanned > 1_000_000) {
|
|
1623
|
+
return { exhausted: true, unsafeSensitiveContext, matches };
|
|
1624
|
+
}
|
|
1625
|
+
const character = masked[cursor] ?? "";
|
|
1626
|
+
if (quote != null) {
|
|
1627
|
+
if (!escaped && character === quote) {
|
|
1628
|
+
quote = undefined;
|
|
1629
|
+
}
|
|
1630
|
+
escaped = !escaped && character === "\\";
|
|
1631
|
+
continue;
|
|
1632
|
+
}
|
|
1633
|
+
if (character === '"' || character === "'" || character === "`") {
|
|
1634
|
+
quote = character;
|
|
1635
|
+
escaped = false;
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
if (character !== "=" ||
|
|
1639
|
+
/[=!<>]/.test(masked[cursor - 1] ?? "") ||
|
|
1640
|
+
/[=>]/.test(masked[cursor + 1] ?? "")) {
|
|
1641
|
+
continue;
|
|
1642
|
+
}
|
|
1643
|
+
chargeWork(32);
|
|
1644
|
+
const key = sensitiveJsKeyBefore(text, masked, cursor);
|
|
1645
|
+
if (key === "exhausted") {
|
|
1646
|
+
const span = residualJsValueSpan(text, masked, cursor + 1);
|
|
1647
|
+
unsafeSensitiveContext ||=
|
|
1648
|
+
hasExhaustedSensitiveJsKeyEvidence(masked, cursor) &&
|
|
1649
|
+
spanContainsHighEntropyLiteral(text.slice(cursor + 1));
|
|
1650
|
+
return { exhausted: true, unsafeSensitiveContext, matches };
|
|
1651
|
+
}
|
|
1652
|
+
if (key == null) {
|
|
1653
|
+
if (hasUnclassifiableSensitiveJsKey(text, masked, cursor)) {
|
|
1654
|
+
const span = residualJsValueSpan(text, masked, cursor + 1);
|
|
1655
|
+
if (span.exhausted) {
|
|
1656
|
+
unsafeSensitiveContext ||= spanContainsHighEntropyLiteral(text.slice(cursor + 1));
|
|
1657
|
+
return { exhausted: true, unsafeSensitiveContext, matches };
|
|
1658
|
+
}
|
|
1659
|
+
const unsafe = spanContainsHighEntropyLiteral(span.content);
|
|
1660
|
+
unsafeSensitiveContext ||= unsafe;
|
|
1661
|
+
}
|
|
1662
|
+
continue;
|
|
1663
|
+
}
|
|
1664
|
+
if (primaryRedaction &&
|
|
1665
|
+
/\/\*|\/\/|\r|\n/.test(text.slice(key.end, cursor))) {
|
|
1666
|
+
continue;
|
|
1667
|
+
}
|
|
1668
|
+
const span = residualJsValueSpan(text, masked, cursor + 1);
|
|
1669
|
+
if (span.exhausted) {
|
|
1670
|
+
unsafeSensitiveContext ||= spanContainsHighEntropyLiteral(text.slice(cursor + 1));
|
|
1671
|
+
return { exhausted: true, unsafeSensitiveContext, matches };
|
|
1672
|
+
}
|
|
1673
|
+
const reconstructed = reconstructStaticJsValue(span.content);
|
|
1674
|
+
const containsLiteral = /["'`]/.test(maskJsCommentTrivia(span.content));
|
|
1675
|
+
if (looksLikeCodeExpression(span.content.trim())) {
|
|
1676
|
+
continue;
|
|
1677
|
+
}
|
|
1678
|
+
const typedFieldWindowStart = Math.max(0, cursor - MAX_JS_KEY_LOOKBACK);
|
|
1679
|
+
const typedField = findBoundedTypedField(masked, typedFieldWindowStart, cursor);
|
|
1680
|
+
const safeTypedReference = typedField != null &&
|
|
1681
|
+
typedField !== "exhausted" &&
|
|
1682
|
+
reconstructed != null &&
|
|
1683
|
+
isSafeTypedFieldReference(span.content, reconstructed.trim());
|
|
1684
|
+
if (safeTypedReference) {
|
|
1685
|
+
continue;
|
|
1686
|
+
}
|
|
1687
|
+
if (reconstructed == null) {
|
|
1688
|
+
const unsafe = spanContainsHighEntropyLiteral(span.content);
|
|
1689
|
+
unsafeSensitiveContext ||= unsafe;
|
|
1690
|
+
continue;
|
|
1691
|
+
}
|
|
1692
|
+
if ((!containsLiteral && looksLikeCodeExpression(reconstructed.trim())) ||
|
|
1693
|
+
!isHighConfidenceAssignedSecret(reconstructed)) {
|
|
1694
|
+
continue;
|
|
1695
|
+
}
|
|
1696
|
+
const valueStart = cursor + 1;
|
|
1697
|
+
const valueEnd = valueStart + span.content.length;
|
|
1698
|
+
matches.push({
|
|
1699
|
+
keyStart: key.start,
|
|
1700
|
+
keyEnd: key.end,
|
|
1701
|
+
valueStart,
|
|
1702
|
+
valueEnd,
|
|
1703
|
+
line: lineNumberAtOffset(text, key.start),
|
|
1704
|
+
confidence: "high",
|
|
1705
|
+
replacement: redactJsExpressionSource(span.content),
|
|
1706
|
+
});
|
|
1707
|
+
cursor = Math.max(cursor, valueEnd - 1);
|
|
1708
|
+
}
|
|
1709
|
+
return { exhausted: false, unsafeSensitiveContext, matches };
|
|
1710
|
+
}
|
|
1711
|
+
function hasExhaustedSensitiveJsKeyEvidence(masked, equalsAt) {
|
|
1712
|
+
const prefix = masked.slice(0, equalsAt);
|
|
1713
|
+
const keyPattern = /(?:^|[;{}])[ \t\r\n]*(?:(?:public|private|protected|static|readonly|declare|abstract|override)[ \t\r\n]+)*(?:password|passwd|api[_-]?key|secret|token|access[_-]?token|private[_-]?key|client[_-]?secret)\b/gi;
|
|
1714
|
+
let lastMatch = null;
|
|
1715
|
+
for (const match of prefix.matchAll(keyPattern)) {
|
|
1716
|
+
lastMatch = match;
|
|
1717
|
+
}
|
|
1718
|
+
if (lastMatch == null) {
|
|
1719
|
+
return false;
|
|
1720
|
+
}
|
|
1721
|
+
const suffix = prefix.slice((lastMatch.index ?? 0) + lastMatch[0].length);
|
|
1722
|
+
return !suffix.includes(";");
|
|
1723
|
+
}
|
|
1724
|
+
function hasUnclassifiableSensitiveJsKey(text, masked, equalsAt) {
|
|
1725
|
+
const start = Math.max(0, equalsAt - 2_048);
|
|
1726
|
+
const prefix = text.slice(start, equalsAt);
|
|
1727
|
+
const maskedPrefix = masked.slice(start, equalsAt);
|
|
1728
|
+
const bracket = /\[[^\]\r\n]{0,384}\]\s*(?:\|\||\?\?|\+)?\s*$/.exec(maskedPrefix);
|
|
1729
|
+
if (bracket != null) {
|
|
1730
|
+
const rawBracket = prefix.slice(bracket.index ?? 0);
|
|
1731
|
+
for (const match of rawBracket.matchAll(/(["'])((?:\\.|(?!\1)[^\r\n])*)\1/g)) {
|
|
1732
|
+
const decoded = decodeJsString(match[2] ?? "");
|
|
1733
|
+
if (decoded != null && isSensitiveDecodedKey(decoded)) {
|
|
1734
|
+
return true;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
return new RegExp(String.raw `(?:^|[;\r\n])[ \t]*(?:const|let|var)?[ \t]*(?:password|passwd|api[_-]?key|secret|token|access[_-]?token|private[_-]?key|client[_-]?secret)[ \t]*(?:\|\||\?\?|\+)[ \t]*$`, "i").test(maskedPrefix);
|
|
1739
|
+
}
|
|
1740
|
+
function spanContainsHighEntropyLiteral(span) {
|
|
1741
|
+
for (const match of span.matchAll(/(["'`])((?:\\.|(?!\1)[\s\S])*)\1/g)) {
|
|
1742
|
+
const encoded = match[2] ?? "";
|
|
1743
|
+
const literal = match[1] === "`" ? encoded : decodeJsString(encoded) ?? encoded;
|
|
1744
|
+
if (isHighConfidenceAssignedSecret(literal) ||
|
|
1745
|
+
[...literal.matchAll(/[A-Za-z0-9_./+=-]{16,}/g)].some((part) => isHighConfidenceAssignedSecret(part[0]))) {
|
|
1746
|
+
return true;
|
|
1747
|
+
}
|
|
1748
|
+
}
|
|
1749
|
+
const withoutComments = maskJsCommentTrivia(span)
|
|
1750
|
+
.split(/\r?\n/)
|
|
1751
|
+
.map((line) => line.replace(/(?:^|[ \t])#.*$/, ""))
|
|
1752
|
+
.join("\n");
|
|
1753
|
+
for (const line of withoutComments.split(/\r?\n/)) {
|
|
1754
|
+
const candidate = line
|
|
1755
|
+
.trim()
|
|
1756
|
+
.replace(/^:[ \t]*/, "")
|
|
1757
|
+
.replace(/^["']/, "")
|
|
1758
|
+
.replace(/["']$/, "")
|
|
1759
|
+
.trim();
|
|
1760
|
+
if (!isRedactionMarkerValue(candidate) &&
|
|
1761
|
+
!candidate.includes(REDACTION_MARKER) &&
|
|
1762
|
+
!new RegExp(String.raw `:\s*${YAML_BLOCK_HEADER_PATTERN_SOURCE}(?:\s+#.*)?$`).test(candidate) &&
|
|
1763
|
+
!looksLikeCodeExpression(candidate) &&
|
|
1764
|
+
isHighConfidenceAssignedSecret(candidate)) {
|
|
1765
|
+
return true;
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
return [...withoutComments.matchAll(/[A-Za-z0-9_./+=-]{16,}/g)].some((match) => !looksLikeCodeExpression(match[0]) &&
|
|
1769
|
+
isHighConfidenceAssignedSecret(match[0]));
|
|
1770
|
+
}
|
|
1771
|
+
function sensitiveJsKeyBefore(text, masked, equalsAt) {
|
|
1772
|
+
const windowStart = Math.max(0, equalsAt - MAX_JS_KEY_LOOKBACK);
|
|
1773
|
+
const typedField = findSensitiveTypedField(masked, windowStart, equalsAt);
|
|
1774
|
+
if (typedField != null) {
|
|
1775
|
+
return typedField;
|
|
1776
|
+
}
|
|
1777
|
+
const prefix = masked.slice(windowStart, equalsAt);
|
|
1778
|
+
const bracket = /\[\s*(["'])((?:\\.|(?!\1)[^\r\n])*)\1\s*\]\s*$/.exec(prefix);
|
|
1779
|
+
if (bracket != null) {
|
|
1780
|
+
const encoded = bracket[2] ?? "";
|
|
1781
|
+
const decoded = decodeJsString(encoded);
|
|
1782
|
+
if (decoded != null && isSensitiveDecodedKey(decoded)) {
|
|
1783
|
+
const encodedOffset = bracket[0].indexOf(encoded);
|
|
1784
|
+
const start = windowStart + (bracket.index ?? 0) + encodedOffset;
|
|
1785
|
+
return { start, end: start + encoded.length };
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
const quoted = /(["'])((?:\\.|(?!\1)[^\r\n])*)\1\s*$/.exec(prefix);
|
|
1789
|
+
if (quoted != null) {
|
|
1790
|
+
const encoded = quoted[2] ?? "";
|
|
1791
|
+
const decoded = decodeJsString(encoded);
|
|
1792
|
+
if (decoded != null && isSensitiveDecodedKey(decoded)) {
|
|
1793
|
+
const encodedOffset = quoted[0].indexOf(encoded);
|
|
1794
|
+
const start = windowStart + (quoted.index ?? 0) + encodedOffset;
|
|
1795
|
+
return { start, end: start + encoded.length };
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
const bare = /([A-Za-z_$][A-Za-z0-9_$-]*)\s*$/.exec(prefix);
|
|
1799
|
+
const rawKey = bare?.[1];
|
|
1800
|
+
if (rawKey == null) {
|
|
1801
|
+
if (windowStart > 0) {
|
|
1802
|
+
return "exhausted";
|
|
1803
|
+
}
|
|
1804
|
+
return undefined;
|
|
1805
|
+
}
|
|
1806
|
+
if (!isSensitiveDecodedKey(rawKey)) {
|
|
1807
|
+
if (windowStart > 0) {
|
|
1808
|
+
const beforeKey = prefix
|
|
1809
|
+
.slice(0, bare?.index ?? 0)
|
|
1810
|
+
.trimEnd()
|
|
1811
|
+
.at(-1);
|
|
1812
|
+
if (beforeKey != null && !/[;{}\r\n]/.test(beforeKey)) {
|
|
1813
|
+
return "exhausted";
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
return undefined;
|
|
1817
|
+
}
|
|
1818
|
+
const start = windowStart + (bare?.index ?? 0);
|
|
1819
|
+
return { start, end: start + rawKey.length };
|
|
1820
|
+
}
|
|
1821
|
+
function classifyTsTypeAnnotation(value) {
|
|
1822
|
+
const type = value.trim();
|
|
1823
|
+
if (type.length === 0) {
|
|
1824
|
+
return "unclassifiable";
|
|
184
1825
|
}
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
1826
|
+
const pairs = {
|
|
1827
|
+
"<": ">",
|
|
1828
|
+
"[": "]",
|
|
1829
|
+
"{": "}",
|
|
1830
|
+
"(": ")",
|
|
1831
|
+
};
|
|
1832
|
+
const closers = new Set(Object.values(pairs));
|
|
1833
|
+
const stack = [];
|
|
1834
|
+
for (let index = 0; index < type.length; index++) {
|
|
1835
|
+
const character = type[index] ?? "";
|
|
1836
|
+
if (character === ";" && stack.length === 0) {
|
|
1837
|
+
return "member_boundary";
|
|
1838
|
+
}
|
|
1839
|
+
if (index >= MAX_TS_TYPE_CHARS ||
|
|
1840
|
+
!/[A-Za-z0-9_$.,;<>\[\]{}()|&?:='" \t\r\n-]/.test(character)) {
|
|
1841
|
+
return "unclassifiable";
|
|
1842
|
+
}
|
|
1843
|
+
if (character === ">" && type[index - 1] === "=") {
|
|
1844
|
+
continue;
|
|
1845
|
+
}
|
|
1846
|
+
const closer = pairs[character];
|
|
1847
|
+
if (closer != null) {
|
|
1848
|
+
if (stack.length >= MAX_TS_TYPE_DEPTH) {
|
|
1849
|
+
return "unclassifiable";
|
|
1850
|
+
}
|
|
1851
|
+
stack.push(closer);
|
|
1852
|
+
}
|
|
1853
|
+
else if (closers.has(character) && stack.pop() !== character) {
|
|
1854
|
+
return "nested_candidate";
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
return stack.length === 0 ? "valid" : "unclassifiable";
|
|
1858
|
+
}
|
|
1859
|
+
function findSensitiveTypedField(source, windowStart, windowEnd) {
|
|
1860
|
+
const field = findBoundedTypedField(source, windowStart, windowEnd);
|
|
1861
|
+
if (field === "exhausted" || field == null || field.sensitive) {
|
|
1862
|
+
return field === "exhausted" || field == null
|
|
1863
|
+
? field
|
|
1864
|
+
: { start: field.start, end: field.end };
|
|
1865
|
+
}
|
|
1866
|
+
return undefined;
|
|
1867
|
+
}
|
|
1868
|
+
function findBoundedTypedField(source, windowStart, windowEnd) {
|
|
1869
|
+
// Index-backed enumeration. A window slice's matchAll could additionally
|
|
1870
|
+
// match at position 0 through the `^` alternative (no boundary character
|
|
1871
|
+
// consumed) — including continuations of a declaration whose boundary sits
|
|
1872
|
+
// just before the window and mid-identifier splits. That candidate comes
|
|
1873
|
+
// first in slice scan order; precomputed matches starting inside it would
|
|
1874
|
+
// have been consumed by the slice scan, so iteration resumes from its end.
|
|
1875
|
+
const candidates = typedFieldCandidatesFor(source);
|
|
1876
|
+
chargeWork(8);
|
|
1877
|
+
const synthetic = matchTypedFieldContinuationAt(source, windowStart, windowEnd);
|
|
1878
|
+
const evaluate = (keyStart, keyEnd, typeStart) => {
|
|
1879
|
+
const rawKey = source.slice(keyStart, keyEnd);
|
|
1880
|
+
if (!isInsideBoundedTypeBodyAt(source, windowStart, keyStart)) {
|
|
1881
|
+
return "continue";
|
|
1882
|
+
}
|
|
1883
|
+
// The legacy window ended at windowEnd, so the type annotation under
|
|
1884
|
+
// examination is bounded by it exactly as the slice was.
|
|
1885
|
+
const type = source.slice(typeStart, windowEnd).trim();
|
|
1886
|
+
const classification = classifyTsTypeAnnotation(type);
|
|
1887
|
+
if (classification === "member_boundary") {
|
|
1888
|
+
return "continue";
|
|
1889
|
+
}
|
|
1890
|
+
if (classification === "nested_candidate") {
|
|
1891
|
+
return "continue";
|
|
1892
|
+
}
|
|
1893
|
+
if (classification === "unclassifiable") {
|
|
1894
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(rawKey) &&
|
|
1895
|
+
isSensitiveDecodedKey(rawKey)
|
|
1896
|
+
? "exhausted"
|
|
1897
|
+
: undefined;
|
|
1898
|
+
}
|
|
1899
|
+
// The first candidate whose entire suffix is a balanced type annotation is
|
|
1900
|
+
// the outer field. Nested object-type properties leave unmatched closers.
|
|
1901
|
+
return {
|
|
1902
|
+
start: keyStart,
|
|
1903
|
+
end: keyEnd,
|
|
1904
|
+
sensitive: isSensitiveDecodedKey(rawKey),
|
|
1905
|
+
};
|
|
1906
|
+
};
|
|
1907
|
+
if (synthetic != null) {
|
|
1908
|
+
chargeWork(4);
|
|
1909
|
+
const outcome = evaluate(synthetic.keyStart, synthetic.keyEnd, synthetic.typeStart);
|
|
1910
|
+
if (outcome !== "continue") {
|
|
1911
|
+
return outcome;
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
const resumeFrom = synthetic != null ? synthetic.typeStart : windowStart;
|
|
1915
|
+
let slot = binarySearchLastLessThan(candidates.matchStarts, resumeFrom) + 1;
|
|
1916
|
+
while (slot < candidates.count) {
|
|
1917
|
+
chargeWork(4);
|
|
1918
|
+
const typeStart = candidates.typeStarts[slot] ?? 0;
|
|
1919
|
+
if (typeStart > windowEnd) {
|
|
1920
|
+
break;
|
|
1921
|
+
}
|
|
1922
|
+
const outcome = evaluate(candidates.keyStarts[slot] ?? typeStart, candidates.keyEnds[slot] ?? typeStart, typeStart);
|
|
1923
|
+
if (outcome !== "continue") {
|
|
1924
|
+
return outcome;
|
|
1925
|
+
}
|
|
1926
|
+
slot++;
|
|
1927
|
+
}
|
|
1928
|
+
return undefined;
|
|
1929
|
+
}
|
|
1930
|
+
function findTsFieldAssignmentEquals(text, start, end) {
|
|
1931
|
+
for (let index = start; index < end; index++) {
|
|
1932
|
+
if (text[index] === "=" &&
|
|
1933
|
+
text[index + 1] !== ">" &&
|
|
1934
|
+
!/[=!<>]/.test(text[index - 1] ?? "")) {
|
|
1935
|
+
return index;
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
return undefined;
|
|
1939
|
+
}
|
|
1940
|
+
function decodeJsString(value) {
|
|
1941
|
+
try {
|
|
1942
|
+
return value.replace(/\\(?:u\{([0-9A-Fa-f]{1,6})\}|u([0-9A-Fa-f]{4})|x([0-9A-Fa-f]{2})|([\\"'bfnrtv0]))/g, (_whole, braced, unicode, hex, simple) => {
|
|
1943
|
+
if (braced != null || unicode != null || hex != null) {
|
|
1944
|
+
const point = Number.parseInt(braced ?? unicode ?? hex, 16);
|
|
1945
|
+
return String.fromCodePoint(point);
|
|
1946
|
+
}
|
|
1947
|
+
const escapes = {
|
|
1948
|
+
"\\": "\\",
|
|
1949
|
+
'"': '"',
|
|
1950
|
+
"'": "'",
|
|
1951
|
+
b: "\b",
|
|
1952
|
+
f: "\f",
|
|
1953
|
+
n: "\n",
|
|
1954
|
+
r: "\r",
|
|
1955
|
+
t: "\t",
|
|
1956
|
+
v: "\v",
|
|
1957
|
+
"0": "\0",
|
|
1958
|
+
};
|
|
1959
|
+
return escapes[simple] ?? simple;
|
|
1960
|
+
});
|
|
189
1961
|
}
|
|
190
|
-
|
|
191
|
-
return
|
|
1962
|
+
catch {
|
|
1963
|
+
return undefined;
|
|
192
1964
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
1965
|
+
}
|
|
1966
|
+
function reconstructStaticJsValue(source) {
|
|
1967
|
+
const masked = maskJsCommentTrivia(source);
|
|
1968
|
+
if (!/["'`]/.test(masked)) {
|
|
1969
|
+
return masked.trim();
|
|
1970
|
+
}
|
|
1971
|
+
let cursor = 0;
|
|
1972
|
+
let value = "";
|
|
1973
|
+
let fragments = 0;
|
|
1974
|
+
while (cursor < masked.length) {
|
|
1975
|
+
while (cursor < masked.length && /[\s()+]/.test(masked[cursor] ?? "")) {
|
|
1976
|
+
cursor++;
|
|
1977
|
+
}
|
|
1978
|
+
if (cursor >= masked.length) {
|
|
1979
|
+
break;
|
|
1980
|
+
}
|
|
1981
|
+
const quote = masked[cursor];
|
|
1982
|
+
if (quote !== '"' && quote !== "'" && quote !== "`") {
|
|
1983
|
+
const bare = masked.slice(cursor).trim();
|
|
1984
|
+
return fragments === 0 ? bare : undefined;
|
|
1985
|
+
}
|
|
1986
|
+
cursor++;
|
|
1987
|
+
let encoded = "";
|
|
1988
|
+
let escaped = false;
|
|
1989
|
+
let dynamicTemplate = false;
|
|
1990
|
+
while (cursor < masked.length) {
|
|
1991
|
+
const character = masked[cursor] ?? "";
|
|
1992
|
+
if (!escaped && character === quote) {
|
|
1993
|
+
break;
|
|
1994
|
+
}
|
|
1995
|
+
if (quote === "`" && !escaped && character === "$" && masked[cursor + 1] === "{") {
|
|
1996
|
+
dynamicTemplate = true;
|
|
1997
|
+
}
|
|
1998
|
+
encoded += source[cursor] ?? "";
|
|
1999
|
+
escaped = !escaped && character === "\\";
|
|
2000
|
+
cursor++;
|
|
2001
|
+
}
|
|
2002
|
+
if (masked[cursor] !== quote || dynamicTemplate) {
|
|
2003
|
+
return undefined;
|
|
2004
|
+
}
|
|
2005
|
+
const decoded = decodeJsString(encoded);
|
|
2006
|
+
if (decoded == null) {
|
|
2007
|
+
return undefined;
|
|
2008
|
+
}
|
|
2009
|
+
value += decoded;
|
|
2010
|
+
fragments++;
|
|
2011
|
+
cursor++;
|
|
2012
|
+
}
|
|
2013
|
+
return fragments > 0 ? value : undefined;
|
|
2014
|
+
}
|
|
2015
|
+
function redactJsExpressionSource(source) {
|
|
2016
|
+
const leading = /^[ \t]*/.exec(source)?.[0] ?? "";
|
|
2017
|
+
const trailing = /[ \t]*$/.exec(source)?.[0] ?? "";
|
|
2018
|
+
const trimmed = source.trim();
|
|
2019
|
+
const single = /^(?:"(?:\\.|[^"\\\r\n])*"|'(?:\\.|[^'\\\r\n])*')$/.test(trimmed);
|
|
2020
|
+
const quote = single ? trimmed[0] : "";
|
|
2021
|
+
const lineBreaks = source.match(/\r\n|\n/g) ?? [];
|
|
2022
|
+
return `${leading}${quote}${REDACTION_MARKER}${quote}${lineBreaks.join("")}${trailing}`;
|
|
2023
|
+
}
|
|
2024
|
+
function* iterateAssignmentSecrets(text) {
|
|
2025
|
+
const yaml = analyzeYamlStructuralSecrets(text);
|
|
2026
|
+
if (yaml.recognized && !yaml.exhausted) {
|
|
2027
|
+
yield* yaml.matches;
|
|
2028
|
+
}
|
|
2029
|
+
const js = analyzeJsStructuralSecrets(text, true);
|
|
2030
|
+
if (!js.exhausted) {
|
|
2031
|
+
yield* js.matches;
|
|
2032
|
+
}
|
|
2033
|
+
const commentMasked = commentMaskedFor(text);
|
|
2034
|
+
const nextSemicolon = nextSemicolonFor(commentMasked);
|
|
2035
|
+
chargeWork(2 * text.length);
|
|
2036
|
+
let line = 1;
|
|
2037
|
+
let lineCursor = 0;
|
|
2038
|
+
const keyPattern = new RegExp(ASSIGNMENT_KEY_PATTERN.source, ASSIGNMENT_KEY_PATTERN.flags);
|
|
2039
|
+
let match;
|
|
2040
|
+
while ((match = keyPattern.exec(commentMasked)) != null) {
|
|
2041
|
+
chargeWork(16);
|
|
2042
|
+
const matchStart = match.index;
|
|
2043
|
+
for (let index = lineCursor; index < matchStart; index++) {
|
|
2044
|
+
if (text[index] === "\n") {
|
|
2045
|
+
line++;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
lineCursor = matchStart;
|
|
2049
|
+
const bracketKey = match[5];
|
|
2050
|
+
const memberChain = match[6];
|
|
2051
|
+
const key = match[2] ?? bracketKey ?? memberChain?.split(/\s*(?:\.|::)\s*/).at(-1);
|
|
2052
|
+
if (key == null || !SENSITIVE_ASSIGNMENT_KEY_PATTERN.test(key)) {
|
|
2053
|
+
continue;
|
|
2054
|
+
}
|
|
2055
|
+
const quotedKey = match[1] != null;
|
|
2056
|
+
const keyOffset = match[0].lastIndexOf(key);
|
|
2057
|
+
const keyStart = matchStart + (quotedKey ? 1 : keyOffset);
|
|
2058
|
+
const keyEnd = keyStart + key.length;
|
|
2059
|
+
let cursor = matchStart + match[0].length;
|
|
2060
|
+
while (commentMasked[cursor] === " " || commentMasked[cursor] === "\t") {
|
|
2061
|
+
cursor++;
|
|
2062
|
+
}
|
|
2063
|
+
if (commentMasked[cursor] !== ":" && commentMasked[cursor] !== "=") {
|
|
2064
|
+
continue;
|
|
2065
|
+
}
|
|
2066
|
+
const delimiter = commentMasked[cursor];
|
|
2067
|
+
if (delimiter === "=" && !js.exhausted) {
|
|
2068
|
+
continue;
|
|
2069
|
+
}
|
|
2070
|
+
if (delimiter === ":" && yaml.recognized && !yaml.exhausted) {
|
|
2071
|
+
continue;
|
|
2072
|
+
}
|
|
2073
|
+
if (delimiter === ":") {
|
|
2074
|
+
const declarationStart = text.lastIndexOf("\n", matchStart - 1) + 1;
|
|
2075
|
+
const declarationEnd = lineEndIndex(text, cursor);
|
|
2076
|
+
const typeTerminator = cursor + 1 <= commentMasked.length
|
|
2077
|
+
? nextSemicolon[cursor + 1] ?? -1
|
|
2078
|
+
: -1;
|
|
2079
|
+
const typeOnlyDeclaration = typeTerminator >= 0 &&
|
|
2080
|
+
typeTerminator <= declarationEnd
|
|
2081
|
+
? commentMasked.slice(cursor + 1, typeTerminator).trim()
|
|
2082
|
+
: "";
|
|
2083
|
+
if (typeOnlyDeclaration.length > 0 &&
|
|
2084
|
+
isInsideBoundedTypeBodyAt(commentMasked, 0, keyStart) &&
|
|
2085
|
+
looksLikeTsTypeExpression(typeOnlyDeclaration, true)) {
|
|
2086
|
+
continue;
|
|
2087
|
+
}
|
|
2088
|
+
const assignmentEquals = findTsFieldAssignmentEquals(commentMasked, cursor + 1, declarationEnd);
|
|
2089
|
+
if (assignmentEquals != null) {
|
|
2090
|
+
const typedField = findBoundedTypedField(commentMasked, declarationStart, assignmentEquals);
|
|
2091
|
+
if (typedField != null) {
|
|
2092
|
+
continue;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
cursor++;
|
|
2097
|
+
while (commentMasked[cursor] === " " || commentMasked[cursor] === "\t") {
|
|
2098
|
+
cursor++;
|
|
2099
|
+
}
|
|
2100
|
+
const blockIndicator = delimiter === ":"
|
|
2101
|
+
? new RegExp(`^(${YAML_BLOCK_HEADER_PATTERN_SOURCE})(?=[ \\t]*(?:#|\\r?$))`, "m").exec(text.slice(cursor))
|
|
2102
|
+
: null;
|
|
2103
|
+
if (blockIndicator != null) {
|
|
2104
|
+
const block = parseYamlBlockScalar(text, matchStart, cursor, blockIndicator[1] ?? "");
|
|
2105
|
+
if (block == null) {
|
|
2106
|
+
continue;
|
|
2107
|
+
}
|
|
2108
|
+
keyPattern.lastIndex = block.valueEnd;
|
|
2109
|
+
const value = text.slice(block.valueStart, block.valueEnd);
|
|
2110
|
+
yield {
|
|
2111
|
+
keyStart,
|
|
2112
|
+
keyEnd,
|
|
2113
|
+
valueStart: block.valueStart,
|
|
2114
|
+
valueEnd: block.valueEnd,
|
|
2115
|
+
line,
|
|
2116
|
+
confidence: isHighConfidenceAssignedSecret(value.trim()) ? "high" : "none",
|
|
2117
|
+
replacement: redactYamlBlockPayload(value),
|
|
2118
|
+
};
|
|
2119
|
+
continue;
|
|
2120
|
+
}
|
|
2121
|
+
const quote = text[cursor] === '"' || text[cursor] === "'" ? text[cursor] : undefined;
|
|
2122
|
+
let valueStart = quote == null ? cursor : cursor + 1;
|
|
2123
|
+
let valueEnd = valueStart;
|
|
2124
|
+
if (quote == null) {
|
|
2125
|
+
if (delimiter === ":") {
|
|
2126
|
+
valueEnd = logicalYamlValueEnd(text, valueStart);
|
|
2127
|
+
}
|
|
2128
|
+
else {
|
|
2129
|
+
while (valueEnd < text.length && !/[\s;,#]/.test(text[valueEnd] ?? "")) {
|
|
2130
|
+
valueEnd++;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
else {
|
|
2135
|
+
let escaped = false;
|
|
2136
|
+
while (valueEnd < text.length) {
|
|
2137
|
+
const character = text[valueEnd];
|
|
2138
|
+
if (character === "\r" || character === "\n") {
|
|
2139
|
+
break;
|
|
2140
|
+
}
|
|
2141
|
+
if (!escaped && character === quote) {
|
|
2142
|
+
break;
|
|
2143
|
+
}
|
|
2144
|
+
if (!escaped && character === "\\") {
|
|
2145
|
+
escaped = true;
|
|
2146
|
+
}
|
|
2147
|
+
else {
|
|
2148
|
+
escaped = false;
|
|
2149
|
+
}
|
|
2150
|
+
valueEnd++;
|
|
2151
|
+
}
|
|
2152
|
+
if (text[valueEnd] !== quote) {
|
|
2153
|
+
continue;
|
|
2154
|
+
}
|
|
2155
|
+
const tailStart = skipHorizontalWhitespace(text, valueEnd + 1);
|
|
2156
|
+
if (text[tailStart] === "+") {
|
|
2157
|
+
valueStart = cursor;
|
|
2158
|
+
const logicalSpan = residualJsValueSpan(text, commentMaskedFor(text), cursor);
|
|
2159
|
+
valueEnd = cursor + logicalSpan.content.length;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
keyPattern.lastIndex =
|
|
2163
|
+
valueEnd + (quote != null && valueStart !== cursor ? 1 : 0);
|
|
2164
|
+
const value = text.slice(valueStart, valueEnd);
|
|
2165
|
+
const confidence = (quote != null || !looksLikeCodeExpression(value)) &&
|
|
2166
|
+
isHighConfidenceAssignedSecret(value)
|
|
2167
|
+
? "high"
|
|
2168
|
+
: "none";
|
|
2169
|
+
yield {
|
|
2170
|
+
keyStart,
|
|
2171
|
+
keyEnd,
|
|
2172
|
+
valueStart,
|
|
2173
|
+
valueEnd,
|
|
2174
|
+
line,
|
|
2175
|
+
confidence,
|
|
2176
|
+
...(/\r|\n/.test(value)
|
|
2177
|
+
? { replacement: redactYamlBlockPayload(value) }
|
|
2178
|
+
: {}),
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
function logicalYamlValueEnd(text, valueStart) {
|
|
2183
|
+
let end = valueStart;
|
|
2184
|
+
const delimiters = [];
|
|
2185
|
+
let quote;
|
|
2186
|
+
let escaped = false;
|
|
2187
|
+
while (end < text.length) {
|
|
2188
|
+
const character = text[end] ?? "";
|
|
2189
|
+
if (quote != null) {
|
|
2190
|
+
if (!escaped && character === quote) {
|
|
2191
|
+
quote = undefined;
|
|
2192
|
+
}
|
|
2193
|
+
escaped = !escaped && character === "\\";
|
|
2194
|
+
end++;
|
|
2195
|
+
continue;
|
|
2196
|
+
}
|
|
2197
|
+
if (character === '"' || character === "'" || character === "`") {
|
|
2198
|
+
quote = character;
|
|
2199
|
+
escaped = false;
|
|
2200
|
+
end++;
|
|
2201
|
+
continue;
|
|
2202
|
+
}
|
|
2203
|
+
if (character === "/" && text[end + 1] === "*") {
|
|
2204
|
+
if (delimiters.length === 0) {
|
|
2205
|
+
break;
|
|
2206
|
+
}
|
|
2207
|
+
const commentEnd = text.indexOf("*/", end + 2);
|
|
2208
|
+
if (commentEnd < 0) {
|
|
2209
|
+
break;
|
|
2210
|
+
}
|
|
2211
|
+
end = commentEnd + 2;
|
|
2212
|
+
continue;
|
|
2213
|
+
}
|
|
2214
|
+
if (character === "(" || character === "[" || character === "{") {
|
|
2215
|
+
delimiters.push(character);
|
|
2216
|
+
end++;
|
|
2217
|
+
continue;
|
|
2218
|
+
}
|
|
2219
|
+
if (character === ")" || character === "]" || character === "}") {
|
|
2220
|
+
const opener = character === ")" ? "(" : character === "]" ? "[" : "{";
|
|
2221
|
+
if (delimiters.at(-1) !== opener) {
|
|
2222
|
+
break;
|
|
2223
|
+
}
|
|
2224
|
+
delimiters.pop();
|
|
2225
|
+
end++;
|
|
2226
|
+
continue;
|
|
2227
|
+
}
|
|
2228
|
+
if (delimiters.length === 0 && (character === "\r" || character === "\n" || character === ";" || character === ",")) {
|
|
2229
|
+
break;
|
|
2230
|
+
}
|
|
2231
|
+
if (delimiters.length === 0 &&
|
|
2232
|
+
((character === "#" &&
|
|
2233
|
+
(end === valueStart || /[ \t]/.test(text[end - 1] ?? ""))))) {
|
|
2234
|
+
break;
|
|
2235
|
+
}
|
|
2236
|
+
end++;
|
|
2237
|
+
}
|
|
2238
|
+
while (end > valueStart && /[ \t]/.test(text[end - 1] ?? "")) {
|
|
2239
|
+
end--;
|
|
2240
|
+
}
|
|
2241
|
+
return end;
|
|
2242
|
+
}
|
|
2243
|
+
function skipHorizontalWhitespace(text, start) {
|
|
2244
|
+
let cursor = start;
|
|
2245
|
+
while (text[cursor] === " " || text[cursor] === "\t") {
|
|
2246
|
+
cursor++;
|
|
2247
|
+
}
|
|
2248
|
+
return cursor;
|
|
2249
|
+
}
|
|
2250
|
+
function parseYamlBlockScalar(text, keyStart, valueCursor, indicator) {
|
|
2251
|
+
const headerEnd = lineEndIndex(text, valueCursor + indicator.length);
|
|
2252
|
+
const newlineLength = text.startsWith("\r\n", headerEnd) ? 2 : text[headerEnd] === "\n" ? 1 : 0;
|
|
2253
|
+
if (newlineLength === 0) {
|
|
2254
|
+
return undefined;
|
|
2255
|
+
}
|
|
2256
|
+
const lineStart = text.lastIndexOf("\n", keyStart - 1) + 1;
|
|
2257
|
+
const keyIndent = leadingIndentWidth(text.slice(lineStart, keyStart));
|
|
2258
|
+
const valueStart = headerEnd + newlineLength;
|
|
2259
|
+
let cursor = valueStart;
|
|
2260
|
+
let valueEnd = valueStart;
|
|
2261
|
+
while (cursor < text.length) {
|
|
2262
|
+
const end = lineEndIndex(text, cursor);
|
|
2263
|
+
const lineText = text.slice(cursor, end);
|
|
2264
|
+
if (lineText.trim().length > 0 && leadingIndentWidth(lineText) <= keyIndent) {
|
|
2265
|
+
break;
|
|
2266
|
+
}
|
|
2267
|
+
const lineBreakLength = text.startsWith("\r\n", end) ? 2 : text[end] === "\n" ? 1 : 0;
|
|
2268
|
+
valueEnd = end + lineBreakLength;
|
|
2269
|
+
cursor = valueEnd;
|
|
2270
|
+
if (lineBreakLength === 0) {
|
|
2271
|
+
break;
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
return { valueStart, valueEnd };
|
|
2275
|
+
}
|
|
2276
|
+
function redactYamlBlockPayload(value) {
|
|
2277
|
+
return value.replace(/([^\r\n]*)(\r\n|\n|$)/g, (whole, line, newline) => {
|
|
2278
|
+
if (whole.length === 0) {
|
|
2279
|
+
return whole;
|
|
2280
|
+
}
|
|
2281
|
+
const indent = /^[ \t]*/.exec(line)?.[0] ?? "";
|
|
2282
|
+
return line.trim().length === 0 ? `${line}${newline}` : `${indent}${REDACTION_MARKER}${newline}`;
|
|
2283
|
+
});
|
|
2284
|
+
}
|
|
2285
|
+
function lineEndIndex(text, start) {
|
|
2286
|
+
const newline = text.indexOf("\n", start);
|
|
2287
|
+
if (newline < 0) {
|
|
2288
|
+
return text.length;
|
|
2289
|
+
}
|
|
2290
|
+
return newline > 0 && text[newline - 1] === "\r" ? newline - 1 : newline;
|
|
2291
|
+
}
|
|
2292
|
+
function leadingIndentWidth(value) {
|
|
2293
|
+
return /^[ \t]*/.exec(value)?.[0].length ?? 0;
|
|
2294
|
+
}
|
|
2295
|
+
function residualJsValueSpan(text, commentMasked, valueStart) {
|
|
2296
|
+
let cursor = valueStart;
|
|
2297
|
+
let quote;
|
|
2298
|
+
let escaped = false;
|
|
2299
|
+
let nesting = 0;
|
|
2300
|
+
let lineCount = 0;
|
|
2301
|
+
let valueSeenOnLine = false;
|
|
2302
|
+
while (cursor < text.length && cursor - valueStart < 32_768 && lineCount < 64) {
|
|
2303
|
+
const character = commentMasked[cursor] ?? "";
|
|
2304
|
+
if (quote != null) {
|
|
2305
|
+
if (!escaped && character === quote) {
|
|
2306
|
+
quote = undefined;
|
|
2307
|
+
}
|
|
2308
|
+
escaped = !escaped && character === "\\";
|
|
2309
|
+
if (character !== "\r" && character !== "\n" && !/\s/.test(character)) {
|
|
2310
|
+
valueSeenOnLine = true;
|
|
2311
|
+
}
|
|
2312
|
+
cursor++;
|
|
2313
|
+
continue;
|
|
2314
|
+
}
|
|
2315
|
+
if (character === '"' || character === "'" || character === "`") {
|
|
2316
|
+
quote = character;
|
|
2317
|
+
valueSeenOnLine = true;
|
|
2318
|
+
cursor++;
|
|
2319
|
+
continue;
|
|
2320
|
+
}
|
|
2321
|
+
if (character === "(" || character === "[" || character === "{") {
|
|
2322
|
+
nesting++;
|
|
2323
|
+
}
|
|
2324
|
+
else if (character === ")" || character === "]" || character === "}") {
|
|
2325
|
+
nesting = Math.max(0, nesting - 1);
|
|
2326
|
+
}
|
|
2327
|
+
else if ((character === ";" || character === ",") && nesting === 0) {
|
|
2328
|
+
break;
|
|
2329
|
+
}
|
|
2330
|
+
if (character === "\r" || character === "\n") {
|
|
2331
|
+
const before = commentMasked.slice(valueStart, cursor).trimEnd();
|
|
2332
|
+
const afterNewline = commentMasked
|
|
2333
|
+
.slice(character === "\r" && commentMasked[cursor + 1] === "\n" ? cursor + 2 : cursor + 1)
|
|
2334
|
+
.trimStart();
|
|
2335
|
+
const continuation = before.endsWith("+") ||
|
|
2336
|
+
before.endsWith("=") ||
|
|
2337
|
+
afterNewline.startsWith("+") ||
|
|
2338
|
+
nesting > 0 ||
|
|
2339
|
+
quote === "`";
|
|
2340
|
+
if (valueSeenOnLine && !continuation) {
|
|
2341
|
+
break;
|
|
2342
|
+
}
|
|
2343
|
+
if (character === "\r" && commentMasked[cursor + 1] === "\n") {
|
|
2344
|
+
cursor++;
|
|
2345
|
+
}
|
|
2346
|
+
lineCount++;
|
|
2347
|
+
valueSeenOnLine = false;
|
|
2348
|
+
cursor++;
|
|
2349
|
+
continue;
|
|
2350
|
+
}
|
|
2351
|
+
if (!/\s/.test(character)) {
|
|
2352
|
+
valueSeenOnLine = true;
|
|
2353
|
+
}
|
|
2354
|
+
cursor++;
|
|
2355
|
+
}
|
|
2356
|
+
return {
|
|
2357
|
+
content: text.slice(valueStart, cursor),
|
|
2358
|
+
exhausted: cursor < text.length &&
|
|
2359
|
+
(cursor - valueStart >= 32_768 || lineCount >= 64),
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
function maskJsCommentTrivia(text) {
|
|
2363
|
+
const output = text.split("");
|
|
2364
|
+
let quote;
|
|
2365
|
+
let escaped = false;
|
|
2366
|
+
for (let index = 0; index < text.length; index++) {
|
|
2367
|
+
const character = text[index] ?? "";
|
|
2368
|
+
if (quote != null) {
|
|
2369
|
+
if (!escaped && character === quote) {
|
|
2370
|
+
quote = undefined;
|
|
2371
|
+
}
|
|
2372
|
+
escaped = !escaped && character === "\\";
|
|
2373
|
+
continue;
|
|
2374
|
+
}
|
|
2375
|
+
if (character === '"' || character === "'" || character === "`") {
|
|
2376
|
+
quote = character;
|
|
2377
|
+
continue;
|
|
2378
|
+
}
|
|
2379
|
+
if (character === "/" && text[index + 1] === "*") {
|
|
2380
|
+
output[index] = " ";
|
|
2381
|
+
output[index + 1] = " ";
|
|
2382
|
+
index += 2;
|
|
2383
|
+
while (index < text.length) {
|
|
2384
|
+
if (text[index] === "*" && text[index + 1] === "/") {
|
|
2385
|
+
output[index] = " ";
|
|
2386
|
+
output[index + 1] = " ";
|
|
2387
|
+
index++;
|
|
2388
|
+
break;
|
|
2389
|
+
}
|
|
2390
|
+
if (text[index] !== "\r" && text[index] !== "\n") {
|
|
2391
|
+
output[index] = " ";
|
|
2392
|
+
}
|
|
2393
|
+
index++;
|
|
2394
|
+
}
|
|
2395
|
+
continue;
|
|
2396
|
+
}
|
|
2397
|
+
if (character === "/" && text[index + 1] === "/") {
|
|
2398
|
+
output[index] = " ";
|
|
2399
|
+
output[index + 1] = " ";
|
|
2400
|
+
index += 2;
|
|
2401
|
+
while (index < text.length && text[index] !== "\r" && text[index] !== "\n") {
|
|
2402
|
+
output[index] = " ";
|
|
2403
|
+
index++;
|
|
2404
|
+
}
|
|
2405
|
+
index--;
|
|
2406
|
+
}
|
|
196
2407
|
}
|
|
197
|
-
return
|
|
2408
|
+
return output.join("");
|
|
198
2409
|
}
|
|
199
2410
|
function shannonEntropy(value) {
|
|
200
2411
|
const counts = new Map();
|