Unclassified

IntegrateWise Continuity Bridge v2.0 — Security Model & Zero-Trust

Status: SUPERSEDED SECURITY DEEP-DIVE. Retained as source material only.
Validate identity, credential, storage and approval controls against
IMPLEMENTATION_CONTRACT_v1.1.md and the
current security code/evidence.

Domain Specification — Stage 1/2 Deep-Dive

Status: DRAFT-for-Review | Version: 2.0.0-SECURITY | Date: 2026-07-02
Domain Owner: Security Architecture | Feeds Into: Canonical Architecture v2.0


Table of Contents

  1. Security Doctrine
  2. Zero-Trust Architecture
  3. P0 Security Posture
  4. P1 Security Posture
  5. Tenant Isolation
  6. JWT Validation
  7. Service Binding Auth
  8. Cross-Tenant Prevention
  9. Secret Management
  10. Audit Model
  11. Compliance Framework
  12. Integration Points
  13. Threat Model
  14. TypeScript Contracts

1. Security Doctrine

"Every hop carries tenant_id. Every gate fails closed. Every audit write halts on failure."

PrincipleMeaningEnforcement
Fail-ClosedAbsence of proof is denial403 on any auth anomaly
Fail-LoudViolations logged, not swallowedSECURITY_VIOLATION audit event
Immutable AuditSynchronous, blocking audit writesFailure halts operation

Security is a cross-cutting fabric through all six layers: Identity → Ingress → Capability → Continuity → Governance → Provider Fabric. The Continuity Layer is the moat.


2. Zero-Trust Architecture

2.1 The Six Security Gates

Consumers ──▶ [G1 Identity] ──▶ [G2 Ingress] ──▶ [G3 Capability]
                  │                  │                  │
            gateway JWT       rate-limit/CORS     tool scope
                  │                  │                  │
                  ▼                  ▼                  ▼
             [G4 Continuity] ──▶ [G5 Governance] ──▶ [G6 Provider]
              tenant fence       confidence gate    credential wall
GateLayerServiceValidates
G1IdentitygatewayJWT signature, expiry, tenant_id claim
G2IngressgatewayRate limits, CORS, API key, plan quotas
G3Capabilitymcp-connector, iw-agent-runtimeTool scope, read/write posture
G4Continuitypipeline, continuityTenant fence on all Spine ops
G5GovernancegovernConfidence threshold, HITL, audit precondition
G6Provider Fabricact, connectorCredential wall, outbound scope, health

Invariant: No gate may be bypassed. G1 before G2, G2 before G3, and so on.


3. P0 Security Posture — Production Blockers

P0 vulnerabilities must close before production tenant data enters the platform.

#VulnerabilityFixOwnerClosure Criteria
P0-1Cross-tenant data leakwithTenant() on every D1 query. JOINs with tenant predicate on both sides.ALLPen-test: mismatched tenant_id → 403
P0-2Tenant-id spoofingGateway extracts tenant_id exclusively from JWT sub. Client x-tenant-id stripped at G1.gatewayFuzz: 1000 forged requests → all rewritten to JWT tenant
P0-3Tenant middleware no-opAll service bindings include x-iw-tenant-context. Receiving Workers validate presence.gatewayCode audit: zero bindings without tenant header
P0-4HITL DO global namespaceAll DO IDs from hash(tenant_id + ":" + user_id + ":" + purpose). Never idFromName("global").govern, twin-orchestratorDO list: no DO without tenant prefix
P0-5Invitation cross-tenant scanSELECT * FROM invitations WHERE tenant_id = ? AND code = ? — two-param minimum.tenantsSQL audit: all invitation queries have tenant predicate
P0-6Hardcoded secretsAll secrets in Cloudflare Secrets. admin manages rotation.admingrep -r "sk-" services/ returns zero
P0-7.env in repository.env gitignored. doppler.yaml for injection. Git history scanned.admingit log --all --full-history -- .env shows removal
P0-8SQL injectionDrizzle ORM exclusively. db-gate.ts enforces parameterization.continuity, knowledgeStatic analysis: zero db.raw() outside db-gate.ts
P0-9MCP-JWT bypassAuthorization: Bearer <jwt> required on /tools, /invoke, /mcp, /sessions. 403 otherwise.mcp-connectorIntegration test: all MCP endpoints reject unauthenticated
P0-10Auth root thrashingFreeze: Gateway JWT + API Keys only. Remove Clerk/Stack/Descope imports.gatewayZero @clerk/, @stack/, @descope/ imports

