@jinn-network/core 0.1.0-canary.3afae198

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.
Files changed (155) hide show
  1. package/dist/canonical-json.d.ts +1 -0
  2. package/dist/canonical-json.js +67 -0
  3. package/dist/captured-task.d.ts +140 -0
  4. package/dist/captured-task.js +122 -0
  5. package/dist/contribution-store.d.ts +97 -0
  6. package/dist/contribution-store.js +770 -0
  7. package/dist/corpus-read/acquire.d.ts +39 -0
  8. package/dist/corpus-read/acquire.js +212 -0
  9. package/dist/corpus-read/cache.d.ts +14 -0
  10. package/dist/corpus-read/cache.js +17 -0
  11. package/dist/corpus-read/capture-meta.d.ts +16 -0
  12. package/dist/corpus-read/capture-meta.js +18 -0
  13. package/dist/corpus-read/create-corpus.d.ts +2 -0
  14. package/dist/corpus-read/create-corpus.js +97 -0
  15. package/dist/corpus-read/fetch-artifact.d.ts +21 -0
  16. package/dist/corpus-read/fetch-artifact.js +32 -0
  17. package/dist/corpus-read/fetch.d.ts +12 -0
  18. package/dist/corpus-read/fetch.js +24 -0
  19. package/dist/corpus-read/http-discovery.d.ts +9 -0
  20. package/dist/corpus-read/http-discovery.js +128 -0
  21. package/dist/corpus-read/index.d.ts +10 -0
  22. package/dist/corpus-read/index.js +10 -0
  23. package/dist/corpus-read/ipfs.d.ts +14 -0
  24. package/dist/corpus-read/ipfs.js +81 -0
  25. package/dist/corpus-read/route-resolver.d.ts +16 -0
  26. package/dist/corpus-read/route-resolver.js +19 -0
  27. package/dist/corpus-read/types.d.ts +177 -0
  28. package/dist/corpus-read/types.js +42 -0
  29. package/dist/envelope.d.ts +134 -0
  30. package/dist/envelope.js +162 -0
  31. package/dist/evidence-adapter.d.ts +26 -0
  32. package/dist/evidence-adapter.js +321 -0
  33. package/dist/evidence-filesystem.d.ts +40 -0
  34. package/dist/evidence-filesystem.js +267 -0
  35. package/dist/evidence-index.d.ts +117 -0
  36. package/dist/evidence-index.js +1083 -0
  37. package/dist/evidence-store-lock.d.ts +2 -0
  38. package/dist/evidence-store-lock.js +208 -0
  39. package/dist/execution-envelope.d.ts +3142 -0
  40. package/dist/execution-envelope.js +203 -0
  41. package/dist/index.d.ts +12 -0
  42. package/dist/index.js +12 -0
  43. package/dist/manifest.d.ts +75 -0
  44. package/dist/manifest.js +184 -0
  45. package/dist/paired.d.ts +68 -0
  46. package/dist/paired.js +92 -0
  47. package/dist/scrub/apply-dispositions.d.ts +74 -0
  48. package/dist/scrub/apply-dispositions.js +309 -0
  49. package/dist/scrub/build.d.ts +75 -0
  50. package/dist/scrub/build.js +143 -0
  51. package/dist/scrub/checksummed-instruments-detector.d.ts +13 -0
  52. package/dist/scrub/checksummed-instruments-detector.js +119 -0
  53. package/dist/scrub/data/bip39-english.d.ts +2 -0
  54. package/dist/scrub/data/bip39-english.js +2051 -0
  55. package/dist/scrub/data/gitleaks-rules.d.ts +56 -0
  56. package/dist/scrub/data/gitleaks-rules.js +67 -0
  57. package/dist/scrub/emit-scrub.d.ts +52 -0
  58. package/dist/scrub/emit-scrub.js +122 -0
  59. package/dist/scrub/eval/findings-from-scrub.d.ts +18 -0
  60. package/dist/scrub/eval/findings-from-scrub.js +99 -0
  61. package/dist/scrub/eval/fixtures.d.ts +14 -0
  62. package/dist/scrub/eval/fixtures.js +159 -0
  63. package/dist/scrub/eval/index.d.ts +6 -0
  64. package/dist/scrub/eval/index.js +6 -0
  65. package/dist/scrub/eval/local-corpus.d.ts +15 -0
  66. package/dist/scrub/eval/local-corpus.js +29 -0
  67. package/dist/scrub/eval/metrics.d.ts +19 -0
  68. package/dist/scrub/eval/metrics.js +77 -0
  69. package/dist/scrub/eval/run-bench.d.ts +8 -0
  70. package/dist/scrub/eval/run-bench.js +125 -0
  71. package/dist/scrub/eval/types.d.ts +69 -0
  72. package/dist/scrub/eval/types.js +7 -0
  73. package/dist/scrub/finding.d.ts +38 -0
  74. package/dist/scrub/finding.js +7 -0
  75. package/dist/scrub/git-identity-detector.d.ts +16 -0
  76. package/dist/scrub/git-identity-detector.js +116 -0
  77. package/dist/scrub/gitleaks-detector.d.ts +13 -0
  78. package/dist/scrub/gitleaks-detector.js +45 -0
  79. package/dist/scrub/gliner-detector.d.ts +65 -0
  80. package/dist/scrub/gliner-detector.js +129 -0
  81. package/dist/scrub/index.d.ts +26 -0
  82. package/dist/scrub/index.js +26 -0
  83. package/dist/scrub/ip-address-detector.d.ts +12 -0
  84. package/dist/scrub/ip-address-detector.js +81 -0
  85. package/dist/scrub/key-policy.d.ts +30 -0
  86. package/dist/scrub/key-policy.js +72 -0
  87. package/dist/scrub/known-identity-detector.d.ts +66 -0
  88. package/dist/scrub/known-identity-detector.js +284 -0
  89. package/dist/scrub/layer2.d.ts +18 -0
  90. package/dist/scrub/layer2.js +28 -0
  91. package/dist/scrub/ml-pii-stage.d.ts +42 -0
  92. package/dist/scrub/ml-pii-stage.js +119 -0
  93. package/dist/scrub/pii-build.d.ts +47 -0
  94. package/dist/scrub/pii-build.js +67 -0
  95. package/dist/scrub/pipeline.d.ts +77 -0
  96. package/dist/scrub/pipeline.js +225 -0
  97. package/dist/scrub/plain-patterns-stage.d.ts +33 -0
  98. package/dist/scrub/plain-patterns-stage.js +88 -0
  99. package/dist/scrub/policy.d.ts +28 -0
  100. package/dist/scrub/policy.js +156 -0
  101. package/dist/scrub/provenance.d.ts +53 -0
  102. package/dist/scrub/provenance.js +83 -0
  103. package/dist/scrub/reject-classes-detector.d.ts +11 -0
  104. package/dist/scrub/reject-classes-detector.js +195 -0
  105. package/dist/scrub/reject-publish-error.d.ts +30 -0
  106. package/dist/scrub/reject-publish-error.js +47 -0
  107. package/dist/scrub/review-queue.d.ts +90 -0
  108. package/dist/scrub/review-queue.js +259 -0
  109. package/dist/scrub/secretlint-stage.d.ts +21 -0
  110. package/dist/scrub/secretlint-stage.js +264 -0
  111. package/dist/scrub/transformers-detector.d.ts +41 -0
  112. package/dist/scrub/transformers-detector.js +77 -0
  113. package/dist/scrub/types.d.ts +36 -0
  114. package/dist/scrub/types.js +1 -0
  115. package/dist/scrub/url-credentials-detector.d.ts +9 -0
  116. package/dist/scrub/url-credentials-detector.js +66 -0
  117. package/dist/session-provenance.d.ts +75 -0
  118. package/dist/session-provenance.js +20 -0
  119. package/dist/skill-artifact.d.ts +442 -0
  120. package/dist/skill-artifact.js +136 -0
  121. package/dist/trajectory/hash-chain.d.ts +18 -0
  122. package/dist/trajectory/hash-chain.js +47 -0
  123. package/dist/trajectory/index.d.ts +4 -0
  124. package/dist/trajectory/index.js +4 -0
  125. package/dist/trajectory/schema.d.ts +681 -0
  126. package/dist/trajectory/schema.js +119 -0
  127. package/dist/trajectory/transcript-parsers/aider-history.d.ts +42 -0
  128. package/dist/trajectory/transcript-parsers/aider-history.js +181 -0
  129. package/dist/trajectory/transcript-parsers/claude-code-jsonl.d.ts +37 -0
  130. package/dist/trajectory/transcript-parsers/claude-code-jsonl.js +142 -0
  131. package/dist/trajectory/transcript-parsers/codex-session.d.ts +42 -0
  132. package/dist/trajectory/transcript-parsers/codex-session.js +235 -0
  133. package/dist/trajectory/transcript-parsers/continue-devdata.d.ts +41 -0
  134. package/dist/trajectory/transcript-parsers/continue-devdata.js +190 -0
  135. package/dist/trajectory/transcript-parsers/cursor-sqlite.d.ts +38 -0
  136. package/dist/trajectory/transcript-parsers/cursor-sqlite.js +118 -0
  137. package/dist/trajectory/transcript-parsers/gemini-session.d.ts +35 -0
  138. package/dist/trajectory/transcript-parsers/gemini-session.js +137 -0
  139. package/dist/trajectory/transcript-parsers/index.d.ts +7 -0
  140. package/dist/trajectory/transcript-parsers/index.js +7 -0
  141. package/dist/trajectory/transcript-parsers/types.d.ts +101 -0
  142. package/dist/trajectory/transcript-parsers/types.js +32 -0
  143. package/dist/trajectory/transcript-to-spans/attrs.d.ts +4 -0
  144. package/dist/trajectory/transcript-to-spans/attrs.js +30 -0
  145. package/dist/trajectory/transcript-to-spans/claude-code-stream-json.d.ts +8 -0
  146. package/dist/trajectory/transcript-to-spans/claude-code-stream-json.js +162 -0
  147. package/dist/trajectory/transcript-to-spans/codex-exec-json.d.ts +8 -0
  148. package/dist/trajectory/transcript-to-spans/codex-exec-json.js +253 -0
  149. package/dist/trajectory/transcript-to-spans/index.d.ts +4 -0
  150. package/dist/trajectory/transcript-to-spans/index.js +4 -0
  151. package/dist/trajectory/transcript-to-spans/types.d.ts +18 -0
  152. package/dist/trajectory/transcript-to-spans/types.js +1 -0
  153. package/dist/window.d.ts +12 -0
  154. package/dist/window.js +5 -0
  155. package/package.json +72 -0
