Skip to content

@foxbook/sdk-claim v0.3.0 — STH signature verification (security)

Latest

Choose a tag to compare

@cloakmaster cloakmaster released this 29 Jul 17:07
· 9 commits to main since this release
f4dce90

⚠️ Security release — upgrade from 0.2.x

@foxbook/sdk-claim 0.3.0 closes a verification bypass in verify(). If you are on 0.2.x, upgrade.

npm i @foxbook/sdk-claim@0.3.0

The hole (#90)

Through 0.2.1, verify() reconstructed the Merkle inclusion proof against the unsigned, server-supplied root (/inclusion → rootHex) and never checked who signed the tree head. A malicious or MITM'd transparency log could serve any leaf, a self-consistent proof, and a matching rootHex — and pass verification. That defeats the core guarantee of the whole design.

The fix

verify() now verifies the log's signed tree head (STH) before trusting any inclusion proof, and fails closed:

  1. Fetches the log's Ed25519 signing public key (log_signing_public_key_hex) from {api_base}/.well-known/foxbook.json. Absent or unreachable → fail.
  2. Verifies the STH compact-JWS (/root → sthJws) Ed25519 signature against that key, via Web Crypto. alg is pinned to EdDSA — no algorithm downgrade. Bad / wrong-key / unsigned / malformed → fail.
  3. Reconstructs the inclusion proof against the signed root_hash from the verified STH — not inc.rootHex — and pins inc.treeSize to the signed tree_size. Mismatch → fail.

verifyAgentCard() threads api_base into verify() and now also verifies the STH signature on the requireFreshSTH path: an unsigned timestamp is forgeable, so the stale-vs-fresh decision is read off a signature-verified payload.

Added

  • Internal module src/sth-verify.ts (verifySthJws) — byte-compatible with the server-side signer (core/src/crypto/jws.ts) and the STH payload key order from packages/db signTreeHead. No new runtime dependencies; footprint stays at @noble/hashes.
  • VerifyInput.api_base?: string — optional override for the API base used to fetch the log signing key. Defaults to https://api.foxbook.dev.

Behavior change — read before upgrading

A verify() / verifyAgentCard({ requireInclusionProof: true }) call that previously returned valid: true / verified against an unsigned root now returns valid: false / unverified if:

  • the configured api_base does not serve /.well-known/foxbook.json with a log_signing_public_key_hex, or
  • the /root STH JWS signature does not verify against that key, or
  • the proof does not reconstruct to the signed root, or the tree sizes disagree.

This is intentional — fail-closed: evidence over assertion.

Migration

  • No public type or signature change. verify(input) and verifyAgentCard(card, options) keep the same required parameters; api_base is optional with a default. Existing callers compile and run unchanged.
  • Network reachability. This release adds one additional fetch to {api_base}/.well-known/foxbook.json. Environments that allowlist outbound traffic must permit the API base (default https://api.foxbook.dev) in addition to the transparency Worker base (default https://transparency.foxbook.dev).
  • Self-hosted deployments. Pass api_base (and worker_base) pointing at your deployment, and serve /.well-known/foxbook.json with a valid log_signing_public_key_hex whose key signs the /root STH. Without it, verification now fails closed.
  • Runtime requirement. STH verification uses Web Crypto Ed25519 (crypto.subtle) — modern Node, Deno, and browsers. Very old runtimes fail closed rather than silently passing.

Full changelog: packages/sdk-claim/CHANGELOG.md · Compare: v0.2.1...v0.3.0