4. P1 Security Posture — Hardening

Close within 30 days of first production tenant onboarding.

#VulnerabilityFixOwnerSLA
P1-1Billing on SupabaseMigrate billing to D1. Usage aggregation in billing service.billing30d
P1-2Rate-limiter fails openCF Rate Limiting: 100/min per JWT, 1000/min per tenant. Fail-closed → 429.gateway14d
P1-3CORS reflects arbitrary originsWhitelist: app.integratewise.ai, *.integratewise.pages.dev, localhost:5173 (dev).gateway14d
P1-4MCP JWT bypass (residual)Close P0-9 + scope verification on every invocation (scope: read vs write).mcp-connector7d
P1-5Unsigned webhook auto-approveloader verifies Nango X-Nango-Signature. Unsigned → 401.loader14d
P1-6Webhook replay/timingwebhook-ingress idempotency keys. KV dedup: 24h window.webhook-ingress14d
P1-7Weak password hashingArgon2id or PBKDF2 600k+ iterations.tenants30d
P1-8Supabase client dependencyRemove @supabase/supabase-js. All DB via packages/lib/db-gate.ts.All60d

5. Tenant Isolation

5.1 Isolation Architecture

Tenant A (acme)     Tenant B (stark)      Tenant C (cz)
     │                    │                    │
     ▼                    ▼                    ▼
┌─────────┐          ┌─────────┐          ┌─────────┐
│JWT: sub │          │JWT: sub │          │JWT: sub │
│=u:acme  │          │=u:stark │          │=u:cz    │
└────┬────┘          └────┬────┘          └────┬────┘
     │                    │                    │
     ▼                    ▼                    ▼
┌─────────────────────────────────────────────────────────────┐
│                    GATEWAY (single Worker)                   │
│   [JWT verify] ──▶ [extract tenant_id] ──▶ [inject binding] │
└─────────────────────────────────────────────────────────────┘
     │                    │                    │
     ▼                    ▼                    ▼
┌─────────────────────────────────────────────────────────────┐
│                         D1 DATABASE                          │
│   entities, relationships, memory, audit_logs               │
│   ALL queries: WHERE tenant_id = ?                          │
│   Cross-tenant query = SECURITY_VIOLATION → 403 + alert     │
└─────────────────────────────────────────────────────────────┘

5.2 Isolation Rules

RuleImplementationViolation
R1 Query IsolationWHERE tenant_id = ? as first predicate403 + SECURITY_VIOLATION
R2 Schema IsolationPer-tenant tenant_spine_config on first authNo shared schema
R3 Memory IsolationMemory scopes sub-scoped by tenantCross-tenant read → 403
R4 Credential IsolationNango connectionId = tenant_id + ":" + providerCredential wall rejects cross-tenant
R5 Audit IsolationTenant-scoped; admin cross-tenant requires super_adminLogged in admin_audit_log

5.3 The withTenant() Helper

// packages/spine-schema/src/helpers.ts

  query: SQLiteSelectBuilder<T>,
  tenantId: string,
  table: T
): SQLiteSelectBuilder<T> {
  // HARD FENCE: tenant_id is first WHERE predicate → composite index prefix usage
  return query.where(eq(table.tenantId, tenantId));
}

  rows: T[],
  expectedTenantId: string,
  operation: string
): void {
  for (const row of rows) {
    if (row.tenantId !== expectedTenantId) {
      throw new SecurityViolationError(
        `Cross-tenant leak in ${operation}. Expected ${expectedTenantId}, got ${row.tenantId}`
      );
    }
  }
}

6. JWT Validation

6.1 JWT Claims Schema

// packages/mcp-types/src/guards.ts

export interface GatewayJwtClaims {
  jti: string; // Unique token ID (revocation, idempotency)
  sub: string; // "user_id:tenant_id"
  tenant_id: string; // Extracted from sub, NEVER from client input
  user_id: string;
  iss: "https://gateway.integratewise.ai";
  aud: string; // Target service binding name
  iat: number;
  exp: number; // Max 24h session, 1y API key
  scope: string; // "read write admin"
  role: "owner" | "admin" | "member" | "viewer" | "service_account";
  plan: "free" | "pro" | "enterprise" | "customer_zero";
  sid: string; // Session correlation ID
  type: "session" | "api_key";
}

