Skip to content

security: close #374 and #262 — KELs travel signed and in-band, or not at all - #375

Merged
bordumb merged 4 commits into
mainfrom
pivot/close-374-262-registry-kel-transports
Jul 18, 2026
Merged

bordumb merged 4 commits into
mainfrom
pivot/close-374-262-registry-kel-transports

Conversation

@bordumb

@bordumb bordumb commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

What this closes

#374 (registry mirror transportless) — closed by removal. The pre-push mirror silently no-oped against every real remote (git2 links no ssh/tls), so the published refs/auths/registry drifted until releases could not verify their own binaries (v0.1.4, v0.1.5). Instead of giving the mirror a transport, this removes the distribution channel it fed and replaces it with the committed identity bundle:

  • release.yml verifies through .auths/ci-bundle.json (no refs seed step), with a staleness guard: HEAD is verified through the bundle first, so a key rotation that stales the bundle fails fast with a regenerate hint instead of at the last release step.
  • auths verify auto-discovers a committed .auths/ci-bundle.json — a plain clone still verifies with no flags; the KEL rides the clone exactly like the .auths/roots pin.
  • auths registry push/pull, push_registry/pull_registry, and the hook's mirror block are deleted (the hook remains, chaining only — core.hooksPath must not eat repo-local hooks).
  • auths-rp GitRegistrySync docs now state the deliberate file://-only transport scope instead of advertising ssh/https that could never work.

