@tangle-network/agent-eval 0.187.1 → 0.188.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 +63 -0
- package/dist/adapters/http.d.ts +2 -2
- package/dist/{agent-profile-uAIQK3Nr.d.ts → agent-profile-CfbuZk8b.d.ts} +4 -4
- package/dist/{agent-profile-uAIQK3Nr.d.ts.map → agent-profile-CfbuZk8b.d.ts.map} +1 -1
- package/dist/analyst/index.d.ts +12 -12
- package/dist/analyst/index.d.ts.map +1 -1
- package/dist/analyst/index.js +6 -6
- package/dist/{analyze-runs-DapYZz02.d.ts → analyze-runs-CLz5O01x.d.ts} +5 -5
- package/dist/{analyze-runs-DapYZz02.d.ts.map → analyze-runs-CLz5O01x.d.ts.map} +1 -1
- package/dist/{benchmark-command-CERRH0E6.js → benchmark-command-CdUGku3W.js} +12 -12
- package/dist/benchmark-command-CdUGku3W.js.map +1 -0
- package/dist/benchmarks/index.d.ts +4 -4
- package/dist/benchmarks/index.js +3 -3
- package/dist/builder-eval/index.d.ts +3 -3
- package/dist/builder-eval/index.js +1 -1
- package/dist/campaign/index.d.ts +5 -5
- package/dist/campaign/index.js +5 -5
- package/dist/{campaign-DlxdTOvw.js → campaign-DZuysBbS.js} +6 -6
- package/dist/{campaign-DlxdTOvw.js.map → campaign-DZuysBbS.js.map} +1 -1
- package/dist/{chat-client-DqPqWzVV.js → chat-client-C4dg7Ng_.js} +2 -2
- package/dist/{chat-client-DqPqWzVV.js.map → chat-client-C4dg7Ng_.js.map} +1 -1
- package/dist/{chat-json-call-C-DnDxEy.js → chat-json-call-JVGbfx95.js} +2 -2
- package/dist/{chat-json-call-C-DnDxEy.js.map → chat-json-call-JVGbfx95.js.map} +1 -1
- package/dist/cli.js +4 -4
- package/dist/{client-DCc7a8ZC.d.ts → client-CFj9dfcn.d.ts} +4 -4
- package/dist/{client-DCc7a8ZC.d.ts.map → client-CFj9dfcn.d.ts.map} +1 -1
- package/dist/contract/index.d.ts +11 -11
- package/dist/contract/index.js +6 -6
- package/dist/{counterfactual-CLgrwhkY.d.ts → counterfactual-CPaWN99-.d.ts} +4 -4
- package/dist/{counterfactual-CLgrwhkY.d.ts.map → counterfactual-CPaWN99-.d.ts.map} +1 -1
- package/dist/{counterfactual-Bjq1mlUu.js → counterfactual-DtMWvn7D.js} +2 -2
- package/dist/{counterfactual-Bjq1mlUu.js.map → counterfactual-DtMWvn7D.js.map} +1 -1
- package/dist/{default-registry-DMxeb4GD.d.ts → default-registry-BLsGfnoO.d.ts} +6 -6
- package/dist/{default-registry-DMxeb4GD.d.ts.map → default-registry-BLsGfnoO.d.ts.map} +1 -1
- package/dist/{default-registry-Ca2IWH1X.js → default-registry-BtZ_t-9b.js} +2 -2
- package/dist/{default-registry-Ca2IWH1X.js.map → default-registry-BtZ_t-9b.js.map} +1 -1
- package/dist/{define-agent-eval-C0EqJ9Gz.js → define-agent-eval-ChtddasD.js} +3 -3
- package/dist/{define-agent-eval-C0EqJ9Gz.js.map → define-agent-eval-ChtddasD.js.map} +1 -1
- package/dist/{define-agent-eval-CGBwPPaq.d.ts → define-agent-eval-CsicmDxt.d.ts} +7 -7
- package/dist/{define-agent-eval-CGBwPPaq.d.ts.map → define-agent-eval-CsicmDxt.d.ts.map} +1 -1
- package/dist/diagnosis/index.d.ts +38 -96
- package/dist/diagnosis/index.d.ts.map +1 -1
- package/dist/diagnosis/index.js +83 -134
- package/dist/diagnosis/index.js.map +1 -1
- package/dist/{dspy-rlm-engine-BE9lCzzL.js → dspy-rlm-engine-sPX7B5ac.js} +3 -3
- package/dist/{dspy-rlm-engine-BE9lCzzL.js.map → dspy-rlm-engine-sPX7B5ac.js.map} +1 -1
- package/dist/emitter-DbMXUIVP.js +334 -0
- package/dist/emitter-DbMXUIVP.js.map +1 -0
- package/dist/{emitter-Cs0egaFd.d.ts → emitter-HcCmFQl_.d.ts} +37 -6
- package/dist/emitter-HcCmFQl_.d.ts.map +1 -0
- package/dist/{engine-DNlKhIm0.d.ts → engine-Gw2n1irt.d.ts} +15 -5
- package/dist/engine-Gw2n1irt.d.ts.map +1 -0
- package/dist/{eval-campaign-XFHEpN86.js → eval-campaign-CeotQ1ZR.js} +7 -4
- package/dist/{eval-campaign-XFHEpN86.js.map → eval-campaign-CeotQ1ZR.js.map} +1 -1
- package/dist/evaluation.d.ts +2 -2
- package/dist/{exact-types-BEHN7tnq.d.ts → exact-types-CrhPoDp8.d.ts} +2 -2
- package/dist/{exact-types-BEHN7tnq.d.ts.map → exact-types-CrhPoDp8.d.ts.map} +1 -1
- package/dist/experiment/index.d.ts +57 -3
- package/dist/experiment/index.d.ts.map +1 -1
- package/dist/experiment/index.js +119 -2
- package/dist/experiment/index.js.map +1 -1
- package/dist/{external-optimizer-process-BbhvXTFx.js → external-optimizer-process-D3BLpZyR.js} +2 -2
- package/dist/{external-optimizer-process-BbhvXTFx.js.map → external-optimizer-process-D3BLpZyR.js.map} +1 -1
- package/dist/{external-optimizer-subprocess-DMgmqTB6.js → external-optimizer-subprocess-BrfsgL92.js} +2 -2
- package/dist/{external-optimizer-subprocess-DMgmqTB6.js.map → external-optimizer-subprocess-BrfsgL92.js.map} +1 -1
- package/dist/{failure-cluster-OldNRoAt.d.ts → failure-cluster-CbgEDcFJ.d.ts} +3 -3
- package/dist/{failure-cluster-OldNRoAt.d.ts.map → failure-cluster-CbgEDcFJ.d.ts.map} +1 -1
- package/dist/failure-taxonomy-CjabTATj.js +538 -0
- package/dist/failure-taxonomy-CjabTATj.js.map +1 -0
- package/dist/{feedback-trajectory-CytTR7u4.d.ts → feedback-trajectory-BJi4t3Vf.d.ts} +6 -6
- package/dist/{feedback-trajectory-CytTR7u4.d.ts.map → feedback-trajectory-BJi4t3Vf.d.ts.map} +1 -1
- package/dist/hosted/index.d.ts +2 -2
- package/dist/{index-CKZa_rnt.d.ts → index-B-s8qDRJ.d.ts} +2 -2
- package/dist/{index-CKZa_rnt.d.ts.map → index-B-s8qDRJ.d.ts.map} +1 -1
- package/dist/{index-YZrbnBSV.d.ts → index-BD1oSbHr.d.ts} +11 -11
- package/dist/{index-YZrbnBSV.d.ts.map → index-BD1oSbHr.d.ts.map} +1 -1
- package/dist/{index-vYNHCV2G.d.ts → index-Ca0_IjFu.d.ts} +7 -7
- package/dist/{index-vYNHCV2G.d.ts.map → index-Ca0_IjFu.d.ts.map} +1 -1
- package/dist/{index-BRsrfSHT.d.ts → index-eTTnPe3r.d.ts} +9 -9
- package/dist/{index-BRsrfSHT.d.ts.map → index-eTTnPe3r.d.ts.map} +1 -1
- package/dist/index.d.ts +383 -68
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1482 -207
- package/dist/index.js.map +1 -1
- package/dist/{insight-report-DpOo-3iL.d.ts → insight-report-DeE1solA.d.ts} +4 -4
- package/dist/{insight-report-DpOo-3iL.d.ts.map → insight-report-DeE1solA.d.ts.map} +1 -1
- package/dist/{integrity-Cy9WHAtb.js → integrity-BFP4kjGj.js} +6 -1
- package/dist/integrity-BFP4kjGj.js.map +1 -0
- package/dist/{integrity-rGOfSUle.d.ts → integrity-CaeNeusS.d.ts} +4 -4
- package/dist/integrity-CaeNeusS.d.ts.map +1 -0
- package/dist/jev.d.ts +2 -2
- package/dist/{kind-factory-V79Hw0AU.js → kind-factory-g1ROw67A.js} +15 -4
- package/dist/{kind-factory-V79Hw0AU.js.map → kind-factory-g1ROw67A.js.map} +1 -1
- package/dist/{llm-client-DDhZ29Vm.js → llm-client-DF9bPNYA.js} +2 -2
- package/dist/{llm-client-DDhZ29Vm.js.map → llm-client-DF9bPNYA.js.map} +1 -1
- package/dist/{llm-judge-BVso5x-u.js → llm-judge-DF18J3CR.js} +9 -9
- package/dist/llm-judge-DF18J3CR.js.map +1 -0
- package/dist/{matrix-CPKsRY8Y.d.ts → matrix-BvA1WUpj.d.ts} +2 -2
- package/dist/{matrix-CPKsRY8Y.d.ts.map → matrix-BvA1WUpj.d.ts.map} +1 -1
- package/dist/meta-eval/index.d.ts +4 -4
- package/dist/{mint-BI14xehJ.d.ts → mint-CJFSYBIU.d.ts} +3 -3
- package/dist/{mint-BI14xehJ.d.ts.map → mint-CJFSYBIU.d.ts.map} +1 -1
- package/dist/multishot/golden/index.d.ts +1 -1
- package/dist/multishot/index.d.ts +2 -2
- package/dist/openapi.json +1 -1
- package/dist/pipelines/index.d.ts +67 -12
- package/dist/pipelines/index.d.ts.map +1 -1
- package/dist/pipelines/index.js +178 -41
- package/dist/pipelines/index.js.map +1 -1
- package/dist/{prime-protocol-CDBcL9NM.d.ts → prime-protocol-CF8Z43Fj.d.ts} +2 -2
- package/dist/{prime-protocol-CDBcL9NM.d.ts.map → prime-protocol-CF8Z43Fj.d.ts.map} +1 -1
- package/dist/{produced-state-Cn9ECr4v.js → produced-state-xsMMjYVc.js} +3 -3
- package/dist/{produced-state-Cn9ECr4v.js.map → produced-state-xsMMjYVc.js.map} +1 -1
- package/dist/{query-D6W6MaGx.d.ts → query-DBUQhehH.d.ts} +3 -3
- package/dist/{query-D6W6MaGx.d.ts.map → query-DBUQhehH.d.ts.map} +1 -1
- package/dist/random-DGEyr3zG.d.ts +83 -0
- package/dist/random-DGEyr3zG.d.ts.map +1 -0
- package/dist/{raw-provider-sink-BQd7mzyT.js → raw-provider-sink-C96CcqeG.js} +19 -56
- package/dist/raw-provider-sink-C96CcqeG.js.map +1 -0
- package/dist/redact-DPoH1-OW.d.ts +158 -0
- package/dist/redact-DPoH1-OW.d.ts.map +1 -0
- package/dist/redact-DxwlUAvh.js +824 -0
- package/dist/redact-DxwlUAvh.js.map +1 -0
- package/dist/{registry-D5Ly3GRV.d.ts → registry-BM2Kdk8T.d.ts} +4 -4
- package/dist/{registry-D5Ly3GRV.d.ts.map → registry-BM2Kdk8T.d.ts.map} +1 -1
- package/dist/{release-confidence-CSp3MsU4.d.ts → release-confidence-CG4Z9QLB.d.ts} +4 -4
- package/dist/{release-confidence-CSp3MsU4.d.ts.map → release-confidence-CG4Z9QLB.d.ts.map} +1 -1
- package/dist/{report-command-CSTjzFGY.js → report-command-DKJ_yAIj.js} +21 -12
- package/dist/{report-command-CSTjzFGY.js.map → report-command-DKJ_yAIj.js.map} +1 -1
- package/dist/reporting.d.ts +3 -3
- package/dist/{researcher-D7E92STT.d.ts → researcher-Bjcd_8T_.d.ts} +7 -7
- package/dist/{researcher-D7E92STT.d.ts.map → researcher-Bjcd_8T_.d.ts.map} +1 -1
- package/dist/{reward-hacking-BIQ2BWLG.d.ts → reward-hacking-DrKXJtgH.d.ts} +2 -2
- package/dist/{reward-hacking-BIQ2BWLG.d.ts.map → reward-hacking-DrKXJtgH.d.ts.map} +1 -1
- package/dist/rl.d.ts +8 -8
- package/dist/rl.js +1 -1
- package/dist/rollout/index.d.ts +2 -2
- package/dist/{rubric-predictive-validity-CTU8y8Cv.d.ts → rubric-predictive-validity-B14yBAmM.d.ts} +2 -2
- package/dist/{rubric-predictive-validity-CTU8y8Cv.d.ts.map → rubric-predictive-validity-B14yBAmM.d.ts.map} +1 -1
- package/dist/{run-record-D9wS35fK.d.ts → run-record-xEULpgML.d.ts} +2 -2
- package/dist/{run-record-D9wS35fK.d.ts.map → run-record-xEULpgML.d.ts.map} +1 -1
- package/dist/{schema-CR5cpjQ3.d.ts → schema-2g7trUwH.d.ts} +17 -2
- package/dist/schema-2g7trUwH.d.ts.map +1 -0
- package/dist/schema-CSf6qWgZ.js.map +1 -1
- package/dist/{semantic-concept-judge-Cnt0TWp0.js → semantic-concept-judge-70LxrCSL.js} +5 -5
- package/dist/{semantic-concept-judge-Cnt0TWp0.js.map → semantic-concept-judge-70LxrCSL.js.map} +1 -1
- package/dist/{series-convergence-BnMs_uAr.d.ts → series-convergence-DY7ha0kx.d.ts} +2 -2
- package/dist/{series-convergence-BnMs_uAr.d.ts.map → series-convergence-DY7ha0kx.d.ts.map} +1 -1
- package/dist/{server-DmpcFiVK.js → server-C8XvqCg8.js} +2 -2
- package/dist/{server-DmpcFiVK.js.map → server-C8XvqCg8.js.map} +1 -1
- package/dist/{skillopt-optimization-method-Cc9SVLqi.js → skillopt-optimization-method-C60h0Got.js} +5 -5
- package/dist/{skillopt-optimization-method-Cc9SVLqi.js.map → skillopt-optimization-method-C60h0Got.js.map} +1 -1
- package/dist/spans-W0o8pbEg.d.ts +60 -0
- package/dist/spans-W0o8pbEg.d.ts.map +1 -0
- package/dist/{statistical-heldout-DTmhTW8P.d.ts → statistical-heldout-CPv-y6sK.d.ts} +3 -3
- package/dist/{statistical-heldout-DTmhTW8P.d.ts.map → statistical-heldout-CPv-y6sK.d.ts.map} +1 -1
- package/dist/{store-BErPvYBr.d.ts → store-C4avFjIi.d.ts} +2 -2
- package/dist/{store-BErPvYBr.d.ts.map → store-C4avFjIi.d.ts.map} +1 -1
- package/dist/{store-otlp-2gYCy-v0.js → store-otlp-DPgzSQKS.js} +2 -2
- package/dist/{store-otlp-2gYCy-v0.js.map → store-otlp-DPgzSQKS.js.map} +1 -1
- package/dist/{store-tool-spans-B3bX9yqn.d.ts → store-tool-spans-C2lLwDzg.d.ts} +9 -54
- package/dist/store-tool-spans-C2lLwDzg.d.ts.map +1 -0
- package/dist/{store-tool-spans-B1e6Auc2.js → store-tool-spans-ClPxcvh2.js} +4 -4
- package/dist/{store-tool-spans-B1e6Auc2.js.map → store-tool-spans-ClPxcvh2.js.map} +1 -1
- package/dist/storyboard/index.d.ts +1 -1
- package/dist/{summary-report-Iy6kXo8x.d.ts → summary-report-SNvJoAvD.d.ts} +3 -3
- package/dist/{summary-report-Iy6kXo8x.d.ts.map → summary-report-SNvJoAvD.d.ts.map} +1 -1
- package/dist/supervisor-run/index.d.ts.map +1 -1
- package/dist/supervisor-run/index.js +1 -1
- package/dist/{tool-groups-veQYPiTy.d.ts → tool-groups-CCQqAa16.d.ts} +3 -3
- package/dist/tool-groups-CCQqAa16.d.ts.map +1 -0
- package/dist/{tool-waste-nbHPAxei.js → tool-waste-CJDfxH45.js} +3 -537
- package/dist/tool-waste-CJDfxH45.js.map +1 -0
- package/dist/{tool-waste-D23I0zWm.d.ts → tool-waste-CP5DO383.d.ts} +4 -4
- package/dist/{tool-waste-D23I0zWm.d.ts.map → tool-waste-CP5DO383.d.ts.map} +1 -1
- package/dist/trace-repair/index.d.ts +2 -2
- package/dist/traces.d.ts +12 -11
- package/dist/traces.d.ts.map +1 -1
- package/dist/traces.js +8 -8
- package/dist/{trajectory-D7qrNvaN.d.ts → trajectory-Y1fmdlw7.d.ts} +3 -3
- package/dist/{trajectory-D7qrNvaN.d.ts.map → trajectory-Y1fmdlw7.d.ts.map} +1 -1
- package/dist/trajectory-replay/index.d.ts +3 -3
- package/dist/trajectory-replay/index.js +2 -2
- package/dist/{types--18oHwVF.d.ts → types-5ak2PqnJ.d.ts} +3 -3
- package/dist/{types--18oHwVF.d.ts.map → types-5ak2PqnJ.d.ts.map} +1 -1
- package/dist/{types-BTRcIqFr.d.ts → types-B1vDZ694.d.ts} +4 -4
- package/dist/{types-BTRcIqFr.d.ts.map → types-B1vDZ694.d.ts.map} +1 -1
- package/dist/{types-CBbLtr2J.d.ts → types-C0bWoSKC.d.ts} +10 -11
- package/dist/types-C0bWoSKC.d.ts.map +1 -0
- package/dist/{types-BjB2wZM9.d.ts → types-DDp2Mb8Y.d.ts} +3 -3
- package/dist/{types-BjB2wZM9.d.ts.map → types-DDp2Mb8Y.d.ts.map} +1 -1
- package/dist/wire/index.d.ts +3 -3
- package/dist/wire/index.js +1 -1
- package/docs/diagnosis.md +2 -3
- package/docs/eval-surface-map.md +1 -1
- package/docs/experiment.md +8 -1
- package/docs/redaction.md +59 -0
- package/docs/trace-contracts.md +102 -0
- package/docs/verdicts.md +1 -1
- package/package.json +3 -2
- package/dist/benchmark-command-CERRH0E6.js.map +0 -1
- package/dist/emitter-Cs0egaFd.d.ts.map +0 -1
- package/dist/emitter-DeQHiDMm.js +0 -257
- package/dist/emitter-DeQHiDMm.js.map +0 -1
- package/dist/engine-DNlKhIm0.d.ts.map +0 -1
- package/dist/experiment-tracker-BKEumQug.js +0 -280
- package/dist/experiment-tracker-BKEumQug.js.map +0 -1
- package/dist/experiment-tracker-URRGP9ek.d.ts +0 -237
- package/dist/experiment-tracker-URRGP9ek.d.ts.map +0 -1
- package/dist/integrity-Cy9WHAtb.js.map +0 -1
- package/dist/integrity-rGOfSUle.d.ts.map +0 -1
- package/dist/llm-judge-BVso5x-u.js.map +0 -1
- package/dist/raw-provider-sink-BQd7mzyT.js.map +0 -1
- package/dist/redact-7Aq1ukl-.js +0 -107
- package/dist/redact-7Aq1ukl-.js.map +0 -1
- package/dist/schema-CR5cpjQ3.d.ts.map +0 -1
- package/dist/store-tool-spans-B3bX9yqn.d.ts.map +0 -1
- package/dist/tool-groups-veQYPiTy.d.ts.map +0 -1
- package/dist/tool-waste-nbHPAxei.js.map +0 -1
- package/dist/types-CBbLtr2J.d.ts.map +0 -1
|
@@ -0,0 +1,824 @@
|
|
|
1
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
2
|
+
//#region src/trace/redact.ts
|
|
3
|
+
/**
|
|
4
|
+
* The redaction core: the one place that decides what must not leave a
|
|
5
|
+
* process or a machine. Every trace writer, exporter, uploader and share path
|
|
6
|
+
* calls it.
|
|
7
|
+
*
|
|
8
|
+
* Three profiles, each a superset of the one before:
|
|
9
|
+
* - `default` removes credentials, found by key name and by value shape, and
|
|
10
|
+
* personal data (email addresses, card numbers, SSNs, phone numbers).
|
|
11
|
+
* Content is kept. Strings are capped at 1 MiB.
|
|
12
|
+
* - `share` also removes phone, IP and postal-address fields, replaces
|
|
13
|
+
* account, user, tenant, session and request identifiers with pseudonyms
|
|
14
|
+
* that stay consistent within one call, and caps strings at 64 KiB.
|
|
15
|
+
* - `strict` also removes prompt, completion, message, tool payload and other
|
|
16
|
+
* raw-content fields and embedded media, and caps strings at 4 KiB.
|
|
17
|
+
*
|
|
18
|
+
* A string that holds a credential is replaced whole with
|
|
19
|
+
* `[REDACTED:<detector>]`. Replacing only the matched span can leave part of a
|
|
20
|
+
* secret behind when a pattern's boundary is wrong. Personal data inside prose
|
|
21
|
+
* is replaced in place, so the rest of the text stays readable.
|
|
22
|
+
*
|
|
23
|
+
* `assessShareSafety` reads a value without changing it and returns SAFE,
|
|
24
|
+
* SAFE_WITH_WARNINGS, UNSAFE or UNKNOWN. UNKNOWN means the scanner could not
|
|
25
|
+
* read part of the value (a cycle, binary data, nesting past the depth limit);
|
|
26
|
+
* it is never reported as safe. `redactForShare` redacts and then assesses the
|
|
27
|
+
* redacted output, so a detector gap shows up as UNSAFE instead of a leak.
|
|
28
|
+
*
|
|
29
|
+
* Reports and findings carry paths, categories and detector ids, never the
|
|
30
|
+
* values they matched.
|
|
31
|
+
*
|
|
32
|
+
* The key classifier and the credential detector list are adapted from
|
|
33
|
+
* agent-inspect (MIT, commit 3c3cbeda). `scripts/redaction-corpus.json` holds
|
|
34
|
+
* the must-flag and must-not-flag cases, including the copied agent-inspect
|
|
35
|
+
* safety corpus with its MIT notice; `pnpm redaction:corpus` runs this module
|
|
36
|
+
* over every case.
|
|
37
|
+
*/
|
|
38
|
+
const REDACTION_PROFILES = [
|
|
39
|
+
"default",
|
|
40
|
+
"share",
|
|
41
|
+
"strict"
|
|
42
|
+
];
|
|
43
|
+
/**
|
|
44
|
+
* Version of the detector set and profile rules. Stored work that depends on
|
|
45
|
+
* redaction behavior (saved optimizer inputs, per-span redaction stamps) keys
|
|
46
|
+
* on it, so bump it whenever a detector, key list, cap or marker changes.
|
|
47
|
+
*/
|
|
48
|
+
const REDACTION_VERSION = "2.0.0";
|
|
49
|
+
const KIB = 1024;
|
|
50
|
+
const PROFILE_RULES = {
|
|
51
|
+
default: {
|
|
52
|
+
maxStringBytes: 1024 * KIB,
|
|
53
|
+
redactKeys: /* @__PURE__ */ new Set(),
|
|
54
|
+
redactMedia: false,
|
|
55
|
+
severity: {
|
|
56
|
+
credential: "error",
|
|
57
|
+
"personal-data": "warning",
|
|
58
|
+
identifier: void 0,
|
|
59
|
+
"raw-content": "warning",
|
|
60
|
+
media: "warning"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
share: {
|
|
64
|
+
maxStringBytes: 64 * KIB,
|
|
65
|
+
redactKeys: /* @__PURE__ */ new Set(["share-personal", "identifier"]),
|
|
66
|
+
redactMedia: false,
|
|
67
|
+
severity: {
|
|
68
|
+
credential: "error",
|
|
69
|
+
"personal-data": "error",
|
|
70
|
+
identifier: "warning",
|
|
71
|
+
"raw-content": "warning",
|
|
72
|
+
media: "warning"
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
strict: {
|
|
76
|
+
maxStringBytes: 4 * KIB,
|
|
77
|
+
redactKeys: /* @__PURE__ */ new Set([
|
|
78
|
+
"share-personal",
|
|
79
|
+
"identifier",
|
|
80
|
+
"raw-content"
|
|
81
|
+
]),
|
|
82
|
+
redactMedia: true,
|
|
83
|
+
severity: {
|
|
84
|
+
credential: "error",
|
|
85
|
+
"personal-data": "error",
|
|
86
|
+
identifier: "warning",
|
|
87
|
+
"raw-content": "error",
|
|
88
|
+
media: "error"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Canonical snake_case form of a field name, so camelCase, kebab-case, dotted
|
|
94
|
+
* and snake_case spellings match one list: `userPassword`, `user-password` and
|
|
95
|
+
* `user.password` all become `user_password`.
|
|
96
|
+
*/
|
|
97
|
+
function normalizeKey(key) {
|
|
98
|
+
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
99
|
+
}
|
|
100
|
+
/** Counts, limits and model vocabulary that contain the word "token" but are not credentials. */
|
|
101
|
+
const NON_CREDENTIAL_TOKEN_KEYS = /* @__PURE__ */ new Set([
|
|
102
|
+
"tokens",
|
|
103
|
+
"max_tokens",
|
|
104
|
+
"min_tokens",
|
|
105
|
+
"ls_max_tokens",
|
|
106
|
+
"token_count",
|
|
107
|
+
"token_limit",
|
|
108
|
+
"token_budget",
|
|
109
|
+
"input_tokens",
|
|
110
|
+
"output_tokens",
|
|
111
|
+
"total_tokens",
|
|
112
|
+
"cached_tokens",
|
|
113
|
+
"prompt_tokens",
|
|
114
|
+
"completion_tokens",
|
|
115
|
+
"reasoning_tokens",
|
|
116
|
+
"bos_token",
|
|
117
|
+
"eos_token",
|
|
118
|
+
"pad_token",
|
|
119
|
+
"unk_token",
|
|
120
|
+
"sep_token",
|
|
121
|
+
"cls_token",
|
|
122
|
+
"mask_token",
|
|
123
|
+
"stop_token",
|
|
124
|
+
"page_token",
|
|
125
|
+
"next_page_token",
|
|
126
|
+
"prev_page_token",
|
|
127
|
+
"next_token",
|
|
128
|
+
"continuation_token",
|
|
129
|
+
"cursor_token",
|
|
130
|
+
"sync_token"
|
|
131
|
+
]);
|
|
132
|
+
const CREDENTIAL_KEYS = [
|
|
133
|
+
"authorization",
|
|
134
|
+
"auth",
|
|
135
|
+
"cookie",
|
|
136
|
+
"api_key",
|
|
137
|
+
"apikey",
|
|
138
|
+
"api_token",
|
|
139
|
+
"access_key",
|
|
140
|
+
"secret_key",
|
|
141
|
+
"private_key",
|
|
142
|
+
"signing_key",
|
|
143
|
+
"encryption_key",
|
|
144
|
+
"master_key",
|
|
145
|
+
"session_key",
|
|
146
|
+
"client_secret",
|
|
147
|
+
"secret",
|
|
148
|
+
"password",
|
|
149
|
+
"passwd",
|
|
150
|
+
"passphrase",
|
|
151
|
+
"credential",
|
|
152
|
+
"credentials",
|
|
153
|
+
"bearer",
|
|
154
|
+
"dsn",
|
|
155
|
+
"database_url",
|
|
156
|
+
"connection_string",
|
|
157
|
+
"pat"
|
|
158
|
+
];
|
|
159
|
+
/** Email fields are personal data in every profile. */
|
|
160
|
+
const EMAIL_KEYS = ["email", "email_address"];
|
|
161
|
+
/** Personal-data fields removed by the share and strict profiles. */
|
|
162
|
+
const SHARE_PERSONAL_KEYS = [
|
|
163
|
+
"phone",
|
|
164
|
+
"phone_number",
|
|
165
|
+
"mobile",
|
|
166
|
+
"ip",
|
|
167
|
+
"ip_address",
|
|
168
|
+
"remote_addr",
|
|
169
|
+
"street_address",
|
|
170
|
+
"postal_address",
|
|
171
|
+
"home_address",
|
|
172
|
+
"billing_address",
|
|
173
|
+
"shipping_address",
|
|
174
|
+
"mailing_address",
|
|
175
|
+
"date_of_birth",
|
|
176
|
+
"dob",
|
|
177
|
+
"ssn"
|
|
178
|
+
];
|
|
179
|
+
/** Identifiers pseudonymized by the share and strict profiles. */
|
|
180
|
+
const IDENTIFIER_KEYS = [
|
|
181
|
+
"user_id",
|
|
182
|
+
"user_uuid",
|
|
183
|
+
"account_id",
|
|
184
|
+
"account_uuid",
|
|
185
|
+
"customer_id",
|
|
186
|
+
"tenant_id",
|
|
187
|
+
"org_id",
|
|
188
|
+
"organization_id",
|
|
189
|
+
"organisation_id",
|
|
190
|
+
"team_id",
|
|
191
|
+
"enduser_id",
|
|
192
|
+
"actor_id",
|
|
193
|
+
"principal_id",
|
|
194
|
+
"member_id",
|
|
195
|
+
"session_id",
|
|
196
|
+
"request_id",
|
|
197
|
+
"correlation_id",
|
|
198
|
+
"device_id",
|
|
199
|
+
"installation_id"
|
|
200
|
+
];
|
|
201
|
+
/** Fields that carry prompts, model output, tool payloads or other free text. Removed by `strict`. */
|
|
202
|
+
const RAW_CONTENT_KEYS = [
|
|
203
|
+
"prompt",
|
|
204
|
+
"prompts",
|
|
205
|
+
"system_prompt",
|
|
206
|
+
"completion",
|
|
207
|
+
"input",
|
|
208
|
+
"output",
|
|
209
|
+
"message",
|
|
210
|
+
"messages",
|
|
211
|
+
"content",
|
|
212
|
+
"text",
|
|
213
|
+
"thinking",
|
|
214
|
+
"reasoning",
|
|
215
|
+
"transcript",
|
|
216
|
+
"context",
|
|
217
|
+
"document",
|
|
218
|
+
"documents",
|
|
219
|
+
"chunk",
|
|
220
|
+
"chunks",
|
|
221
|
+
"retrieval",
|
|
222
|
+
"query",
|
|
223
|
+
"args",
|
|
224
|
+
"arguments",
|
|
225
|
+
"result",
|
|
226
|
+
"body",
|
|
227
|
+
"command",
|
|
228
|
+
"full_command",
|
|
229
|
+
"input_value",
|
|
230
|
+
"output_value",
|
|
231
|
+
"instructions",
|
|
232
|
+
"system_instructions",
|
|
233
|
+
"stdout",
|
|
234
|
+
"stderr",
|
|
235
|
+
"stacktrace",
|
|
236
|
+
"status_description",
|
|
237
|
+
"input_preview",
|
|
238
|
+
"output_preview",
|
|
239
|
+
"current_task",
|
|
240
|
+
"user_input",
|
|
241
|
+
"request_text"
|
|
242
|
+
];
|
|
243
|
+
/**
|
|
244
|
+
* A key matches a list entry exactly or as a suffix (`openai_api_key`,
|
|
245
|
+
* `db_password`, `tool_output`). Prefix compounds (`secret_name`,
|
|
246
|
+
* `password_min_length`, `auth_type`, `content_type`) do not match: they name
|
|
247
|
+
* or describe a value rather than hold it, and flagging them would make the
|
|
248
|
+
* share verdict refuse ordinary traces. Value detectors still catch a
|
|
249
|
+
* credential-shaped value under any key.
|
|
250
|
+
*/
|
|
251
|
+
function matchesKeyList(normalized, keys) {
|
|
252
|
+
for (const key of keys) if (normalized === key || normalized.endsWith(`_${key}`)) return true;
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
function isTokenCredentialKey(normalized) {
|
|
256
|
+
if (NON_CREDENTIAL_TOKEN_KEYS.has(normalized)) return false;
|
|
257
|
+
if (normalized === "token") return true;
|
|
258
|
+
if (normalized.endsWith("tokens")) return false;
|
|
259
|
+
return normalized.endsWith("token");
|
|
260
|
+
}
|
|
261
|
+
/** Trace keys repeat on every span, so classifications are cached. */
|
|
262
|
+
const keyClassCache = /* @__PURE__ */ new Map();
|
|
263
|
+
function classifyKeyName(key) {
|
|
264
|
+
const cached = keyClassCache.get(key);
|
|
265
|
+
if (cached !== void 0) return cached ?? void 0;
|
|
266
|
+
const kind = classifyNormalizedKey(normalizeKey(key));
|
|
267
|
+
if (keyClassCache.size >= 1e4) keyClassCache.clear();
|
|
268
|
+
keyClassCache.set(key, kind ?? null);
|
|
269
|
+
return kind;
|
|
270
|
+
}
|
|
271
|
+
function classifyNormalizedKey(normalized) {
|
|
272
|
+
if (!normalized || NON_CREDENTIAL_TOKEN_KEYS.has(normalized)) return void 0;
|
|
273
|
+
if (isTokenCredentialKey(normalized)) return "credential";
|
|
274
|
+
if (matchesKeyList(normalized, CREDENTIAL_KEYS)) return "credential";
|
|
275
|
+
if (matchesKeyList(normalized, EMAIL_KEYS)) return "email";
|
|
276
|
+
if (matchesKeyList(normalized, SHARE_PERSONAL_KEYS)) return "share-personal";
|
|
277
|
+
if (matchesKeyList(normalized, IDENTIFIER_KEYS)) return "identifier";
|
|
278
|
+
if (matchesKeyList(normalized, RAW_CONTENT_KEYS)) return "raw-content";
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* The safety category a field name puts its value in, or undefined for an
|
|
282
|
+
* ordinary field. Token counts and model limits (`inputTokens`, `max_tokens`,
|
|
283
|
+
* `token_count`) and names such as `author` are not credentials.
|
|
284
|
+
*/
|
|
285
|
+
function classifyKey(key) {
|
|
286
|
+
const kind = classifyKeyName(key);
|
|
287
|
+
if (kind === void 0) return void 0;
|
|
288
|
+
if (kind === "credential") return "credential";
|
|
289
|
+
if (kind === "identifier") return "identifier";
|
|
290
|
+
if (kind === "raw-content") return "raw-content";
|
|
291
|
+
return "personal-data";
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* High-confidence credential shapes. A hit replaces the whole string. Each
|
|
295
|
+
* pattern is written without the `g` flag so `test` stays stateless.
|
|
296
|
+
*/
|
|
297
|
+
const CREDENTIAL_DETECTORS = [
|
|
298
|
+
{
|
|
299
|
+
id: "private-key",
|
|
300
|
+
pattern: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----/
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
id: "jwt",
|
|
304
|
+
pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
id: "bearer",
|
|
308
|
+
pattern: /\bbearer\s+[A-Za-z0-9._~+/=-]{12,}/i
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
id: "authorization-header",
|
|
312
|
+
pattern: /^\s*(?:basic|digest|apikey)\s+[A-Za-z0-9._~+/=:-]{8,}\s*$/i
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
id: "anthropic-key",
|
|
316
|
+
pattern: /\bsk-ant-[A-Za-z0-9_-]{16,}/
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: "provider-key",
|
|
320
|
+
pattern: /\bsk-(?:proj-|tan-)?[A-Za-z0-9_-]{16,}/
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
id: "stripe-key",
|
|
324
|
+
pattern: /\b(?:sk|rk)_(?:live|test)_[A-Za-z0-9_]{10,}/
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: "google-api-key",
|
|
328
|
+
pattern: /\bAIza[0-9A-Za-z_-]{20,}/
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
id: "github-token",
|
|
332
|
+
pattern: /\b(?:gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,})/
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
id: "slack-token",
|
|
336
|
+
pattern: /\bxox[abeoprs]-[A-Za-z0-9-]{10,}/
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
id: "aws-access-key",
|
|
340
|
+
pattern: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
id: "tangle-capability",
|
|
344
|
+
pattern: /\b(?:hubcap_|hct_)[A-Za-z0-9_=-]{8,}/
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: "url-credentials",
|
|
348
|
+
pattern: /\b[a-z][a-z0-9+.-]*:\/\/[^\s/:@]+:[^\s/@]+@/i
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
id: "url-secret-param",
|
|
352
|
+
pattern: /[?&](?:access_token|api_key|apikey|auth|token|secret|password|sig|signature|x-amz-signature|x-goog-signature)=[^&\s"'#)]{8,}/i
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: "secret-assignment",
|
|
356
|
+
pattern: /(?:api[_-]?key|apikey|access[_-]?key|secret[_-]?key|private[_-]?key|secret|token|password|passwd|passphrase|credentials?)["']?[ \t]*[:=][ \t]*["']?(?!encrypted:)(?![$`<[{(%])(?=[^\s"'`,;&|]*[0-9])(?=[^\s"'`,;&|]*[A-Za-z])[^\s"'`,;&|]{12,}/i
|
|
357
|
+
}
|
|
358
|
+
];
|
|
359
|
+
/** Personal-data shapes, replaced in place so the surrounding text survives. */
|
|
360
|
+
const PERSONAL_DATA_DETECTORS = [
|
|
361
|
+
{
|
|
362
|
+
id: "email",
|
|
363
|
+
requires: "@",
|
|
364
|
+
pattern: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
365
|
+
accept: isEmailAddress
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
id: "card-number",
|
|
369
|
+
pattern: /(?<![\w-])(?:\d{13,19}|\d{4}([ -])\d{4}\1\d{4}\1\d{4}(?:\1\d{3})?|\d{4}([ -])\d{6}\2\d{5})(?![\w-])/g,
|
|
370
|
+
accept: (match) => {
|
|
371
|
+
const digits = match.replace(/[ -]/g, "");
|
|
372
|
+
return CARD_PREFIX.test(digits) && luhnValid(digits);
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
id: "ssn",
|
|
377
|
+
pattern: /\b\d{3}-\d{2}-\d{4}\b/g
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
id: "phone",
|
|
381
|
+
pattern: /(?:\+1[-.\s]?)?(?:\(\d{3}\)\s?|\b\d{3}[-.\s])\d{3}[-.\s]\d{4}\b/g
|
|
382
|
+
}
|
|
383
|
+
];
|
|
384
|
+
/** Visa, Mastercard, Amex, Diners, JCB, Discover and UnionPay issuer prefixes. */
|
|
385
|
+
const CARD_PREFIX = /^(?:4|5[1-5]|2[2-7]|3[04-9]|6(?:011|4[4-9]|5|2))/;
|
|
386
|
+
/**
|
|
387
|
+
* Top-level domains that are file extensions: `icon@2x.png`, `worker@3.service`
|
|
388
|
+
* and `notes@AGENTS.md` are not addresses.
|
|
389
|
+
*/
|
|
390
|
+
const NON_EMAIL_TLDS = /* @__PURE__ */ new Set([
|
|
391
|
+
"md",
|
|
392
|
+
"mdx",
|
|
393
|
+
"txt",
|
|
394
|
+
"json",
|
|
395
|
+
"jsonl",
|
|
396
|
+
"yaml",
|
|
397
|
+
"yml",
|
|
398
|
+
"toml",
|
|
399
|
+
"lock",
|
|
400
|
+
"ts",
|
|
401
|
+
"tsx",
|
|
402
|
+
"js",
|
|
403
|
+
"jsx",
|
|
404
|
+
"mjs",
|
|
405
|
+
"cjs",
|
|
406
|
+
"py",
|
|
407
|
+
"rs",
|
|
408
|
+
"go",
|
|
409
|
+
"sh",
|
|
410
|
+
"css",
|
|
411
|
+
"html",
|
|
412
|
+
"svg",
|
|
413
|
+
"png",
|
|
414
|
+
"jpg",
|
|
415
|
+
"jpeg",
|
|
416
|
+
"gif",
|
|
417
|
+
"webp",
|
|
418
|
+
"service",
|
|
419
|
+
"socket",
|
|
420
|
+
"timer",
|
|
421
|
+
"mount",
|
|
422
|
+
"target"
|
|
423
|
+
]);
|
|
424
|
+
function isEmailAddress(match) {
|
|
425
|
+
if (match.toLowerCase().startsWith("git@")) return false;
|
|
426
|
+
const tld = match.slice(match.lastIndexOf(".") + 1).toLowerCase();
|
|
427
|
+
return !NON_EMAIL_TLDS.has(tld);
|
|
428
|
+
}
|
|
429
|
+
function luhnValid(digits) {
|
|
430
|
+
if (/^(\d)\1+$/.test(digits)) return false;
|
|
431
|
+
let sum = 0;
|
|
432
|
+
let double = false;
|
|
433
|
+
for (let index = digits.length - 1; index >= 0; index -= 1) {
|
|
434
|
+
let digit = digits.charCodeAt(index) - 48;
|
|
435
|
+
if (double) {
|
|
436
|
+
digit *= 2;
|
|
437
|
+
if (digit > 9) digit -= 9;
|
|
438
|
+
}
|
|
439
|
+
sum += digit;
|
|
440
|
+
double = !double;
|
|
441
|
+
}
|
|
442
|
+
return sum % 10 === 0;
|
|
443
|
+
}
|
|
444
|
+
/** The first credential detector that matches, or undefined. */
|
|
445
|
+
function detectCredential(text) {
|
|
446
|
+
for (const detector of CREDENTIAL_DETECTORS) if (detector.pattern.test(text)) return detector.id;
|
|
447
|
+
}
|
|
448
|
+
/** Personal-data detector ids that match somewhere in `text`. */
|
|
449
|
+
function detectPersonalData(text) {
|
|
450
|
+
const hits = [];
|
|
451
|
+
for (const detector of PERSONAL_DATA_DETECTORS) {
|
|
452
|
+
if (detector.requires && !text.includes(detector.requires)) continue;
|
|
453
|
+
detector.pattern.lastIndex = 0;
|
|
454
|
+
for (const match of text.matchAll(detector.pattern)) if (!detector.accept || detector.accept(match[0])) {
|
|
455
|
+
hits.push(detector.id);
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return hits;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Every form in which a known secret can appear in a trace: as written,
|
|
463
|
+
* base64, base64url and URL-encoded. Values shorter than 4 characters are
|
|
464
|
+
* ignored; they would match ordinary text.
|
|
465
|
+
*/
|
|
466
|
+
function knownSecretForms(values) {
|
|
467
|
+
const forms = /* @__PURE__ */ new Set();
|
|
468
|
+
for (const value of values ?? []) {
|
|
469
|
+
if (value.length < 4) continue;
|
|
470
|
+
const bytes = Buffer.from(value, "utf8");
|
|
471
|
+
forms.add(value);
|
|
472
|
+
forms.add(bytes.toString("base64"));
|
|
473
|
+
forms.add(bytes.toString("base64url"));
|
|
474
|
+
forms.add(encodeURIComponent(value));
|
|
475
|
+
}
|
|
476
|
+
return [...forms].sort((left, right) => right.length - left.length);
|
|
477
|
+
}
|
|
478
|
+
const BASE64_TEXT = /^[A-Za-z0-9+/_-]+={0,2}$/;
|
|
479
|
+
function containsKnownSecret(text, forms) {
|
|
480
|
+
if (forms.length === 0) return false;
|
|
481
|
+
if (forms.some((form) => text.includes(form))) return true;
|
|
482
|
+
if (text.length < 8 || !BASE64_TEXT.test(text)) return false;
|
|
483
|
+
const decoded = Buffer.from(text, text.includes("-") || text.includes("_") ? "base64url" : "base64").toString("utf8");
|
|
484
|
+
return forms.some((form) => decoded.includes(form));
|
|
485
|
+
}
|
|
486
|
+
/** The detector that marks `text` as a credential: a known secret first, then the value shapes. */
|
|
487
|
+
function credentialIn(text, knownSecrets) {
|
|
488
|
+
return containsKnownSecret(text, knownSecrets) ? "known-secret" : detectCredential(text);
|
|
489
|
+
}
|
|
490
|
+
const MARKER_PREFIX = "[REDACTED:";
|
|
491
|
+
function marker(detector) {
|
|
492
|
+
return `${MARKER_PREFIX}${detector}]`;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Placeholders other tools write for a removed value. A credential field that
|
|
496
|
+
* holds one of these is already redacted and is not flagged again.
|
|
497
|
+
*/
|
|
498
|
+
const REDACTED_PLACEHOLDER = /^(?:\[(?:REDACTED|redacted)(?::[^\]]*)?\]|\[ID:[0-9a-f]{12}\]|\*{3,}|<redacted>|«redacted»|\[HASH:[0-9a-f]{8,}\])$/;
|
|
499
|
+
function isPlaceholder(value) {
|
|
500
|
+
return value === "" || REDACTED_PLACEHOLDER.test(value);
|
|
501
|
+
}
|
|
502
|
+
const DATA_URI = /^data:[\w.+-]+\/[\w.+-]+(?:;[\w.+-]+=[\w.+-]+)*;base64,/i;
|
|
503
|
+
const MAX_DEPTH = 64;
|
|
504
|
+
function record(state, path, category, detector, action) {
|
|
505
|
+
const report = state.report;
|
|
506
|
+
if (action === "truncated") report.truncatedCount += 1;
|
|
507
|
+
report.redactionCount += 1;
|
|
508
|
+
report.byDetector[detector] = (report.byDetector[detector] ?? 0) + 1;
|
|
509
|
+
report.findings.push({
|
|
510
|
+
path,
|
|
511
|
+
category,
|
|
512
|
+
detector,
|
|
513
|
+
action
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
function pointer(parent, key) {
|
|
517
|
+
return `${parent}/${String(key).replace(/~/g, "~0").replace(/\//g, "~1")}`;
|
|
518
|
+
}
|
|
519
|
+
function truncateUtf8(text, maxBytes) {
|
|
520
|
+
if (text.length <= maxBytes / 4) return void 0;
|
|
521
|
+
const bytes = Buffer.from(text, "utf8");
|
|
522
|
+
if (bytes.length <= maxBytes) return void 0;
|
|
523
|
+
let cut = maxBytes;
|
|
524
|
+
while (cut > 0 && ((bytes[cut] ?? 0) & 192) === 128) cut -= 1;
|
|
525
|
+
return `${bytes.subarray(0, cut).toString("utf8")}…[truncated ${bytes.length - cut} bytes]`;
|
|
526
|
+
}
|
|
527
|
+
function redactStringAt(text, path, state) {
|
|
528
|
+
if (isPlaceholder(text)) return text;
|
|
529
|
+
if (DATA_URI.test(text)) {
|
|
530
|
+
if (!state.rules.redactMedia) return text;
|
|
531
|
+
record(state, path, "media", "media", "redacted");
|
|
532
|
+
return marker("media");
|
|
533
|
+
}
|
|
534
|
+
const credential = credentialIn(text, state.knownSecrets);
|
|
535
|
+
if (credential) {
|
|
536
|
+
record(state, path, "credential", credential, "redacted");
|
|
537
|
+
return marker(credential);
|
|
538
|
+
}
|
|
539
|
+
let output = text;
|
|
540
|
+
for (const detector of PERSONAL_DATA_DETECTORS) {
|
|
541
|
+
if (detector.requires && !output.includes(detector.requires)) continue;
|
|
542
|
+
output = output.replace(detector.pattern, (match) => {
|
|
543
|
+
if (detector.accept && !detector.accept(match)) return match;
|
|
544
|
+
record(state, path, "personal-data", detector.id, "redacted");
|
|
545
|
+
return marker(detector.id);
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
const truncated = truncateUtf8(output, state.maxStringBytes);
|
|
549
|
+
if (truncated === void 0) return output;
|
|
550
|
+
record(state, path, "raw-content", "size-cap", "truncated");
|
|
551
|
+
return truncated;
|
|
552
|
+
}
|
|
553
|
+
function pseudonym(value, state) {
|
|
554
|
+
return `[ID:${createHmac("sha256", state.pseudonymKey).update(String(value)).digest("hex").slice(0, 12)}]`;
|
|
555
|
+
}
|
|
556
|
+
function redactKeyed(key, value, path, depth, state) {
|
|
557
|
+
const kind = classifyKeyName(key);
|
|
558
|
+
if (value === null || value === void 0 || typeof value === "boolean") return value;
|
|
559
|
+
if (typeof value === "string" && isPlaceholder(value)) return value;
|
|
560
|
+
if (kind === "credential" && typeof value !== "number") {
|
|
561
|
+
record(state, path, "credential", "credential-key", "redacted");
|
|
562
|
+
return marker("credential-key");
|
|
563
|
+
}
|
|
564
|
+
if (kind === "email" || kind === "share-personal" && state.rules.redactKeys.has("share-personal")) {
|
|
565
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
566
|
+
record(state, path, "personal-data", "personal-data-key", "redacted");
|
|
567
|
+
return marker("personal-data-key");
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (kind === "identifier" && state.rules.redactKeys.has("identifier")) {
|
|
571
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
572
|
+
record(state, path, "identifier", "identifier-key", "pseudonymized");
|
|
573
|
+
return pseudonym(value, state);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
if (kind === "raw-content" && state.rules.redactKeys.has("raw-content") && typeof value !== "number") {
|
|
577
|
+
record(state, path, "raw-content", "raw-content-key", "redacted");
|
|
578
|
+
return marker("raw-content-key");
|
|
579
|
+
}
|
|
580
|
+
return walk(value, path, depth + 1, state);
|
|
581
|
+
}
|
|
582
|
+
function walk(value, path, depth, state) {
|
|
583
|
+
if (typeof value === "string") return redactStringAt(value, path, state);
|
|
584
|
+
if (value === null || typeof value !== "object") return value;
|
|
585
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
|
|
586
|
+
record(state, path, "media", "binary", "redacted");
|
|
587
|
+
return marker("binary");
|
|
588
|
+
}
|
|
589
|
+
if (value instanceof Date) return value;
|
|
590
|
+
if (depth > MAX_DEPTH) {
|
|
591
|
+
record(state, path, "raw-content", "depth-limit", "truncated");
|
|
592
|
+
return marker("depth-limit");
|
|
593
|
+
}
|
|
594
|
+
if (state.seen.has(value)) {
|
|
595
|
+
record(state, path, "raw-content", "circular", "redacted");
|
|
596
|
+
return marker("circular");
|
|
597
|
+
}
|
|
598
|
+
state.seen.add(value);
|
|
599
|
+
try {
|
|
600
|
+
if (Array.isArray(value)) return value.map((item, index) => walk(item, pointer(path, index), depth + 1, state));
|
|
601
|
+
const out = {};
|
|
602
|
+
let renamed = 0;
|
|
603
|
+
for (const [key, item] of Object.entries(value)) {
|
|
604
|
+
let outKey = key;
|
|
605
|
+
const keyCredential = credentialIn(key, state.knownSecrets);
|
|
606
|
+
if (keyCredential) {
|
|
607
|
+
renamed += 1;
|
|
608
|
+
outKey = `${marker(keyCredential)}#${renamed}`;
|
|
609
|
+
record(state, pointer(path, outKey), "credential", keyCredential, "redacted");
|
|
610
|
+
}
|
|
611
|
+
out[outKey] = redactKeyed(key, item, pointer(path, outKey), depth, state);
|
|
612
|
+
}
|
|
613
|
+
return out;
|
|
614
|
+
} finally {
|
|
615
|
+
state.seen.delete(value);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function newReport(profile) {
|
|
619
|
+
return {
|
|
620
|
+
version: REDACTION_VERSION,
|
|
621
|
+
profile,
|
|
622
|
+
redactionCount: 0,
|
|
623
|
+
byDetector: {},
|
|
624
|
+
truncatedCount: 0,
|
|
625
|
+
findings: []
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function newState(options, report) {
|
|
629
|
+
const profile = options.profile ?? "default";
|
|
630
|
+
const rules = PROFILE_RULES[profile];
|
|
631
|
+
if (!rules) throw new Error(`unknown redaction profile: ${String(profile)}`);
|
|
632
|
+
const requested = options.maxStringBytes;
|
|
633
|
+
return {
|
|
634
|
+
rules,
|
|
635
|
+
maxStringBytes: typeof requested === "number" && Number.isFinite(requested) && requested >= 0 ? Math.min(Math.floor(requested), rules.maxStringBytes) : rules.maxStringBytes,
|
|
636
|
+
knownSecrets: knownSecretForms(options.knownSecrets),
|
|
637
|
+
report: report ?? newReport(profile),
|
|
638
|
+
pseudonymKey: randomBytes(32),
|
|
639
|
+
seen: /* @__PURE__ */ new WeakSet()
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Return a redacted deep copy of `value` and a report of what changed. Pure:
|
|
644
|
+
* the input is not mutated. Never throws on the value's shape; cycles, binary
|
|
645
|
+
* data and nesting past 64 levels are replaced with markers.
|
|
646
|
+
*/
|
|
647
|
+
function redact(value, options = {}) {
|
|
648
|
+
const state = newState(options, options.report);
|
|
649
|
+
return {
|
|
650
|
+
value: walk(value, "", 0, state),
|
|
651
|
+
report: state.report
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Redact one free-text string with the value detectors (no field name is
|
|
656
|
+
* known). Pass `report` to accumulate counts across calls.
|
|
657
|
+
*/
|
|
658
|
+
function redactText(text, options = {}) {
|
|
659
|
+
return redactStringAt(text, "", newState(options, options.report));
|
|
660
|
+
}
|
|
661
|
+
/** An empty report to accumulate `redactText` calls into. */
|
|
662
|
+
function emptyRedactionReport(profile = "default") {
|
|
663
|
+
return newReport(profile);
|
|
664
|
+
}
|
|
665
|
+
function flag(state, path, category, detector) {
|
|
666
|
+
const severity = state.rules.severity[category];
|
|
667
|
+
if (!severity) return;
|
|
668
|
+
const id = `${category}\u0000${detector}`;
|
|
669
|
+
const existing = state.findings.get(id);
|
|
670
|
+
if (existing) {
|
|
671
|
+
existing.count += 1;
|
|
672
|
+
if (existing.paths.length < 5) existing.paths.push(path);
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
state.findings.set(id, {
|
|
676
|
+
category,
|
|
677
|
+
detector,
|
|
678
|
+
severity,
|
|
679
|
+
count: 1,
|
|
680
|
+
paths: [path]
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
function scanString(text, path, state) {
|
|
684
|
+
if (isPlaceholder(text)) return;
|
|
685
|
+
if (DATA_URI.test(text)) {
|
|
686
|
+
flag(state, path, "media", "media");
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const credential = credentialIn(text, state.knownSecrets);
|
|
690
|
+
if (credential) {
|
|
691
|
+
flag(state, path, "credential", credential);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
for (const detector of detectPersonalData(text)) flag(state, path, "personal-data", detector);
|
|
695
|
+
}
|
|
696
|
+
function scanKeyed(key, value, path, depth, state) {
|
|
697
|
+
if (value === null || value === void 0 || typeof value === "boolean") return;
|
|
698
|
+
if (typeof value === "string" && isPlaceholder(value)) return;
|
|
699
|
+
const category = classifyKey(key);
|
|
700
|
+
if (category === "credential" && typeof value !== "number") {
|
|
701
|
+
flag(state, path, "credential", "credential-key");
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const scalar = typeof value === "string" || typeof value === "number";
|
|
705
|
+
if (category === "personal-data" && scalar) {
|
|
706
|
+
if (classifyKeyName(key) === "email" || state.profile !== "default") {
|
|
707
|
+
flag(state, path, "personal-data", "personal-data-key");
|
|
708
|
+
return;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (category === "identifier" && scalar) flag(state, path, "identifier", "identifier-key");
|
|
712
|
+
if (category === "raw-content" && typeof value !== "number") flag(state, path, "raw-content", "raw-content-key");
|
|
713
|
+
scan(value, path, depth + 1, state);
|
|
714
|
+
}
|
|
715
|
+
function scan(value, path, depth, state) {
|
|
716
|
+
if (typeof value === "string") {
|
|
717
|
+
scanString(value, path, state);
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
if (value === null || typeof value !== "object") return;
|
|
721
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
|
|
722
|
+
state.unreadable.push(`${path || "/"}: binary data`);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (value instanceof Date) return;
|
|
726
|
+
if (depth > MAX_DEPTH) {
|
|
727
|
+
state.unreadable.push(`${path || "/"}: nested deeper than ${MAX_DEPTH} levels`);
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
if (state.seen.has(value)) {
|
|
731
|
+
state.unreadable.push(`${path || "/"}: circular reference`);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
state.seen.add(value);
|
|
735
|
+
try {
|
|
736
|
+
if (Array.isArray(value)) {
|
|
737
|
+
for (const [index, item] of value.entries()) scan(item, pointer(path, index), depth + 1, state);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
for (const [key, item] of Object.entries(value)) {
|
|
741
|
+
const keyCredential = credentialIn(key, state.knownSecrets);
|
|
742
|
+
const segment = keyCredential ? marker(keyCredential) : key;
|
|
743
|
+
if (keyCredential) flag(state, pointer(path, segment), "credential", keyCredential);
|
|
744
|
+
scanKeyed(key, item, pointer(path, segment), depth, state);
|
|
745
|
+
}
|
|
746
|
+
} finally {
|
|
747
|
+
state.seen.delete(value);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* Read `value` without changing it and say whether it is safe to share under
|
|
752
|
+
* `profile`. UNKNOWN when any part could not be read; UNSAFE when any
|
|
753
|
+
* error-severity finding remains; SAFE_WITH_WARNINGS when only warnings
|
|
754
|
+
* remain (raw content, embedded media, identifiers under `share`).
|
|
755
|
+
*/
|
|
756
|
+
function assessShareSafety(value, options = {}) {
|
|
757
|
+
const profile = options.profile ?? "default";
|
|
758
|
+
const rules = PROFILE_RULES[profile];
|
|
759
|
+
if (!rules) throw new Error(`unknown redaction profile: ${String(profile)}`);
|
|
760
|
+
const state = {
|
|
761
|
+
profile,
|
|
762
|
+
rules,
|
|
763
|
+
knownSecrets: knownSecretForms(options.knownSecrets),
|
|
764
|
+
findings: /* @__PURE__ */ new Map(),
|
|
765
|
+
unreadable: [],
|
|
766
|
+
seen: /* @__PURE__ */ new WeakSet()
|
|
767
|
+
};
|
|
768
|
+
scan(value, "", 0, state);
|
|
769
|
+
return verdictFrom(profile, [...state.findings.values()], state.unreadable);
|
|
770
|
+
}
|
|
771
|
+
/**
|
|
772
|
+
* One verdict over several parts (files in a directory, spans in a bundle).
|
|
773
|
+
* Callers prefix each part's paths with its source before combining.
|
|
774
|
+
*/
|
|
775
|
+
function combineVerdicts(profile, verdicts, unreadable = []) {
|
|
776
|
+
const findings = /* @__PURE__ */ new Map();
|
|
777
|
+
for (const verdict of verdicts) for (const finding of verdict.findings) {
|
|
778
|
+
const id = `${finding.category}\u0000${finding.detector}`;
|
|
779
|
+
const existing = findings.get(id);
|
|
780
|
+
if (!existing) {
|
|
781
|
+
findings.set(id, {
|
|
782
|
+
...finding,
|
|
783
|
+
paths: [...finding.paths]
|
|
784
|
+
});
|
|
785
|
+
continue;
|
|
786
|
+
}
|
|
787
|
+
existing.count += finding.count;
|
|
788
|
+
for (const path of finding.paths) if (existing.paths.length < 5) existing.paths.push(path);
|
|
789
|
+
}
|
|
790
|
+
return verdictFrom(profile, [...findings.values()], [...verdicts.flatMap((verdict) => verdict.unreadable), ...unreadable]);
|
|
791
|
+
}
|
|
792
|
+
function verdictFrom(profile, findings, unreadable) {
|
|
793
|
+
return {
|
|
794
|
+
status: unreadable.length > 0 ? "UNKNOWN" : findings.some((finding) => finding.severity === "error") ? "UNSAFE" : findings.length > 0 ? "SAFE_WITH_WARNINGS" : "SAFE",
|
|
795
|
+
profile,
|
|
796
|
+
findings: [...findings],
|
|
797
|
+
unreadable: [...unreadable]
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
/** Whether a verdict allows sharing: SAFE or SAFE_WITH_WARNINGS. UNSAFE and UNKNOWN refuse. */
|
|
801
|
+
function shareAllowed(verdict) {
|
|
802
|
+
return verdict.status === "SAFE" || verdict.status === "SAFE_WITH_WARNINGS";
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Redact `value` under `profile`, then assess the redacted output. The
|
|
806
|
+
* verdict describes what would actually be shared: a detector that failed to
|
|
807
|
+
* remove something shows up as UNSAFE here.
|
|
808
|
+
*/
|
|
809
|
+
function redactForShare(value, options = {}) {
|
|
810
|
+
const redacted = redact(value, options);
|
|
811
|
+
const verdict = assessShareSafety(redacted.value, {
|
|
812
|
+
profile: options.profile,
|
|
813
|
+
knownSecrets: options.knownSecrets
|
|
814
|
+
});
|
|
815
|
+
return {
|
|
816
|
+
value: redacted.value,
|
|
817
|
+
report: redacted.report,
|
|
818
|
+
verdict
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
//#endregion
|
|
822
|
+
export { combineVerdicts as a, emptyRedactionReport as c, redactForShare as d, redactText as f, classifyKey as i, normalizeKey as l, REDACTION_VERSION as n, detectCredential as o, shareAllowed as p, assessShareSafety as r, detectPersonalData as s, REDACTION_PROFILES as t, redact as u };
|
|
823
|
+
|
|
824
|
+
//# sourceMappingURL=redact-DxwlUAvh.js.map
|