@composed-di/core 0.5.2-alpha → 0.5.3-alpha
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.
|
@@ -1,35 +1,97 @@
|
|
|
1
1
|
import type { DisposeContext, EventSpan, InitializeContext, MethodCallContext, ServiceModuleListener } from './serviceModuleListener';
|
|
2
2
|
import type { ServiceKey } from './serviceKey';
|
|
3
3
|
/**
|
|
4
|
-
* The placeholder that replaces redacted
|
|
5
|
-
*
|
|
4
|
+
* The placeholder that replaces a redacted value when no custom
|
|
5
|
+
* transform is given for it.
|
|
6
6
|
*/
|
|
7
|
-
export declare const REDACTED_VALUE = "[
|
|
7
|
+
export declare const REDACTED_VALUE = "[REDACTED]";
|
|
8
|
+
/**
|
|
9
|
+
* Custom masking for one included property, narrowed to the exact
|
|
10
|
+
* method named when {@link RedactionRuleBuilder.redact} is called.
|
|
11
|
+
* Omitting a side fully blanks it with {@link REDACTED_VALUE}; providing
|
|
12
|
+
* one lets you report a partial mask instead (e.g. the last 4 digits of
|
|
13
|
+
* a card number) — both always return a `string`, the masked
|
|
14
|
+
* representation to report in place of the real value.
|
|
15
|
+
*/
|
|
16
|
+
export type Mask<T, K extends Extract<keyof T, string>> = T[K] extends (...args: infer A) => infer R ? {
|
|
17
|
+
maskArgs?: (...args: A) => string;
|
|
18
|
+
maskResult?: (result: R) => string;
|
|
19
|
+
} : never;
|
|
8
20
|
/**
|
|
9
21
|
* Marks a service — or specific properties of it — as sensitive, so the
|
|
10
|
-
* values flowing through its events are
|
|
22
|
+
* values flowing through its events are redacted. Returned by
|
|
23
|
+
* {@link RedactionRuleBuilder.build}, which is the only way to construct
|
|
24
|
+
* one — the `redactAll`/per-property merge logic lives entirely inside
|
|
25
|
+
* `maskArgs`/`maskResult`, so callers never need to know how a rule
|
|
26
|
+
* reached its decision, only what to do with it.
|
|
11
27
|
*/
|
|
12
28
|
export interface RedactionRule<T> {
|
|
29
|
+
readonly key: ServiceKey<T>;
|
|
30
|
+
/**
|
|
31
|
+
* The args to report for a call to `functionName`: unchanged if not
|
|
32
|
+
* redacted, otherwise blanked or run through a custom `maskArgs`.
|
|
33
|
+
*/
|
|
34
|
+
maskArgs(functionName: string, args: readonly unknown[]): readonly unknown[];
|
|
35
|
+
/**
|
|
36
|
+
* The value to report for a success outcome: unchanged if not
|
|
37
|
+
* redacted, otherwise blanked or run through a custom `maskResult`.
|
|
38
|
+
* Omit `functionName` to ask about the initialize result (the service
|
|
39
|
+
* instance itself), which only the rule's `redactAll` default touches.
|
|
40
|
+
*/
|
|
41
|
+
maskResult(functionName: string | undefined, result: unknown): unknown;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Fluent, single-key rule builder returned by {@link redactionRule}.
|
|
45
|
+
* `redactAll`, `redact`, and `exclude` all merge into the same rule —
|
|
46
|
+
* call them in any combination, in any order; the more specific
|
|
47
|
+
* per-property calls (`redact`/`exclude`) always win over the blanket
|
|
48
|
+
* `redactAll` default for the properties they name.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* const rules = [
|
|
53
|
+
* redactionRule(SecretClientKey)
|
|
54
|
+
* .redactAll()
|
|
55
|
+
* .build(), // whole service is sensitive
|
|
56
|
+
* redactionRule(BillingKey)
|
|
57
|
+
* .redactAll()
|
|
58
|
+
* .redact('chargeCard', { maskResult: (card) => `card ending in ${card.number.slice(-4)}` })
|
|
59
|
+
* .exclude('ping') // redact everything except this, with one custom mask
|
|
60
|
+
* .build(),
|
|
61
|
+
* redactionRule(VaultKey)
|
|
62
|
+
* .redact('getSecret')
|
|
63
|
+
* .build(), // only this call, nothing else
|
|
64
|
+
* ];
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare class RedactionRuleBuilder<T> {
|
|
68
|
+
private readonly key;
|
|
69
|
+
private redactAllFlag;
|
|
70
|
+
private readonly overrides;
|
|
71
|
+
constructor(key: ServiceKey<T>);
|
|
72
|
+
/** Redacts every property, plus the initialize result, by default. */
|
|
73
|
+
redactAll(): this;
|
|
13
74
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
75
|
+
* Marks one property (method) as redacted, with optional custom
|
|
76
|
+
* masking. Call repeatedly for several properties. Overrides
|
|
77
|
+
* `redactAll`/`exclude` for this specific property.
|
|
16
78
|
*/
|
|
17
|
-
|
|
79
|
+
redact<K extends Extract<keyof T, string>>(name: K, mask?: Mask<T, K>): this;
|
|
18
80
|
/**
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* service instance itself may carry credentials or config).
|
|
81
|
+
* Marks one or more properties as explicitly NOT redacted, overriding
|
|
82
|
+
* `redactAll` for just these.
|
|
22
83
|
*/
|
|
23
|
-
|
|
84
|
+
exclude(...names: Extract<keyof T, string>[]): this;
|
|
85
|
+
build(): RedactionRule<any>;
|
|
24
86
|
}
|
|
25
|
-
export declare function redactionRule<T>(key: ServiceKey<T
|
|
87
|
+
export declare function redactionRule<T>(key: ServiceKey<T>): RedactionRuleBuilder<T>;
|
|
26
88
|
/**
|
|
27
89
|
* A ServiceEventListener decorator that redacts sensitive values before
|
|
28
90
|
* they reach the wrapped listener. Works with any implementation via
|
|
29
91
|
* delegation: arguments in MethodCallContext and success values in
|
|
30
|
-
* EventOutcome are replaced
|
|
31
|
-
*
|
|
32
|
-
* already scrubbed.
|
|
92
|
+
* EventOutcome are replaced (wholesale, or via a custom transform)
|
|
93
|
+
* before the delegate ever sees them — whatever it captures or exports
|
|
94
|
+
* is already scrubbed.
|
|
33
95
|
*
|
|
34
96
|
* Failure outcomes are passed through unchanged so error reporting keeps
|
|
35
97
|
* working; keep secrets out of error messages at the throwing site.
|
|
@@ -39,8 +101,9 @@ export declare function redactionRule<T>(key: ServiceKey<T>, properties?: Extrac
|
|
|
39
101
|
* const listener = new RedactingEventListener(
|
|
40
102
|
* new OTELEventListener({ captureArguments: true, captureResults: true }),
|
|
41
103
|
* [
|
|
42
|
-
*
|
|
43
|
-
*
|
|
104
|
+
* redactionRule(SecretClientKey).redactAll().build(), // whole service is sensitive
|
|
105
|
+
* redactionRule(VaultKey).redact('getSecret').build(), // only this call
|
|
106
|
+
* redactionRule(HealthKey).redactAll().exclude('ping').build(), // everything but this call
|
|
44
107
|
* ],
|
|
45
108
|
* );
|
|
46
109
|
* ```
|
|
@@ -52,6 +115,5 @@ export declare class RedactingEventListener implements ServiceModuleListener {
|
|
|
52
115
|
onInitialize(context: InitializeContext): EventSpan | void;
|
|
53
116
|
onDispose(context: DisposeContext): EventSpan | void;
|
|
54
117
|
onMethodCall(context: MethodCallContext): EventSpan | void;
|
|
55
|
-
private isRedacted;
|
|
56
118
|
}
|
|
57
119
|
//# sourceMappingURL=redactingEventListener.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redactingEventListener.d.ts","sourceRoot":"","sources":["../src/redactingEventListener.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAEd,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,cAAc,eAAe,CAAC;AAE3C
|
|
1
|
+
{"version":3,"file":"redactingEventListener.d.ts","sourceRoot":"","sources":["../src/redactingEventListener.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EAEd,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,qBAAqB,EACtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,cAAc,eAAe,CAAC;AAE3C;;;;;;;GAOG;AACH,MAAM,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CACrE,GAAG,IAAI,EAAE,MAAM,CAAC,KACb,MAAM,CAAC,GACR;IACE,QAAQ,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,KAAK,MAAM,CAAC;IAClC,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;CACpC,GACD,KAAK,CAAC;AAeV;;;;;;;GAOG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IAE5B;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,SAAS,OAAO,EAAE,CAAC;IAE7E;;;;;OAKG;IACH,UAAU,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC;CACxE;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,oBAAoB,CAAC,CAAC;IAIrB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAHhC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwC;gBAErC,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC;IAE/C,sEAAsE;IACtE,SAAS,IAAI,IAAI;IAKjB;;;;OAIG;IACH,MAAM,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,IAAI;IAK5E;;;OAGG;IACH,OAAO,CAAC,GAAG,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI;IAOnD,KAAK,IAAI,aAAa,CAAC,GAAG,CAAC;CAwC5B;AAED,wBAAgB,aAAa,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,oBAAoB,CAAC,CAAC,CAAC,CAE5E;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,sBAAuB,YAAW,qBAAqB;IAEhE,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,KAAK;gBADL,QAAQ,EAAE,qBAAqB,EAC/B,KAAK,EAAE,SAAS,aAAa,CAAC,GAAG,CAAC,EAAE;IAGvD,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI;IAK1D,SAAS,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,GAAG,IAAI;IAKpD,YAAY,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI;CAY3D"}
|
|
@@ -1,22 +1,113 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RedactingEventListener = exports.REDACTED_VALUE = void 0;
|
|
3
|
+
exports.RedactingEventListener = exports.RedactionRuleBuilder = exports.REDACTED_VALUE = void 0;
|
|
4
4
|
exports.redactionRule = redactionRule;
|
|
5
5
|
/**
|
|
6
|
-
* The placeholder that replaces redacted
|
|
7
|
-
*
|
|
6
|
+
* The placeholder that replaces a redacted value when no custom
|
|
7
|
+
* transform is given for it.
|
|
8
8
|
*/
|
|
9
|
-
exports.REDACTED_VALUE = '[
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
exports.REDACTED_VALUE = '[REDACTED]';
|
|
10
|
+
/**
|
|
11
|
+
* Fluent, single-key rule builder returned by {@link redactionRule}.
|
|
12
|
+
* `redactAll`, `redact`, and `exclude` all merge into the same rule —
|
|
13
|
+
* call them in any combination, in any order; the more specific
|
|
14
|
+
* per-property calls (`redact`/`exclude`) always win over the blanket
|
|
15
|
+
* `redactAll` default for the properties they name.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const rules = [
|
|
20
|
+
* redactionRule(SecretClientKey)
|
|
21
|
+
* .redactAll()
|
|
22
|
+
* .build(), // whole service is sensitive
|
|
23
|
+
* redactionRule(BillingKey)
|
|
24
|
+
* .redactAll()
|
|
25
|
+
* .redact('chargeCard', { maskResult: (card) => `card ending in ${card.number.slice(-4)}` })
|
|
26
|
+
* .exclude('ping') // redact everything except this, with one custom mask
|
|
27
|
+
* .build(),
|
|
28
|
+
* redactionRule(VaultKey)
|
|
29
|
+
* .redact('getSecret')
|
|
30
|
+
* .build(), // only this call, nothing else
|
|
31
|
+
* ];
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
class RedactionRuleBuilder {
|
|
35
|
+
constructor(key) {
|
|
36
|
+
this.key = key;
|
|
37
|
+
this.redactAllFlag = false;
|
|
38
|
+
this.overrides = {};
|
|
39
|
+
}
|
|
40
|
+
/** Redacts every property, plus the initialize result, by default. */
|
|
41
|
+
redactAll() {
|
|
42
|
+
this.redactAllFlag = true;
|
|
43
|
+
return this;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Marks one property (method) as redacted, with optional custom
|
|
47
|
+
* masking. Call repeatedly for several properties. Overrides
|
|
48
|
+
* `redactAll`/`exclude` for this specific property.
|
|
49
|
+
*/
|
|
50
|
+
redact(name, mask) {
|
|
51
|
+
this.overrides[name] = Object.assign({ redacted: true }, mask);
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Marks one or more properties as explicitly NOT redacted, overriding
|
|
56
|
+
* `redactAll` for just these.
|
|
57
|
+
*/
|
|
58
|
+
exclude(...names) {
|
|
59
|
+
for (const name of names) {
|
|
60
|
+
this.overrides[name] = { redacted: false };
|
|
61
|
+
}
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
build() {
|
|
65
|
+
if (!this.redactAllFlag && Object.keys(this.overrides).length === 0) {
|
|
66
|
+
throw new Error(`redactionRule(${this.key.name}) has no effect: call .redactAll() and/or .redact(...) ` +
|
|
67
|
+
'before .build(), otherwise this rule never redacts anything.');
|
|
68
|
+
}
|
|
69
|
+
const redactAllFlag = this.redactAllFlag;
|
|
70
|
+
const overrides = this.overrides;
|
|
71
|
+
return {
|
|
72
|
+
key: this.key,
|
|
73
|
+
maskArgs(functionName, args) {
|
|
74
|
+
const override = overrides[functionName];
|
|
75
|
+
const redacted = override ? override.redacted : redactAllFlag;
|
|
76
|
+
if (!redacted) {
|
|
77
|
+
return args;
|
|
78
|
+
}
|
|
79
|
+
return (override === null || override === void 0 ? void 0 : override.maskArgs)
|
|
80
|
+
? // A custom mask reports one string for the whole call,
|
|
81
|
+
// rather than a value per argument.
|
|
82
|
+
[override.maskArgs(...args)]
|
|
83
|
+
: // Replace each argument rather than the whole array, so the
|
|
84
|
+
// delegate still sees the call's arity.
|
|
85
|
+
args.map(() => exports.REDACTED_VALUE);
|
|
86
|
+
},
|
|
87
|
+
maskResult(functionName, result) {
|
|
88
|
+
const override = functionName === undefined ? undefined : overrides[functionName];
|
|
89
|
+
const redacted = override ? override.redacted : redactAllFlag;
|
|
90
|
+
if (!redacted) {
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
return (override === null || override === void 0 ? void 0 : override.maskResult)
|
|
94
|
+
? override.maskResult(result)
|
|
95
|
+
: exports.REDACTED_VALUE;
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
exports.RedactionRuleBuilder = RedactionRuleBuilder;
|
|
101
|
+
function redactionRule(key) {
|
|
102
|
+
return new RedactionRuleBuilder(key);
|
|
12
103
|
}
|
|
13
104
|
/**
|
|
14
105
|
* A ServiceEventListener decorator that redacts sensitive values before
|
|
15
106
|
* they reach the wrapped listener. Works with any implementation via
|
|
16
107
|
* delegation: arguments in MethodCallContext and success values in
|
|
17
|
-
* EventOutcome are replaced
|
|
18
|
-
*
|
|
19
|
-
* already scrubbed.
|
|
108
|
+
* EventOutcome are replaced (wholesale, or via a custom transform)
|
|
109
|
+
* before the delegate ever sees them — whatever it captures or exports
|
|
110
|
+
* is already scrubbed.
|
|
20
111
|
*
|
|
21
112
|
* Failure outcomes are passed through unchanged so error reporting keeps
|
|
22
113
|
* working; keep secrets out of error messages at the throwing site.
|
|
@@ -26,8 +117,9 @@ function redactionRule(key, properties) {
|
|
|
26
117
|
* const listener = new RedactingEventListener(
|
|
27
118
|
* new OTELEventListener({ captureArguments: true, captureResults: true }),
|
|
28
119
|
* [
|
|
29
|
-
*
|
|
30
|
-
*
|
|
120
|
+
* redactionRule(SecretClientKey).redactAll().build(), // whole service is sensitive
|
|
121
|
+
* redactionRule(VaultKey).redact('getSecret').build(), // only this call
|
|
122
|
+
* redactionRule(HealthKey).redactAll().exclude('ping').build(), // everything but this call
|
|
31
123
|
* ],
|
|
32
124
|
* );
|
|
33
125
|
* ```
|
|
@@ -39,10 +131,8 @@ class RedactingEventListener {
|
|
|
39
131
|
}
|
|
40
132
|
onInitialize(context) {
|
|
41
133
|
var _a, _b;
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// `properties`.
|
|
45
|
-
return redactSpan((_b = (_a = this.delegate).onInitialize) === null || _b === void 0 ? void 0 : _b.call(_a, context), this.isRedacted(context.key));
|
|
134
|
+
const rule = this.rules.find((r) => r.key === context.key);
|
|
135
|
+
return redactSpan((_b = (_a = this.delegate).onInitialize) === null || _b === void 0 ? void 0 : _b.call(_a, context), rule);
|
|
46
136
|
}
|
|
47
137
|
onDispose(context) {
|
|
48
138
|
var _a, _b;
|
|
@@ -51,39 +141,29 @@ class RedactingEventListener {
|
|
|
51
141
|
}
|
|
52
142
|
onMethodCall(context) {
|
|
53
143
|
var _a, _b;
|
|
54
|
-
const
|
|
55
|
-
const span = (_b = (_a = this.delegate).onMethodCall) === null || _b === void 0 ? void 0 : _b.call(_a,
|
|
56
|
-
?
|
|
57
|
-
|
|
58
|
-
return redactSpan(span, redacted);
|
|
59
|
-
}
|
|
60
|
-
// 1. Si expiro el dbr `EXPIRED` el cron debe mandar a `FACE_MISMATCH`?
|
|
61
|
-
// 2. Si hubo un matchLevel intermediario el cron debe mandar a `FACE_MISMATCH`? (Probablemente no)
|
|
62
|
-
isRedacted(key, propertyName) {
|
|
63
|
-
return this.rules.some((rule) => {
|
|
64
|
-
var _a;
|
|
65
|
-
return rule.key === key &&
|
|
66
|
-
// A rule without `properties` redacts everything on the key,
|
|
67
|
-
// including initialize — where no property name is passed —
|
|
68
|
-
// while a rule with `properties` matches only those calls.
|
|
69
|
-
(rule.properties === undefined ||
|
|
70
|
-
(propertyName !== undefined &&
|
|
71
|
-
((_a = rule.properties) === null || _a === void 0 ? void 0 : _a.includes(propertyName))));
|
|
72
|
-
});
|
|
144
|
+
const rule = this.rules.find((r) => r.key === context.key);
|
|
145
|
+
const span = (_b = (_a = this.delegate).onMethodCall) === null || _b === void 0 ? void 0 : _b.call(_a, rule
|
|
146
|
+
? Object.assign(Object.assign({}, context), { args: rule.maskArgs(context.functionName, context.args) }) : context);
|
|
147
|
+
return redactSpan(span, rule, context.functionName);
|
|
73
148
|
}
|
|
74
149
|
}
|
|
75
150
|
exports.RedactingEventListener = RedactingEventListener;
|
|
76
151
|
/**
|
|
77
152
|
* Wraps the delegate's EventSpan so success values are redacted before
|
|
78
|
-
* `end` sees them
|
|
153
|
+
* `end` sees them, by delegating to the rule's `maskResult`. `run` (and
|
|
154
|
+
* any future fields) pass through untouched. `functionName` is omitted
|
|
155
|
+
* for the initialize result (the instance itself).
|
|
79
156
|
*/
|
|
80
|
-
function redactSpan(span,
|
|
81
|
-
if (!
|
|
157
|
+
function redactSpan(span, rule, functionName) {
|
|
158
|
+
if (!rule || !(span === null || span === void 0 ? void 0 : span.end)) {
|
|
82
159
|
return span;
|
|
83
160
|
}
|
|
84
161
|
const end = span.end.bind(span);
|
|
85
162
|
return Object.assign(Object.assign({}, span), { end: (outcome) => end(outcome.type === 'success'
|
|
86
|
-
? {
|
|
163
|
+
? {
|
|
164
|
+
type: 'success',
|
|
165
|
+
value: rule.maskResult(functionName, outcome.value),
|
|
166
|
+
}
|
|
87
167
|
: outcome) });
|
|
88
168
|
}
|
|
89
169
|
//# sourceMappingURL=redactingEventListener.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redactingEventListener.js","sourceRoot":"","sources":["../src/redactingEventListener.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"redactingEventListener.js","sourceRoot":"","sources":["../src/redactingEventListener.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -9,41 +9,177 @@ import type {
|
|
|
9
9
|
import type { ServiceKey } from './serviceKey';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* The placeholder that replaces redacted
|
|
13
|
-
*
|
|
12
|
+
* The placeholder that replaces a redacted value when no custom
|
|
13
|
+
* transform is given for it.
|
|
14
14
|
*/
|
|
15
|
-
export const REDACTED_VALUE = '[
|
|
15
|
+
export const REDACTED_VALUE = '[REDACTED]';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Custom masking for one included property, narrowed to the exact
|
|
19
|
+
* method named when {@link RedactionRuleBuilder.redact} is called.
|
|
20
|
+
* Omitting a side fully blanks it with {@link REDACTED_VALUE}; providing
|
|
21
|
+
* one lets you report a partial mask instead (e.g. the last 4 digits of
|
|
22
|
+
* a card number) — both always return a `string`, the masked
|
|
23
|
+
* representation to report in place of the real value.
|
|
24
|
+
*/
|
|
25
|
+
export type Mask<T, K extends Extract<keyof T, string>> = T[K] extends (
|
|
26
|
+
...args: infer A
|
|
27
|
+
) => infer R
|
|
28
|
+
? {
|
|
29
|
+
maskArgs?: (...args: A) => string;
|
|
30
|
+
maskResult?: (result: R) => string;
|
|
31
|
+
}
|
|
32
|
+
: never;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Per-property override, layered on top of a rule's `redactAll` default.
|
|
36
|
+
* `redacted: true` (from {@link RedactionRuleBuilder.redact}) redacts
|
|
37
|
+
* that property, optionally with a custom mask; `redacted: false` (from
|
|
38
|
+
* {@link RedactionRuleBuilder.exclude}) forces it to be left untouched
|
|
39
|
+
* regardless of `redactAll`.
|
|
40
|
+
*/
|
|
41
|
+
interface PropertyOverride {
|
|
42
|
+
redacted: boolean;
|
|
43
|
+
maskArgs?: (...args: any[]) => string;
|
|
44
|
+
maskResult?: (result: any) => string;
|
|
45
|
+
}
|
|
16
46
|
|
|
17
47
|
/**
|
|
18
48
|
* Marks a service — or specific properties of it — as sensitive, so the
|
|
19
|
-
* values flowing through its events are
|
|
49
|
+
* values flowing through its events are redacted. Returned by
|
|
50
|
+
* {@link RedactionRuleBuilder.build}, which is the only way to construct
|
|
51
|
+
* one — the `redactAll`/per-property merge logic lives entirely inside
|
|
52
|
+
* `maskArgs`/`maskResult`, so callers never need to know how a rule
|
|
53
|
+
* reached its decision, only what to do with it.
|
|
20
54
|
*/
|
|
21
55
|
export interface RedactionRule<T> {
|
|
56
|
+
readonly key: ServiceKey<T>;
|
|
57
|
+
|
|
22
58
|
/**
|
|
23
|
-
* The
|
|
24
|
-
*
|
|
59
|
+
* The args to report for a call to `functionName`: unchanged if not
|
|
60
|
+
* redacted, otherwise blanked or run through a custom `maskArgs`.
|
|
25
61
|
*/
|
|
26
|
-
|
|
62
|
+
maskArgs(functionName: string, args: readonly unknown[]): readonly unknown[];
|
|
27
63
|
|
|
28
64
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
65
|
+
* The value to report for a success outcome: unchanged if not
|
|
66
|
+
* redacted, otherwise blanked or run through a custom `maskResult`.
|
|
67
|
+
* Omit `functionName` to ask about the initialize result (the service
|
|
68
|
+
* instance itself), which only the rule's `redactAll` default touches.
|
|
32
69
|
*/
|
|
33
|
-
|
|
70
|
+
maskResult(functionName: string | undefined, result: unknown): unknown;
|
|
34
71
|
}
|
|
35
72
|
|
|
36
|
-
|
|
37
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Fluent, single-key rule builder returned by {@link redactionRule}.
|
|
75
|
+
* `redactAll`, `redact`, and `exclude` all merge into the same rule —
|
|
76
|
+
* call them in any combination, in any order; the more specific
|
|
77
|
+
* per-property calls (`redact`/`exclude`) always win over the blanket
|
|
78
|
+
* `redactAll` default for the properties they name.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* const rules = [
|
|
83
|
+
* redactionRule(SecretClientKey)
|
|
84
|
+
* .redactAll()
|
|
85
|
+
* .build(), // whole service is sensitive
|
|
86
|
+
* redactionRule(BillingKey)
|
|
87
|
+
* .redactAll()
|
|
88
|
+
* .redact('chargeCard', { maskResult: (card) => `card ending in ${card.number.slice(-4)}` })
|
|
89
|
+
* .exclude('ping') // redact everything except this, with one custom mask
|
|
90
|
+
* .build(),
|
|
91
|
+
* redactionRule(VaultKey)
|
|
92
|
+
* .redact('getSecret')
|
|
93
|
+
* .build(), // only this call, nothing else
|
|
94
|
+
* ];
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export class RedactionRuleBuilder<T> {
|
|
98
|
+
private redactAllFlag = false;
|
|
99
|
+
private readonly overrides: Record<string, PropertyOverride> = {};
|
|
100
|
+
|
|
101
|
+
constructor(private readonly key: ServiceKey<T>) {}
|
|
102
|
+
|
|
103
|
+
/** Redacts every property, plus the initialize result, by default. */
|
|
104
|
+
redactAll(): this {
|
|
105
|
+
this.redactAllFlag = true;
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Marks one property (method) as redacted, with optional custom
|
|
111
|
+
* masking. Call repeatedly for several properties. Overrides
|
|
112
|
+
* `redactAll`/`exclude` for this specific property.
|
|
113
|
+
*/
|
|
114
|
+
redact<K extends Extract<keyof T, string>>(name: K, mask?: Mask<T, K>): this {
|
|
115
|
+
this.overrides[name] = { redacted: true, ...mask };
|
|
116
|
+
return this;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Marks one or more properties as explicitly NOT redacted, overriding
|
|
121
|
+
* `redactAll` for just these.
|
|
122
|
+
*/
|
|
123
|
+
exclude(...names: Extract<keyof T, string>[]): this {
|
|
124
|
+
for (const name of names) {
|
|
125
|
+
this.overrides[name] = { redacted: false };
|
|
126
|
+
}
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
build(): RedactionRule<any> {
|
|
131
|
+
if (!this.redactAllFlag && Object.keys(this.overrides).length === 0) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`redactionRule(${this.key.name}) has no effect: call .redactAll() and/or .redact(...) ` +
|
|
134
|
+
'before .build(), otherwise this rule never redacts anything.',
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const redactAllFlag = this.redactAllFlag;
|
|
139
|
+
const overrides = this.overrides;
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
key: this.key,
|
|
143
|
+
maskArgs(functionName, args) {
|
|
144
|
+
const override = overrides[functionName];
|
|
145
|
+
const redacted = override ? override.redacted : redactAllFlag;
|
|
146
|
+
if (!redacted) {
|
|
147
|
+
return args;
|
|
148
|
+
}
|
|
149
|
+
return override?.maskArgs
|
|
150
|
+
? // A custom mask reports one string for the whole call,
|
|
151
|
+
// rather than a value per argument.
|
|
152
|
+
[override.maskArgs(...args)]
|
|
153
|
+
: // Replace each argument rather than the whole array, so the
|
|
154
|
+
// delegate still sees the call's arity.
|
|
155
|
+
args.map(() => REDACTED_VALUE);
|
|
156
|
+
},
|
|
157
|
+
maskResult(functionName, result) {
|
|
158
|
+
const override =
|
|
159
|
+
functionName === undefined ? undefined : overrides[functionName];
|
|
160
|
+
const redacted = override ? override.redacted : redactAllFlag;
|
|
161
|
+
if (!redacted) {
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
return override?.maskResult
|
|
165
|
+
? override.maskResult(result)
|
|
166
|
+
: REDACTED_VALUE;
|
|
167
|
+
},
|
|
168
|
+
} as RedactionRule<any>;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function redactionRule<T>(key: ServiceKey<T>): RedactionRuleBuilder<T> {
|
|
173
|
+
return new RedactionRuleBuilder(key);
|
|
38
174
|
}
|
|
39
175
|
|
|
40
176
|
/**
|
|
41
177
|
* A ServiceEventListener decorator that redacts sensitive values before
|
|
42
178
|
* they reach the wrapped listener. Works with any implementation via
|
|
43
179
|
* delegation: arguments in MethodCallContext and success values in
|
|
44
|
-
* EventOutcome are replaced
|
|
45
|
-
*
|
|
46
|
-
* already scrubbed.
|
|
180
|
+
* EventOutcome are replaced (wholesale, or via a custom transform)
|
|
181
|
+
* before the delegate ever sees them — whatever it captures or exports
|
|
182
|
+
* is already scrubbed.
|
|
47
183
|
*
|
|
48
184
|
* Failure outcomes are passed through unchanged so error reporting keeps
|
|
49
185
|
* working; keep secrets out of error messages at the throwing site.
|
|
@@ -53,8 +189,9 @@ export function redactionRule<T>(key: ServiceKey<T>, properties?: Extract<keyof
|
|
|
53
189
|
* const listener = new RedactingEventListener(
|
|
54
190
|
* new OTELEventListener({ captureArguments: true, captureResults: true }),
|
|
55
191
|
* [
|
|
56
|
-
*
|
|
57
|
-
*
|
|
192
|
+
* redactionRule(SecretClientKey).redactAll().build(), // whole service is sensitive
|
|
193
|
+
* redactionRule(VaultKey).redact('getSecret').build(), // only this call
|
|
194
|
+
* redactionRule(HealthKey).redactAll().exclude('ping').build(), // everything but this call
|
|
58
195
|
* ],
|
|
59
196
|
* );
|
|
60
197
|
* ```
|
|
@@ -66,13 +203,8 @@ export class RedactingEventListener implements ServiceModuleListener {
|
|
|
66
203
|
) {}
|
|
67
204
|
|
|
68
205
|
onInitialize(context: InitializeContext): EventSpan | void {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// `properties`.
|
|
72
|
-
return redactSpan(
|
|
73
|
-
this.delegate.onInitialize?.(context),
|
|
74
|
-
this.isRedacted(context.key),
|
|
75
|
-
);
|
|
206
|
+
const rule = this.rules.find((r) => r.key === context.key);
|
|
207
|
+
return redactSpan(this.delegate.onInitialize?.(context), rule);
|
|
76
208
|
}
|
|
77
209
|
|
|
78
210
|
onDispose(context: DisposeContext): EventSpan | void {
|
|
@@ -81,42 +213,31 @@ export class RedactingEventListener implements ServiceModuleListener {
|
|
|
81
213
|
}
|
|
82
214
|
|
|
83
215
|
onMethodCall(context: MethodCallContext): EventSpan | void {
|
|
84
|
-
const
|
|
216
|
+
const rule = this.rules.find((r) => r.key === context.key);
|
|
85
217
|
const span = this.delegate.onMethodCall?.(
|
|
86
|
-
|
|
87
|
-
?
|
|
88
|
-
|
|
89
|
-
|
|
218
|
+
rule
|
|
219
|
+
? {
|
|
220
|
+
...context,
|
|
221
|
+
args: rule.maskArgs(context.functionName, context.args),
|
|
222
|
+
}
|
|
90
223
|
: context,
|
|
91
224
|
);
|
|
92
|
-
return redactSpan(span,
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// 1. Si expiro el dbr `EXPIRED` el cron debe mandar a `FACE_MISMATCH`?
|
|
96
|
-
// 2. Si hubo un matchLevel intermediario el cron debe mandar a `FACE_MISMATCH`? (Probablemente no)
|
|
97
|
-
private isRedacted(key: ServiceKey<any>, propertyName?: string): boolean {
|
|
98
|
-
return this.rules.some(
|
|
99
|
-
(rule) =>
|
|
100
|
-
rule.key === key &&
|
|
101
|
-
// A rule without `properties` redacts everything on the key,
|
|
102
|
-
// including initialize — where no property name is passed —
|
|
103
|
-
// while a rule with `properties` matches only those calls.
|
|
104
|
-
(rule.properties === undefined ||
|
|
105
|
-
(propertyName !== undefined &&
|
|
106
|
-
rule.properties?.includes(propertyName))),
|
|
107
|
-
);
|
|
225
|
+
return redactSpan(span, rule, context.functionName);
|
|
108
226
|
}
|
|
109
227
|
}
|
|
110
228
|
|
|
111
229
|
/**
|
|
112
230
|
* Wraps the delegate's EventSpan so success values are redacted before
|
|
113
|
-
* `end` sees them
|
|
231
|
+
* `end` sees them, by delegating to the rule's `maskResult`. `run` (and
|
|
232
|
+
* any future fields) pass through untouched. `functionName` is omitted
|
|
233
|
+
* for the initialize result (the instance itself).
|
|
114
234
|
*/
|
|
115
235
|
function redactSpan(
|
|
116
236
|
span: EventSpan | void,
|
|
117
|
-
|
|
237
|
+
rule: RedactionRule<any> | undefined,
|
|
238
|
+
functionName?: string,
|
|
118
239
|
): EventSpan | void {
|
|
119
|
-
if (!
|
|
240
|
+
if (!rule || !span?.end) {
|
|
120
241
|
return span;
|
|
121
242
|
}
|
|
122
243
|
const end = span.end.bind(span);
|
|
@@ -125,7 +246,10 @@ function redactSpan(
|
|
|
125
246
|
end: (outcome: EventOutcome) =>
|
|
126
247
|
end(
|
|
127
248
|
outcome.type === 'success'
|
|
128
|
-
? {
|
|
249
|
+
? {
|
|
250
|
+
type: 'success',
|
|
251
|
+
value: rule.maskResult(functionName, outcome.value),
|
|
252
|
+
}
|
|
129
253
|
: outcome,
|
|
130
254
|
),
|
|
131
255
|
};
|