@volidator/node 1.0.1 → 1.0.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 +72 -2
- package/dist/{chunk-UUUKNFQ6.js → chunk-AJL3OYF2.js} +7 -3
- package/dist/chunk-AJL3OYF2.js.map +1 -0
- package/dist/{chunk-YI4DCCHT.js → chunk-UDBMJC25.js} +207 -26
- package/dist/chunk-UDBMJC25.js.map +1 -0
- package/dist/index.cjs +206 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -3
- package/dist/index.d.ts +91 -3
- package/dist/index.js +1 -1
- package/dist/middleware/next.cjs +211 -26
- package/dist/middleware/next.cjs.map +1 -1
- package/dist/middleware/next.d.cts +2 -0
- package/dist/middleware/next.d.ts +2 -0
- package/dist/middleware/next.js +2 -2
- package/dist/plugins/agent-langchain.d.cts +2 -0
- package/dist/plugins/agent-langchain.d.ts +2 -0
- package/dist/plugins/agent-vercel.d.cts +2 -0
- package/dist/plugins/agent-vercel.d.ts +2 -0
- package/dist/plugins/clerk.cjs +211 -26
- package/dist/plugins/clerk.cjs.map +1 -1
- package/dist/plugins/clerk.d.cts +2 -0
- package/dist/plugins/clerk.d.ts +2 -0
- package/dist/plugins/clerk.js +2 -2
- package/dist/plugins/universal.cjs +211 -26
- package/dist/plugins/universal.cjs.map +1 -1
- package/dist/plugins/universal.d.cts +2 -0
- package/dist/plugins/universal.d.ts +2 -0
- package/dist/plugins/universal.js +2 -2
- package/package.json +3 -3
- package/dist/chunk-UUUKNFQ6.js.map +0 -1
- package/dist/chunk-YI4DCCHT.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Volidator Authors
|
|
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
CHANGED
|
@@ -7,6 +7,8 @@ Volidator encrypts every log entry locally — on your server — before sending
|
|
|
7
7
|
[](https://www.npmjs.com/package/@volidator/node)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
|
|
10
|
+
**[Website & Sign Up](https://volidator.com) | [Developer Documentation](https://docs.volidator.com)**
|
|
11
|
+
|
|
10
12
|
---
|
|
11
13
|
|
|
12
14
|
## What is Volidator?
|
|
@@ -52,6 +54,7 @@ Volidator solves this with **Zero-Knowledge Audit Trails**:
|
|
|
52
54
|
14. [`@volidator/react`](#14-volidatorreact)
|
|
53
55
|
15. [AI Agent Auditing (VolidatorAgent)](#15-ai-agent-auditing-volidatoragent)
|
|
54
56
|
16. [Batch Ingestion (logBatch)](#16-batch-ingestion-logbatch)
|
|
57
|
+
17. [Large Payloads (Claim Check Pattern)](#17-large-payloads-claim-check-pattern)
|
|
55
58
|
|
|
56
59
|
---
|
|
57
60
|
|
|
@@ -65,8 +68,12 @@ npm install @volidator/node
|
|
|
65
68
|
|
|
66
69
|
## 2. Environment Setup
|
|
67
70
|
|
|
68
|
-
|
|
71
|
+
Generate a secure 32-byte encryption key (64 hex characters) with the `vol-dek-` prefix:
|
|
72
|
+
```bash
|
|
73
|
+
node -e "const b=require('crypto').randomBytes(32);console.log('vol-dek-'+b.toString('hex'))"
|
|
74
|
+
```
|
|
69
75
|
|
|
76
|
+
Then add it as an environment secret:
|
|
70
77
|
```bash
|
|
71
78
|
# Authenticates your server with the Volidator ingestion endpoint
|
|
72
79
|
VOLIDATOR_API_KEY="val_live_xxxxxxxx..."
|
|
@@ -87,9 +94,29 @@ import { VolidatorClient } from "@volidator/node";
|
|
|
87
94
|
export const volidator = new VolidatorClient({
|
|
88
95
|
apiKey: process.env.VOLIDATOR_API_KEY!,
|
|
89
96
|
encryptionKey: process.env.VOLIDATOR_ENCRYPTION_KEY!,
|
|
97
|
+
|
|
98
|
+
// Optional configuration for delivery retries and sizes:
|
|
99
|
+
maxRetries: 3, // Retry up to 3 times on transient errors (default: 3)
|
|
100
|
+
maxMetadataSize: 10240, // Cap serialized metadata size at 10KB (default: 10KB)
|
|
101
|
+
|
|
102
|
+
onDeliveryFailure: (payload, error) => {
|
|
103
|
+
// Callback invoked when a log permanently fails to deliver after all retries
|
|
104
|
+
console.error(`Log delivery failed permanently for action "${payload.action}": ${error.message}`);
|
|
105
|
+
}
|
|
90
106
|
});
|
|
91
107
|
```
|
|
92
108
|
|
|
109
|
+
### Transient Errors & Retry Strategy
|
|
110
|
+
By default, the SDK automatically retries log delivery on network errors or 5xx server responses using an exponential backoff strategy (delays: ~500ms → ~1500ms → ~4500ms). Client errors (4xx) are never retried.
|
|
111
|
+
|
|
112
|
+
> **⚠️ Serverless / Edge Function execution time caveat:**
|
|
113
|
+
> Worst-case retry attempts take up to ~6.5 seconds. If you are running inside a serverless or Edge environment (e.g. Vercel, Next.js Edge, Cloudflare Workers) with strict duration limits:
|
|
114
|
+
> - Wrap log calls in `ctx.waitUntil(volidator.log(...))` so the worker doesn't block response delivery.
|
|
115
|
+
> - Or reduce `maxRetries` to `1` (or `0` to disable retries) to avoid hitting runtime execution limits.
|
|
116
|
+
|
|
117
|
+
### Metadata Limits & Truncation
|
|
118
|
+
Log metadata is subject to a hard depth limit of **5 levels** and string value length limit of **1000 characters**. In non-production environments (`NODE_ENV !== 'production'`), the SDK will emit warnings in the console if any metadata is truncated.
|
|
119
|
+
|
|
93
120
|
---
|
|
94
121
|
|
|
95
122
|
## 4. Log an Event
|
|
@@ -476,7 +503,7 @@ await volidator.agent.handoff({
|
|
|
476
503
|
});
|
|
477
504
|
```
|
|
478
505
|
|
|
479
|
-
### Trace Correlation (
|
|
506
|
+
### Trace Correlation & Logical Clocks (Lamport Timestamps)
|
|
480
507
|
|
|
481
508
|
Pass standard trace metadata (`traceId`, `spanId`, `parentSpanId`) to map out parent-child relationships and causality graphs between agent steps.
|
|
482
509
|
|
|
@@ -490,6 +517,11 @@ await volidator.agent.toolCall({
|
|
|
490
517
|
});
|
|
491
518
|
```
|
|
492
519
|
|
|
520
|
+
#### Deterministic Edge Trace Ordering
|
|
521
|
+
To resolve NTP (Network Time Protocol) clock drift anomalies between distributed edge servers or serverless invocations, the SDK automatically maintains and propagates a **Lamport Logical Clock** value inside request headers (`x-volidator-clock`). Clocks are synchronized across boundary calls using:
|
|
522
|
+
$$\text{localClock} = \max(\text{localClock}, \text{incomingClock}) + 1$$
|
|
523
|
+
This guarantees that visualizer graphs and dashboard causality lines render completely deterministically in chronological order.
|
|
524
|
+
|
|
493
525
|
---
|
|
494
526
|
|
|
495
527
|
## 16. Batch Ingestion (`logBatch`)
|
|
@@ -513,6 +545,44 @@ console.log(`Successfully ingested ${accepted} logs, failed to prepare ${rejecte
|
|
|
513
545
|
|
|
514
546
|
---
|
|
515
547
|
|
|
548
|
+
## 17. Large Payloads (Claim Check Pattern)
|
|
549
|
+
|
|
550
|
+
When logging autonomous agent thinking processes, prompt contexts, or tool data, payloads can easily grow quite large. If your encrypted log payload exceeds **30KB**, the SDK automatically and transparently switches to the **Claim Check Pattern**:
|
|
551
|
+
* The SDK uploads the encrypted ciphertext chunk to Cloudflare R2 object storage securely before database ingestion.
|
|
552
|
+
* It writes a content-addressed SHA-256 hash pointer to the database log record instead of the full payload, setting `isClaimCheck` to true.
|
|
553
|
+
* The Volidator dashboard and embed widgets detect this flag and automatically retrieve the encrypted chunk from the storage proxy to decrypt it locally in the browser.
|
|
554
|
+
|
|
555
|
+
This maintains absolute Zero-Knowledge privacy guarantees for large payloads without bloat.
|
|
556
|
+
|
|
557
|
+
---
|
|
558
|
+
|
|
559
|
+
## 18. Fluent Batcher (`batcher`)
|
|
560
|
+
|
|
561
|
+
Instead of managing arrays and calling `logBatch` manually, you can use the fluent `batcher()` client helper. This is highly recommended for loop-heavy agent runs or script execution paths.
|
|
562
|
+
|
|
563
|
+
```typescript
|
|
564
|
+
const batcher = volidator.batcher({
|
|
565
|
+
autoFlushCount: 50, // Automatically flush and send when buffer hits 50 logs
|
|
566
|
+
autoFlushInterval: 5000, // Or automatically flush every 5 seconds (Node-only, see warning)
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// Inside your loop or agent reasoning cycle:
|
|
570
|
+
batcher.push({
|
|
571
|
+
actor: "agent-1",
|
|
572
|
+
action: "thought",
|
|
573
|
+
metadata: { step: 1, text: "Thinking..." }
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
// Always call flush manually at the end of the script or request path to send any leftovers:
|
|
577
|
+
await batcher.flush();
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
> **⚠️ Serverless / Edge Warning:**
|
|
581
|
+
> `autoFlushInterval` uses `setInterval` internally. In serverless and Edge environments (like Cloudflare Workers, Vercel Edge, Next.js Edge), the V8 isolate is **frozen or destroyed** as soon as the response is returned to the user. Background timers will silently fail to execute, resulting in dropped logs.
|
|
582
|
+
> **Only use `autoFlushInterval` in long-lived Node.js applications** (Express, Fastify, CLI tools). For serverless/edge functions, **always manually call `await batcher.flush()`** or pass the promise to `ctx.waitUntil()`.
|
|
583
|
+
|
|
584
|
+
---
|
|
585
|
+
|
|
516
586
|
## License
|
|
517
587
|
|
|
518
588
|
MIT © Volidator Contributors
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VolidatorClient
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-UDBMJC25.js";
|
|
4
4
|
|
|
5
5
|
// src/middleware/next.ts
|
|
6
6
|
function withVolidator(client, handler) {
|
|
7
7
|
return async (req, ctx = {}, ...args) => {
|
|
8
8
|
const extractedContext = VolidatorClient.extractContext(req);
|
|
9
|
+
const extractedTrace = VolidatorClient.extractTraceContext(req);
|
|
10
|
+
const incomingClock = extractedTrace.logicalClock || 0;
|
|
9
11
|
const scopedLog = async (payload) => {
|
|
10
12
|
return client.log({
|
|
11
13
|
...payload,
|
|
@@ -46,11 +48,13 @@ function withVolidator(client, handler) {
|
|
|
46
48
|
compliance: scopedCompliance
|
|
47
49
|
}
|
|
48
50
|
};
|
|
49
|
-
return
|
|
51
|
+
return VolidatorClient.logicalClockStore.run({ clock: incomingClock }, () => {
|
|
52
|
+
return handler(req, newCtx, ...args);
|
|
53
|
+
});
|
|
50
54
|
};
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
export {
|
|
54
58
|
withVolidator
|
|
55
59
|
};
|
|
56
|
-
//# sourceMappingURL=chunk-
|
|
60
|
+
//# sourceMappingURL=chunk-AJL3OYF2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/middleware/next.ts"],"sourcesContent":["import { VolidatorClient, LogPayload } from \"../index\";\n\n/**\n * Next.js App Router Middleware for Volidator.\n * Automatically extracts telemetry context (IP, User Agent, Location)\n * from the Next.js standard Request object and injects it into every\n * `volidator.log()` and `volidator.compliance.*()` call made inside\n * the wrapped handler.\n */\nexport function withVolidator<T extends Function>(\n client: VolidatorClient,\n handler: T\n): (req: Request, ctx?: any, ...args: any[]) => Promise<any> {\n return async (req: Request, ctx: any = {}, ...args: any[]) => {\n const extractedContext = VolidatorClient.extractContext(req);\n const extractedTrace = VolidatorClient.extractTraceContext(req);\n const incomingClock = extractedTrace.logicalClock || 0;\n\n // ---------------------------------------------------------------------------\n // Scoped log — merges request-level IP/UA context into every log call.\n // ---------------------------------------------------------------------------\n const scopedLog = async (payload: LogPayload) => {\n return client.log({\n ...payload,\n context: {\n ...extractedContext,\n ...payload.context,\n location: {\n ...extractedContext.location,\n ...(payload.context?.location || {}),\n },\n device: {\n ...extractedContext.device,\n ...(payload.context?.device || {}),\n },\n },\n });\n };\n\n // ---------------------------------------------------------------------------\n // Scoped compliance — mirrors VolidatorCompliance but routes through\n // scopedLog so that IP/UA telemetry is preserved in compliance events.\n // ---------------------------------------------------------------------------\n const withControl = (\n action: string,\n soc2Control: string,\n isoControl: string,\n payload: Omit<LogPayload, \"action\">\n ) =>\n scopedLog({\n ...payload,\n action,\n metadata: {\n ...payload.metadata,\n soc2_control: soc2Control,\n iso27001: isoControl,\n },\n });\n\n const scopedCompliance = {\n accessRevoked: (p: Omit<LogPayload, \"action\">) =>\n withControl(\"access.revoked\", \"CC6.1\", \"A.9.2.6\", p),\n accessGranted: (p: Omit<LogPayload, \"action\">) =>\n withControl(\"access.granted\", \"CC6.1\", \"A.9.2.1\", p),\n dataExported: (p: Omit<LogPayload, \"action\">) =>\n withControl(\"data.exported\", \"CC6.6\", \"A.12.4.1\", p),\n systemConfigChanged: (p: Omit<LogPayload, \"action\">) =>\n withControl(\"system.config_changed\", \"CC6.2\", \"A.12.1.2\", p),\n mfaEnabled: (p: Omit<LogPayload, \"action\">) =>\n withControl(\"mfa.enabled\", \"CC6.3\", \"A.9.4.2\", p),\n };\n\n const newCtx = {\n ...ctx,\n volidator: {\n log: scopedLog,\n compliance: scopedCompliance,\n },\n };\n\n return VolidatorClient.logicalClockStore.run({ clock: incomingClock }, () => {\n return handler(req, newCtx, ...args);\n });\n };\n}\n"],"mappings":";;;;;AASO,SAAS,cACd,QACA,SAC2D;AAC3D,SAAO,OAAO,KAAc,MAAW,CAAC,MAAM,SAAgB;AAC5D,UAAM,mBAAmB,gBAAgB,eAAe,GAAG;AAC3D,UAAM,iBAAiB,gBAAgB,oBAAoB,GAAG;AAC9D,UAAM,gBAAgB,eAAe,gBAAgB;AAKrD,UAAM,YAAY,OAAO,YAAwB;AAC/C,aAAO,OAAO,IAAI;AAAA,QAChB,GAAG;AAAA,QACH,SAAS;AAAA,UACP,GAAG;AAAA,UACH,GAAG,QAAQ;AAAA,UACX,UAAU;AAAA,YACR,GAAG,iBAAiB;AAAA,YACpB,GAAI,QAAQ,SAAS,YAAY,CAAC;AAAA,UACpC;AAAA,UACA,QAAQ;AAAA,YACN,GAAG,iBAAiB;AAAA,YACpB,GAAI,QAAQ,SAAS,UAAU,CAAC;AAAA,UAClC;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAMA,UAAM,cAAc,CAClB,QACA,aACA,YACA,YAEA,UAAU;AAAA,MACR,GAAG;AAAA,MACH;AAAA,MACA,UAAU;AAAA,QACR,GAAG,QAAQ;AAAA,QACX,cAAc;AAAA,QACd,UAAU;AAAA,MACZ;AAAA,IACF,CAAC;AAEH,UAAM,mBAAmB;AAAA,MACvB,eAAe,CAAC,MACd,YAAY,kBAAkB,SAAS,WAAW,CAAC;AAAA,MACrD,eAAe,CAAC,MACd,YAAY,kBAAkB,SAAS,WAAW,CAAC;AAAA,MACrD,cAAc,CAAC,MACb,YAAY,iBAAiB,SAAS,YAAY,CAAC;AAAA,MACrD,qBAAqB,CAAC,MACpB,YAAY,yBAAyB,SAAS,YAAY,CAAC;AAAA,MAC7D,YAAY,CAAC,MACX,YAAY,eAAe,SAAS,WAAW,CAAC;AAAA,IACpD;AAEA,UAAM,SAAS;AAAA,MACb,GAAG;AAAA,MACH,WAAW;AAAA,QACT,KAAK;AAAA,QACL,YAAY;AAAA,MACd;AAAA,IACF;AAEA,WAAO,gBAAgB,kBAAkB,IAAI,EAAE,OAAO,cAAc,GAAG,MAAM;AAC3E,aAAO,QAAQ,KAAK,QAAQ,GAAG,IAAI;AAAA,IACrC,CAAC;AAAA,EACH;AACF;","names":[]}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
|
|
2
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
3
|
+
var _VolidatorClient = class _VolidatorClient {
|
|
3
4
|
constructor(config) {
|
|
5
|
+
this.fallbackLogicalClock = 0;
|
|
4
6
|
this.apiKey = config.apiKey;
|
|
5
7
|
this.endpoint = config.endpoint || "https://ingestion.volidator.com";
|
|
6
8
|
this.projectId = config.projectId;
|
|
@@ -8,6 +10,8 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
8
10
|
this.redactKeys = config.redactKeys || [];
|
|
9
11
|
this.referenceKeys = config.referenceKeys || [];
|
|
10
12
|
this.maxMetadataSize = config.maxMetadataSize || 10240;
|
|
13
|
+
this.maxRetries = config.maxRetries !== void 0 ? config.maxRetries : 3;
|
|
14
|
+
this.onDeliveryFailure = config.onDeliveryFailure;
|
|
11
15
|
if (config.keyring && config.activeEncryptionKeyId) {
|
|
12
16
|
this.keyring = config.keyring;
|
|
13
17
|
this.activeKeyId = config.activeEncryptionKeyId;
|
|
@@ -28,6 +32,22 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
28
32
|
this.compliance = new VolidatorCompliance(this);
|
|
29
33
|
this.agent = new VolidatorAgent(this);
|
|
30
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Increments and returns the current Lamport Logical Clock counter.
|
|
37
|
+
* If a logicalClockStore context is active, it reads, syncs, and updates
|
|
38
|
+
* the thread-scoped clock; otherwise, it falls back to a global instance-scoped counter.
|
|
39
|
+
*
|
|
40
|
+
* @param incomingClock Optional logical clock sequence from incoming trace parent header
|
|
41
|
+
*/
|
|
42
|
+
getAndIncrementClock(incomingClock) {
|
|
43
|
+
const store = _VolidatorClient.logicalClockStore.getStore();
|
|
44
|
+
if (store) {
|
|
45
|
+
store.clock = Math.max(store.clock, incomingClock || 0) + 1;
|
|
46
|
+
return store.clock;
|
|
47
|
+
}
|
|
48
|
+
this.fallbackLogicalClock = Math.max(this.fallbackLogicalClock, incomingClock || 0) + 1;
|
|
49
|
+
return this.fallbackLogicalClock;
|
|
50
|
+
}
|
|
31
51
|
// ---------------------------------------------------------------------------
|
|
32
52
|
// Telemetry Context Parser (Zero-latency header parsing)
|
|
33
53
|
// ---------------------------------------------------------------------------
|
|
@@ -69,11 +89,23 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
69
89
|
}
|
|
70
90
|
return "";
|
|
71
91
|
};
|
|
92
|
+
const result = {};
|
|
93
|
+
const clockVal = getHeader("x-volidator-clock");
|
|
94
|
+
if (clockVal) {
|
|
95
|
+
const parsed = parseInt(clockVal, 10);
|
|
96
|
+
if (!isNaN(parsed)) {
|
|
97
|
+
result.logicalClock = parsed;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
72
100
|
const traceparent = getHeader("traceparent");
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
101
|
+
if (traceparent) {
|
|
102
|
+
const parts = traceparent.split("-");
|
|
103
|
+
if (parts.length === 4) {
|
|
104
|
+
result.traceId = parts[1];
|
|
105
|
+
result.spanId = parts[2];
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
77
109
|
}
|
|
78
110
|
static resolveTelemetryConfig(config) {
|
|
79
111
|
const preset = config.preset || "standard";
|
|
@@ -196,6 +228,12 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
196
228
|
// prepareLogEntry() — Prepares, redacts, and encrypts a single log entry payload
|
|
197
229
|
// ---------------------------------------------------------------------------
|
|
198
230
|
async prepareLogEntry(payload, maxMetaOverride) {
|
|
231
|
+
const PAYLOAD_5MB_LIMIT = 5 * 1024 * 1024;
|
|
232
|
+
const metadata = payload.metadata || {};
|
|
233
|
+
const payloadSize = new TextEncoder().encode(JSON.stringify(metadata)).length;
|
|
234
|
+
if (payloadSize > PAYLOAD_5MB_LIMIT) {
|
|
235
|
+
throw new Error(`Volidator SDK Error: Audit log payload exceeds the 5MB hard limit (${payloadSize} bytes).`);
|
|
236
|
+
}
|
|
199
237
|
const actorRaw = payload.actor || payload.actorId || "unknown";
|
|
200
238
|
const targetRaw = payload.target || payload.targetId || "unknown";
|
|
201
239
|
const tenantRaw = payload.tenant || payload.tenantId || "";
|
|
@@ -277,13 +315,20 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
277
315
|
safeMetadata[k] = v;
|
|
278
316
|
}
|
|
279
317
|
}
|
|
318
|
+
let didTruncateDepth = false;
|
|
319
|
+
let didTruncateString = false;
|
|
280
320
|
const limitDepth = (obj, currentDepth = 1) => {
|
|
281
321
|
if (currentDepth > 5) {
|
|
322
|
+
didTruncateDepth = true;
|
|
282
323
|
return "[Truncated - Depth Exceeded]";
|
|
283
324
|
}
|
|
284
325
|
if (typeof obj !== "object" || obj === null) {
|
|
285
326
|
if (typeof obj === "string") {
|
|
286
|
-
|
|
327
|
+
const truncatedVal = truncate(obj, 1e3);
|
|
328
|
+
if (truncatedVal.length !== obj.length) {
|
|
329
|
+
didTruncateString = true;
|
|
330
|
+
}
|
|
331
|
+
return truncatedVal;
|
|
287
332
|
}
|
|
288
333
|
return obj;
|
|
289
334
|
}
|
|
@@ -297,6 +342,14 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
297
342
|
return result;
|
|
298
343
|
};
|
|
299
344
|
const processedMetadata = limitDepth(safeMetadata);
|
|
345
|
+
if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
|
|
346
|
+
if (didTruncateDepth) {
|
|
347
|
+
console.warn("[Volidator] Warning: Log metadata exceeded maximum depth limit (5) and was truncated.");
|
|
348
|
+
}
|
|
349
|
+
if (didTruncateString) {
|
|
350
|
+
console.warn("[Volidator] Warning: One or more log metadata string values exceeded the 1000-character limit and were truncated.");
|
|
351
|
+
}
|
|
352
|
+
}
|
|
300
353
|
const serializedMeta = JSON.stringify(processedMetadata);
|
|
301
354
|
const maxMetaLimit = maxMetaOverride || this.maxMetadataSize;
|
|
302
355
|
if (serializedMeta.length > maxMetaLimit) {
|
|
@@ -320,6 +373,7 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
320
373
|
let traceId = payload.traceId;
|
|
321
374
|
let spanId = payload.spanId;
|
|
322
375
|
let parentSpanId = payload.parentSpanId;
|
|
376
|
+
let logicalClock = payload.logicalClock;
|
|
323
377
|
if (payload.req) {
|
|
324
378
|
const extractedTrace = _VolidatorClient.extractTraceContext(payload.req);
|
|
325
379
|
if (!traceId && extractedTrace.traceId) {
|
|
@@ -328,7 +382,11 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
328
382
|
if (!spanId && extractedTrace.spanId) {
|
|
329
383
|
spanId = extractedTrace.spanId;
|
|
330
384
|
}
|
|
385
|
+
if (logicalClock === void 0 && extractedTrace.logicalClock !== void 0) {
|
|
386
|
+
logicalClock = extractedTrace.logicalClock;
|
|
387
|
+
}
|
|
331
388
|
}
|
|
389
|
+
const resolvedClock = this.getAndIncrementClock(logicalClock);
|
|
332
390
|
if (spanId) {
|
|
333
391
|
enrichedPayload.spanId = spanId;
|
|
334
392
|
}
|
|
@@ -341,14 +399,39 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
341
399
|
const targetBlindIndex = await this.generateBlindIndex(target, activeKeyBuffer);
|
|
342
400
|
const tenantBlindIndex = tenant ? await this.generateBlindIndex(tenant, activeKeyBuffer) : void 0;
|
|
343
401
|
const traceBlindIndex = traceId ? await this.generateBlindIndex(traceId, activeKeyBuffer) : void 0;
|
|
344
|
-
|
|
402
|
+
let encryptedPayload = await this.encryptPayload(enrichedPayload);
|
|
403
|
+
let isClaimCheck = false;
|
|
404
|
+
if (encryptedPayload.length > 30720) {
|
|
405
|
+
const binaryBuffer = new TextEncoder().encode(encryptedPayload);
|
|
406
|
+
const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", binaryBuffer);
|
|
407
|
+
const hashHex = Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
408
|
+
try {
|
|
409
|
+
const uploadRes = await fetch(`${this.endpoint}/v1/log/upload/${hashHex}`, {
|
|
410
|
+
method: "PUT",
|
|
411
|
+
headers: {
|
|
412
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
413
|
+
"Content-Type": "application/octet-stream"
|
|
414
|
+
},
|
|
415
|
+
body: encryptedPayload
|
|
416
|
+
});
|
|
417
|
+
if (!uploadRes.ok) {
|
|
418
|
+
throw new Error(`Upload failed with status: ${uploadRes.status}`);
|
|
419
|
+
}
|
|
420
|
+
encryptedPayload = hashHex;
|
|
421
|
+
isClaimCheck = true;
|
|
422
|
+
} catch (err) {
|
|
423
|
+
console.error(`[Volidator] Claim check upload failed: ${err.message}`);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
345
426
|
return {
|
|
346
427
|
actorBlindIndex,
|
|
347
428
|
actionBlindIndex,
|
|
348
429
|
targetBlindIndex,
|
|
349
430
|
tenantBlindIndex,
|
|
350
431
|
traceBlindIndex,
|
|
351
|
-
encryptedPayload
|
|
432
|
+
encryptedPayload,
|
|
433
|
+
logicalClock: resolvedClock,
|
|
434
|
+
isClaimCheck
|
|
352
435
|
};
|
|
353
436
|
}
|
|
354
437
|
// ---------------------------------------------------------------------------
|
|
@@ -356,20 +439,44 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
356
439
|
// ---------------------------------------------------------------------------
|
|
357
440
|
async log(payload, maxMetaOverride) {
|
|
358
441
|
const entry = await this.prepareLogEntry(payload, maxMetaOverride);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
442
|
+
let attempt = 0;
|
|
443
|
+
let delay = 500;
|
|
444
|
+
let lastError = new Error("Unknown error");
|
|
445
|
+
while (attempt <= this.maxRetries) {
|
|
446
|
+
try {
|
|
447
|
+
const res = await fetch(`${this.endpoint}/v1/log`, {
|
|
448
|
+
method: "POST",
|
|
449
|
+
headers: {
|
|
450
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
451
|
+
"Content-Type": "application/json"
|
|
452
|
+
},
|
|
453
|
+
body: JSON.stringify(entry)
|
|
454
|
+
});
|
|
455
|
+
if (res.ok) {
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
lastError = new Error(`Server returned status ${res.status}`);
|
|
459
|
+
if (res.status >= 400 && res.status < 500) {
|
|
460
|
+
break;
|
|
461
|
+
}
|
|
462
|
+
} catch (err) {
|
|
463
|
+
lastError = err instanceof Error ? err : new Error(String(err));
|
|
464
|
+
}
|
|
465
|
+
attempt++;
|
|
466
|
+
if (attempt <= this.maxRetries) {
|
|
467
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
468
|
+
delay *= 3;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
console.error(`[Volidator] Failed to send log after ${attempt} attempts: ${lastError.message}`);
|
|
472
|
+
if (this.onDeliveryFailure) {
|
|
473
|
+
try {
|
|
474
|
+
this.onDeliveryFailure(payload, lastError);
|
|
475
|
+
} catch (cbErr) {
|
|
476
|
+
console.error(`[Volidator] Error in onDeliveryFailure callback: ${cbErr.message}`);
|
|
477
|
+
}
|
|
372
478
|
}
|
|
479
|
+
return false;
|
|
373
480
|
}
|
|
374
481
|
// ---------------------------------------------------------------------------
|
|
375
482
|
// logBatch() — Bulk encrypt and ingest a batch of up to 100 audit events
|
|
@@ -416,12 +523,79 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
416
523
|
}
|
|
417
524
|
}
|
|
418
525
|
// ---------------------------------------------------------------------------
|
|
526
|
+
// batcher() — Convenience batcher for buffered log ingestion
|
|
527
|
+
// ---------------------------------------------------------------------------
|
|
528
|
+
/**
|
|
529
|
+
* Creates a convenience batcher instance for buffering and ingestion.
|
|
530
|
+
*
|
|
531
|
+
* ⚠️ SERVERLESS/EDGE CAVEAT:
|
|
532
|
+
* autoFlushInterval uses setInterval internally. In serverless/edge environments
|
|
533
|
+
* (e.g. Cloudflare Workers, Vercel Edge), the V8 isolate is frozen or destroyed
|
|
534
|
+
* once the response is sent. Background timers will silently fail to fire, leading
|
|
535
|
+
* to dropped logs. Only use autoFlushInterval in long-lived Node.js processes.
|
|
536
|
+
* In serverless/edge environments, always call await batcher.flush() explicitly
|
|
537
|
+
* before returning the response, or wrap it in ctx.waitUntil().
|
|
538
|
+
*/
|
|
539
|
+
batcher(options) {
|
|
540
|
+
const client = this;
|
|
541
|
+
let buffer = [];
|
|
542
|
+
let intervalId = null;
|
|
543
|
+
const flush = async () => {
|
|
544
|
+
if (intervalId) {
|
|
545
|
+
clearInterval(intervalId);
|
|
546
|
+
intervalId = null;
|
|
547
|
+
}
|
|
548
|
+
if (buffer.length === 0) {
|
|
549
|
+
if (options?.autoFlushInterval) {
|
|
550
|
+
startTimer();
|
|
551
|
+
}
|
|
552
|
+
return { accepted: 0, rejected: 0 };
|
|
553
|
+
}
|
|
554
|
+
const payloadsToFlush = [...buffer];
|
|
555
|
+
buffer = [];
|
|
556
|
+
if (options?.autoFlushInterval) {
|
|
557
|
+
startTimer();
|
|
558
|
+
}
|
|
559
|
+
return client.logBatch(payloadsToFlush);
|
|
560
|
+
};
|
|
561
|
+
const startTimer = () => {
|
|
562
|
+
if (options?.autoFlushInterval && !intervalId) {
|
|
563
|
+
intervalId = setInterval(() => {
|
|
564
|
+
flush().catch((err) => {
|
|
565
|
+
console.error(`[Volidator] Batcher auto-flush failed: ${err.message}`);
|
|
566
|
+
});
|
|
567
|
+
}, options.autoFlushInterval);
|
|
568
|
+
if (intervalId && typeof intervalId.unref === "function") {
|
|
569
|
+
intervalId.unref();
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
const push = (payload) => {
|
|
574
|
+
buffer.push(payload);
|
|
575
|
+
const maxCount = options?.autoFlushCount || 100;
|
|
576
|
+
if (buffer.length >= Math.min(maxCount, 100)) {
|
|
577
|
+
flush().catch((err) => {
|
|
578
|
+
console.error(`[Volidator] Batcher auto-flush on count failed: ${err.message}`);
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
const size = () => buffer.length;
|
|
583
|
+
startTimer();
|
|
584
|
+
return {
|
|
585
|
+
push,
|
|
586
|
+
flush,
|
|
587
|
+
size
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
// ---------------------------------------------------------------------------
|
|
419
591
|
// generateEmbedToken() — Sign a HS256 JWT for the embeddable dashboard widget
|
|
420
592
|
// ---------------------------------------------------------------------------
|
|
421
593
|
async generateEmbedToken(config) {
|
|
422
|
-
|
|
594
|
+
const projectId = config.projectId || this.projectId;
|
|
595
|
+
const clientSecret = config.clientSecret || this.clientSecret;
|
|
596
|
+
if (!projectId || !clientSecret) {
|
|
423
597
|
throw new Error(
|
|
424
|
-
"generateEmbedToken() requires projectId and clientSecret in the VolidatorClient constructor."
|
|
598
|
+
"generateEmbedToken() requires projectId and clientSecret to be provided in either the configuration or the VolidatorClient constructor."
|
|
425
599
|
);
|
|
426
600
|
}
|
|
427
601
|
const {
|
|
@@ -448,7 +622,7 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
448
622
|
const expiresInSeconds = Math.min(parsedExpiry, maxExpiry);
|
|
449
623
|
const now = Math.floor(Date.now() / 1e3);
|
|
450
624
|
const payload = {
|
|
451
|
-
pid:
|
|
625
|
+
pid: projectId,
|
|
452
626
|
scope: defaultScope,
|
|
453
627
|
iat: now,
|
|
454
628
|
exp: now + expiresInSeconds
|
|
@@ -481,7 +655,7 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
481
655
|
}
|
|
482
656
|
payload.view = compressed;
|
|
483
657
|
}
|
|
484
|
-
const token = await this.signHS256JWT(payload,
|
|
658
|
+
const token = await this.signHS256JWT(payload, clientSecret);
|
|
485
659
|
const keyringString = Object.entries(this.keyring).map(([id, key]) => `${id}:${key}`).join(",");
|
|
486
660
|
const hostParam = hostOrigin ? `?host=${encodeURIComponent(hostOrigin)}` : "";
|
|
487
661
|
const embedUrl = `${dashboardUrl}/embed/${token}${hostParam}#${keyringString}`;
|
|
@@ -530,6 +704,13 @@ var VolidatorClient = class _VolidatorClient {
|
|
|
530
704
|
}
|
|
531
705
|
}
|
|
532
706
|
};
|
|
707
|
+
/**
|
|
708
|
+
* Thread-local asynchronous context storage used to maintain
|
|
709
|
+
* and increment Lamport logical clock sequences across execution boundaries
|
|
710
|
+
* (e.g. within API requests or serverless handler contexts).
|
|
711
|
+
*/
|
|
712
|
+
_VolidatorClient.logicalClockStore = new AsyncLocalStorage();
|
|
713
|
+
var VolidatorClient = _VolidatorClient;
|
|
533
714
|
var VolidatorCompliance = class {
|
|
534
715
|
constructor(client) {
|
|
535
716
|
this.client = client;
|
|
@@ -660,4 +841,4 @@ export {
|
|
|
660
841
|
VolidatorCompliance,
|
|
661
842
|
VolidatorAgent
|
|
662
843
|
};
|
|
663
|
-
//# sourceMappingURL=chunk-
|
|
844
|
+
//# sourceMappingURL=chunk-UDBMJC25.js.map
|