UNPKG

@safeapi/safeapi

Version:

SafeAPI: Secure, deterministic, and tamper-resistant API policy engine for Node and browser.

118 lines (117 loc) 4.46 kB
import { createSafeApiAuditPayload } from "./audit/SafeApiAuditPayload"; import { deepFreeze } from "./shared"; import { evaluateGuards } from "./SafeApiGuardEngine"; import { computePolicyHash, normalizePolicy, } from "./SafeApiPolicyLifecycle"; function createDefaultContext() { return { clientId: "unknown", environment: "dev", metadata: {}, }; } /** @internal */ export class SafeApiEngine { policyResolver; guard; audit; hooks; constructor(options) { this.policyResolver = options.policyResolver; this.guard = options.guardEvaluator; this.audit = options.audit; this.hooks = options.hooks; } async process(request, context) { await this.invokePreValidate(request); const effectiveContext = { ...createDefaultContext(), ...context }; const resolvedPolicy = await this.policyResolver.resolve(request, effectiveContext); const policy = normalizePolicy(resolvedPolicy); const policyHash = computePolicyHash(policy); await this.invokePreExecute(request, policy); // Phase 13.2: Guard evaluation const guardMatrix = policy.guards ?? []; const guardEval = evaluateGuards(guardMatrix, effectiveContext, { trace: true }); if (!guardEval.allowed) { const response = Object.freeze({ requestId: request.requestId, status: "blocked", payload: request.payload, reason: `Blocked by guard: ${guardEval.matchedGuard}`, policyId: policy.policyId, }); await this.invokePostExecute(response, effectiveContext); if (this.audit) { const auditPayload = createSafeApiAuditPayload({ ruleId: policy.policyId ?? policy.ruleSetId, phase: "safeapi", priority: 0, eventType: "evaluation", timestampMs: 1, meta: { safeApi: { policyHash, guardTrace: guardEval.guardTrace, result: "deny", }, }, }); await this.audit.record(request, effectiveContext, response, auditPayload); } return response; } // If allowed, continue with existing pipeline const guardResult = await this.guard.evaluate(request, effectiveContext, policy); const response = this.buildResponse(request, policy, guardResult); await this.invokePostExecute(response, effectiveContext); if (this.audit) { const auditPayload = createSafeApiAuditPayload({ ruleId: policy.policyId ?? policy.ruleSetId, phase: "safeapi", priority: 0, eventType: "evaluation", timestampMs: 1, meta: { safeApi: { policyHash, guardTrace: guardEval.guardTrace, result: "allow", }, }, }); await this.audit.record(request, effectiveContext, response, auditPayload); } return response; } buildResponse(request, policy, guardResult) { const status = guardResult.allowed ? guardResult.modifiedPayload !== undefined ? "modified" : "allowed" : "blocked"; return Object.freeze({ requestId: request.requestId, status, payload: guardResult.modifiedPayload ?? request.payload, reason: guardResult.reason, policyId: policy.policyId, }); } async invokePreValidate(request) { const handler = this.hooks?.preValidate; if (!handler) return; await handler(deepFreeze(request)); } async invokePreExecute(request, policy) { const handler = this.hooks?.preExecute; if (!handler) return; await handler(deepFreeze(request), deepFreeze(policy)); } async invokePostExecute(response, context) { const handler = this.hooks?.postExecute; if (!handler) return; await handler(deepFreeze(response), deepFreeze(context)); } }