#262 (unsigned KELs at untrusted boundaries) — residuals 1+2 closed by removal, residual 3 by authentication.

  • --oobi / --remote are deleted. Both fed bare unsigned events into structural replay (RT-002's exact shape), and with the mirror gone their feeds had no producer. Note the broken git2 transport was the only thing keeping --remote unreachable; removing both sides together is what keeps that hole closed.
  • The JSON verify contract (one layer shared by WASM and FFI) now requires a parallel …KelAttachmentsB64 array per KEL slice and authenticates each via validate_signed_kel before any downstream replay — delegator standalone, subject against the delegator's seals, issuer standalone. Forged/stripped attachments refuse with the new kelUnauthenticated verdict kind.
  • WASM verifyDeviceLink gains the same attachments input, mirroring validateKelJson.
  • rt-002-allow annotations rewritten to the ingestion-boundary justification; check-verify-path-completeness passes (0 violations).

Verified

  • Contract round-trips pass on both curves (valid stays valid through the authenticated path); cross-surface parity battery — including new forged-subject-attachment / forged-issuer-attachment / stripped-attachments cases — asserts byte-identical verdicts native vs FFI.
  • Fixtures regenerated with attachments; Node/Python/Go/TS binding tests consume them as-is. TS verdict union + exhaustiveness guard and Go verdict kinds updated.
  • check-command-drift clean after doc updates (CLI reference, git guides, kel-distribution architecture note).
  • CLI e2e third-party tests rewritten onto the bundle channel: clone-and-verify-no-flags now proves the committed bundle carries the KEL.

Notes for reviewers

  • Breaking (prelaunch, no compat kept): the verify-contract request format requires the attachment arrays; verifyDeviceLink has a new parameter. The external widget (auths-verify-widget#4) must adopt the new signature when it updates.
  • The relying-party freshness story after mirror removal is: KELs arrive in-band and per-request; auths-rp remains the port where a network sync adapter would land if ever needed.
  • This unblocks the market's agent-seller authentication (auths-site#20): wasm_verify_presentation_json is now safe to expose to stranger-supplied KELs.

🤖 Generated with Claude Code

bordumb added 2 commits July 18, 2026 02:41
…ndle, not a mirrored ref

The pre-push registry mirror silently no-oped against every real remote
(git2 links no ssh/tls transport), so the published ref drifted until the
release could not verify its own binaries. Instead of giving the mirror a
transport, remove the distribution channel it fed:

- pre-push hook: chaining only (core.hooksPath still must not eat repo hooks)
- auths registry push/pull + storage push_registry/pull_registry: deleted
- verify: a committed .auths/ci-bundle.json is auto-discovered, so a plain
  clone still verifies with no flags — the KEL rides the clone like the pin
- release.yml: artifact verify runs on the committed bundle; a guard step
  verifies HEAD through the bundle first and fails with a regenerate hint
  when a key rotation staled it
- auths-rp registry_sync: docs now state the file://-only transport scope

Closes #374.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1
Residuals 1+2 close by removal, residual 3 by authentication:

- --oobi (HttpOobiResolver) and --remote (RemoteKelSource, resolver
  reconcile/rollback machinery) are deleted: both fed bare unsigned events
  to a structural replay, and the mirror that would have populated their
  remotes is gone (#374). Verify resolution is committed-bundle or local
  registry — never a network fetch.
- The JSON verify contract (WASM + FFI, one shared layer) now requires a
  parallel base64 CESR attachments array per KEL slice and authenticates
  each via validate_signed_kel before any downstream replay: delegator
  standalone, subject against the delegator's seals, issuer standalone.
  Forged or stripped attachments refuse with the new kelUnauthenticated
  verdict kind (TS union + exhaustiveness guard + Go kinds updated).
- WASM verifyDeviceLink takes the same attachments input and authenticates
  before the link check, mirroring validateKelJson.
- rt-002-allow annotations rewritten to the ingestion-boundary
  justification; the stale --remote/--oobi residual clauses are gone;
  check-verify-path-completeness passes with 0 violations.
- Cross-surface parity battery gains forged-subject-kel-attachment,
  forged-issuer-kel-attachment, and stripped-attachment cases (asserted
  byte-identical native vs FFI); fixtures regenerated with attachments,
  which the Node/Python/Go/TS binding tests consume as-is.

Closes #262.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1
@github-actions

Copy link
Copy Markdown

Auths Commit Verification

Commit Status Details
1132c4a9 ✅ Verified Signed by did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
72fff6c9 ✅ Verified Signed by did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd

Result: ✅ 2/2 commits verified

bordumb added 2 commits July 18, 2026 03:17
Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1
Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1
@github-actions

Copy link
Copy Markdown

Auths Commit Verification

Commit Status Details
d0949e67 ✅ Verified Signed by did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
5b882739 ✅ Verified Signed by did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
1132c4a9 ✅ Verified Signed by did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
72fff6c9 ✅ Verified Signed by did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd

Result: ✅ 4/4 commits verified

@bordumb
bordumb merged commit 5d3dd2d into main Jul 18, 2026
19 of 21 checks passed
@bordumb
bordumb deleted the pivot/close-374-262-registry-kel-transports branch July 18, 2026 02:29
bordumb added a commit that referenced this pull request Jul 18, 2026
…jectRoot on the verdict (#377)

* security: close #374 by removal — the KEL travels in the committed bundle, not a mirrored ref

The pre-push registry mirror silently no-oped against every real remote
(git2 links no ssh/tls transport), so the published ref drifted until the
release could not verify its own binaries. Instead of giving the mirror a
transport, remove the distribution channel it fed:

- pre-push hook: chaining only (core.hooksPath still must not eat repo hooks)
- auths registry push/pull + storage push_registry/pull_registry: deleted
- verify: a committed .auths/ci-bundle.json is auto-discovered, so a plain
  clone still verifies with no flags — the KEL rides the clone like the pin
- release.yml: artifact verify runs on the committed bundle; a guard step
  verifies HEAD through the bundle first and fails with a regenerate hint
  when a key rotation staled it
- auths-rp registry_sync: docs now state the file://-only transport scope

Closes #374.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* security: close #262 — signed KELs at every remaining untrusted boundary

Residuals 1+2 close by removal, residual 3 by authentication:

- --oobi (HttpOobiResolver) and --remote (RemoteKelSource, resolver
  reconcile/rollback machinery) are deleted: both fed bare unsigned events
  to a structural replay, and the mirror that would have populated their
  remotes is gone (#374). Verify resolution is committed-bundle or local
  registry — never a network fetch.
- The JSON verify contract (WASM + FFI, one shared layer) now requires a
  parallel base64 CESR attachments array per KEL slice and authenticates
  each via validate_signed_kel before any downstream replay: delegator
  standalone, subject against the delegator's seals, issuer standalone.
  Forged or stripped attachments refuse with the new kelUnauthenticated
  verdict kind (TS union + exhaustiveness guard + Go kinds updated).
- WASM verifyDeviceLink takes the same attachments input and authenticates
  before the link check, mirroring validateKelJson.
- rt-002-allow annotations rewritten to the ingestion-boundary
  justification; the stale --remote/--oobi residual clauses are gone;
  check-verify-path-completeness passes with 0 violations.
- Cross-surface parity battery gains forged-subject-kel-attachment,
  forged-issuer-kel-attachment, and stripped-attachment cases (asserted
  byte-identical native vs FFI); fixtures regenerated with attachments,
  which the Node/Python/Go/TS binding tests consume as-is.

Closes #262.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* fix: formatting

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* fix: format

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* sdk: presentation evidence export — the agent hands its verifier-ready proof to any relying party

load_presentation_evidence gathers what load_presentation_inputs resolves
PLUS the per-event CESR signature attachments each KEL carries in the
registry, wire-shaped to the verify contract's field names. A KEL event
without a stored attachment fails closed — evidence that cannot be
authenticated is not exported as if it could.

auths credential present --with-evidence emits { authorization, evidence }
as one JSON object: the Auths-Presentation header plus everything a
relying party with no registry replica splices into its verify request.
Proven end-to-end against the market's agent write API (9-check e2e:
valid create, replayed nonce, forged attachment, forged signature).

The Node SDK's status enums gain KelUnauthenticated so the new verdict
surfaces typed instead of through the Unknown forward-compat arm.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* sdk: regenerate Node type declarations for the KelUnauthenticated status

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* fix: the two failures the mirror removal left behind — and the bundle learns device KELs

The third-party no-flags clone test failed for a real reason: auths init
delegates a DEVICE identity and commits are signed by it, but the exported
bundle carried only the ROOT KEL, so a clone could not resolve the signer.
The bundle now carries every delegated device KEL the root anchored
(device_kels, per-event hex attachments). BundleTrust authenticates each
one — self-certifying dip SAID, then validate_signed_kel with the
just-authenticated root's seal index as delegator lookup — and the verify
resolver serves any bundle KEL (root or device) by DID, prefix-bound.

The pre-push hook test asserted the removed mirror behavior; it now pins
the actual contract: chain to the repo hook, exit clean, and never name
registry push again.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* verifier: the Valid presentation verdict names the proven subject root

A relying party crediting an account to a delegated agent's root must get
that root from the verdict, never by parsing the dip out of unverified
evidence. subject_root is the delegator AID when the subject KEL is
dip-rooted (proven — the seal-checked replay validated the delegation) and
the subject itself otherwise. Threaded through the JSON contract
(subjectRoot), the TS union, and the Node SDK report.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* fix: delegated attachments authenticate everywhere — and the schema catches up

pair_kel_attachments now parses the full delegated attachment form
(-A signatures ++ -G source seal) and rehydrates the seal onto dip/drt
events before validation — the JSON body of a delegated event carries no
seal, and the bilateral binding needs it. This is the same rehydration
org_bundle already did locally; it now lives once, at the pairing seam,
so bundle verification and the JSON contract authenticate device KELs
instead of refusing on trailing -GAB bytes (the golden-path failure).

Also: the three IdentityBundle test initializers gain device_kels, the
identity-bundle-v1 schema is regenerated for it, and the Node report
surfaces kelUnauthenticated's detail instead of dropping it.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* fix: downstream replays use the rehydrated events the boundary authenticated

authenticate_kel now returns the paired events — rehydration restored each
delegated event's -G source seal from its attachment, and the structural
replays after the boundary re-enforce that seal against the delegator KEL,
so handing them the bare JSON events failed delegated subjects with
SubjectKelInvalid. Same fix in BundleTrust's device KELs. Proven by the
market e2e: a delegated device identity now presents, verifies, and its
root credit lands on the verdict's subjectRoot (13 checks green).

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* sdk(node): delegateAgent mints a KERI-delegated agent, not an attestation link

The napi surface now routes through agents::delegation::add_scoped — the
agent gets its own dip-rooted KEL the parent anchors, with the requested
capabilities as a delegator-anchored scope seal. The old path minted a
did:key attestation agent, which no verifier in the delegated presentation
flow could honor. The bundle drops attestationJson and gains agentPrefix;
the TS wrapper converts expiresInDays to seconds instead of passing days
where seconds were read. Proven by the market e2e: the SDK-minted agent
presents with delegator evidence and its root credit lands on subjectRoot.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* sdk(node): the relying party lives here — one call authenticates a presentation request

presentationNonce parses an incoming Auths-Presentation header (parse-only,
nothing trusted) so a relying party can burn its single-use challenge
before verification. authenticatePresentation does everything else in Rust
over the auths-rp wire types and the verifier's JSON contract: audience and
nonce binding, the evidence-carrying request, KEL-authenticated
verification, and the verdict-to-denial mapping. A relying party keeps
storage and grant policy; it re-implements no wire, no base64, no assembly,
no doctrine.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* docs: the report is the only API — relying parties never inspect evidence

Institutionalizes the subjectRoot precedent: any fact an RP acts on is a
verdict field the verifier proved, and the wire lives once behind the
SDK's relying-party surface. RP-side evidence parsing is a missing
verdict field, to be fixed in auths-verifier, never in the consumer.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* lint: bindings are a presentation layer — check-binding-boundary

packages/auths-node marshals; it does not reach into auths_id/auths_core/
auths_storage primitives (the delegateAgent incident — bindings minting
attestation agents long after the SDK went KERI-native — is the class this
prevents). Existing reaches carry a binding-boundary-allow annotation and
shrink over time; a new unannotated deep import fails CI.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* sdk(node): the contract ships with the package — conformance vectors + canonical statuses

conformance/ carries the exact golden vectors the Rust verifier tests
against (valid + revoked presentations/credentials) and statuses.json
parsed from the generated declarations, so a consumer asserts against the
contract version it actually runs — the seam is tested from both sides,
versioned together. Synced on build and prepublish; published in files.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* sdk(node): mintChallengeNonce — the nonce shape is the contract's, not the caller's

32 random bytes in base64url via the auths-rp Nonce type, so a relying
party never invents its own format and parse_b64url on the other side can
never disagree with the mint.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* style: rustfmt on the binding-boundary check

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* fix: formatting

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* ci: the four reds — fmt-proof boundary annotations, OsRng, regenerated declarations

The binding-boundary marker now counts on the line above or below (rustfmt
floats trailing comments off brace lines), and every grandfathered
annotation sits on its own line above its import. mintChallengeNonce draws
from OsRng explicitly (rand::random may delegate to thread_rng — the
disallowed-methods rule). The regenerated Node declarations (agentPrefix)
ride along for the docs build.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* style: rustfmt pass on the boundary check

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1

* sdk(node): the hand-maintained native shadow catches up with the KERI delegateAgent

lib/native.ts redeclares the napi surface for the typed wrapper; its
NapiDelegatedAgentBundle still had the attestation-era shape, so typedoc
failed on agentPrefix. Shadow now matches the generated declarations
(agentPrefix in, attestationJson out, expiry in seconds). typedoc: 0 errors.

Auths-Id: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Device: did:keri:EB5cPHY0t-ejNC_rUzPS1dclTvd6kG-R9mQzjozCuGgd
Auths-Anchor-Seq: 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant