@agenticprimitives/audit 0.1.0-alpha.2
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 +21 -0
- package/README.md +49 -0
- package/dist/index.d.ts +347 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +496 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
- package/spec.md +6 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Agentic Trust Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# @agenticprimitives/audit
|
|
2
|
+
|
|
3
|
+
Append-only audit-event schema + sink interface for the agentic primitives stack. Transport-agnostic primitive — consumers wire concrete persistent sinks (D1, Postgres, Cloud Logging, SIEM) behind a single interface.
|
|
4
|
+
|
|
5
|
+
Closes system-audit finding **C3** (forensic trail) from `docs/architecture/product-readiness-audit.md`.
|
|
6
|
+
|
|
7
|
+
See [`spec.md`](./spec.md) → [`specs/206-audit.md`](../../specs/206-audit.md).
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import {
|
|
13
|
+
buildEvent,
|
|
14
|
+
createConsoleAuditSink,
|
|
15
|
+
composeSinks,
|
|
16
|
+
type AuditSink,
|
|
17
|
+
} from '@agenticprimitives/audit';
|
|
18
|
+
|
|
19
|
+
const sink: AuditSink = composeSinks(
|
|
20
|
+
createConsoleAuditSink({ prefix: '[AUDIT]' }),
|
|
21
|
+
// ...add a persistent sink from your app layer (D1, Postgres, etc.)
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
await sink.write(
|
|
25
|
+
buildEvent({
|
|
26
|
+
action: 'delegation.verify.accept',
|
|
27
|
+
outcome: 'success',
|
|
28
|
+
actor: { type: 'agent', id: 'demo-a2a' },
|
|
29
|
+
subject: { type: 'jti', id: claims.jti },
|
|
30
|
+
audience: claims.aud,
|
|
31
|
+
correlationId,
|
|
32
|
+
}),
|
|
33
|
+
);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Boundaries
|
|
37
|
+
|
|
38
|
+
- Ships **in-band sinks only** (console + memory + composeSinks). Concrete persistent sinks (D1, Postgres, Cloud Logging) live in consumer apps.
|
|
39
|
+
- Sits at the **base of the dep graph** alongside `@agenticprimitives/types`. Forbidden from importing connect-auth / agent-account / delegation / key-custody / tool-policy / mcp-runtime — those import us.
|
|
40
|
+
|
|
41
|
+
## Invariants
|
|
42
|
+
|
|
43
|
+
- **No secret material in events.** Emitters hash sessionIds, omit raw keys / tokens. `createPiiGuardrailSink` (v0.1) is defense-in-depth.
|
|
44
|
+
- **Append-only by interface.** `AuditSink` has `write` only — no `delete` / `update`. Persistent sinks must enforce at the schema level.
|
|
45
|
+
- **Fail-soft for the trail.** Emit failures never throw to the caller. The decision (verify, sign) is the security boundary.
|
|
46
|
+
|
|
47
|
+
## Status
|
|
48
|
+
|
|
49
|
+
Pre-alpha. Schema versioned (`schemaVersion: 1`); bumps are major.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agenticprimitives/audit — append-only audit/forensics primitives.
|
|
3
|
+
*
|
|
4
|
+
* Owns:
|
|
5
|
+
* - The canonical `AuditEvent` schema every other package emits.
|
|
6
|
+
* - The `AuditSink` interface a consumer implements (or composes from
|
|
7
|
+
* one of the provided sinks).
|
|
8
|
+
* - In-band sinks: `createConsoleAuditSink`, `createMemoryAuditSink`.
|
|
9
|
+
*
|
|
10
|
+
* Does NOT own:
|
|
11
|
+
* - Concrete persistence backends (D1 / Cloud Logging / Splunk / etc.).
|
|
12
|
+
* Those live with the consumer that needs them.
|
|
13
|
+
* - Domain semantics — `action` is a free-string the emitter chooses
|
|
14
|
+
* (e.g. `<package>.<surface>.<outcome>`).
|
|
15
|
+
*
|
|
16
|
+
* Doctrine (per CLAUDE.md): zero domain knowledge. The schema is
|
|
17
|
+
* deliberately structural — every package's emitter knows what to put
|
|
18
|
+
* in `subject` / `context`; this package only stamps the shape.
|
|
19
|
+
*
|
|
20
|
+
* Closes system audit finding C3.
|
|
21
|
+
*/
|
|
22
|
+
import type { Hex } from '@agenticprimitives/types';
|
|
23
|
+
/**
|
|
24
|
+
* One audit event. Designed to be JSON-serialisable for sinks that
|
|
25
|
+
* persist or forward over HTTP. All fields are strings or numbers so
|
|
26
|
+
* the on-wire shape matches the in-memory shape with no codec needed.
|
|
27
|
+
*
|
|
28
|
+
* `id` is per-event (UUID-ish); `correlationId` ties multiple events
|
|
29
|
+
* to a single request / flow / session.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* H7-F.2 / PKG-audit-002 / EXT-037 closure — canonical action registry.
|
|
33
|
+
*
|
|
34
|
+
* Free-string `action` previously let each emitter pick its own name
|
|
35
|
+
* with no central catalog — drift hazard the moment more than one
|
|
36
|
+
* package emits the same conceptual event. The registry below is the
|
|
37
|
+
* canonical list. `action: AuditAction` keeps the string convention
|
|
38
|
+
* (still indexes cleanly in structured-log backends) but type-checks
|
|
39
|
+
* the caller against the registry.
|
|
40
|
+
*
|
|
41
|
+
* Convention: `<package>.<surface>.<outcome?>`.
|
|
42
|
+
*
|
|
43
|
+
* Each emitting package documents WHICH of these names IT emits in
|
|
44
|
+
* its `AUDIT.md` "Audit events emitted" section.
|
|
45
|
+
*
|
|
46
|
+
* To add a new action:
|
|
47
|
+
* 1. Add the string to `AUDIT_ACTION_REGISTRY` (below).
|
|
48
|
+
* 2. Add it to the owning package's `AUDIT.md` list.
|
|
49
|
+
* 3. The TypeScript union `AuditAction` is derived automatically.
|
|
50
|
+
*
|
|
51
|
+
* `action` STILL accepts a raw `string` (the legacy escape hatch) so a
|
|
52
|
+
* package can ship a one-off event without churn. Production paths
|
|
53
|
+
* should use the typed name + extend the registry.
|
|
54
|
+
*/
|
|
55
|
+
export declare const AUDIT_ACTION_REGISTRY: readonly ["delegation.mint", "delegation.verify.accept", "delegation.verify.reject", "delegation.revoke", "key-custody.sign", "key-custody.rotate", "key-custody.envelope.encrypt", "key-custody.envelope.decrypt", "key-custody.session-data-key.generate", "account-custody.schedule", "account-custody.apply", "account-custody.cancel", "account-custody.credential.add", "account-custody.credential.remove", "account-custody.credential.replace", "agent-account.admin.propose", "agent-account.admin.execute", "agent-account.admin.cancel", "connect-auth.session.mint", "connect-auth.session.verify.accept", "connect-auth.session.verify.reject", "connect.issue.accept", "connect.issue.reject", "mcp-runtime.with-delegation.accept", "mcp-runtime.with-delegation.reject", "mcp-runtime.service-mac.issue", "mcp-runtime.service-mac.accept", "mcp-runtime.service-mac.reject", "agent-naming.resolve", "agent-naming.register", "agent-naming.records.update", "agent-naming.primary-name.update", "agent-naming.subregistry.update"];
|
|
56
|
+
export type AuditAction = (typeof AUDIT_ACTION_REGISTRY)[number];
|
|
57
|
+
/** Type guard for callers that need to validate an arbitrary string. */
|
|
58
|
+
export declare function isCanonicalAuditAction(s: string): s is AuditAction;
|
|
59
|
+
export interface AuditEvent {
|
|
60
|
+
/** Per-event unique ID. UUID v4 (or v7 if you have it) recommended. */
|
|
61
|
+
id: string;
|
|
62
|
+
/** Ties events from the same browser request / job / pipeline. */
|
|
63
|
+
correlationId?: string;
|
|
64
|
+
/** ISO-8601 timestamp (UTC). */
|
|
65
|
+
timestamp: string;
|
|
66
|
+
/**
|
|
67
|
+
* Dotted action name. Prefer {@link AuditAction} from the canonical
|
|
68
|
+
* {@link AUDIT_ACTION_REGISTRY}; raw `string` remains accepted for
|
|
69
|
+
* legacy callers + one-off events. Convention:
|
|
70
|
+
* `<package>.<surface>.<outcome?>`. Each emitting package documents
|
|
71
|
+
* its actions in its `AUDIT.md` under "Audit events emitted".
|
|
72
|
+
*/
|
|
73
|
+
action: AuditAction | (string & {});
|
|
74
|
+
/**
|
|
75
|
+
* Outcome class. `success` for normal operations, `denied` for
|
|
76
|
+
* authority/policy rejects (NOT errors — denial IS a security
|
|
77
|
+
* outcome that must be auditable), `error` for unexpected failures.
|
|
78
|
+
*/
|
|
79
|
+
outcome: 'success' | 'denied' | 'error';
|
|
80
|
+
/**
|
|
81
|
+
* Who initiated. Use stable identifiers — smart-account address,
|
|
82
|
+
* service identity (`a2a-to-mcp`), system label (`bundler`, etc.).
|
|
83
|
+
*/
|
|
84
|
+
actor?: {
|
|
85
|
+
type: 'user' | 'service' | 'system' | 'unknown';
|
|
86
|
+
/** Smart-account address, service name, or system label. */
|
|
87
|
+
id?: string;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* What was acted on. Free-form `type` discriminator + opaque `id` —
|
|
91
|
+
* consumers choose conventions like `tool`/`session`/`tx`/etc. The
|
|
92
|
+
* audit package deliberately does not enumerate types because that
|
|
93
|
+
* would couple it to domain vocabulary.
|
|
94
|
+
*/
|
|
95
|
+
subject?: {
|
|
96
|
+
type: string;
|
|
97
|
+
id: string;
|
|
98
|
+
};
|
|
99
|
+
/** Human-readable + structured reason for non-success outcomes. */
|
|
100
|
+
reason?: string;
|
|
101
|
+
/**
|
|
102
|
+
* Free-form additional structured context. Keep keys flat (no nested
|
|
103
|
+
* objects) so structured-log backends (Cloud Logging, Datadog) index
|
|
104
|
+
* cleanly.
|
|
105
|
+
*/
|
|
106
|
+
context?: Record<string, string | number | boolean | null>;
|
|
107
|
+
/** Audience / route for service-to-service events. */
|
|
108
|
+
audience?: string;
|
|
109
|
+
/** Chain ID for on-chain events. */
|
|
110
|
+
chainId?: number;
|
|
111
|
+
/** On-chain tx / digest, when relevant. */
|
|
112
|
+
digest?: Hex;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* The minimal contract every audit sink implements. `write` is async
|
|
116
|
+
* because the most useful sinks are persistent (D1, HTTP, etc.); even
|
|
117
|
+
* in-memory sinks return a resolved promise for shape symmetry.
|
|
118
|
+
*
|
|
119
|
+
* Sinks MUST be append-only — events never edited or deleted. The
|
|
120
|
+
* sink's persistence backend (D1 schema, log retention, etc.) should
|
|
121
|
+
* enforce this, but the interface assumes it.
|
|
122
|
+
*
|
|
123
|
+
* Sinks SHOULD be fail-soft: an audit-emission failure should NOT
|
|
124
|
+
* propagate as a request failure. Instead, log the failure to a
|
|
125
|
+
* fallback channel (the in-band console). Implementations decide
|
|
126
|
+
* how to handle the rare double-failure.
|
|
127
|
+
*/
|
|
128
|
+
export interface AuditSink {
|
|
129
|
+
/** Emit one event. Resolves on durable acceptance; rejects only on
|
|
130
|
+
* programmer error (malformed event), never on transient backend
|
|
131
|
+
* failure (those are absorbed inside the sink). */
|
|
132
|
+
write(event: AuditEvent): Promise<void>;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Emit events as one-line JSON to `console.log`. Suitable for
|
|
136
|
+
* Cloudflare Workers (where `wrangler tail` surfaces console output)
|
|
137
|
+
* and for local dev. Production deploys typically PAIR this with a
|
|
138
|
+
* durable sink — see `composeSinks` below.
|
|
139
|
+
*/
|
|
140
|
+
export declare function createConsoleAuditSink(opts?: {
|
|
141
|
+
/** Prefix used to filter the audit events out of regular logs.
|
|
142
|
+
* Default: `'[AUDIT]'`. */
|
|
143
|
+
prefix?: string;
|
|
144
|
+
}): AuditSink;
|
|
145
|
+
/**
|
|
146
|
+
* In-memory ring buffer. **Test-only.** Production sinks must be
|
|
147
|
+
* durable; this is for unit tests + the demo's recent-events UI.
|
|
148
|
+
*/
|
|
149
|
+
export interface MemoryAuditSink extends AuditSink {
|
|
150
|
+
/** Read out the captured events (oldest first). */
|
|
151
|
+
events(): AuditEvent[];
|
|
152
|
+
/** Clear the buffer. */
|
|
153
|
+
reset(): void;
|
|
154
|
+
}
|
|
155
|
+
export declare function createMemoryAuditSink(opts?: {
|
|
156
|
+
/** Maximum events retained; older events discarded FIFO. Default 1024. */
|
|
157
|
+
capacity?: number;
|
|
158
|
+
}): MemoryAuditSink;
|
|
159
|
+
/**
|
|
160
|
+
* Combine multiple sinks into one with **fail-soft** semantics — each
|
|
161
|
+
* emit fans out to all sinks sequentially; a failure in one sink does NOT
|
|
162
|
+
* short-circuit the others, AND does NOT propagate to the caller. The
|
|
163
|
+
* caller's `await write(event)` always resolves successfully, even if every
|
|
164
|
+
* sink failed. Best for telemetry / metrics-grade events where dropping
|
|
165
|
+
* one event is acceptable.
|
|
166
|
+
*
|
|
167
|
+
* **H7-B.7 / PKG-AUDIT-001 closure** — security-critical events (authority
|
|
168
|
+
* changes, custody operations, signing-side actions) need durable-before-
|
|
169
|
+
* commit semantics; use {@link composeFailHardSinks} for those. The
|
|
170
|
+
* audit package stays domain-agnostic — emitting packages document which
|
|
171
|
+
* of their action names belong to the fail-hard class in their own docs.
|
|
172
|
+
*
|
|
173
|
+
* Production wiring is typically `composeSinks(d1Sink, consoleSink)` for
|
|
174
|
+
* non-critical events, and `composeFailHardSinks(d1Sink)` for events that
|
|
175
|
+
* MUST be persisted before the action commits.
|
|
176
|
+
*/
|
|
177
|
+
export declare function composeSinks(...sinks: AuditSink[]): AuditSink;
|
|
178
|
+
/** Explicit alias of {@link composeSinks} — fail-soft, no propagation. */
|
|
179
|
+
export declare function composeFailSoftSinks(...sinks: AuditSink[]): AuditSink;
|
|
180
|
+
/**
|
|
181
|
+
* H7-B.7 — Combine multiple sinks with **fail-hard** semantics for
|
|
182
|
+
* security-critical events. The first sink failure THROWS the original
|
|
183
|
+
* error (after the remaining sinks have been attempted — every sink still
|
|
184
|
+
* gets a chance to record, but the caller's `await write(event)` rejects
|
|
185
|
+
* if any failed).
|
|
186
|
+
*
|
|
187
|
+
* Use for events whose absence from durable storage would be a security
|
|
188
|
+
* regression — the precise action vocabulary is each emitting package's
|
|
189
|
+
* responsibility to document. The caller's flow MUST treat a thrown audit
|
|
190
|
+
* as the action FAILING — do not commit the on-chain or off-chain effect
|
|
191
|
+
* if the audit didn't persist.
|
|
192
|
+
*
|
|
193
|
+
* Closure: PKG-AUDIT-001 / EXT-022 / CT-11.
|
|
194
|
+
*/
|
|
195
|
+
export declare function composeFailHardSinks(...sinks: AuditSink[]): AuditSink;
|
|
196
|
+
export interface MetricsSink {
|
|
197
|
+
/**
|
|
198
|
+
* Increment a monotonic counter. `value` defaults to 1.
|
|
199
|
+
* Typical use: request count, error count, retry count.
|
|
200
|
+
*/
|
|
201
|
+
increment(name: string, value?: number, tags?: Record<string, string>): void;
|
|
202
|
+
/**
|
|
203
|
+
* Record a single observation in a histogram / distribution.
|
|
204
|
+
* Typical use: latency, payload size, response bytes.
|
|
205
|
+
*/
|
|
206
|
+
observe(name: string, value: number, tags?: Record<string, string>): void;
|
|
207
|
+
/**
|
|
208
|
+
* Set a gauge — a value that can go up or down. The latest value wins.
|
|
209
|
+
* Typical use: queue depth, in-flight session count, active connections.
|
|
210
|
+
*/
|
|
211
|
+
gauge(name: string, value: number, tags?: Record<string, string>): void;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* No-op sink. The default when consumers haven't wired metrics yet —
|
|
215
|
+
* runtime always succeeds, never observably affects throughput.
|
|
216
|
+
*/
|
|
217
|
+
export declare const noopMetricsSink: MetricsSink;
|
|
218
|
+
/**
|
|
219
|
+
* Console-out metrics sink for local dev. NOT a production sink — the
|
|
220
|
+
* volume in production overwhelms console output and provides no
|
|
221
|
+
* aggregation. Production wires `composeMetricsSinks(prometheusSink, …)`.
|
|
222
|
+
*/
|
|
223
|
+
export declare function createConsoleMetricsSink(opts?: {
|
|
224
|
+
prefix?: string;
|
|
225
|
+
}): MetricsSink;
|
|
226
|
+
/**
|
|
227
|
+
* Fan-out to multiple metrics sinks. Same fail-soft semantics as
|
|
228
|
+
* `composeSinks` for audit: a failure in one sink doesn't short-circuit
|
|
229
|
+
* the others. Metrics emissions are synchronous + must never block
|
|
230
|
+
* the request path; sinks that need async work MUST queue internally.
|
|
231
|
+
*/
|
|
232
|
+
export declare function composeMetricsSinks(...sinks: MetricsSink[]): MetricsSink;
|
|
233
|
+
/**
|
|
234
|
+
* In-memory metrics sink for test assertions. Counts + histograms +
|
|
235
|
+
* gauges are captured in plain Maps; `.snapshot()` returns the
|
|
236
|
+
* accumulated state for assertions. NOT a production sink.
|
|
237
|
+
*/
|
|
238
|
+
export interface MemoryMetricsSink extends MetricsSink {
|
|
239
|
+
snapshot(): {
|
|
240
|
+
counts: Map<string, number>;
|
|
241
|
+
observations: Map<string, number[]>;
|
|
242
|
+
gauges: Map<string, number>;
|
|
243
|
+
};
|
|
244
|
+
reset(): void;
|
|
245
|
+
}
|
|
246
|
+
export declare function createMemoryMetricsSink(): MemoryMetricsSink;
|
|
247
|
+
/**
|
|
248
|
+
* One detected probable-leak. Reported to `onDetect` and embedded in
|
|
249
|
+
* the redacted event's `_pii` context field so reviewers can trace
|
|
250
|
+
* where the leak came from without seeing the value.
|
|
251
|
+
*/
|
|
252
|
+
export interface PiiFinding {
|
|
253
|
+
/** Dotted path within the event (e.g. `context.token`, `subject.id`, `reason`). */
|
|
254
|
+
path: string;
|
|
255
|
+
/** Why we flagged it (`long-hex`, `jwt-shape`, `pem-block`, `secret-substring`). */
|
|
256
|
+
reason: string;
|
|
257
|
+
/** Safe descriptor of the original value (e.g. `hex-130`, `jwt-3-seg`). */
|
|
258
|
+
preview: string;
|
|
259
|
+
}
|
|
260
|
+
export interface PiiGuardrailOpts {
|
|
261
|
+
/**
|
|
262
|
+
* What to do on detection.
|
|
263
|
+
* - `'redact'` (default): replace the offending value in-place with a
|
|
264
|
+
* safe descriptor (`<redacted:<reason>:<preview>>`); forward the
|
|
265
|
+
* sanitized event to the inner sink. Preserves forensics minus the
|
|
266
|
+
* leaked field.
|
|
267
|
+
* - `'drop'`: do NOT forward the event at all. Strictest setting;
|
|
268
|
+
* loses the row from the trail entirely.
|
|
269
|
+
* - `'warn'`: forward unchanged, just notify via `onDetect`. Useful
|
|
270
|
+
* during initial roll-out so reviewers see what would be flagged
|
|
271
|
+
* before enforcement kicks in.
|
|
272
|
+
*/
|
|
273
|
+
mode?: 'redact' | 'drop' | 'warn';
|
|
274
|
+
/**
|
|
275
|
+
* Maximum hex string length (chars, including any `0x` prefix) tolerated
|
|
276
|
+
* in non-allowlisted positions. Default 80 — leaves Ethereum addresses
|
|
277
|
+
* (42), keccak/sha256 digests (66), and tx hashes (66) all comfortably
|
|
278
|
+
* below the threshold while catching raw private keys (130) and longer
|
|
279
|
+
* session secrets.
|
|
280
|
+
*/
|
|
281
|
+
maxHexLength?: number;
|
|
282
|
+
/**
|
|
283
|
+
* `context` keys (and well-known top-level fields) where long hex is
|
|
284
|
+
* legitimately expected. Merged with the built-in safe set
|
|
285
|
+
* (`signerAddress`, `address`, `paymaster`, `entryPoint`, `keyId`,
|
|
286
|
+
* `nonceHash`, `sessionHash`, `digest`, `txHash`, `blockHash`, `jti`,
|
|
287
|
+
* `eventId`).
|
|
288
|
+
*/
|
|
289
|
+
allowKeys?: string[];
|
|
290
|
+
/**
|
|
291
|
+
* `subject.type` values whose `subject.id` is allowed to be any length
|
|
292
|
+
* of hex (e.g. `sign-digest` carries a 32-byte digest). Merged with
|
|
293
|
+
* the built-in safe set (`jti`, `sign-digest`, `tx-hash`, `address`,
|
|
294
|
+
* `event-id`).
|
|
295
|
+
*/
|
|
296
|
+
allowSubjectTypes?: string[];
|
|
297
|
+
/**
|
|
298
|
+
* Callback invoked on every detection, regardless of `mode`. The
|
|
299
|
+
* sanitized event (or original, in `warn` mode) is passed alongside
|
|
300
|
+
* the findings. Useful for routing a "guardrail caught a leak" signal
|
|
301
|
+
* to a separate alerting channel.
|
|
302
|
+
*/
|
|
303
|
+
onDetect?: (info: {
|
|
304
|
+
event: AuditEvent;
|
|
305
|
+
findings: PiiFinding[];
|
|
306
|
+
}) => void;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Wrapper sink that scans event payloads for likely-secret material and
|
|
310
|
+
* either redacts / drops / warns before forwarding to `inner`. **This is
|
|
311
|
+
* defense-in-depth**, not a substitute for the emitter discipline rule
|
|
312
|
+
* "MUST hash or omit raw secrets" (per the audit package CLAUDE.md
|
|
313
|
+
* invariant). It catches accidental leaks at the sink boundary.
|
|
314
|
+
*
|
|
315
|
+
* Production wiring example:
|
|
316
|
+
* ```ts
|
|
317
|
+
* const sink = composeSinks(
|
|
318
|
+
* createConsoleAuditSink({ prefix: '[AUDIT]' }),
|
|
319
|
+
* createPiiGuardrailSink(createD1AuditSink(env.DB), { mode: 'redact' }),
|
|
320
|
+
* );
|
|
321
|
+
* ```
|
|
322
|
+
*
|
|
323
|
+
* The guardrail's own decisions are not themselves audit events — they
|
|
324
|
+
* surface through `onDetect`. Use that to wire alerts ("found a leak in
|
|
325
|
+
* action=X") if you want them in your own metrics pipeline.
|
|
326
|
+
*/
|
|
327
|
+
export declare function createPiiGuardrailSink(inner: AuditSink, opts?: PiiGuardrailOpts): AuditSink;
|
|
328
|
+
/**
|
|
329
|
+
* Generate a UUID-shaped event ID. Uses Web Crypto when available
|
|
330
|
+
* (Workers, modern Node); falls back to a deterministic ordering for
|
|
331
|
+
* legacy Node (warning: NOT cryptographically random — tests + dev only).
|
|
332
|
+
*/
|
|
333
|
+
export declare function generateEventId(): string;
|
|
334
|
+
/** Convenience: produce an ISO-8601 UTC timestamp. */
|
|
335
|
+
export declare function nowIso(): string;
|
|
336
|
+
/**
|
|
337
|
+
* Build an `AuditEvent` with sane defaults. Saves callers from
|
|
338
|
+
* repeatedly writing `id: generateEventId(), timestamp: nowIso()`.
|
|
339
|
+
*
|
|
340
|
+
* Required: `action`, `outcome`. Everything else optional + spreads
|
|
341
|
+
* through.
|
|
342
|
+
*/
|
|
343
|
+
export declare function buildEvent(partial: Omit<AuditEvent, 'id' | 'timestamp'> & {
|
|
344
|
+
id?: string;
|
|
345
|
+
timestamp?: string;
|
|
346
|
+
}): AuditEvent;
|
|
347
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @agenticprimitives/audit — append-only audit/forensics primitives.
|
|
3
|
+
*
|
|
4
|
+
* Owns:
|
|
5
|
+
* - The canonical `AuditEvent` schema every other package emits.
|
|
6
|
+
* - The `AuditSink` interface a consumer implements (or composes from
|
|
7
|
+
* one of the provided sinks).
|
|
8
|
+
* - In-band sinks: `createConsoleAuditSink`, `createMemoryAuditSink`.
|
|
9
|
+
*
|
|
10
|
+
* Does NOT own:
|
|
11
|
+
* - Concrete persistence backends (D1 / Cloud Logging / Splunk / etc.).
|
|
12
|
+
* Those live with the consumer that needs them.
|
|
13
|
+
* - Domain semantics — `action` is a free-string the emitter chooses
|
|
14
|
+
* (e.g. `<package>.<surface>.<outcome>`).
|
|
15
|
+
*
|
|
16
|
+
* Doctrine (per CLAUDE.md): zero domain knowledge. The schema is
|
|
17
|
+
* deliberately structural — every package's emitter knows what to put
|
|
18
|
+
* in `subject` / `context`; this package only stamps the shape.
|
|
19
|
+
*
|
|
20
|
+
* Closes system audit finding C3.
|
|
21
|
+
*/
|
|
22
|
+
// ─── Event schema ─────────────────────────────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* One audit event. Designed to be JSON-serialisable for sinks that
|
|
25
|
+
* persist or forward over HTTP. All fields are strings or numbers so
|
|
26
|
+
* the on-wire shape matches the in-memory shape with no codec needed.
|
|
27
|
+
*
|
|
28
|
+
* `id` is per-event (UUID-ish); `correlationId` ties multiple events
|
|
29
|
+
* to a single request / flow / session.
|
|
30
|
+
*/
|
|
31
|
+
/**
|
|
32
|
+
* H7-F.2 / PKG-audit-002 / EXT-037 closure — canonical action registry.
|
|
33
|
+
*
|
|
34
|
+
* Free-string `action` previously let each emitter pick its own name
|
|
35
|
+
* with no central catalog — drift hazard the moment more than one
|
|
36
|
+
* package emits the same conceptual event. The registry below is the
|
|
37
|
+
* canonical list. `action: AuditAction` keeps the string convention
|
|
38
|
+
* (still indexes cleanly in structured-log backends) but type-checks
|
|
39
|
+
* the caller against the registry.
|
|
40
|
+
*
|
|
41
|
+
* Convention: `<package>.<surface>.<outcome?>`.
|
|
42
|
+
*
|
|
43
|
+
* Each emitting package documents WHICH of these names IT emits in
|
|
44
|
+
* its `AUDIT.md` "Audit events emitted" section.
|
|
45
|
+
*
|
|
46
|
+
* To add a new action:
|
|
47
|
+
* 1. Add the string to `AUDIT_ACTION_REGISTRY` (below).
|
|
48
|
+
* 2. Add it to the owning package's `AUDIT.md` list.
|
|
49
|
+
* 3. The TypeScript union `AuditAction` is derived automatically.
|
|
50
|
+
*
|
|
51
|
+
* `action` STILL accepts a raw `string` (the legacy escape hatch) so a
|
|
52
|
+
* package can ship a one-off event without churn. Production paths
|
|
53
|
+
* should use the typed name + extend the registry.
|
|
54
|
+
*/
|
|
55
|
+
export const AUDIT_ACTION_REGISTRY = [
|
|
56
|
+
// ─── delegation (spec 202)
|
|
57
|
+
'delegation.mint',
|
|
58
|
+
'delegation.verify.accept',
|
|
59
|
+
'delegation.verify.reject',
|
|
60
|
+
'delegation.revoke',
|
|
61
|
+
// ─── key-custody (spec 203)
|
|
62
|
+
'key-custody.sign',
|
|
63
|
+
'key-custody.rotate',
|
|
64
|
+
'key-custody.envelope.encrypt',
|
|
65
|
+
'key-custody.envelope.decrypt',
|
|
66
|
+
'key-custody.session-data-key.generate',
|
|
67
|
+
// ─── account-custody (spec 213)
|
|
68
|
+
'account-custody.schedule',
|
|
69
|
+
'account-custody.apply',
|
|
70
|
+
'account-custody.cancel',
|
|
71
|
+
'account-custody.credential.add',
|
|
72
|
+
'account-custody.credential.remove',
|
|
73
|
+
'account-custody.credential.replace',
|
|
74
|
+
// ─── agent-account (spec 201)
|
|
75
|
+
'agent-account.admin.propose',
|
|
76
|
+
'agent-account.admin.execute',
|
|
77
|
+
'agent-account.admin.cancel',
|
|
78
|
+
// ─── connect-auth (spec 200)
|
|
79
|
+
'connect-auth.session.mint',
|
|
80
|
+
'connect-auth.session.verify.accept',
|
|
81
|
+
'connect-auth.session.verify.reject',
|
|
82
|
+
// ─── connect (spec 224)
|
|
83
|
+
'connect.issue.accept',
|
|
84
|
+
'connect.issue.reject',
|
|
85
|
+
// ─── mcp-runtime (spec 205)
|
|
86
|
+
'mcp-runtime.with-delegation.accept',
|
|
87
|
+
'mcp-runtime.with-delegation.reject',
|
|
88
|
+
'mcp-runtime.service-mac.issue',
|
|
89
|
+
'mcp-runtime.service-mac.accept',
|
|
90
|
+
'mcp-runtime.service-mac.reject',
|
|
91
|
+
// ─── agent-naming (spec 215)
|
|
92
|
+
'agent-naming.resolve',
|
|
93
|
+
'agent-naming.register',
|
|
94
|
+
'agent-naming.records.update',
|
|
95
|
+
'agent-naming.primary-name.update',
|
|
96
|
+
'agent-naming.subregistry.update',
|
|
97
|
+
];
|
|
98
|
+
/** Type guard for callers that need to validate an arbitrary string. */
|
|
99
|
+
export function isCanonicalAuditAction(s) {
|
|
100
|
+
return AUDIT_ACTION_REGISTRY.includes(s);
|
|
101
|
+
}
|
|
102
|
+
// ─── Provided sinks ───────────────────────────────────────────────────
|
|
103
|
+
/**
|
|
104
|
+
* Emit events as one-line JSON to `console.log`. Suitable for
|
|
105
|
+
* Cloudflare Workers (where `wrangler tail` surfaces console output)
|
|
106
|
+
* and for local dev. Production deploys typically PAIR this with a
|
|
107
|
+
* durable sink — see `composeSinks` below.
|
|
108
|
+
*/
|
|
109
|
+
export function createConsoleAuditSink(opts) {
|
|
110
|
+
const prefix = opts?.prefix ?? '[AUDIT]';
|
|
111
|
+
return {
|
|
112
|
+
async write(event) {
|
|
113
|
+
// One-line JSON; Cloudflare's log line limit is 1KB so we stay
|
|
114
|
+
// compact. If `context` overflows, the line gets clipped — that's
|
|
115
|
+
// the sink's responsibility to handle (e.g. by pre-truncating).
|
|
116
|
+
const line = `${prefix} ${JSON.stringify(event)}`;
|
|
117
|
+
// eslint-disable-next-line no-console
|
|
118
|
+
console.log(line);
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export function createMemoryAuditSink(opts) {
|
|
123
|
+
const capacity = opts?.capacity ?? 1024;
|
|
124
|
+
const buf = [];
|
|
125
|
+
return {
|
|
126
|
+
async write(event) {
|
|
127
|
+
buf.push(event);
|
|
128
|
+
if (buf.length > capacity)
|
|
129
|
+
buf.shift();
|
|
130
|
+
},
|
|
131
|
+
events() {
|
|
132
|
+
return buf.slice();
|
|
133
|
+
},
|
|
134
|
+
reset() {
|
|
135
|
+
buf.length = 0;
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Combine multiple sinks into one with **fail-soft** semantics — each
|
|
141
|
+
* emit fans out to all sinks sequentially; a failure in one sink does NOT
|
|
142
|
+
* short-circuit the others, AND does NOT propagate to the caller. The
|
|
143
|
+
* caller's `await write(event)` always resolves successfully, even if every
|
|
144
|
+
* sink failed. Best for telemetry / metrics-grade events where dropping
|
|
145
|
+
* one event is acceptable.
|
|
146
|
+
*
|
|
147
|
+
* **H7-B.7 / PKG-AUDIT-001 closure** — security-critical events (authority
|
|
148
|
+
* changes, custody operations, signing-side actions) need durable-before-
|
|
149
|
+
* commit semantics; use {@link composeFailHardSinks} for those. The
|
|
150
|
+
* audit package stays domain-agnostic — emitting packages document which
|
|
151
|
+
* of their action names belong to the fail-hard class in their own docs.
|
|
152
|
+
*
|
|
153
|
+
* Production wiring is typically `composeSinks(d1Sink, consoleSink)` for
|
|
154
|
+
* non-critical events, and `composeFailHardSinks(d1Sink)` for events that
|
|
155
|
+
* MUST be persisted before the action commits.
|
|
156
|
+
*/
|
|
157
|
+
export function composeSinks(...sinks) {
|
|
158
|
+
return composeFailSoftSinks(...sinks);
|
|
159
|
+
}
|
|
160
|
+
/** Explicit alias of {@link composeSinks} — fail-soft, no propagation. */
|
|
161
|
+
export function composeFailSoftSinks(...sinks) {
|
|
162
|
+
return {
|
|
163
|
+
async write(event) {
|
|
164
|
+
const errors = [];
|
|
165
|
+
for (const sink of sinks) {
|
|
166
|
+
try {
|
|
167
|
+
await sink.write(event);
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
errors.push(e);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (errors.length > 0) {
|
|
174
|
+
// Surface to console as a last resort — the audit event itself
|
|
175
|
+
// may have been written to some sinks but not others.
|
|
176
|
+
// eslint-disable-next-line no-console
|
|
177
|
+
console.error(`[audit] ${errors.length} sink(s) failed for event ${event.id}:`, errors);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* H7-B.7 — Combine multiple sinks with **fail-hard** semantics for
|
|
184
|
+
* security-critical events. The first sink failure THROWS the original
|
|
185
|
+
* error (after the remaining sinks have been attempted — every sink still
|
|
186
|
+
* gets a chance to record, but the caller's `await write(event)` rejects
|
|
187
|
+
* if any failed).
|
|
188
|
+
*
|
|
189
|
+
* Use for events whose absence from durable storage would be a security
|
|
190
|
+
* regression — the precise action vocabulary is each emitting package's
|
|
191
|
+
* responsibility to document. The caller's flow MUST treat a thrown audit
|
|
192
|
+
* as the action FAILING — do not commit the on-chain or off-chain effect
|
|
193
|
+
* if the audit didn't persist.
|
|
194
|
+
*
|
|
195
|
+
* Closure: PKG-AUDIT-001 / EXT-022 / CT-11.
|
|
196
|
+
*/
|
|
197
|
+
export function composeFailHardSinks(...sinks) {
|
|
198
|
+
return {
|
|
199
|
+
async write(event) {
|
|
200
|
+
const errors = [];
|
|
201
|
+
for (const sink of sinks) {
|
|
202
|
+
try {
|
|
203
|
+
await sink.write(event);
|
|
204
|
+
}
|
|
205
|
+
catch (e) {
|
|
206
|
+
errors.push(e);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (errors.length > 0) {
|
|
210
|
+
// eslint-disable-next-line no-console
|
|
211
|
+
console.error(`[audit:fail-hard] ${errors.length} sink(s) failed for event ${event.id}:`, errors);
|
|
212
|
+
const err = errors[0];
|
|
213
|
+
if (err instanceof Error)
|
|
214
|
+
throw err;
|
|
215
|
+
throw new Error(`[audit:fail-hard] sink failure for event ${event.id}: ${String(err)}`);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* No-op sink. The default when consumers haven't wired metrics yet —
|
|
222
|
+
* runtime always succeeds, never observably affects throughput.
|
|
223
|
+
*/
|
|
224
|
+
export const noopMetricsSink = {
|
|
225
|
+
increment: () => undefined,
|
|
226
|
+
observe: () => undefined,
|
|
227
|
+
gauge: () => undefined,
|
|
228
|
+
};
|
|
229
|
+
/**
|
|
230
|
+
* Console-out metrics sink for local dev. NOT a production sink — the
|
|
231
|
+
* volume in production overwhelms console output and provides no
|
|
232
|
+
* aggregation. Production wires `composeMetricsSinks(prometheusSink, …)`.
|
|
233
|
+
*/
|
|
234
|
+
export function createConsoleMetricsSink(opts) {
|
|
235
|
+
const prefix = opts?.prefix ?? '[METRIC]';
|
|
236
|
+
const fmt = (kind, name, value, tags) => `${prefix} ${kind} ${name}=${value}${tags ? ' ' + JSON.stringify(tags) : ''}`;
|
|
237
|
+
return {
|
|
238
|
+
increment: (name, value = 1, tags) => {
|
|
239
|
+
// eslint-disable-next-line no-console
|
|
240
|
+
console.log(fmt('count', name, value, tags));
|
|
241
|
+
},
|
|
242
|
+
observe: (name, value, tags) => {
|
|
243
|
+
// eslint-disable-next-line no-console
|
|
244
|
+
console.log(fmt('hist', name, value, tags));
|
|
245
|
+
},
|
|
246
|
+
gauge: (name, value, tags) => {
|
|
247
|
+
// eslint-disable-next-line no-console
|
|
248
|
+
console.log(fmt('gauge', name, value, tags));
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Fan-out to multiple metrics sinks. Same fail-soft semantics as
|
|
254
|
+
* `composeSinks` for audit: a failure in one sink doesn't short-circuit
|
|
255
|
+
* the others. Metrics emissions are synchronous + must never block
|
|
256
|
+
* the request path; sinks that need async work MUST queue internally.
|
|
257
|
+
*/
|
|
258
|
+
export function composeMetricsSinks(...sinks) {
|
|
259
|
+
const safely = (fn) => {
|
|
260
|
+
try {
|
|
261
|
+
fn();
|
|
262
|
+
}
|
|
263
|
+
catch { /* fail-soft */ }
|
|
264
|
+
};
|
|
265
|
+
return {
|
|
266
|
+
increment: (name, value, tags) => {
|
|
267
|
+
for (const s of sinks)
|
|
268
|
+
safely(() => s.increment(name, value, tags));
|
|
269
|
+
},
|
|
270
|
+
observe: (name, value, tags) => {
|
|
271
|
+
for (const s of sinks)
|
|
272
|
+
safely(() => s.observe(name, value, tags));
|
|
273
|
+
},
|
|
274
|
+
gauge: (name, value, tags) => {
|
|
275
|
+
for (const s of sinks)
|
|
276
|
+
safely(() => s.gauge(name, value, tags));
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
export function createMemoryMetricsSink() {
|
|
281
|
+
const counts = new Map();
|
|
282
|
+
const observations = new Map();
|
|
283
|
+
const gauges = new Map();
|
|
284
|
+
const key = (name, tags) => tags && Object.keys(tags).length > 0
|
|
285
|
+
? `${name}|${Object.keys(tags).sort().map((k) => `${k}=${tags[k]}`).join(',')}`
|
|
286
|
+
: name;
|
|
287
|
+
return {
|
|
288
|
+
increment(name, value = 1, tags) {
|
|
289
|
+
const k = key(name, tags);
|
|
290
|
+
counts.set(k, (counts.get(k) ?? 0) + value);
|
|
291
|
+
},
|
|
292
|
+
observe(name, value, tags) {
|
|
293
|
+
const k = key(name, tags);
|
|
294
|
+
const arr = observations.get(k) ?? [];
|
|
295
|
+
arr.push(value);
|
|
296
|
+
observations.set(k, arr);
|
|
297
|
+
},
|
|
298
|
+
gauge(name, value, tags) {
|
|
299
|
+
gauges.set(key(name, tags), value);
|
|
300
|
+
},
|
|
301
|
+
snapshot() {
|
|
302
|
+
return { counts: new Map(counts), observations: new Map(observations), gauges: new Map(gauges) };
|
|
303
|
+
},
|
|
304
|
+
reset() {
|
|
305
|
+
counts.clear();
|
|
306
|
+
observations.clear();
|
|
307
|
+
gauges.clear();
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const DEFAULT_ALLOW_KEYS = new Set([
|
|
312
|
+
'signerAddress',
|
|
313
|
+
'address',
|
|
314
|
+
'paymaster',
|
|
315
|
+
'entryPoint',
|
|
316
|
+
'keyId',
|
|
317
|
+
'nonceHash',
|
|
318
|
+
'sessionHash',
|
|
319
|
+
'digest',
|
|
320
|
+
'txHash',
|
|
321
|
+
'blockHash',
|
|
322
|
+
'jti',
|
|
323
|
+
'eventId',
|
|
324
|
+
]);
|
|
325
|
+
const DEFAULT_ALLOW_SUBJECT_TYPES = new Set([
|
|
326
|
+
'jti',
|
|
327
|
+
'sign-digest',
|
|
328
|
+
'tx-hash',
|
|
329
|
+
'address',
|
|
330
|
+
'event-id',
|
|
331
|
+
]);
|
|
332
|
+
const JWT_SHAPE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
333
|
+
const PEM_BLOCK = /-----BEGIN /;
|
|
334
|
+
const SECRET_SUBSTRING = /(private[_-]?key|client[_-]?secret|api[_-]?key|access[_-]?token|refresh[_-]?token)/i;
|
|
335
|
+
/**
|
|
336
|
+
* Wrapper sink that scans event payloads for likely-secret material and
|
|
337
|
+
* either redacts / drops / warns before forwarding to `inner`. **This is
|
|
338
|
+
* defense-in-depth**, not a substitute for the emitter discipline rule
|
|
339
|
+
* "MUST hash or omit raw secrets" (per the audit package CLAUDE.md
|
|
340
|
+
* invariant). It catches accidental leaks at the sink boundary.
|
|
341
|
+
*
|
|
342
|
+
* Production wiring example:
|
|
343
|
+
* ```ts
|
|
344
|
+
* const sink = composeSinks(
|
|
345
|
+
* createConsoleAuditSink({ prefix: '[AUDIT]' }),
|
|
346
|
+
* createPiiGuardrailSink(createD1AuditSink(env.DB), { mode: 'redact' }),
|
|
347
|
+
* );
|
|
348
|
+
* ```
|
|
349
|
+
*
|
|
350
|
+
* The guardrail's own decisions are not themselves audit events — they
|
|
351
|
+
* surface through `onDetect`. Use that to wire alerts ("found a leak in
|
|
352
|
+
* action=X") if you want them in your own metrics pipeline.
|
|
353
|
+
*/
|
|
354
|
+
export function createPiiGuardrailSink(inner, opts) {
|
|
355
|
+
const mode = opts?.mode ?? 'redact';
|
|
356
|
+
const maxHexLength = opts?.maxHexLength ?? 80;
|
|
357
|
+
const allowKeys = new Set([...DEFAULT_ALLOW_KEYS, ...(opts?.allowKeys ?? [])]);
|
|
358
|
+
const allowSubjectTypes = new Set([
|
|
359
|
+
...DEFAULT_ALLOW_SUBJECT_TYPES,
|
|
360
|
+
...(opts?.allowSubjectTypes ?? []),
|
|
361
|
+
]);
|
|
362
|
+
const longHex = new RegExp(`^0x[0-9a-fA-F]{${maxHexLength - 2},}$`);
|
|
363
|
+
function classify(value) {
|
|
364
|
+
if (longHex.test(value)) {
|
|
365
|
+
return { reason: 'long-hex', preview: `hex-${value.length}` };
|
|
366
|
+
}
|
|
367
|
+
if (value.length > 100 && JWT_SHAPE.test(value)) {
|
|
368
|
+
const segs = value.split('.').length;
|
|
369
|
+
return { reason: 'jwt-shape', preview: `jwt-${segs}-seg-len-${value.length}` };
|
|
370
|
+
}
|
|
371
|
+
if (PEM_BLOCK.test(value)) {
|
|
372
|
+
return { reason: 'pem-block', preview: 'pem' };
|
|
373
|
+
}
|
|
374
|
+
if (SECRET_SUBSTRING.test(value)) {
|
|
375
|
+
return { reason: 'secret-substring', preview: `match-len-${value.length}` };
|
|
376
|
+
}
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
function redactToken(reason, preview) {
|
|
380
|
+
return `<redacted:${reason}:${preview}>`;
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
async write(event) {
|
|
384
|
+
const findings = [];
|
|
385
|
+
// Deep-ish copy of the event so we can mutate fields without
|
|
386
|
+
// affecting the caller's reference.
|
|
387
|
+
const sanitized = {
|
|
388
|
+
...event,
|
|
389
|
+
context: event.context ? { ...event.context } : undefined,
|
|
390
|
+
subject: event.subject ? { ...event.subject } : undefined,
|
|
391
|
+
actor: event.actor ? { ...event.actor } : undefined,
|
|
392
|
+
};
|
|
393
|
+
const scan = (path, value, redactInPlace) => {
|
|
394
|
+
if (typeof value !== 'string')
|
|
395
|
+
return;
|
|
396
|
+
const hit = classify(value);
|
|
397
|
+
if (!hit)
|
|
398
|
+
return;
|
|
399
|
+
findings.push({ path, reason: hit.reason, preview: hit.preview });
|
|
400
|
+
if (mode === 'redact')
|
|
401
|
+
redactInPlace(redactToken(hit.reason, hit.preview));
|
|
402
|
+
};
|
|
403
|
+
// context.* — skip allowlisted keys (legitimate hex carriers).
|
|
404
|
+
if (sanitized.context) {
|
|
405
|
+
for (const [k, v] of Object.entries(sanitized.context)) {
|
|
406
|
+
if (allowKeys.has(k))
|
|
407
|
+
continue;
|
|
408
|
+
scan(`context.${k}`, v, (token) => {
|
|
409
|
+
sanitized.context[k] = token;
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// subject.id — skip when subject.type is a known hex-carrier.
|
|
414
|
+
if (sanitized.subject && !allowSubjectTypes.has(sanitized.subject.type)) {
|
|
415
|
+
scan('subject.id', sanitized.subject.id, (token) => {
|
|
416
|
+
sanitized.subject.id = token;
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
// actor.id — same rules; addresses + service names are typically short.
|
|
420
|
+
if (sanitized.actor?.id) {
|
|
421
|
+
scan('actor.id', sanitized.actor.id, (token) => {
|
|
422
|
+
sanitized.actor.id = token;
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
// reason / audience — free-string fields that could carry stack traces.
|
|
426
|
+
if (sanitized.reason) {
|
|
427
|
+
scan('reason', sanitized.reason, (token) => {
|
|
428
|
+
sanitized.reason = token;
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
if (sanitized.audience) {
|
|
432
|
+
scan('audience', sanitized.audience, (token) => {
|
|
433
|
+
sanitized.audience = token;
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
if (findings.length > 0) {
|
|
437
|
+
opts?.onDetect?.({ event: sanitized, findings });
|
|
438
|
+
}
|
|
439
|
+
if (findings.length > 0 && mode === 'drop') {
|
|
440
|
+
// Don't forward at all. The onDetect callback above is the
|
|
441
|
+
// only signal that a row was dropped.
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
if (findings.length > 0 && mode === 'warn') {
|
|
445
|
+
// Forward unchanged.
|
|
446
|
+
await inner.write(event);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
await inner.write(sanitized);
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
// ─── Helpers ──────────────────────────────────────────────────────────
|
|
454
|
+
/**
|
|
455
|
+
* Generate a UUID-shaped event ID. Uses Web Crypto when available
|
|
456
|
+
* (Workers, modern Node); falls back to a deterministic ordering for
|
|
457
|
+
* legacy Node (warning: NOT cryptographically random — tests + dev only).
|
|
458
|
+
*/
|
|
459
|
+
export function generateEventId() {
|
|
460
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
461
|
+
const g = globalThis;
|
|
462
|
+
if (g.crypto?.randomUUID)
|
|
463
|
+
return g.crypto.randomUUID();
|
|
464
|
+
// Fallback: 16 random-ish bytes formatted as a UUID v4.
|
|
465
|
+
const bytes = new Uint8Array(16);
|
|
466
|
+
if (g.crypto?.getRandomValues) {
|
|
467
|
+
g.crypto.getRandomValues(bytes);
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
for (let i = 0; i < 16; i++)
|
|
471
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
472
|
+
}
|
|
473
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
474
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
475
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
476
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
477
|
+
}
|
|
478
|
+
/** Convenience: produce an ISO-8601 UTC timestamp. */
|
|
479
|
+
export function nowIso() {
|
|
480
|
+
return new Date().toISOString();
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Build an `AuditEvent` with sane defaults. Saves callers from
|
|
484
|
+
* repeatedly writing `id: generateEventId(), timestamp: nowIso()`.
|
|
485
|
+
*
|
|
486
|
+
* Required: `action`, `outcome`. Everything else optional + spreads
|
|
487
|
+
* through.
|
|
488
|
+
*/
|
|
489
|
+
export function buildEvent(partial) {
|
|
490
|
+
return {
|
|
491
|
+
id: partial.id ?? generateEventId(),
|
|
492
|
+
timestamp: partial.timestamp ?? nowIso(),
|
|
493
|
+
...partial,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agenticprimitives/audit",
|
|
3
|
+
"version": "0.1.0-alpha.2",
|
|
4
|
+
"description": "Append-only audit event schema + sink interface. Transport-agnostic; consumers wire concrete sinks (console / D1 / Cloud Logging / etc.).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/agentictrustlabs/agenticprimitives.git",
|
|
9
|
+
"directory": "packages/audit"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/agentictrustlabs/agenticprimitives/tree/master/packages/audit",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/agentictrustlabs/agenticprimitives/issues"
|
|
14
|
+
},
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"import": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"files": [
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"dist",
|
|
27
|
+
"spec.md",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -p tsconfig.build.json",
|
|
32
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:unit": "vitest run test/unit",
|
|
35
|
+
"test:integration": "vitest run test/integration --passWithNoTests",
|
|
36
|
+
"test:watch": "vitest",
|
|
37
|
+
"clean": "rm -rf dist"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@agenticprimitives/types": "workspace:*"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"vitest": "^2.1.0"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"audit",
|
|
50
|
+
"forensics",
|
|
51
|
+
"observability",
|
|
52
|
+
"agentic"
|
|
53
|
+
]
|
|
54
|
+
}
|