export interface ValidatedIdentity {
  jti: string;
  tenantId: string;
  userId: string;
  role: GatewayJwtClaims["role"];
  scope: string[];
  plan: GatewayJwtClaims["plan"];
  sessionId: string;
  tokenType: "session" | "api_key";
  issuedAt: number;
  expiresAt: number;
}

6.2 JWT Validation Flow

Request ──▶ G1.1 Extract Auth header ──▶ Missing → 401
                │
                ▼
           G1.2 Verify RS256 signature (CF Secrets key) ──▶ Invalid → 403
                │
                ▼
           G1.3 Validate iat/exp/nbf (60s skew) ──▶ Expired → 401
                │
                ▼
           G1.4 Extract tenant_id from sub ("user:tenant") ──▶ Strip client x-tenant-id
                │
                ▼
           G1.5 Verify aud matches target binding
                │
                ▼
           G1.6 Inject binding headers: x-iw-tenant-id, x-iw-user-id,
                x-iw-jti, x-iw-scope, x-iw-role, x-iw-caller
                │
                ▼
           [Proceed to G2: Ingress Gate]

6.3 Gateway Auth Implementation

// services/gateway/src/auth.ts

const GATEWAY_ISSUER = "https://gateway.integratewise.ai";
const CLOCK_SKEW_MS = 60000;

export async function validateJwt(
  authHeader: string | null,
  targetAudience: string,
  env: Env
): Promise<ValidatedIdentity> {
  if (!authHeader?.startsWith("Bearer ")) {
    throw new AuthError("MISSING_AUTH_HEADER", 401, "Authorization required");
  }

  const publicKey = await importSPKI(env.JWT_PUBLIC_KEY, "RS256");
  const { payload } = await jwtVerify(authHeader.slice(7), publicKey, {
    issuer: GATEWAY_ISSUER,
    audience: targetAudience,
    clockTolerance: CLOCK_SKEW_MS / 1000,
  });

  const [userId, tenantId] = (payload.sub as string).split(":");
  if (!userId || !tenantId) {
    throw new AuthError("MALFORMED_SUBJECT", 403, "sub must be user_id:tenant_id");
  }

  return {
    jti: payload.jti as string,
    tenantId,
    userId,
    role: payload.role as GatewayJwtClaims["role"],
    scope: (payload.scope as string).split(" "),
    plan: payload.plan as GatewayJwtClaims["plan"],
    sessionId: payload.sid as string,
    tokenType: payload.type as "session" | "api_key",
    issuedAt: payload.iat as number,
    expiresAt: payload.exp as number,
  };
}

  try {
    const identity = await validateJwt(c.req.header("Authorization"), targetAudience, c.env);
    c.set("identity", identity);

    const headers = new Headers(c.req.raw.headers);
    headers.delete("x-tenant-id");
    headers.delete("x-iw-tenant-id"); // Strip spoofing

    headers.set("x-iw-tenant-id", identity.tenantId);
    headers.set("x-iw-user-id", identity.userId);
    headers.set("x-iw-jti", identity.jti);
    headers.set("x-iw-scope", identity.scope.join(" "));
    headers.set("x-iw-role", identity.role);

    await next();
  } catch (err) {
    // FAIL-CLOSED: any auth error = 403
    return c.json(
      { error: err instanceof AuthError ? err.code : "AUTH_FAILURE" },
      err instanceof AuthError ? err.status : 403
    );
  }
};

7. Service Binding Authentication

7.1 Internal Trust Model

Service bindings are not implicitly trusted. Each callee validates caller identity and tenant context.

Caller (gateway) ──fetch(binding, {
  headers: {
    'x-iw-tenant-id': 'acme', 'x-iw-user-id': 'user-123',
    'x-iw-jti': 'jwt-id', 'x-iw-scope': 'read',
    'x-iw-role': 'admin', 'x-iw-caller': 'gateway',
    'x-iw-correlation-id': 'uuid'
  }
})──▶ Callee (tenants)

Callee validates:
1. All required headers present
2. Caller in SERVICE_CALLER_ALLOWLISTS[serviceName]
3. tenant_id well-formed: /^[a-z0-9_-]{3,64}$/

7.2 Binding Validation Contract

// packages/mcp-types/src/guards.ts

