guardrail-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ import type { SdkEvent, ResolvedConfig } from "./types.js";
2
+ /**
3
+ * Collects SDK events in-memory and sends them in batches to the GuardRail API.
4
+ * Also tracks blocked IPs returned by the server.
5
+ */
6
+ export declare class EventCollector {
7
+ private config;
8
+ private queue;
9
+ private blockedIps;
10
+ private timer;
11
+ private flushing;
12
+ constructor(config: ResolvedConfig);
13
+ /** Add an event to the queue. Triggers immediate flush if batch is full. */
14
+ push(event: SdkEvent): void;
15
+ /** Check if an IP is in the blocked list. */
16
+ isBlocked(ip: string): boolean;
17
+ /** Send queued events to the GuardRail API. */
18
+ flush(): Promise<void>;
19
+ /** Stop the collector and flush remaining events. */
20
+ destroy(): Promise<void>;
21
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Collects SDK events in-memory and sends them in batches to the GuardRail API.
3
+ * Also tracks blocked IPs returned by the server.
4
+ */
5
+ export class EventCollector {
6
+ config;
7
+ queue = [];
8
+ blockedIps = new Set();
9
+ timer = null;
10
+ flushing = false;
11
+ constructor(config) {
12
+ this.config = config;
13
+ // Start periodic flush
14
+ this.timer = setInterval(() => {
15
+ void this.flush();
16
+ }, this.config.flushIntervalMs);
17
+ // Ensure timer doesn't prevent process exit
18
+ if (this.timer && typeof this.timer === "object" && "unref" in this.timer) {
19
+ this.timer.unref();
20
+ }
21
+ }
22
+ /** Add an event to the queue. Triggers immediate flush if batch is full. */
23
+ push(event) {
24
+ this.queue.push(event);
25
+ if (this.queue.length >= this.config.maxBatchSize) {
26
+ void this.flush();
27
+ }
28
+ }
29
+ /** Check if an IP is in the blocked list. */
30
+ isBlocked(ip) {
31
+ return this.blockedIps.has(ip);
32
+ }
33
+ /** Send queued events to the GuardRail API. */
34
+ async flush() {
35
+ if (this.flushing || this.queue.length === 0)
36
+ return;
37
+ this.flushing = true;
38
+ const batch = this.queue.splice(0, this.config.maxBatchSize);
39
+ try {
40
+ const res = await fetch(`${this.config.apiUrl}/api/events`, {
41
+ method: "POST",
42
+ headers: {
43
+ "Content-Type": "application/json",
44
+ Authorization: `Bearer ${this.config.projectKey}`,
45
+ },
46
+ body: JSON.stringify({ events: batch }),
47
+ });
48
+ if (res.ok) {
49
+ const data = (await res.json());
50
+ // Update blocked IPs from server response
51
+ if (data.blockedIps && Array.isArray(data.blockedIps)) {
52
+ for (const entry of data.blockedIps) {
53
+ this.blockedIps.add(entry.ip);
54
+ }
55
+ }
56
+ if (this.config.debug) {
57
+ console.error(`[guardrail-sdk] Flushed ${batch.length} events. Blocked IPs: ${this.blockedIps.size}`);
58
+ }
59
+ }
60
+ else {
61
+ // API error — put events back in queue for retry
62
+ this.queue.unshift(...batch);
63
+ if (this.config.debug) {
64
+ console.error(`[guardrail-sdk] Flush failed (${res.status}). ${batch.length} events re-queued.`);
65
+ }
66
+ }
67
+ }
68
+ catch (err) {
69
+ // Network error — put events back in queue for retry
70
+ this.queue.unshift(...batch);
71
+ if (this.config.debug) {
72
+ console.error(`[guardrail-sdk] Flush error: ${err instanceof Error ? err.message : "Unknown error"}. ${batch.length} events re-queued.`);
73
+ }
74
+ }
75
+ finally {
76
+ this.flushing = false;
77
+ }
78
+ }
79
+ /** Stop the collector and flush remaining events. */
80
+ async destroy() {
81
+ if (this.timer) {
82
+ clearInterval(this.timer);
83
+ this.timer = null;
84
+ }
85
+ await this.flush();
86
+ }
87
+ }
@@ -0,0 +1,30 @@
1
+ import type { GuardrailConfig } from "./types.js";
2
+ export type { GuardrailConfig, SdkEvent, SdkEventType } from "./types.js";
3
+ /**
4
+ * Express/Connect middleware for GuardRail runtime monitoring.
5
+ *
6
+ * Usage:
7
+ * ```ts
8
+ * import { guardrail } from 'guardrail-sdk'
9
+ *
10
+ * app.use(guardrail({
11
+ * projectKey: process.env.GUARDRAIL_PROJECT_KEY
12
+ * }))
13
+ * ```
14
+ */
15
+ export declare function guardrail(config: GuardrailConfig): (req: {
16
+ ip?: string;
17
+ socket?: {
18
+ remoteAddress?: string;
19
+ };
20
+ headers: Record<string, string | string[] | undefined>;
21
+ method?: string;
22
+ path?: string;
23
+ url?: string;
24
+ }, res: {
25
+ statusCode: number;
26
+ status: (code: number) => {
27
+ json: (body: unknown) => void;
28
+ };
29
+ end: (...args: unknown[]) => unknown;
30
+ }, next: () => void) => void;
package/dist/index.js ADDED
@@ -0,0 +1,99 @@
1
+ import { EventCollector } from "./collector.js";
2
+ const DEFAULT_API_URL = "https://guardrail-seven.vercel.app";
3
+ const DEFAULT_FLUSH_INTERVAL_MS = 30_000;
4
+ const DEFAULT_MAX_BATCH_SIZE = 50;
5
+ /** Auth endpoint patterns */
6
+ const AUTH_PATTERNS = [
7
+ /\/login/i,
8
+ /\/signin/i,
9
+ /\/sign-in/i,
10
+ /\/auth/i,
11
+ /\/api\/auth/i,
12
+ /\/api\/login/i,
13
+ /\/session/i,
14
+ ];
15
+ function isAuthEndpoint(path) {
16
+ return AUTH_PATTERNS.some((p) => p.test(path));
17
+ }
18
+ function resolveConfig(config) {
19
+ return {
20
+ projectKey: config.projectKey,
21
+ apiUrl: config.apiUrl || DEFAULT_API_URL,
22
+ flushIntervalMs: config.flushIntervalMs || DEFAULT_FLUSH_INTERVAL_MS,
23
+ maxBatchSize: config.maxBatchSize || DEFAULT_MAX_BATCH_SIZE,
24
+ debug: config.debug || false,
25
+ };
26
+ }
27
+ /**
28
+ * Express/Connect middleware for GuardRail runtime monitoring.
29
+ *
30
+ * Usage:
31
+ * ```ts
32
+ * import { guardrail } from 'guardrail-sdk'
33
+ *
34
+ * app.use(guardrail({
35
+ * projectKey: process.env.GUARDRAIL_PROJECT_KEY
36
+ * }))
37
+ * ```
38
+ */
39
+ export function guardrail(config) {
40
+ if (!config.projectKey) {
41
+ console.error("[guardrail-sdk] Missing projectKey. Set GUARDRAIL_PROJECT_KEY environment variable.");
42
+ // Return no-op middleware
43
+ return function guardrailNoOp(_req, _res, next) {
44
+ next();
45
+ };
46
+ }
47
+ const resolved = resolveConfig(config);
48
+ const collector = new EventCollector(resolved);
49
+ if (resolved.debug) {
50
+ console.error("[guardrail-sdk] Initialized with Express middleware");
51
+ }
52
+ // Express middleware signature
53
+ return function guardrailMiddleware(req, res, next) {
54
+ const ip = req.ip || req.socket?.remoteAddress || "";
55
+ const userAgent = req.headers["user-agent"] || "";
56
+ const path = req.path || req.url || "";
57
+ const now = new Date().toISOString();
58
+ // Block denied IPs
59
+ if (collector.isBlocked(ip)) {
60
+ res.status(403).json({ error: "Access denied" });
61
+ return;
62
+ }
63
+ // Track auth endpoint responses
64
+ if (isAuthEndpoint(path) && req.method === "POST") {
65
+ const origEnd = res.end;
66
+ res.end = function (...args) {
67
+ const status = res.statusCode;
68
+ if (status === 401 || status === 403) {
69
+ collector.push({
70
+ type: "auth.login_failed",
71
+ timestamp: now,
72
+ ip,
73
+ userAgent,
74
+ metadata: { path, method: req.method },
75
+ });
76
+ }
77
+ else if (status >= 200 && status < 300) {
78
+ collector.push({
79
+ type: "auth.login_success",
80
+ timestamp: now,
81
+ ip,
82
+ userAgent,
83
+ metadata: { path, method: req.method },
84
+ });
85
+ }
86
+ return origEnd.apply(res, args);
87
+ };
88
+ }
89
+ // Track all API requests
90
+ collector.push({
91
+ type: "api.request",
92
+ timestamp: now,
93
+ ip,
94
+ userAgent,
95
+ metadata: { path, method: req.method },
96
+ });
97
+ next();
98
+ };
99
+ }
package/dist/next.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ import type { GuardrailConfig } from "./types.js";
2
+ export type { GuardrailConfig, SdkEvent, SdkEventType } from "./types.js";
3
+ /**
4
+ * Next.js middleware for GuardRail runtime monitoring.
5
+ *
6
+ * Usage in middleware.ts:
7
+ * ```ts
8
+ * import { guardrailMiddleware } from 'guardrail-sdk/next'
9
+ *
10
+ * const guardrail = guardrailMiddleware({
11
+ * projectKey: process.env.GUARDRAIL_PROJECT_KEY!,
12
+ * })
13
+ *
14
+ * export async function middleware(request: Request) {
15
+ * const blocked = guardrail(request)
16
+ * if (blocked) return blocked
17
+ * }
18
+ * ```
19
+ *
20
+ * Note: Next.js middleware cannot access response status codes,
21
+ * so auth.login_failed/success events cannot be auto-detected.
22
+ * For auth event tracking, call the GuardRail API directly from
23
+ * your API route handlers.
24
+ */
25
+ export declare function guardrailMiddleware(config: GuardrailConfig): (request: Request) => Response | null;
package/dist/next.js ADDED
@@ -0,0 +1,110 @@
1
+ import { EventCollector } from "./collector.js";
2
+ const DEFAULT_API_URL = "https://guardrail-seven.vercel.app";
3
+ const DEFAULT_FLUSH_INTERVAL_MS = 30_000;
4
+ const DEFAULT_MAX_BATCH_SIZE = 50;
5
+ const AUTH_PATTERNS = [
6
+ /\/login/i,
7
+ /\/signin/i,
8
+ /\/sign-in/i,
9
+ /\/auth/i,
10
+ /\/api\/auth/i,
11
+ /\/api\/login/i,
12
+ /\/session/i,
13
+ ];
14
+ function isAuthEndpoint(path) {
15
+ return AUTH_PATTERNS.some((p) => p.test(path));
16
+ }
17
+ function resolveConfig(config) {
18
+ return {
19
+ projectKey: config.projectKey,
20
+ apiUrl: config.apiUrl || DEFAULT_API_URL,
21
+ flushIntervalMs: config.flushIntervalMs || DEFAULT_FLUSH_INTERVAL_MS,
22
+ maxBatchSize: config.maxBatchSize || DEFAULT_MAX_BATCH_SIZE,
23
+ debug: config.debug || false,
24
+ };
25
+ }
26
+ // Singleton collector (Next.js middleware runs in edge runtime, persists across requests)
27
+ let collector = null;
28
+ function getCollector(config) {
29
+ if (!collector) {
30
+ collector = new EventCollector(config);
31
+ }
32
+ return collector;
33
+ }
34
+ /**
35
+ * Next.js middleware for GuardRail runtime monitoring.
36
+ *
37
+ * Usage in middleware.ts:
38
+ * ```ts
39
+ * import { guardrailMiddleware } from 'guardrail-sdk/next'
40
+ *
41
+ * const guardrail = guardrailMiddleware({
42
+ * projectKey: process.env.GUARDRAIL_PROJECT_KEY!,
43
+ * })
44
+ *
45
+ * export async function middleware(request: Request) {
46
+ * const blocked = guardrail(request)
47
+ * if (blocked) return blocked
48
+ * }
49
+ * ```
50
+ *
51
+ * Note: Next.js middleware cannot access response status codes,
52
+ * so auth.login_failed/success events cannot be auto-detected.
53
+ * For auth event tracking, call the GuardRail API directly from
54
+ * your API route handlers.
55
+ */
56
+ export function guardrailMiddleware(config) {
57
+ if (!config.projectKey) {
58
+ console.error("[guardrail-sdk] Missing projectKey. Set GUARDRAIL_PROJECT_KEY environment variable.");
59
+ return function guardrailNoOp() {
60
+ return null;
61
+ };
62
+ }
63
+ const resolved = resolveConfig(config);
64
+ if (resolved.debug) {
65
+ console.error("[guardrail-sdk] Initialized with Next.js middleware");
66
+ }
67
+ /**
68
+ * Call this function inside your Next.js middleware.
69
+ * Returns a Response if the IP is blocked, or null to continue.
70
+ */
71
+ return function guardrail(request) {
72
+ const c = getCollector(resolved);
73
+ const ip = request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ||
74
+ request.headers.get("x-real-ip") ||
75
+ "";
76
+ const userAgent = request.headers.get("user-agent") || "";
77
+ let pathname;
78
+ try {
79
+ pathname = new URL(request.url).pathname;
80
+ }
81
+ catch {
82
+ pathname = request.url;
83
+ }
84
+ // Block denied IPs
85
+ if (c.isBlocked(ip)) {
86
+ return new Response(JSON.stringify({ error: "Access denied" }), {
87
+ status: 403,
88
+ headers: { "Content-Type": "application/json" },
89
+ });
90
+ }
91
+ const now = new Date().toISOString();
92
+ // Track API requests
93
+ c.push({
94
+ type: "api.request",
95
+ timestamp: now,
96
+ ip,
97
+ userAgent,
98
+ metadata: { path: pathname, method: request.method },
99
+ });
100
+ // Track auth endpoints (request-only, since we can't see response status)
101
+ if (isAuthEndpoint(pathname) && request.method === "POST") {
102
+ // We log as a generic auth attempt — the server-side analyzer
103
+ // will use the api.request pattern combined with other signals
104
+ if (resolved.debug) {
105
+ console.error(`[guardrail-sdk] Auth endpoint hit: ${pathname} from ${ip}`);
106
+ }
107
+ }
108
+ return null; // Continue to next middleware
109
+ };
110
+ }
@@ -0,0 +1,35 @@
1
+ export type SdkEventType = "auth.login_failed" | "auth.login_success" | "api.request";
2
+ export interface SdkEvent {
3
+ type: SdkEventType;
4
+ timestamp: string;
5
+ ip: string;
6
+ userAgent: string;
7
+ metadata: Record<string, unknown>;
8
+ }
9
+ export interface GuardrailConfig {
10
+ /** Project key from GuardRail dashboard (gr_sk_...) */
11
+ projectKey: string;
12
+ /** API URL override (default: https://guardrail-seven.vercel.app) */
13
+ apiUrl?: string;
14
+ /** Batch flush interval in ms (default: 30000) */
15
+ flushIntervalMs?: number;
16
+ /** Max events per batch (default: 50) */
17
+ maxBatchSize?: number;
18
+ /** Enable debug logging to stderr (default: false) */
19
+ debug?: boolean;
20
+ }
21
+ export interface ResolvedConfig {
22
+ projectKey: string;
23
+ apiUrl: string;
24
+ flushIntervalMs: number;
25
+ maxBatchSize: number;
26
+ debug: boolean;
27
+ }
28
+ export interface BlockedIpEntry {
29
+ ip: string;
30
+ blockedAt: string;
31
+ }
32
+ export interface FlushResponse {
33
+ received: number;
34
+ blockedIps: BlockedIpEntry[];
35
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "guardrail-sdk",
3
+ "version": "0.1.0",
4
+ "description": "GuardRail runtime security monitoring SDK — detect brute force, suspicious logins, and traffic spikes",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./dist/index.js",
8
+ "./next": "./dist/next.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "dev": "tsc --watch"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^22.0.0",
19
+ "typescript": "^5.7.0"
20
+ }
21
+ }