autotel-audit 1.1.5 → 1.2.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/README.md +15 -0
- package/dist/index.cjs +16 -3
- package/dist/index.d.cts +57 -47
- package/dist/index.d.ts +57 -47
- package/dist/index.js +17 -5
- package/package.json +7 -12
package/README.md
CHANGED
|
@@ -54,6 +54,21 @@ Wraps an async operation with audit metadata and handles success/failure outcome
|
|
|
54
54
|
- `forceKeep?: boolean`: Force event through tail-sampling (default: true)
|
|
55
55
|
- `ctx?: AuditContext`: Provide custom audit context (auto-resolved from trace if omitted)
|
|
56
56
|
- `logger?: RequestLogger`: Override the request logger instance
|
|
57
|
+
- `onMissingContext?: 'throw' | 'warn' | 'skip'`: What to do when no trace context is active (see below)
|
|
58
|
+
|
|
59
|
+
**Without an active trace context**, `withAudit` and `securityEvent` run your handler un-audited. `onMissingContext` sets how they report it:
|
|
60
|
+
|
|
61
|
+
- `skip` is the default when telemetry is off: autotel `init()` has not run and no OpenTelemetry tracer provider is registered. Tests and local runs without a backend stay quiet.
|
|
62
|
+
- `warn` is the default when telemetry is on and the call runs outside a span. You get one warning per action; wrap the call in `trace()`/`instrument()` or pass `ctx`.
|
|
63
|
+
- `throw` fails fast. An explicit `onMissingContext` takes precedence over both defaults.
|
|
64
|
+
|
|
65
|
+
Set a process-wide default with `configureAudit`. A per-call option overrides it:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { configureAudit } from 'autotel-audit';
|
|
69
|
+
|
|
70
|
+
configureAudit({ onMissingContext: 'throw' });
|
|
71
|
+
```
|
|
57
72
|
|
|
58
73
|
**Example with custom context:**
|
|
59
74
|
|
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,18 @@ function resolveContextSafe(ctx) {
|
|
|
26
26
|
setAttributes: (attrs) => span.setAttributes(attrs)
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
|
+
let defaultOnMissingContext;
|
|
30
|
+
/**
|
|
31
|
+
* Process-wide defaults. A per-call `onMissingContext` still wins; pass
|
|
32
|
+
* `undefined` to restore the built-in default.
|
|
33
|
+
*/
|
|
34
|
+
function configureAudit(options) {
|
|
35
|
+
defaultOnMissingContext = options.onMissingContext;
|
|
36
|
+
}
|
|
37
|
+
/** Per-call option, then configureAudit(), then skip-if-telemetry-off-else-warn. */
|
|
38
|
+
function resolveOnMissingContext(explicit) {
|
|
39
|
+
return explicit ?? defaultOnMissingContext ?? ((0, autotel.isInitialized)() || (0, autotel.hasTracerProvider)() ? "warn" : "skip");
|
|
40
|
+
}
|
|
29
41
|
/** A no-op {@link AuditContext} whose attribute setters do nothing. */
|
|
30
42
|
function noopAuditContext() {
|
|
31
43
|
return {
|
|
@@ -166,7 +178,7 @@ function securityEvent(metadata, options = {}) {
|
|
|
166
178
|
const traceCtx = resolveContextSafe(options.ctx);
|
|
167
179
|
if (options.metrics !== false) countSecurityEvent(metadata);
|
|
168
180
|
if (!traceCtx) {
|
|
169
|
-
const mode = options.onMissingContext
|
|
181
|
+
const mode = resolveOnMissingContext(options.onMissingContext);
|
|
170
182
|
if (mode === "throw") throw new Error(MISSING_CONTEXT_MESSAGE);
|
|
171
183
|
if (mode === "warn") warnMissingContextOnce(metadata.name);
|
|
172
184
|
return;
|
|
@@ -598,7 +610,7 @@ function setAuditAttributes(metadata, ctx) {
|
|
|
598
610
|
async function withAudit(metadata, fn, options = {}) {
|
|
599
611
|
const traceCtx = resolveContextSafe(options.ctx);
|
|
600
612
|
if (!traceCtx) {
|
|
601
|
-
const mode = options.onMissingContext
|
|
613
|
+
const mode = resolveOnMissingContext(options.onMissingContext);
|
|
602
614
|
if (mode === "throw") throw new Error(MISSING_CONTEXT_MESSAGE);
|
|
603
615
|
if (mode === "warn") warnMissingContextOnce(metadata.action);
|
|
604
616
|
return fn(noopAuditContext(), options.logger ?? (0, autotel.createNoopRequestLogger)());
|
|
@@ -607,7 +619,7 @@ async function withAudit(metadata, fn, options = {}) {
|
|
|
607
619
|
setAuditAttributes(metadata, traceCtx);
|
|
608
620
|
let logger = options.logger ?? (0, autotel.getRequestLoggerSafe)() ?? void 0;
|
|
609
621
|
if (!logger) {
|
|
610
|
-
if ((options.onMissingContext
|
|
622
|
+
if (resolveOnMissingContext(options.onMissingContext) === "warn") warnMissingLoggerOnce(metadata.action);
|
|
611
623
|
logger = (0, autotel.createNoopRequestLogger)();
|
|
612
624
|
}
|
|
613
625
|
logger.set({ audit: {
|
|
@@ -640,6 +652,7 @@ async function withAudit(metadata, fn, options = {}) {
|
|
|
640
652
|
//#endregion
|
|
641
653
|
exports.SUSPICIOUS_REQUEST_PATTERNS = SUSPICIOUS_REQUEST_PATTERNS;
|
|
642
654
|
exports.applySecurityEventAttributes = applySecurityEventAttributes;
|
|
655
|
+
exports.configureAudit = configureAudit;
|
|
643
656
|
exports.createMcpSecurityEventBridge = createMcpSecurityEventBridge;
|
|
644
657
|
exports.createSecuritySignalProcessor = createSecuritySignalProcessor;
|
|
645
658
|
exports.forceKeepAuditEvent = forceKeepAuditEvent;
|
package/dist/index.d.cts
CHANGED
|
@@ -12,29 +12,38 @@ interface AuditContext {
|
|
|
12
12
|
* How instrumentation should behave when no trace context is available.
|
|
13
13
|
*
|
|
14
14
|
* - `throw` — fail fast (original behaviour). Use when telemetry is mandatory.
|
|
15
|
-
* - `warn` — run the wrapped handler un-audited and log one warning per action
|
|
15
|
+
* - `warn` — run the wrapped handler un-audited and log one warning per action.
|
|
16
16
|
* - `skip` — run the wrapped handler un-audited, silently.
|
|
17
17
|
*
|
|
18
18
|
* Telemetry is observability: a missing context should never crash the business
|
|
19
|
-
* logic it wraps, so the default is best-effort
|
|
19
|
+
* logic it wraps, so the default is best-effort: `skip` when telemetry is off
|
|
20
|
+
* (no autotel init(), no tracer provider: nothing could be recorded anyway),
|
|
21
|
+
* `warn` otherwise.
|
|
20
22
|
*/
|
|
21
23
|
type OnMissingContext = 'throw' | 'warn' | 'skip';
|
|
24
|
+
/**
|
|
25
|
+
* Process-wide defaults. A per-call `onMissingContext` still wins; pass
|
|
26
|
+
* `undefined` to restore the built-in default.
|
|
27
|
+
*/
|
|
28
|
+
export declare function configureAudit(options: {
|
|
29
|
+
onMissingContext?: OnMissingContext;
|
|
30
|
+
}): void;
|
|
22
31
|
//#endregion
|
|
23
32
|
//#region src/security.d.ts
|
|
24
33
|
/**
|
|
25
34
|
* Security event categories, aligned with OWASP A09:2025
|
|
26
35
|
* (Security Logging & Alerting Failures) and ASVS V7.
|
|
27
36
|
*/
|
|
28
|
-
type SecurityEventCategory = 'authentication' | 'authorization' | 'data_access' | 'admin_action' | 'configuration' | 'secrets' | 'rate_limit' | 'validation' | 'supply_chain' | 'llm';
|
|
29
|
-
type SecurityOutcome = 'success' | 'failure' | 'denied' | 'blocked' | 'error';
|
|
37
|
+
export type SecurityEventCategory = 'authentication' | 'authorization' | 'data_access' | 'admin_action' | 'configuration' | 'secrets' | 'rate_limit' | 'validation' | 'supply_chain' | 'llm';
|
|
38
|
+
export type SecurityOutcome = 'success' | 'failure' | 'denied' | 'blocked' | 'error';
|
|
30
39
|
/**
|
|
31
40
|
* Well-known security event names. Free-form names are allowed —
|
|
32
41
|
* this union exists for autocomplete and consistency across services.
|
|
33
42
|
*/
|
|
34
|
-
type SuggestedSecurityEventName = 'auth.login.success' | 'auth.login.failed' | 'auth.mfa.failed' | 'auth.session.revoked' | 'auth.password.reset' | 'auth.account.locked' | 'access.denied' | 'access.role.changed' | 'access.permission.changed' | 'access.tenant.violation' | 'admin.action' | 'config.changed' | 'secret.accessed' | 'secret.rotation.failed' | 'api_key.created' | 'api_key.revoked' | 'rate_limit.exceeded' | 'validation.failed' | 'webhook.signature.failed' | 'dependency.scan.failed' | 'llm.prompt_injection.detected' | 'llm.tool_call.denied' | 'llm.output.blocked' | 'llm.output.budget_exceeded' | 'llm.guard.triggered' | 'llm.action_chain.suspicious' | 'llm.manifest.suspicious' | 'llm.plan.risk.elevated';
|
|
43
|
+
export type SuggestedSecurityEventName = 'auth.login.success' | 'auth.login.failed' | 'auth.mfa.failed' | 'auth.session.revoked' | 'auth.password.reset' | 'auth.account.locked' | 'access.denied' | 'access.role.changed' | 'access.permission.changed' | 'access.tenant.violation' | 'admin.action' | 'config.changed' | 'secret.accessed' | 'secret.rotation.failed' | 'api_key.created' | 'api_key.revoked' | 'rate_limit.exceeded' | 'validation.failed' | 'webhook.signature.failed' | 'dependency.scan.failed' | 'llm.prompt_injection.detected' | 'llm.tool_call.denied' | 'llm.output.blocked' | 'llm.output.budget_exceeded' | 'llm.guard.triggered' | 'llm.action_chain.suspicious' | 'llm.manifest.suspicious' | 'llm.plan.risk.elevated';
|
|
35
44
|
/** An attribute value, as OpenTelemetry allows one on a span. */
|
|
36
|
-
type SecurityAttributeValue = string | number | boolean | string[] | number[] | boolean[];
|
|
37
|
-
interface SecurityEventMetadata {
|
|
45
|
+
export type SecurityAttributeValue = string | number | boolean | string[] | number[] | boolean[];
|
|
46
|
+
export interface SecurityEventMetadata {
|
|
38
47
|
/** Stable, dot-separated event name, e.g. `auth.login.failed`. */
|
|
39
48
|
name: SuggestedSecurityEventName | (string & {});
|
|
40
49
|
category: SecurityEventCategory;
|
|
@@ -51,7 +60,7 @@ interface SecurityEventMetadata {
|
|
|
51
60
|
/** Extra fields ride along as span attributes; see AuditMetadataValue. */
|
|
52
61
|
[key: string]: AuditMetadataValue;
|
|
53
62
|
}
|
|
54
|
-
interface SecurityEventOptions {
|
|
63
|
+
export interface SecurityEventOptions {
|
|
55
64
|
ctx?: AuditContext;
|
|
56
65
|
/**
|
|
57
66
|
* Security events are exempt from tail sampling by default —
|
|
@@ -71,17 +80,17 @@ interface SecurityEventOptions {
|
|
|
71
80
|
*/
|
|
72
81
|
metrics?: boolean;
|
|
73
82
|
/**
|
|
74
|
-
* Behaviour when no trace context can be resolved. Defaults to
|
|
75
|
-
* (
|
|
76
|
-
*
|
|
83
|
+
* Behaviour when no trace context can be resolved. Defaults to the
|
|
84
|
+
* configureAudit() value, else `skip` when telemetry is off and `warn`
|
|
85
|
+
* (once per event) otherwise. See {@link OnMissingContext}.
|
|
77
86
|
*/
|
|
78
87
|
onMissingContext?: OnMissingContext;
|
|
79
88
|
}
|
|
80
|
-
type WithSecurityOptions = SecurityEventOptions;
|
|
89
|
+
export type WithSecurityOptions = SecurityEventOptions;
|
|
81
90
|
interface SecurityAttributeSink {
|
|
82
91
|
setAttribute(key: string, value: SecurityAttributeValue): void;
|
|
83
92
|
}
|
|
84
|
-
declare function applySecurityEventAttributes(sink: SecurityAttributeSink, metadata: SecurityEventMetadata, options?: Pick<SecurityEventOptions, 'forceKeep' | 'metrics'>): void;
|
|
93
|
+
export declare function applySecurityEventAttributes(sink: SecurityAttributeSink, metadata: SecurityEventMetadata, options?: Pick<SecurityEventOptions, 'forceKeep' | 'metrics'>): void;
|
|
85
94
|
/**
|
|
86
95
|
* Record a security event on the active trace and request logger.
|
|
87
96
|
*
|
|
@@ -101,7 +110,7 @@ declare function applySecurityEventAttributes(sink: SecurityAttributeSink, metad
|
|
|
101
110
|
* });
|
|
102
111
|
* ```
|
|
103
112
|
*/
|
|
104
|
-
declare function securityEvent(metadata: SecurityEventMetadata, options?: SecurityEventOptions): void;
|
|
113
|
+
export declare function securityEvent(metadata: SecurityEventMetadata, options?: SecurityEventOptions): void;
|
|
105
114
|
/**
|
|
106
115
|
* Wrap a security-sensitive operation. On success the event outcome is
|
|
107
116
|
* recorded as given (default `success`); a thrown error records
|
|
@@ -115,8 +124,8 @@ declare function securityEvent(metadata: SecurityEventMetadata, options?: Securi
|
|
|
115
124
|
* );
|
|
116
125
|
* ```
|
|
117
126
|
*/
|
|
118
|
-
declare function withSecurity<T>(metadata: SecurityEventMetadata, fn: (ctx: AuditContext, logger: RequestLogger) => T | Promise<T>, options?: WithSecurityOptions): Promise<T>;
|
|
119
|
-
interface HashIdentifierOptions {
|
|
127
|
+
export declare function withSecurity<T>(metadata: SecurityEventMetadata, fn: (ctx: AuditContext, logger: RequestLogger) => T | Promise<T>, options?: WithSecurityOptions): Promise<T>;
|
|
128
|
+
export interface HashIdentifierOptions {
|
|
120
129
|
/** Optional salt; use one stable per-deployment salt to defeat rainbow lookups. */
|
|
121
130
|
salt?: string;
|
|
122
131
|
/** Digest length in hex chars (default 16). */
|
|
@@ -128,7 +137,7 @@ interface HashIdentifierOptions {
|
|
|
128
137
|
*
|
|
129
138
|
* NOT for secrets — never log secrets in any form, hashed or not.
|
|
130
139
|
*/
|
|
131
|
-
declare function hashIdentifier(value: string, options?: HashIdentifierOptions): string;
|
|
140
|
+
export declare function hashIdentifier(value: string, options?: HashIdentifierOptions): string;
|
|
132
141
|
//#endregion
|
|
133
142
|
//#region src/security-signals.d.ts
|
|
134
143
|
/**
|
|
@@ -155,13 +164,13 @@ declare function hashIdentifier(value: string, options?: HashIdentifierOptions):
|
|
|
155
164
|
* observability backend — this processor's job is to make the signals
|
|
156
165
|
* exist, survive sampling, and stay queryable under a stable schema.
|
|
157
166
|
*/
|
|
158
|
-
type AttributeValue = string | number | boolean | Array<null | undefined | string> | Array<null | undefined | number> | Array<null | undefined | boolean>;
|
|
167
|
+
export type AttributeValue = string | number | boolean | Array<null | undefined | string> | Array<null | undefined | number> | Array<null | undefined | boolean>;
|
|
159
168
|
/** The parent context OTel hands a processor. This package never reads it. */
|
|
160
|
-
interface SpanParentContext {
|
|
169
|
+
export interface SpanParentContext {
|
|
161
170
|
getValue?: (key: symbol) => ContextValue;
|
|
162
171
|
}
|
|
163
172
|
/** Whatever OTel stored under a context key. This package never reads one. */
|
|
164
|
-
type ContextValue = object | string | number | boolean | undefined;
|
|
173
|
+
export type ContextValue = object | string | number | boolean | undefined;
|
|
165
174
|
interface MutableSpanLike {
|
|
166
175
|
attributes: Record<string, AttributeValue | undefined>;
|
|
167
176
|
spanContext?: {
|
|
@@ -175,20 +184,20 @@ interface ReadableSpanLike {
|
|
|
175
184
|
traceId: string;
|
|
176
185
|
};
|
|
177
186
|
}
|
|
178
|
-
interface SecuritySignalProcessor {
|
|
187
|
+
export interface SecuritySignalProcessor {
|
|
179
188
|
onStart(span: MutableSpanLike, parentContext?: SpanParentContext): void;
|
|
180
189
|
onEnd(span: ReadableSpanLike): void;
|
|
181
190
|
shutdown(): Promise<void>;
|
|
182
191
|
forceFlush(): Promise<void>;
|
|
183
192
|
}
|
|
184
|
-
interface SuspiciousRequestSignal {
|
|
193
|
+
export interface SuspiciousRequestSignal {
|
|
185
194
|
signal: 'suspicious_request';
|
|
186
195
|
/** Which pattern matched, e.g. `path_traversal`. */
|
|
187
196
|
pattern: string;
|
|
188
197
|
/** The matched request path/URL (as found on the span). */
|
|
189
198
|
target: string;
|
|
190
199
|
}
|
|
191
|
-
interface AuthFailureBurstSignal {
|
|
200
|
+
export interface AuthFailureBurstSignal {
|
|
192
201
|
signal: 'auth_failure_burst';
|
|
193
202
|
/** Value of the configured key attribute (e.g. client address). */
|
|
194
203
|
key: string;
|
|
@@ -197,14 +206,14 @@ interface AuthFailureBurstSignal {
|
|
|
197
206
|
windowMs: number;
|
|
198
207
|
status: number;
|
|
199
208
|
}
|
|
200
|
-
interface LlmExcessiveTokensSignal {
|
|
209
|
+
export interface LlmExcessiveTokensSignal {
|
|
201
210
|
signal: 'llm_excessive_tokens';
|
|
202
211
|
/** Total tokens consumed by the single LLM call. */
|
|
203
212
|
tokens: number;
|
|
204
213
|
maxTokens: number;
|
|
205
214
|
model?: string;
|
|
206
215
|
}
|
|
207
|
-
interface LlmTokenBudgetSignal {
|
|
216
|
+
export interface LlmTokenBudgetSignal {
|
|
208
217
|
signal: 'llm_token_budget_exceeded';
|
|
209
218
|
/** Value of the configured key attribute (e.g. end-user id). */
|
|
210
219
|
key: string;
|
|
@@ -213,7 +222,7 @@ interface LlmTokenBudgetSignal {
|
|
|
213
222
|
budget: number;
|
|
214
223
|
windowMs: number;
|
|
215
224
|
}
|
|
216
|
-
interface LlmActionChainSuspiciousSignal {
|
|
225
|
+
export interface LlmActionChainSuspiciousSignal {
|
|
217
226
|
signal: 'llm_action_chain_suspicious';
|
|
218
227
|
/** Trace where the suspicious chain was observed. */
|
|
219
228
|
traceId: string;
|
|
@@ -223,8 +232,8 @@ interface LlmActionChainSuspiciousSignal {
|
|
|
223
232
|
elapsedMs: number;
|
|
224
233
|
untrustedTool?: string;
|
|
225
234
|
}
|
|
226
|
-
type SecuritySignal = SuspiciousRequestSignal | AuthFailureBurstSignal | LlmExcessiveTokensSignal | LlmTokenBudgetSignal | LlmActionChainSuspiciousSignal;
|
|
227
|
-
interface BurstOptions {
|
|
235
|
+
export type SecuritySignal = SuspiciousRequestSignal | AuthFailureBurstSignal | LlmExcessiveTokensSignal | LlmTokenBudgetSignal | LlmActionChainSuspiciousSignal;
|
|
236
|
+
export interface BurstOptions {
|
|
228
237
|
/** HTTP statuses counted toward a burst. Default `[401, 403]`. */
|
|
229
238
|
statuses?: number[];
|
|
230
239
|
/** Denied responses within the window that trigger a signal. Default 10. */
|
|
@@ -239,7 +248,7 @@ interface BurstOptions {
|
|
|
239
248
|
/** Max distinct clients tracked (oldest evicted). Default 10_000. */
|
|
240
249
|
maxKeys?: number;
|
|
241
250
|
}
|
|
242
|
-
interface LlmSignalOptions {
|
|
251
|
+
export interface LlmSignalOptions {
|
|
243
252
|
/**
|
|
244
253
|
* Single-call token ceiling (`gen_ai.usage.total_tokens`, or input+output).
|
|
245
254
|
* Default 100_000. Pass `false` to disable the per-call check.
|
|
@@ -263,7 +272,7 @@ interface LlmSignalOptions {
|
|
|
263
272
|
maxKeys?: number;
|
|
264
273
|
};
|
|
265
274
|
}
|
|
266
|
-
interface SecuritySignalProcessorOptions {
|
|
275
|
+
export interface SecuritySignalProcessorOptions {
|
|
267
276
|
/** Flag suspicious request paths on span start. Default true. */
|
|
268
277
|
detectSuspiciousRequests?: boolean;
|
|
269
278
|
/** Additional name → pattern pairs checked against the request target. */
|
|
@@ -298,14 +307,14 @@ interface SecuritySignalProcessorOptions {
|
|
|
298
307
|
* Conservative request-target patterns. Tuned for scanner/probe traffic —
|
|
299
308
|
* high signal, low false-positive — not as a WAF. Extend via `extraPatterns`.
|
|
300
309
|
*/
|
|
301
|
-
declare const SUSPICIOUS_REQUEST_PATTERNS: {
|
|
310
|
+
export declare const SUSPICIOUS_REQUEST_PATTERNS: {
|
|
302
311
|
path_traversal: RegExp;
|
|
303
312
|
sensitive_file_probe: RegExp;
|
|
304
313
|
sqli_probe: RegExp;
|
|
305
314
|
xss_probe: RegExp;
|
|
306
315
|
null_byte: RegExp;
|
|
307
316
|
};
|
|
308
|
-
declare function createSecuritySignalProcessor(options?: SecuritySignalProcessorOptions): SecuritySignalProcessor;
|
|
317
|
+
export declare function createSecuritySignalProcessor(options?: SecuritySignalProcessorOptions): SecuritySignalProcessor;
|
|
309
318
|
//#endregion
|
|
310
319
|
//#region src/security-heartbeat.d.ts
|
|
311
320
|
/**
|
|
@@ -327,16 +336,16 @@ declare function createSecuritySignalProcessor(options?: SecuritySignalProcessor
|
|
|
327
336
|
* heartbeat.stop();
|
|
328
337
|
* ```
|
|
329
338
|
*/
|
|
330
|
-
interface SecurityHeartbeatOptions {
|
|
339
|
+
export interface SecurityHeartbeatOptions {
|
|
331
340
|
/** Beat interval in milliseconds. Default 60_000. */
|
|
332
341
|
intervalMs?: number;
|
|
333
342
|
/** Extra counter attributes (keep cardinality low — labels, not data). */
|
|
334
343
|
attributes?: Record<string, string | number | boolean>;
|
|
335
344
|
}
|
|
336
|
-
interface SecurityHeartbeat {
|
|
345
|
+
export interface SecurityHeartbeat {
|
|
337
346
|
stop(): void;
|
|
338
347
|
}
|
|
339
|
-
declare function startSecurityHeartbeat(options?: SecurityHeartbeatOptions): SecurityHeartbeat;
|
|
348
|
+
export declare function startSecurityHeartbeat(options?: SecurityHeartbeatOptions): SecurityHeartbeat;
|
|
340
349
|
//#endregion
|
|
341
350
|
//#region src/mcp-bridge.d.ts
|
|
342
351
|
/**
|
|
@@ -344,7 +353,7 @@ declare function startSecurityHeartbeat(options?: SecurityHeartbeatOptions): Sec
|
|
|
344
353
|
* unified `security.*` schema. Used by `autotel-mcp-instrumentation` when
|
|
345
354
|
* `bridgeSecurityEvents` is enabled.
|
|
346
355
|
*/
|
|
347
|
-
interface McpBridgedSecurityEvent {
|
|
356
|
+
export interface McpBridgedSecurityEvent {
|
|
348
357
|
name: SecurityEventMetadata['name'];
|
|
349
358
|
category: 'llm';
|
|
350
359
|
outcome: SecurityEventMetadata['outcome'];
|
|
@@ -356,7 +365,7 @@ interface McpBridgedSecurityEvent {
|
|
|
356
365
|
/** Extra fields ride along as span attributes; see AuditMetadataValue. */
|
|
357
366
|
[key: string]: AuditMetadataValue;
|
|
358
367
|
}
|
|
359
|
-
interface McpSecurityEventBridgeOptions extends SecurityEventOptions {
|
|
368
|
+
export interface McpSecurityEventBridgeOptions extends SecurityEventOptions {
|
|
360
369
|
/** Optional fixed audit context for bridged events. */
|
|
361
370
|
ctx?: AuditContext;
|
|
362
371
|
}
|
|
@@ -375,10 +384,10 @@ interface McpSecurityEventBridgeOptions extends SecurityEventOptions {
|
|
|
375
384
|
* });
|
|
376
385
|
* ```
|
|
377
386
|
*/
|
|
378
|
-
declare function createMcpSecurityEventBridge(options?: McpSecurityEventBridgeOptions): (metadata: McpBridgedSecurityEvent) => void;
|
|
387
|
+
export declare function createMcpSecurityEventBridge(options?: McpSecurityEventBridgeOptions): (metadata: McpBridgedSecurityEvent) => void;
|
|
379
388
|
//#endregion
|
|
380
389
|
//#region src/index.d.ts
|
|
381
|
-
interface AuditMetadata {
|
|
390
|
+
export interface AuditMetadata {
|
|
382
391
|
action: string;
|
|
383
392
|
resource?: string;
|
|
384
393
|
actorId?: string;
|
|
@@ -392,22 +401,23 @@ interface AuditMetadata {
|
|
|
392
401
|
* span. Scalars and scalar arrays pass through; a Date becomes an ISO string;
|
|
393
402
|
* anything else nested is JSON-serialized.
|
|
394
403
|
*/
|
|
395
|
-
type AuditMetadataValue = string | number | boolean | null | undefined | Date | Array<AuditMetadataValue> | {
|
|
404
|
+
export type AuditMetadataValue = string | number | boolean | null | undefined | Date | Array<AuditMetadataValue> | {
|
|
396
405
|
[key: string]: AuditMetadataValue;
|
|
397
406
|
};
|
|
398
|
-
interface WithAuditOptions {
|
|
407
|
+
export interface WithAuditOptions {
|
|
399
408
|
ctx?: AuditContext;
|
|
400
409
|
emitNow?: boolean;
|
|
401
410
|
forceKeep?: boolean;
|
|
402
411
|
logger?: RequestLogger;
|
|
403
412
|
/**
|
|
404
|
-
* Behaviour when no trace context can be resolved. Defaults to
|
|
405
|
-
* (
|
|
413
|
+
* Behaviour when no trace context can be resolved. Defaults to the
|
|
414
|
+
* configureAudit() value, else `skip` when telemetry is off and `warn`
|
|
415
|
+
* otherwise. See {@link OnMissingContext}.
|
|
406
416
|
*/
|
|
407
417
|
onMissingContext?: OnMissingContext;
|
|
408
418
|
}
|
|
409
|
-
declare function forceKeepAuditEvent(ctx?: AuditContext): void;
|
|
410
|
-
declare function setAuditAttributes(metadata: AuditMetadata, ctx?: AuditContext): void;
|
|
411
|
-
declare function withAudit<T>(metadata: AuditMetadata, fn: (ctx: AuditContext, logger: RequestLogger) => T | Promise<T>, options?: WithAuditOptions): Promise<T>;
|
|
419
|
+
export declare function forceKeepAuditEvent(ctx?: AuditContext): void;
|
|
420
|
+
export declare function setAuditAttributes(metadata: AuditMetadata, ctx?: AuditContext): void;
|
|
421
|
+
export declare function withAudit<T>(metadata: AuditMetadata, fn: (ctx: AuditContext, logger: RequestLogger) => T | Promise<T>, options?: WithAuditOptions): Promise<T>;
|
|
412
422
|
//#endregion
|
|
413
|
-
export {
|
|
423
|
+
export type { AuditContext, OnMissingContext, SecuritySeverity };
|
package/dist/index.d.ts
CHANGED
|
@@ -12,29 +12,38 @@ interface AuditContext {
|
|
|
12
12
|
* How instrumentation should behave when no trace context is available.
|
|
13
13
|
*
|
|
14
14
|
* - `throw` — fail fast (original behaviour). Use when telemetry is mandatory.
|
|
15
|
-
* - `warn` — run the wrapped handler un-audited and log one warning per action
|
|
15
|
+
* - `warn` — run the wrapped handler un-audited and log one warning per action.
|
|
16
16
|
* - `skip` — run the wrapped handler un-audited, silently.
|
|
17
17
|
*
|
|
18
18
|
* Telemetry is observability: a missing context should never crash the business
|
|
19
|
-
* logic it wraps, so the default is best-effort
|
|
19
|
+
* logic it wraps, so the default is best-effort: `skip` when telemetry is off
|
|
20
|
+
* (no autotel init(), no tracer provider: nothing could be recorded anyway),
|
|
21
|
+
* `warn` otherwise.
|
|
20
22
|
*/
|
|
21
23
|
type OnMissingContext = 'throw' | 'warn' | 'skip';
|
|
24
|
+
/**
|
|
25
|
+
* Process-wide defaults. A per-call `onMissingContext` still wins; pass
|
|
26
|
+
* `undefined` to restore the built-in default.
|
|
27
|
+
*/
|
|
28
|
+
export declare function configureAudit(options: {
|
|
29
|
+
onMissingContext?: OnMissingContext;
|
|
30
|
+
}): void;
|
|
22
31
|
//#endregion
|
|
23
32
|
//#region src/security.d.ts
|
|
24
33
|
/**
|
|
25
34
|
* Security event categories, aligned with OWASP A09:2025
|
|
26
35
|
* (Security Logging & Alerting Failures) and ASVS V7.
|
|
27
36
|
*/
|
|
28
|
-
type SecurityEventCategory = 'authentication' | 'authorization' | 'data_access' | 'admin_action' | 'configuration' | 'secrets' | 'rate_limit' | 'validation' | 'supply_chain' | 'llm';
|
|
29
|
-
type SecurityOutcome = 'success' | 'failure' | 'denied' | 'blocked' | 'error';
|
|
37
|
+
export type SecurityEventCategory = 'authentication' | 'authorization' | 'data_access' | 'admin_action' | 'configuration' | 'secrets' | 'rate_limit' | 'validation' | 'supply_chain' | 'llm';
|
|
38
|
+
export type SecurityOutcome = 'success' | 'failure' | 'denied' | 'blocked' | 'error';
|
|
30
39
|
/**
|
|
31
40
|
* Well-known security event names. Free-form names are allowed —
|
|
32
41
|
* this union exists for autocomplete and consistency across services.
|
|
33
42
|
*/
|
|
34
|
-
type SuggestedSecurityEventName = 'auth.login.success' | 'auth.login.failed' | 'auth.mfa.failed' | 'auth.session.revoked' | 'auth.password.reset' | 'auth.account.locked' | 'access.denied' | 'access.role.changed' | 'access.permission.changed' | 'access.tenant.violation' | 'admin.action' | 'config.changed' | 'secret.accessed' | 'secret.rotation.failed' | 'api_key.created' | 'api_key.revoked' | 'rate_limit.exceeded' | 'validation.failed' | 'webhook.signature.failed' | 'dependency.scan.failed' | 'llm.prompt_injection.detected' | 'llm.tool_call.denied' | 'llm.output.blocked' | 'llm.output.budget_exceeded' | 'llm.guard.triggered' | 'llm.action_chain.suspicious' | 'llm.manifest.suspicious' | 'llm.plan.risk.elevated';
|
|
43
|
+
export type SuggestedSecurityEventName = 'auth.login.success' | 'auth.login.failed' | 'auth.mfa.failed' | 'auth.session.revoked' | 'auth.password.reset' | 'auth.account.locked' | 'access.denied' | 'access.role.changed' | 'access.permission.changed' | 'access.tenant.violation' | 'admin.action' | 'config.changed' | 'secret.accessed' | 'secret.rotation.failed' | 'api_key.created' | 'api_key.revoked' | 'rate_limit.exceeded' | 'validation.failed' | 'webhook.signature.failed' | 'dependency.scan.failed' | 'llm.prompt_injection.detected' | 'llm.tool_call.denied' | 'llm.output.blocked' | 'llm.output.budget_exceeded' | 'llm.guard.triggered' | 'llm.action_chain.suspicious' | 'llm.manifest.suspicious' | 'llm.plan.risk.elevated';
|
|
35
44
|
/** An attribute value, as OpenTelemetry allows one on a span. */
|
|
36
|
-
type SecurityAttributeValue = string | number | boolean | string[] | number[] | boolean[];
|
|
37
|
-
interface SecurityEventMetadata {
|
|
45
|
+
export type SecurityAttributeValue = string | number | boolean | string[] | number[] | boolean[];
|
|
46
|
+
export interface SecurityEventMetadata {
|
|
38
47
|
/** Stable, dot-separated event name, e.g. `auth.login.failed`. */
|
|
39
48
|
name: SuggestedSecurityEventName | (string & {});
|
|
40
49
|
category: SecurityEventCategory;
|
|
@@ -51,7 +60,7 @@ interface SecurityEventMetadata {
|
|
|
51
60
|
/** Extra fields ride along as span attributes; see AuditMetadataValue. */
|
|
52
61
|
[key: string]: AuditMetadataValue;
|
|
53
62
|
}
|
|
54
|
-
interface SecurityEventOptions {
|
|
63
|
+
export interface SecurityEventOptions {
|
|
55
64
|
ctx?: AuditContext;
|
|
56
65
|
/**
|
|
57
66
|
* Security events are exempt from tail sampling by default —
|
|
@@ -71,17 +80,17 @@ interface SecurityEventOptions {
|
|
|
71
80
|
*/
|
|
72
81
|
metrics?: boolean;
|
|
73
82
|
/**
|
|
74
|
-
* Behaviour when no trace context can be resolved. Defaults to
|
|
75
|
-
* (
|
|
76
|
-
*
|
|
83
|
+
* Behaviour when no trace context can be resolved. Defaults to the
|
|
84
|
+
* configureAudit() value, else `skip` when telemetry is off and `warn`
|
|
85
|
+
* (once per event) otherwise. See {@link OnMissingContext}.
|
|
77
86
|
*/
|
|
78
87
|
onMissingContext?: OnMissingContext;
|
|
79
88
|
}
|
|
80
|
-
type WithSecurityOptions = SecurityEventOptions;
|
|
89
|
+
export type WithSecurityOptions = SecurityEventOptions;
|
|
81
90
|
interface SecurityAttributeSink {
|
|
82
91
|
setAttribute(key: string, value: SecurityAttributeValue): void;
|
|
83
92
|
}
|
|
84
|
-
declare function applySecurityEventAttributes(sink: SecurityAttributeSink, metadata: SecurityEventMetadata, options?: Pick<SecurityEventOptions, 'forceKeep' | 'metrics'>): void;
|
|
93
|
+
export declare function applySecurityEventAttributes(sink: SecurityAttributeSink, metadata: SecurityEventMetadata, options?: Pick<SecurityEventOptions, 'forceKeep' | 'metrics'>): void;
|
|
85
94
|
/**
|
|
86
95
|
* Record a security event on the active trace and request logger.
|
|
87
96
|
*
|
|
@@ -101,7 +110,7 @@ declare function applySecurityEventAttributes(sink: SecurityAttributeSink, metad
|
|
|
101
110
|
* });
|
|
102
111
|
* ```
|
|
103
112
|
*/
|
|
104
|
-
declare function securityEvent(metadata: SecurityEventMetadata, options?: SecurityEventOptions): void;
|
|
113
|
+
export declare function securityEvent(metadata: SecurityEventMetadata, options?: SecurityEventOptions): void;
|
|
105
114
|
/**
|
|
106
115
|
* Wrap a security-sensitive operation. On success the event outcome is
|
|
107
116
|
* recorded as given (default `success`); a thrown error records
|
|
@@ -115,8 +124,8 @@ declare function securityEvent(metadata: SecurityEventMetadata, options?: Securi
|
|
|
115
124
|
* );
|
|
116
125
|
* ```
|
|
117
126
|
*/
|
|
118
|
-
declare function withSecurity<T>(metadata: SecurityEventMetadata, fn: (ctx: AuditContext, logger: RequestLogger) => T | Promise<T>, options?: WithSecurityOptions): Promise<T>;
|
|
119
|
-
interface HashIdentifierOptions {
|
|
127
|
+
export declare function withSecurity<T>(metadata: SecurityEventMetadata, fn: (ctx: AuditContext, logger: RequestLogger) => T | Promise<T>, options?: WithSecurityOptions): Promise<T>;
|
|
128
|
+
export interface HashIdentifierOptions {
|
|
120
129
|
/** Optional salt; use one stable per-deployment salt to defeat rainbow lookups. */
|
|
121
130
|
salt?: string;
|
|
122
131
|
/** Digest length in hex chars (default 16). */
|
|
@@ -128,7 +137,7 @@ interface HashIdentifierOptions {
|
|
|
128
137
|
*
|
|
129
138
|
* NOT for secrets — never log secrets in any form, hashed or not.
|
|
130
139
|
*/
|
|
131
|
-
declare function hashIdentifier(value: string, options?: HashIdentifierOptions): string;
|
|
140
|
+
export declare function hashIdentifier(value: string, options?: HashIdentifierOptions): string;
|
|
132
141
|
//#endregion
|
|
133
142
|
//#region src/security-signals.d.ts
|
|
134
143
|
/**
|
|
@@ -155,13 +164,13 @@ declare function hashIdentifier(value: string, options?: HashIdentifierOptions):
|
|
|
155
164
|
* observability backend — this processor's job is to make the signals
|
|
156
165
|
* exist, survive sampling, and stay queryable under a stable schema.
|
|
157
166
|
*/
|
|
158
|
-
type AttributeValue = string | number | boolean | Array<null | undefined | string> | Array<null | undefined | number> | Array<null | undefined | boolean>;
|
|
167
|
+
export type AttributeValue = string | number | boolean | Array<null | undefined | string> | Array<null | undefined | number> | Array<null | undefined | boolean>;
|
|
159
168
|
/** The parent context OTel hands a processor. This package never reads it. */
|
|
160
|
-
interface SpanParentContext {
|
|
169
|
+
export interface SpanParentContext {
|
|
161
170
|
getValue?: (key: symbol) => ContextValue;
|
|
162
171
|
}
|
|
163
172
|
/** Whatever OTel stored under a context key. This package never reads one. */
|
|
164
|
-
type ContextValue = object | string | number | boolean | undefined;
|
|
173
|
+
export type ContextValue = object | string | number | boolean | undefined;
|
|
165
174
|
interface MutableSpanLike {
|
|
166
175
|
attributes: Record<string, AttributeValue | undefined>;
|
|
167
176
|
spanContext?: {
|
|
@@ -175,20 +184,20 @@ interface ReadableSpanLike {
|
|
|
175
184
|
traceId: string;
|
|
176
185
|
};
|
|
177
186
|
}
|
|
178
|
-
interface SecuritySignalProcessor {
|
|
187
|
+
export interface SecuritySignalProcessor {
|
|
179
188
|
onStart(span: MutableSpanLike, parentContext?: SpanParentContext): void;
|
|
180
189
|
onEnd(span: ReadableSpanLike): void;
|
|
181
190
|
shutdown(): Promise<void>;
|
|
182
191
|
forceFlush(): Promise<void>;
|
|
183
192
|
}
|
|
184
|
-
interface SuspiciousRequestSignal {
|
|
193
|
+
export interface SuspiciousRequestSignal {
|
|
185
194
|
signal: 'suspicious_request';
|
|
186
195
|
/** Which pattern matched, e.g. `path_traversal`. */
|
|
187
196
|
pattern: string;
|
|
188
197
|
/** The matched request path/URL (as found on the span). */
|
|
189
198
|
target: string;
|
|
190
199
|
}
|
|
191
|
-
interface AuthFailureBurstSignal {
|
|
200
|
+
export interface AuthFailureBurstSignal {
|
|
192
201
|
signal: 'auth_failure_burst';
|
|
193
202
|
/** Value of the configured key attribute (e.g. client address). */
|
|
194
203
|
key: string;
|
|
@@ -197,14 +206,14 @@ interface AuthFailureBurstSignal {
|
|
|
197
206
|
windowMs: number;
|
|
198
207
|
status: number;
|
|
199
208
|
}
|
|
200
|
-
interface LlmExcessiveTokensSignal {
|
|
209
|
+
export interface LlmExcessiveTokensSignal {
|
|
201
210
|
signal: 'llm_excessive_tokens';
|
|
202
211
|
/** Total tokens consumed by the single LLM call. */
|
|
203
212
|
tokens: number;
|
|
204
213
|
maxTokens: number;
|
|
205
214
|
model?: string;
|
|
206
215
|
}
|
|
207
|
-
interface LlmTokenBudgetSignal {
|
|
216
|
+
export interface LlmTokenBudgetSignal {
|
|
208
217
|
signal: 'llm_token_budget_exceeded';
|
|
209
218
|
/** Value of the configured key attribute (e.g. end-user id). */
|
|
210
219
|
key: string;
|
|
@@ -213,7 +222,7 @@ interface LlmTokenBudgetSignal {
|
|
|
213
222
|
budget: number;
|
|
214
223
|
windowMs: number;
|
|
215
224
|
}
|
|
216
|
-
interface LlmActionChainSuspiciousSignal {
|
|
225
|
+
export interface LlmActionChainSuspiciousSignal {
|
|
217
226
|
signal: 'llm_action_chain_suspicious';
|
|
218
227
|
/** Trace where the suspicious chain was observed. */
|
|
219
228
|
traceId: string;
|
|
@@ -223,8 +232,8 @@ interface LlmActionChainSuspiciousSignal {
|
|
|
223
232
|
elapsedMs: number;
|
|
224
233
|
untrustedTool?: string;
|
|
225
234
|
}
|
|
226
|
-
type SecuritySignal = SuspiciousRequestSignal | AuthFailureBurstSignal | LlmExcessiveTokensSignal | LlmTokenBudgetSignal | LlmActionChainSuspiciousSignal;
|
|
227
|
-
interface BurstOptions {
|
|
235
|
+
export type SecuritySignal = SuspiciousRequestSignal | AuthFailureBurstSignal | LlmExcessiveTokensSignal | LlmTokenBudgetSignal | LlmActionChainSuspiciousSignal;
|
|
236
|
+
export interface BurstOptions {
|
|
228
237
|
/** HTTP statuses counted toward a burst. Default `[401, 403]`. */
|
|
229
238
|
statuses?: number[];
|
|
230
239
|
/** Denied responses within the window that trigger a signal. Default 10. */
|
|
@@ -239,7 +248,7 @@ interface BurstOptions {
|
|
|
239
248
|
/** Max distinct clients tracked (oldest evicted). Default 10_000. */
|
|
240
249
|
maxKeys?: number;
|
|
241
250
|
}
|
|
242
|
-
interface LlmSignalOptions {
|
|
251
|
+
export interface LlmSignalOptions {
|
|
243
252
|
/**
|
|
244
253
|
* Single-call token ceiling (`gen_ai.usage.total_tokens`, or input+output).
|
|
245
254
|
* Default 100_000. Pass `false` to disable the per-call check.
|
|
@@ -263,7 +272,7 @@ interface LlmSignalOptions {
|
|
|
263
272
|
maxKeys?: number;
|
|
264
273
|
};
|
|
265
274
|
}
|
|
266
|
-
interface SecuritySignalProcessorOptions {
|
|
275
|
+
export interface SecuritySignalProcessorOptions {
|
|
267
276
|
/** Flag suspicious request paths on span start. Default true. */
|
|
268
277
|
detectSuspiciousRequests?: boolean;
|
|
269
278
|
/** Additional name → pattern pairs checked against the request target. */
|
|
@@ -298,14 +307,14 @@ interface SecuritySignalProcessorOptions {
|
|
|
298
307
|
* Conservative request-target patterns. Tuned for scanner/probe traffic —
|
|
299
308
|
* high signal, low false-positive — not as a WAF. Extend via `extraPatterns`.
|
|
300
309
|
*/
|
|
301
|
-
declare const SUSPICIOUS_REQUEST_PATTERNS: {
|
|
310
|
+
export declare const SUSPICIOUS_REQUEST_PATTERNS: {
|
|
302
311
|
path_traversal: RegExp;
|
|
303
312
|
sensitive_file_probe: RegExp;
|
|
304
313
|
sqli_probe: RegExp;
|
|
305
314
|
xss_probe: RegExp;
|
|
306
315
|
null_byte: RegExp;
|
|
307
316
|
};
|
|
308
|
-
declare function createSecuritySignalProcessor(options?: SecuritySignalProcessorOptions): SecuritySignalProcessor;
|
|
317
|
+
export declare function createSecuritySignalProcessor(options?: SecuritySignalProcessorOptions): SecuritySignalProcessor;
|
|
309
318
|
//#endregion
|
|
310
319
|
//#region src/security-heartbeat.d.ts
|
|
311
320
|
/**
|
|
@@ -327,16 +336,16 @@ declare function createSecuritySignalProcessor(options?: SecuritySignalProcessor
|
|
|
327
336
|
* heartbeat.stop();
|
|
328
337
|
* ```
|
|
329
338
|
*/
|
|
330
|
-
interface SecurityHeartbeatOptions {
|
|
339
|
+
export interface SecurityHeartbeatOptions {
|
|
331
340
|
/** Beat interval in milliseconds. Default 60_000. */
|
|
332
341
|
intervalMs?: number;
|
|
333
342
|
/** Extra counter attributes (keep cardinality low — labels, not data). */
|
|
334
343
|
attributes?: Record<string, string | number | boolean>;
|
|
335
344
|
}
|
|
336
|
-
interface SecurityHeartbeat {
|
|
345
|
+
export interface SecurityHeartbeat {
|
|
337
346
|
stop(): void;
|
|
338
347
|
}
|
|
339
|
-
declare function startSecurityHeartbeat(options?: SecurityHeartbeatOptions): SecurityHeartbeat;
|
|
348
|
+
export declare function startSecurityHeartbeat(options?: SecurityHeartbeatOptions): SecurityHeartbeat;
|
|
340
349
|
//#endregion
|
|
341
350
|
//#region src/mcp-bridge.d.ts
|
|
342
351
|
/**
|
|
@@ -344,7 +353,7 @@ declare function startSecurityHeartbeat(options?: SecurityHeartbeatOptions): Sec
|
|
|
344
353
|
* unified `security.*` schema. Used by `autotel-mcp-instrumentation` when
|
|
345
354
|
* `bridgeSecurityEvents` is enabled.
|
|
346
355
|
*/
|
|
347
|
-
interface McpBridgedSecurityEvent {
|
|
356
|
+
export interface McpBridgedSecurityEvent {
|
|
348
357
|
name: SecurityEventMetadata['name'];
|
|
349
358
|
category: 'llm';
|
|
350
359
|
outcome: SecurityEventMetadata['outcome'];
|
|
@@ -356,7 +365,7 @@ interface McpBridgedSecurityEvent {
|
|
|
356
365
|
/** Extra fields ride along as span attributes; see AuditMetadataValue. */
|
|
357
366
|
[key: string]: AuditMetadataValue;
|
|
358
367
|
}
|
|
359
|
-
interface McpSecurityEventBridgeOptions extends SecurityEventOptions {
|
|
368
|
+
export interface McpSecurityEventBridgeOptions extends SecurityEventOptions {
|
|
360
369
|
/** Optional fixed audit context for bridged events. */
|
|
361
370
|
ctx?: AuditContext;
|
|
362
371
|
}
|
|
@@ -375,10 +384,10 @@ interface McpSecurityEventBridgeOptions extends SecurityEventOptions {
|
|
|
375
384
|
* });
|
|
376
385
|
* ```
|
|
377
386
|
*/
|
|
378
|
-
declare function createMcpSecurityEventBridge(options?: McpSecurityEventBridgeOptions): (metadata: McpBridgedSecurityEvent) => void;
|
|
387
|
+
export declare function createMcpSecurityEventBridge(options?: McpSecurityEventBridgeOptions): (metadata: McpBridgedSecurityEvent) => void;
|
|
379
388
|
//#endregion
|
|
380
389
|
//#region src/index.d.ts
|
|
381
|
-
interface AuditMetadata {
|
|
390
|
+
export interface AuditMetadata {
|
|
382
391
|
action: string;
|
|
383
392
|
resource?: string;
|
|
384
393
|
actorId?: string;
|
|
@@ -392,22 +401,23 @@ interface AuditMetadata {
|
|
|
392
401
|
* span. Scalars and scalar arrays pass through; a Date becomes an ISO string;
|
|
393
402
|
* anything else nested is JSON-serialized.
|
|
394
403
|
*/
|
|
395
|
-
type AuditMetadataValue = string | number | boolean | null | undefined | Date | Array<AuditMetadataValue> | {
|
|
404
|
+
export type AuditMetadataValue = string | number | boolean | null | undefined | Date | Array<AuditMetadataValue> | {
|
|
396
405
|
[key: string]: AuditMetadataValue;
|
|
397
406
|
};
|
|
398
|
-
interface WithAuditOptions {
|
|
407
|
+
export interface WithAuditOptions {
|
|
399
408
|
ctx?: AuditContext;
|
|
400
409
|
emitNow?: boolean;
|
|
401
410
|
forceKeep?: boolean;
|
|
402
411
|
logger?: RequestLogger;
|
|
403
412
|
/**
|
|
404
|
-
* Behaviour when no trace context can be resolved. Defaults to
|
|
405
|
-
* (
|
|
413
|
+
* Behaviour when no trace context can be resolved. Defaults to the
|
|
414
|
+
* configureAudit() value, else `skip` when telemetry is off and `warn`
|
|
415
|
+
* otherwise. See {@link OnMissingContext}.
|
|
406
416
|
*/
|
|
407
417
|
onMissingContext?: OnMissingContext;
|
|
408
418
|
}
|
|
409
|
-
declare function forceKeepAuditEvent(ctx?: AuditContext): void;
|
|
410
|
-
declare function setAuditAttributes(metadata: AuditMetadata, ctx?: AuditContext): void;
|
|
411
|
-
declare function withAudit<T>(metadata: AuditMetadata, fn: (ctx: AuditContext, logger: RequestLogger) => T | Promise<T>, options?: WithAuditOptions): Promise<T>;
|
|
419
|
+
export declare function forceKeepAuditEvent(ctx?: AuditContext): void;
|
|
420
|
+
export declare function setAuditAttributes(metadata: AuditMetadata, ctx?: AuditContext): void;
|
|
421
|
+
export declare function withAudit<T>(metadata: AuditMetadata, fn: (ctx: AuditContext, logger: RequestLogger) => T | Promise<T>, options?: WithAuditOptions): Promise<T>;
|
|
412
422
|
//#endregion
|
|
413
|
-
export {
|
|
423
|
+
export type { AuditContext, OnMissingContext, SecuritySeverity };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AUTOTEL_SAMPLING_TAIL_EVALUATED, AUTOTEL_SAMPLING_TAIL_KEEP, REDACTOR_PATTERNS, createCounter, createNoopRequestLogger, forceKeep, getRequestLoggerSafe, getTraceContext, otelTrace } from "autotel";
|
|
1
|
+
import { AUTOTEL_SAMPLING_TAIL_EVALUATED, AUTOTEL_SAMPLING_TAIL_KEEP, REDACTOR_PATTERNS, createCounter, createNoopRequestLogger, forceKeep, getRequestLoggerSafe, getTraceContext, hasTracerProvider, isInitialized, otelTrace } from "autotel";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { HTTP_STATUS_ATTRIBUTES, SECURITY_ATTR, SECURITY_DENIED_STATUSES, SECURITY_METRICS, escalateSecuritySeverity } from "autotel/security-schema";
|
|
4
4
|
|
|
@@ -25,6 +25,18 @@ function resolveContextSafe(ctx) {
|
|
|
25
25
|
setAttributes: (attrs) => span.setAttributes(attrs)
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
+
let defaultOnMissingContext;
|
|
29
|
+
/**
|
|
30
|
+
* Process-wide defaults. A per-call `onMissingContext` still wins; pass
|
|
31
|
+
* `undefined` to restore the built-in default.
|
|
32
|
+
*/
|
|
33
|
+
function configureAudit(options) {
|
|
34
|
+
defaultOnMissingContext = options.onMissingContext;
|
|
35
|
+
}
|
|
36
|
+
/** Per-call option, then configureAudit(), then skip-if-telemetry-off-else-warn. */
|
|
37
|
+
function resolveOnMissingContext(explicit) {
|
|
38
|
+
return explicit ?? defaultOnMissingContext ?? (isInitialized() || hasTracerProvider() ? "warn" : "skip");
|
|
39
|
+
}
|
|
28
40
|
/** A no-op {@link AuditContext} whose attribute setters do nothing. */
|
|
29
41
|
function noopAuditContext() {
|
|
30
42
|
return {
|
|
@@ -165,7 +177,7 @@ function securityEvent(metadata, options = {}) {
|
|
|
165
177
|
const traceCtx = resolveContextSafe(options.ctx);
|
|
166
178
|
if (options.metrics !== false) countSecurityEvent(metadata);
|
|
167
179
|
if (!traceCtx) {
|
|
168
|
-
const mode = options.onMissingContext
|
|
180
|
+
const mode = resolveOnMissingContext(options.onMissingContext);
|
|
169
181
|
if (mode === "throw") throw new Error(MISSING_CONTEXT_MESSAGE);
|
|
170
182
|
if (mode === "warn") warnMissingContextOnce(metadata.name);
|
|
171
183
|
return;
|
|
@@ -597,7 +609,7 @@ function setAuditAttributes(metadata, ctx) {
|
|
|
597
609
|
async function withAudit(metadata, fn, options = {}) {
|
|
598
610
|
const traceCtx = resolveContextSafe(options.ctx);
|
|
599
611
|
if (!traceCtx) {
|
|
600
|
-
const mode = options.onMissingContext
|
|
612
|
+
const mode = resolveOnMissingContext(options.onMissingContext);
|
|
601
613
|
if (mode === "throw") throw new Error(MISSING_CONTEXT_MESSAGE);
|
|
602
614
|
if (mode === "warn") warnMissingContextOnce(metadata.action);
|
|
603
615
|
return fn(noopAuditContext(), options.logger ?? createNoopRequestLogger());
|
|
@@ -606,7 +618,7 @@ async function withAudit(metadata, fn, options = {}) {
|
|
|
606
618
|
setAuditAttributes(metadata, traceCtx);
|
|
607
619
|
let logger = options.logger ?? getRequestLoggerSafe() ?? void 0;
|
|
608
620
|
if (!logger) {
|
|
609
|
-
if ((options.onMissingContext
|
|
621
|
+
if (resolveOnMissingContext(options.onMissingContext) === "warn") warnMissingLoggerOnce(metadata.action);
|
|
610
622
|
logger = createNoopRequestLogger();
|
|
611
623
|
}
|
|
612
624
|
logger.set({ audit: {
|
|
@@ -637,4 +649,4 @@ async function withAudit(metadata, fn, options = {}) {
|
|
|
637
649
|
}
|
|
638
650
|
|
|
639
651
|
//#endregion
|
|
640
|
-
export { SUSPICIOUS_REQUEST_PATTERNS, applySecurityEventAttributes, createMcpSecurityEventBridge, createSecuritySignalProcessor, forceKeepAuditEvent, hashIdentifier, securityEvent, setAuditAttributes, startSecurityHeartbeat, withAudit, withSecurity };
|
|
652
|
+
export { SUSPICIOUS_REQUEST_PATTERNS, applySecurityEventAttributes, configureAudit, createMcpSecurityEventBridge, createSecuritySignalProcessor, forceKeepAuditEvent, hashIdentifier, securityEvent, setAuditAttributes, startSecurityHeartbeat, withAudit, withSecurity };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autotel-audit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Audit-focused helpers for Autotel (force-keep + structured audit instrumentation)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -29,18 +29,13 @@
|
|
|
29
29
|
"author": "Jag Reehal <jag@jagreehal.com> (https://jagreehal.com)",
|
|
30
30
|
"license": "Apache-2.0",
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"autotel": "7.
|
|
32
|
+
"autotel": "7.10.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@types/node": "^26.
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"eslint-plugin-unicorn": "^64.0.0",
|
|
40
|
-
"tsdown": "^0.22.14",
|
|
41
|
-
"typescript": "^6.0.3",
|
|
42
|
-
"typescript-eslint": "^8.65.0",
|
|
43
|
-
"vitest": "^4.1.10"
|
|
35
|
+
"@types/node": "^26.6.3",
|
|
36
|
+
"tsdown": "^0.23.0",
|
|
37
|
+
"typescript": "^7.0.2",
|
|
38
|
+
"vitest": "^5.0.2"
|
|
44
39
|
},
|
|
45
40
|
"repository": {
|
|
46
41
|
"type": "git",
|
|
@@ -54,7 +49,7 @@
|
|
|
54
49
|
"scripts": {
|
|
55
50
|
"build": "tsdown",
|
|
56
51
|
"dev": "tsdown --watch",
|
|
57
|
-
"lint": "
|
|
52
|
+
"lint": "oxlint -c oxlint.json --disable-nested-config src",
|
|
58
53
|
"type-check": "tsc --noEmit",
|
|
59
54
|
"test": "vitest run"
|
|
60
55
|
}
|