export interface ServiceBindingHeaders {
  "x-iw-tenant-id": string;
  "x-iw-user-id": string;
  "x-iw-jti": string;
  "x-iw-scope": string;
  "x-iw-role": string;
  "x-iw-caller": string;
  "x-iw-correlation-id": string;
}

  tenants: ["gateway", "admin", "billing"],
  pipeline: ["normalizer", "act", "workflow", "gateway"],
  govern: ["twin-orchestrator", "workflow", "gateway"],
  act: ["gateway", "workflow", "hermes", "iw-agent-runtime"],
  continuity: ["pipeline", "triage", "twin-orchestrator"],
  "mcp-connector": ["gateway"],
  "agent-registry": ["gateway", "mcp-connector", "iw-agent-runtime"],
};

  headers: Headers,
  serviceName: string
): { tenantId: string; userId: string; scope: string[]; role: string; caller: string } {
  const tenantId = headers.get("x-iw-tenant-id");
  const userId = headers.get("x-iw-user-id");
  const jti = headers.get("x-iw-jti");
  const scope = headers.get("x-iw-scope");
  const role = headers.get("x-iw-role");
  const caller = headers.get("x-iw-caller");

  if (!tenantId || !userId || !jti || !scope || !role || !caller) {
    throw new ServiceBindingError(
      "MISSING_BINDING_CONTEXT",
      "Required: x-iw-tenant-id, x-iw-user-id, x-iw-jti, x-iw-scope, x-iw-role, x-iw-caller"
    );
  }

  const allowlist = SERVICE_CALLER_ALLOWLISTS[serviceName] || [];
  if (!allowlist.includes(caller)) {
    throw new ServiceBindingError(
      "UNAUTHORIZED_CALLER",
      `Service '${caller}' cannot call '${serviceName}'`
    );
  }

  if (!/^[a-z0-9_-]{3,64}$/.test(tenantId)) {
    throw new ServiceBindingError("INVALID_TENANT_ID", `Malformed tenant_id: ${tenantId}`);
  }

  return { tenantId, userId, scope: scope.split(" "), role, caller };
}

8. Cross-Tenant Prevention

8.1 Defense-in-Depth Layers

LayerMechanismFailure Mode
L1 Gatewaytenant_id from JWT only; client headers strippedSpoofed headers ignored
L2 Bindingx-iw-tenant-id on every internal callMissing header → 403
L3 QuerywithTenant() on every DB queryMissing predicate → linter rejection
L4 ResultassertTenantScope() on every result setCross-tenant row → exception
L5 TelemetryContinuous query pattern analysisAnomaly → alert

8.2 Detection & Response Flow

DETECTION:
1. Gateway: forged x-tenant-id → Stripped, rewritten
2. Binding: missing tenant header → 403 + audit
3. DB Query: no WHERE tenant_id → Linter blocks
4. Result: row.tenant_id ≠ expected → SecurityViolationError
5. Telemetry: anomalous JOIN → Alert

RESPONSE:
1. Return 403 (fail-closed, NOT 404)
2. Audit: type="SECURITY_VIOLATION", severity="CRITICAL",
   violation_type="CROSS_TENANT_ACCESS_ATTEMPT", jti=<revoke>
3. Alert: PagerDuty + #security-alerts
4. Investigate: same jti in 24h
5. If data leaked: customer notification 72h SLA
6. Post-mortem: 48h

8.3 Cross-Tenant Prevention in Adapters

// services/act/src/adapters/credentials.ts

export async function getCredentials(
  provider: string,
  tenantId: string,
  env: Env
): Promise<Credentials> {
  // L1: Validate format (injection defense)
  if (!/^[a-z0-9_-]{3,64}$/.test(tenantId)) {
    throw new SecurityViolationError("INVALID_TENANT_ID_FORMAT");
  }

  // L2: Fetch from CF Secrets using tenant-scoped key
  const secretName = `${provider}:${tenantId}:credentials`;
  const encryptedCreds = await env.CREDENTIALS_SECRETS.get(secretName);

  if (!encryptedCreds) {
    throw new AuthError(
      "CREDENTIALS_NOT_FOUND",
      403,
      `No credentials for ${provider} / ${tenantId}`
    );
  }

  // L3: Validate stored tenant matches request
  const creds: Credentials = JSON.parse(await decrypt(encryptedCreds, env.ENCRYPTION_KEY));
  if (creds.tenantId !== tenantId) {
    throw new SecurityViolationError(
      "CREDENTIAL_TENANT_MISMATCH",
      `Requested ${tenantId}, stored ${creds.tenantId}`
    );
  }

  return creds;
}

9. Secret Management

9.1 Secret Tiers

