@basaltkit/audit 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/dist/index.d.ts +17 -2
- package/dist/index.js +21 -5
- package/package.json +1 -1
package/LICENSE
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -36,9 +36,18 @@ declare class MemoryAuditStore implements AuditStore {
|
|
|
36
36
|
* 'order.*' matches 'order.created', '**' matches everything.
|
|
37
37
|
*/
|
|
38
38
|
declare function patternMatches(pattern: string, name: string): boolean;
|
|
39
|
+
/** Recursively masks sensitive fields so secrets/PII never reach the trail. */
|
|
40
|
+
declare function redactSensitive(value: unknown, depth?: number): unknown;
|
|
41
|
+
type AuditRedactor = (payload: unknown, event: string) => unknown;
|
|
42
|
+
/** Default payload scrubber: masks common secret keys, ignoring the event name. */
|
|
43
|
+
declare const defaultAuditRedactor: AuditRedactor;
|
|
39
44
|
declare class Audit {
|
|
40
45
|
private readonly store;
|
|
41
|
-
|
|
46
|
+
/** Scrubs each payload before it is stored. Default masks common secret keys. */
|
|
47
|
+
private readonly redact;
|
|
48
|
+
constructor(store: AuditStore,
|
|
49
|
+
/** Scrubs each payload before it is stored. Default masks common secret keys. */
|
|
50
|
+
redact?: AuditRedactor);
|
|
42
51
|
/** Manual entry — for actions no hook covers. */
|
|
43
52
|
record(event: string, payload?: unknown): Promise<AuditEntry>;
|
|
44
53
|
/** @internal used by the plugin's hook/event taps. */
|
|
@@ -59,7 +68,13 @@ interface AuditPluginOptions {
|
|
|
59
68
|
* Default: everything. Pass [] to disable.
|
|
60
69
|
*/
|
|
61
70
|
events?: string[];
|
|
71
|
+
/**
|
|
72
|
+
* Scrubs each payload before it is stored. Defaults to masking common secret
|
|
73
|
+
* keys (password, token, secret, authorization, api-key, …). Pass a custom
|
|
74
|
+
* function to change the policy, or `(p) => p` to store payloads verbatim.
|
|
75
|
+
*/
|
|
76
|
+
redact?: AuditRedactor;
|
|
62
77
|
}
|
|
63
78
|
declare function auditPlugin(options?: AuditPluginOptions): _basaltkit_core.BasaltPlugin<unknown>;
|
|
64
79
|
|
|
65
|
-
export { AUDIT, Audit, type AuditEntry, type AuditPluginOptions, type AuditQuery, type AuditStore, MemoryAuditStore, auditPlugin, patternMatches };
|
|
80
|
+
export { AUDIT, Audit, type AuditEntry, type AuditPluginOptions, type AuditQuery, type AuditRedactor, type AuditStore, MemoryAuditStore, auditPlugin, defaultAuditRedactor, patternMatches, redactSensitive };
|
package/dist/index.js
CHANGED
|
@@ -28,11 +28,24 @@ function patternMatches(pattern, name) {
|
|
|
28
28
|
}
|
|
29
29
|
return patternSegments.length === nameSegments.length;
|
|
30
30
|
}
|
|
31
|
+
var SENSITIVE_KEY = /pass(word|wd)?|secret|token|authorization|api[-_]?key|credential|cookie|session|otp|mfa/i;
|
|
32
|
+
function redactSensitive(value, depth = 0) {
|
|
33
|
+
if (depth > 6 || value === null || typeof value !== "object") return value;
|
|
34
|
+
if (Array.isArray(value)) return value.map((v) => redactSensitive(v, depth + 1));
|
|
35
|
+
const out = {};
|
|
36
|
+
for (const [k, v] of Object.entries(value)) {
|
|
37
|
+
out[k] = SENSITIVE_KEY.test(k) ? "[redacted]" : redactSensitive(v, depth + 1);
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
var defaultAuditRedactor = (payload) => redactSensitive(payload);
|
|
31
42
|
var Audit = class {
|
|
32
|
-
constructor(store) {
|
|
43
|
+
constructor(store, redact = defaultAuditRedactor) {
|
|
33
44
|
this.store = store;
|
|
45
|
+
this.redact = redact;
|
|
34
46
|
}
|
|
35
47
|
store;
|
|
48
|
+
redact;
|
|
36
49
|
/** Manual entry — for actions no hook covers. */
|
|
37
50
|
async record(event, payload) {
|
|
38
51
|
const entry = this.build("manual", event, payload);
|
|
@@ -44,7 +57,8 @@ var Audit = class {
|
|
|
44
57
|
await this.store.append(this.build(source, event, payload));
|
|
45
58
|
}
|
|
46
59
|
async trail(query = {}) {
|
|
47
|
-
|
|
60
|
+
const tenantId = query.tenantId ?? tryCtx()?.["tenant"]?.id;
|
|
61
|
+
return this.store.query(tenantId !== void 0 ? { ...query, tenantId } : query);
|
|
48
62
|
}
|
|
49
63
|
build(source, event, payload) {
|
|
50
64
|
const context = tryCtx();
|
|
@@ -54,7 +68,7 @@ var Audit = class {
|
|
|
54
68
|
id: randomUUID(),
|
|
55
69
|
source,
|
|
56
70
|
event,
|
|
57
|
-
payload,
|
|
71
|
+
payload: this.redact(payload, event),
|
|
58
72
|
actorId: user?.id,
|
|
59
73
|
tenantId: tenant?.id,
|
|
60
74
|
requestId: context?.requestId,
|
|
@@ -70,7 +84,7 @@ function auditPlugin(options = {}) {
|
|
|
70
84
|
return definePlugin({
|
|
71
85
|
name: "basalt:audit",
|
|
72
86
|
register({ container, hooks }) {
|
|
73
|
-
container.singleton(AUDIT, () => new Audit(options.store ?? new MemoryAuditStore()));
|
|
87
|
+
container.singleton(AUDIT, () => new Audit(options.store ?? new MemoryAuditStore(), options.redact ?? defaultAuditRedactor));
|
|
74
88
|
hooks.onAny(async (hook, payload) => {
|
|
75
89
|
if (!hookPatterns.some((pattern) => patternMatches(pattern, hook))) return;
|
|
76
90
|
await container.get(AUDIT).capture("hook", hook, payload);
|
|
@@ -91,5 +105,7 @@ export {
|
|
|
91
105
|
Audit,
|
|
92
106
|
MemoryAuditStore,
|
|
93
107
|
auditPlugin,
|
|
94
|
-
|
|
108
|
+
defaultAuditRedactor,
|
|
109
|
+
patternMatches,
|
|
110
|
+
redactSensitive
|
|
95
111
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@basaltkit/audit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Append-only audit trail for Basalt: automatically records lifecycle hooks and domain events, enriched with actor/tenant/request from the context.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|