feat: verify context attestations via ES256/JWKS + rename client to appshell-crm

Proof #3 now supports the production asymmetric path, not just the dev shared secret.

- context-attestation.ts: PublicKeyResolverPort seam; verify() picks ES256 (JWKS by
  kid) when the client has a jwksUri, else HS256 (dev). signDevAttestationES256 helper.
- attestation-stores.ts: JwksPublicKeyResolver (jwks-rsa, one cached client per URI,
  refetch on rotation, fail-closed to NO_KEY).
- attestation.module.ts: inject the resolver into the verifier; dev-seed the client as
  clientType APPSHELL (it is the CRM browser-flow parent per the July-12 notes).
- rename the registered client crm-support-widget -> appshell-crm (the name reflects the
  attesting parent, not a widget).
- specs: ES256 (valid via JWKS, wrong key -> BAD_SIGNATURE, unknown kid -> NO_KEY, no
  resolver -> NO_KEY) + two stolen-token gate cases (wrong app binding -> APP_MISMATCH,
  wrong audience -> WRONG_AUDIENCE).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-15 12:56:10 +05:30
parent 85a78eb21e
commit e39caa3c80
8 changed files with 252 additions and 29 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ IIOS_AI_BUDGET_UNITS=100000 # per-scope AI cost-unit budget (KG-12)
# Audience IIOS requires on attestations addressed to it.
IIOS_ATTESTATION_AUDIENCE=iios-core
# Dev only: shared HS256 secret AppShell's stand-in signs attestations with (seeds the
# crm-support-widget client into the registry when IIOS_DEV_TOKENS=1).
# appshell-crm client into the registry when IIOS_DEV_TOKENS=1).
# IIOS_ATTESTATION_DEV_SECRET=appshell-dev-signing-key
# When '1', every guarded request MUST carry a valid X-Context-Attestation (else 403).
# Leave OFF until callers (AppShell/be-crm) forward attestations. Verified-if-present regardless.