TierWhatStorageRotationAccess
T0Gateway RSA keys, DB encryptionCloudflare Secrets90denv.SECRET_NAME
T1OAuth tokens, API keysCF Secrets (namespaced)180d / refreshCredential wall in act
T2JWT tokens, SSE session stateKV (encrypted)JWT: 24h, SSE: 1hsessions namespace
T3Feature flags, rate limitsKVReal-timeconfig namespace
T4Raw audit, telemetryR2 (encrypted at rest)7ytelemetry bucket

9.2 Naming Conventions

Cloudflare Secrets:
├── PLATFORM/
│   ├── GATEWAY_RSA_PRIVATE_KEY, GATEWAY_RSA_PUBLIC_KEY
│   ├── DB_ENCRYPTION_KEY, API_KEY_SALT
│   ├── MCP_SIGNING_SECRET, NANGO_WEBHOOK_SECRET
│
└── TENANT-SCOPED (pattern: {provider}:{tenantId}:credentials)
    ├── salesforce:acme-corp:credentials
    ├── hubspot:acme-corp:credentials
    ├── stripe:acme-corp:webhook_secret
    └── github:stark-ind:credentials

9.3 Field-Level Encryption

// packages/lib/src/encryption.ts

export async function encryptField(plaintext: string, key: CryptoKey): Promise<string> {
  const iv = crypto.getRandomValues(new Uint8Array(12));
  const encoder = new TextEncoder();
  const ciphertext = await crypto.subtle.encrypt(
    { name: "AES-GCM", iv },
    key,
    encoder.encode(plaintext)
  );
  return `${btoa(String.fromCharCode(...iv))}:${btoa(String.fromCharCode(...new Uint8Array(ciphertext)))}`;
}

  "access_token",
  "refresh_token",
  "api_key",
  "password_hash",
  "email_raw",
  "phone_raw",
] as const;

9.4 Secret Rotation Flow

Trigger (90d cron or admin) ──▶ Generate new key (RSA-4096 / AES-256)
                                      │
                                      ▼
                         Dual-write phase (old + new valid, 24h grace)
                                      │
                                      ▼
                         Issue new JWTs with new key
                                      │
                                      ▼
                         Monitor: validation errors on old tokens?
                                      │
                                      ▼
                         After grace: revoke old key
                                      │
                                      ▼
                         Audit: type="SECRET_ROTATION", key fingerprint

10. Audit Model

10.1 Audit Log Schema

// packages/spine-schema/src/schema/audit.ts

  id: integer("id").primaryKey({ autoIncrement: true }),
  timestamp: integer("timestamp", { mode: "timestamp" }).notNull(),
  tenantId: text("tenant_id").notNull(),
  userId: text("user_id").notNull(),
  jti: text("jti").notNull(),
  sessionId: text("session_id"),
  type: text("type", {
    enum: [
      "ENTITY_CREATE",
      "ENTITY_UPDATE",
      "ENTITY_DELETE",
      "ENTITY_READ",
      "GOVERNANCE_APPROVE",
      "GOVERNANCE_REJECT",
      "GOVERNANCE_DISCARD",
      "MCP_INVOKE",
      "MCP_TOOL_LIST",
      "MCP_SESSION",
      "WORKFLOW_START",
      "WORKFLOW_STEP",
      "WORKFLOW_COMPLETE",
      "SECURITY_VIOLATION",
      "AUTH_FAILURE",
      "RATE_LIMIT_HIT",
      "OUTBOUND_CALL",
      "SYNC_START",
      "SYNC_COMPLETE",
      "TWIN_PROPOSE",
      "TWIN_REASONING",
      "ADMIN_OVERRIDE",
      "SECRET_ROTATION",
    ],
  }).notNull(),
  severity: text("severity", { enum: ["INFO", "WARN", "ERROR", "CRITICAL"] })
    .notNull()
    .default("INFO"),
  service: text("service").notNull(),
  action: text("action").notNull(),
  resourceType: text("resource_type"),
  resourceId: text("resource_id"),
  correlationId: text("correlation_id").notNull(),
  clientIp: text("client_ip"),
  userAgent: text("user_agent"),
  requestHash: text("request_hash"), // SHA-256
  responseHash: text("response_hash"), // SHA-256
  confidenceScore: real("confidence_score"),
  approvalState: text("approval_state", {
    enum: ["auto_approved", "pending", "approved", "rejected", "discarded"],
  }),
  statusCode: integer("status_code"),
  errorCode: text("error_code"),
  errorMessage: text("error_message"),
  dataClassification: text("data_classification", {
    enum: ["public", "internal", "confidential", "restricted"],
  }),
  retentionPolicy: text("retention_policy").notNull().default("7_years"),
  parentAuditId: integer("parent_audit_id"),
  chainHash: text("chain_hash").notNull(), // Tamper detection
});

