@optimuslabs/pii-scrub-staging 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,27 @@
1
- # Temporary Holding Version
1
+ # @optimuslabs/pii-scrub
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Redacts secrets from what an Optimus endpoint sends, before the request leaves the machine. Personal
4
+ data (emails, home directories) is not redacted: the backend reads it for owners, tenants and path
5
+ matching. Used by `@optimuslabs/runtime`
6
+ (tool calls), `@optimuslabs/harness-map` (config file inventory) and `@optimuslabs/discovery`
7
+ (which sends the version). Design: `docs/pii-scrubbing/DESIGN.md`.
8
+
9
+ ```ts
10
+ import { SCRUBBER_VERSION, scrub, scrubText, scrubValue } from "@optimuslabs/pii-scrub";
11
+
12
+ scrubValue({ env: { BRAVE_API_KEY: "BSA..." } }); // { env: { BRAVE_API_KEY: "[OL_REDACTED:CREDENTIAL_ENV_VAR]" } }
13
+ scrubText("key sk-ant-api03-...", { fingerprintKey }); // "key sk-ant-api03-[OL_REDACTED:ANTHROPIC_KEY:<24 hex>]"
14
+ scrubValue({ command: "npx", env: { LOG_LEVEL: "debug" } }); // unchanged: only secrets are redacted
15
+ scrub(payload).hits; // { ANTHROPIC_KEY: 1, ... }; placeholders already there are not hits
16
+ ```
17
+
18
+ The rules are in `rules.json`, which the backend reads too; every pattern works in both the
19
+ JavaScript and Python regex engines. Only vendor-format secrets are fingerprinted (HMAC-SHA256
20
+ under the org key, 96 bits), and only when the caller passes the key.
21
+
22
+ An organization can switch scrubbing off (OPT-942). The senders store the backend's `scrub_pii` in
23
+ `~/.optimuslabs/management/org/config.json`, and every function here returns its input unchanged
24
+ while that file says `"scrub_pii": false`. No file, or no field, means scrub.
25
+
26
+ The staging build publishes as `@optimuslabs/pii-scrub-staging`, the production build as
27
+ `@optimuslabs/pii-scrub`.
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Redact secrets from what an endpoint sends to Optimus. Personal data (emails, home directories)
3
+ * is not redacted: the backend reads it for identity, owners and path matching.
4
+ *
5
+ * Runs on the endpoint, just before a request is built, so the value never leaves the machine.
6
+ * A value is replaced with a typed placeholder rather than dropped, so the backend still knows a
7
+ * credential was there and of what kind:
8
+ *
9
+ * sk-ant-api03-[OL_REDACTED:ANTHROPIC_KEY:<fp>] a vendor key keeps its published prefix
10
+ * [OL_REDACTED:CREDENTIAL_ENV_VAR] a value under a credential-named key
11
+ * [OL_REDACTED:POTENTIAL_TOKEN] a 40+ character token in an MCP server's env
12
+ *
13
+ * `<fp>` is a 96-bit fingerprint (24 hex characters, HMAC-SHA256 under the org's key), on
14
+ * vendor-format secrets only, so the same key is recognisable across configs and machines
15
+ * without being held. A credential-named value with no known format could be a guessable
16
+ * password, so it gets none. Without a key, nothing gets one.
17
+ *
18
+ * Structure first, then text: a 29-character API key matches no format, but nobody names a
19
+ * variable `BRAVE_API_KEY` unless it holds one. A placeholder already in the input is left as it
20
+ * is and is not a hit, so scrubbing twice changes nothing and counts nothing.
21
+ */
22
+ export { SECRET_FORMATS } from "./rules.js";
23
+ export { scrubbingSwitchedOff } from "./org-setting.js";
24
+ /** This scrubber's version (the package's), which every sender includes with what it scrubbed. */
25
+ export declare const SCRUBBER_VERSION: string;
26
+ export declare const PLACEHOLDER_PREFIX = "[OL_REDACTED:";
27
+ export interface ScrubOptions {
28
+ /** The org's fingerprint key, hex. Absent until the endpoint has received it: no fingerprints. */
29
+ fingerprintKey?: string;
30
+ }
31
+ /** How many of each type were redacted. Placeholders already in the input are not counted. */
32
+ export type ScrubHits = Record<string, number>;
33
+ export interface ScrubResult<T> {
34
+ value: T;
35
+ hits: ScrubHits;
36
+ }
37
+ export declare function placeholder(type: string, fingerprint?: string): string;
38
+ /**
39
+ * A deep copy of `value` with every string scrubbed and every value under a credential-named key
40
+ * redacted, and how many of each type were redacted. Non-string leaves are returned as they are.
41
+ * When the organization has switched scrubbing off, `value` itself, with no hits.
42
+ */
43
+ export declare function scrub<T>(value: T, options?: ScrubOptions): ScrubResult<T>;
44
+ /** `scrub(value).value`. */
45
+ export declare function scrubValue<T>(value: T, options?: ScrubOptions): T;
46
+ /**
47
+ * A copy of `text` with every secret redacted;
48
+ * `text` itself when the organization has switched scrubbing off.
49
+ */
50
+ export declare function scrubText(text: string, options?: ScrubOptions): string;
51
+ /**
52
+ * A copy of `record` with each of `fields` that is a string scrubbed as text, the rest untouched:
53
+ * for log lines and run records (`command`, `message`, `detail`), where the fields are free text.
54
+ */
55
+ export declare function scrubFields<T extends Record<string, unknown>>(record: T, fields: readonly string[], options?: ScrubOptions): T;
56
+ /** Whether `value` holds a placeholder anywhere (to refuse writing scrubbed data back to disk). */
57
+ export declare function containsPlaceholder(value: unknown): boolean;
58
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAyBH,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,kGAAkG;AAClG,eAAO,MAAM,gBAAgB,EAAE,MAA2F,CAAC;AAE3H,eAAO,MAAM,kBAAkB,kBAAkB,CAAC;AAElD,MAAM,WAAW,YAAY;IAC3B,kGAAkG;IAClG,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,8FAA8F;AAC9F,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE/C,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,KAAK,EAAE,CAAC,CAAC;IACT,IAAI,EAAE,SAAS,CAAC;CACjB;AAcD,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtE;AA4MD;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,GAAE,YAAiB,GAAG,WAAW,CAAC,CAAC,CAAC,CAI7E;AAED,4BAA4B;AAC5B,wBAAgB,UAAU,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,GAAE,YAAiB,GAAG,CAAC,CAErE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAG1E;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC3D,MAAM,EAAE,CAAC,EACT,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,OAAO,GAAE,YAAiB,GACzB,CAAC,CAOH;AAED,mGAAmG;AACnG,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAO3D"}
package/dist/index.js ADDED
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Redact secrets from what an endpoint sends to Optimus. Personal data (emails, home directories)
3
+ * is not redacted: the backend reads it for identity, owners and path matching.
4
+ *
5
+ * Runs on the endpoint, just before a request is built, so the value never leaves the machine.
6
+ * A value is replaced with a typed placeholder rather than dropped, so the backend still knows a
7
+ * credential was there and of what kind:
8
+ *
9
+ * sk-ant-api03-[OL_REDACTED:ANTHROPIC_KEY:<fp>] a vendor key keeps its published prefix
10
+ * [OL_REDACTED:CREDENTIAL_ENV_VAR] a value under a credential-named key
11
+ * [OL_REDACTED:POTENTIAL_TOKEN] a 40+ character token in an MCP server's env
12
+ *
13
+ * `<fp>` is a 96-bit fingerprint (24 hex characters, HMAC-SHA256 under the org's key), on
14
+ * vendor-format secrets only, so the same key is recognisable across configs and machines
15
+ * without being held. A credential-named value with no known format could be a guessable
16
+ * password, so it gets none. Without a key, nothing gets one.
17
+ *
18
+ * Structure first, then text: a 29-character API key matches no format, but nobody names a
19
+ * variable `BRAVE_API_KEY` unless it holds one. A placeholder already in the input is left as it
20
+ * is and is not a hit, so scrubbing twice changes nothing and counts nothing.
21
+ */
22
+ import { createHmac } from "node:crypto";
23
+ import { createRequire } from "node:module";
24
+ import { CODE_VALUE, CONTAINER_KEEP, CONTAINS_REFERENCE, CREDENTIAL_ASSIGNMENT, CREDENTIAL_CONTAINER, CREDENTIAL_FLAG, CREDENTIAL_KEY, ENV_REFERENCE, GENERIC_TOKEN, JSON_SCHEMA_TYPES, MCP_SERVER_KEYS, MCP_VALUE_BLOCKS, MIN_CREDENTIAL_LENGTH, NOT_A_SECRET_UNDER_WEAK_KEY, PLACEHOLDER_VALUE, SECRET_FORMATS, WEAK_CREDENTIAL_KEY, } from "./rules.js";
25
+ import { scrubbingSwitchedOff } from "./org-setting.js";
26
+ export { SECRET_FORMATS } from "./rules.js";
27
+ export { scrubbingSwitchedOff } from "./org-setting.js";
28
+ /** This scrubber's version (the package's), which every sender includes with what it scrubbed. */
29
+ export const SCRUBBER_VERSION = createRequire(import.meta.url)("../package.json").version;
30
+ export const PLACEHOLDER_PREFIX = "[OL_REDACTED:";
31
+ /**
32
+ * How deep a payload may nest before the scrubber stops descending (and redacts the rest). MCP tool
33
+ * input schemas in agent configs nest past 32 (measured on prod data), so the limit sits well above.
34
+ */
35
+ const MAX_DEPTH = 128;
36
+ const FINGERPRINT_HEX_LENGTH = 24;
37
+ export function placeholder(type, fingerprint) {
38
+ return fingerprint ? `${PLACEHOLDER_PREFIX}${type}:${fingerprint}]` : `${PLACEHOLDER_PREFIX}${type}]`;
39
+ }
40
+ function redacted(pass, type, fingerprint) {
41
+ pass.hits[type] = (pass.hits[type] ?? 0) + 1;
42
+ return placeholder(type, fingerprint);
43
+ }
44
+ function fingerprintOf(pass, secret) {
45
+ const key = pass.options.fingerprintKey;
46
+ if (!key)
47
+ return undefined;
48
+ return createHmac("sha256", Buffer.from(key, "hex")).update(secret).digest("hex").slice(0, FINGERPRINT_HEX_LENGTH);
49
+ }
50
+ /** A value worth redacting under a credential-named key: not a flag, a reference or a placeholder. */
51
+ function isCredentialValue(value) {
52
+ const trimmed = value.trim();
53
+ return (trimmed.length >= MIN_CREDENTIAL_LENGTH &&
54
+ !/\s/.test(trimmed) &&
55
+ !ENV_REFERENCE.test(trimmed) &&
56
+ !PLACEHOLDER_VALUE.test(trimmed) &&
57
+ // A placeholder, or the front of one that a text rule cut at its closing `]`.
58
+ !trimmed.includes(PLACEHOLDER_PREFIX));
59
+ }
60
+ /**
61
+ * Whether `name` names a credential, for its `value`. A bare `_key` name (`DEPLOY_SSH_KEY`, but
62
+ * also `issue_key`, `session_key`) counts only when the value is not an identifier of something else.
63
+ */
64
+ function isCredentialName(name, value) {
65
+ if (CREDENTIAL_KEY.test(name))
66
+ return true;
67
+ return WEAK_CREDENTIAL_KEY.test(name) && !NOT_A_SECRET_UNDER_WEAK_KEY.test(value.trim());
68
+ }
69
+ /** A reference to a secret stored elsewhere, or a documentation placeholder where one goes. */
70
+ function isReferenceOrPlaceholder(secret) {
71
+ return CONTAINS_REFERENCE.test(secret) || ENV_REFERENCE.test(secret) || PLACEHOLDER_VALUE.test(secret);
72
+ }
73
+ function redactFormats(text, pass) {
74
+ let out = text;
75
+ for (const format of SECRET_FORMATS) {
76
+ out = out.replace(format.pattern, (match, prefix) => {
77
+ // Over a placeholder already written (`https://[OL_REDACTED:URL_CREDENTIALS]@host`): left as
78
+ // it is, so scrubbing twice changes nothing and counts nothing.
79
+ if (match.includes(PLACEHOLDER_PREFIX))
80
+ return match;
81
+ const kept = typeof prefix === "string" ? prefix : "";
82
+ if (format.skipPlaceholders && isReferenceOrPlaceholder(match.slice(kept.length)))
83
+ return match;
84
+ const fingerprint = format.fingerprint ? fingerprintOf(pass, match) : undefined;
85
+ return `${kept}${redacted(pass, format.type, fingerprint)}`;
86
+ });
87
+ }
88
+ return out;
89
+ }
90
+ /** Whether `offset` falls inside a placeholder already written, such as `[OL_REDACTED:GITHUB_TOKEN:<fp>]`. */
91
+ function insidePlaceholder(text, offset) {
92
+ const open = text.lastIndexOf(PLACEHOLDER_PREFIX, offset);
93
+ return open !== -1 && text.indexOf("]", open) >= offset;
94
+ }
95
+ function redactAssignments(text, pass) {
96
+ return text.replace(CREDENTIAL_ASSIGNMENT, (match, name, value, offset) =>
97
+ // `credential = client.beta(` and `credential: data.credential` are code, not a secret; the
98
+ // `TOKEN:<fp>` of a fingerprinted placeholder is the placeholder, not an assignment.
99
+ isCredentialValue(value) && !CODE_VALUE.test(value) && !insidePlaceholder(text, offset)
100
+ ? `${name}${redacted(pass, "CREDENTIAL_ENV_VAR")}`
101
+ : match);
102
+ }
103
+ function scrubString(text, pass) {
104
+ if (!text)
105
+ return text;
106
+ return redactAssignments(redactFormats(text, pass), pass);
107
+ }
108
+ function scrubLeaf(value, pass, key) {
109
+ if (key !== undefined && isCredentialName(key, value) && isCredentialValue(value)) {
110
+ // A recognised format keeps its prefix, type and fingerprint; anything else is just a credential.
111
+ const formatted = redactFormats(value, pass);
112
+ return formatted !== value ? formatted : redacted(pass, "CREDENTIAL_ENV_VAR");
113
+ }
114
+ return scrubString(value, pass);
115
+ }
116
+ function isPlainObject(value) {
117
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
118
+ }
119
+ /** A JSON Schema node (an MCP tool's input schema): `{"type": "array", ...}` describes data, holds none. */
120
+ function isSchemaNode(value) {
121
+ if (!isPlainObject(value))
122
+ return false;
123
+ return typeof value.type === "string" && JSON_SCHEMA_TYPES.has(value.type);
124
+ }
125
+ /**
126
+ * An MCP server definition: a server key holding a string or list (`command`, `url`, `args`). A
127
+ * schema's `properties` map with a `type` or `url` property holds objects there, and is not one.
128
+ */
129
+ function isServerDefinition(obj) {
130
+ if (isSchemaNode(obj))
131
+ return false;
132
+ return Object.entries(obj).some(([k, v]) => MCP_SERVER_KEYS.has(k) && (typeof v === "string" || Array.isArray(v)));
133
+ }
134
+ /** Whether a value can be redacted whole: not empty, not a reference, not already a placeholder. */
135
+ function isRedactable(value) {
136
+ return Boolean(value.trim()) && !CONTAINS_REFERENCE.test(value) && !ENV_REFERENCE.test(value.trim()) && !value.includes(PLACEHOLDER_PREFIX);
137
+ }
138
+ /**
139
+ * The backend's generic token (`HardcodedSecretRule`'s "Potential token/secret"): 40 or more
140
+ * letters, digits, `_` and `-`. In a URL only the query is searched, as the backend does: a host
141
+ * or path is not a token.
142
+ */
143
+ function redactGenericTokens(value, pass) {
144
+ const replace = (text) => text.replace(GENERIC_TOKEN, (token) => (token.length >= 40 ? redacted(pass, "POTENTIAL_TOKEN") : token));
145
+ if (!value.includes("://"))
146
+ return replace(value);
147
+ const query = value.indexOf("?");
148
+ return query === -1 ? value : value.slice(0, query + 1) + replace(value.slice(query + 1));
149
+ }
150
+ /**
151
+ * A value in an MCP server's env or headers block, redacted only when it is a secret: a known
152
+ * format keeps its prefix, type and fingerprint; a credential-named one is a credential; a 40+
153
+ * character token is a potential token, as the backend reads one. Anything else (`LOG_LEVEL=debug`)
154
+ * stays readable, as before scrubbing. A reference to a secret stored elsewhere (`${TOKEN}`,
155
+ * `Bearer ${TOKEN}`) stays readable.
156
+ */
157
+ function redactBlockValue(value, name, pass) {
158
+ if (!isRedactable(value))
159
+ return value;
160
+ const formatted = redactFormats(value, pass);
161
+ if (formatted !== value)
162
+ return formatted;
163
+ if (isCredentialName(name, value) && isCredentialValue(value))
164
+ return redacted(pass, "CREDENTIAL_ENV_VAR");
165
+ return redactGenericTokens(redactAssignments(value, pass), pass);
166
+ }
167
+ function walkValueBlock(block, pass, depth) {
168
+ const out = {};
169
+ for (const [name, v] of Object.entries(block)) {
170
+ out[name] = typeof v === "string" ? redactBlockValue(v, name, pass) : walk(v, pass, name, depth + 1);
171
+ }
172
+ return out;
173
+ }
174
+ /** A value in a credential container: a credential whatever its shape (a known format keeps its type). */
175
+ function redactContainerValue(value, pass) {
176
+ if (!isRedactable(value))
177
+ return value;
178
+ const formatted = redactFormats(value, pass);
179
+ return formatted !== value ? formatted : redacted(pass, "CREDENTIAL_ENV_VAR");
180
+ }
181
+ /** `["--api-key", "BSA..."]`: the argument after a credential flag is the credential. */
182
+ function walkArgs(args, pass, depth) {
183
+ return args.map((arg, i) => {
184
+ const flag = typeof args[i - 1] === "string" ? CREDENTIAL_FLAG.exec(args[i - 1]) : null;
185
+ if (flag && typeof arg === "string" && isCredentialName(flag[1], arg) && isCredentialValue(arg)) {
186
+ const formatted = redactFormats(arg, pass);
187
+ return formatted !== arg ? formatted : redacted(pass, "CREDENTIAL_ENV_VAR");
188
+ }
189
+ return walk(arg, pass, "args", depth + 1);
190
+ });
191
+ }
192
+ /**
193
+ * A credential container (`copilotTokens: {"https://github.com:ada": "gho_..."}`): every string
194
+ * value is a credential whatever its key, so a token with no known format is still redacted.
195
+ * Fields ending in `id` stay readable.
196
+ */
197
+ function walkCredentialContainer(container, pass, depth) {
198
+ const out = {};
199
+ for (const [name, v] of Object.entries(container)) {
200
+ const isCredential = typeof v === "string" && !CONTAINER_KEEP.test(name);
201
+ out[name] = isCredential ? redactContainerValue(v, pass) : walk(v, pass, name, depth + 1);
202
+ }
203
+ return out;
204
+ }
205
+ /** One child of an object: MCP value blocks, credential containers and `args` get their structural rule. */
206
+ function walkChild(key, value, inServer, pass, depth) {
207
+ const isBlock = inServer && MCP_VALUE_BLOCKS.has(key);
208
+ const isData = isPlainObject(value) && !isSchemaNode(value);
209
+ if (isBlock && isData)
210
+ return walkValueBlock(value, pass, depth);
211
+ if (CREDENTIAL_CONTAINER.test(key) && isData)
212
+ return walkCredentialContainer(value, pass, depth);
213
+ if (key === "args" && Array.isArray(value))
214
+ return walkArgs(value, pass, depth);
215
+ return walk(value, pass, key, depth + 1);
216
+ }
217
+ function walkObject(obj, pass, depth) {
218
+ const inServer = isServerDefinition(obj);
219
+ const out = {};
220
+ for (const [k, v] of Object.entries(obj))
221
+ out[k] = walkChild(k, v, inServer, pass, depth);
222
+ return out;
223
+ }
224
+ function walk(value, pass, key, depth) {
225
+ // The TOO_DEEP placeholder a first pass left at this depth stays as it is.
226
+ if (depth > MAX_DEPTH)
227
+ return value === placeholder("TOO_DEEP") ? value : redacted(pass, "TOO_DEEP");
228
+ if (Array.isArray(value))
229
+ return value.map((item) => walk(item, pass, key, depth + 1));
230
+ if (isPlainObject(value))
231
+ return walkObject(value, pass, depth);
232
+ return typeof value === "string" ? scrubLeaf(value, pass, key) : value;
233
+ }
234
+ /**
235
+ * A deep copy of `value` with every string scrubbed and every value under a credential-named key
236
+ * redacted, and how many of each type were redacted. Non-string leaves are returned as they are.
237
+ * When the organization has switched scrubbing off, `value` itself, with no hits.
238
+ */
239
+ export function scrub(value, options = {}) {
240
+ if (scrubbingSwitchedOff())
241
+ return { value, hits: {} };
242
+ const pass = { options, hits: {} };
243
+ return { value: walk(value, pass, undefined, 0), hits: pass.hits };
244
+ }
245
+ /** `scrub(value).value`. */
246
+ export function scrubValue(value, options = {}) {
247
+ return scrub(value, options).value;
248
+ }
249
+ /**
250
+ * A copy of `text` with every secret redacted;
251
+ * `text` itself when the organization has switched scrubbing off.
252
+ */
253
+ export function scrubText(text, options = {}) {
254
+ if (scrubbingSwitchedOff())
255
+ return text;
256
+ return scrubString(text, { options, hits: {} });
257
+ }
258
+ /**
259
+ * A copy of `record` with each of `fields` that is a string scrubbed as text, the rest untouched:
260
+ * for log lines and run records (`command`, `message`, `detail`), where the fields are free text.
261
+ */
262
+ export function scrubFields(record, fields, options = {}) {
263
+ const out = { ...record };
264
+ if (scrubbingSwitchedOff())
265
+ return out;
266
+ for (const field of fields) {
267
+ if (typeof out[field] === "string")
268
+ out[field] = scrubText(out[field], options);
269
+ }
270
+ return out;
271
+ }
272
+ /** Whether `value` holds a placeholder anywhere (to refuse writing scrubbed data back to disk). */
273
+ export function containsPlaceholder(value) {
274
+ if (typeof value === "string")
275
+ return value.includes(PLACEHOLDER_PREFIX);
276
+ try {
277
+ return JSON.stringify(value ?? null).includes(PLACEHOLDER_PREFIX);
278
+ }
279
+ catch {
280
+ return false;
281
+ }
282
+ }
283
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Whether the organization has switched scrubbing off (OPT-942).
3
+ *
4
+ * The backend sends `scrub_pii` with the prompt policy on startup and inventory responses, and
5
+ * the senders store it in the org config file beside `collect_prompts`. Only an explicit `false`
6
+ * turns scrubbing off: no file yet, an unreadable one or no field means scrub, so an endpoint
7
+ * that has not heard from its organization yet scrubs.
8
+ *
9
+ * Read on every call rather than once per process, so a change reaches a sender as soon as the
10
+ * file is rewritten. The file is a few hundred bytes.
11
+ */
12
+ export declare function orgConfigPath(): string;
13
+ export declare function scrubbingSwitchedOff(): boolean;
14
+ //# sourceMappingURL=org-setting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-setting.d.ts","sourceRoot":"","sources":["../src/org-setting.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAMH,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED,wBAAgB,oBAAoB,IAAI,OAAO,CAQ9C"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Whether the organization has switched scrubbing off (OPT-942).
3
+ *
4
+ * The backend sends `scrub_pii` with the prompt policy on startup and inventory responses, and
5
+ * the senders store it in the org config file beside `collect_prompts`. Only an explicit `false`
6
+ * turns scrubbing off: no file yet, an unreadable one or no field means scrub, so an endpoint
7
+ * that has not heard from its organization yet scrubs.
8
+ *
9
+ * Read on every call rather than once per process, so a change reaches a sender as soon as the
10
+ * file is rewritten. The file is a few hundred bytes.
11
+ */
12
+ import { readFileSync } from "node:fs";
13
+ import { homedir } from "node:os";
14
+ import { join } from "node:path";
15
+ export function orgConfigPath() {
16
+ return join(homedir(), ".optimuslabs", "management", "org", "config.json");
17
+ }
18
+ export function scrubbingSwitchedOff() {
19
+ try {
20
+ const config = JSON.parse(readFileSync(orgConfigPath(), "utf8"));
21
+ return Boolean(config) && typeof config === "object" && config.scrub_pii === false;
22
+ }
23
+ catch {
24
+ // No file yet, or not JSON: scrub.
25
+ return false;
26
+ }
27
+ }
28
+ //# sourceMappingURL=org-setting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"org-setting.js","sourceRoot":"","sources":["../src/org-setting.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,UAAU,aAAa;IAC3B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,aAAa,EAAE,EAAE,MAAM,CAAC,CAAY,CAAC;QAC5E,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAK,MAAkC,CAAC,SAAS,KAAK,KAAK,CAAC;IAClH,CAAC;IAAC,MAAM,CAAC;QACP,mCAAmC;QACnC,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The scrubber's rules, loaded from `rules.json` at the package root.
3
+ *
4
+ * The rules are data, checked in, so the backend's safety net reads the very same file (and a
5
+ * Python test compiles every pattern in it). Each pattern is written for both the JavaScript and
6
+ * Python regex engines: no lookbehind, no named groups, no Unicode property escapes, and every
7
+ * repeat that can start a match is bounded, so a long run of letters scans in linear time.
8
+ *
9
+ * Formats run in file order, most specific first (Anthropic before the generic `sk-` OpenAI
10
+ * shape, vendor tokens before `Bearer`), so a value is typed by the most precise rule that
11
+ * matches. Group 1 of a format, when present, is the published vendor prefix that stays.
12
+ */
13
+ export interface SecretFormat {
14
+ type: string;
15
+ fingerprint: boolean;
16
+ skipPlaceholders: boolean;
17
+ /** Global, for `replace`. */
18
+ pattern: RegExp;
19
+ }
20
+ export declare const SECRET_FORMATS: readonly SecretFormat[];
21
+ export declare const CREDENTIAL_KEY: RegExp;
22
+ export declare const WEAK_CREDENTIAL_KEY: RegExp;
23
+ export declare const NOT_A_SECRET_UNDER_WEAK_KEY: RegExp;
24
+ export declare const CREDENTIAL_CONTAINER: RegExp;
25
+ export declare const CONTAINER_KEEP: RegExp;
26
+ export declare const CREDENTIAL_ASSIGNMENT: RegExp;
27
+ export declare const CODE_VALUE: RegExp;
28
+ export declare const ENV_REFERENCE: RegExp;
29
+ export declare const CONTAINS_REFERENCE: RegExp;
30
+ export declare const PLACEHOLDER_VALUE: RegExp;
31
+ export declare const CREDENTIAL_FLAG: RegExp;
32
+ /** The blocks of an MCP server definition whose values are checked for secrets (`env`, `headers`). */
33
+ export declare const MCP_VALUE_BLOCKS: ReadonlySet<string>;
34
+ /** The backend's generic token shape; a match of 40 or more characters is a potential token. */
35
+ export declare const GENERIC_TOKEN: RegExp;
36
+ export declare const MCP_SERVER_KEYS: ReadonlySet<string>;
37
+ export declare const JSON_SCHEMA_TYPES: ReadonlySet<string>;
38
+ export declare const MIN_CREDENTIAL_LENGTH: number;
39
+ //# sourceMappingURL=rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAkCH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,OAAO,CAAC;IACrB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;CACjB;AAWD,eAAO,MAAM,cAAc,EAAE,SAAS,YAAY,EAK/C,CAAC;AACJ,eAAO,MAAM,cAAc,QAAuC,CAAC;AACnE,eAAO,MAAM,mBAAmB,QAA4C,CAAC;AAC7E,eAAO,MAAM,2BAA2B,QAA6D,CAAC;AACtG,eAAO,MAAM,oBAAoB,QAA6C,CAAC;AAC/E,eAAO,MAAM,cAAc,QAAsD,CAAC;AAClF,eAAO,MAAM,qBAAqB,QAA6C,CAAC;AAChF,eAAO,MAAM,UAAU,QAAmC,CAAC;AAC3D,eAAO,MAAM,aAAa,QAAsC,CAAC;AACjE,eAAO,MAAM,kBAAkB,QAA2C,CAAC;AAC3E,eAAO,MAAM,iBAAiB,QAA0C,CAAC;AACzE,eAAO,MAAM,eAAe,QAAwC,CAAC;AACrE,sGAAsG;AACtG,eAAO,MAAM,gBAAgB,EAAE,WAAW,CAAC,MAAM,CAA0C,CAAC;AAC5F,gGAAgG;AAChG,eAAO,MAAM,aAAa,QAAqC,CAAC;AAChE,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,CAA+C,CAAC;AAChG,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAA0C,CAAC;AAC7F,eAAO,MAAM,qBAAqB,QAA8B,CAAC"}
package/dist/rules.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The scrubber's rules, loaded from `rules.json` at the package root.
3
+ *
4
+ * The rules are data, checked in, so the backend's safety net reads the very same file (and a
5
+ * Python test compiles every pattern in it). Each pattern is written for both the JavaScript and
6
+ * Python regex engines: no lookbehind, no named groups, no Unicode property escapes, and every
7
+ * repeat that can start a match is bounded, so a long run of letters scans in linear time.
8
+ *
9
+ * Formats run in file order, most specific first (Anthropic before the generic `sk-` OpenAI
10
+ * shape, vendor tokens before `Bearer`), so a value is typed by the most precise rule that
11
+ * matches. Group 1 of a format, when present, is the published vendor prefix that stays.
12
+ */
13
+ import { createRequire } from "node:module";
14
+ // dist/rules.js → ../rules.json, the file at the package root (shipped in `files`). Loaded with
15
+ // Node's JSON loader rather than `fs`, which a consumer's tests may mock.
16
+ const RULES = createRequire(import.meta.url)("../rules.json");
17
+ /** `global` for patterns used with `replace`; never for `test`, whose `lastIndex` would stick. */
18
+ function compile(spec, global) {
19
+ return new RegExp(spec.pattern, `${global ? "g" : ""}${spec.ignore_case ? "i" : ""}`);
20
+ }
21
+ export const SECRET_FORMATS = RULES.formats.map((f) => ({
22
+ type: f.type,
23
+ fingerprint: f.fingerprint,
24
+ skipPlaceholders: Boolean(f.skip_placeholders),
25
+ pattern: compile(f, true),
26
+ }));
27
+ export const CREDENTIAL_KEY = compile(RULES.credential_key, false);
28
+ export const WEAK_CREDENTIAL_KEY = compile(RULES.weak_credential_key, false);
29
+ export const NOT_A_SECRET_UNDER_WEAK_KEY = new RegExp(RULES.weak_credential_key.not_a_secret_pattern);
30
+ export const CREDENTIAL_CONTAINER = compile(RULES.credential_container, false);
31
+ export const CONTAINER_KEEP = new RegExp(RULES.credential_container.keep_pattern);
32
+ export const CREDENTIAL_ASSIGNMENT = compile(RULES.credential_assignment, true);
33
+ export const CODE_VALUE = compile(RULES.code_value, false);
34
+ export const ENV_REFERENCE = compile(RULES.env_reference, false);
35
+ export const CONTAINS_REFERENCE = compile(RULES.contains_reference, false);
36
+ export const PLACEHOLDER_VALUE = compile(RULES.placeholder_value, false);
37
+ export const CREDENTIAL_FLAG = compile(RULES.credential_flag, false);
38
+ /** The blocks of an MCP server definition whose values are checked for secrets (`env`, `headers`). */
39
+ export const MCP_VALUE_BLOCKS = new Set(RULES.mcp_value_blocks.blocks);
40
+ /** The backend's generic token shape; a match of 40 or more characters is a potential token. */
41
+ export const GENERIC_TOKEN = compile(RULES.generic_token, true);
42
+ export const MCP_SERVER_KEYS = new Set(RULES.mcp_value_blocks.server_keys);
43
+ export const JSON_SCHEMA_TYPES = new Set(RULES.json_schema_types.types);
44
+ export const MIN_CREDENTIAL_LENGTH = RULES.min_credential_length;
45
+ //# sourceMappingURL=rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rules.js","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAwC5C,gGAAgG;AAChG,0EAA0E;AAC1E,MAAM,KAAK,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,eAAe,CAAc,CAAC;AAE3E,kGAAkG;AAClG,SAAS,OAAO,CAAC,IAAiB,EAAE,MAAe;IACjD,OAAO,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACxF,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAA4B,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC/E,IAAI,EAAE,CAAC,CAAC,IAAI;IACZ,WAAW,EAAE,CAAC,CAAC,WAAW;IAC1B,gBAAgB,EAAE,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC;IAC9C,OAAO,EAAE,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC;CAC1B,CAAC,CAAC,CAAC;AACJ,MAAM,CAAC,MAAM,cAAc,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC;AACnE,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC,KAAK,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;AAC7E,MAAM,CAAC,MAAM,2BAA2B,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,oBAAoB,CAAC,CAAC;AACtG,MAAM,CAAC,MAAM,oBAAoB,GAAG,OAAO,CAAC,KAAK,CAAC,oBAAoB,EAAE,KAAK,CAAC,CAAC;AAC/E,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,YAAY,CAAC,CAAC;AAClF,MAAM,CAAC,MAAM,qBAAqB,GAAG,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC;AAChF,MAAM,CAAC,MAAM,UAAU,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;AAC3D,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;AACjE,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC,KAAK,CAAC,kBAAkB,EAAE,KAAK,CAAC,CAAC;AAC3E,MAAM,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;AACzE,MAAM,CAAC,MAAM,eAAe,GAAG,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC;AACrE,sGAAsG;AACtG,MAAM,CAAC,MAAM,gBAAgB,GAAwB,IAAI,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;AAC5F,gGAAgG;AAChG,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;AAChE,MAAM,CAAC,MAAM,eAAe,GAAwB,IAAI,GAAG,CAAC,KAAK,CAAC,gBAAgB,CAAC,WAAW,CAAC,CAAC;AAChG,MAAM,CAAC,MAAM,iBAAiB,GAAwB,IAAI,GAAG,CAAC,KAAK,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;AAC7F,MAAM,CAAC,MAAM,qBAAqB,GAAG,KAAK,CAAC,qBAAqB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,32 @@
1
1
  {
2
2
  "name": "@optimuslabs/pii-scrub-staging",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Redacts secrets and personal data from what an Optimus endpoint sends, before it leaves the machine.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist/**/*",
10
+ "!dist/test/**",
11
+ "rules.json",
12
+ "README.md"
13
+ ],
14
+ "scripts": {
15
+ "build": "rm -rf dist && tsc -p tsconfig.json",
16
+ "pretest": "npm run build",
17
+ "test": "node --test dist/test/*.test.js",
18
+ "prepack": "npm run build"
19
+ },
20
+ "engines": {
21
+ "node": ">=18"
22
+ },
23
+ "license": "UNLICENSED",
24
+ "dependencies": {},
25
+ "devDependencies": {
26
+ "@types/node": "^20.19.39",
27
+ "typescript": "^5.4.5"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ }
32
+ }
package/rules.json ADDED
@@ -0,0 +1,261 @@
1
+ {
2
+ "description": "Rules for @optimuslabs/pii-scrub, read by the endpoint scrubber and the backend. Every pattern is valid in both the JavaScript and Python regex engines; see docs/pii-scrubbing/DESIGN.md.",
3
+ "placeholder": {
4
+ "description": "A placeholder the scrubber wrote: left unchanged and never counted as a hit.",
5
+ "pattern": "\\[OL_REDACTED:[A-Z_]+(?::[0-9a-f]{24})?\\]"
6
+ },
7
+ "formats": [
8
+ {
9
+ "type": "PRIVATE_KEY",
10
+ "pattern": "-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY-----[\\s\\S]*?-----END (?:[A-Z0-9]+ )*PRIVATE KEY-----",
11
+ "fingerprint": true
12
+ },
13
+ {
14
+ "type": "URL_CREDENTIALS",
15
+ "pattern": "\\b([a-z][a-z0-9+.-]{0,31}:\\/\\/[^\\s:@/\"'`]{1,256}:)[^\\s@/\"'`]{1,256}(?=@)",
16
+ "ignore_case": true,
17
+ "fingerprint": false,
18
+ "skip_placeholders": true
19
+ },
20
+ {
21
+ "type": "URL_CREDENTIALS",
22
+ "pattern": "\\b([a-z][a-z0-9+.-]{0,31}:\\/\\/)[^\\s:@/\"'`]{16,255}(?=@)",
23
+ "ignore_case": true,
24
+ "fingerprint": false,
25
+ "skip_placeholders": true
26
+ },
27
+ {
28
+ "type": "ANTHROPIC_KEY",
29
+ "pattern": "\\b(sk-ant-[a-z]+\\d{2}-)[A-Za-z0-9_-]{20,}",
30
+ "fingerprint": true
31
+ },
32
+ {
33
+ "type": "OPENAI_KEY",
34
+ "pattern": "\\b(sk-(?:proj|svcacct|admin)-)[A-Za-z0-9_-]{20,}",
35
+ "fingerprint": true
36
+ },
37
+ {
38
+ "type": "OPENAI_KEY",
39
+ "pattern": "\\b(sk-)[A-Za-z0-9]{20,}",
40
+ "fingerprint": true
41
+ },
42
+ {
43
+ "type": "GITHUB_TOKEN",
44
+ "pattern": "\\b(gh[pousr]_)[A-Za-z0-9]{36,}",
45
+ "fingerprint": true
46
+ },
47
+ {
48
+ "type": "GITHUB_TOKEN",
49
+ "pattern": "\\b(github_pat_)[A-Za-z0-9_]{22,}",
50
+ "fingerprint": true
51
+ },
52
+ {
53
+ "type": "AWS_ACCESS_KEY",
54
+ "pattern": "\\b(AKIA|ASIA)[0-9A-Z]{16}\\b",
55
+ "fingerprint": true
56
+ },
57
+ {
58
+ "type": "STRIPE_KEY",
59
+ "pattern": "\\b((?:sk|rk)_(?:live|test)_)[A-Za-z0-9]{16,}",
60
+ "fingerprint": true
61
+ },
62
+ {
63
+ "type": "SLACK_TOKEN",
64
+ "pattern": "\\b(xox[abposr]-|xapp-)[A-Za-z0-9-]{10,}",
65
+ "fingerprint": true
66
+ },
67
+ {
68
+ "type": "GOOGLE_API_KEY",
69
+ "pattern": "\\b(AIza)[0-9A-Za-z_-]{35}",
70
+ "fingerprint": true
71
+ },
72
+ {
73
+ "type": "NPM_TOKEN",
74
+ "pattern": "\\b(npm_)[A-Za-z0-9]{36}\\b",
75
+ "fingerprint": true
76
+ },
77
+ {
78
+ "type": "PYPI_TOKEN",
79
+ "pattern": "\\b(pypi-)AgEIcHlwaS5vcmc[A-Za-z0-9_-]{50,}",
80
+ "fingerprint": true
81
+ },
82
+ {
83
+ "type": "SENDGRID_KEY",
84
+ "pattern": "\\b(SG\\.)[A-Za-z0-9_-]{22}\\.[A-Za-z0-9_-]{43}",
85
+ "fingerprint": true
86
+ },
87
+ {
88
+ "type": "HUGGINGFACE_TOKEN",
89
+ "pattern": "\\b(hf_)[A-Za-z0-9]{34,}",
90
+ "fingerprint": true
91
+ },
92
+ {
93
+ "type": "PERPLEXITY_KEY",
94
+ "pattern": "\\b(pplx-)[A-Za-z0-9]{32,}",
95
+ "fingerprint": true
96
+ },
97
+ {
98
+ "type": "BRAVE_KEY",
99
+ "pattern": "\\b(BSA)[A-Za-z0-9_-]{20,}",
100
+ "fingerprint": true
101
+ },
102
+ {
103
+ "type": "JWT",
104
+ "pattern": "\\beyJ[A-Za-z0-9_-]{8,}\\.eyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}",
105
+ "fingerprint": true
106
+ },
107
+ {
108
+ "type": "BEARER_TOKEN",
109
+ "pattern": "\\b(Bearer\\s+)[A-Za-z0-9._~+/=-]{16,}",
110
+ "ignore_case": true,
111
+ "fingerprint": false,
112
+ "skip_placeholders": true
113
+ },
114
+ {
115
+ "type": "BASIC_AUTH",
116
+ "pattern": "\\b(Basic\\s+)(?=[A-Za-z0-9+/=]{0,512}(?:[0-9+/=]|[a-z][A-Z]))(?:[A-Za-z0-9+/]{4}){2,128}(?:[A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)(?![A-Za-z0-9+/=])",
117
+ "fingerprint": false
118
+ }
119
+ ],
120
+ "credential_key": {
121
+ "pattern": "(?:api[_-]?key|apikey|secret|token|password|passwd|credential|private[_-]?key|(?:secret|signing|encryption)[_-]?key|access[_-]?key|authorization|token[_-]?cache(?:v\\d+)?)$",
122
+ "ignore_case": true
123
+ },
124
+ "weak_credential_key": {
125
+ "description": "A name ending in `_key` (DEPLOY_SSH_KEY, LICENSE_KEY) is a credential only when its value is not an identifier of something else: an issue key (INCIDENT-31138) or a session key of three or more colon-separated lowercase parts (agent:main:slack). Secret, signing and encryption keys are always credentials (credential_key).",
126
+ "pattern": "[_.-]key$",
127
+ "ignore_case": true,
128
+ "not_a_secret_pattern": "^[a-z0-9_-]+(?::[a-z0-9_+.-]+){2,}$|^[A-Z][A-Z0-9]{1,15}-[0-9]{1,9}$"
129
+ },
130
+ "credential_container": {
131
+ "description": "An object whose every string value is a credential, whatever its own key (Copilot's copilotTokens maps host:user to a token). Fields ending in id stay (Codex tokens.account_id).",
132
+ "pattern": "(?:tokens|secrets|credentials|api[_-]?keys)$",
133
+ "ignore_case": true,
134
+ "keep_pattern": "(?:^|[_-])id$|[a-z]Id$"
135
+ },
136
+ "credential_assignment": {
137
+ "pattern": "((?:api[_-]?key|apikey|secret|token|password|passwd|credential|private[_-]?key|access[_-]?key)[\"']?\\s*[:=]\\s*[\"']?)([^\\s\"'`,;&)}\\]]{8,})",
138
+ "ignore_case": true
139
+ },
140
+ "code_value": {
141
+ "description": "A value after a credential name in text that is code, not a secret: a call or block opener (`os.environ.get(`, `T.type_alias{`), a template (`{client_private_key}`, `<paste_key_here>`), or a variable named for a credential (`data.credential`, `walletKey`, `this.env.VAPID_PRIVATE_KEY`). A secret that merely contains `(` or `{` is still one.",
142
+ "pattern": "^[A-Za-z_$][\\w$.]*[({]$|^\\{[\\w.]{1,128}$|^<[^<>]{1,64}>$|^(?=[\\w$.]{0,256}(?:\\.|[a-z][A-Z]))(?=[\\w$.]{0,256}(?:[Kk]ey|KEY|[Ss]ecret|SECRET|[Tt]oken|TOKEN|[Pp]assw|PASSW|[Cc]redential|CREDENTIAL|\\.env\\.|\\.environ\\b))[A-Za-z_$][\\w$]*(?:\\.[A-Za-z_$][\\w$]*)*$"
143
+ },
144
+ "env_reference": {
145
+ "pattern": "^(?:\\$|\\{\\{|process\\.env\\.)|^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$"
146
+ },
147
+ "contains_reference": {
148
+ "description": "A reference to a secret stored elsewhere anywhere in a value (`Bearer ${TOKEN}`): the value stays readable.",
149
+ "pattern": "\\$\\{[^}]{1,128}\\}|\\$env:|process\\.env\\."
150
+ },
151
+ "placeholder_value": {
152
+ "description": "A documentation placeholder where a secret goes (`<your_key>`, `YOUR_API_TOKEN`, `my_secret`): not a secret, so not redacted and never a finding.",
153
+ "pattern": "^<[^<>]{1,64}>$|(?:^|[_.-])(?:YOUR|your|MY|my)[_-]|_(?:TOKEN|KEY|SECRET|PASSWORD)$"
154
+ },
155
+ "credential_flag": {
156
+ "description": "A command-line flag whose next argument is a credential (`--api-key`, `--token`); group 1 is the name, tested against credential_key.",
157
+ "pattern": "^-{1,2}([A-Za-z0-9_.-]{1,64})$"
158
+ },
159
+ "mcp_value_blocks": {
160
+ "description": "Blocks inside an MCP server definition whose values are checked for secrets: a known format, a value under a credential-named key, or a generic token (generic_token) is redacted; any other value stays. A server definition is an object with one of server_keys. A server key counts only when its value is a string or a list, so a JSON Schema `properties` map with a `type` or `url` property is not a server.",
161
+ "blocks": [
162
+ "env",
163
+ "environment",
164
+ "headers",
165
+ "http_headers"
166
+ ],
167
+ "server_keys": [
168
+ "command",
169
+ "args",
170
+ "url",
171
+ "serverUrl",
172
+ "httpUrl",
173
+ "transport",
174
+ "type"
175
+ ]
176
+ },
177
+ "generic_token": {
178
+ "description": "The backend's generic token (HardcodedSecretRule.GENERIC_TOKEN_PATTERN): in an MCP env or headers value, a match of 40 or more characters is redacted as POTENTIAL_TOKEN, which the backend reads as a potential token/secret. In a URL only the query is searched.",
179
+ "pattern": "\\b[a-zA-Z0-9][a-zA-Z0-9_-]{31,}\\b"
180
+ },
181
+ "json_schema_types": {
182
+ "description": "An object whose `type` is one of these is a JSON Schema node (an MCP tool's input schema), not data: never a credential container or an MCP value block.",
183
+ "types": [
184
+ "object",
185
+ "array",
186
+ "string",
187
+ "number",
188
+ "integer",
189
+ "boolean",
190
+ "null"
191
+ ]
192
+ },
193
+ "min_credential_length": 8,
194
+ "admin_summary": {
195
+ "description": "What admins are told is redacted (org settings, Data redaction; OPT-953): one item per kind, naming the rule groups it describes; \"list\": \"formats\" adds the format types as a list. \"items\" are the names or kinds the other items remove. The backend sends these items; a backend test fails when a rule group that redacts has no item here.",
196
+ "lines": [
197
+ {
198
+ "title": "API keys and tokens",
199
+ "rules": [
200
+ "formats"
201
+ ],
202
+ "list": "formats",
203
+ "text": "Known vendor formats, replaced by [OL_REDACTED:<format>]. The vendor's prefix stays."
204
+ },
205
+ {
206
+ "title": "Credentials by name",
207
+ "rules": [
208
+ "credential_key",
209
+ "weak_credential_key",
210
+ "credential_assignment",
211
+ "credential_flag"
212
+ ],
213
+ "text": "Values named like a credential (api_key, token, secret, password), in any format.",
214
+ "items": [
215
+ "api_key",
216
+ "apikey",
217
+ "secret",
218
+ "token",
219
+ "password",
220
+ "passwd",
221
+ "credential",
222
+ "private_key",
223
+ "secret_key",
224
+ "signing_key",
225
+ "encryption_key",
226
+ "access_key",
227
+ "authorization",
228
+ "token_cache",
229
+ "*_key"
230
+ ]
231
+ },
232
+ {
233
+ "title": "Token maps",
234
+ "rules": [
235
+ "credential_container"
236
+ ],
237
+ "text": "Every value in a map of tokens, such as Copilot's copilotTokens.",
238
+ "items": [
239
+ "*tokens",
240
+ "*secrets",
241
+ "*credentials",
242
+ "*api_keys"
243
+ ]
244
+ },
245
+ {
246
+ "title": "MCP server env and headers",
247
+ "rules": [
248
+ "mcp_value_blocks",
249
+ "generic_token"
250
+ ],
251
+ "text": "Secrets in an MCP server's env and headers: known formats, credential names and long tokens. Other values stay.",
252
+ "items": [
253
+ "env",
254
+ "environment",
255
+ "headers",
256
+ "http_headers"
257
+ ]
258
+ }
259
+ ]
260
+ }
261
+ }