@excitedjs/dreamux-utils 0.5.2-beta.242 → 0.6.0-beta.243
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/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/json-invoke.d.ts +21 -5
- package/dist/json-invoke.d.ts.map +1 -1
- package/dist/json-invoke.js.map +1 -1
- package/dist/redaction.d.ts +90 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +231 -0
- package/dist/redaction.js.map +1 -0
- package/package.json +2 -5
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,SAAS,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC;AACnC,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC"}
|
package/dist/json-invoke.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The failure boundary for a one-request/one-result JSON invocation.
|
|
3
3
|
*
|
|
4
|
-
* A
|
|
5
|
-
* a deep implementation can throw when it knows what the caller did
|
|
6
|
-
* a runner that turns exactly that marker into the settled
|
|
4
|
+
* A one-request/one-result call needs a producing side, and this is all of it:
|
|
5
|
+
* a marker a deep implementation can throw when it knows what the caller did
|
|
6
|
+
* wrong, and a runner that turns exactly that marker into the settled
|
|
7
|
+
* `ok: false` answer.
|
|
7
8
|
*
|
|
8
9
|
* The marker exists so a refusal does not have to be plumbed by hand out of an
|
|
9
10
|
* argument parser, a policy check, or whatever else is five frames down. It
|
|
@@ -15,9 +16,24 @@
|
|
|
15
16
|
* result, and turning it into one here would be the exact mistake this boundary
|
|
16
17
|
* exists to prevent.
|
|
17
18
|
*/
|
|
18
|
-
|
|
19
|
+
/**
|
|
20
|
+
* The settled shape, declared here rather than imported.
|
|
21
|
+
*
|
|
22
|
+
* This package knows no Dreamux contracts: `@excitedjs/dreamux-types` is the
|
|
23
|
+
* type set an *external provider* compiles against, and a utility every layer
|
|
24
|
+
* calls must not reach into it. The shape is structural and identical to that
|
|
25
|
+
* package's `JsonInvokeResult`, so a caller that names the contract assigns one
|
|
26
|
+
* of these to it without a cast.
|
|
27
|
+
*/
|
|
28
|
+
export type SettledInvoke<TValue> = {
|
|
29
|
+
readonly ok: true;
|
|
30
|
+
readonly value: TValue;
|
|
31
|
+
} | {
|
|
32
|
+
readonly ok: false;
|
|
33
|
+
readonly message: string;
|
|
34
|
+
};
|
|
19
35
|
export declare class PublicInvokeFailure extends Error {
|
|
20
36
|
constructor(message: string);
|
|
21
37
|
}
|
|
22
|
-
export declare function settleJsonInvoke<TValue>(body: () => Promise<TValue>): Promise<
|
|
38
|
+
export declare function settleJsonInvoke<TValue>(body: () => Promise<TValue>): Promise<SettledInvoke<TValue>>;
|
|
23
39
|
//# sourceMappingURL=json-invoke.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-invoke.d.ts","sourceRoot":"","sources":["../src/json-invoke.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"json-invoke.d.ts","sourceRoot":"","sources":["../src/json-invoke.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,CAAC,MAAM,IAC5B;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAC7C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAErD,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAsB,gBAAgB,CAAC,MAAM,EAC3C,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,GAC1B,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAShC"}
|
package/dist/json-invoke.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"json-invoke.js","sourceRoot":"","sources":["../src/json-invoke.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"json-invoke.js","sourceRoot":"","sources":["../src/json-invoke.ts"],"names":[],"mappings":"AA+BA,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IAC5C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,IAA2B;IAE3B,IAAI,CAAC;QACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,IAAI,EAAE,EAAE,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,mBAAmB,EAAE,CAAC;YACzC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;QAC/C,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a conversation, a log line, and a printed config must not publish
|
|
3
|
+
* verbatim. One capability with two ways in, because a caller has one of two
|
|
4
|
+
* things: text, or structure.
|
|
5
|
+
*
|
|
6
|
+
* `redactText` is for text — a command line, an assistant message, a serialized
|
|
7
|
+
* payload — and finds secrets by shape. `isSecretKeyName` is for structure,
|
|
8
|
+
* where a field's *name* already says the value is a secret and no shape
|
|
9
|
+
* matching is needed or wanted. They share one list of secret key names, which
|
|
10
|
+
* is why this module exists: that list used to be written out three times, and
|
|
11
|
+
* the three copies had drifted.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* A value shaped like JSON, declared here rather than imported.
|
|
15
|
+
*
|
|
16
|
+
* JSON is not a Dreamux concept, and this package deliberately knows no Dreamux
|
|
17
|
+
* contracts: `@excitedjs/dreamux-types` is the type set an *external provider*
|
|
18
|
+
* compiles against, so a utility every layer calls must not reach into it — not
|
|
19
|
+
* even for a type. The shape is structural, so a caller holding that package's
|
|
20
|
+
* `JsonValue` passes one of these without a cast.
|
|
21
|
+
*/
|
|
22
|
+
export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
|
|
23
|
+
readonly [key: string]: JsonValue;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Redaction never truncates. How much of a payload a surface can show is that
|
|
27
|
+
* surface's own limit, applied where it sends. Cutting here would hand every
|
|
28
|
+
* surface an already-damaged value — a JSON result that no longer parses —
|
|
29
|
+
* with no way to get it back.
|
|
30
|
+
*/
|
|
31
|
+
export interface RedactedText {
|
|
32
|
+
value: string;
|
|
33
|
+
redacted: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Whether a field's *name* says its value is a secret.
|
|
37
|
+
*
|
|
38
|
+
* Structure needs no shape matching: a caller holding `{ api_key: "..." }`
|
|
39
|
+
* knows the value is a secret whatever it looks like, and a low-entropy or
|
|
40
|
+
* oddly-punctuated one would slip past every pattern. The substring test is
|
|
41
|
+
* deliberate, so `feishu_app_secret` and `refreshToken` both count.
|
|
42
|
+
*/
|
|
43
|
+
export declare function isSecretKeyName(key: string): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Replace, in place, every value whose key names a secret — at any depth.
|
|
46
|
+
*
|
|
47
|
+
* The caller owns a parsed, mutable structure it is about to display, and this
|
|
48
|
+
* is the one thing it must not show. Values are destroyed rather than masked
|
|
49
|
+
* by shape, because the key already settled the question.
|
|
50
|
+
*/
|
|
51
|
+
export declare function redactSecretKeyValues(value: unknown): void;
|
|
52
|
+
/**
|
|
53
|
+
* Rewrite what displayed text must not publish verbatim.
|
|
54
|
+
*
|
|
55
|
+
* Two different jobs share this function. Secrets are *destroyed* — a token has
|
|
56
|
+
* no legible form worth keeping. Paths are only *renamed*: a reader still needs
|
|
57
|
+
* to know which file was touched, so the workspace becomes `.` and this host's
|
|
58
|
+
* home becomes `~`, exactly the way the operator's own shell prints them. Order
|
|
59
|
+
* matters: the workspace usually sits under the home, so relativizing it first
|
|
60
|
+
* keeps the shorter, more useful form.
|
|
61
|
+
*
|
|
62
|
+
* `homePaths` is explicit so this stays pure and never depends on
|
|
63
|
+
* process-global resolution state.
|
|
64
|
+
*/
|
|
65
|
+
export declare function redactText(value: string, cwd: string, homePaths: readonly string[]): RedactedText;
|
|
66
|
+
/**
|
|
67
|
+
* Redact a JSON value by walking it, not by reading its serialization.
|
|
68
|
+
*
|
|
69
|
+
* Text is the wrong level for a payload that has structure. Serializing first
|
|
70
|
+
* hides every string inside escapes — `{"client_secret":"x"}` written into a
|
|
71
|
+
* field becomes `{\"client_secret\":\"x\"}`, where the key no longer sits
|
|
72
|
+
* beside its separator and no pattern tuned for text can see it, at any nesting
|
|
73
|
+
* depth. Walking removes the problem instead of chasing it: each level of
|
|
74
|
+
* structure is peeled by the caller that parsed it, so every string this
|
|
75
|
+
* reaches is ordinary text, which is the one thing `redactText` is good at.
|
|
76
|
+
*
|
|
77
|
+
* Two rules, and a key outranks a shape. A field whose *name* says secret has
|
|
78
|
+
* its value destroyed whatever it looks like, which is what catches the
|
|
79
|
+
* low-entropy and oddly-punctuated values no pattern would. Every other string
|
|
80
|
+
* goes through `redactText`. Numbers and booleans carry neither a secret worth
|
|
81
|
+
* masking nor a path worth renaming, so they travel as they are.
|
|
82
|
+
*
|
|
83
|
+
* The result is a new value; the input is not touched. Because the structure
|
|
84
|
+
* survives, a caller that serializes afterwards always gets valid JSON.
|
|
85
|
+
*/
|
|
86
|
+
export declare function redactJson(value: JsonValue | null, cwd: string, homePaths: readonly string[]): {
|
|
87
|
+
value: JsonValue | null;
|
|
88
|
+
redacted: boolean;
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=redaction.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH;;;;;;;;GAQG;AACH,MAAM,MAAM,SAAS,GACjB,IAAI,GACJ,OAAO,GACP,MAAM,GACN,MAAM,GACN,SAAS,SAAS,EAAE,GACpB;IAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAsE1C;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE;AAElE;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAa1D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,YAAY,CAmBd;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,SAAS,GAAG,IAAI,EACvB,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B;IAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,OAAO,CAAA;CAAE,CA0BhD"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { isPlainObject } from './config-validate.js';
|
|
2
|
+
/**
|
|
3
|
+
* The key names that mean "the value beside me is a secret". Written once and
|
|
4
|
+
* compiled into both the inline text pattern and the bare-name test, so the
|
|
5
|
+
* names a log line hides and the names a tool argument hides cannot drift
|
|
6
|
+
* apart again.
|
|
7
|
+
*/
|
|
8
|
+
const SECRET_KEY_NAMES = [
|
|
9
|
+
'secret',
|
|
10
|
+
'password',
|
|
11
|
+
'passwd',
|
|
12
|
+
'token',
|
|
13
|
+
'authorization',
|
|
14
|
+
'cookie',
|
|
15
|
+
'credential',
|
|
16
|
+
'api[_-]?key',
|
|
17
|
+
'private[_-]?key',
|
|
18
|
+
'client[_-]?secret',
|
|
19
|
+
].join('|');
|
|
20
|
+
const BACKTICK = '`';
|
|
21
|
+
/**
|
|
22
|
+
* `key: value` / `key=value` pairs whose key names a secret. The value is one
|
|
23
|
+
* quoted string (a JSON string with its escapes, or a shell-style single- or
|
|
24
|
+
* back-quoted one) or one bare word. A bare word stops at whitespace, a
|
|
25
|
+
* separator, a quote, or a closing bracket, so the shape *around* the secret
|
|
26
|
+
* survives: a `token: xyz` phrase inside a quoted string does not swallow the
|
|
27
|
+
* quote and bracket that close it.
|
|
28
|
+
*
|
|
29
|
+
* This reads *text*, so what it sees is one level of quoting. A caller holding
|
|
30
|
+
* a structure walks it instead and hands each string leaf here on its own —
|
|
31
|
+
* that is what `redactJson` is for, and it is why this pattern does not try to
|
|
32
|
+
* understand a JSON string nested inside another. A regex cannot count nesting
|
|
33
|
+
* depth; the walk does not have to.
|
|
34
|
+
*/
|
|
35
|
+
const INLINE_SECRET_RE = new RegExp(String.raw `(["']?\b(?:` +
|
|
36
|
+
SECRET_KEY_NAMES +
|
|
37
|
+
String.raw `)\b["']?)(\s*[:=]\s*)(` +
|
|
38
|
+
String.raw `"(?:[^"\\]|\\.)*"` + '|' +
|
|
39
|
+
String.raw `'[^']*'` + '|' +
|
|
40
|
+
BACKTICK + `[^${BACKTICK}]*` + BACKTICK + '|' +
|
|
41
|
+
String.raw `[^\s,;"'` + BACKTICK + String.raw `)\]}]+)`, 'giu');
|
|
42
|
+
const SECRET_KEY_NAME_RE = new RegExp(`(?:${SECRET_KEY_NAMES})`, 'iu');
|
|
43
|
+
const BEARER_RE = /\bBearer\s+[A-Za-z0-9._~+/-]+=*/giu;
|
|
44
|
+
const PRIVATE_KEY_RE = /-----BEGIN [A-Z0-9 ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9 ]*PRIVATE KEY-----/giu;
|
|
45
|
+
const JWT_RE = /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/gu;
|
|
46
|
+
const COMMON_ACCESS_KEY_RE = /\b(?:AKIA|ASIA|AKLT)[A-Z0-9]{12,}\b/gu;
|
|
47
|
+
/**
|
|
48
|
+
* What may sit inside a path without ending it.
|
|
49
|
+
*
|
|
50
|
+
* A prefix only counts as a path when the characters around it agree that it is
|
|
51
|
+
* one: `~/work` is this operator's home, `/home/alicexyz` is somebody else's
|
|
52
|
+
* directory that merely starts with the same letters, and `not/home/alice` is a
|
|
53
|
+
* fragment of a longer path that was never rooted here. Letters, digits, and
|
|
54
|
+
* the separators/punctuation that appear inside real path segments continue a
|
|
55
|
+
* token; anything else — whitespace, a quote, a colon, a comma — ends it.
|
|
56
|
+
* Two narrower exceptions are handled at a match: a period closes prose only
|
|
57
|
+
* when what follows is already a boundary, and a preceding slash starts a path
|
|
58
|
+
* only when it completes a URL scheme's `://`.
|
|
59
|
+
*/
|
|
60
|
+
const PATH_TOKEN_CHARACTER_RE = /[\p{L}\p{N}_.~\\/-]/u;
|
|
61
|
+
/**
|
|
62
|
+
* Whether a field's *name* says its value is a secret.
|
|
63
|
+
*
|
|
64
|
+
* Structure needs no shape matching: a caller holding `{ api_key: "..." }`
|
|
65
|
+
* knows the value is a secret whatever it looks like, and a low-entropy or
|
|
66
|
+
* oddly-punctuated one would slip past every pattern. The substring test is
|
|
67
|
+
* deliberate, so `feishu_app_secret` and `refreshToken` both count.
|
|
68
|
+
*/
|
|
69
|
+
export function isSecretKeyName(key) {
|
|
70
|
+
return SECRET_KEY_NAME_RE.test(key);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Replace, in place, every value whose key names a secret — at any depth.
|
|
74
|
+
*
|
|
75
|
+
* The caller owns a parsed, mutable structure it is about to display, and this
|
|
76
|
+
* is the one thing it must not show. Values are destroyed rather than masked
|
|
77
|
+
* by shape, because the key already settled the question.
|
|
78
|
+
*/
|
|
79
|
+
export function redactSecretKeyValues(value) {
|
|
80
|
+
if (Array.isArray(value)) {
|
|
81
|
+
for (const item of value)
|
|
82
|
+
redactSecretKeyValues(item);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
if (!isPlainObject(value))
|
|
86
|
+
return;
|
|
87
|
+
for (const [key, child] of Object.entries(value)) {
|
|
88
|
+
if (isSecretKeyName(key)) {
|
|
89
|
+
value[key] = '<redacted>';
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
redactSecretKeyValues(child);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Rewrite what displayed text must not publish verbatim.
|
|
97
|
+
*
|
|
98
|
+
* Two different jobs share this function. Secrets are *destroyed* — a token has
|
|
99
|
+
* no legible form worth keeping. Paths are only *renamed*: a reader still needs
|
|
100
|
+
* to know which file was touched, so the workspace becomes `.` and this host's
|
|
101
|
+
* home becomes `~`, exactly the way the operator's own shell prints them. Order
|
|
102
|
+
* matters: the workspace usually sits under the home, so relativizing it first
|
|
103
|
+
* keeps the shorter, more useful form.
|
|
104
|
+
*
|
|
105
|
+
* `homePaths` is explicit so this stays pure and never depends on
|
|
106
|
+
* process-global resolution state.
|
|
107
|
+
*/
|
|
108
|
+
export function redactText(value, cwd, homePaths) {
|
|
109
|
+
let redacted = replacePathPrefix(value, cwd, '.', '', true);
|
|
110
|
+
redacted = redacted.replace(PRIVATE_KEY_RE, '<redacted-private-key>');
|
|
111
|
+
for (const homePath of homePaths) {
|
|
112
|
+
redacted = replacePathPrefix(redacted, homePath, '~', '~', false);
|
|
113
|
+
}
|
|
114
|
+
redacted = redacted.replace(BEARER_RE, 'Bearer <redacted>');
|
|
115
|
+
redacted = redacted.replace(JWT_RE, '<redacted-jwt>');
|
|
116
|
+
redacted = redacted.replace(COMMON_ACCESS_KEY_RE, '<redacted-access-key>');
|
|
117
|
+
redacted = redacted.replace(INLINE_SECRET_RE, (_match, key, separator, secret) => {
|
|
118
|
+
// A quoted secret stays a quoted (now empty of meaning) string, so the
|
|
119
|
+
// text around it keeps whatever grammar it had — JSON included.
|
|
120
|
+
const quote = /^["'`]/u.test(secret) ? secret[0] : '';
|
|
121
|
+
return `${key}${separator}${quote}<redacted>${quote}`;
|
|
122
|
+
});
|
|
123
|
+
return { value: redacted, redacted: redacted !== value };
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Redact a JSON value by walking it, not by reading its serialization.
|
|
127
|
+
*
|
|
128
|
+
* Text is the wrong level for a payload that has structure. Serializing first
|
|
129
|
+
* hides every string inside escapes — `{"client_secret":"x"}` written into a
|
|
130
|
+
* field becomes `{\"client_secret\":\"x\"}`, where the key no longer sits
|
|
131
|
+
* beside its separator and no pattern tuned for text can see it, at any nesting
|
|
132
|
+
* depth. Walking removes the problem instead of chasing it: each level of
|
|
133
|
+
* structure is peeled by the caller that parsed it, so every string this
|
|
134
|
+
* reaches is ordinary text, which is the one thing `redactText` is good at.
|
|
135
|
+
*
|
|
136
|
+
* Two rules, and a key outranks a shape. A field whose *name* says secret has
|
|
137
|
+
* its value destroyed whatever it looks like, which is what catches the
|
|
138
|
+
* low-entropy and oddly-punctuated values no pattern would. Every other string
|
|
139
|
+
* goes through `redactText`. Numbers and booleans carry neither a secret worth
|
|
140
|
+
* masking nor a path worth renaming, so they travel as they are.
|
|
141
|
+
*
|
|
142
|
+
* The result is a new value; the input is not touched. Because the structure
|
|
143
|
+
* survives, a caller that serializes afterwards always gets valid JSON.
|
|
144
|
+
*/
|
|
145
|
+
export function redactJson(value, cwd, homePaths) {
|
|
146
|
+
let redacted = false;
|
|
147
|
+
const walk = (node) => {
|
|
148
|
+
if (typeof node === 'string') {
|
|
149
|
+
const result = redactText(node, cwd, homePaths);
|
|
150
|
+
redacted ||= result.redacted;
|
|
151
|
+
return result.value;
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(node))
|
|
154
|
+
return node.map(walk);
|
|
155
|
+
if (node === null || typeof node !== 'object')
|
|
156
|
+
return node;
|
|
157
|
+
// Built with `Object.fromEntries` because it creates own properties. A
|
|
158
|
+
// plain `out[key] = ...` would not: a payload carrying its own `__proto__`
|
|
159
|
+
// member — ordinary data, which is exactly what `JSON.parse` builds it as —
|
|
160
|
+
// would reach the prototype setter instead and disappear from the result.
|
|
161
|
+
const entries = Object.entries(node).map(([key, child]) => {
|
|
162
|
+
if (isSecretKeyName(key)) {
|
|
163
|
+
redacted = true;
|
|
164
|
+
return [key, '<redacted>'];
|
|
165
|
+
}
|
|
166
|
+
return [key, walk(child)];
|
|
167
|
+
});
|
|
168
|
+
return Object.fromEntries(entries);
|
|
169
|
+
};
|
|
170
|
+
return value === null
|
|
171
|
+
? { value: null, redacted: false }
|
|
172
|
+
: { value: walk(value), redacted };
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Replace every occurrence of `rawPrefix` that is actually the head of a path.
|
|
176
|
+
*
|
|
177
|
+
* Scanning for a known prefix is what makes this honest where a regex is not: a
|
|
178
|
+
* pattern like `/home/<name>/...` matches any string of that *shape*, including
|
|
179
|
+
* a directory on some other machine quoted in a log, and blanking those costs
|
|
180
|
+
* legibility for no privacy gain. Only the prefixes this host really uses are
|
|
181
|
+
* offered here, and each hit must still be bounded on both sides — preceded by
|
|
182
|
+
* a non-path character and followed by a separator or the end of a token.
|
|
183
|
+
*
|
|
184
|
+
* A hit with a path continuing after it (`<prefix>/rest`) takes
|
|
185
|
+
* `nestedReplacement`, and `stripNestedSeparator` drops the separator with it so
|
|
186
|
+
* a workspace `<cwd>/rest` turns into `rest`. A hit that ends there takes
|
|
187
|
+
* `exactReplacement`, so a bare workspace becomes `.`.
|
|
188
|
+
*/
|
|
189
|
+
function replacePathPrefix(value, rawPrefix, exactReplacement, nestedReplacement, stripNestedSeparator) {
|
|
190
|
+
const prefix = rawPrefix.replace(/[\\/]+$/u, '');
|
|
191
|
+
if (prefix === '')
|
|
192
|
+
return value;
|
|
193
|
+
let cursor = 0;
|
|
194
|
+
let searchFrom = 0;
|
|
195
|
+
let result = '';
|
|
196
|
+
while (searchFrom < value.length) {
|
|
197
|
+
const matchAt = value.indexOf(prefix, searchFrom);
|
|
198
|
+
if (matchAt < 0)
|
|
199
|
+
break;
|
|
200
|
+
const suffixAt = matchAt + prefix.length;
|
|
201
|
+
const next = value[suffixAt];
|
|
202
|
+
const nested = next === '/' || next === '\\';
|
|
203
|
+
const ends = isPathPrefixEnd(value, suffixAt);
|
|
204
|
+
if (isPathPrefixBoundary(value, matchAt) && (nested || ends)) {
|
|
205
|
+
result += value.slice(cursor, matchAt);
|
|
206
|
+
result += nested ? nestedReplacement : exactReplacement;
|
|
207
|
+
cursor = suffixAt + (nested && stripNestedSeparator ? 1 : 0);
|
|
208
|
+
searchFrom = cursor;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
searchFrom = suffixAt;
|
|
212
|
+
}
|
|
213
|
+
return cursor === 0 ? value : result + value.slice(cursor);
|
|
214
|
+
}
|
|
215
|
+
function isPathPrefixBoundary(value, matchAt) {
|
|
216
|
+
if (matchAt === 0 || isPathTokenBoundary(value[matchAt - 1]))
|
|
217
|
+
return true;
|
|
218
|
+
return matchAt >= 3 && value.slice(matchAt - 3, matchAt) === '://';
|
|
219
|
+
}
|
|
220
|
+
function isPathPrefixEnd(value, suffixAt) {
|
|
221
|
+
const next = value[suffixAt];
|
|
222
|
+
if (next === undefined)
|
|
223
|
+
return true;
|
|
224
|
+
return next === '.'
|
|
225
|
+
? isPathTokenBoundary(value[suffixAt + 1])
|
|
226
|
+
: isPathTokenBoundary(next);
|
|
227
|
+
}
|
|
228
|
+
function isPathTokenBoundary(character) {
|
|
229
|
+
return character === undefined || !PATH_TOKEN_CHARACTER_RE.test(character);
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../src/redaction.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@excitedjs/dreamux-utils",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Pure shared utilities for Dreamux providers and host: neutral config-validation primitives, OS/filesystem helpers, completion-body resolution, and inbound-turn render helpers. Depends on @excitedjs/dreamux
|
|
3
|
+
"version": "0.6.0-beta.243",
|
|
4
|
+
"description": "Pure shared utilities for Dreamux providers and host: neutral config-validation primitives, OS/filesystem helpers, completion-body resolution, and inbound-turn render helpers. Depends on nothing: not on @excitedjs/dreamux core, and not on @excitedjs/dreamux-types, which is the type set external providers compile against.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -26,9 +26,6 @@
|
|
|
26
26
|
"README.md",
|
|
27
27
|
"LICENSE"
|
|
28
28
|
],
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"@excitedjs/dreamux-types": "0.11.0-beta.242"
|
|
31
|
-
},
|
|
32
29
|
"devDependencies": {
|
|
33
30
|
"@types/node": "^22.10.0",
|
|
34
31
|
"eslint": "^9.39.0",
|