10.2 Audit Write Flow (Fail-Loud)

Service performs auditable action
        │
        ▼
Build AuditEvent (all fields)
        │
        ▼
Call writeAuditLog() ──synchronous, blocking──▶ pipeline service writes to D1
                                                       │
                                              ┌────────┴────────┐
                                              ▼                 ▼
                                         SUCCESS           FAILURE
                                         Return 200         HALT OPERATION
                                         Continue           Throw AuditWriteFailure
                                                            Return 503

Rationale: Cannot prove compliance without audit. Silent failures = liability.

10.3 Audit Events by Service

ServiceEventsFrequency
gatewayAUTH_FAILURE, RATE_LIMIT_HIT, MCP_SESSIONPer request
mcp-connectorMCP_INVOKE, MCP_TOOL_LISTPer MCP op
pipelineENTITY_CREATE/UPDATE/DELETEPer Spine write
governGOVERNANCE_APPROVE/REJECT/DISCARDPer proposal
twin-orchestratorTWIN_PROPOSE, TWIN_REASONINGPer cycle
actOUTBOUND_CALLPer provider exec
workflowWORKFLOW_START/STEP/COMPLETEPer workflow
connector-syncSYNC_START, SYNC_COMPLETEPer sync
adminADMIN_OVERRIDE, SECRET_ROTATIONPer admin action
ALLSECURITY_VIOLATIONOn detection

11. Compliance Framework

11.1 Compliance Posture

FrameworkStatusScopeEvidence
SOC 2 Type IITarget: Month 12Security, Availability, Confidentialityaudit_logs, rotation records
GDPRRequired: LaunchSubject rights, erasure, portabilityTenant isolation, retention, export API
CCPA/CPRARequired: Launch (US)Consumer privacy rightsUnified privacy API
HIPAAFuture (BAA required)PHI handlingField-level encryption, access logs
ISO 27001Target: Month 18ISMSRisk register, documentation

11.2 Data Classification

ClassificationExamplesStorageEncryptionRetention
PublicMarketing, docsR2, PagesTLS in transit2y
InternalTenant config, metadataD1, KVTLS + field-level7y
ConfidentialEntity data, memoryD1TLS + field-level7y
RestrictedOAuth tokens, API keys, PIICF Secrets + D1AES-256-GCM + TLSToken life + 30d

11.3 GDPR Privacy API

// services/admin/src/privacy.ts

/** Article 17 — Right to Erasure */
export async function handleErasureRequest(
  tenantId: string,
  userId: string | null,
  initiatedBy: string,
  env: Env
): Promise<ErasureTicket> {
  const ticket: ErasureTicket = {
    id: crypto.randomUUID(),
    tenantId,
    userId,
    status: "queued",
    requestedAt: Date.now(),
    deadlineAt: Date.now() + 30 * 24 * 60 * 60 * 1000,
  };

  await env.WORKFLOW_QUEUE.send({
    type: "gdpr_erasure",
    ticket,
    stages: [
      "audit_backup",
      "spine_entities_delete",
      "spine_relationships_delete",
      "memory_delete",
      "connector_cleanup",
      "nango_disconnect",
      "kv_invalidate",
      "final_audit",
    ],
  });

  return ticket;
}

/** Article 20 — Data Portability */
export async function handlePortabilityRequest(
  tenantId: string,
  env: Env
): Promise<PortabilityExport> {
  const entities = await withTenant(db.select().from(schema.entities), tenantId, schema.entities);
  const relationships = await withTenant(
    db.select().from(schema.relationships),
    tenantId,
    schema.relationships
  );
  return {
    tenantId,
    exportedAt: Date.now(),
    format: "json",
    entities,
    relationships,
    memory: [],
    auditTrail: [],
  };
}

11.4 Retention Policies

Data TypeDefaultConfigurableDestruction
Audit logs7 yearsNoArchive to R2 cold after 2y
Active memory30 days (reinforcement resets)Yes (plan)Soft → archive → purge
Staging memory7 daysNoHard delete
Spine entitiesIndefiniteNoErasure request only
Telemetry traces30 daysYesAuto purge
Error logs90 daysNoAuto purge
Session tokens24hNoJWT expiry
API keys1 year maxNoRotation or revocation

