{"slug":"business/v2-security","title":"IntegrateWise Continuity Bridge v2.0 — Security Model & Zero-Trust","section":"business","sectionLabel":"Business","status":"unclassified","type":"document","path":"business/v2-security.mdx","bytes":41615,"description":null,"updated":null,"source":"integratewise/Integratewise-docs-wiki","content":"\n> **Status: SUPERSEDED SECURITY DEEP-DIVE.** Retained as source material only.\n> Validate identity, credential, storage and approval controls against\n> [`IMPLEMENTATION_CONTRACT_v1.1.md`](./IMPLEMENTATION_CONTRACT_v1.1.md) and the\n> current security code/evidence.\n\n# Domain Specification — Stage 1/2 Deep-Dive\n\n**Status:** DRAFT-for-Review | **Version:** 2.0.0-SECURITY | **Date:** 2026-07-02\n**Domain Owner:** Security Architecture | **Feeds Into:** Canonical Architecture v2.0\n\n---\n\n## Table of Contents\n\n1. [Security Doctrine](#1-security-doctrine)\n2. [Zero-Trust Architecture](#2-zero-trust-architecture)\n3. [P0 Security Posture](#3-p0-security-posture)\n4. [P1 Security Posture](#4-p1-security-posture)\n5. [Tenant Isolation](#5-tenant-isolation)\n6. [JWT Validation](#6-jwt-validation)\n7. [Service Binding Auth](#7-service-binding-authentication)\n8. [Cross-Tenant Prevention](#8-cross-tenant-prevention)\n9. [Secret Management](#9-secret-management)\n10. [Audit Model](#10-audit-model)\n11. [Compliance Framework](#11-compliance-framework)\n12. [Integration Points](#12-integration-points)\n13. [Threat Model](#13-threat-model)\n14. [TypeScript Contracts](#14-typescript-contracts)\n\n---\n\n## 1. Security Doctrine\n\n> **\"Every hop carries tenant_id. Every gate fails closed. Every audit write halts on failure.\"**\n\n| Principle           | Meaning                            | Enforcement                      |\n| ------------------- | ---------------------------------- | -------------------------------- |\n| **Fail-Closed**     | Absence of proof is denial         | 403 on any auth anomaly          |\n| **Fail-Loud**       | Violations logged, not swallowed   | `SECURITY_VIOLATION` audit event |\n| **Immutable Audit** | Synchronous, blocking audit writes | Failure halts operation          |\n\nSecurity is a **cross-cutting fabric** through all six layers: Identity → Ingress → Capability → Continuity → Governance → Provider Fabric. The Continuity Layer is the **moat**.\n\n---\n\n## 2. Zero-Trust Architecture\n\n### 2.1 The Six Security Gates\n\n```\nConsumers ──▶ [G1 Identity] ──▶ [G2 Ingress] ──▶ [G3 Capability]\n                  │                  │                  │\n            gateway JWT       rate-limit/CORS     tool scope\n                  │                  │                  │\n                  ▼                  ▼                  ▼\n             [G4 Continuity] ──▶ [G5 Governance] ──▶ [G6 Provider]\n              tenant fence       confidence gate    credential wall\n```\n\n| Gate   | Layer           | Service                             | Validates                                      |\n| ------ | --------------- | ----------------------------------- | ---------------------------------------------- |\n| **G1** | Identity        | `gateway`                           | JWT signature, expiry, `tenant_id` claim       |\n| **G2** | Ingress         | `gateway`                           | Rate limits, CORS, API key, plan quotas        |\n| **G3** | Capability      | `mcp-connector`, `iw-agent-runtime` | Tool scope, read/write posture                 |\n| **G4** | Continuity      | `pipeline`, `continuity`            | Tenant fence on all Spine ops                  |\n| **G5** | Governance      | `govern`                            | Confidence threshold, HITL, audit precondition |\n| **G6** | Provider Fabric | `act`, `connector`                  | Credential wall, outbound scope, health        |\n\n**Invariant:** No gate may be bypassed. G1 before G2, G2 before G3, and so on.\n\n---\n\n## 3. P0 Security Posture — Production Blockers\n\nP0 vulnerabilities **must close before production** tenant data enters the platform.\n\n| #     | Vulnerability                | Fix                                                                                                | Owner                         | Closure Criteria                                           |\n| ----- | ---------------------------- | -------------------------------------------------------------------------------------------------- | ----------------------------- | ---------------------------------------------------------- |\n| P0-1  | Cross-tenant data leak       | `withTenant()` on every D1 query. JOINs with tenant predicate on both sides.                       | ALL                           | Pen-test: mismatched tenant_id → 403                       |\n| P0-2  | Tenant-id spoofing           | Gateway extracts `tenant_id` **exclusively** from JWT `sub`. Client `x-tenant-id` stripped at G1.  | `gateway`                     | Fuzz: 1000 forged requests → all rewritten to JWT tenant   |\n| P0-3  | Tenant middleware no-op      | All service bindings include `x-iw-tenant-context`. Receiving Workers validate presence.           | `gateway`                     | Code audit: zero bindings without tenant header            |\n| P0-4  | HITL DO global namespace     | All DO IDs from `hash(tenant_id + \":\" + user_id + \":\" + purpose)`. Never `idFromName(\"global\")`.   | `govern`, `twin-orchestrator` | DO list: no DO without tenant prefix                       |\n| P0-5  | Invitation cross-tenant scan | `SELECT * FROM invitations WHERE tenant_id = ? AND code = ?` — two-param minimum.                  | `tenants`                     | SQL audit: all invitation queries have tenant predicate    |\n| P0-6  | Hardcoded secrets            | All secrets in Cloudflare Secrets. `admin` manages rotation.                                       | `admin`                       | `grep -r \"sk-\" services/` returns zero                     |\n| P0-7  | `.env` in repository         | `.env` gitignored. `doppler.yaml` for injection. Git history scanned.                              | `admin`                       | `git log --all --full-history -- .env` shows removal       |\n| P0-8  | SQL injection                | Drizzle ORM exclusively. `db-gate.ts` enforces parameterization.                                   | `continuity`, `knowledge`     | Static analysis: zero `db.raw()` outside `db-gate.ts`      |\n| P0-9  | MCP-JWT bypass               | `Authorization: Bearer <jwt>` required on `/tools`, `/invoke`, `/mcp`, `/sessions`. 403 otherwise. | `mcp-connector`               | Integration test: all MCP endpoints reject unauthenticated |\n| P0-10 | Auth root thrashing          | **Freeze:** Gateway JWT + API Keys only. Remove Clerk/Stack/Descope imports.                       | `gateway`                     | Zero `@clerk/`, `@stack/`, `@descope/` imports             |\n\n---\n\n## 4. P1 Security Posture — Hardening\n\nClose within **30 days** of first production tenant onboarding.\n\n| #    | Vulnerability                   | Fix                                                                                     | Owner             | SLA |\n| ---- | ------------------------------- | --------------------------------------------------------------------------------------- | ----------------- | --- |\n| P1-1 | Billing on Supabase             | Migrate billing to D1. Usage aggregation in `billing` service.                          | `billing`         | 30d |\n| P1-2 | Rate-limiter fails open         | CF Rate Limiting: 100/min per JWT, 1000/min per tenant. Fail-closed → 429.              | `gateway`         | 14d |\n| P1-3 | CORS reflects arbitrary origins | Whitelist: `app.integratewise.ai`, `*.integratewise.pages.dev`, `localhost:5173` (dev). | `gateway`         | 14d |\n| P1-4 | MCP JWT bypass (residual)       | Close P0-9 + scope verification on every invocation (`scope: read` vs `write`).         | `mcp-connector`   | 7d  |\n| P1-5 | Unsigned webhook auto-approve   | `loader` verifies Nango `X-Nango-Signature`. Unsigned → 401.                            | `loader`          | 14d |\n| P1-6 | Webhook replay/timing           | `webhook-ingress` idempotency keys. KV dedup: 24h window.                               | `webhook-ingress` | 14d |\n| P1-7 | Weak password hashing           | Argon2id or PBKDF2 600k+ iterations.                                                    | `tenants`         | 30d |\n| P1-8 | Supabase client dependency      | Remove `@supabase/supabase-js`. All DB via `packages/lib/db-gate.ts`.                   | All               | 60d |\n\n---\n\n## 5. Tenant Isolation\n\n### 5.1 Isolation Architecture\n\n```\nTenant A (acme)     Tenant B (stark)      Tenant C (cz)\n     │                    │                    │\n     ▼                    ▼                    ▼\n┌─────────┐          ┌─────────┐          ┌─────────┐\n│JWT: sub │          │JWT: sub │          │JWT: sub │\n│=u:acme  │          │=u:stark │          │=u:cz    │\n└────┬────┘          └────┬────┘          └────┬────┘\n     │                    │                    │\n     ▼                    ▼                    ▼\n┌─────────────────────────────────────────────────────────────┐\n│                    GATEWAY (single Worker)                   │\n│   [JWT verify] ──▶ [extract tenant_id] ──▶ [inject binding] │\n└─────────────────────────────────────────────────────────────┘\n     │                    │                    │\n     ▼                    ▼                    ▼\n┌─────────────────────────────────────────────────────────────┐\n│                         D1 DATABASE                          │\n│   entities, relationships, memory, audit_logs               │\n│   ALL queries: WHERE tenant_id = ?                          │\n│   Cross-tenant query = SECURITY_VIOLATION → 403 + alert     │\n└─────────────────────────────────────────────────────────────┘\n```\n\n### 5.2 Isolation Rules\n\n| Rule                        | Implementation                                           | Violation                            |\n| --------------------------- | -------------------------------------------------------- | ------------------------------------ |\n| **R1** Query Isolation      | `WHERE tenant_id = ?` as first predicate                 | 403 + `SECURITY_VIOLATION`           |\n| **R2** Schema Isolation     | Per-tenant `tenant_spine_config` on first auth           | No shared schema                     |\n| **R3** Memory Isolation     | Memory scopes sub-scoped by tenant                       | Cross-tenant read → 403              |\n| **R4** Credential Isolation | Nango `connectionId = tenant_id + \":\" + provider`        | Credential wall rejects cross-tenant |\n| **R5** Audit Isolation      | Tenant-scoped; admin cross-tenant requires `super_admin` | Logged in `admin_audit_log`          |\n\n### 5.3 The `withTenant()` Helper\n\n```typescript\n// packages/spine-schema/src/helpers.ts\n\n  query: SQLiteSelectBuilder<T>,\n  tenantId: string,\n  table: T\n): SQLiteSelectBuilder<T> {\n  // HARD FENCE: tenant_id is first WHERE predicate → composite index prefix usage\n  return query.where(eq(table.tenantId, tenantId));\n}\n\n  rows: T[],\n  expectedTenantId: string,\n  operation: string\n): void {\n  for (const row of rows) {\n    if (row.tenantId !== expectedTenantId) {\n      throw new SecurityViolationError(\n        `Cross-tenant leak in ${operation}. Expected ${expectedTenantId}, got ${row.tenantId}`\n      );\n    }\n  }\n}\n```\n\n---\n\n## 6. JWT Validation\n\n### 6.1 JWT Claims Schema\n\n```typescript\n// packages/mcp-types/src/guards.ts\n\nexport interface GatewayJwtClaims {\n  jti: string; // Unique token ID (revocation, idempotency)\n  sub: string; // \"user_id:tenant_id\"\n  tenant_id: string; // Extracted from sub, NEVER from client input\n  user_id: string;\n  iss: \"https://gateway.integratewise.ai\";\n  aud: string; // Target service binding name\n  iat: number;\n  exp: number; // Max 24h session, 1y API key\n  scope: string; // \"read write admin\"\n  role: \"owner\" | \"admin\" | \"member\" | \"viewer\" | \"service_account\";\n  plan: \"free\" | \"pro\" | \"enterprise\" | \"customer_zero\";\n  sid: string; // Session correlation ID\n  type: \"session\" | \"api_key\";\n}\n\nexport interface ValidatedIdentity {\n  jti: string;\n  tenantId: string;\n  userId: string;\n  role: GatewayJwtClaims[\"role\"];\n  scope: string[];\n  plan: GatewayJwtClaims[\"plan\"];\n  sessionId: string;\n  tokenType: \"session\" | \"api_key\";\n  issuedAt: number;\n  expiresAt: number;\n}\n```\n\n### 6.2 JWT Validation Flow\n\n```\nRequest ──▶ G1.1 Extract Auth header ──▶ Missing → 401\n                │\n                ▼\n           G1.2 Verify RS256 signature (CF Secrets key) ──▶ Invalid → 403\n                │\n                ▼\n           G1.3 Validate iat/exp/nbf (60s skew) ──▶ Expired → 401\n                │\n                ▼\n           G1.4 Extract tenant_id from sub (\"user:tenant\") ──▶ Strip client x-tenant-id\n                │\n                ▼\n           G1.5 Verify aud matches target binding\n                │\n                ▼\n           G1.6 Inject binding headers: x-iw-tenant-id, x-iw-user-id,\n                x-iw-jti, x-iw-scope, x-iw-role, x-iw-caller\n                │\n                ▼\n           [Proceed to G2: Ingress Gate]\n```\n\n### 6.3 Gateway Auth Implementation\n\n```typescript\n// services/gateway/src/auth.ts\n\nconst GATEWAY_ISSUER = \"https://gateway.integratewise.ai\";\nconst CLOCK_SKEW_MS = 60000;\n\nexport async function validateJwt(\n  authHeader: string | null,\n  targetAudience: string,\n  env: Env\n): Promise<ValidatedIdentity> {\n  if (!authHeader?.startsWith(\"Bearer \")) {\n    throw new AuthError(\"MISSING_AUTH_HEADER\", 401, \"Authorization required\");\n  }\n\n  const publicKey = await importSPKI(env.JWT_PUBLIC_KEY, \"RS256\");\n  const { payload } = await jwtVerify(authHeader.slice(7), publicKey, {\n    issuer: GATEWAY_ISSUER,\n    audience: targetAudience,\n    clockTolerance: CLOCK_SKEW_MS / 1000,\n  });\n\n  const [userId, tenantId] = (payload.sub as string).split(\":\");\n  if (!userId || !tenantId) {\n    throw new AuthError(\"MALFORMED_SUBJECT\", 403, \"sub must be user_id:tenant_id\");\n  }\n\n  return {\n    jti: payload.jti as string,\n    tenantId,\n    userId,\n    role: payload.role as GatewayJwtClaims[\"role\"],\n    scope: (payload.scope as string).split(\" \"),\n    plan: payload.plan as GatewayJwtClaims[\"plan\"],\n    sessionId: payload.sid as string,\n    tokenType: payload.type as \"session\" | \"api_key\",\n    issuedAt: payload.iat as number,\n    expiresAt: payload.exp as number,\n  };\n}\n\n  try {\n    const identity = await validateJwt(c.req.header(\"Authorization\"), targetAudience, c.env);\n    c.set(\"identity\", identity);\n\n    const headers = new Headers(c.req.raw.headers);\n    headers.delete(\"x-tenant-id\");\n    headers.delete(\"x-iw-tenant-id\"); // Strip spoofing\n\n    headers.set(\"x-iw-tenant-id\", identity.tenantId);\n    headers.set(\"x-iw-user-id\", identity.userId);\n    headers.set(\"x-iw-jti\", identity.jti);\n    headers.set(\"x-iw-scope\", identity.scope.join(\" \"));\n    headers.set(\"x-iw-role\", identity.role);\n\n    await next();\n  } catch (err) {\n    // FAIL-CLOSED: any auth error = 403\n    return c.json(\n      { error: err instanceof AuthError ? err.code : \"AUTH_FAILURE\" },\n      err instanceof AuthError ? err.status : 403\n    );\n  }\n};\n```\n\n---\n\n## 7. Service Binding Authentication\n\n### 7.1 Internal Trust Model\n\nService bindings are **not implicitly trusted**. Each callee validates caller identity and tenant context.\n\n```\nCaller (gateway) ──fetch(binding, {\n  headers: {\n    'x-iw-tenant-id': 'acme', 'x-iw-user-id': 'user-123',\n    'x-iw-jti': 'jwt-id', 'x-iw-scope': 'read',\n    'x-iw-role': 'admin', 'x-iw-caller': 'gateway',\n    'x-iw-correlation-id': 'uuid'\n  }\n})──▶ Callee (tenants)\n\nCallee validates:\n1. All required headers present\n2. Caller in SERVICE_CALLER_ALLOWLISTS[serviceName]\n3. tenant_id well-formed: /^[a-z0-9_-]{3,64}$/\n```\n\n### 7.2 Binding Validation Contract\n\n```typescript\n// packages/mcp-types/src/guards.ts\n\nexport interface ServiceBindingHeaders {\n  \"x-iw-tenant-id\": string;\n  \"x-iw-user-id\": string;\n  \"x-iw-jti\": string;\n  \"x-iw-scope\": string;\n  \"x-iw-role\": string;\n  \"x-iw-caller\": string;\n  \"x-iw-correlation-id\": string;\n}\n\n  tenants: [\"gateway\", \"admin\", \"billing\"],\n  pipeline: [\"normalizer\", \"act\", \"workflow\", \"gateway\"],\n  govern: [\"twin-orchestrator\", \"workflow\", \"gateway\"],\n  act: [\"gateway\", \"workflow\", \"hermes\", \"iw-agent-runtime\"],\n  continuity: [\"pipeline\", \"triage\", \"twin-orchestrator\"],\n  \"mcp-connector\": [\"gateway\"],\n  \"agent-registry\": [\"gateway\", \"mcp-connector\", \"iw-agent-runtime\"],\n};\n\n  headers: Headers,\n  serviceName: string\n): { tenantId: string; userId: string; scope: string[]; role: string; caller: string } {\n  const tenantId = headers.get(\"x-iw-tenant-id\");\n  const userId = headers.get(\"x-iw-user-id\");\n  const jti = headers.get(\"x-iw-jti\");\n  const scope = headers.get(\"x-iw-scope\");\n  const role = headers.get(\"x-iw-role\");\n  const caller = headers.get(\"x-iw-caller\");\n\n  if (!tenantId || !userId || !jti || !scope || !role || !caller) {\n    throw new ServiceBindingError(\n      \"MISSING_BINDING_CONTEXT\",\n      \"Required: x-iw-tenant-id, x-iw-user-id, x-iw-jti, x-iw-scope, x-iw-role, x-iw-caller\"\n    );\n  }\n\n  const allowlist = SERVICE_CALLER_ALLOWLISTS[serviceName] || [];\n  if (!allowlist.includes(caller)) {\n    throw new ServiceBindingError(\n      \"UNAUTHORIZED_CALLER\",\n      `Service '${caller}' cannot call '${serviceName}'`\n    );\n  }\n\n  if (!/^[a-z0-9_-]{3,64}$/.test(tenantId)) {\n    throw new ServiceBindingError(\"INVALID_TENANT_ID\", `Malformed tenant_id: ${tenantId}`);\n  }\n\n  return { tenantId, userId, scope: scope.split(\" \"), role, caller };\n}\n```\n\n---\n\n## 8. Cross-Tenant Prevention\n\n### 8.1 Defense-in-Depth Layers\n\n| Layer            | Mechanism                                          | Failure Mode                         |\n| ---------------- | -------------------------------------------------- | ------------------------------------ |\n| **L1** Gateway   | `tenant_id` from JWT only; client headers stripped | Spoofed headers ignored              |\n| **L2** Binding   | `x-iw-tenant-id` on every internal call            | Missing header → 403                 |\n| **L3** Query     | `withTenant()` on every DB query                   | Missing predicate → linter rejection |\n| **L4** Result    | `assertTenantScope()` on every result set          | Cross-tenant row → exception         |\n| **L5** Telemetry | Continuous query pattern analysis                  | Anomaly → alert                      |\n\n### 8.2 Detection & Response Flow\n\n```\nDETECTION:\n1. Gateway: forged x-tenant-id → Stripped, rewritten\n2. Binding: missing tenant header → 403 + audit\n3. DB Query: no WHERE tenant_id → Linter blocks\n4. Result: row.tenant_id ≠ expected → SecurityViolationError\n5. Telemetry: anomalous JOIN → Alert\n\nRESPONSE:\n1. Return 403 (fail-closed, NOT 404)\n2. Audit: type=\"SECURITY_VIOLATION\", severity=\"CRITICAL\",\n   violation_type=\"CROSS_TENANT_ACCESS_ATTEMPT\", jti=<revoke>\n3. Alert: PagerDuty + #security-alerts\n4. Investigate: same jti in 24h\n5. If data leaked: customer notification 72h SLA\n6. Post-mortem: 48h\n```\n\n### 8.3 Cross-Tenant Prevention in Adapters\n\n```typescript\n// services/act/src/adapters/credentials.ts\n\nexport async function getCredentials(\n  provider: string,\n  tenantId: string,\n  env: Env\n): Promise<Credentials> {\n  // L1: Validate format (injection defense)\n  if (!/^[a-z0-9_-]{3,64}$/.test(tenantId)) {\n    throw new SecurityViolationError(\"INVALID_TENANT_ID_FORMAT\");\n  }\n\n  // L2: Fetch from CF Secrets using tenant-scoped key\n  const secretName = `${provider}:${tenantId}:credentials`;\n  const encryptedCreds = await env.CREDENTIALS_SECRETS.get(secretName);\n\n  if (!encryptedCreds) {\n    throw new AuthError(\n      \"CREDENTIALS_NOT_FOUND\",\n      403,\n      `No credentials for ${provider} / ${tenantId}`\n    );\n  }\n\n  // L3: Validate stored tenant matches request\n  const creds: Credentials = JSON.parse(await decrypt(encryptedCreds, env.ENCRYPTION_KEY));\n  if (creds.tenantId !== tenantId) {\n    throw new SecurityViolationError(\n      \"CREDENTIAL_TENANT_MISMATCH\",\n      `Requested ${tenantId}, stored ${creds.tenantId}`\n    );\n  }\n\n  return creds;\n}\n```\n\n---\n\n## 9. Secret Management\n\n### 9.1 Secret Tiers\n\n| Tier   | What                            | Storage                 | Rotation          | Access                   |\n| ------ | ------------------------------- | ----------------------- | ----------------- | ------------------------ |\n| **T0** | Gateway RSA keys, DB encryption | Cloudflare Secrets      | 90d               | `env.SECRET_NAME`        |\n| **T1** | OAuth tokens, API keys          | CF Secrets (namespaced) | 180d / refresh    | Credential wall in `act` |\n| **T2** | JWT tokens, SSE session state   | KV (encrypted)          | JWT: 24h, SSE: 1h | `sessions` namespace     |\n| **T3** | Feature flags, rate limits      | KV                      | Real-time         | `config` namespace       |\n| **T4** | Raw audit, telemetry            | R2 (encrypted at rest)  | 7y                | `telemetry` bucket       |\n\n### 9.2 Naming Conventions\n\n```\nCloudflare Secrets:\n├── PLATFORM/\n│   ├── GATEWAY_RSA_PRIVATE_KEY, GATEWAY_RSA_PUBLIC_KEY\n│   ├── DB_ENCRYPTION_KEY, API_KEY_SALT\n│   ├── MCP_SIGNING_SECRET, NANGO_WEBHOOK_SECRET\n│\n└── TENANT-SCOPED (pattern: {provider}:{tenantId}:credentials)\n    ├── salesforce:acme-corp:credentials\n    ├── hubspot:acme-corp:credentials\n    ├── stripe:acme-corp:webhook_secret\n    └── github:stark-ind:credentials\n```\n\n### 9.3 Field-Level Encryption\n\n```typescript\n// packages/lib/src/encryption.ts\n\nexport async function encryptField(plaintext: string, key: CryptoKey): Promise<string> {\n  const iv = crypto.getRandomValues(new Uint8Array(12));\n  const encoder = new TextEncoder();\n  const ciphertext = await crypto.subtle.encrypt(\n    { name: \"AES-GCM\", iv },\n    key,\n    encoder.encode(plaintext)\n  );\n  return `${btoa(String.fromCharCode(...iv))}:${btoa(String.fromCharCode(...new Uint8Array(ciphertext)))}`;\n}\n\n  \"access_token\",\n  \"refresh_token\",\n  \"api_key\",\n  \"password_hash\",\n  \"email_raw\",\n  \"phone_raw\",\n] as const;\n```\n\n### 9.4 Secret Rotation Flow\n\n```\nTrigger (90d cron or admin) ──▶ Generate new key (RSA-4096 / AES-256)\n                                      │\n                                      ▼\n                         Dual-write phase (old + new valid, 24h grace)\n                                      │\n                                      ▼\n                         Issue new JWTs with new key\n                                      │\n                                      ▼\n                         Monitor: validation errors on old tokens?\n                                      │\n                                      ▼\n                         After grace: revoke old key\n                                      │\n                                      ▼\n                         Audit: type=\"SECRET_ROTATION\", key fingerprint\n```\n\n---\n\n## 10. Audit Model\n\n### 10.1 Audit Log Schema\n\n```typescript\n// packages/spine-schema/src/schema/audit.ts\n\n  id: integer(\"id\").primaryKey({ autoIncrement: true }),\n  timestamp: integer(\"timestamp\", { mode: \"timestamp\" }).notNull(),\n  tenantId: text(\"tenant_id\").notNull(),\n  userId: text(\"user_id\").notNull(),\n  jti: text(\"jti\").notNull(),\n  sessionId: text(\"session_id\"),\n  type: text(\"type\", {\n    enum: [\n      \"ENTITY_CREATE\",\n      \"ENTITY_UPDATE\",\n      \"ENTITY_DELETE\",\n      \"ENTITY_READ\",\n      \"GOVERNANCE_APPROVE\",\n      \"GOVERNANCE_REJECT\",\n      \"GOVERNANCE_DISCARD\",\n      \"MCP_INVOKE\",\n      \"MCP_TOOL_LIST\",\n      \"MCP_SESSION\",\n      \"WORKFLOW_START\",\n      \"WORKFLOW_STEP\",\n      \"WORKFLOW_COMPLETE\",\n      \"SECURITY_VIOLATION\",\n      \"AUTH_FAILURE\",\n      \"RATE_LIMIT_HIT\",\n      \"OUTBOUND_CALL\",\n      \"SYNC_START\",\n      \"SYNC_COMPLETE\",\n      \"TWIN_PROPOSE\",\n      \"TWIN_REASONING\",\n      \"ADMIN_OVERRIDE\",\n      \"SECRET_ROTATION\",\n    ],\n  }).notNull(),\n  severity: text(\"severity\", { enum: [\"INFO\", \"WARN\", \"ERROR\", \"CRITICAL\"] })\n    .notNull()\n    .default(\"INFO\"),\n  service: text(\"service\").notNull(),\n  action: text(\"action\").notNull(),\n  resourceType: text(\"resource_type\"),\n  resourceId: text(\"resource_id\"),\n  correlationId: text(\"correlation_id\").notNull(),\n  clientIp: text(\"client_ip\"),\n  userAgent: text(\"user_agent\"),\n  requestHash: text(\"request_hash\"), // SHA-256\n  responseHash: text(\"response_hash\"), // SHA-256\n  confidenceScore: real(\"confidence_score\"),\n  approvalState: text(\"approval_state\", {\n    enum: [\"auto_approved\", \"pending\", \"approved\", \"rejected\", \"discarded\"],\n  }),\n  statusCode: integer(\"status_code\"),\n  errorCode: text(\"error_code\"),\n  errorMessage: text(\"error_message\"),\n  dataClassification: text(\"data_classification\", {\n    enum: [\"public\", \"internal\", \"confidential\", \"restricted\"],\n  }),\n  retentionPolicy: text(\"retention_policy\").notNull().default(\"7_years\"),\n  parentAuditId: integer(\"parent_audit_id\"),\n  chainHash: text(\"chain_hash\").notNull(), // Tamper detection\n});\n```\n\n### 10.2 Audit Write Flow (Fail-Loud)\n\n```\nService performs auditable action\n        │\n        ▼\nBuild AuditEvent (all fields)\n        │\n        ▼\nCall writeAuditLog() ──synchronous, blocking──▶ pipeline service writes to D1\n                                                       │\n                                              ┌────────┴────────┐\n                                              ▼                 ▼\n                                         SUCCESS           FAILURE\n                                         Return 200         HALT OPERATION\n                                         Continue           Throw AuditWriteFailure\n                                                            Return 503\n```\n\n**Rationale:** Cannot prove compliance without audit. Silent failures = liability.\n\n### 10.3 Audit Events by Service\n\n| Service             | Events                                          | Frequency         |\n| ------------------- | ----------------------------------------------- | ----------------- |\n| `gateway`           | `AUTH_FAILURE`, `RATE_LIMIT_HIT`, `MCP_SESSION` | Per request       |\n| `mcp-connector`     | `MCP_INVOKE`, `MCP_TOOL_LIST`                   | Per MCP op        |\n| `pipeline`          | `ENTITY_CREATE/UPDATE/DELETE`                   | Per Spine write   |\n| `govern`            | `GOVERNANCE_APPROVE/REJECT/DISCARD`             | Per proposal      |\n| `twin-orchestrator` | `TWIN_PROPOSE`, `TWIN_REASONING`                | Per cycle         |\n| `act`               | `OUTBOUND_CALL`                                 | Per provider exec |\n| `workflow`          | `WORKFLOW_START/STEP/COMPLETE`                  | Per workflow      |\n| `connector-sync`    | `SYNC_START`, `SYNC_COMPLETE`                   | Per sync          |\n| `admin`             | `ADMIN_OVERRIDE`, `SECRET_ROTATION`             | Per admin action  |\n| ALL                 | `SECURITY_VIOLATION`                            | On detection      |\n\n---\n\n## 11. Compliance Framework\n\n### 11.1 Compliance Posture\n\n| Framework         | Status                | Scope                                   | Evidence                                |\n| ----------------- | --------------------- | --------------------------------------- | --------------------------------------- |\n| **SOC 2 Type II** | Target: Month 12      | Security, Availability, Confidentiality | `audit_logs`, rotation records          |\n| **GDPR**          | Required: Launch      | Subject rights, erasure, portability    | Tenant isolation, retention, export API |\n| **CCPA/CPRA**     | Required: Launch (US) | Consumer privacy rights                 | Unified privacy API                     |\n| **HIPAA**         | Future (BAA required) | PHI handling                            | Field-level encryption, access logs     |\n| **ISO 27001**     | Target: Month 18      | ISMS                                    | Risk register, documentation            |\n\n### 11.2 Data Classification\n\n| Classification   | Examples                    | Storage         | Encryption        | Retention        |\n| ---------------- | --------------------------- | --------------- | ----------------- | ---------------- |\n| **Public**       | Marketing, docs             | R2, Pages       | TLS in transit    | 2y               |\n| **Internal**     | Tenant config, metadata     | D1, KV          | TLS + field-level | 7y               |\n| **Confidential** | Entity data, memory         | D1              | TLS + field-level | 7y               |\n| **Restricted**   | OAuth tokens, API keys, PII | CF Secrets + D1 | AES-256-GCM + TLS | Token life + 30d |\n\n### 11.3 GDPR Privacy API\n\n```typescript\n// services/admin/src/privacy.ts\n\n/** Article 17 — Right to Erasure */\nexport async function handleErasureRequest(\n  tenantId: string,\n  userId: string | null,\n  initiatedBy: string,\n  env: Env\n): Promise<ErasureTicket> {\n  const ticket: ErasureTicket = {\n    id: crypto.randomUUID(),\n    tenantId,\n    userId,\n    status: \"queued\",\n    requestedAt: Date.now(),\n    deadlineAt: Date.now() + 30 * 24 * 60 * 60 * 1000,\n  };\n\n  await env.WORKFLOW_QUEUE.send({\n    type: \"gdpr_erasure\",\n    ticket,\n    stages: [\n      \"audit_backup\",\n      \"spine_entities_delete\",\n      \"spine_relationships_delete\",\n      \"memory_delete\",\n      \"connector_cleanup\",\n      \"nango_disconnect\",\n      \"kv_invalidate\",\n      \"final_audit\",\n    ],\n  });\n\n  return ticket;\n}\n\n/** Article 20 — Data Portability */\nexport async function handlePortabilityRequest(\n  tenantId: string,\n  env: Env\n): Promise<PortabilityExport> {\n  const entities = await withTenant(db.select().from(schema.entities), tenantId, schema.entities);\n  const relationships = await withTenant(\n    db.select().from(schema.relationships),\n    tenantId,\n    schema.relationships\n  );\n  return {\n    tenantId,\n    exportedAt: Date.now(),\n    format: \"json\",\n    entities,\n    relationships,\n    memory: [],\n    auditTrail: [],\n  };\n}\n```\n\n### 11.4 Retention Policies\n\n| Data Type        | Default                        | Configurable | Destruction                 |\n| ---------------- | ------------------------------ | ------------ | --------------------------- |\n| Audit logs       | 7 years                        | No           | Archive to R2 cold after 2y |\n| Active memory    | 30 days (reinforcement resets) | Yes (plan)   | Soft → archive → purge      |\n| Staging memory   | 7 days                         | No           | Hard delete                 |\n| Spine entities   | Indefinite                     | No           | Erasure request only        |\n| Telemetry traces | 30 days                        | Yes          | Auto purge                  |\n| Error logs       | 90 days                        | No           | Auto purge                  |\n| Session tokens   | 24h                            | No           | JWT expiry                  |\n| API keys         | 1 year max                     | No           | Rotation or revocation      |\n\n---\n\n## 12. Integration Points with Adjacent Layers\n\n### 12.1 Security ↔ Identity (Layer 1)\n\n```\nIdentity (gateway, tenants)\n     │ 1. JWT issuance on auth\n     │ 2. RBAC → included in JWT claims\n     ▼\nContract: JWT is the ONLY identity artifact. Role drives Discovery visibility.\n          Plan drives rate limits.\n```\n\n### 12.2 Security ↔ Ingress (Layer 2)\n\n```\nIngress (gateway, webhook-ingress, mcp-connector)\n     │ 1. Rate limiting: 100/min per JWT, 1000/min per tenant\n     │ 2. CORS: whitelist only\n     │ 3. Webhook signatures verified before processing\n     ▼\nContract: All ingress validated before Capability Layer. Webhooks untrusted until signed.\n```\n\n### 12.3 Security ↔ Capability (Layer 3)\n\n```\nCapability (iw-agent-runtime, mcp-connector, think, hermes)\n     │ 1. Tool catalog filtered by tenant's connected providers\n     │ 2. Read-only default; write requires scope:write + governance\n     │ 3. Model access gated by plan tier\n     ▼\nContract: AI agents cannot discover other tenants' tools. Write = scope + governance.\n```\n\n### 12.4 Security ↔ Continuity (Layer 4 — THE MOAT)\n\n```\nContinuity (pipeline, continuity, spine-v2)\n     │ 1. pipeline (ONLY writer) enforces WHERE tenant_id = ?\n     │ 2. continuity validates memory scope against tenant\n     │ 3. spine-v2 ensures SSOC UUIDs are tenant-scoped\n     ▼\nContract: The moat. Data here passed all upstream gates. pipeline is the ONLY Spine writer.\n          Memory compounding never crosses tenants.\n```\n\n### 12.5 Security ↔ Governance (Layer 5)\n\n```\nGovernance (govern, workflow, twin-orchestrator)\n     │ 1. 0.70/0.85 thresholds before act\n     │ 2. 0.70–0.85 queued to My Desk with audit trail\n     │ 3. No governance decision without audit log\n     ▼\nContract: Final human safety layer. All proposals audited. HITL DOs per-tenant+user only.\n```\n\n### 12.6 Security ↔ Provider Fabric (Layer 6)\n\n```\nProvider Fabric (act, connector, connector-sync, external APIs)\n     │ 1. act fetches per-tenant credentials from CF Secrets\n     │ 2. Adapter validates capability path before provider call\n     │ 3. Normalizer strips PII before Spine write\n     ▼\nContract: No shared tokens. Provider errors never leak credentials.\n          Outbound calls audited with request/response hashes.\n```\n\n---\n\n## 13. Threat Model & Mitigation Matrix\n\n| Threat                 | STRIDE          | Likelihood | Impact   | Mitigation                                             | Layer |\n| ---------------------- | --------------- | ---------- | -------- | ------------------------------------------------------ | ----- |\n| Cross-tenant data leak | Elevation       | Medium     | Critical | `withTenant()`, `assertTenantScope()`, binding headers | L4    |\n| JWT theft/replay       | Spoofing        | Medium     | High     | Short expiry, JTI tracking, RS256                      | L1    |\n| Credential exposure    | Info Disclosure | Low        | Critical | CF Secrets, field-level encryption, no `.env`          | L6    |\n| DDoS / Abuse           | DoS             | High       | Medium   | Rate limiting (fail-closed), CF WAF                    | L2    |\n| SQL injection          | Tampering       | Low        | High     | Drizzle ORM, no raw queries                            | L4    |\n| Webhook spoofing       | Spoofing        | Medium     | High     | Signature verification, idempotency keys               | L2    |\n| AI prompt injection    | Tampering       | Medium     | Medium   | Input validation, governance gate                      | L3    |\n| Insider threat (admin) | Elevation       | Low        | Critical | Admin audit, 2-eyes, role separation                   | L5    |\n| Supply chain           | Tampering       | Medium     | High     | pnpm lockfile, Snyk, pinned versions                   | L0    |\n| Session fixation       | Spoofing        | Low        | Medium   | Regenerate on auth, short JWTs                         | L1    |\n\n---\n\n## 14. TypeScript Contracts\n\n### 14.1 Core Security Types\n\n```typescript\n// packages/mcp-types/src/guards.ts\n\nexport class SecurityViolationError extends Error {\n  constructor(\n    public code: string,\n    message?: string,\n    public metadata?: Record<string, unknown>\n  ) {\n    super(message || `Security violation: ${code}`);\n    this.name = \"SecurityViolationError\";\n  }\n}\n\nexport class AuthError extends Error {\n  constructor(\n    public code: string,\n    public status: number,\n    message: string\n  ) {\n    super(message);\n    this.name = \"AuthError\";\n  }\n}\n\nexport class ServiceBindingError extends Error {\n  constructor(\n    public code: string,\n    message: string\n  ) {\n    super(message);\n    this.name = \"ServiceBindingError\";\n  }\n}\n\nexport class AuditWriteFailure extends Error {\n  constructor(\n    public originalOperation: string,\n    public cause: Error\n  ) {\n    super(`Audit write failed for '${originalOperation}'. Halted. Cause: ${cause.message}`);\n    this.name = \"AuditWriteFailure\";\n  }\n}\n\nexport interface DurableObjectIdPolicy {\n  generate(tenantId: string, userId: string, purpose: string): string;\n  parse(id: string): { tenantId: string; userId: string; purpose: string };\n}\n\n  generate(t, u, p) {\n    const raw = `${t}:${u}:${p}`;\n    return raw.length <= 128 ? raw : hashString(raw);\n  },\n  parse(id) {\n    const parts = id.split(\":\");\n    return parts.length < 3\n      ? { tenantId: \"HASHED\", userId: \"HASHED\", purpose: \"HASHED\" }\n      : { tenantId: parts[0], userId: parts[1], purpose: parts.slice(2).join(\":\") };\n  },\n};\n\nfunction hashString(input: string): string {\n  let h = 0;\n  for (let i = 0; i < input.length; i++) h = ((h << 5) - h + input.charCodeAt(i)) | 0;\n  return `hash_${Math.abs(h).toString(36)}`;\n}\n```\n\n### 14.2 Rate Limit Configuration\n\n```typescript\n// packages/lib/src/rate-limit.ts\n\nexport interface RateLimitConfig {\n  userLimit: number;\n  tenantLimit: number;\n  ipLimit: number;\n  burstSize: number;\n  windowSeconds: number;\n  exposeHeaders: boolean;\n}\n\n  free: {\n    userLimit: 30,\n    tenantLimit: 300,\n    ipLimit: 10,\n    burstSize: 5,\n    windowSeconds: 60,\n    exposeHeaders: true,\n  },\n  pro: {\n    userLimit: 100,\n    tenantLimit: 1000,\n    ipLimit: 20,\n    burstSize: 10,\n    windowSeconds: 60,\n    exposeHeaders: true,\n  },\n  enterprise: {\n    userLimit: 500,\n    tenantLimit: 5000,\n    ipLimit: 50,\n    burstSize: 50,\n    windowSeconds: 60,\n    exposeHeaders: true,\n  },\n  customer_zero: {\n    userLimit: 1000,\n    tenantLimit: 10000,\n    ipLimit: 100,\n    burstSize: 100,\n    windowSeconds: 60,\n    exposeHeaders: true,\n  },\n};\n```\n\n### 14.3 Privacy Types\n\n```typescript\n// packages/spine-schema/src/schema/audit.ts\n\nexport interface ErasureTicket {\n  id: string;\n  tenantId: string;\n  userId: string | null;\n  status: \"queued\" | \"in_progress\" | \"completed\" | \"failed\";\n  requestedAt: number;\n  deadlineAt: number;\n  completedAt?: number;\n}\n\nexport interface PortabilityExport {\n  tenantId: string;\n  exportedAt: number;\n  format: \"json\";\n  entities: unknown[];\n  relationships: unknown[];\n  memory: unknown[];\n  auditTrail: unknown[];\n}\n```\n\n---\n\n## 15. Operational Runbooks\n\n### 15.1 Cross-Tenant Alert Response\n\n1. **Acknowledge** — Page on-call via PagerDuty\n2. **Contain** — Revoke JWT (`jti`) from alert\n3. **Assess** — Query audit logs for same user/tenant in 24h\n4. **Investigate** — Check if data returned before detection\n5. **Remediate** — Fix root cause (query, binding, or Gateway)\n6. **Communicate** — If leaked: customer notification 72h SLA\n7. **Post-Mortem** — Document within 48h\n\n### 15.2 Secret Rotation\n\n1. Generate new key (RSA-4096 / AES-256)\n2. Dual-write: old + new valid for 24h grace\n3. Issue new JWTs with new key\n4. Monitor for old token validation errors\n5. After grace: revoke old key\n6. Audit: `type: \"SECRET_ROTATION\"`\n\n### 15.3 Incident Severity\n\n| Severity     | Criteria                                 | Response | Example               |\n| ------------ | ---------------------------------------- | -------- | --------------------- |\n| **CRITICAL** | Cross-tenant access, credential exposure | 15 min   | P0-1 detection        |\n| **HIGH**     | Auth bypass, privilege escalation        | 1 hour   | P0-9 bypass           |\n| **MEDIUM**   | Rate limit evasion, CORS misconfig       | 4 hours  | P1-3 finding          |\n| **LOW**      | Info disclosure in logs                  | 24 hours | Verbose error message |\n\n---\n\n## 16. Security Invariants\n\n> 1. **Identity gates the door.** Gateway JWT is the only trusted identity artifact.\n> 2. **Every hop carries `tenant_id`.** From Gateway to Provider Fabric and back.\n> 3. **Cross-tenant access = 403 fail-loud.** Never 404. Never silent.\n> 4. **Audit write failure halts operation.** Compliance is not optional.\n> 5. **Pipeline is the only Spine writer.** All context mutations flow through one gate.\n> 6. **Read-only default.** Write requires `scope: write` + governance approval.\n> 7. **No shared credentials.** Per-tenant credential wall in `act` adapter.\n> 8. **Secrets in Cloudflare Secrets only.** No `.env`. No hardcoded keys.\n> 9. **Durable Objects are tenant-scoped.** Never `idFromName(\"global\")`.\n> 10. **Governance sits between reasoning and execution.** The 0.70/0.85 law is invariant.\n\n---\n\n**END OF SECURITY DOMAIN SPECIFICATION**\n\n_IntegrateWise Continuity Bridge v2.0 — Security Model and Zero-Trust Architecture. Stage 1/2 deep-dive. P0 must close before production. P1 within 30 days of launch._\n"}