@socwarden/sdk 1.0.0-alpha.1

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 ADDED
@@ -0,0 +1,131 @@
1
+ # @socwarden/sdk
2
+
3
+ Node.js/TypeScript SDK for [SOCWarden](https://socwarden.io) security event tracking.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @socwarden/sdk
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { SOCWardenClient } from '@socwarden/sdk';
15
+
16
+ const soc = new SOCWardenClient({
17
+ apiKey: 'sk_live_...',
18
+ // endpoint: 'https://ingest.socwarden.io', // default
19
+ // timeout: 5000, // default, in ms
20
+ });
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ ### track() — Named Arguments
26
+
27
+ ```typescript
28
+ // Simple event
29
+ await soc.track('auth.login.success', { actor: userId });
30
+
31
+ // With full options
32
+ await soc.track('data.exported', {
33
+ actor: { id: user.id, email: user.email },
34
+ metadata: { format: 'csv', rows: 1500 },
35
+ resource: { type: 'Report', id: report.id },
36
+ ip: '203.0.113.42',
37
+ });
38
+
39
+ // With explicit actor fields
40
+ await soc.track('auth.login.failure', {
41
+ actorEmail: req.body.email,
42
+ ip: req.ip,
43
+ userAgent: req.get('user-agent'),
44
+ });
45
+ ```
46
+
47
+ ### trackData() — Raw Object
48
+
49
+ ```typescript
50
+ await soc.trackData('auth.login.success', {
51
+ actor_id: user.id,
52
+ actor_email: user.email,
53
+ metadata: { role: 'admin' },
54
+ });
55
+ ```
56
+
57
+ ### event() — Fluent Builder
58
+
59
+ ```typescript
60
+ await soc.event('data.exported')
61
+ .actor({ id: user.id, email: user.email })
62
+ .resource('Report', report.id)
63
+ .meta('format', 'csv')
64
+ .meta('rows', 1500)
65
+ .send();
66
+
67
+ // Chaining metadata
68
+ await soc.event('auth.mfa.enrolled')
69
+ .actor(user.id)
70
+ .metadata({ method: 'totp', provider: 'google' })
71
+ .severity('info')
72
+ .send();
73
+ ```
74
+
75
+ ### Express Middleware
76
+
77
+ The middleware captures request context (IP, user-agent, path, etc.) and attaches it to every event sent during the request lifecycle.
78
+
79
+ ```typescript
80
+ import express from 'express';
81
+ import { SOCWardenClient, socwardenMiddleware } from '@socwarden/sdk';
82
+
83
+ const soc = new SOCWardenClient({ apiKey: 'sk_live_...' });
84
+ const app = express();
85
+
86
+ // Attach SOCWarden middleware
87
+ app.use(socwardenMiddleware(soc));
88
+
89
+ app.post('/login', async (req, res) => {
90
+ const user = await authenticate(req.body);
91
+
92
+ if (user) {
93
+ // IP, user-agent, path are auto-captured from the request
94
+ await soc.track('auth.login.success', { actor: user.id });
95
+ res.json({ ok: true });
96
+ } else {
97
+ await soc.track('auth.login.failure', {
98
+ actorEmail: req.body.email,
99
+ });
100
+ res.status(401).json({ error: 'Invalid credentials' });
101
+ }
102
+ });
103
+ ```
104
+
105
+ ## Rate Limit Handling
106
+
107
+ The SDK automatically handles 429 (rate limit) responses:
108
+
109
+ 1. On receiving a 429, it backs off for the `Retry-After` duration (default: 1 hour).
110
+ 2. During backoff, events are silently dropped to avoid overwhelming the ingestor.
111
+ 3. Every 5 minutes, a probe request is sent to check if the quota has been restored.
112
+ 4. On a successful probe, normal sending resumes immediately.
113
+
114
+ ## Requirements
115
+
116
+ - Node.js 18+ (uses native `fetch`)
117
+ - No runtime dependencies
118
+
119
+ ## TypeScript
120
+
121
+ All types are exported for full TypeScript support:
122
+
123
+ ```typescript
124
+ import type {
125
+ SOCWardenOptions,
126
+ TrackOptions,
127
+ ActorInput,
128
+ ResourceInput,
129
+ EventPayload,
130
+ } from '@socwarden/sdk';
131
+ ```
@@ -0,0 +1,90 @@
1
+ import type { SOCWardenClient } from './client';
2
+ import type { ActorInput, ResourceInput } from './types';
3
+ /**
4
+ * Fluent builder for constructing and sending SOCWarden events.
5
+ *
6
+ * ```ts
7
+ * await soc.event('auth.login.success')
8
+ * .actor({ id: 'usr_123', email: 'john@example.com' })
9
+ * .ip('203.0.113.42')
10
+ * .metadata({ mfa: true })
11
+ * .resource('Session', 'sess_abc')
12
+ * .send();
13
+ * ```
14
+ */
15
+ export declare class EventBuilder {
16
+ private readonly eventName;
17
+ private readonly client;
18
+ private data;
19
+ constructor(event: string, client: SOCWardenClient);
20
+ /**
21
+ * Set the actor (user) who triggered the event.
22
+ *
23
+ * Accepts a string ID or an object with `id` and optional `email`.
24
+ *
25
+ * ```ts
26
+ * .actor('usr_123')
27
+ * .actor({ id: 'usr_123', email: 'john@example.com' })
28
+ * ```
29
+ */
30
+ actor(actorOrId: ActorInput, email?: string): this;
31
+ /**
32
+ * Set the actor ID directly.
33
+ */
34
+ actorId(id: string): this;
35
+ /**
36
+ * Set the actor email directly.
37
+ */
38
+ actorEmail(email: string): this;
39
+ /**
40
+ * Set the source IP address.
41
+ */
42
+ ip(ip: string): this;
43
+ /**
44
+ * Set the User-Agent string.
45
+ */
46
+ userAgent(ua: string): this;
47
+ /**
48
+ * Merge custom metadata key-value pairs. Can be called multiple times;
49
+ * values are merged (later calls override earlier keys).
50
+ *
51
+ * ```ts
52
+ * .metadata({ role: 'admin', format: 'csv' })
53
+ * ```
54
+ */
55
+ metadata(obj: Record<string, unknown>): this;
56
+ /**
57
+ * Set a single metadata key-value pair.
58
+ *
59
+ * ```ts
60
+ * .meta('role', 'admin')
61
+ * ```
62
+ */
63
+ meta(key: string, value: unknown): this;
64
+ /**
65
+ * Set the event timestamp (ISO 8601 string or Date object).
66
+ */
67
+ timestamp(ts: string | Date): this;
68
+ /**
69
+ * Set the event severity hint for the enricher.
70
+ */
71
+ severity(severity: string): this;
72
+ /**
73
+ * Attach the resource that was acted upon.
74
+ *
75
+ * ```ts
76
+ * .resource('Order', 'ord_123')
77
+ * .resource({ type: 'Order', id: 'ord_123' })
78
+ * ```
79
+ */
80
+ resource(typeOrObj: ResourceInput, id?: string): this;
81
+ /**
82
+ * Send the event to SOCWarden.
83
+ */
84
+ send(): Promise<void>;
85
+ /**
86
+ * Get the built data object (for testing/inspection).
87
+ */
88
+ toObject(): Record<string, unknown>;
89
+ }
90
+ //# sourceMappingURL=builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAEzD;;;;;;;;;;;GAWG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IACzC,OAAO,CAAC,IAAI,CAA+B;gBAE/B,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe;IASlD;;;;;;;;;OASG;IACH,KAAK,CAAC,SAAS,EAAE,UAAU,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAkBlD;;OAEG;IACH,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAKzB;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAS/B;;OAEG;IACH,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAKpB;;OAEG;IACH,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAS3B;;;;;;;OAOG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAQ5C;;;;;;OAMG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,IAAI;IAWvC;;OAEG;IACH,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAKlC;;OAEG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAWhC;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,aAAa,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI;IAmBrD;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B;;OAEG;IACH,QAAQ,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAMpC"}
@@ -0,0 +1,182 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EventBuilder = void 0;
4
+ /**
5
+ * Fluent builder for constructing and sending SOCWarden events.
6
+ *
7
+ * ```ts
8
+ * await soc.event('auth.login.success')
9
+ * .actor({ id: 'usr_123', email: 'john@example.com' })
10
+ * .ip('203.0.113.42')
11
+ * .metadata({ mfa: true })
12
+ * .resource('Session', 'sess_abc')
13
+ * .send();
14
+ * ```
15
+ */
16
+ class EventBuilder {
17
+ eventName;
18
+ client;
19
+ data = {};
20
+ constructor(event, client) {
21
+ this.eventName = event;
22
+ this.client = client;
23
+ }
24
+ // ---------------------------------------------------------------------------
25
+ // Actor
26
+ // ---------------------------------------------------------------------------
27
+ /**
28
+ * Set the actor (user) who triggered the event.
29
+ *
30
+ * Accepts a string ID or an object with `id` and optional `email`.
31
+ *
32
+ * ```ts
33
+ * .actor('usr_123')
34
+ * .actor({ id: 'usr_123', email: 'john@example.com' })
35
+ * ```
36
+ */
37
+ actor(actorOrId, email) {
38
+ if (typeof actorOrId === 'string') {
39
+ this.data.actor_id = actorOrId;
40
+ if (email !== undefined) {
41
+ this.data.actor_email = email;
42
+ }
43
+ }
44
+ else {
45
+ this.data.actor_id = actorOrId.id;
46
+ if (actorOrId.email) {
47
+ this.data.actor_email = actorOrId.email;
48
+ }
49
+ if (email !== undefined) {
50
+ this.data.actor_email = email;
51
+ }
52
+ }
53
+ return this;
54
+ }
55
+ /**
56
+ * Set the actor ID directly.
57
+ */
58
+ actorId(id) {
59
+ this.data.actor_id = id;
60
+ return this;
61
+ }
62
+ /**
63
+ * Set the actor email directly.
64
+ */
65
+ actorEmail(email) {
66
+ this.data.actor_email = email;
67
+ return this;
68
+ }
69
+ // ---------------------------------------------------------------------------
70
+ // Request context
71
+ // ---------------------------------------------------------------------------
72
+ /**
73
+ * Set the source IP address.
74
+ */
75
+ ip(ip) {
76
+ this.data.ip = ip;
77
+ return this;
78
+ }
79
+ /**
80
+ * Set the User-Agent string.
81
+ */
82
+ userAgent(ua) {
83
+ this.data.user_agent = ua;
84
+ return this;
85
+ }
86
+ // ---------------------------------------------------------------------------
87
+ // Metadata
88
+ // ---------------------------------------------------------------------------
89
+ /**
90
+ * Merge custom metadata key-value pairs. Can be called multiple times;
91
+ * values are merged (later calls override earlier keys).
92
+ *
93
+ * ```ts
94
+ * .metadata({ role: 'admin', format: 'csv' })
95
+ * ```
96
+ */
97
+ metadata(obj) {
98
+ this.data.metadata = {
99
+ ...(this.data.metadata ?? {}),
100
+ ...obj,
101
+ };
102
+ return this;
103
+ }
104
+ /**
105
+ * Set a single metadata key-value pair.
106
+ *
107
+ * ```ts
108
+ * .meta('role', 'admin')
109
+ * ```
110
+ */
111
+ meta(key, value) {
112
+ const existing = this.data.metadata ?? {};
113
+ existing[key] = value;
114
+ this.data.metadata = existing;
115
+ return this;
116
+ }
117
+ // ---------------------------------------------------------------------------
118
+ // Timestamp & severity
119
+ // ---------------------------------------------------------------------------
120
+ /**
121
+ * Set the event timestamp (ISO 8601 string or Date object).
122
+ */
123
+ timestamp(ts) {
124
+ this.data.timestamp = ts instanceof Date ? ts.toISOString() : ts;
125
+ return this;
126
+ }
127
+ /**
128
+ * Set the event severity hint for the enricher.
129
+ */
130
+ severity(severity) {
131
+ const existing = this.data.metadata ?? {};
132
+ existing._severity = severity;
133
+ this.data.metadata = existing;
134
+ return this;
135
+ }
136
+ // ---------------------------------------------------------------------------
137
+ // Resource
138
+ // ---------------------------------------------------------------------------
139
+ /**
140
+ * Attach the resource that was acted upon.
141
+ *
142
+ * ```ts
143
+ * .resource('Order', 'ord_123')
144
+ * .resource({ type: 'Order', id: 'ord_123' })
145
+ * ```
146
+ */
147
+ resource(typeOrObj, id) {
148
+ const existing = this.data.metadata ?? {};
149
+ if (typeof typeOrObj === 'string') {
150
+ existing.resource_type = typeOrObj;
151
+ if (id !== undefined) {
152
+ existing.resource_id = id;
153
+ }
154
+ }
155
+ else {
156
+ existing.resource_type = typeOrObj.type;
157
+ existing.resource_id = typeOrObj.id;
158
+ }
159
+ this.data.metadata = existing;
160
+ return this;
161
+ }
162
+ // ---------------------------------------------------------------------------
163
+ // Send
164
+ // ---------------------------------------------------------------------------
165
+ /**
166
+ * Send the event to SOCWarden.
167
+ */
168
+ async send() {
169
+ await this.client.trackData(this.eventName, this.data);
170
+ }
171
+ /**
172
+ * Get the built data object (for testing/inspection).
173
+ */
174
+ toObject() {
175
+ return {
176
+ event: this.eventName,
177
+ ...this.data,
178
+ };
179
+ }
180
+ }
181
+ exports.EventBuilder = EventBuilder;
182
+ //# sourceMappingURL=builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"builder.js","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ import { EventBuilder } from './builder';
2
+ import { SOCWardenOptions, TrackOptions } from './types';
3
+ export declare class SOCWardenClient {
4
+ private readonly apiKey;
5
+ private readonly endpoint;
6
+ private readonly timeout;
7
+ /** In-memory backoff state for 429 handling. */
8
+ private backoffUntil;
9
+ private lastProbe;
10
+ constructor(options: SOCWardenOptions);
11
+ /**
12
+ * Track a security event using named arguments.
13
+ *
14
+ * ```ts
15
+ * await soc.track('auth.login.success', { actor: user.id });
16
+ * await soc.track('data.exported', {
17
+ * actor: { id: user.id, email: user.email },
18
+ * metadata: { format: 'csv' },
19
+ * resource: { type: 'Report', id: report.id },
20
+ * });
21
+ * ```
22
+ */
23
+ track(event: string, options?: TrackOptions): Promise<void>;
24
+ /**
25
+ * Track a security event using a raw data object.
26
+ *
27
+ * ```ts
28
+ * await soc.trackData('auth.login.success', {
29
+ * actor_id: user.id,
30
+ * actor_email: user.email,
31
+ * metadata: { role: 'admin' },
32
+ * });
33
+ * ```
34
+ */
35
+ trackData(event: string, data?: Record<string, unknown>): Promise<void>;
36
+ /**
37
+ * Start building an event with the fluent API.
38
+ *
39
+ * ```ts
40
+ * await soc.event('data.exported')
41
+ * .actor(user.id)
42
+ * .resource('Report', report.id)
43
+ * .meta('format', 'csv')
44
+ * .send();
45
+ * ```
46
+ */
47
+ event(name: string): EventBuilder;
48
+ /**
49
+ * @deprecated No-op — context is now stored per-request in AsyncLocalStorage.
50
+ * Kept for API compatibility only; will be removed in a future major version.
51
+ */
52
+ setContext(): void;
53
+ /**
54
+ * @deprecated No-op — context is now stored per-request in AsyncLocalStorage.
55
+ * Kept for API compatibility only; will be removed in a future major version.
56
+ */
57
+ clearContext(): void;
58
+ private resolveNamedArgs;
59
+ private static readonly EVENT_TYPE_REGEX;
60
+ private dispatch;
61
+ private buildPayload;
62
+ private collectContext;
63
+ private sanitizeQueryString;
64
+ /**
65
+ * Send an event payload to the ingestor with 429 backoff handling.
66
+ *
67
+ * Backoff strategy (mirrors Laravel SDK):
68
+ * - On 429: back off for Retry-After seconds (default 1 hour).
69
+ * - During backoff: silently drop events, except for a probe every 5 minutes.
70
+ * - On successful probe: clear backoff and resume normal sending.
71
+ */
72
+ private send;
73
+ }
74
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAGL,gBAAgB,EAChB,YAAY,EACb,MAAM,SAAS,CAAC;AA6BjB,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IAEjC,gDAAgD;IAChD,OAAO,CAAC,YAAY,CAAa;IACjC,OAAO,CAAC,SAAS,CAAa;gBAKlB,OAAO,EAAE,gBAAgB;IAsBrC;;;;;;;;;;;OAWG;IACG,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjE;;;;;;;;;;OAUG;IACG,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjF;;;;;;;;;;OAUG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;IAIjC;;;OAGG;IACH,UAAU,IAAI,IAAI;IAKlB;;;OAGG;IACH,YAAY,IAAI,IAAI;IAQpB,OAAO,CAAC,gBAAgB;IAgExB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAwD;YAElF,QAAQ;IAiBtB,OAAO,CAAC,YAAY;IAsBpB,OAAO,CAAC,cAAc;IAuDtB,OAAO,CAAC,mBAAmB;IAiB3B;;;;;;;OAOG;YACW,IAAI;CAqDnB"}
package/dist/client.js ADDED
@@ -0,0 +1,333 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SOCWardenClient = void 0;
4
+ const builder_1 = require("./builder");
5
+ const os_1 = require("os");
6
+ const context_1 = require("./context");
7
+ const SDK_NAME = 'socwarden-node';
8
+ const SDK_VERSION = '1.0.0';
9
+ const BACKOFF_DURATION = 3600; // 1 hour in seconds
10
+ const PROBE_INTERVAL = 300; // 5 minutes in seconds
11
+ const SENSITIVE_PARAMS = ['token', 'key', 'password', 'secret', 'code', 'auth', 'session', 'csrf'];
12
+ /**
13
+ * Returns ip if it is a valid IPv4 or IPv6 address, otherwise undefined.
14
+ * Matches the ingestor's validate:"omitempty,ip" constraint.
15
+ */
16
+ function sanitizeIP(ip) {
17
+ if (!ip)
18
+ return undefined;
19
+ // IPv4
20
+ const ipv4 = /^(\d{1,3}\.){3}\d{1,3}$/;
21
+ if (ipv4.test(ip)) {
22
+ const parts = ip.split('.').map(Number);
23
+ if (parts.every((p) => p >= 0 && p <= 255))
24
+ return ip;
25
+ }
26
+ // IPv6: contains colon and only hex digits and colons
27
+ if (ip.includes(':') && /^[0-9a-fA-F:]+$/.test(ip))
28
+ return ip;
29
+ return undefined;
30
+ }
31
+ class SOCWardenClient {
32
+ apiKey;
33
+ endpoint;
34
+ timeout;
35
+ /** In-memory backoff state for 429 handling. */
36
+ backoffUntil = 0;
37
+ lastProbe = 0;
38
+ // D4 FIX: Per-request context is now stored in AsyncLocalStorage (see middleware.ts)
39
+ // rather than on the shared instance, preventing concurrent request contamination.
40
+ constructor(options) {
41
+ if (!options.apiKey) {
42
+ throw new Error('[SOCWarden] apiKey is required');
43
+ }
44
+ this.apiKey = options.apiKey;
45
+ this.endpoint = (options.endpoint ?? 'https://ingest.socwarden.io').replace(/\/+$/, '');
46
+ this.timeout = options.timeout ?? 5000;
47
+ // D2 FIX: Enforce HTTPS to prevent API key transmission in cleartext.
48
+ if (!this.endpoint.startsWith('https://')) {
49
+ if (process.env.NODE_ENV === 'production') {
50
+ throw new Error('[SOCWarden] Endpoint must use HTTPS in production. API keys must not be transmitted in cleartext.');
51
+ }
52
+ console.warn('[SOCWarden] WARNING: Endpoint is using HTTP. API keys will be transmitted in cleartext.');
53
+ }
54
+ }
55
+ // ---------------------------------------------------------------------------
56
+ // Public API
57
+ // ---------------------------------------------------------------------------
58
+ /**
59
+ * Track a security event using named arguments.
60
+ *
61
+ * ```ts
62
+ * await soc.track('auth.login.success', { actor: user.id });
63
+ * await soc.track('data.exported', {
64
+ * actor: { id: user.id, email: user.email },
65
+ * metadata: { format: 'csv' },
66
+ * resource: { type: 'Report', id: report.id },
67
+ * });
68
+ * ```
69
+ */
70
+ async track(event, options) {
71
+ const data = options ? this.resolveNamedArgs(options) : {};
72
+ await this.dispatch(event, data);
73
+ }
74
+ /**
75
+ * Track a security event using a raw data object.
76
+ *
77
+ * ```ts
78
+ * await soc.trackData('auth.login.success', {
79
+ * actor_id: user.id,
80
+ * actor_email: user.email,
81
+ * metadata: { role: 'admin' },
82
+ * });
83
+ * ```
84
+ */
85
+ async trackData(event, data = {}) {
86
+ await this.dispatch(event, data);
87
+ }
88
+ /**
89
+ * Start building an event with the fluent API.
90
+ *
91
+ * ```ts
92
+ * await soc.event('data.exported')
93
+ * .actor(user.id)
94
+ * .resource('Report', report.id)
95
+ * .meta('format', 'csv')
96
+ * .send();
97
+ * ```
98
+ */
99
+ event(name) {
100
+ return new builder_1.EventBuilder(name, this);
101
+ }
102
+ /**
103
+ * @deprecated No-op — context is now stored per-request in AsyncLocalStorage.
104
+ * Kept for API compatibility only; will be removed in a future major version.
105
+ */
106
+ setContext() {
107
+ // D4 FIX: Context is stored in AsyncLocalStorage by the middleware.
108
+ // This method is intentionally a no-op.
109
+ }
110
+ /**
111
+ * @deprecated No-op — context is now stored per-request in AsyncLocalStorage.
112
+ * Kept for API compatibility only; will be removed in a future major version.
113
+ */
114
+ clearContext() {
115
+ // D4 FIX: AsyncLocalStorage context is automatically scoped to the request.
116
+ }
117
+ // ---------------------------------------------------------------------------
118
+ // Internal: argument resolution
119
+ // ---------------------------------------------------------------------------
120
+ resolveNamedArgs(options) {
121
+ const data = {};
122
+ // Actor: object reads id + email; string is just id
123
+ if (options.actor !== undefined) {
124
+ if (typeof options.actor === 'string') {
125
+ data.actor_id = options.actor;
126
+ }
127
+ else {
128
+ data.actor_id = options.actor.id;
129
+ if (options.actor.email) {
130
+ data.actor_email = options.actor.email;
131
+ }
132
+ }
133
+ }
134
+ // Explicit scalars override actor-resolved values
135
+ if (options.actorId !== undefined) {
136
+ data.actor_id = options.actorId;
137
+ }
138
+ if (options.actorEmail !== undefined) {
139
+ data.actor_email = options.actorEmail;
140
+ }
141
+ if (options.ip !== undefined) {
142
+ data.ip = sanitizeIP(options.ip);
143
+ }
144
+ if (options.userAgent !== undefined) {
145
+ data.user_agent = options.userAgent;
146
+ }
147
+ if (options.metadata !== undefined) {
148
+ data.metadata = { ...options.metadata };
149
+ }
150
+ if (options.timestamp !== undefined) {
151
+ data.timestamp =
152
+ options.timestamp instanceof Date
153
+ ? options.timestamp.toISOString()
154
+ : options.timestamp;
155
+ }
156
+ // Resource: object reads type + id; string is just type
157
+ if (options.resource !== undefined) {
158
+ const meta = (data.metadata ?? {});
159
+ if (typeof options.resource === 'string') {
160
+ meta.resource_type = options.resource;
161
+ if (options.resourceId !== undefined) {
162
+ meta.resource_id = options.resourceId;
163
+ }
164
+ }
165
+ else {
166
+ meta.resource_type = options.resource.type;
167
+ meta.resource_id = options.resource.id;
168
+ }
169
+ data.metadata = meta;
170
+ }
171
+ // Remove undefined values
172
+ return Object.fromEntries(Object.entries(data).filter(([, v]) => v !== undefined && v !== null));
173
+ }
174
+ // ---------------------------------------------------------------------------
175
+ // Internal: dispatch and send
176
+ // ---------------------------------------------------------------------------
177
+ // D3 FIX: Validate event_type format before sending to the ingestor.
178
+ static EVENT_TYPE_REGEX = /^[a-z][a-z0-9]{0,29}(\.[a-z][a-z0-9_]{0,29}){1,3}$/;
179
+ async dispatch(event, data) {
180
+ // D3 FIX: Validate event type format before sending.
181
+ if (!SOCWardenClient.EVENT_TYPE_REGEX.test(event)) {
182
+ console.warn(`[SOCWarden] Invalid event type format, dropping event: "${event}". ` +
183
+ 'Event types must match ^[a-z][a-z0-9]{0,29}(\\.[a-z][a-z0-9_]{0,29}){1,3}$');
184
+ return;
185
+ }
186
+ const payload = this.buildPayload(event, data);
187
+ try {
188
+ await this.send(payload);
189
+ }
190
+ catch (err) {
191
+ const message = err instanceof Error ? err.message : String(err);
192
+ console.warn(`[SOCWarden] Failed to send event: ${message}`);
193
+ }
194
+ }
195
+ buildPayload(event, data) {
196
+ const payload = {
197
+ event,
198
+ source: 'sdk',
199
+ };
200
+ const fields = ['actor_id', 'actor_email', 'user_agent', 'metadata', 'timestamp'];
201
+ for (const field of fields) {
202
+ if (data[field] !== undefined) {
203
+ payload[field] = data[field];
204
+ }
205
+ }
206
+ if (data.ip !== undefined) {
207
+ payload.ip = data.ip;
208
+ }
209
+ // Attach auto-context if middleware captured it
210
+ payload.context = this.collectContext(data);
211
+ return payload;
212
+ }
213
+ collectContext(data) {
214
+ const context = {
215
+ sdk: {
216
+ name: SDK_NAME,
217
+ version: SDK_VERSION,
218
+ },
219
+ server: {
220
+ hostname: (0, os_1.hostname)(),
221
+ runtime: `Node.js ${process.version}`,
222
+ pid: process.pid,
223
+ },
224
+ };
225
+ // D4 FIX: Read per-request context from AsyncLocalStorage instead of the
226
+ // shared instance property to prevent concurrent request contamination.
227
+ const requestCtx = context_1.requestContextStorage.getStore();
228
+ if (requestCtx?.request) {
229
+ const req = requestCtx.request;
230
+ context.request = {
231
+ method: req.method,
232
+ path: req.path,
233
+ };
234
+ if (req.ip) {
235
+ context.request.ip = req.ip;
236
+ }
237
+ if (req.queryString) {
238
+ context.request.query_string = this.sanitizeQueryString(req.queryString);
239
+ }
240
+ if (req.referer) {
241
+ context.request.referer = req.referer;
242
+ }
243
+ if (req.origin) {
244
+ context.request.origin = req.origin;
245
+ }
246
+ if (req.contentType) {
247
+ context.request.content_type = req.contentType;
248
+ }
249
+ if (req.acceptLanguage) {
250
+ context.request.accept_language = req.acceptLanguage;
251
+ }
252
+ if (req.requestId) {
253
+ context.request.request_id = req.requestId;
254
+ }
255
+ if (req.userAgent) {
256
+ context.request.user_agent = req.userAgent;
257
+ }
258
+ }
259
+ // D1 FIX: Browser context from X-SOCWarden-Context header removed —
260
+ // trusting arbitrary HTTP headers allows spoofing of server-side metadata.
261
+ return context;
262
+ }
263
+ sanitizeQueryString(qs) {
264
+ if (!qs)
265
+ return '';
266
+ return qs
267
+ .split('&')
268
+ .map((pair) => {
269
+ const [key, ...rest] = pair.split('=');
270
+ const paramName = key.toLowerCase();
271
+ const isSensitive = SENSITIVE_PARAMS.some((s) => paramName.includes(s));
272
+ if (isSensitive && rest.length > 0) {
273
+ return `${key}=[REDACTED]`;
274
+ }
275
+ return pair;
276
+ })
277
+ .join('&');
278
+ }
279
+ /**
280
+ * Send an event payload to the ingestor with 429 backoff handling.
281
+ *
282
+ * Backoff strategy (mirrors Laravel SDK):
283
+ * - On 429: back off for Retry-After seconds (default 1 hour).
284
+ * - During backoff: silently drop events, except for a probe every 5 minutes.
285
+ * - On successful probe: clear backoff and resume normal sending.
286
+ */
287
+ async send(payload) {
288
+ const now = Math.floor(Date.now() / 1000);
289
+ // Check backoff
290
+ if (this.backoffUntil > 0 && now < this.backoffUntil) {
291
+ // During backoff, only send probes at PROBE_INTERVAL
292
+ if (now - this.lastProbe < PROBE_INTERVAL) {
293
+ return; // silently drop
294
+ }
295
+ this.lastProbe = now;
296
+ }
297
+ const controller = new AbortController();
298
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
299
+ try {
300
+ const response = await fetch(`${this.endpoint}/v1/events`, {
301
+ method: 'POST',
302
+ headers: {
303
+ 'Content-Type': 'application/json',
304
+ Authorization: `Bearer ${this.apiKey}`,
305
+ },
306
+ body: JSON.stringify(payload),
307
+ signal: controller.signal,
308
+ });
309
+ if (response.status === 429) {
310
+ const retryAfter = parseInt(response.headers.get('Retry-After') ?? '', 10);
311
+ const backoffSeconds = isNaN(retryAfter) ? BACKOFF_DURATION : retryAfter;
312
+ this.backoffUntil = now + backoffSeconds;
313
+ console.warn(`[SOCWarden] Quota exceeded (429). Backing off for ${backoffSeconds}s`);
314
+ return;
315
+ }
316
+ // Clear backoff on any successful response
317
+ if (response.ok && this.backoffUntil > 0) {
318
+ this.backoffUntil = 0;
319
+ this.lastProbe = 0;
320
+ console.info('[SOCWarden] Quota restored, backoff cleared');
321
+ }
322
+ if (!response.ok) {
323
+ const body = await response.text().catch(() => '');
324
+ console.warn(`[SOCWarden] Event send failed (HTTP ${response.status}): ${body}`);
325
+ }
326
+ }
327
+ finally {
328
+ clearTimeout(timeoutId);
329
+ }
330
+ }
331
+ }
332
+ exports.SOCWardenClient = SOCWardenClient;
333
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+ import type { CapturedContext } from './types';
3
+ /**
4
+ * D4 FIX: Per-request AsyncLocalStorage context store.
5
+ *
6
+ * Storing per-request context (IP, user-agent, etc.) on the shared singleton
7
+ * SOCWardenClient instance causes concurrent requests to contaminate each
8
+ * other's context. AsyncLocalStorage provides automatic isolation: each
9
+ * request runs in its own async context and getStore() always returns the
10
+ * correct context for the current request, even under high concurrency.
11
+ */
12
+ export declare const requestContextStorage: AsyncLocalStorage<CapturedContext>;
13
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,oCAA2C,CAAC"}
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requestContextStorage = void 0;
4
+ const async_hooks_1 = require("async_hooks");
5
+ /**
6
+ * D4 FIX: Per-request AsyncLocalStorage context store.
7
+ *
8
+ * Storing per-request context (IP, user-agent, etc.) on the shared singleton
9
+ * SOCWardenClient instance causes concurrent requests to contaminate each
10
+ * other's context. AsyncLocalStorage provides automatic isolation: each
11
+ * request runs in its own async context and getStore() always returns the
12
+ * correct context for the current request, even under high concurrency.
13
+ */
14
+ exports.requestContextStorage = new async_hooks_1.AsyncLocalStorage();
15
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":";;;AAAA,6CAAgD;AAGhD;;;;;;;;GAQG;AACU,QAAA,qBAAqB,GAAG,IAAI,+BAAiB,EAAmB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { SOCWardenClient } from './client';
2
+ export { EventBuilder } from './builder';
3
+ export { socwardenMiddleware } from './middleware';
4
+ export type { SOCWardenOptions, TrackOptions, ActorInput, ResourceInput, EventPayload, RequestContext, CapturedContext, } from './types';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,aAAa,EACb,YAAY,EACZ,cAAc,EACd,eAAe,GAChB,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.socwardenMiddleware = exports.EventBuilder = exports.SOCWardenClient = void 0;
4
+ var client_1 = require("./client");
5
+ Object.defineProperty(exports, "SOCWardenClient", { enumerable: true, get: function () { return client_1.SOCWardenClient; } });
6
+ var builder_1 = require("./builder");
7
+ Object.defineProperty(exports, "EventBuilder", { enumerable: true, get: function () { return builder_1.EventBuilder; } });
8
+ var middleware_1 = require("./middleware");
9
+ Object.defineProperty(exports, "socwardenMiddleware", { enumerable: true, get: function () { return middleware_1.socwardenMiddleware; } });
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,mCAA2C;AAAlC,yGAAA,eAAe,OAAA;AACxB,qCAAyC;AAAhC,uGAAA,YAAY,OAAA;AACrB,2CAAmD;AAA1C,iHAAA,mBAAmB,OAAA"}
@@ -0,0 +1,42 @@
1
+ import type { SOCWardenClient } from './client';
2
+ export { requestContextStorage } from './context';
3
+ /**
4
+ * Minimal Express-compatible request interface.
5
+ * Avoids requiring Express as a dependency.
6
+ */
7
+ interface ExpressRequest {
8
+ method: string;
9
+ path: string;
10
+ ip?: string;
11
+ query?: Record<string, unknown>;
12
+ get(header: string): string | undefined;
13
+ }
14
+ /**
15
+ * Minimal Express-compatible response interface.
16
+ */
17
+ interface ExpressResponse {
18
+ on(event: string, listener: () => void): void;
19
+ }
20
+ /**
21
+ * Minimal Express-compatible next function.
22
+ */
23
+ type NextFunction = (err?: unknown) => void;
24
+ /**
25
+ * Express middleware that captures request context for SOCWarden events.
26
+ *
27
+ * When active, every event sent during the request lifecycle will automatically
28
+ * include request metadata (method, path, IP, user-agent, etc.) in the
29
+ * `context` field of the payload.
30
+ *
31
+ * ```ts
32
+ * import express from 'express';
33
+ * import { SOCWardenClient, socwardenMiddleware } from '@socwarden/sdk';
34
+ *
35
+ * const soc = new SOCWardenClient({ apiKey: 'sk_live_...' });
36
+ * const app = express();
37
+ *
38
+ * app.use(socwardenMiddleware(soc));
39
+ * ```
40
+ */
41
+ export declare function socwardenMiddleware(client: SOCWardenClient): (req: ExpressRequest, _res: ExpressResponse, next: NextFunction) => void;
42
+ //# sourceMappingURL=middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAGhD,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAElD;;;GAGG;AACH,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACzC;AAED;;GAEG;AACH,UAAU,eAAe;IACvB,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;CAC/C;AAED;;GAEG;AACH,KAAK,YAAY,GAAG,CAAC,GAAG,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;AAE5C;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,eAAe,IAMjD,KAAK,cAAc,EAAE,MAAM,eAAe,EAAE,MAAM,YAAY,KAAG,IAAI,CA8B9E"}
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requestContextStorage = void 0;
4
+ exports.socwardenMiddleware = socwardenMiddleware;
5
+ const context_1 = require("./context");
6
+ var context_2 = require("./context");
7
+ Object.defineProperty(exports, "requestContextStorage", { enumerable: true, get: function () { return context_2.requestContextStorage; } });
8
+ /**
9
+ * Express middleware that captures request context for SOCWarden events.
10
+ *
11
+ * When active, every event sent during the request lifecycle will automatically
12
+ * include request metadata (method, path, IP, user-agent, etc.) in the
13
+ * `context` field of the payload.
14
+ *
15
+ * ```ts
16
+ * import express from 'express';
17
+ * import { SOCWardenClient, socwardenMiddleware } from '@socwarden/sdk';
18
+ *
19
+ * const soc = new SOCWardenClient({ apiKey: 'sk_live_...' });
20
+ * const app = express();
21
+ *
22
+ * app.use(socwardenMiddleware(soc));
23
+ * ```
24
+ */
25
+ function socwardenMiddleware(client) {
26
+ // D4 FIX: client parameter retained for API compatibility but context is now
27
+ // stored in AsyncLocalStorage instead of on the shared singleton instance,
28
+ // preventing concurrent request context contamination.
29
+ void client;
30
+ return (req, _res, next) => {
31
+ const queryString = buildQueryString(req.query);
32
+ const capturedContext = {
33
+ request: {
34
+ method: req.method,
35
+ path: req.path,
36
+ ip: req.ip ?? req.get('x-forwarded-for')?.split(',')[0].trim(),
37
+ userAgent: req.get('user-agent'),
38
+ queryString,
39
+ referer: req.get('referer'),
40
+ origin: req.get('origin'),
41
+ contentType: req.get('content-type'),
42
+ acceptLanguage: req.get('accept-language'),
43
+ requestId: req.get('x-request-id') ?? req.get('x-correlation-id'),
44
+ },
45
+ sdk: {
46
+ name: 'socwarden-node',
47
+ version: '1.0.0',
48
+ },
49
+ };
50
+ // D1 FIX: X-SOCWarden-Context header removed — trusting arbitrary HTTP headers
51
+ // allows any client to spoof server-side metadata. Server context is collected
52
+ // locally by the SDK and must not be merged from incoming request headers.
53
+ // D4 FIX: Run the rest of the request handling within the AsyncLocalStorage
54
+ // context so each concurrent request has its own isolated context.
55
+ context_1.requestContextStorage.run(capturedContext, next);
56
+ };
57
+ }
58
+ /**
59
+ * Reconstruct a query string from Express's parsed query object.
60
+ */
61
+ function buildQueryString(query) {
62
+ if (!query)
63
+ return undefined;
64
+ const entries = Object.entries(query).filter(([, v]) => v !== undefined && v !== null);
65
+ if (entries.length === 0)
66
+ return undefined;
67
+ return entries
68
+ .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
69
+ .join('&');
70
+ }
71
+ //# sourceMappingURL=middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":";;;AA8CA,kDAoCC;AAjFD,uCAAkD;AAElD,qCAAkD;AAAzC,gHAAA,qBAAqB,OAAA;AA0B9B;;;;;;;;;;;;;;;;GAgBG;AACH,SAAgB,mBAAmB,CAAC,MAAuB;IACzD,6EAA6E;IAC7E,2EAA2E;IAC3E,uDAAuD;IACvD,KAAK,MAAM,CAAC;IAEZ,OAAO,CAAC,GAAmB,EAAE,IAAqB,EAAE,IAAkB,EAAQ,EAAE;QAC9E,MAAM,WAAW,GAAG,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEhD,MAAM,eAAe,GAAoB;YACvC,OAAO,EAAE;gBACP,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,EAAE,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE;gBAC9D,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC;gBAChC,WAAW;gBACX,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC;gBAC3B,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC;gBACzB,WAAW,EAAE,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC;gBACpC,cAAc,EAAE,GAAG,CAAC,GAAG,CAAC,iBAAiB,CAAC;gBAC1C,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,GAAG,CAAC,GAAG,CAAC,kBAAkB,CAAC;aAClE;YACD,GAAG,EAAE;gBACH,IAAI,EAAE,gBAAgB;gBACtB,OAAO,EAAE,OAAO;aACjB;SACF,CAAC;QAEF,+EAA+E;QAC/E,+EAA+E;QAC/E,2EAA2E;QAE3E,4EAA4E;QAC5E,mEAAmE;QACnE,+BAAqB,CAAC,GAAG,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC;IACnD,CAAC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,SAAS,gBAAgB,CAAC,KAA+B;IACvD,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAE7B,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAC1C,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,IAAI,CACzC,CAAC;IACF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAE3C,OAAO,OAAO;SACX,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5E,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC"}
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Configuration options for the SOCWarden client.
3
+ */
4
+ export interface SOCWardenOptions {
5
+ /** API key for authenticating with the SOCWarden ingestor. */
6
+ apiKey: string;
7
+ /** Ingestor endpoint URL. Defaults to https://ingest.socwarden.io */
8
+ endpoint?: string;
9
+ /** HTTP request timeout in milliseconds. Defaults to 5000. */
10
+ timeout?: number;
11
+ }
12
+ /**
13
+ * Actor identification — either a string ID or an object with id and optional email.
14
+ */
15
+ export type ActorInput = string | {
16
+ id: string;
17
+ email?: string;
18
+ };
19
+ /**
20
+ * Resource identification — either a string type or an object with type and id.
21
+ */
22
+ export type ResourceInput = string | {
23
+ type: string;
24
+ id: string;
25
+ };
26
+ /**
27
+ * Options for the `track()` method (named-args style).
28
+ */
29
+ export interface TrackOptions {
30
+ /** Actor (user) who triggered the event. String is treated as actor ID. */
31
+ actor?: ActorInput;
32
+ /** Explicit actor ID (overrides actor if both provided). */
33
+ actorId?: string;
34
+ /** Explicit actor email (overrides actor.email if both provided). */
35
+ actorEmail?: string;
36
+ /** Source IP address. Auto-detected from request context if middleware is active. */
37
+ ip?: string;
38
+ /** User-Agent string. Auto-detected from request context if middleware is active. */
39
+ userAgent?: string;
40
+ /** Custom metadata key-value pairs. */
41
+ metadata?: Record<string, unknown>;
42
+ /** Event timestamp (ISO 8601 string or Date object). Defaults to now. */
43
+ timestamp?: string | Date;
44
+ /** Resource that was acted upon. String is treated as resource type. */
45
+ resource?: ResourceInput;
46
+ /** Explicit resource ID (used when resource is a string type). */
47
+ resourceId?: string;
48
+ }
49
+ /**
50
+ * Payload sent to the SOCWarden ingestor POST /v1/events endpoint.
51
+ */
52
+ export interface EventPayload {
53
+ event: string;
54
+ source: string;
55
+ actor_id?: string;
56
+ actor_email?: string;
57
+ ip?: string;
58
+ user_agent?: string;
59
+ metadata?: Record<string, unknown>;
60
+ timestamp?: string;
61
+ context?: RequestContext;
62
+ }
63
+ /**
64
+ * Auto-collected context attached to every event when middleware is active.
65
+ */
66
+ export interface RequestContext {
67
+ sdk: {
68
+ name: string;
69
+ version: string;
70
+ };
71
+ server?: {
72
+ hostname: string;
73
+ runtime: string;
74
+ pid: number;
75
+ };
76
+ request?: {
77
+ method: string;
78
+ path: string;
79
+ ip?: string;
80
+ user_agent?: string;
81
+ query_string?: string;
82
+ referer?: string;
83
+ origin?: string;
84
+ content_type?: string;
85
+ accept_language?: string;
86
+ request_id?: string;
87
+ };
88
+ /** Browser context relayed from the browser SDK via X-SOCWarden-Context header. */
89
+ browser?: Record<string, unknown>;
90
+ }
91
+ /**
92
+ * Internal context captured by the Express middleware for the current request.
93
+ */
94
+ export interface CapturedContext {
95
+ request: {
96
+ method: string;
97
+ path: string;
98
+ ip?: string;
99
+ userAgent?: string;
100
+ queryString?: string;
101
+ referer?: string;
102
+ origin?: string;
103
+ contentType?: string;
104
+ acceptLanguage?: string;
105
+ requestId?: string;
106
+ };
107
+ sdk: {
108
+ name: string;
109
+ version: string;
110
+ };
111
+ /** Browser context decoded from X-SOCWarden-Context header (relay mode). */
112
+ browser?: Record<string, unknown>;
113
+ }
114
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAElE;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,2EAA2E;IAC3E,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qFAAqF;IACrF,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,qFAAqF;IACrF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,kEAAkE;IAClE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,cAAc,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE;QACH,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,MAAM,CAAC,EAAE;QACP,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,GAAG,EAAE,MAAM,CAAC;KACb,CAAC;IACF,OAAO,CAAC,EAAE;QACR,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,eAAe,CAAC,EAAE,MAAM,CAAC;QACzB,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,mFAAmF;IACnF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,EAAE,MAAM,CAAC;QACb,EAAE,CAAC,EAAE,MAAM,CAAC;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,GAAG,EAAE;QACH,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC"}
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@socwarden/sdk",
3
+ "version": "1.0.0-alpha.1",
4
+ "description": "SOCWarden Node.js/TypeScript SDK for security event tracking",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "test": "npx tsx --test src/__tests__/*.test.ts",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "socwarden",
17
+ "security",
18
+ "soc",
19
+ "event-tracking",
20
+ "threat-detection"
21
+ ],
22
+ "license": "MIT",
23
+ "devDependencies": {
24
+ "@types/node": "^25.5.0",
25
+ "tsx": "^4.21.0",
26
+ "typescript": "^5.5.0"
27
+ },
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ }
31
+ }