12. Integration Points with Adjacent Layers

12.1 Security ↔ Identity (Layer 1)

Identity (gateway, tenants)
     │ 1. JWT issuance on auth
     │ 2. RBAC → included in JWT claims
     ▼
Contract: JWT is the ONLY identity artifact. Role drives Discovery visibility.
          Plan drives rate limits.

12.2 Security ↔ Ingress (Layer 2)

Ingress (gateway, webhook-ingress, mcp-connector)
     │ 1. Rate limiting: 100/min per JWT, 1000/min per tenant
     │ 2. CORS: whitelist only
     │ 3. Webhook signatures verified before processing
     ▼
Contract: All ingress validated before Capability Layer. Webhooks untrusted until signed.

12.3 Security ↔ Capability (Layer 3)

Capability (iw-agent-runtime, mcp-connector, think, hermes)
     │ 1. Tool catalog filtered by tenant's connected providers
     │ 2. Read-only default; write requires scope:write + governance
     │ 3. Model access gated by plan tier
     ▼
Contract: AI agents cannot discover other tenants' tools. Write = scope + governance.

12.4 Security ↔ Continuity (Layer 4 — THE MOAT)

Continuity (pipeline, continuity, spine-v2)
     │ 1. pipeline (ONLY writer) enforces WHERE tenant_id = ?
     │ 2. continuity validates memory scope against tenant
     │ 3. spine-v2 ensures SSOC UUIDs are tenant-scoped
     ▼
Contract: The moat. Data here passed all upstream gates. pipeline is the ONLY Spine writer.
          Memory compounding never crosses tenants.

12.5 Security ↔ Governance (Layer 5)

Governance (govern, workflow, twin-orchestrator)
     │ 1. 0.70/0.85 thresholds before act
     │ 2. 0.70–0.85 queued to My Desk with audit trail
     │ 3. No governance decision without audit log
     ▼
Contract: Final human safety layer. All proposals audited. HITL DOs per-tenant+user only.

12.6 Security ↔ Provider Fabric (Layer 6)

Provider Fabric (act, connector, connector-sync, external APIs)
     │ 1. act fetches per-tenant credentials from CF Secrets
     │ 2. Adapter validates capability path before provider call
     │ 3. Normalizer strips PII before Spine write
     ▼
Contract: No shared tokens. Provider errors never leak credentials.
          Outbound calls audited with request/response hashes.

13. Threat Model & Mitigation Matrix

ThreatSTRIDELikelihoodImpactMitigationLayer
Cross-tenant data leakElevationMediumCriticalwithTenant(), assertTenantScope(), binding headersL4
JWT theft/replaySpoofingMediumHighShort expiry, JTI tracking, RS256L1
Credential exposureInfo DisclosureLowCriticalCF Secrets, field-level encryption, no .envL6
DDoS / AbuseDoSHighMediumRate limiting (fail-closed), CF WAFL2
SQL injectionTamperingLowHighDrizzle ORM, no raw queriesL4
Webhook spoofingSpoofingMediumHighSignature verification, idempotency keysL2
AI prompt injectionTamperingMediumMediumInput validation, governance gateL3
Insider threat (admin)ElevationLowCriticalAdmin audit, 2-eyes, role separationL5
Supply chainTamperingMediumHighpnpm lockfile, Snyk, pinned versionsL0
Session fixationSpoofingLowMediumRegenerate on auth, short JWTsL1

14. TypeScript Contracts

14.1 Core Security Types

// packages/mcp-types/src/guards.ts

export class SecurityViolationError extends Error {
  constructor(
    public code: string,
    message?: string,
    public metadata?: Record<string, unknown>
  ) {
    super(message || `Security violation: ${code}`);
    this.name = "SecurityViolationError";
  }
}

export class AuthError extends Error {
  constructor(
    public code: string,
    public status: number,
    message: string
  ) {
    super(message);
    this.name = "AuthError";
  }
}

export class ServiceBindingError extends Error {
  constructor(
    public code: string,
    message: string
  ) {
    super(message);
    this.name = "ServiceBindingError";
  }
}

export class AuditWriteFailure extends Error {
  constructor(
    public originalOperation: string,
    public cause: Error
  ) {
    super(`Audit write failed for '${originalOperation}'. Halted. Cause: ${cause.message}`);
    this.name = "AuditWriteFailure";
  }
}

