@simplr-ai/express 1.1.0 → 1.1.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/dist/fastify.cjs CHANGED
@@ -190,10 +190,11 @@ function createClient(config) {
190
190
  "Simplr: no global fetch available \u2014 use Node 18+ or pass `fetch` in options"
191
191
  );
192
192
  }
193
- const logBodies = config.logBodies ?? config.shipNetworkLogs;
193
+ const logSelf = !!config.logSelfCalls;
194
+ const logBodies = config.logBodies ?? (config.shipNetworkLogs && logSelf);
194
195
  const redactFields = config.redactFields;
195
196
  let shipper;
196
- if (config.shipNetworkLogs) {
197
+ if (config.shipNetworkLogs && logSelf) {
197
198
  shipper = new NetworkLogShipper({
198
199
  baseUrl,
199
200
  apiKey: config.apiKey,
@@ -346,7 +347,8 @@ function createGuard(options) {
346
347
  redactFields: options.redactFields,
347
348
  shipNetworkLogs: options.shipNetworkLogs,
348
349
  applicationId: options.applicationId,
349
- environment: options.environment
350
+ environment: options.environment,
351
+ logSelfCalls: options.logSelfCalls
350
352
  });
351
353
  const extract = options.extract ?? defaultExtract;
352
354
  const shouldBlock = normalizeBlock(options.block);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/fastify.ts","../src/network-log.ts","../src/network-shipper.ts","../src/client.ts","../src/core.ts"],"sourcesContent":["/**\n * Fastify adapter. A plugin that registers a `preHandler` hook decorating\n * `request.simplr` and short-circuiting blocked requests with a 403.\n *\n * ```ts\n * import Fastify from \"fastify\";\n * import { simplrFastify } from \"@simplr-ai/express/fastify\";\n * const app = Fastify();\n * await app.register(simplrFastify, { apiKey: process.env.SIMPLR_API_KEY! });\n * ```\n */\nimport { blockEnvelope, createGuard } from \"./core.js\";\nimport type { CheckResult, GuardOptions } from \"./types.js\";\n\ntype FastifyInstance = any;\ntype FastifyRequest = any;\ntype FastifyReply = {\n code(statusCode: number): FastifyReply;\n send(payload: unknown): unknown;\n};\n\n/**\n * Fastify plugin. Register with `app.register(simplrFastify, options)`.\n * Accepts the standard `(fastify, options, done)` plugin signature.\n */\nexport function simplrFastify(\n fastify: FastifyInstance,\n options: GuardOptions,\n done?: (err?: Error) => void,\n): void {\n try {\n const guard = createGuard(options);\n const failOpen = options.failOpen ?? true;\n\n // Decorate so TypeScript/Fastify know about `request.simplr`.\n if (typeof fastify.decorateRequest === \"function\") {\n fastify.decorateRequest(\"simplr\", null);\n }\n\n fastify.addHook(\n \"preHandler\",\n async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const outcome = await guard.run(request);\n request.simplr = outcome.result;\n if (outcome.blocked && outcome.result) {\n reply.code(403).send(blockEnvelope(outcome.result));\n return reply;\n }\n } catch (err) {\n if (failOpen) {\n request.simplr = null;\n return;\n }\n throw err;\n }\n },\n );\n\n done?.();\n } catch (err) {\n if (done) done(err as Error);\n else throw err;\n }\n}\n\n// Mark as a Fastify plugin so `fastify-plugin`-style encapsulation skipping is\n// not required; Fastify reads this property when present.\n(simplrFastify as any)[Symbol.for(\"skip-override\")] = true;\n\n// Consumers can augment `FastifyRequest` with `simplr?: CheckResult | null`\n// themselves; we don't `declare module \"fastify\"` here so the package builds\n// without `fastify` (an optional peer) installed. `CheckResult` is re-exported\n// for that purpose.\nexport type { CheckResult };\n","export type NetworkSource = \"frontend\" | \"backend\";\n\nexport interface NetworkLogEntry {\n id: string;\n requestId?: string;\n source: NetworkSource;\n timestamp: string;\n sdk?: string;\n applicationId?: string;\n environment?: string;\n method: string;\n url: string;\n requestHeaders?: Record<string, string>;\n requestBody?: unknown;\n status?: number;\n statusText?: string;\n responseHeaders?: Record<string, string>;\n responseBody?: unknown;\n durationMs?: number;\n ok?: boolean;\n error?: string;\n}\n\nexport type NetworkLogger = (entry: NetworkLogEntry) => void;\n\nconst SENSITIVE_HEADERS = new Set([\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n \"x-auth-token\",\n \"x-csrf-token\",\n \"x-xsrf-token\",\n]);\n\nconst SENSITIVE_KEY_PARTS = [\n \"password\",\n \"passwd\",\n \"secret\",\n \"token\",\n \"api_key\",\n \"apikey\",\n \"authorization\",\n \"auth\",\n \"credential\",\n \"private_key\",\n \"card\",\n \"cardnumber\",\n \"pan\",\n \"cvv\",\n \"cvc\",\n \"ssn\",\n \"pin\",\n \"otp\",\n];\n\nconst MAX_REDACT_DEPTH = 8;\n\nexport const MAX_BODY_CHARS = 10_000;\n\nfunction isSensitiveKey(key: string, extraKeys: string[]): boolean {\n const lower = key.toLowerCase();\n if (extraKeys.some((k) => lower === k.toLowerCase())) return true;\n return SENSITIVE_KEY_PARTS.some((part) => lower.includes(part));\n}\n\nfunction redactDeep(value: unknown, extraKeys: string[], depth = 0): unknown {\n if (depth >= MAX_REDACT_DEPTH) return \"[truncated]\";\n if (Array.isArray(value)) return value.map((v) => redactDeep(v, extraKeys, depth + 1));\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n out[key] = isSensitiveKey(key, extraKeys)\n ? \"[REDACTED]\"\n : redactDeep(val, extraKeys, depth + 1);\n }\n return out;\n }\n return value;\n}\n\nexport function redactHeaders(\n headers: Record<string, string> | Headers | undefined,\n): Record<string, string> | undefined {\n if (!headers) return undefined;\n const out: Record<string, string> = {};\n const set = (key: string, value: string) => {\n out[key] = SENSITIVE_HEADERS.has(key.toLowerCase()) ? \"[REDACTED]\" : value;\n };\n if (typeof (headers as Headers).forEach === \"function\" && !Array.isArray(headers)) {\n (headers as Headers).forEach((value, key) => set(key, value));\n } else {\n for (const [key, value] of Object.entries(headers as Record<string, string>)) {\n set(key, String(value));\n }\n }\n return out;\n}\n\nexport function previewBody(raw: unknown, redactFields: string[] = []): unknown {\n if (raw === undefined || raw === null) return undefined;\n let value: unknown = raw;\n if (typeof raw === \"string\") {\n try {\n value = JSON.parse(raw);\n } catch {\n return raw.length > MAX_BODY_CHARS ? raw.slice(0, MAX_BODY_CHARS) + \"…[truncated]\" : raw;\n }\n }\n const redacted = redactDeep(value, redactFields);\n let text: string;\n try {\n text = JSON.stringify(redacted);\n } catch {\n return \"[unserializable]\";\n }\n if (text.length > MAX_BODY_CHARS) {\n return text.slice(0, MAX_BODY_CHARS) + \"…[truncated]\";\n }\n return redacted;\n}\n\nexport function newLogId(): string {\n const uuid = (globalThis as any)?.crypto?.randomUUID;\n if (typeof uuid === \"function\") return uuid.call((globalThis as any).crypto);\n return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;\n}\n","import type { NetworkLogEntry } from \"./network-log.js\";\n\nexport interface ShipperConfig {\n baseUrl: string;\n apiKey: string;\n fetchImpl: typeof fetch;\n sdk: string;\n applicationId?: string;\n environment?: string;\n batchSize?: number;\n flushIntervalMs?: number;\n}\n\nconst DEFAULT_BATCH = 25;\nconst DEFAULT_FLUSH_MS = 5000;\n\nexport class NetworkLogShipper {\n private readonly cfg: ShipperConfig;\n private queue: NetworkLogEntry[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(cfg: ShipperConfig) {\n this.cfg = cfg;\n }\n\n start(): void {\n if (this.timer) return;\n const interval = this.cfg.flushIntervalMs ?? DEFAULT_FLUSH_MS;\n this.timer = setInterval(() => {\n void this.flush();\n }, interval);\n (this.timer as any)?.unref?.();\n }\n\n add(entry: NetworkLogEntry): void {\n this.queue.push({\n ...entry,\n sdk: this.cfg.sdk,\n applicationId: entry.applicationId ?? this.cfg.applicationId,\n environment: entry.environment ?? this.cfg.environment,\n });\n if (this.queue.length >= (this.cfg.batchSize ?? DEFAULT_BATCH)) {\n void this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n const logs = this.queue;\n this.queue = [];\n try {\n await this.cfg.fetchImpl(`${this.cfg.baseUrl}/v1/network-logs`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.cfg.apiKey,\n },\n body: JSON.stringify({ logs }),\n });\n } catch {\n /* empty */\n }\n }\n\n stop(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n void this.flush();\n }\n}\n","/**\n * Thin internal client.\n *\n * This is a ~40-line port of the minimal call path from `@simplr-ai/node`\n * (`apiRequest` + `check` + `edge.ingestLogs`) so that this package builds and\n * tests without an unbuilt workspace dependency or any network access.\n *\n * In production this package pairs with `@simplr-ai/node`; the endpoints, auth\n * header (`X-API-Key`), `{ success, message, content }` envelope unwrapping, and\n * 15s default timeout here are byte-for-byte compatible with that SDK and the\n * Simplr API contract.\n */\nimport type { CheckInput, CheckResult, EdgeLogEntry } from \"./types.js\";\nimport {\n newLogId,\n previewBody,\n redactHeaders,\n type NetworkLogger,\n} from \"./network-log.js\";\nimport { NetworkLogShipper } from \"./network-shipper.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.simplr.sh\";\n\n/** Thrown when the Simplr API returns a non-2xx response or the request fails. */\nexport class SimplrError extends Error {\n readonly status: number;\n readonly body: unknown;\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"SimplrError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport interface ClientConfig {\n apiKey: string;\n baseUrl?: string;\n timeoutMs?: number;\n fetch?: typeof fetch;\n onNetworkLog?: NetworkLogger;\n logBodies?: boolean;\n redactFields?: string[];\n shipNetworkLogs?: boolean;\n applicationId?: string;\n environment?: string;\n}\n\nexport interface SimplrClient {\n check(input: CheckInput): Promise<CheckResult>;\n ingestLogs(deviceId: string, logs: EdgeLogEntry[]): Promise<unknown>;\n flushNetworkLogs(): Promise<void>;\n close(): void;\n}\n\nexport function createClient(config: ClientConfig): SimplrClient {\n if (!config?.apiKey) throw new Error(\"Simplr: `apiKey` is required\");\n const baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const timeoutMs = config.timeoutMs ?? 15000;\n const fetchImpl = config.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new Error(\n \"Simplr: no global fetch available — use Node 18+ or pass `fetch` in options\",\n );\n }\n\n const logBodies = config.logBodies ?? config.shipNetworkLogs;\n const redactFields = config.redactFields;\n\n let shipper: NetworkLogShipper | undefined;\n if (config.shipNetworkLogs) {\n shipper = new NetworkLogShipper({\n baseUrl,\n apiKey: config.apiKey,\n fetchImpl,\n sdk: \"express\",\n applicationId: config.applicationId,\n environment: config.environment,\n });\n shipper.start();\n }\n\n const userLog = config.onNetworkLog;\n const onNetworkLog: NetworkLogger | undefined =\n shipper || userLog\n ? (entry) => {\n shipper?.add(entry);\n userLog?.(entry);\n }\n : undefined;\n\n async function apiRequest<T>(method: string, path: string, body?: unknown): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n const url = `${baseUrl}${path}`;\n const requestHeaders = { \"Content-Type\": \"application/json\", \"X-API-Key\": config.apiKey };\n const startedAt = Date.now();\n const log = onNetworkLog\n ? {\n id: newLogId(),\n source: \"backend\" as const,\n timestamp: new Date(startedAt).toISOString(),\n method,\n url,\n requestHeaders: redactHeaders(requestHeaders),\n requestBody: logBodies ? previewBody(body, redactFields) : undefined,\n }\n : null;\n\n const emit = (extra: Record<string, unknown>) => {\n if (!log || !onNetworkLog) return;\n try {\n onNetworkLog({ ...log, durationMs: Date.now() - startedAt, ...extra });\n } catch {\n /* empty */\n }\n };\n\n try {\n const res = await fetchImpl(url, {\n method,\n headers: requestHeaders,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n const text = await res.text();\n let parsed: any;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n parsed = text;\n }\n\n emit({\n status: res.status,\n statusText: res.statusText,\n ok: res.ok,\n responseHeaders: redactHeaders(res.headers),\n responseBody: logBodies ? previewBody(parsed ?? text, redactFields) : undefined,\n });\n\n if (!res.ok) {\n const message =\n (parsed && (parsed.message || parsed.error)) || `Simplr API error ${res.status}`;\n throw new SimplrError(message, res.status, parsed);\n }\n return (parsed && typeof parsed === \"object\" && \"content\" in parsed\n ? parsed.content\n : parsed) as T;\n } catch (err) {\n if (err instanceof SimplrError) throw err;\n if (err instanceof Error && err.name === \"AbortError\") {\n emit({ ok: false, error: `timed out after ${timeoutMs}ms` });\n throw new SimplrError(`Request to ${path} timed out after ${timeoutMs}ms`, 0, null);\n }\n emit({ ok: false, error: err instanceof Error ? err.message : \"Network error\" });\n throw new SimplrError(err instanceof Error ? err.message : \"Network error\", 0, null);\n } finally {\n clearTimeout(timer);\n }\n }\n\n return {\n check: (input) => apiRequest<CheckResult>(\"POST\", \"/v1/check\", input),\n ingestLogs: (deviceId, logs) =>\n apiRequest(\"POST\", \"/v1/edge/logs\", { device_id: deviceId, logs }),\n flushNetworkLogs: () => shipper?.flush() ?? Promise.resolve(),\n close: () => shipper?.stop(),\n };\n}\n","/**\n * Framework-agnostic guard engine.\n *\n * `createGuard` returns an object that, given any request-like object, builds a\n * `CheckInput`, calls `/v1/check`, decides whether to block, optionally ships an\n * edge log, and fails open on error. Framework adapters (express/fastify/hono/\n * next) are thin wrappers over this engine.\n */\nimport { createClient, type SimplrClient } from \"./client.js\";\nimport type {\n BlockThreshold,\n CheckInput,\n CheckResult,\n GuardContext,\n GuardOptions,\n GuardOutcome,\n RiskLevel,\n} from \"./types.js\";\n\nconst RISK_ORDER: Record<RiskLevel, number> = {\n low: 0,\n medium: 1,\n high: 2,\n critical: 3,\n};\n\nconst DEFAULT_DEVICE_ID = \"simplr-edge-middleware\";\nconst DEFAULT_THRESHOLD: RiskLevel = \"high\";\n\n/** Numeric rank of a risk level. Unknown levels rank as the lowest (0). */\nexport function riskRank(level: RiskLevel | string | undefined): number {\n return RISK_ORDER[level as RiskLevel] ?? 0;\n}\n\n/** True when `level` is at least `min` in the low<medium<high<critical ordering. */\nexport function riskAtLeast(level: RiskLevel | string | undefined, min: RiskLevel): boolean {\n return riskRank(level) >= riskRank(min);\n}\n\n/** Read a header from either a plain bag (lowercase keys) or a Headers-like getter. */\nfunction readHeader(headers: unknown, name: string): string | undefined {\n if (!headers) return undefined;\n const h = headers as any;\n if (typeof h.get === \"function\") {\n const v = h.get(name);\n return v == null ? undefined : String(v);\n }\n // Plain object: try the lowercase key, then a case-insensitive scan.\n const lower = name.toLowerCase();\n if (lower in h && h[lower] != null) return String(h[lower]);\n for (const key of Object.keys(h)) {\n if (key.toLowerCase() === lower && h[key] != null) return String(h[key]);\n }\n return undefined;\n}\n\n/** Best-effort client IP from common header and socket locations. */\nfunction extractIp(req: any): string | undefined {\n const fwd = readHeader(req?.headers, \"x-forwarded-for\");\n if (fwd) return fwd.split(\",\")[0]!.trim();\n const real = readHeader(req?.headers, \"x-real-ip\");\n if (real) return real;\n return (\n req?.ip ||\n req?.socket?.remoteAddress ||\n req?.connection?.remoteAddress ||\n undefined\n );\n}\n\n/**\n * Default request → CheckInput extractor. Pulls the client IP and user-agent into\n * `device`, and lifts `email`/`phone` from a parsed JSON body when present.\n */\nexport function defaultExtract(req: any): CheckInput {\n const input: CheckInput = {};\n\n const device: Record<string, unknown> = {};\n const ip = extractIp(req);\n if (ip) device.ip = ip;\n const ua = readHeader(req?.headers, \"user-agent\");\n if (ua) device.user_agent = ua;\n if (Object.keys(device).length > 0) input.device = device;\n\n const body = req?.body;\n if (body && typeof body === \"object\") {\n const b = body as Record<string, unknown>;\n if (typeof b.email === \"string\") input.email = b.email;\n if (typeof b.phone === \"string\") input.phone = b.phone;\n }\n\n return input;\n}\n\nfunction normalizeBlock(\n block: GuardOptions[\"block\"],\n): (result: CheckResult) => boolean {\n if (typeof block === \"function\") return block;\n const threshold = (block as BlockThreshold | undefined)?.whenRiskAtLeast ?? DEFAULT_THRESHOLD;\n return (result) => riskAtLeast(result.risk_level, threshold);\n}\n\nexport interface Guard<Req = any> {\n /** The underlying thin Simplr client (check + edge log ingestion). */\n readonly client: SimplrClient;\n /** Run the full engine against a request; never throws when `failOpen`. */\n run(req: Req): Promise<GuardOutcome>;\n}\n\nexport function createGuard<Req = any>(options: GuardOptions<Req>): Guard<Req> {\n if (!options?.apiKey) throw new Error(\"createGuard: `apiKey` is required\");\n\n const client = createClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n timeoutMs: options.timeoutMs,\n fetch: options.fetch,\n onNetworkLog: options.onNetworkLog,\n logBodies: options.logBodies,\n redactFields: options.redactFields,\n shipNetworkLogs: options.shipNetworkLogs,\n applicationId: options.applicationId,\n environment: options.environment,\n });\n\n const extract = options.extract ?? defaultExtract;\n const shouldBlock = normalizeBlock(options.block);\n const failOpen = options.failOpen ?? true;\n const ingestLogs = options.ingestLogs ?? false;\n const deviceId = options.deviceId ?? DEFAULT_DEVICE_ID;\n\n async function run(req: Req): Promise<GuardOutcome> {\n const input = await extract(req);\n let result: CheckResult;\n try {\n result = await client.check(input);\n } catch (error) {\n if (failOpen) {\n return { result: null, blocked: false, input, error };\n }\n throw error;\n }\n\n const blocked = shouldBlock(result);\n const ctx: GuardContext<Req> = { req, input, blocked };\n\n if (options.onResult) {\n try {\n await options.onResult(result, ctx);\n } catch {\n // onResult is observational; never let it break the request.\n }\n }\n\n if (ingestLogs) {\n try {\n await client.ingestLogs(deviceId, [\n {\n category: \"security\",\n level: blocked ? \"warn\" : \"info\",\n message: blocked ? \"simplr guard blocked request\" : \"simplr guard checked request\",\n risk_level: result.risk_level,\n risk_score: result.risk_score,\n },\n ]);\n } catch {\n // Log shipping is best-effort and must never affect the request.\n }\n }\n\n return { result, blocked, input };\n }\n\n return { client, run };\n}\n\n/** Standard JSON envelope returned to the client on a blocked request. */\nexport function blockEnvelope(result: CheckResult) {\n return {\n error: \"request_blocked\",\n message: \"This request was blocked by Simplr fraud protection.\",\n risk_level: result.risk_level,\n risk_score: result.risk_score,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/fastify.ts","../src/network-log.ts","../src/network-shipper.ts","../src/client.ts","../src/core.ts"],"sourcesContent":["/**\n * Fastify adapter. A plugin that registers a `preHandler` hook decorating\n * `request.simplr` and short-circuiting blocked requests with a 403.\n *\n * ```ts\n * import Fastify from \"fastify\";\n * import { simplrFastify } from \"@simplr-ai/express/fastify\";\n * const app = Fastify();\n * await app.register(simplrFastify, { apiKey: process.env.SIMPLR_API_KEY! });\n * ```\n */\nimport { blockEnvelope, createGuard } from \"./core.js\";\nimport type { CheckResult, GuardOptions } from \"./types.js\";\n\ntype FastifyInstance = any;\ntype FastifyRequest = any;\ntype FastifyReply = {\n code(statusCode: number): FastifyReply;\n send(payload: unknown): unknown;\n};\n\n/**\n * Fastify plugin. Register with `app.register(simplrFastify, options)`.\n * Accepts the standard `(fastify, options, done)` plugin signature.\n */\nexport function simplrFastify(\n fastify: FastifyInstance,\n options: GuardOptions,\n done?: (err?: Error) => void,\n): void {\n try {\n const guard = createGuard(options);\n const failOpen = options.failOpen ?? true;\n\n // Decorate so TypeScript/Fastify know about `request.simplr`.\n if (typeof fastify.decorateRequest === \"function\") {\n fastify.decorateRequest(\"simplr\", null);\n }\n\n fastify.addHook(\n \"preHandler\",\n async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const outcome = await guard.run(request);\n request.simplr = outcome.result;\n if (outcome.blocked && outcome.result) {\n reply.code(403).send(blockEnvelope(outcome.result));\n return reply;\n }\n } catch (err) {\n if (failOpen) {\n request.simplr = null;\n return;\n }\n throw err;\n }\n },\n );\n\n done?.();\n } catch (err) {\n if (done) done(err as Error);\n else throw err;\n }\n}\n\n// Mark as a Fastify plugin so `fastify-plugin`-style encapsulation skipping is\n// not required; Fastify reads this property when present.\n(simplrFastify as any)[Symbol.for(\"skip-override\")] = true;\n\n// Consumers can augment `FastifyRequest` with `simplr?: CheckResult | null`\n// themselves; we don't `declare module \"fastify\"` here so the package builds\n// without `fastify` (an optional peer) installed. `CheckResult` is re-exported\n// for that purpose.\nexport type { CheckResult };\n","export type NetworkSource = \"frontend\" | \"backend\";\n\nexport interface NetworkLogEntry {\n id: string;\n requestId?: string;\n source: NetworkSource;\n timestamp: string;\n sdk?: string;\n applicationId?: string;\n environment?: string;\n method: string;\n url: string;\n requestHeaders?: Record<string, string>;\n requestBody?: unknown;\n status?: number;\n statusText?: string;\n responseHeaders?: Record<string, string>;\n responseBody?: unknown;\n durationMs?: number;\n ok?: boolean;\n error?: string;\n}\n\nexport type NetworkLogger = (entry: NetworkLogEntry) => void;\n\nconst SENSITIVE_HEADERS = new Set([\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n \"x-auth-token\",\n \"x-csrf-token\",\n \"x-xsrf-token\",\n]);\n\nconst SENSITIVE_KEY_PARTS = [\n \"password\",\n \"passwd\",\n \"secret\",\n \"token\",\n \"api_key\",\n \"apikey\",\n \"authorization\",\n \"auth\",\n \"credential\",\n \"private_key\",\n \"card\",\n \"cardnumber\",\n \"pan\",\n \"cvv\",\n \"cvc\",\n \"ssn\",\n \"pin\",\n \"otp\",\n];\n\nconst MAX_REDACT_DEPTH = 8;\n\nexport const MAX_BODY_CHARS = 10_000;\n\nfunction isSensitiveKey(key: string, extraKeys: string[]): boolean {\n const lower = key.toLowerCase();\n if (extraKeys.some((k) => lower === k.toLowerCase())) return true;\n return SENSITIVE_KEY_PARTS.some((part) => lower.includes(part));\n}\n\nfunction redactDeep(value: unknown, extraKeys: string[], depth = 0): unknown {\n if (depth >= MAX_REDACT_DEPTH) return \"[truncated]\";\n if (Array.isArray(value)) return value.map((v) => redactDeep(v, extraKeys, depth + 1));\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n out[key] = isSensitiveKey(key, extraKeys)\n ? \"[REDACTED]\"\n : redactDeep(val, extraKeys, depth + 1);\n }\n return out;\n }\n return value;\n}\n\nexport function redactHeaders(\n headers: Record<string, string> | Headers | undefined,\n): Record<string, string> | undefined {\n if (!headers) return undefined;\n const out: Record<string, string> = {};\n const set = (key: string, value: string) => {\n out[key] = SENSITIVE_HEADERS.has(key.toLowerCase()) ? \"[REDACTED]\" : value;\n };\n if (typeof (headers as Headers).forEach === \"function\" && !Array.isArray(headers)) {\n (headers as Headers).forEach((value, key) => set(key, value));\n } else {\n for (const [key, value] of Object.entries(headers as Record<string, string>)) {\n set(key, String(value));\n }\n }\n return out;\n}\n\nexport function previewBody(raw: unknown, redactFields: string[] = []): unknown {\n if (raw === undefined || raw === null) return undefined;\n let value: unknown = raw;\n if (typeof raw === \"string\") {\n try {\n value = JSON.parse(raw);\n } catch {\n return raw.length > MAX_BODY_CHARS ? raw.slice(0, MAX_BODY_CHARS) + \"…[truncated]\" : raw;\n }\n }\n const redacted = redactDeep(value, redactFields);\n let text: string;\n try {\n text = JSON.stringify(redacted);\n } catch {\n return \"[unserializable]\";\n }\n if (text.length > MAX_BODY_CHARS) {\n return text.slice(0, MAX_BODY_CHARS) + \"…[truncated]\";\n }\n return redacted;\n}\n\nexport function newLogId(): string {\n const uuid = (globalThis as any)?.crypto?.randomUUID;\n if (typeof uuid === \"function\") return uuid.call((globalThis as any).crypto);\n return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;\n}\n","import type { NetworkLogEntry } from \"./network-log.js\";\n\nexport interface ShipperConfig {\n baseUrl: string;\n apiKey: string;\n fetchImpl: typeof fetch;\n sdk: string;\n applicationId?: string;\n environment?: string;\n batchSize?: number;\n flushIntervalMs?: number;\n}\n\nconst DEFAULT_BATCH = 25;\nconst DEFAULT_FLUSH_MS = 5000;\n\nexport class NetworkLogShipper {\n private readonly cfg: ShipperConfig;\n private queue: NetworkLogEntry[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(cfg: ShipperConfig) {\n this.cfg = cfg;\n }\n\n start(): void {\n if (this.timer) return;\n const interval = this.cfg.flushIntervalMs ?? DEFAULT_FLUSH_MS;\n this.timer = setInterval(() => {\n void this.flush();\n }, interval);\n (this.timer as any)?.unref?.();\n }\n\n add(entry: NetworkLogEntry): void {\n this.queue.push({\n ...entry,\n sdk: this.cfg.sdk,\n applicationId: entry.applicationId ?? this.cfg.applicationId,\n environment: entry.environment ?? this.cfg.environment,\n });\n if (this.queue.length >= (this.cfg.batchSize ?? DEFAULT_BATCH)) {\n void this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n const logs = this.queue;\n this.queue = [];\n try {\n await this.cfg.fetchImpl(`${this.cfg.baseUrl}/v1/network-logs`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.cfg.apiKey,\n },\n body: JSON.stringify({ logs }),\n });\n } catch {\n /* empty */\n }\n }\n\n stop(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n void this.flush();\n }\n}\n","/**\n * Thin internal client.\n *\n * This is a ~40-line port of the minimal call path from `@simplr-ai/node`\n * (`apiRequest` + `check` + `edge.ingestLogs`) so that this package builds and\n * tests without an unbuilt workspace dependency or any network access.\n *\n * In production this package pairs with `@simplr-ai/node`; the endpoints, auth\n * header (`X-API-Key`), `{ success, message, content }` envelope unwrapping, and\n * 15s default timeout here are byte-for-byte compatible with that SDK and the\n * Simplr API contract.\n */\nimport type { CheckInput, CheckResult, EdgeLogEntry } from \"./types.js\";\nimport {\n newLogId,\n previewBody,\n redactHeaders,\n type NetworkLogger,\n} from \"./network-log.js\";\nimport { NetworkLogShipper } from \"./network-shipper.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.simplr.sh\";\n\n/** Thrown when the Simplr API returns a non-2xx response or the request fails. */\nexport class SimplrError extends Error {\n readonly status: number;\n readonly body: unknown;\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"SimplrError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport interface ClientConfig {\n apiKey: string;\n baseUrl?: string;\n timeoutMs?: number;\n fetch?: typeof fetch;\n onNetworkLog?: NetworkLogger;\n logBodies?: boolean;\n redactFields?: string[];\n shipNetworkLogs?: boolean;\n applicationId?: string;\n environment?: string;\n logSelfCalls?: boolean;\n}\n\nexport interface SimplrClient {\n check(input: CheckInput): Promise<CheckResult>;\n ingestLogs(deviceId: string, logs: EdgeLogEntry[]): Promise<unknown>;\n flushNetworkLogs(): Promise<void>;\n close(): void;\n}\n\nexport function createClient(config: ClientConfig): SimplrClient {\n if (!config?.apiKey) throw new Error(\"Simplr: `apiKey` is required\");\n const baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const timeoutMs = config.timeoutMs ?? 15000;\n const fetchImpl = config.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new Error(\n \"Simplr: no global fetch available — use Node 18+ or pass `fetch` in options\",\n );\n }\n\n const logSelf = !!config.logSelfCalls;\n const logBodies = config.logBodies ?? (config.shipNetworkLogs && logSelf);\n const redactFields = config.redactFields;\n\n let shipper: NetworkLogShipper | undefined;\n if (config.shipNetworkLogs && logSelf) {\n shipper = new NetworkLogShipper({\n baseUrl,\n apiKey: config.apiKey,\n fetchImpl,\n sdk: \"express\",\n applicationId: config.applicationId,\n environment: config.environment,\n });\n shipper.start();\n }\n\n const userLog = config.onNetworkLog;\n const onNetworkLog: NetworkLogger | undefined =\n shipper || userLog\n ? (entry) => {\n shipper?.add(entry);\n userLog?.(entry);\n }\n : undefined;\n\n async function apiRequest<T>(method: string, path: string, body?: unknown): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n const url = `${baseUrl}${path}`;\n const requestHeaders = { \"Content-Type\": \"application/json\", \"X-API-Key\": config.apiKey };\n const startedAt = Date.now();\n const log = onNetworkLog\n ? {\n id: newLogId(),\n source: \"backend\" as const,\n timestamp: new Date(startedAt).toISOString(),\n method,\n url,\n requestHeaders: redactHeaders(requestHeaders),\n requestBody: logBodies ? previewBody(body, redactFields) : undefined,\n }\n : null;\n\n const emit = (extra: Record<string, unknown>) => {\n if (!log || !onNetworkLog) return;\n try {\n onNetworkLog({ ...log, durationMs: Date.now() - startedAt, ...extra });\n } catch {\n /* empty */\n }\n };\n\n try {\n const res = await fetchImpl(url, {\n method,\n headers: requestHeaders,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n const text = await res.text();\n let parsed: any;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n parsed = text;\n }\n\n emit({\n status: res.status,\n statusText: res.statusText,\n ok: res.ok,\n responseHeaders: redactHeaders(res.headers),\n responseBody: logBodies ? previewBody(parsed ?? text, redactFields) : undefined,\n });\n\n if (!res.ok) {\n const message =\n (parsed && (parsed.message || parsed.error)) || `Simplr API error ${res.status}`;\n throw new SimplrError(message, res.status, parsed);\n }\n return (parsed && typeof parsed === \"object\" && \"content\" in parsed\n ? parsed.content\n : parsed) as T;\n } catch (err) {\n if (err instanceof SimplrError) throw err;\n if (err instanceof Error && err.name === \"AbortError\") {\n emit({ ok: false, error: `timed out after ${timeoutMs}ms` });\n throw new SimplrError(`Request to ${path} timed out after ${timeoutMs}ms`, 0, null);\n }\n emit({ ok: false, error: err instanceof Error ? err.message : \"Network error\" });\n throw new SimplrError(err instanceof Error ? err.message : \"Network error\", 0, null);\n } finally {\n clearTimeout(timer);\n }\n }\n\n return {\n check: (input) => apiRequest<CheckResult>(\"POST\", \"/v1/check\", input),\n ingestLogs: (deviceId, logs) =>\n apiRequest(\"POST\", \"/v1/edge/logs\", { device_id: deviceId, logs }),\n flushNetworkLogs: () => shipper?.flush() ?? Promise.resolve(),\n close: () => shipper?.stop(),\n };\n}\n","/**\n * Framework-agnostic guard engine.\n *\n * `createGuard` returns an object that, given any request-like object, builds a\n * `CheckInput`, calls `/v1/check`, decides whether to block, optionally ships an\n * edge log, and fails open on error. Framework adapters (express/fastify/hono/\n * next) are thin wrappers over this engine.\n */\nimport { createClient, type SimplrClient } from \"./client.js\";\nimport type {\n BlockThreshold,\n CheckInput,\n CheckResult,\n GuardContext,\n GuardOptions,\n GuardOutcome,\n RiskLevel,\n} from \"./types.js\";\n\nconst RISK_ORDER: Record<RiskLevel, number> = {\n low: 0,\n medium: 1,\n high: 2,\n critical: 3,\n};\n\nconst DEFAULT_DEVICE_ID = \"simplr-edge-middleware\";\nconst DEFAULT_THRESHOLD: RiskLevel = \"high\";\n\n/** Numeric rank of a risk level. Unknown levels rank as the lowest (0). */\nexport function riskRank(level: RiskLevel | string | undefined): number {\n return RISK_ORDER[level as RiskLevel] ?? 0;\n}\n\n/** True when `level` is at least `min` in the low<medium<high<critical ordering. */\nexport function riskAtLeast(level: RiskLevel | string | undefined, min: RiskLevel): boolean {\n return riskRank(level) >= riskRank(min);\n}\n\n/** Read a header from either a plain bag (lowercase keys) or a Headers-like getter. */\nfunction readHeader(headers: unknown, name: string): string | undefined {\n if (!headers) return undefined;\n const h = headers as any;\n if (typeof h.get === \"function\") {\n const v = h.get(name);\n return v == null ? undefined : String(v);\n }\n // Plain object: try the lowercase key, then a case-insensitive scan.\n const lower = name.toLowerCase();\n if (lower in h && h[lower] != null) return String(h[lower]);\n for (const key of Object.keys(h)) {\n if (key.toLowerCase() === lower && h[key] != null) return String(h[key]);\n }\n return undefined;\n}\n\n/** Best-effort client IP from common header and socket locations. */\nfunction extractIp(req: any): string | undefined {\n const fwd = readHeader(req?.headers, \"x-forwarded-for\");\n if (fwd) return fwd.split(\",\")[0]!.trim();\n const real = readHeader(req?.headers, \"x-real-ip\");\n if (real) return real;\n return (\n req?.ip ||\n req?.socket?.remoteAddress ||\n req?.connection?.remoteAddress ||\n undefined\n );\n}\n\n/**\n * Default request → CheckInput extractor. Pulls the client IP and user-agent into\n * `device`, and lifts `email`/`phone` from a parsed JSON body when present.\n */\nexport function defaultExtract(req: any): CheckInput {\n const input: CheckInput = {};\n\n const device: Record<string, unknown> = {};\n const ip = extractIp(req);\n if (ip) device.ip = ip;\n const ua = readHeader(req?.headers, \"user-agent\");\n if (ua) device.user_agent = ua;\n if (Object.keys(device).length > 0) input.device = device;\n\n const body = req?.body;\n if (body && typeof body === \"object\") {\n const b = body as Record<string, unknown>;\n if (typeof b.email === \"string\") input.email = b.email;\n if (typeof b.phone === \"string\") input.phone = b.phone;\n }\n\n return input;\n}\n\nfunction normalizeBlock(\n block: GuardOptions[\"block\"],\n): (result: CheckResult) => boolean {\n if (typeof block === \"function\") return block;\n const threshold = (block as BlockThreshold | undefined)?.whenRiskAtLeast ?? DEFAULT_THRESHOLD;\n return (result) => riskAtLeast(result.risk_level, threshold);\n}\n\nexport interface Guard<Req = any> {\n /** The underlying thin Simplr client (check + edge log ingestion). */\n readonly client: SimplrClient;\n /** Run the full engine against a request; never throws when `failOpen`. */\n run(req: Req): Promise<GuardOutcome>;\n}\n\nexport function createGuard<Req = any>(options: GuardOptions<Req>): Guard<Req> {\n if (!options?.apiKey) throw new Error(\"createGuard: `apiKey` is required\");\n\n const client = createClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n timeoutMs: options.timeoutMs,\n fetch: options.fetch,\n onNetworkLog: options.onNetworkLog,\n logBodies: options.logBodies,\n redactFields: options.redactFields,\n shipNetworkLogs: options.shipNetworkLogs,\n applicationId: options.applicationId,\n environment: options.environment,\n logSelfCalls: options.logSelfCalls,\n });\n\n const extract = options.extract ?? defaultExtract;\n const shouldBlock = normalizeBlock(options.block);\n const failOpen = options.failOpen ?? true;\n const ingestLogs = options.ingestLogs ?? false;\n const deviceId = options.deviceId ?? DEFAULT_DEVICE_ID;\n\n async function run(req: Req): Promise<GuardOutcome> {\n const input = await extract(req);\n let result: CheckResult;\n try {\n result = await client.check(input);\n } catch (error) {\n if (failOpen) {\n return { result: null, blocked: false, input, error };\n }\n throw error;\n }\n\n const blocked = shouldBlock(result);\n const ctx: GuardContext<Req> = { req, input, blocked };\n\n if (options.onResult) {\n try {\n await options.onResult(result, ctx);\n } catch {\n // onResult is observational; never let it break the request.\n }\n }\n\n if (ingestLogs) {\n try {\n await client.ingestLogs(deviceId, [\n {\n category: \"security\",\n level: blocked ? \"warn\" : \"info\",\n message: blocked ? \"simplr guard blocked request\" : \"simplr guard checked request\",\n risk_level: result.risk_level,\n risk_score: result.risk_score,\n },\n ]);\n } catch {\n // Log shipping is best-effort and must never affect the request.\n }\n }\n\n return { result, blocked, input };\n }\n\n return { client, run };\n}\n\n/** Standard JSON envelope returned to the client on a blocked request. */\nexport function blockEnvelope(result: CheckResult) {\n return {\n error: \"request_blocked\",\n message: \"This request was blocked by Simplr fraud protection.\",\n risk_level: result.risk_level,\n risk_score: result.risk_score,\n };\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
- import { G as GuardOptions } from './types-YIpI-xG_.cjs';
2
- export { C as CheckResult } from './types-YIpI-xG_.cjs';
1
+ import { G as GuardOptions } from './types-fP1HbtSf.cjs';
2
+ export { C as CheckResult } from './types-fP1HbtSf.cjs';
3
3
 
4
4
  type FastifyInstance = any;
5
5
  /**
package/dist/fastify.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { G as GuardOptions } from './types-YIpI-xG_.js';
2
- export { C as CheckResult } from './types-YIpI-xG_.js';
1
+ import { G as GuardOptions } from './types-fP1HbtSf.js';
2
+ export { C as CheckResult } from './types-fP1HbtSf.js';
3
3
 
4
4
  type FastifyInstance = any;
5
5
  /**
package/dist/fastify.js CHANGED
@@ -164,10 +164,11 @@ function createClient(config) {
164
164
  "Simplr: no global fetch available \u2014 use Node 18+ or pass `fetch` in options"
165
165
  );
166
166
  }
167
- const logBodies = config.logBodies ?? config.shipNetworkLogs;
167
+ const logSelf = !!config.logSelfCalls;
168
+ const logBodies = config.logBodies ?? (config.shipNetworkLogs && logSelf);
168
169
  const redactFields = config.redactFields;
169
170
  let shipper;
170
- if (config.shipNetworkLogs) {
171
+ if (config.shipNetworkLogs && logSelf) {
171
172
  shipper = new NetworkLogShipper({
172
173
  baseUrl,
173
174
  apiKey: config.apiKey,
@@ -320,7 +321,8 @@ function createGuard(options) {
320
321
  redactFields: options.redactFields,
321
322
  shipNetworkLogs: options.shipNetworkLogs,
322
323
  applicationId: options.applicationId,
323
- environment: options.environment
324
+ environment: options.environment,
325
+ logSelfCalls: options.logSelfCalls
324
326
  });
325
327
  const extract = options.extract ?? defaultExtract;
326
328
  const shouldBlock = normalizeBlock(options.block);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/network-log.ts","../src/network-shipper.ts","../src/client.ts","../src/core.ts","../src/fastify.ts"],"sourcesContent":["export type NetworkSource = \"frontend\" | \"backend\";\n\nexport interface NetworkLogEntry {\n id: string;\n requestId?: string;\n source: NetworkSource;\n timestamp: string;\n sdk?: string;\n applicationId?: string;\n environment?: string;\n method: string;\n url: string;\n requestHeaders?: Record<string, string>;\n requestBody?: unknown;\n status?: number;\n statusText?: string;\n responseHeaders?: Record<string, string>;\n responseBody?: unknown;\n durationMs?: number;\n ok?: boolean;\n error?: string;\n}\n\nexport type NetworkLogger = (entry: NetworkLogEntry) => void;\n\nconst SENSITIVE_HEADERS = new Set([\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n \"x-auth-token\",\n \"x-csrf-token\",\n \"x-xsrf-token\",\n]);\n\nconst SENSITIVE_KEY_PARTS = [\n \"password\",\n \"passwd\",\n \"secret\",\n \"token\",\n \"api_key\",\n \"apikey\",\n \"authorization\",\n \"auth\",\n \"credential\",\n \"private_key\",\n \"card\",\n \"cardnumber\",\n \"pan\",\n \"cvv\",\n \"cvc\",\n \"ssn\",\n \"pin\",\n \"otp\",\n];\n\nconst MAX_REDACT_DEPTH = 8;\n\nexport const MAX_BODY_CHARS = 10_000;\n\nfunction isSensitiveKey(key: string, extraKeys: string[]): boolean {\n const lower = key.toLowerCase();\n if (extraKeys.some((k) => lower === k.toLowerCase())) return true;\n return SENSITIVE_KEY_PARTS.some((part) => lower.includes(part));\n}\n\nfunction redactDeep(value: unknown, extraKeys: string[], depth = 0): unknown {\n if (depth >= MAX_REDACT_DEPTH) return \"[truncated]\";\n if (Array.isArray(value)) return value.map((v) => redactDeep(v, extraKeys, depth + 1));\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n out[key] = isSensitiveKey(key, extraKeys)\n ? \"[REDACTED]\"\n : redactDeep(val, extraKeys, depth + 1);\n }\n return out;\n }\n return value;\n}\n\nexport function redactHeaders(\n headers: Record<string, string> | Headers | undefined,\n): Record<string, string> | undefined {\n if (!headers) return undefined;\n const out: Record<string, string> = {};\n const set = (key: string, value: string) => {\n out[key] = SENSITIVE_HEADERS.has(key.toLowerCase()) ? \"[REDACTED]\" : value;\n };\n if (typeof (headers as Headers).forEach === \"function\" && !Array.isArray(headers)) {\n (headers as Headers).forEach((value, key) => set(key, value));\n } else {\n for (const [key, value] of Object.entries(headers as Record<string, string>)) {\n set(key, String(value));\n }\n }\n return out;\n}\n\nexport function previewBody(raw: unknown, redactFields: string[] = []): unknown {\n if (raw === undefined || raw === null) return undefined;\n let value: unknown = raw;\n if (typeof raw === \"string\") {\n try {\n value = JSON.parse(raw);\n } catch {\n return raw.length > MAX_BODY_CHARS ? raw.slice(0, MAX_BODY_CHARS) + \"…[truncated]\" : raw;\n }\n }\n const redacted = redactDeep(value, redactFields);\n let text: string;\n try {\n text = JSON.stringify(redacted);\n } catch {\n return \"[unserializable]\";\n }\n if (text.length > MAX_BODY_CHARS) {\n return text.slice(0, MAX_BODY_CHARS) + \"…[truncated]\";\n }\n return redacted;\n}\n\nexport function newLogId(): string {\n const uuid = (globalThis as any)?.crypto?.randomUUID;\n if (typeof uuid === \"function\") return uuid.call((globalThis as any).crypto);\n return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;\n}\n","import type { NetworkLogEntry } from \"./network-log.js\";\n\nexport interface ShipperConfig {\n baseUrl: string;\n apiKey: string;\n fetchImpl: typeof fetch;\n sdk: string;\n applicationId?: string;\n environment?: string;\n batchSize?: number;\n flushIntervalMs?: number;\n}\n\nconst DEFAULT_BATCH = 25;\nconst DEFAULT_FLUSH_MS = 5000;\n\nexport class NetworkLogShipper {\n private readonly cfg: ShipperConfig;\n private queue: NetworkLogEntry[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(cfg: ShipperConfig) {\n this.cfg = cfg;\n }\n\n start(): void {\n if (this.timer) return;\n const interval = this.cfg.flushIntervalMs ?? DEFAULT_FLUSH_MS;\n this.timer = setInterval(() => {\n void this.flush();\n }, interval);\n (this.timer as any)?.unref?.();\n }\n\n add(entry: NetworkLogEntry): void {\n this.queue.push({\n ...entry,\n sdk: this.cfg.sdk,\n applicationId: entry.applicationId ?? this.cfg.applicationId,\n environment: entry.environment ?? this.cfg.environment,\n });\n if (this.queue.length >= (this.cfg.batchSize ?? DEFAULT_BATCH)) {\n void this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n const logs = this.queue;\n this.queue = [];\n try {\n await this.cfg.fetchImpl(`${this.cfg.baseUrl}/v1/network-logs`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.cfg.apiKey,\n },\n body: JSON.stringify({ logs }),\n });\n } catch {\n /* empty */\n }\n }\n\n stop(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n void this.flush();\n }\n}\n","/**\n * Thin internal client.\n *\n * This is a ~40-line port of the minimal call path from `@simplr-ai/node`\n * (`apiRequest` + `check` + `edge.ingestLogs`) so that this package builds and\n * tests without an unbuilt workspace dependency or any network access.\n *\n * In production this package pairs with `@simplr-ai/node`; the endpoints, auth\n * header (`X-API-Key`), `{ success, message, content }` envelope unwrapping, and\n * 15s default timeout here are byte-for-byte compatible with that SDK and the\n * Simplr API contract.\n */\nimport type { CheckInput, CheckResult, EdgeLogEntry } from \"./types.js\";\nimport {\n newLogId,\n previewBody,\n redactHeaders,\n type NetworkLogger,\n} from \"./network-log.js\";\nimport { NetworkLogShipper } from \"./network-shipper.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.simplr.sh\";\n\n/** Thrown when the Simplr API returns a non-2xx response or the request fails. */\nexport class SimplrError extends Error {\n readonly status: number;\n readonly body: unknown;\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"SimplrError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport interface ClientConfig {\n apiKey: string;\n baseUrl?: string;\n timeoutMs?: number;\n fetch?: typeof fetch;\n onNetworkLog?: NetworkLogger;\n logBodies?: boolean;\n redactFields?: string[];\n shipNetworkLogs?: boolean;\n applicationId?: string;\n environment?: string;\n}\n\nexport interface SimplrClient {\n check(input: CheckInput): Promise<CheckResult>;\n ingestLogs(deviceId: string, logs: EdgeLogEntry[]): Promise<unknown>;\n flushNetworkLogs(): Promise<void>;\n close(): void;\n}\n\nexport function createClient(config: ClientConfig): SimplrClient {\n if (!config?.apiKey) throw new Error(\"Simplr: `apiKey` is required\");\n const baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const timeoutMs = config.timeoutMs ?? 15000;\n const fetchImpl = config.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new Error(\n \"Simplr: no global fetch available — use Node 18+ or pass `fetch` in options\",\n );\n }\n\n const logBodies = config.logBodies ?? config.shipNetworkLogs;\n const redactFields = config.redactFields;\n\n let shipper: NetworkLogShipper | undefined;\n if (config.shipNetworkLogs) {\n shipper = new NetworkLogShipper({\n baseUrl,\n apiKey: config.apiKey,\n fetchImpl,\n sdk: \"express\",\n applicationId: config.applicationId,\n environment: config.environment,\n });\n shipper.start();\n }\n\n const userLog = config.onNetworkLog;\n const onNetworkLog: NetworkLogger | undefined =\n shipper || userLog\n ? (entry) => {\n shipper?.add(entry);\n userLog?.(entry);\n }\n : undefined;\n\n async function apiRequest<T>(method: string, path: string, body?: unknown): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n const url = `${baseUrl}${path}`;\n const requestHeaders = { \"Content-Type\": \"application/json\", \"X-API-Key\": config.apiKey };\n const startedAt = Date.now();\n const log = onNetworkLog\n ? {\n id: newLogId(),\n source: \"backend\" as const,\n timestamp: new Date(startedAt).toISOString(),\n method,\n url,\n requestHeaders: redactHeaders(requestHeaders),\n requestBody: logBodies ? previewBody(body, redactFields) : undefined,\n }\n : null;\n\n const emit = (extra: Record<string, unknown>) => {\n if (!log || !onNetworkLog) return;\n try {\n onNetworkLog({ ...log, durationMs: Date.now() - startedAt, ...extra });\n } catch {\n /* empty */\n }\n };\n\n try {\n const res = await fetchImpl(url, {\n method,\n headers: requestHeaders,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n const text = await res.text();\n let parsed: any;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n parsed = text;\n }\n\n emit({\n status: res.status,\n statusText: res.statusText,\n ok: res.ok,\n responseHeaders: redactHeaders(res.headers),\n responseBody: logBodies ? previewBody(parsed ?? text, redactFields) : undefined,\n });\n\n if (!res.ok) {\n const message =\n (parsed && (parsed.message || parsed.error)) || `Simplr API error ${res.status}`;\n throw new SimplrError(message, res.status, parsed);\n }\n return (parsed && typeof parsed === \"object\" && \"content\" in parsed\n ? parsed.content\n : parsed) as T;\n } catch (err) {\n if (err instanceof SimplrError) throw err;\n if (err instanceof Error && err.name === \"AbortError\") {\n emit({ ok: false, error: `timed out after ${timeoutMs}ms` });\n throw new SimplrError(`Request to ${path} timed out after ${timeoutMs}ms`, 0, null);\n }\n emit({ ok: false, error: err instanceof Error ? err.message : \"Network error\" });\n throw new SimplrError(err instanceof Error ? err.message : \"Network error\", 0, null);\n } finally {\n clearTimeout(timer);\n }\n }\n\n return {\n check: (input) => apiRequest<CheckResult>(\"POST\", \"/v1/check\", input),\n ingestLogs: (deviceId, logs) =>\n apiRequest(\"POST\", \"/v1/edge/logs\", { device_id: deviceId, logs }),\n flushNetworkLogs: () => shipper?.flush() ?? Promise.resolve(),\n close: () => shipper?.stop(),\n };\n}\n","/**\n * Framework-agnostic guard engine.\n *\n * `createGuard` returns an object that, given any request-like object, builds a\n * `CheckInput`, calls `/v1/check`, decides whether to block, optionally ships an\n * edge log, and fails open on error. Framework adapters (express/fastify/hono/\n * next) are thin wrappers over this engine.\n */\nimport { createClient, type SimplrClient } from \"./client.js\";\nimport type {\n BlockThreshold,\n CheckInput,\n CheckResult,\n GuardContext,\n GuardOptions,\n GuardOutcome,\n RiskLevel,\n} from \"./types.js\";\n\nconst RISK_ORDER: Record<RiskLevel, number> = {\n low: 0,\n medium: 1,\n high: 2,\n critical: 3,\n};\n\nconst DEFAULT_DEVICE_ID = \"simplr-edge-middleware\";\nconst DEFAULT_THRESHOLD: RiskLevel = \"high\";\n\n/** Numeric rank of a risk level. Unknown levels rank as the lowest (0). */\nexport function riskRank(level: RiskLevel | string | undefined): number {\n return RISK_ORDER[level as RiskLevel] ?? 0;\n}\n\n/** True when `level` is at least `min` in the low<medium<high<critical ordering. */\nexport function riskAtLeast(level: RiskLevel | string | undefined, min: RiskLevel): boolean {\n return riskRank(level) >= riskRank(min);\n}\n\n/** Read a header from either a plain bag (lowercase keys) or a Headers-like getter. */\nfunction readHeader(headers: unknown, name: string): string | undefined {\n if (!headers) return undefined;\n const h = headers as any;\n if (typeof h.get === \"function\") {\n const v = h.get(name);\n return v == null ? undefined : String(v);\n }\n // Plain object: try the lowercase key, then a case-insensitive scan.\n const lower = name.toLowerCase();\n if (lower in h && h[lower] != null) return String(h[lower]);\n for (const key of Object.keys(h)) {\n if (key.toLowerCase() === lower && h[key] != null) return String(h[key]);\n }\n return undefined;\n}\n\n/** Best-effort client IP from common header and socket locations. */\nfunction extractIp(req: any): string | undefined {\n const fwd = readHeader(req?.headers, \"x-forwarded-for\");\n if (fwd) return fwd.split(\",\")[0]!.trim();\n const real = readHeader(req?.headers, \"x-real-ip\");\n if (real) return real;\n return (\n req?.ip ||\n req?.socket?.remoteAddress ||\n req?.connection?.remoteAddress ||\n undefined\n );\n}\n\n/**\n * Default request → CheckInput extractor. Pulls the client IP and user-agent into\n * `device`, and lifts `email`/`phone` from a parsed JSON body when present.\n */\nexport function defaultExtract(req: any): CheckInput {\n const input: CheckInput = {};\n\n const device: Record<string, unknown> = {};\n const ip = extractIp(req);\n if (ip) device.ip = ip;\n const ua = readHeader(req?.headers, \"user-agent\");\n if (ua) device.user_agent = ua;\n if (Object.keys(device).length > 0) input.device = device;\n\n const body = req?.body;\n if (body && typeof body === \"object\") {\n const b = body as Record<string, unknown>;\n if (typeof b.email === \"string\") input.email = b.email;\n if (typeof b.phone === \"string\") input.phone = b.phone;\n }\n\n return input;\n}\n\nfunction normalizeBlock(\n block: GuardOptions[\"block\"],\n): (result: CheckResult) => boolean {\n if (typeof block === \"function\") return block;\n const threshold = (block as BlockThreshold | undefined)?.whenRiskAtLeast ?? DEFAULT_THRESHOLD;\n return (result) => riskAtLeast(result.risk_level, threshold);\n}\n\nexport interface Guard<Req = any> {\n /** The underlying thin Simplr client (check + edge log ingestion). */\n readonly client: SimplrClient;\n /** Run the full engine against a request; never throws when `failOpen`. */\n run(req: Req): Promise<GuardOutcome>;\n}\n\nexport function createGuard<Req = any>(options: GuardOptions<Req>): Guard<Req> {\n if (!options?.apiKey) throw new Error(\"createGuard: `apiKey` is required\");\n\n const client = createClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n timeoutMs: options.timeoutMs,\n fetch: options.fetch,\n onNetworkLog: options.onNetworkLog,\n logBodies: options.logBodies,\n redactFields: options.redactFields,\n shipNetworkLogs: options.shipNetworkLogs,\n applicationId: options.applicationId,\n environment: options.environment,\n });\n\n const extract = options.extract ?? defaultExtract;\n const shouldBlock = normalizeBlock(options.block);\n const failOpen = options.failOpen ?? true;\n const ingestLogs = options.ingestLogs ?? false;\n const deviceId = options.deviceId ?? DEFAULT_DEVICE_ID;\n\n async function run(req: Req): Promise<GuardOutcome> {\n const input = await extract(req);\n let result: CheckResult;\n try {\n result = await client.check(input);\n } catch (error) {\n if (failOpen) {\n return { result: null, blocked: false, input, error };\n }\n throw error;\n }\n\n const blocked = shouldBlock(result);\n const ctx: GuardContext<Req> = { req, input, blocked };\n\n if (options.onResult) {\n try {\n await options.onResult(result, ctx);\n } catch {\n // onResult is observational; never let it break the request.\n }\n }\n\n if (ingestLogs) {\n try {\n await client.ingestLogs(deviceId, [\n {\n category: \"security\",\n level: blocked ? \"warn\" : \"info\",\n message: blocked ? \"simplr guard blocked request\" : \"simplr guard checked request\",\n risk_level: result.risk_level,\n risk_score: result.risk_score,\n },\n ]);\n } catch {\n // Log shipping is best-effort and must never affect the request.\n }\n }\n\n return { result, blocked, input };\n }\n\n return { client, run };\n}\n\n/** Standard JSON envelope returned to the client on a blocked request. */\nexport function blockEnvelope(result: CheckResult) {\n return {\n error: \"request_blocked\",\n message: \"This request was blocked by Simplr fraud protection.\",\n risk_level: result.risk_level,\n risk_score: result.risk_score,\n };\n}\n","/**\n * Fastify adapter. A plugin that registers a `preHandler` hook decorating\n * `request.simplr` and short-circuiting blocked requests with a 403.\n *\n * ```ts\n * import Fastify from \"fastify\";\n * import { simplrFastify } from \"@simplr-ai/express/fastify\";\n * const app = Fastify();\n * await app.register(simplrFastify, { apiKey: process.env.SIMPLR_API_KEY! });\n * ```\n */\nimport { blockEnvelope, createGuard } from \"./core.js\";\nimport type { CheckResult, GuardOptions } from \"./types.js\";\n\ntype FastifyInstance = any;\ntype FastifyRequest = any;\ntype FastifyReply = {\n code(statusCode: number): FastifyReply;\n send(payload: unknown): unknown;\n};\n\n/**\n * Fastify plugin. Register with `app.register(simplrFastify, options)`.\n * Accepts the standard `(fastify, options, done)` plugin signature.\n */\nexport function simplrFastify(\n fastify: FastifyInstance,\n options: GuardOptions,\n done?: (err?: Error) => void,\n): void {\n try {\n const guard = createGuard(options);\n const failOpen = options.failOpen ?? true;\n\n // Decorate so TypeScript/Fastify know about `request.simplr`.\n if (typeof fastify.decorateRequest === \"function\") {\n fastify.decorateRequest(\"simplr\", null);\n }\n\n fastify.addHook(\n \"preHandler\",\n async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const outcome = await guard.run(request);\n request.simplr = outcome.result;\n if (outcome.blocked && outcome.result) {\n reply.code(403).send(blockEnvelope(outcome.result));\n return reply;\n }\n } catch (err) {\n if (failOpen) {\n request.simplr = null;\n return;\n }\n throw err;\n }\n },\n );\n\n done?.();\n } catch (err) {\n if (done) done(err as Error);\n else throw err;\n }\n}\n\n// Mark as a Fastify plugin so `fastify-plugin`-style encapsulation skipping is\n// not required; Fastify reads this property when present.\n(simplrFastify as any)[Symbol.for(\"skip-override\")] = true;\n\n// Consumers can augment `FastifyRequest` with `simplr?: CheckResult | null`\n// themselves; we don't `declare module \"fastify\"` here so the package builds\n// without `fastify` (an optional peer) installed. `CheckResult` is re-exported\n// for that purpose.\nexport type { CheckResult };\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/network-log.ts","../src/network-shipper.ts","../src/client.ts","../src/core.ts","../src/fastify.ts"],"sourcesContent":["export type NetworkSource = \"frontend\" | \"backend\";\n\nexport interface NetworkLogEntry {\n id: string;\n requestId?: string;\n source: NetworkSource;\n timestamp: string;\n sdk?: string;\n applicationId?: string;\n environment?: string;\n method: string;\n url: string;\n requestHeaders?: Record<string, string>;\n requestBody?: unknown;\n status?: number;\n statusText?: string;\n responseHeaders?: Record<string, string>;\n responseBody?: unknown;\n durationMs?: number;\n ok?: boolean;\n error?: string;\n}\n\nexport type NetworkLogger = (entry: NetworkLogEntry) => void;\n\nconst SENSITIVE_HEADERS = new Set([\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n \"x-auth-token\",\n \"x-csrf-token\",\n \"x-xsrf-token\",\n]);\n\nconst SENSITIVE_KEY_PARTS = [\n \"password\",\n \"passwd\",\n \"secret\",\n \"token\",\n \"api_key\",\n \"apikey\",\n \"authorization\",\n \"auth\",\n \"credential\",\n \"private_key\",\n \"card\",\n \"cardnumber\",\n \"pan\",\n \"cvv\",\n \"cvc\",\n \"ssn\",\n \"pin\",\n \"otp\",\n];\n\nconst MAX_REDACT_DEPTH = 8;\n\nexport const MAX_BODY_CHARS = 10_000;\n\nfunction isSensitiveKey(key: string, extraKeys: string[]): boolean {\n const lower = key.toLowerCase();\n if (extraKeys.some((k) => lower === k.toLowerCase())) return true;\n return SENSITIVE_KEY_PARTS.some((part) => lower.includes(part));\n}\n\nfunction redactDeep(value: unknown, extraKeys: string[], depth = 0): unknown {\n if (depth >= MAX_REDACT_DEPTH) return \"[truncated]\";\n if (Array.isArray(value)) return value.map((v) => redactDeep(v, extraKeys, depth + 1));\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n out[key] = isSensitiveKey(key, extraKeys)\n ? \"[REDACTED]\"\n : redactDeep(val, extraKeys, depth + 1);\n }\n return out;\n }\n return value;\n}\n\nexport function redactHeaders(\n headers: Record<string, string> | Headers | undefined,\n): Record<string, string> | undefined {\n if (!headers) return undefined;\n const out: Record<string, string> = {};\n const set = (key: string, value: string) => {\n out[key] = SENSITIVE_HEADERS.has(key.toLowerCase()) ? \"[REDACTED]\" : value;\n };\n if (typeof (headers as Headers).forEach === \"function\" && !Array.isArray(headers)) {\n (headers as Headers).forEach((value, key) => set(key, value));\n } else {\n for (const [key, value] of Object.entries(headers as Record<string, string>)) {\n set(key, String(value));\n }\n }\n return out;\n}\n\nexport function previewBody(raw: unknown, redactFields: string[] = []): unknown {\n if (raw === undefined || raw === null) return undefined;\n let value: unknown = raw;\n if (typeof raw === \"string\") {\n try {\n value = JSON.parse(raw);\n } catch {\n return raw.length > MAX_BODY_CHARS ? raw.slice(0, MAX_BODY_CHARS) + \"…[truncated]\" : raw;\n }\n }\n const redacted = redactDeep(value, redactFields);\n let text: string;\n try {\n text = JSON.stringify(redacted);\n } catch {\n return \"[unserializable]\";\n }\n if (text.length > MAX_BODY_CHARS) {\n return text.slice(0, MAX_BODY_CHARS) + \"…[truncated]\";\n }\n return redacted;\n}\n\nexport function newLogId(): string {\n const uuid = (globalThis as any)?.crypto?.randomUUID;\n if (typeof uuid === \"function\") return uuid.call((globalThis as any).crypto);\n return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;\n}\n","import type { NetworkLogEntry } from \"./network-log.js\";\n\nexport interface ShipperConfig {\n baseUrl: string;\n apiKey: string;\n fetchImpl: typeof fetch;\n sdk: string;\n applicationId?: string;\n environment?: string;\n batchSize?: number;\n flushIntervalMs?: number;\n}\n\nconst DEFAULT_BATCH = 25;\nconst DEFAULT_FLUSH_MS = 5000;\n\nexport class NetworkLogShipper {\n private readonly cfg: ShipperConfig;\n private queue: NetworkLogEntry[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(cfg: ShipperConfig) {\n this.cfg = cfg;\n }\n\n start(): void {\n if (this.timer) return;\n const interval = this.cfg.flushIntervalMs ?? DEFAULT_FLUSH_MS;\n this.timer = setInterval(() => {\n void this.flush();\n }, interval);\n (this.timer as any)?.unref?.();\n }\n\n add(entry: NetworkLogEntry): void {\n this.queue.push({\n ...entry,\n sdk: this.cfg.sdk,\n applicationId: entry.applicationId ?? this.cfg.applicationId,\n environment: entry.environment ?? this.cfg.environment,\n });\n if (this.queue.length >= (this.cfg.batchSize ?? DEFAULT_BATCH)) {\n void this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n const logs = this.queue;\n this.queue = [];\n try {\n await this.cfg.fetchImpl(`${this.cfg.baseUrl}/v1/network-logs`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.cfg.apiKey,\n },\n body: JSON.stringify({ logs }),\n });\n } catch {\n /* empty */\n }\n }\n\n stop(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n void this.flush();\n }\n}\n","/**\n * Thin internal client.\n *\n * This is a ~40-line port of the minimal call path from `@simplr-ai/node`\n * (`apiRequest` + `check` + `edge.ingestLogs`) so that this package builds and\n * tests without an unbuilt workspace dependency or any network access.\n *\n * In production this package pairs with `@simplr-ai/node`; the endpoints, auth\n * header (`X-API-Key`), `{ success, message, content }` envelope unwrapping, and\n * 15s default timeout here are byte-for-byte compatible with that SDK and the\n * Simplr API contract.\n */\nimport type { CheckInput, CheckResult, EdgeLogEntry } from \"./types.js\";\nimport {\n newLogId,\n previewBody,\n redactHeaders,\n type NetworkLogger,\n} from \"./network-log.js\";\nimport { NetworkLogShipper } from \"./network-shipper.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.simplr.sh\";\n\n/** Thrown when the Simplr API returns a non-2xx response or the request fails. */\nexport class SimplrError extends Error {\n readonly status: number;\n readonly body: unknown;\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"SimplrError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport interface ClientConfig {\n apiKey: string;\n baseUrl?: string;\n timeoutMs?: number;\n fetch?: typeof fetch;\n onNetworkLog?: NetworkLogger;\n logBodies?: boolean;\n redactFields?: string[];\n shipNetworkLogs?: boolean;\n applicationId?: string;\n environment?: string;\n logSelfCalls?: boolean;\n}\n\nexport interface SimplrClient {\n check(input: CheckInput): Promise<CheckResult>;\n ingestLogs(deviceId: string, logs: EdgeLogEntry[]): Promise<unknown>;\n flushNetworkLogs(): Promise<void>;\n close(): void;\n}\n\nexport function createClient(config: ClientConfig): SimplrClient {\n if (!config?.apiKey) throw new Error(\"Simplr: `apiKey` is required\");\n const baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const timeoutMs = config.timeoutMs ?? 15000;\n const fetchImpl = config.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new Error(\n \"Simplr: no global fetch available — use Node 18+ or pass `fetch` in options\",\n );\n }\n\n const logSelf = !!config.logSelfCalls;\n const logBodies = config.logBodies ?? (config.shipNetworkLogs && logSelf);\n const redactFields = config.redactFields;\n\n let shipper: NetworkLogShipper | undefined;\n if (config.shipNetworkLogs && logSelf) {\n shipper = new NetworkLogShipper({\n baseUrl,\n apiKey: config.apiKey,\n fetchImpl,\n sdk: \"express\",\n applicationId: config.applicationId,\n environment: config.environment,\n });\n shipper.start();\n }\n\n const userLog = config.onNetworkLog;\n const onNetworkLog: NetworkLogger | undefined =\n shipper || userLog\n ? (entry) => {\n shipper?.add(entry);\n userLog?.(entry);\n }\n : undefined;\n\n async function apiRequest<T>(method: string, path: string, body?: unknown): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n const url = `${baseUrl}${path}`;\n const requestHeaders = { \"Content-Type\": \"application/json\", \"X-API-Key\": config.apiKey };\n const startedAt = Date.now();\n const log = onNetworkLog\n ? {\n id: newLogId(),\n source: \"backend\" as const,\n timestamp: new Date(startedAt).toISOString(),\n method,\n url,\n requestHeaders: redactHeaders(requestHeaders),\n requestBody: logBodies ? previewBody(body, redactFields) : undefined,\n }\n : null;\n\n const emit = (extra: Record<string, unknown>) => {\n if (!log || !onNetworkLog) return;\n try {\n onNetworkLog({ ...log, durationMs: Date.now() - startedAt, ...extra });\n } catch {\n /* empty */\n }\n };\n\n try {\n const res = await fetchImpl(url, {\n method,\n headers: requestHeaders,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n const text = await res.text();\n let parsed: any;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n parsed = text;\n }\n\n emit({\n status: res.status,\n statusText: res.statusText,\n ok: res.ok,\n responseHeaders: redactHeaders(res.headers),\n responseBody: logBodies ? previewBody(parsed ?? text, redactFields) : undefined,\n });\n\n if (!res.ok) {\n const message =\n (parsed && (parsed.message || parsed.error)) || `Simplr API error ${res.status}`;\n throw new SimplrError(message, res.status, parsed);\n }\n return (parsed && typeof parsed === \"object\" && \"content\" in parsed\n ? parsed.content\n : parsed) as T;\n } catch (err) {\n if (err instanceof SimplrError) throw err;\n if (err instanceof Error && err.name === \"AbortError\") {\n emit({ ok: false, error: `timed out after ${timeoutMs}ms` });\n throw new SimplrError(`Request to ${path} timed out after ${timeoutMs}ms`, 0, null);\n }\n emit({ ok: false, error: err instanceof Error ? err.message : \"Network error\" });\n throw new SimplrError(err instanceof Error ? err.message : \"Network error\", 0, null);\n } finally {\n clearTimeout(timer);\n }\n }\n\n return {\n check: (input) => apiRequest<CheckResult>(\"POST\", \"/v1/check\", input),\n ingestLogs: (deviceId, logs) =>\n apiRequest(\"POST\", \"/v1/edge/logs\", { device_id: deviceId, logs }),\n flushNetworkLogs: () => shipper?.flush() ?? Promise.resolve(),\n close: () => shipper?.stop(),\n };\n}\n","/**\n * Framework-agnostic guard engine.\n *\n * `createGuard` returns an object that, given any request-like object, builds a\n * `CheckInput`, calls `/v1/check`, decides whether to block, optionally ships an\n * edge log, and fails open on error. Framework adapters (express/fastify/hono/\n * next) are thin wrappers over this engine.\n */\nimport { createClient, type SimplrClient } from \"./client.js\";\nimport type {\n BlockThreshold,\n CheckInput,\n CheckResult,\n GuardContext,\n GuardOptions,\n GuardOutcome,\n RiskLevel,\n} from \"./types.js\";\n\nconst RISK_ORDER: Record<RiskLevel, number> = {\n low: 0,\n medium: 1,\n high: 2,\n critical: 3,\n};\n\nconst DEFAULT_DEVICE_ID = \"simplr-edge-middleware\";\nconst DEFAULT_THRESHOLD: RiskLevel = \"high\";\n\n/** Numeric rank of a risk level. Unknown levels rank as the lowest (0). */\nexport function riskRank(level: RiskLevel | string | undefined): number {\n return RISK_ORDER[level as RiskLevel] ?? 0;\n}\n\n/** True when `level` is at least `min` in the low<medium<high<critical ordering. */\nexport function riskAtLeast(level: RiskLevel | string | undefined, min: RiskLevel): boolean {\n return riskRank(level) >= riskRank(min);\n}\n\n/** Read a header from either a plain bag (lowercase keys) or a Headers-like getter. */\nfunction readHeader(headers: unknown, name: string): string | undefined {\n if (!headers) return undefined;\n const h = headers as any;\n if (typeof h.get === \"function\") {\n const v = h.get(name);\n return v == null ? undefined : String(v);\n }\n // Plain object: try the lowercase key, then a case-insensitive scan.\n const lower = name.toLowerCase();\n if (lower in h && h[lower] != null) return String(h[lower]);\n for (const key of Object.keys(h)) {\n if (key.toLowerCase() === lower && h[key] != null) return String(h[key]);\n }\n return undefined;\n}\n\n/** Best-effort client IP from common header and socket locations. */\nfunction extractIp(req: any): string | undefined {\n const fwd = readHeader(req?.headers, \"x-forwarded-for\");\n if (fwd) return fwd.split(\",\")[0]!.trim();\n const real = readHeader(req?.headers, \"x-real-ip\");\n if (real) return real;\n return (\n req?.ip ||\n req?.socket?.remoteAddress ||\n req?.connection?.remoteAddress ||\n undefined\n );\n}\n\n/**\n * Default request → CheckInput extractor. Pulls the client IP and user-agent into\n * `device`, and lifts `email`/`phone` from a parsed JSON body when present.\n */\nexport function defaultExtract(req: any): CheckInput {\n const input: CheckInput = {};\n\n const device: Record<string, unknown> = {};\n const ip = extractIp(req);\n if (ip) device.ip = ip;\n const ua = readHeader(req?.headers, \"user-agent\");\n if (ua) device.user_agent = ua;\n if (Object.keys(device).length > 0) input.device = device;\n\n const body = req?.body;\n if (body && typeof body === \"object\") {\n const b = body as Record<string, unknown>;\n if (typeof b.email === \"string\") input.email = b.email;\n if (typeof b.phone === \"string\") input.phone = b.phone;\n }\n\n return input;\n}\n\nfunction normalizeBlock(\n block: GuardOptions[\"block\"],\n): (result: CheckResult) => boolean {\n if (typeof block === \"function\") return block;\n const threshold = (block as BlockThreshold | undefined)?.whenRiskAtLeast ?? DEFAULT_THRESHOLD;\n return (result) => riskAtLeast(result.risk_level, threshold);\n}\n\nexport interface Guard<Req = any> {\n /** The underlying thin Simplr client (check + edge log ingestion). */\n readonly client: SimplrClient;\n /** Run the full engine against a request; never throws when `failOpen`. */\n run(req: Req): Promise<GuardOutcome>;\n}\n\nexport function createGuard<Req = any>(options: GuardOptions<Req>): Guard<Req> {\n if (!options?.apiKey) throw new Error(\"createGuard: `apiKey` is required\");\n\n const client = createClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n timeoutMs: options.timeoutMs,\n fetch: options.fetch,\n onNetworkLog: options.onNetworkLog,\n logBodies: options.logBodies,\n redactFields: options.redactFields,\n shipNetworkLogs: options.shipNetworkLogs,\n applicationId: options.applicationId,\n environment: options.environment,\n logSelfCalls: options.logSelfCalls,\n });\n\n const extract = options.extract ?? defaultExtract;\n const shouldBlock = normalizeBlock(options.block);\n const failOpen = options.failOpen ?? true;\n const ingestLogs = options.ingestLogs ?? false;\n const deviceId = options.deviceId ?? DEFAULT_DEVICE_ID;\n\n async function run(req: Req): Promise<GuardOutcome> {\n const input = await extract(req);\n let result: CheckResult;\n try {\n result = await client.check(input);\n } catch (error) {\n if (failOpen) {\n return { result: null, blocked: false, input, error };\n }\n throw error;\n }\n\n const blocked = shouldBlock(result);\n const ctx: GuardContext<Req> = { req, input, blocked };\n\n if (options.onResult) {\n try {\n await options.onResult(result, ctx);\n } catch {\n // onResult is observational; never let it break the request.\n }\n }\n\n if (ingestLogs) {\n try {\n await client.ingestLogs(deviceId, [\n {\n category: \"security\",\n level: blocked ? \"warn\" : \"info\",\n message: blocked ? \"simplr guard blocked request\" : \"simplr guard checked request\",\n risk_level: result.risk_level,\n risk_score: result.risk_score,\n },\n ]);\n } catch {\n // Log shipping is best-effort and must never affect the request.\n }\n }\n\n return { result, blocked, input };\n }\n\n return { client, run };\n}\n\n/** Standard JSON envelope returned to the client on a blocked request. */\nexport function blockEnvelope(result: CheckResult) {\n return {\n error: \"request_blocked\",\n message: \"This request was blocked by Simplr fraud protection.\",\n risk_level: result.risk_level,\n risk_score: result.risk_score,\n };\n}\n","/**\n * Fastify adapter. A plugin that registers a `preHandler` hook decorating\n * `request.simplr` and short-circuiting blocked requests with a 403.\n *\n * ```ts\n * import Fastify from \"fastify\";\n * import { simplrFastify } from \"@simplr-ai/express/fastify\";\n * const app = Fastify();\n * await app.register(simplrFastify, { apiKey: process.env.SIMPLR_API_KEY! });\n * ```\n */\nimport { blockEnvelope, createGuard } from \"./core.js\";\nimport type { CheckResult, GuardOptions } from \"./types.js\";\n\ntype FastifyInstance = any;\ntype FastifyRequest = any;\ntype FastifyReply = {\n code(statusCode: number): FastifyReply;\n send(payload: unknown): unknown;\n};\n\n/**\n * Fastify plugin. Register with `app.register(simplrFastify, options)`.\n * Accepts the standard `(fastify, options, done)` plugin signature.\n */\nexport function simplrFastify(\n fastify: FastifyInstance,\n options: GuardOptions,\n done?: (err?: Error) => void,\n): void {\n try {\n const guard = createGuard(options);\n const failOpen = options.failOpen ?? true;\n\n // Decorate so TypeScript/Fastify know about `request.simplr`.\n if (typeof fastify.decorateRequest === \"function\") {\n fastify.decorateRequest(\"simplr\", null);\n }\n\n fastify.addHook(\n \"preHandler\",\n async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const outcome = await guard.run(request);\n request.simplr = outcome.result;\n if (outcome.blocked && outcome.result) {\n reply.code(403).send(blockEnvelope(outcome.result));\n return reply;\n }\n } catch (err) {\n if (failOpen) {\n request.simplr = null;\n return;\n }\n throw err;\n }\n },\n );\n\n done?.();\n } catch (err) {\n if (done) done(err as Error);\n else throw err;\n }\n}\n\n// Mark as a Fastify plugin so `fastify-plugin`-style encapsulation skipping is\n// not required; Fastify reads this property when present.\n(simplrFastify as any)[Symbol.for(\"skip-override\")] = true;\n\n// Consumers can augment `FastifyRequest` with `simplr?: CheckResult | null`\n// themselves; we don't `declare module \"fastify\"` here so the package builds\n// without `fastify` (an optional peer) installed. `CheckResult` is re-exported\n// for that purpose.\nexport type { CheckResult };\n"],"mappings":"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","names":[]}
package/dist/hono.cjs CHANGED
@@ -191,10 +191,11 @@ function createClient(config) {
191
191
  "Simplr: no global fetch available \u2014 use Node 18+ or pass `fetch` in options"
192
192
  );
193
193
  }
194
- const logBodies = config.logBodies ?? config.shipNetworkLogs;
194
+ const logSelf = !!config.logSelfCalls;
195
+ const logBodies = config.logBodies ?? (config.shipNetworkLogs && logSelf);
195
196
  const redactFields = config.redactFields;
196
197
  let shipper;
197
- if (config.shipNetworkLogs) {
198
+ if (config.shipNetworkLogs && logSelf) {
198
199
  shipper = new NetworkLogShipper({
199
200
  baseUrl,
200
201
  apiKey: config.apiKey,
@@ -347,7 +348,8 @@ function createGuard(options) {
347
348
  redactFields: options.redactFields,
348
349
  shipNetworkLogs: options.shipNetworkLogs,
349
350
  applicationId: options.applicationId,
350
- environment: options.environment
351
+ environment: options.environment,
352
+ logSelfCalls: options.logSelfCalls
351
353
  });
352
354
  const extract = options.extract ?? defaultExtract;
353
355
  const shouldBlock = normalizeBlock(options.block);
package/dist/hono.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hono.ts","../src/network-log.ts","../src/network-shipper.ts","../src/client.ts","../src/core.ts"],"sourcesContent":["/**\n * Hono adapter. Middleware that stores the result via `c.set('simplr', result)`\n * (read it back with `c.get('simplr')`) and returns a 403 JSON response on block.\n *\n * ```ts\n * import { Hono } from \"hono\";\n * import { simplrHono } from \"@simplr-ai/express/hono\";\n * const app = new Hono();\n * app.use(\"*\", simplrHono({ apiKey: process.env.SIMPLR_API_KEY! }));\n * ```\n */\nimport { blockEnvelope, createGuard } from \"./core.js\";\nimport type { CheckResult, GuardOptions } from \"./types.js\";\n\ntype HonoContext = {\n req: {\n /** Hono exposes headers via `c.req.header(name)`. */\n header(name: string): string | undefined;\n /** Parsed JSON body, if read. The default extractor reads `c.get`-supplied body. */\n [key: string]: unknown;\n };\n set(key: string, value: unknown): void;\n get(key: string): unknown;\n json(body: unknown, status?: number): Response;\n};\ntype HonoNext = () => Promise<void>;\n\nexport type HonoMiddleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;\n\n/**\n * Build Hono middleware. Because Hono request bodies are read asynchronously,\n * the default extractor relies on headers; to also score body email/phone, set\n * a custom `extract` that awaits `await c.req.json()` (and stashes it as needed).\n */\nexport function simplrHono(options: GuardOptions<HonoContext>): HonoMiddleware {\n const guard = createGuard<HonoContext>({\n ...options,\n // Adapt Hono's header accessor into the shape the default extractor expects.\n extract:\n options.extract ??\n ((c) => {\n const headerGet = (name: string) => c.req.header(name);\n return defaultHonoExtract({ get: headerGet });\n }),\n });\n const failOpen = options.failOpen ?? true;\n\n return async function simplrMiddleware(c, next) {\n try {\n const outcome = await guard.run(c);\n c.set(\"simplr\", outcome.result);\n if (outcome.blocked && outcome.result) {\n return c.json(blockEnvelope(outcome.result), 403);\n }\n } catch (err) {\n if (!failOpen) throw err;\n c.set(\"simplr\", null);\n }\n await next();\n };\n}\n\n/** Minimal extractor over a Headers-like getter (IP + user-agent into device). */\nfunction defaultHonoExtract(headers: { get(name: string): string | undefined }) {\n const device: Record<string, unknown> = {};\n const fwd = headers.get(\"x-forwarded-for\");\n const ip = fwd ? fwd.split(\",\")[0]!.trim() : headers.get(\"x-real-ip\");\n if (ip) device.ip = ip;\n const ua = headers.get(\"user-agent\");\n if (ua) device.user_agent = ua;\n return Object.keys(device).length > 0 ? { device } : {};\n}\n\n/** Helper to read the stored result inside a handler: `getSimplr(c)`. */\nexport function getSimplr(c: HonoContext): CheckResult | null {\n return (c.get(\"simplr\") as CheckResult | null) ?? null;\n}\n","export type NetworkSource = \"frontend\" | \"backend\";\n\nexport interface NetworkLogEntry {\n id: string;\n requestId?: string;\n source: NetworkSource;\n timestamp: string;\n sdk?: string;\n applicationId?: string;\n environment?: string;\n method: string;\n url: string;\n requestHeaders?: Record<string, string>;\n requestBody?: unknown;\n status?: number;\n statusText?: string;\n responseHeaders?: Record<string, string>;\n responseBody?: unknown;\n durationMs?: number;\n ok?: boolean;\n error?: string;\n}\n\nexport type NetworkLogger = (entry: NetworkLogEntry) => void;\n\nconst SENSITIVE_HEADERS = new Set([\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n \"x-auth-token\",\n \"x-csrf-token\",\n \"x-xsrf-token\",\n]);\n\nconst SENSITIVE_KEY_PARTS = [\n \"password\",\n \"passwd\",\n \"secret\",\n \"token\",\n \"api_key\",\n \"apikey\",\n \"authorization\",\n \"auth\",\n \"credential\",\n \"private_key\",\n \"card\",\n \"cardnumber\",\n \"pan\",\n \"cvv\",\n \"cvc\",\n \"ssn\",\n \"pin\",\n \"otp\",\n];\n\nconst MAX_REDACT_DEPTH = 8;\n\nexport const MAX_BODY_CHARS = 10_000;\n\nfunction isSensitiveKey(key: string, extraKeys: string[]): boolean {\n const lower = key.toLowerCase();\n if (extraKeys.some((k) => lower === k.toLowerCase())) return true;\n return SENSITIVE_KEY_PARTS.some((part) => lower.includes(part));\n}\n\nfunction redactDeep(value: unknown, extraKeys: string[], depth = 0): unknown {\n if (depth >= MAX_REDACT_DEPTH) return \"[truncated]\";\n if (Array.isArray(value)) return value.map((v) => redactDeep(v, extraKeys, depth + 1));\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n out[key] = isSensitiveKey(key, extraKeys)\n ? \"[REDACTED]\"\n : redactDeep(val, extraKeys, depth + 1);\n }\n return out;\n }\n return value;\n}\n\nexport function redactHeaders(\n headers: Record<string, string> | Headers | undefined,\n): Record<string, string> | undefined {\n if (!headers) return undefined;\n const out: Record<string, string> = {};\n const set = (key: string, value: string) => {\n out[key] = SENSITIVE_HEADERS.has(key.toLowerCase()) ? \"[REDACTED]\" : value;\n };\n if (typeof (headers as Headers).forEach === \"function\" && !Array.isArray(headers)) {\n (headers as Headers).forEach((value, key) => set(key, value));\n } else {\n for (const [key, value] of Object.entries(headers as Record<string, string>)) {\n set(key, String(value));\n }\n }\n return out;\n}\n\nexport function previewBody(raw: unknown, redactFields: string[] = []): unknown {\n if (raw === undefined || raw === null) return undefined;\n let value: unknown = raw;\n if (typeof raw === \"string\") {\n try {\n value = JSON.parse(raw);\n } catch {\n return raw.length > MAX_BODY_CHARS ? raw.slice(0, MAX_BODY_CHARS) + \"…[truncated]\" : raw;\n }\n }\n const redacted = redactDeep(value, redactFields);\n let text: string;\n try {\n text = JSON.stringify(redacted);\n } catch {\n return \"[unserializable]\";\n }\n if (text.length > MAX_BODY_CHARS) {\n return text.slice(0, MAX_BODY_CHARS) + \"…[truncated]\";\n }\n return redacted;\n}\n\nexport function newLogId(): string {\n const uuid = (globalThis as any)?.crypto?.randomUUID;\n if (typeof uuid === \"function\") return uuid.call((globalThis as any).crypto);\n return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;\n}\n","import type { NetworkLogEntry } from \"./network-log.js\";\n\nexport interface ShipperConfig {\n baseUrl: string;\n apiKey: string;\n fetchImpl: typeof fetch;\n sdk: string;\n applicationId?: string;\n environment?: string;\n batchSize?: number;\n flushIntervalMs?: number;\n}\n\nconst DEFAULT_BATCH = 25;\nconst DEFAULT_FLUSH_MS = 5000;\n\nexport class NetworkLogShipper {\n private readonly cfg: ShipperConfig;\n private queue: NetworkLogEntry[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(cfg: ShipperConfig) {\n this.cfg = cfg;\n }\n\n start(): void {\n if (this.timer) return;\n const interval = this.cfg.flushIntervalMs ?? DEFAULT_FLUSH_MS;\n this.timer = setInterval(() => {\n void this.flush();\n }, interval);\n (this.timer as any)?.unref?.();\n }\n\n add(entry: NetworkLogEntry): void {\n this.queue.push({\n ...entry,\n sdk: this.cfg.sdk,\n applicationId: entry.applicationId ?? this.cfg.applicationId,\n environment: entry.environment ?? this.cfg.environment,\n });\n if (this.queue.length >= (this.cfg.batchSize ?? DEFAULT_BATCH)) {\n void this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n const logs = this.queue;\n this.queue = [];\n try {\n await this.cfg.fetchImpl(`${this.cfg.baseUrl}/v1/network-logs`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.cfg.apiKey,\n },\n body: JSON.stringify({ logs }),\n });\n } catch {\n /* empty */\n }\n }\n\n stop(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n void this.flush();\n }\n}\n","/**\n * Thin internal client.\n *\n * This is a ~40-line port of the minimal call path from `@simplr-ai/node`\n * (`apiRequest` + `check` + `edge.ingestLogs`) so that this package builds and\n * tests without an unbuilt workspace dependency or any network access.\n *\n * In production this package pairs with `@simplr-ai/node`; the endpoints, auth\n * header (`X-API-Key`), `{ success, message, content }` envelope unwrapping, and\n * 15s default timeout here are byte-for-byte compatible with that SDK and the\n * Simplr API contract.\n */\nimport type { CheckInput, CheckResult, EdgeLogEntry } from \"./types.js\";\nimport {\n newLogId,\n previewBody,\n redactHeaders,\n type NetworkLogger,\n} from \"./network-log.js\";\nimport { NetworkLogShipper } from \"./network-shipper.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.simplr.sh\";\n\n/** Thrown when the Simplr API returns a non-2xx response or the request fails. */\nexport class SimplrError extends Error {\n readonly status: number;\n readonly body: unknown;\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"SimplrError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport interface ClientConfig {\n apiKey: string;\n baseUrl?: string;\n timeoutMs?: number;\n fetch?: typeof fetch;\n onNetworkLog?: NetworkLogger;\n logBodies?: boolean;\n redactFields?: string[];\n shipNetworkLogs?: boolean;\n applicationId?: string;\n environment?: string;\n}\n\nexport interface SimplrClient {\n check(input: CheckInput): Promise<CheckResult>;\n ingestLogs(deviceId: string, logs: EdgeLogEntry[]): Promise<unknown>;\n flushNetworkLogs(): Promise<void>;\n close(): void;\n}\n\nexport function createClient(config: ClientConfig): SimplrClient {\n if (!config?.apiKey) throw new Error(\"Simplr: `apiKey` is required\");\n const baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const timeoutMs = config.timeoutMs ?? 15000;\n const fetchImpl = config.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new Error(\n \"Simplr: no global fetch available — use Node 18+ or pass `fetch` in options\",\n );\n }\n\n const logBodies = config.logBodies ?? config.shipNetworkLogs;\n const redactFields = config.redactFields;\n\n let shipper: NetworkLogShipper | undefined;\n if (config.shipNetworkLogs) {\n shipper = new NetworkLogShipper({\n baseUrl,\n apiKey: config.apiKey,\n fetchImpl,\n sdk: \"express\",\n applicationId: config.applicationId,\n environment: config.environment,\n });\n shipper.start();\n }\n\n const userLog = config.onNetworkLog;\n const onNetworkLog: NetworkLogger | undefined =\n shipper || userLog\n ? (entry) => {\n shipper?.add(entry);\n userLog?.(entry);\n }\n : undefined;\n\n async function apiRequest<T>(method: string, path: string, body?: unknown): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n const url = `${baseUrl}${path}`;\n const requestHeaders = { \"Content-Type\": \"application/json\", \"X-API-Key\": config.apiKey };\n const startedAt = Date.now();\n const log = onNetworkLog\n ? {\n id: newLogId(),\n source: \"backend\" as const,\n timestamp: new Date(startedAt).toISOString(),\n method,\n url,\n requestHeaders: redactHeaders(requestHeaders),\n requestBody: logBodies ? previewBody(body, redactFields) : undefined,\n }\n : null;\n\n const emit = (extra: Record<string, unknown>) => {\n if (!log || !onNetworkLog) return;\n try {\n onNetworkLog({ ...log, durationMs: Date.now() - startedAt, ...extra });\n } catch {\n /* empty */\n }\n };\n\n try {\n const res = await fetchImpl(url, {\n method,\n headers: requestHeaders,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n const text = await res.text();\n let parsed: any;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n parsed = text;\n }\n\n emit({\n status: res.status,\n statusText: res.statusText,\n ok: res.ok,\n responseHeaders: redactHeaders(res.headers),\n responseBody: logBodies ? previewBody(parsed ?? text, redactFields) : undefined,\n });\n\n if (!res.ok) {\n const message =\n (parsed && (parsed.message || parsed.error)) || `Simplr API error ${res.status}`;\n throw new SimplrError(message, res.status, parsed);\n }\n return (parsed && typeof parsed === \"object\" && \"content\" in parsed\n ? parsed.content\n : parsed) as T;\n } catch (err) {\n if (err instanceof SimplrError) throw err;\n if (err instanceof Error && err.name === \"AbortError\") {\n emit({ ok: false, error: `timed out after ${timeoutMs}ms` });\n throw new SimplrError(`Request to ${path} timed out after ${timeoutMs}ms`, 0, null);\n }\n emit({ ok: false, error: err instanceof Error ? err.message : \"Network error\" });\n throw new SimplrError(err instanceof Error ? err.message : \"Network error\", 0, null);\n } finally {\n clearTimeout(timer);\n }\n }\n\n return {\n check: (input) => apiRequest<CheckResult>(\"POST\", \"/v1/check\", input),\n ingestLogs: (deviceId, logs) =>\n apiRequest(\"POST\", \"/v1/edge/logs\", { device_id: deviceId, logs }),\n flushNetworkLogs: () => shipper?.flush() ?? Promise.resolve(),\n close: () => shipper?.stop(),\n };\n}\n","/**\n * Framework-agnostic guard engine.\n *\n * `createGuard` returns an object that, given any request-like object, builds a\n * `CheckInput`, calls `/v1/check`, decides whether to block, optionally ships an\n * edge log, and fails open on error. Framework adapters (express/fastify/hono/\n * next) are thin wrappers over this engine.\n */\nimport { createClient, type SimplrClient } from \"./client.js\";\nimport type {\n BlockThreshold,\n CheckInput,\n CheckResult,\n GuardContext,\n GuardOptions,\n GuardOutcome,\n RiskLevel,\n} from \"./types.js\";\n\nconst RISK_ORDER: Record<RiskLevel, number> = {\n low: 0,\n medium: 1,\n high: 2,\n critical: 3,\n};\n\nconst DEFAULT_DEVICE_ID = \"simplr-edge-middleware\";\nconst DEFAULT_THRESHOLD: RiskLevel = \"high\";\n\n/** Numeric rank of a risk level. Unknown levels rank as the lowest (0). */\nexport function riskRank(level: RiskLevel | string | undefined): number {\n return RISK_ORDER[level as RiskLevel] ?? 0;\n}\n\n/** True when `level` is at least `min` in the low<medium<high<critical ordering. */\nexport function riskAtLeast(level: RiskLevel | string | undefined, min: RiskLevel): boolean {\n return riskRank(level) >= riskRank(min);\n}\n\n/** Read a header from either a plain bag (lowercase keys) or a Headers-like getter. */\nfunction readHeader(headers: unknown, name: string): string | undefined {\n if (!headers) return undefined;\n const h = headers as any;\n if (typeof h.get === \"function\") {\n const v = h.get(name);\n return v == null ? undefined : String(v);\n }\n // Plain object: try the lowercase key, then a case-insensitive scan.\n const lower = name.toLowerCase();\n if (lower in h && h[lower] != null) return String(h[lower]);\n for (const key of Object.keys(h)) {\n if (key.toLowerCase() === lower && h[key] != null) return String(h[key]);\n }\n return undefined;\n}\n\n/** Best-effort client IP from common header and socket locations. */\nfunction extractIp(req: any): string | undefined {\n const fwd = readHeader(req?.headers, \"x-forwarded-for\");\n if (fwd) return fwd.split(\",\")[0]!.trim();\n const real = readHeader(req?.headers, \"x-real-ip\");\n if (real) return real;\n return (\n req?.ip ||\n req?.socket?.remoteAddress ||\n req?.connection?.remoteAddress ||\n undefined\n );\n}\n\n/**\n * Default request → CheckInput extractor. Pulls the client IP and user-agent into\n * `device`, and lifts `email`/`phone` from a parsed JSON body when present.\n */\nexport function defaultExtract(req: any): CheckInput {\n const input: CheckInput = {};\n\n const device: Record<string, unknown> = {};\n const ip = extractIp(req);\n if (ip) device.ip = ip;\n const ua = readHeader(req?.headers, \"user-agent\");\n if (ua) device.user_agent = ua;\n if (Object.keys(device).length > 0) input.device = device;\n\n const body = req?.body;\n if (body && typeof body === \"object\") {\n const b = body as Record<string, unknown>;\n if (typeof b.email === \"string\") input.email = b.email;\n if (typeof b.phone === \"string\") input.phone = b.phone;\n }\n\n return input;\n}\n\nfunction normalizeBlock(\n block: GuardOptions[\"block\"],\n): (result: CheckResult) => boolean {\n if (typeof block === \"function\") return block;\n const threshold = (block as BlockThreshold | undefined)?.whenRiskAtLeast ?? DEFAULT_THRESHOLD;\n return (result) => riskAtLeast(result.risk_level, threshold);\n}\n\nexport interface Guard<Req = any> {\n /** The underlying thin Simplr client (check + edge log ingestion). */\n readonly client: SimplrClient;\n /** Run the full engine against a request; never throws when `failOpen`. */\n run(req: Req): Promise<GuardOutcome>;\n}\n\nexport function createGuard<Req = any>(options: GuardOptions<Req>): Guard<Req> {\n if (!options?.apiKey) throw new Error(\"createGuard: `apiKey` is required\");\n\n const client = createClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n timeoutMs: options.timeoutMs,\n fetch: options.fetch,\n onNetworkLog: options.onNetworkLog,\n logBodies: options.logBodies,\n redactFields: options.redactFields,\n shipNetworkLogs: options.shipNetworkLogs,\n applicationId: options.applicationId,\n environment: options.environment,\n });\n\n const extract = options.extract ?? defaultExtract;\n const shouldBlock = normalizeBlock(options.block);\n const failOpen = options.failOpen ?? true;\n const ingestLogs = options.ingestLogs ?? false;\n const deviceId = options.deviceId ?? DEFAULT_DEVICE_ID;\n\n async function run(req: Req): Promise<GuardOutcome> {\n const input = await extract(req);\n let result: CheckResult;\n try {\n result = await client.check(input);\n } catch (error) {\n if (failOpen) {\n return { result: null, blocked: false, input, error };\n }\n throw error;\n }\n\n const blocked = shouldBlock(result);\n const ctx: GuardContext<Req> = { req, input, blocked };\n\n if (options.onResult) {\n try {\n await options.onResult(result, ctx);\n } catch {\n // onResult is observational; never let it break the request.\n }\n }\n\n if (ingestLogs) {\n try {\n await client.ingestLogs(deviceId, [\n {\n category: \"security\",\n level: blocked ? \"warn\" : \"info\",\n message: blocked ? \"simplr guard blocked request\" : \"simplr guard checked request\",\n risk_level: result.risk_level,\n risk_score: result.risk_score,\n },\n ]);\n } catch {\n // Log shipping is best-effort and must never affect the request.\n }\n }\n\n return { result, blocked, input };\n }\n\n return { client, run };\n}\n\n/** Standard JSON envelope returned to the client on a blocked request. */\nexport function blockEnvelope(result: CheckResult) {\n return {\n error: \"request_blocked\",\n message: \"This request was blocked by Simplr fraud protection.\",\n risk_level: result.risk_level,\n risk_score: result.risk_score,\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/hono.ts","../src/network-log.ts","../src/network-shipper.ts","../src/client.ts","../src/core.ts"],"sourcesContent":["/**\n * Hono adapter. Middleware that stores the result via `c.set('simplr', result)`\n * (read it back with `c.get('simplr')`) and returns a 403 JSON response on block.\n *\n * ```ts\n * import { Hono } from \"hono\";\n * import { simplrHono } from \"@simplr-ai/express/hono\";\n * const app = new Hono();\n * app.use(\"*\", simplrHono({ apiKey: process.env.SIMPLR_API_KEY! }));\n * ```\n */\nimport { blockEnvelope, createGuard } from \"./core.js\";\nimport type { CheckResult, GuardOptions } from \"./types.js\";\n\ntype HonoContext = {\n req: {\n /** Hono exposes headers via `c.req.header(name)`. */\n header(name: string): string | undefined;\n /** Parsed JSON body, if read. The default extractor reads `c.get`-supplied body. */\n [key: string]: unknown;\n };\n set(key: string, value: unknown): void;\n get(key: string): unknown;\n json(body: unknown, status?: number): Response;\n};\ntype HonoNext = () => Promise<void>;\n\nexport type HonoMiddleware = (c: HonoContext, next: HonoNext) => Promise<Response | void>;\n\n/**\n * Build Hono middleware. Because Hono request bodies are read asynchronously,\n * the default extractor relies on headers; to also score body email/phone, set\n * a custom `extract` that awaits `await c.req.json()` (and stashes it as needed).\n */\nexport function simplrHono(options: GuardOptions<HonoContext>): HonoMiddleware {\n const guard = createGuard<HonoContext>({\n ...options,\n // Adapt Hono's header accessor into the shape the default extractor expects.\n extract:\n options.extract ??\n ((c) => {\n const headerGet = (name: string) => c.req.header(name);\n return defaultHonoExtract({ get: headerGet });\n }),\n });\n const failOpen = options.failOpen ?? true;\n\n return async function simplrMiddleware(c, next) {\n try {\n const outcome = await guard.run(c);\n c.set(\"simplr\", outcome.result);\n if (outcome.blocked && outcome.result) {\n return c.json(blockEnvelope(outcome.result), 403);\n }\n } catch (err) {\n if (!failOpen) throw err;\n c.set(\"simplr\", null);\n }\n await next();\n };\n}\n\n/** Minimal extractor over a Headers-like getter (IP + user-agent into device). */\nfunction defaultHonoExtract(headers: { get(name: string): string | undefined }) {\n const device: Record<string, unknown> = {};\n const fwd = headers.get(\"x-forwarded-for\");\n const ip = fwd ? fwd.split(\",\")[0]!.trim() : headers.get(\"x-real-ip\");\n if (ip) device.ip = ip;\n const ua = headers.get(\"user-agent\");\n if (ua) device.user_agent = ua;\n return Object.keys(device).length > 0 ? { device } : {};\n}\n\n/** Helper to read the stored result inside a handler: `getSimplr(c)`. */\nexport function getSimplr(c: HonoContext): CheckResult | null {\n return (c.get(\"simplr\") as CheckResult | null) ?? null;\n}\n","export type NetworkSource = \"frontend\" | \"backend\";\n\nexport interface NetworkLogEntry {\n id: string;\n requestId?: string;\n source: NetworkSource;\n timestamp: string;\n sdk?: string;\n applicationId?: string;\n environment?: string;\n method: string;\n url: string;\n requestHeaders?: Record<string, string>;\n requestBody?: unknown;\n status?: number;\n statusText?: string;\n responseHeaders?: Record<string, string>;\n responseBody?: unknown;\n durationMs?: number;\n ok?: boolean;\n error?: string;\n}\n\nexport type NetworkLogger = (entry: NetworkLogEntry) => void;\n\nconst SENSITIVE_HEADERS = new Set([\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n \"x-auth-token\",\n \"x-csrf-token\",\n \"x-xsrf-token\",\n]);\n\nconst SENSITIVE_KEY_PARTS = [\n \"password\",\n \"passwd\",\n \"secret\",\n \"token\",\n \"api_key\",\n \"apikey\",\n \"authorization\",\n \"auth\",\n \"credential\",\n \"private_key\",\n \"card\",\n \"cardnumber\",\n \"pan\",\n \"cvv\",\n \"cvc\",\n \"ssn\",\n \"pin\",\n \"otp\",\n];\n\nconst MAX_REDACT_DEPTH = 8;\n\nexport const MAX_BODY_CHARS = 10_000;\n\nfunction isSensitiveKey(key: string, extraKeys: string[]): boolean {\n const lower = key.toLowerCase();\n if (extraKeys.some((k) => lower === k.toLowerCase())) return true;\n return SENSITIVE_KEY_PARTS.some((part) => lower.includes(part));\n}\n\nfunction redactDeep(value: unknown, extraKeys: string[], depth = 0): unknown {\n if (depth >= MAX_REDACT_DEPTH) return \"[truncated]\";\n if (Array.isArray(value)) return value.map((v) => redactDeep(v, extraKeys, depth + 1));\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n out[key] = isSensitiveKey(key, extraKeys)\n ? \"[REDACTED]\"\n : redactDeep(val, extraKeys, depth + 1);\n }\n return out;\n }\n return value;\n}\n\nexport function redactHeaders(\n headers: Record<string, string> | Headers | undefined,\n): Record<string, string> | undefined {\n if (!headers) return undefined;\n const out: Record<string, string> = {};\n const set = (key: string, value: string) => {\n out[key] = SENSITIVE_HEADERS.has(key.toLowerCase()) ? \"[REDACTED]\" : value;\n };\n if (typeof (headers as Headers).forEach === \"function\" && !Array.isArray(headers)) {\n (headers as Headers).forEach((value, key) => set(key, value));\n } else {\n for (const [key, value] of Object.entries(headers as Record<string, string>)) {\n set(key, String(value));\n }\n }\n return out;\n}\n\nexport function previewBody(raw: unknown, redactFields: string[] = []): unknown {\n if (raw === undefined || raw === null) return undefined;\n let value: unknown = raw;\n if (typeof raw === \"string\") {\n try {\n value = JSON.parse(raw);\n } catch {\n return raw.length > MAX_BODY_CHARS ? raw.slice(0, MAX_BODY_CHARS) + \"…[truncated]\" : raw;\n }\n }\n const redacted = redactDeep(value, redactFields);\n let text: string;\n try {\n text = JSON.stringify(redacted);\n } catch {\n return \"[unserializable]\";\n }\n if (text.length > MAX_BODY_CHARS) {\n return text.slice(0, MAX_BODY_CHARS) + \"…[truncated]\";\n }\n return redacted;\n}\n\nexport function newLogId(): string {\n const uuid = (globalThis as any)?.crypto?.randomUUID;\n if (typeof uuid === \"function\") return uuid.call((globalThis as any).crypto);\n return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`;\n}\n","import type { NetworkLogEntry } from \"./network-log.js\";\n\nexport interface ShipperConfig {\n baseUrl: string;\n apiKey: string;\n fetchImpl: typeof fetch;\n sdk: string;\n applicationId?: string;\n environment?: string;\n batchSize?: number;\n flushIntervalMs?: number;\n}\n\nconst DEFAULT_BATCH = 25;\nconst DEFAULT_FLUSH_MS = 5000;\n\nexport class NetworkLogShipper {\n private readonly cfg: ShipperConfig;\n private queue: NetworkLogEntry[] = [];\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(cfg: ShipperConfig) {\n this.cfg = cfg;\n }\n\n start(): void {\n if (this.timer) return;\n const interval = this.cfg.flushIntervalMs ?? DEFAULT_FLUSH_MS;\n this.timer = setInterval(() => {\n void this.flush();\n }, interval);\n (this.timer as any)?.unref?.();\n }\n\n add(entry: NetworkLogEntry): void {\n this.queue.push({\n ...entry,\n sdk: this.cfg.sdk,\n applicationId: entry.applicationId ?? this.cfg.applicationId,\n environment: entry.environment ?? this.cfg.environment,\n });\n if (this.queue.length >= (this.cfg.batchSize ?? DEFAULT_BATCH)) {\n void this.flush();\n }\n }\n\n async flush(): Promise<void> {\n if (this.queue.length === 0) return;\n const logs = this.queue;\n this.queue = [];\n try {\n await this.cfg.fetchImpl(`${this.cfg.baseUrl}/v1/network-logs`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-API-Key\": this.cfg.apiKey,\n },\n body: JSON.stringify({ logs }),\n });\n } catch {\n /* empty */\n }\n }\n\n stop(): void {\n if (this.timer) clearInterval(this.timer);\n this.timer = null;\n void this.flush();\n }\n}\n","/**\n * Thin internal client.\n *\n * This is a ~40-line port of the minimal call path from `@simplr-ai/node`\n * (`apiRequest` + `check` + `edge.ingestLogs`) so that this package builds and\n * tests without an unbuilt workspace dependency or any network access.\n *\n * In production this package pairs with `@simplr-ai/node`; the endpoints, auth\n * header (`X-API-Key`), `{ success, message, content }` envelope unwrapping, and\n * 15s default timeout here are byte-for-byte compatible with that SDK and the\n * Simplr API contract.\n */\nimport type { CheckInput, CheckResult, EdgeLogEntry } from \"./types.js\";\nimport {\n newLogId,\n previewBody,\n redactHeaders,\n type NetworkLogger,\n} from \"./network-log.js\";\nimport { NetworkLogShipper } from \"./network-shipper.js\";\n\nconst DEFAULT_BASE_URL = \"https://api.simplr.sh\";\n\n/** Thrown when the Simplr API returns a non-2xx response or the request fails. */\nexport class SimplrError extends Error {\n readonly status: number;\n readonly body: unknown;\n constructor(message: string, status: number, body: unknown) {\n super(message);\n this.name = \"SimplrError\";\n this.status = status;\n this.body = body;\n }\n}\n\nexport interface ClientConfig {\n apiKey: string;\n baseUrl?: string;\n timeoutMs?: number;\n fetch?: typeof fetch;\n onNetworkLog?: NetworkLogger;\n logBodies?: boolean;\n redactFields?: string[];\n shipNetworkLogs?: boolean;\n applicationId?: string;\n environment?: string;\n logSelfCalls?: boolean;\n}\n\nexport interface SimplrClient {\n check(input: CheckInput): Promise<CheckResult>;\n ingestLogs(deviceId: string, logs: EdgeLogEntry[]): Promise<unknown>;\n flushNetworkLogs(): Promise<void>;\n close(): void;\n}\n\nexport function createClient(config: ClientConfig): SimplrClient {\n if (!config?.apiKey) throw new Error(\"Simplr: `apiKey` is required\");\n const baseUrl = (config.baseUrl || DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const timeoutMs = config.timeoutMs ?? 15000;\n const fetchImpl = config.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== \"function\") {\n throw new Error(\n \"Simplr: no global fetch available — use Node 18+ or pass `fetch` in options\",\n );\n }\n\n const logSelf = !!config.logSelfCalls;\n const logBodies = config.logBodies ?? (config.shipNetworkLogs && logSelf);\n const redactFields = config.redactFields;\n\n let shipper: NetworkLogShipper | undefined;\n if (config.shipNetworkLogs && logSelf) {\n shipper = new NetworkLogShipper({\n baseUrl,\n apiKey: config.apiKey,\n fetchImpl,\n sdk: \"express\",\n applicationId: config.applicationId,\n environment: config.environment,\n });\n shipper.start();\n }\n\n const userLog = config.onNetworkLog;\n const onNetworkLog: NetworkLogger | undefined =\n shipper || userLog\n ? (entry) => {\n shipper?.add(entry);\n userLog?.(entry);\n }\n : undefined;\n\n async function apiRequest<T>(method: string, path: string, body?: unknown): Promise<T> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n\n const url = `${baseUrl}${path}`;\n const requestHeaders = { \"Content-Type\": \"application/json\", \"X-API-Key\": config.apiKey };\n const startedAt = Date.now();\n const log = onNetworkLog\n ? {\n id: newLogId(),\n source: \"backend\" as const,\n timestamp: new Date(startedAt).toISOString(),\n method,\n url,\n requestHeaders: redactHeaders(requestHeaders),\n requestBody: logBodies ? previewBody(body, redactFields) : undefined,\n }\n : null;\n\n const emit = (extra: Record<string, unknown>) => {\n if (!log || !onNetworkLog) return;\n try {\n onNetworkLog({ ...log, durationMs: Date.now() - startedAt, ...extra });\n } catch {\n /* empty */\n }\n };\n\n try {\n const res = await fetchImpl(url, {\n method,\n headers: requestHeaders,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n const text = await res.text();\n let parsed: any;\n try {\n parsed = text ? JSON.parse(text) : undefined;\n } catch {\n parsed = text;\n }\n\n emit({\n status: res.status,\n statusText: res.statusText,\n ok: res.ok,\n responseHeaders: redactHeaders(res.headers),\n responseBody: logBodies ? previewBody(parsed ?? text, redactFields) : undefined,\n });\n\n if (!res.ok) {\n const message =\n (parsed && (parsed.message || parsed.error)) || `Simplr API error ${res.status}`;\n throw new SimplrError(message, res.status, parsed);\n }\n return (parsed && typeof parsed === \"object\" && \"content\" in parsed\n ? parsed.content\n : parsed) as T;\n } catch (err) {\n if (err instanceof SimplrError) throw err;\n if (err instanceof Error && err.name === \"AbortError\") {\n emit({ ok: false, error: `timed out after ${timeoutMs}ms` });\n throw new SimplrError(`Request to ${path} timed out after ${timeoutMs}ms`, 0, null);\n }\n emit({ ok: false, error: err instanceof Error ? err.message : \"Network error\" });\n throw new SimplrError(err instanceof Error ? err.message : \"Network error\", 0, null);\n } finally {\n clearTimeout(timer);\n }\n }\n\n return {\n check: (input) => apiRequest<CheckResult>(\"POST\", \"/v1/check\", input),\n ingestLogs: (deviceId, logs) =>\n apiRequest(\"POST\", \"/v1/edge/logs\", { device_id: deviceId, logs }),\n flushNetworkLogs: () => shipper?.flush() ?? Promise.resolve(),\n close: () => shipper?.stop(),\n };\n}\n","/**\n * Framework-agnostic guard engine.\n *\n * `createGuard` returns an object that, given any request-like object, builds a\n * `CheckInput`, calls `/v1/check`, decides whether to block, optionally ships an\n * edge log, and fails open on error. Framework adapters (express/fastify/hono/\n * next) are thin wrappers over this engine.\n */\nimport { createClient, type SimplrClient } from \"./client.js\";\nimport type {\n BlockThreshold,\n CheckInput,\n CheckResult,\n GuardContext,\n GuardOptions,\n GuardOutcome,\n RiskLevel,\n} from \"./types.js\";\n\nconst RISK_ORDER: Record<RiskLevel, number> = {\n low: 0,\n medium: 1,\n high: 2,\n critical: 3,\n};\n\nconst DEFAULT_DEVICE_ID = \"simplr-edge-middleware\";\nconst DEFAULT_THRESHOLD: RiskLevel = \"high\";\n\n/** Numeric rank of a risk level. Unknown levels rank as the lowest (0). */\nexport function riskRank(level: RiskLevel | string | undefined): number {\n return RISK_ORDER[level as RiskLevel] ?? 0;\n}\n\n/** True when `level` is at least `min` in the low<medium<high<critical ordering. */\nexport function riskAtLeast(level: RiskLevel | string | undefined, min: RiskLevel): boolean {\n return riskRank(level) >= riskRank(min);\n}\n\n/** Read a header from either a plain bag (lowercase keys) or a Headers-like getter. */\nfunction readHeader(headers: unknown, name: string): string | undefined {\n if (!headers) return undefined;\n const h = headers as any;\n if (typeof h.get === \"function\") {\n const v = h.get(name);\n return v == null ? undefined : String(v);\n }\n // Plain object: try the lowercase key, then a case-insensitive scan.\n const lower = name.toLowerCase();\n if (lower in h && h[lower] != null) return String(h[lower]);\n for (const key of Object.keys(h)) {\n if (key.toLowerCase() === lower && h[key] != null) return String(h[key]);\n }\n return undefined;\n}\n\n/** Best-effort client IP from common header and socket locations. */\nfunction extractIp(req: any): string | undefined {\n const fwd = readHeader(req?.headers, \"x-forwarded-for\");\n if (fwd) return fwd.split(\",\")[0]!.trim();\n const real = readHeader(req?.headers, \"x-real-ip\");\n if (real) return real;\n return (\n req?.ip ||\n req?.socket?.remoteAddress ||\n req?.connection?.remoteAddress ||\n undefined\n );\n}\n\n/**\n * Default request → CheckInput extractor. Pulls the client IP and user-agent into\n * `device`, and lifts `email`/`phone` from a parsed JSON body when present.\n */\nexport function defaultExtract(req: any): CheckInput {\n const input: CheckInput = {};\n\n const device: Record<string, unknown> = {};\n const ip = extractIp(req);\n if (ip) device.ip = ip;\n const ua = readHeader(req?.headers, \"user-agent\");\n if (ua) device.user_agent = ua;\n if (Object.keys(device).length > 0) input.device = device;\n\n const body = req?.body;\n if (body && typeof body === \"object\") {\n const b = body as Record<string, unknown>;\n if (typeof b.email === \"string\") input.email = b.email;\n if (typeof b.phone === \"string\") input.phone = b.phone;\n }\n\n return input;\n}\n\nfunction normalizeBlock(\n block: GuardOptions[\"block\"],\n): (result: CheckResult) => boolean {\n if (typeof block === \"function\") return block;\n const threshold = (block as BlockThreshold | undefined)?.whenRiskAtLeast ?? DEFAULT_THRESHOLD;\n return (result) => riskAtLeast(result.risk_level, threshold);\n}\n\nexport interface Guard<Req = any> {\n /** The underlying thin Simplr client (check + edge log ingestion). */\n readonly client: SimplrClient;\n /** Run the full engine against a request; never throws when `failOpen`. */\n run(req: Req): Promise<GuardOutcome>;\n}\n\nexport function createGuard<Req = any>(options: GuardOptions<Req>): Guard<Req> {\n if (!options?.apiKey) throw new Error(\"createGuard: `apiKey` is required\");\n\n const client = createClient({\n apiKey: options.apiKey,\n baseUrl: options.baseUrl,\n timeoutMs: options.timeoutMs,\n fetch: options.fetch,\n onNetworkLog: options.onNetworkLog,\n logBodies: options.logBodies,\n redactFields: options.redactFields,\n shipNetworkLogs: options.shipNetworkLogs,\n applicationId: options.applicationId,\n environment: options.environment,\n logSelfCalls: options.logSelfCalls,\n });\n\n const extract = options.extract ?? defaultExtract;\n const shouldBlock = normalizeBlock(options.block);\n const failOpen = options.failOpen ?? true;\n const ingestLogs = options.ingestLogs ?? false;\n const deviceId = options.deviceId ?? DEFAULT_DEVICE_ID;\n\n async function run(req: Req): Promise<GuardOutcome> {\n const input = await extract(req);\n let result: CheckResult;\n try {\n result = await client.check(input);\n } catch (error) {\n if (failOpen) {\n return { result: null, blocked: false, input, error };\n }\n throw error;\n }\n\n const blocked = shouldBlock(result);\n const ctx: GuardContext<Req> = { req, input, blocked };\n\n if (options.onResult) {\n try {\n await options.onResult(result, ctx);\n } catch {\n // onResult is observational; never let it break the request.\n }\n }\n\n if (ingestLogs) {\n try {\n await client.ingestLogs(deviceId, [\n {\n category: \"security\",\n level: blocked ? \"warn\" : \"info\",\n message: blocked ? \"simplr guard blocked request\" : \"simplr guard checked request\",\n risk_level: result.risk_level,\n risk_score: result.risk_score,\n },\n ]);\n } catch {\n // Log shipping is best-effort and must never affect the request.\n }\n }\n\n return { result, blocked, input };\n }\n\n return { client, run };\n}\n\n/** Standard JSON envelope returned to the client on a blocked request. */\nexport function blockEnvelope(result: CheckResult) {\n return {\n error: \"request_blocked\",\n message: \"This request was blocked by Simplr fraud protection.\",\n risk_level: result.risk_level,\n risk_score: result.risk_score,\n };\n}\n"],"mappings":"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","names":[]}
package/dist/hono.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as CheckResult, G as GuardOptions } from './types-YIpI-xG_.cjs';
1
+ import { C as CheckResult, G as GuardOptions } from './types-fP1HbtSf.cjs';
2
2
 
3
3
  type HonoContext = {
4
4
  req: {
package/dist/hono.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as CheckResult, G as GuardOptions } from './types-YIpI-xG_.js';
1
+ import { C as CheckResult, G as GuardOptions } from './types-fP1HbtSf.js';
2
2
 
3
3
  type HonoContext = {
4
4
  req: {
package/dist/hono.js CHANGED
@@ -164,10 +164,11 @@ function createClient(config) {
164
164
  "Simplr: no global fetch available \u2014 use Node 18+ or pass `fetch` in options"
165
165
  );
166
166
  }
167
- const logBodies = config.logBodies ?? config.shipNetworkLogs;
167
+ const logSelf = !!config.logSelfCalls;
168
+ const logBodies = config.logBodies ?? (config.shipNetworkLogs && logSelf);
168
169
  const redactFields = config.redactFields;
169
170
  let shipper;
170
- if (config.shipNetworkLogs) {
171
+ if (config.shipNetworkLogs && logSelf) {
171
172
  shipper = new NetworkLogShipper({
172
173
  baseUrl,
173
174
  apiKey: config.apiKey,
@@ -320,7 +321,8 @@ function createGuard(options) {
320
321
  redactFields: options.redactFields,
321
322
  shipNetworkLogs: options.shipNetworkLogs,
322
323
  applicationId: options.applicationId,
323
- environment: options.environment
324
+ environment: options.environment,
325
+ logSelfCalls: options.logSelfCalls
324
326
  });
325
327
  const extract = options.extract ?? defaultExtract;
326
328
  const shouldBlock = normalizeBlock(options.block);