@@ -0,0 +1,77 @@
1
+ import { assertNoRejectPublish, shouldRejectPublish } from './apply-dispositions.js';
2
+ import type { Detector } from './finding.js';
3
+ import { type PolicyTable } from './policy.js';
4
+ import { type PolicyHashInput } from './provenance.js';
5
+ import { type ReviewQueueStore } from './review-queue.js';
6
+ import type { Attributes, ScrubResult, ScrubStage } from './types.js';
7
+ export { shouldRejectPublish, assertNoRejectPublish };
8
+ export interface ScrubPipelineOptions {
9
+ policy?: PolicyTable;
10
+ /** Check-mode: any non-pass disposition rejects (design §6.5). */
11
+ checkMode?: boolean;
12
+ /**
13
+ * Review-queue store for flag resolutions (#1973). Defaults to the
14
+ * operator-local `~/.jinn-client/scrub-review/queue.jsonl` store in
15
+ * redact-mode. Pass an in-memory store in tests.
16
+ */
17
+ reviewStore?: ReviewQueueStore;
18
+ /**
19
+ * When true (default in redact-mode), unresolved flags enqueue + throw
20
+ * {@link UnresolvedFlagError}. Check-mode leaves this off.
21
+ */
22
+ failClosedOnUnresolvedFlags?: boolean;
23
+ /**
24
+ * Inputs for the published policy hash (#1974). When omitted, emit helpers
25
+ * still bump `schemaVersion` but leave `policyHash` unset.
26
+ */
27
+ provenance?: Omit<PolicyHashInput, 'policy' | 'detectors'>;
28
+ }
29
+ /**
30
+ * Runs one detector inventory, then one disposition pass (#1969).
31
+ *
32
+ * Nested values (#1378): after the flat detect→dispose pass, surviving
33
+ * object/array values are walked and every string leaf is scrubbed through
34
+ * the same detect→dispose path, keyed by the TOP-LEVEL attribute key.
35
+ *
36
+ * Constructor also accepts legacy {@link ScrubStage} arrays (stage.scrub chain)
37
+ * so existing unit tests and injectors keep working through the migration.
38
+ */
39
+ export declare class ScrubPipeline {
40
+ private readonly policy;
41
+ private readonly checkMode;
42
+ private readonly detectors;
43
+ private readonly stages;
44
+ private readonly reviewStore;
45
+ private readonly failClosedOnUnresolvedFlags;
46
+ private readonly provenanceExtras;
47
+ constructor(detectorsOrStages: Array<Detector | ScrubStage>, opts?: ScrubPipelineOptions);
48
+ get components(): Array<{
49
+ name: string;
50
+ version: string;
51
+ }>;
52
+ /** Disposition policy table used by this pipeline (#1974). */
53
+ get policyTable(): PolicyTable;
54
+ /**
55
+ * Additive provenance fields for a redaction manifest (#1974). Always
56
+ * includes `schemaVersion`; `policyHash` is present when provenance
57
+ * extras were supplied at construction.
58
+ */
59
+ manifestProvenance(perClassCounts?: Record<string, number>): {
60
+ schemaVersion: number;
61
+ policyHash?: string;
62
+ perClassCounts?: Record<string, number>;
63
+ };
64
+ /**
65
+ * Reproducible policy hash, or undefined when provenance inputs were not
66
+ * configured (legacy stage-only pipelines).
67
+ */
68
+ policyHash(): string | undefined;
69
+ run(attributes: Attributes): Promise<ScrubResult>;
70
+ private assertAndEnqueueUnresolvedFlags;
71
+ /** Legacy ScrubStage chain (tests / residual injectors). */
72
+ private runLegacyStages;
73
+ private runStagesFlat;
74
+ private scrubNestedLegacy;
75
+ private runDetectDispose;
76
+ private scrubNested;
77
+ }
@@ -0,0 +1,225 @@
1
+ import { applyDispositions, assertNoRejectPublish, shouldRejectPublish } from './apply-dispositions.js';
2
+ import { DEFAULT_POLICY } from './policy.js';
3
+ import { REDACTION_MANIFEST_SCHEMA_VERSION, computePolicyHash, mergePerClassCounts, } from './provenance.js';
4
+ import { UnresolvedFlagError, createReviewQueueStore, } from './review-queue.js';
5
+ export { shouldRejectPublish, assertNoRejectPublish };
6
+ function isDetector(item) {
7
+ return typeof item.detect === 'function';
8
+ }
9
+ /**
10
+ * Runs one detector inventory, then one disposition pass (#1969).
11
+ *
12
+ * Nested values (#1378): after the flat detect→dispose pass, surviving
13
+ * object/array values are walked and every string leaf is scrubbed through
14
+ * the same detect→dispose path, keyed by the TOP-LEVEL attribute key.
15
+ *
16
+ * Constructor also accepts legacy {@link ScrubStage} arrays (stage.scrub chain)
17
+ * so existing unit tests and injectors keep working through the migration.
18
+ */
19
+ export class ScrubPipeline {
20
+ policy;
21
+ checkMode;
22
+ detectors;
23
+ stages;
24
+ reviewStore;
25
+ failClosedOnUnresolvedFlags;
26
+ provenanceExtras;
27
+ constructor(detectorsOrStages, opts = {}) {
28
+ this.policy = opts.policy ?? DEFAULT_POLICY;
29
+ this.checkMode = opts.checkMode ?? false;
30
+ this.reviewStore = opts.reviewStore;
31
+ this.failClosedOnUnresolvedFlags =
32
+ opts.failClosedOnUnresolvedFlags ?? !this.checkMode;
33
+ this.provenanceExtras = opts.provenance;
34
+ if (detectorsOrStages.length > 0 && detectorsOrStages.every(isDetector)) {
35
+ this.detectors = detectorsOrStages;
36
+ this.stages = null;
37
+ }
38
+ else {
39
+ this.detectors = null;
40
+ this.stages = detectorsOrStages;
41
+ }
42
+ }
43
+ get components() {
44
+ const items = this.detectors ?? this.stages ?? [];
45
+ return items.map((d) => ({ name: d.name, version: d.version }));
46
+ }
47
+ /** Disposition policy table used by this pipeline (#1974). */
48
+ get policyTable() {
49
+ return this.policy;
50
+ }
51
+ /**
52
+ * Additive provenance fields for a redaction manifest (#1974). Always
53
+ * includes `schemaVersion`; `policyHash` is present when provenance
54
+ * extras were supplied at construction.
55
+ */
56
+ manifestProvenance(perClassCounts) {
57
+ const out = { schemaVersion: REDACTION_MANIFEST_SCHEMA_VERSION };
58
+ const hash = this.policyHash();
59
+ if (hash)
60
+ out.policyHash = hash;
61
+ if (perClassCounts && Object.keys(perClassCounts).length > 0) {
62
+ out.perClassCounts = perClassCounts;
63
+ }
64
+ return out;
65
+ }
66
+ /**
67
+ * Reproducible policy hash, or undefined when provenance inputs were not
68
+ * configured (legacy stage-only pipelines).
69
+ */
70
+ policyHash() {
71
+ if (!this.provenanceExtras)
72
+ return undefined;
73
+ return computePolicyHash({
74
+ policy: this.policy,
75
+ detectors: this.components,
76
+ modelId: this.provenanceExtras.modelId,
77
+ labels: this.provenanceExtras.labels,
78
+ allowlistDigest: this.provenanceExtras.allowlistDigest,
79
+ });
80
+ }
81
+ async run(attributes) {
82
+ if (this.stages) {
83
+ return this.runLegacyStages(attributes);
84
+ }
85
+ const flat = await this.runDetectDispose(attributes);
86
+ const out = {};
87
+ const redactions = [...flat.redactions];
88
+ const findings = [...flat.findings];
89
+ const unresolvedFlags = [...flat.unresolvedFlags];
90
+ let perClassCounts = { ...flat.perClassCounts };
91
+ let rejected = flat.rejected;
92
+ for (const [key, value] of Object.entries(flat.attributes)) {
93
+ out[key] =
94
+ typeof value === 'string'
95
+ ? value
96
+ : await this.scrubNested(key, value, redactions, findings, unresolvedFlags, (nestedCounts) => {
97
+ perClassCounts =
98
+ mergePerClassCounts(perClassCounts, nestedCounts) ?? perClassCounts;
99
+ });
100
+ }
101
+ // Check-mode: trust the disposition `rejected` bit. Do not re-derive from
102
+ // raw redactions.length — allowlist-pass records are auditable passes.
103
+ if (this.checkMode && flat.rejected)
104
+ rejected = true;
105
+ const result = {
106
+ attributes: out,
107
+ redactions,
108
+ findings,
109
+ rejected,
110
+ unresolvedFlags,
111
+ perClassCounts,
112
+ };
113
+ // Publish altitude: reject-publish classes abort loudly with a class name.
114
+ // Check-mode distill maps redactions → rejection reasons without throwing.
115
+ if (!this.checkMode)
116
+ assertNoRejectPublish(result, this.policy);
117
+ if (!this.checkMode && this.failClosedOnUnresolvedFlags) {
118
+ this.assertAndEnqueueUnresolvedFlags(result.unresolvedFlags ?? [], attributes);
119
+ }
120
+ return result;
121
+ }
122
+ assertAndEnqueueUnresolvedFlags(unresolved, attributes) {
123
+ if (unresolved.length === 0)
124
+ return;
125
+ // Lazy default: operator-local queue so unattended publishes leave a
126
+ // reviewable trail. Tests inject `reviewStore` (memory) or disable
127
+ // fail-closed via `failClosedOnUnresolvedFlags: false`.
128
+ const store = this.reviewStore ?? createReviewQueueStore();
129
+ const enqueued = store.enqueue(unresolved.map((finding) => {
130
+ const value = attributes[finding.span.key];
131
+ const text = typeof value === 'string' ? value : '';
132
+ const snippet = text.slice(Math.max(0, finding.span.start - 40), Math.min(text.length, finding.span.end + 40));
133
+ return {
134
+ finding,
135
+ context: { attributeKey: finding.span.key, snippet },
136
+ };
137
+ }));
138
+ throw new UnresolvedFlagError(unresolved, enqueued.map((e) => e.id));
139
+ }
140
+ /** Legacy ScrubStage chain (tests / residual injectors). */
141
+ async runLegacyStages(attributes) {
142
+ const flat = await this.runStagesFlat(attributes);
143
+ const out = {};
144
+ const redactions = [...flat.redactions];
145
+ for (const [key, value] of Object.entries(flat.attributes)) {
146
+ out[key] = typeof value === 'string' ? value : await this.scrubNestedLegacy(key, value, redactions);
147
+ }
148
+ return { attributes: out, redactions };
149
+ }
150
+ async runStagesFlat(attributes) {
151
+ let current = attributes;
152
+ const redactions = [];
153
+ for (const stage of this.stages) {
154
+ const result = await stage.scrub(current);
155
+ current = result.attributes;
156
+ redactions.push(...result.redactions);
157
+ }
158
+ return { attributes: current, redactions };
159
+ }
160
+ async scrubNestedLegacy(key, value, redactions) {
161
+ if (typeof value === 'string') {
162
+ const result = await this.runStagesFlat({ [key]: value });
163
+ redactions.push(...result.redactions);
164
+ const scrubbed = result.attributes[key];
165
+ return typeof scrubbed === 'string' ? scrubbed : value;
166
+ }
167
+ if (Array.isArray(value)) {
168
+ const items = [];
169
+ for (const item of value)
170
+ items.push(await this.scrubNestedLegacy(key, item, redactions));
171
+ return items;
172
+ }
173
+ if (value !== null && typeof value === 'object') {
174
+ const proto = Object.getPrototypeOf(value);
175
+ if (proto !== Object.prototype && proto !== null)
176
+ return value;
177
+ const entries = {};
178
+ for (const [k, v] of Object.entries(value)) {
179
+ entries[k] = await this.scrubNestedLegacy(key, v, redactions);
180
+ }
181
+ return entries;
182
+ }
183
+ return value;
184
+ }
185
+ async runDetectDispose(attributes) {
186
+ const findings = [];
187
+ for (const detector of this.detectors) {
188
+ findings.push(...(await detector.detect(attributes)));
189
+ }
190
+ return applyDispositions(attributes, findings, {
191
+ policy: this.policy,
192
+ checkMode: this.checkMode,
193
+ reviewStore: this.reviewStore,
194
+ });
195
+ }
196
+ async scrubNested(key, value, redactions, findings, unresolvedFlags, onCounts) {
197
+ if (typeof value === 'string') {
198
+ const result = await this.runDetectDispose({ [key]: value });
199
+ redactions.push(...result.redactions);
200
+ findings.push(...result.findings);
201
+ unresolvedFlags.push(...result.unresolvedFlags);
202
+ onCounts(result.perClassCounts);
203
+ const scrubbed = result.attributes[key];
204
+ return typeof scrubbed === 'string' ? scrubbed : value;
205
+ }
206
+ if (Array.isArray(value)) {
207
+ const items = [];
208
+ for (const item of value) {
209
+ items.push(await this.scrubNested(key, item, redactions, findings, unresolvedFlags, onCounts));
210
+ }
211
+ return items;
212
+ }
213
+ if (value !== null && typeof value === 'object') {
214
+ const proto = Object.getPrototypeOf(value);
215
+ if (proto !== Object.prototype && proto !== null)
216
+ return value;
217
+ const entries = {};
218
+ for (const [k, v] of Object.entries(value)) {
219
+ entries[k] = await this.scrubNested(key, v, redactions, findings, unresolvedFlags, onCounts);
220
+ }
221
+ return entries;
222
+ }
223
+ return value;
224
+ }
225
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * plain-patterns detector — deterministic regex findings for shapes the
3
+ * probabilistic detectors miss (#1330 / #1415 / #1959 / #1969):
4
+ *
5
+ * - email (B1)
6
+ * - POSIX home-directory paths (D1)
7
+ * - AWS/GCP credential-ID prefixes (A1)
8
+ * - Ethereum-style wallet addresses `0x`+40 hex (C1)
9
+ *
10
+ * Emits findings only — disposition owns stubs. Wallet + credential-ID shapes
11
+ * are always registered in the shared inventory (#1969); policy decides.
12
+ */
13
+ import { type KeyPolicy } from './key-policy.js';
14
+ import type { Detector } from './finding.js';
15
+ import type { ScrubStage } from './types.js';
16
+ export interface PlainPatternsOptions {
17
+ /**
18
+ * @deprecated (#1969) Credential IDs are always in the shared inventory.
19
+ * Kept so call sites that pass `{ credentialIds: true }` still type-check.
20
+ */
21
+ credentialIds?: boolean;
22
+ /**
23
+ * @deprecated (#1969) Wallet addresses are always in the shared inventory.
24
+ * Kept so call sites that pass `{ walletAddresses: true }` still type-check.
25
+ */
26
+ walletAddresses?: boolean;
27
+ }
28
+ export declare function plainPatternsDetector(policy: KeyPolicy, _opts?: PlainPatternsOptions): Detector;
29
+ /**
30
+ * Legacy ScrubStage wrapper: detect + apply default dispositions. New callers
31
+ * should use {@link plainPatternsDetector} via ScrubPipeline.
32
+ */
33
+ export declare function plainPatternsStage(policy: KeyPolicy, opts?: PlainPatternsOptions): ScrubStage;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * plain-patterns detector — deterministic regex findings for shapes the
3
+ * probabilistic detectors miss (#1330 / #1415 / #1959 / #1969):
4
+ *
5
+ * - email (B1)
6
+ * - POSIX home-directory paths (D1)
7
+ * - AWS/GCP credential-ID prefixes (A1)
8
+ * - Ethereum-style wallet addresses `0x`+40 hex (C1)
9
+ *
10
+ * Emits findings only — disposition owns stubs. Wallet + credential-ID shapes
11
+ * are always registered in the shared inventory (#1969); policy decides.
12
+ */
13
+ import { applyDispositions } from './apply-dispositions.js';
14
+ import { classifyKey } from './key-policy.js';
15
+ const VERSION = '0.2.0';
16
+ /** Plain email shape. */
17
+ const EMAIL_PATTERN = /[A-Za-z0-9._%+-]+@[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])+)+/g;
18
+ /** POSIX home-dir path segment carrying a username. */
19
+ const HOME_PATH_PATTERN = /\/(?:Users|home)\/[^/\s"'`]+/g;
20
+ /**
21
+ * Bare AWS access-key ID — the fixed four-char prefixes + 16 key chars that
22
+ * secretlint's aws rule only scans for under `enableIDScanRule: true`.
23
+ */
24
+ const AWS_KEY_ID_PATTERN = /\b(?:A3T[A-Z0-9]|AKIA|AGPA|AIDA|AROA|AIPA|ANPA|ANVA|ASIA)[A-Z0-9]{16}\b/g;
25
+ /**
26
+ * GCP API key: fixed `AIza` prefix + 35 key chars. No trailing `\b` — the key
27
+ * charset includes `-`, which breaks word-boundary semantics at the end.
28
+ */
29
+ const GCP_API_KEY_PATTERN = /\bAIza[0-9A-Za-z_-]{35}/g;
30
+ /**
31
+ * Ethereum-style wallet address: `0x` + exactly 40 hex chars (#1959 / C1).
32
+ * Bare 40-hex git SHAs and `0x`+64 tx hashes must survive.
33
+ */
34
+ const ETH_ADDRESS_PATTERN = /\b0x[a-fA-F0-9]{40}\b/g;
35
+ const RULES = [
36
+ { pattern: EMAIL_PATTERN, class: 'B1', evidence: 'email' },
37
+ { pattern: HOME_PATH_PATTERN, class: 'D1', evidence: 'home-path' },
38
+ { pattern: AWS_KEY_ID_PATTERN, class: 'A1', evidence: 'aws-access-key-id' },
39
+ { pattern: GCP_API_KEY_PATTERN, class: 'A1', evidence: 'gcp-api-key' },
40
+ { pattern: ETH_ADDRESS_PATTERN, class: 'C1', evidence: 'eth-address' },
41
+ ];
42
+ function collectMatches(text, key, rule, detector) {
43
+ const findings = [];
44
+ const re = new RegExp(rule.pattern.source, rule.pattern.flags.includes('g') ? rule.pattern.flags : `${rule.pattern.flags}g`);
45
+ let match;
46
+ while ((match = re.exec(text)) !== null) {
47
+ findings.push({
48
+ class: rule.class,
49
+ span: { key, start: match.index, end: match.index + match[0].length },
50
+ confidence: 'VERY_HIGH',
51
+ evidence: [rule.evidence],
52
+ detector,
53
+ });
54
+ }
55
+ return findings;
56
+ }
57
+ export function plainPatternsDetector(policy, _opts = {}) {
58
+ const meta = { name: 'plain-patterns', version: VERSION };
59
+ return {
60
+ ...meta,
61
+ detect(attributes) {
62
+ const findings = [];
63
+ for (const [key, value] of Object.entries(attributes)) {
64
+ if (typeof value !== 'string' || classifyKey(key, policy) !== 'content')
65
+ continue;
66
+ for (const rule of RULES) {
67
+ findings.push(...collectMatches(value, key, rule, meta));
68
+ }
69
+ }
70
+ return findings;
71
+ },
72
+ };
73
+ }
74
+ /**
75
+ * Legacy ScrubStage wrapper: detect + apply default dispositions. New callers
76
+ * should use {@link plainPatternsDetector} via ScrubPipeline.
77
+ */
78
+ export function plainPatternsStage(policy, opts = {}) {
79
+ const detector = plainPatternsDetector(policy, opts);
80
+ return {
81
+ name: detector.name,
82
+ version: detector.version,
83
+ scrub(attributes) {
84
+ const findings = detector.detect(attributes);
85
+ return applyDispositions(attributes, findings);
86
+ },
87
+ };
88
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Versioned disposition policy (#1969 / design §6.5).
3
+ *
4
+ * disposition(class, band) -> redact | reject-publish | flag | pass
5
+ *
6
+ * Defaults follow §3.2's rightmost columns. C2 stays `pass` until the flag
7
+ * review surface exists (do not redact 0x+64 tx hashes).
8
+ */
9
+ import type { Band, Finding, ScrubClass } from './finding.js';
10
+ export type Disposition = 'redact' | 'reject-publish' | 'flag' | 'pass';
11
+ export declare const POLICY_VERSION = "1.0.0";
12
+ /** Per-band dispositions for one class. Missing bands fall back to `pass`. */
13
+ export type ClassDispositionRow = Partial<Record<Band, Disposition>>;
14
+ export interface PolicyTable {
15
+ version: string;
16
+ dispositions: Partial<Record<ScrubClass, ClassDispositionRow>>;
17
+ }
18
+ /**
19
+ * Default policy. High-confidence deterministic classes redact; reject classes
20
+ * (A4/A5) reject-publish; A2 mid-band flags; C2 passes pending flag surface.
21
+ */
22
+ export declare const DEFAULT_POLICY: PolicyTable;
23
+ export declare function resolveDisposition(scrubClass: ScrubClass, band: Band, policy?: PolicyTable): Disposition;
24
+ /**
25
+ * Check-mode mapping (design §6.5): any non-`pass` disposition → reject.
26
+ * One mapping line — not a second pipeline.
27
+ */
28
+ export declare function checkModeRejects(findings: Finding[], policy?: PolicyTable): boolean;
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Versioned disposition policy (#1969 / design §6.5).
3
+ *
4
+ * disposition(class, band) -> redact | reject-publish | flag | pass
5
+ *
6
+ * Defaults follow §3.2's rightmost columns. C2 stays `pass` until the flag
7
+ * review surface exists (do not redact 0x+64 tx hashes).
8
+ */
9
+ export const POLICY_VERSION = '1.0.0';
10
+ /**
11
+ * Default policy. High-confidence deterministic classes redact; reject classes
12
+ * (A4/A5) reject-publish; A2 mid-band flags; C2 passes pending flag surface.
13
+ */
14
+ export const DEFAULT_POLICY = {
15
+ version: POLICY_VERSION,
16
+ dispositions: {
17
+ A1: {
18
+ VERY_HIGH: 'redact',
19
+ HIGH: 'redact',
20
+ MEDIUM: 'redact',
21
+ LOW: 'flag',
22
+ VERY_LOW: 'pass',
23
+ },
24
+ A2: {
25
+ VERY_HIGH: 'redact',
26
+ HIGH: 'redact',
27
+ MEDIUM: 'flag',
28
+ LOW: 'pass',
29
+ VERY_LOW: 'pass',
30
+ },
31
+ A3: {
32
+ VERY_HIGH: 'redact',
33
+ HIGH: 'redact',
34
+ MEDIUM: 'redact',
35
+ LOW: 'flag',
36
+ VERY_LOW: 'pass',
37
+ },
38
+ A4: {
39
+ VERY_HIGH: 'reject-publish',
40
+ HIGH: 'reject-publish',
41
+ MEDIUM: 'reject-publish',
42
+ LOW: 'flag',
43
+ VERY_LOW: 'pass',
44
+ },
45
+ A5: {
46
+ VERY_HIGH: 'reject-publish',
47
+ HIGH: 'reject-publish',
48
+ MEDIUM: 'reject-publish',
49
+ LOW: 'flag',
50
+ VERY_LOW: 'pass',
51
+ },
52
+ B1: {
53
+ VERY_HIGH: 'redact',
54
+ HIGH: 'redact',
55
+ MEDIUM: 'redact',
56
+ LOW: 'flag',
57
+ VERY_LOW: 'pass',
58
+ },
59
+ B2: {
60
+ VERY_HIGH: 'redact',
61
+ HIGH: 'redact',
62
+ MEDIUM: 'redact',
63
+ LOW: 'flag',
64
+ VERY_LOW: 'pass',
65
+ },
66
+ B3: {
67
+ VERY_HIGH: 'redact',
68
+ HIGH: 'flag',
69
+ MEDIUM: 'flag',
70
+ LOW: 'pass',
71
+ VERY_LOW: 'pass',
72
+ },
73
+ B4: {
74
+ VERY_HIGH: 'redact',
75
+ HIGH: 'redact',
76
+ MEDIUM: 'flag',
77
+ LOW: 'pass',
78
+ VERY_LOW: 'pass',
79
+ },
80
+ B5: {
81
+ VERY_HIGH: 'redact',
82
+ HIGH: 'redact',
83
+ MEDIUM: 'flag',
84
+ LOW: 'pass',
85
+ VERY_LOW: 'pass',
86
+ },
87
+ B6: {
88
+ VERY_HIGH: 'flag',
89
+ HIGH: 'flag',
90
+ MEDIUM: 'flag',
91
+ LOW: 'pass',
92
+ VERY_LOW: 'pass',
93
+ },
94
+ B7: {
95
+ VERY_HIGH: 'redact',
96
+ HIGH: 'redact',
97
+ MEDIUM: 'flag',
98
+ LOW: 'pass',
99
+ VERY_LOW: 'pass',
100
+ },
101
+ C1: {
102
+ VERY_HIGH: 'redact',
103
+ HIGH: 'redact',
104
+ MEDIUM: 'redact',
105
+ LOW: 'flag',
106
+ VERY_LOW: 'pass',
107
+ },
108
+ // C2: §3.2 default is flag, but provenance receipts (0x+64 tx hashes) must
109
+ // survive — leave as pass (locked Q / deferred from review-queue landing).
110
+ C2: {
111
+ VERY_HIGH: 'pass',
112
+ HIGH: 'pass',
113
+ MEDIUM: 'pass',
114
+ LOW: 'pass',
115
+ VERY_LOW: 'pass',
116
+ },
117
+ D1: {
118
+ VERY_HIGH: 'redact',
119
+ HIGH: 'redact',
120
+ MEDIUM: 'redact',
121
+ LOW: 'flag',
122
+ VERY_LOW: 'pass',
123
+ },
124
+ D2: {
125
+ VERY_HIGH: 'redact',
126
+ HIGH: 'redact',
127
+ MEDIUM: 'flag',
128
+ LOW: 'pass',
129
+ VERY_LOW: 'pass',
130
+ },
131
+ D3: {
132
+ VERY_HIGH: 'redact',
133
+ HIGH: 'redact',
134
+ MEDIUM: 'flag',
135
+ LOW: 'pass',
136
+ VERY_LOW: 'pass',
137
+ },
138
+ E1: {
139
+ VERY_HIGH: 'flag',
140
+ HIGH: 'flag',
141
+ MEDIUM: 'flag',
142
+ LOW: 'pass',
143
+ VERY_LOW: 'pass',
144
+ },
145
+ },
146
+ };
147
+ export function resolveDisposition(scrubClass, band, policy = DEFAULT_POLICY) {
148
+ return policy.dispositions[scrubClass]?.[band] ?? 'pass';
149
+ }
150
+ /**
151
+ * Check-mode mapping (design §6.5): any non-`pass` disposition → reject.
152
+ * One mapping line — not a second pipeline.
153
+ */
154
+ export function checkModeRejects(findings, policy = DEFAULT_POLICY) {
155
+ return findings.some((f) => resolveDisposition(f.class, f.confidence, policy) !== 'pass');
156
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Scrub provenance for the published redaction manifest (#1974 / design §6.7).
3
+ *
4
+ * Locked Q5: additive optional fields on the existing redaction-manifest shape
5
+ * (`schemaVersion` 2) — no TraceEnvelope rev. Old envelopes remain readable.
6
+ */
7
+ import type { ScrubClass } from './finding.js';
8
+ import type { Disposition, PolicyTable } from './policy.js';
9
+ /** Manifest schema version that includes optional provenance fields. */
10
+ export declare const REDACTION_MANIFEST_SCHEMA_VERSION = 2;
11
+ /** Disposition kinds counted in `perClassCounts` (pass is omitted). */
12
+ export type CountedDisposition = 'redact' | 'flag' | 'reject';
13
+ export interface PolicyHashInput {
14
+ /** Versioned disposition policy table. */
15
+ policy: PolicyTable;
16
+ /** Detector inventory (name + pinned version), order-insensitive. */
17
+ detectors: ReadonlyArray<{
18
+ name: string;
19
+ version: string;
20
+ }>;
21
+ /** ML model id when the ML tier is present; null otherwise. */
22
+ modelId: string | null;
23
+ /** Zero-shot / NER label set hashed into the policy digest. */
24
+ labels: readonly string[];
25
+ /** Digest of the instance allowlist (see {@link computeAllowlistDigest}). */
26
+ allowlistDigest: string;
27
+ }
28
+ /**
29
+ * Stable key for one class × applied disposition in `perClassCounts`.
30
+ * Reject-publish dispositions are recorded as `reject`.
31
+ */
32
+ export declare function perClassCountKey(scrubClass: ScrubClass, disposition: CountedDisposition): string;
33
+ /** Map a policy disposition onto the counted kind (or null for pass). */
34
+ export declare function countedDisposition(disposition: Disposition): CountedDisposition | null;
35
+ /**
36
+ * sha256 hex over a canonical JSON of allowlist entries sorted by
37
+ * (value, kind). Provenance notes are excluded so the digest is about
38
+ * *what* is allowlisted, not the human rationale string.
39
+ */
40
+ export declare function computeAllowlistDigest(entries: ReadonlyArray<{
41
+ value: string;
42
+ kind: string;
43
+ }>): string;
44
+ /**
45
+ * Reproducible policy/composition hash: same inputs → same hex digest.
46
+ * Payload is RFC 8785 canonical JSON of policy + sorted detectors + model
47
+ * id + sorted labels + allowlist digest, then sha256.
48
+ */
49
+ export declare function computePolicyHash(input: PolicyHashInput): string;
50
+ /** Increment one class:disposition counter (mutates `counts`). */
51
+ export declare function incrementPerClassCount(counts: Record<string, number>, scrubClass: ScrubClass, disposition: Disposition): void;
52
+ /** Sum two per-class count maps (missing → empty). */
53
+ export declare function mergePerClassCounts(a?: Record<string, number>, b?: Record<string, number>): Record<string, number> | undefined;