export interface DurableObjectIdPolicy {
  generate(tenantId: string, userId: string, purpose: string): string;
  parse(id: string): { tenantId: string; userId: string; purpose: string };
}

  generate(t, u, p) {
    const raw = `${t}:${u}:${p}`;
    return raw.length <= 128 ? raw : hashString(raw);
  },
  parse(id) {
    const parts = id.split(":");
    return parts.length < 3
      ? { tenantId: "HASHED", userId: "HASHED", purpose: "HASHED" }
      : { tenantId: parts[0], userId: parts[1], purpose: parts.slice(2).join(":") };
  },
};

function hashString(input: string): string {
  let h = 0;
  for (let i = 0; i < input.length; i++) h = ((h << 5) - h + input.charCodeAt(i)) | 0;
  return `hash_${Math.abs(h).toString(36)}`;
}

14.2 Rate Limit Configuration

// packages/lib/src/rate-limit.ts

export interface RateLimitConfig {
  userLimit: number;
  tenantLimit: number;
  ipLimit: number;
  burstSize: number;
  windowSeconds: number;
  exposeHeaders: boolean;
}

  free: {
    userLimit: 30,
    tenantLimit: 300,
    ipLimit: 10,
    burstSize: 5,
    windowSeconds: 60,
    exposeHeaders: true,
  },
  pro: {
    userLimit: 100,
    tenantLimit: 1000,
    ipLimit: 20,
    burstSize: 10,
    windowSeconds: 60,
    exposeHeaders: true,
  },
  enterprise: {
    userLimit: 500,
    tenantLimit: 5000,
    ipLimit: 50,
    burstSize: 50,
    windowSeconds: 60,
    exposeHeaders: true,
  },
  customer_zero: {
    userLimit: 1000,
    tenantLimit: 10000,
    ipLimit: 100,
    burstSize: 100,
    windowSeconds: 60,
    exposeHeaders: true,
  },
};

14.3 Privacy Types

// packages/spine-schema/src/schema/audit.ts

export interface ErasureTicket {
  id: string;
  tenantId: string;
  userId: string | null;
  status: "queued" | "in_progress" | "completed" | "failed";
  requestedAt: number;
  deadlineAt: number;
  completedAt?: number;
}

export interface PortabilityExport {
  tenantId: string;
  exportedAt: number;
  format: "json";
  entities: unknown[];
  relationships: unknown[];
  memory: unknown[];
  auditTrail: unknown[];
}

15. Operational Runbooks

15.1 Cross-Tenant Alert Response

  1. Acknowledge — Page on-call via PagerDuty
  2. Contain — Revoke JWT (jti) from alert
  3. Assess — Query audit logs for same user/tenant in 24h
  4. Investigate — Check if data returned before detection
  5. Remediate — Fix root cause (query, binding, or Gateway)
  6. Communicate — If leaked: customer notification 72h SLA
  7. Post-Mortem — Document within 48h

15.2 Secret Rotation

  1. Generate new key (RSA-4096 / AES-256)
  2. Dual-write: old + new valid for 24h grace
  3. Issue new JWTs with new key
  4. Monitor for old token validation errors
  5. After grace: revoke old key
  6. Audit: type: "SECRET_ROTATION"

15.3 Incident Severity

SeverityCriteriaResponseExample
CRITICALCross-tenant access, credential exposure15 minP0-1 detection
HIGHAuth bypass, privilege escalation1 hourP0-9 bypass
MEDIUMRate limit evasion, CORS misconfig4 hoursP1-3 finding
LOWInfo disclosure in logs24 hoursVerbose error message

16. Security Invariants

  1. Identity gates the door. Gateway JWT is the only trusted identity artifact.
  2. Every hop carries tenant_id. From Gateway to Provider Fabric and back.
  3. Cross-tenant access = 403 fail-loud. Never 404. Never silent.
  4. Audit write failure halts operation. Compliance is not optional.
  5. Pipeline is the only Spine writer. All context mutations flow through one gate.
  6. Read-only default. Write requires scope: write + governance approval.
  7. No shared credentials. Per-tenant credential wall in act adapter.
  8. Secrets in Cloudflare Secrets only. No .env. No hardcoded keys.
  9. Durable Objects are tenant-scoped. Never idFromName("global").
  10. Governance sits between reasoning and execution. The 0.70/0.85 law is invariant.

END OF SECURITY DOMAIN SPECIFICATION

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.