autotel-audit 0.2.1 → 0.3.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/dist/index.cjs +486 -433
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +175 -150
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +175 -150
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +485 -431
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/context.ts +81 -13
- package/src/index.test.ts +50 -0
- package/src/index.ts +47 -8
- package/src/security.test.ts +3 -0
- package/src/security.ts +43 -13
package/src/security.ts
CHANGED
|
@@ -3,7 +3,8 @@ import {
|
|
|
3
3
|
AUTOTEL_SAMPLING_TAIL_EVALUATED,
|
|
4
4
|
AUTOTEL_SAMPLING_TAIL_KEEP,
|
|
5
5
|
REDACTOR_PATTERNS,
|
|
6
|
-
|
|
6
|
+
createNoopRequestLogger,
|
|
7
|
+
getRequestLoggerSafe,
|
|
7
8
|
} from 'autotel';
|
|
8
9
|
import type { RequestLogger } from 'autotel';
|
|
9
10
|
import {
|
|
@@ -12,7 +13,15 @@ import {
|
|
|
12
13
|
escalateSecuritySeverity,
|
|
13
14
|
} from 'autotel/security-schema';
|
|
14
15
|
import type { SecuritySeverity } from 'autotel/security-schema';
|
|
15
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
MISSING_CONTEXT_MESSAGE,
|
|
18
|
+
noopAuditContext,
|
|
19
|
+
resolveContextSafe,
|
|
20
|
+
toAttributeValue,
|
|
21
|
+
warnMissingContextOnce,
|
|
22
|
+
type AuditContext,
|
|
23
|
+
type OnMissingContext,
|
|
24
|
+
} from './context';
|
|
16
25
|
import { lazyCounter } from './lazy-counter';
|
|
17
26
|
|
|
18
27
|
export type { SecuritySeverity };
|
|
@@ -105,6 +114,12 @@ export interface SecurityEventOptions {
|
|
|
105
114
|
* to a stable catalogue, never interpolate user input into them.
|
|
106
115
|
*/
|
|
107
116
|
metrics?: boolean;
|
|
117
|
+
/**
|
|
118
|
+
* Behaviour when no trace context can be resolved. Defaults to `warn`
|
|
119
|
+
* (best-effort: record nothing, warn once). A dropped security event is still
|
|
120
|
+
* better than a crashed request — but the warning makes the gap visible.
|
|
121
|
+
*/
|
|
122
|
+
onMissingContext?: OnMissingContext;
|
|
108
123
|
}
|
|
109
124
|
|
|
110
125
|
export type WithSecurityOptions = SecurityEventOptions;
|
|
@@ -201,7 +216,24 @@ export function securityEvent(
|
|
|
201
216
|
metadata: SecurityEventMetadata,
|
|
202
217
|
options: SecurityEventOptions = {},
|
|
203
218
|
): void {
|
|
204
|
-
const traceCtx =
|
|
219
|
+
const traceCtx = resolveContextSafe(options.ctx);
|
|
220
|
+
|
|
221
|
+
// Counters are independent of trace context — always record the security signal
|
|
222
|
+
// even when there's no span to attach attributes to.
|
|
223
|
+
if (options.metrics !== false) {
|
|
224
|
+
countSecurityEvent(metadata);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (!traceCtx) {
|
|
228
|
+
const mode = options.onMissingContext ?? 'warn';
|
|
229
|
+
if (mode === 'throw') {
|
|
230
|
+
throw new Error(MISSING_CONTEXT_MESSAGE);
|
|
231
|
+
}
|
|
232
|
+
if (mode === 'warn') {
|
|
233
|
+
warnMissingContextOnce(metadata.name);
|
|
234
|
+
}
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
205
237
|
|
|
206
238
|
if (options.forceKeep !== false) {
|
|
207
239
|
traceCtx.setAttribute(AUTOTEL_SAMPLING_TAIL_EVALUATED, true);
|
|
@@ -211,11 +243,7 @@ export function securityEvent(
|
|
|
211
243
|
|
|
212
244
|
traceCtx.setAttributes(flattenSecurityAttributes(metadata));
|
|
213
245
|
|
|
214
|
-
|
|
215
|
-
countSecurityEvent(metadata);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const logger = options.logger ?? getRequestLogger();
|
|
246
|
+
const logger = options.logger ?? getRequestLoggerSafe() ?? createNoopRequestLogger();
|
|
219
247
|
logger.set({
|
|
220
248
|
security: {
|
|
221
249
|
name: metadata.name,
|
|
@@ -250,12 +278,14 @@ export async function withSecurity<T>(
|
|
|
250
278
|
fn: (ctx: AuditContext, logger: RequestLogger) => T | Promise<T>,
|
|
251
279
|
options: WithSecurityOptions = {},
|
|
252
280
|
): Promise<T> {
|
|
253
|
-
const traceCtx =
|
|
254
|
-
const logger =
|
|
281
|
+
const traceCtx = resolveContextSafe(options.ctx);
|
|
282
|
+
const logger =
|
|
283
|
+
options.logger ?? getRequestLoggerSafe() ?? createNoopRequestLogger();
|
|
284
|
+
const ctx = traceCtx ?? noopAuditContext();
|
|
255
285
|
|
|
256
286
|
try {
|
|
257
|
-
const result = await fn(
|
|
258
|
-
securityEvent(metadata, { ...options, ctx: traceCtx, logger });
|
|
287
|
+
const result = await fn(ctx, logger);
|
|
288
|
+
securityEvent(metadata, { ...options, ctx: traceCtx ?? undefined, logger });
|
|
259
289
|
return result;
|
|
260
290
|
} catch (error) {
|
|
261
291
|
const asError = error instanceof Error ? error : new Error(String(error));
|
|
@@ -267,7 +297,7 @@ export async function withSecurity<T>(
|
|
|
267
297
|
// but an explicit `critical` stays critical.
|
|
268
298
|
severity: escalateSecuritySeverity(metadata.severity ?? 'info', 'error'),
|
|
269
299
|
},
|
|
270
|
-
{ ...options, ctx: traceCtx, logger },
|
|
300
|
+
{ ...options, ctx: traceCtx ?? undefined, logger },
|
|
271
301
|
);
|
|
272
302
|
logger.error(asError, {
|
|
273
303
|
security: {
|