Repository navigation
Conversation
…d binding
A nodeId is self-chosen, so "X has key K" is a statement some source
makes. The R2-1 fix made the in-force grant view the weakest source, so
a squatter that reached a receiver first with its own key under a
certified nodeId kept it. Only the identity authority says which key is
a nodeId's; a session proves only that its peer holds some key.
Precedence, strongest first: the anchor pin and this node's own key, an
out-of-band pin, an anchor certification (an in-force grant that
bindsKey: unscoped, its chain unscoped admins up to the anchor), trust
on first proven use, a legacy claim.
- RosterKeyRegistry.binding() is the one decision; get/source/expected
read it. While a certification of X -> K1 is in force, a session or
legacy claim naming another key is refused and recorded, and a stored
binding of another key is superseded: kept on disk, marked (a
persisted line, and a member of a compacted one), recorded with kind
'superseded', never used to verify, never expiring and outside the
new-binding share. A session proving K1 stores nothing.
- Revoked or expired, a nodeId nothing else binds is unbound again; a
superseded binding stays conflicted (fail closed: it verifies nothing,
and every source but a pin is refused) until the operator resolves it
or a pin or a new certification settles it.
- A pin outranks a certification (recorded 'pin-over-certification');
an invite still never repoints a binding the authority has not
superseded. Certifications naming two keys for one nodeId certify
neither ('certifications-disagree').
- A grant not in force supersedes and records nothing.
- AuthorityStore.certifiedKeys/certifications, indexed once per
resolution; the node offers every certification in force at start and
each one that enters at a settle, and closes a live session whose key
no longer decides its nodeId when a certification enters or leaves or
a pin is made (1009, recorded, metric identity-conflict).
- _onSessionConfirmed/_admitSession/_identityKey/_keySource/_earnBinding
follow binding(); keyBindings() lists the key that decides each nodeId
and leaves a conflicted one out; grantRole takes a certified key; an
attestation's role is judged under the key that verified it.
- sym keys lists a superseded binding as such.
Tests: security-0.14.3-cert.test.js (new) has one or more per rule;
the ones encoding "a session outranks the grant view" are updated, each
with its reason.
…sts, SECURITY, README, operator guide) The R2-1 entry described the grant view as the weakest source; it now describes the precedence as built: the anchor and this node's own key, an out-of-band pin, an anchor certification, trust on first proven use, a legacy claim; supersession, failing closed after a revoke, pins over certifications, two certified keys, roles under the verified key. The Tests section gains the new test file, the updated tests with their reasons, the 28-mutant check and this branch's suite, integration, gate and audit results. SECURITY and the README no longer say that no source overrides a different key; the operator guide says that an unscoped grant certifies its key, and what a wrong key costs.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a1615d1de0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| b.superseded = null; | ||
| this._track(nodeId, b); | ||
| this._persist({ nodeId, key: b.key, superseded: false }); | ||
| this._log(`Key registry: ${String(nodeId).slice(0, 8)} — a certification in force names the key that was superseded; it is bound to it again`); |
There was a problem hiding this comment.
Clear conflict records when a certification settles a binding
When a later certification names the stored key, this branch clears b.superseded and makes that key valid again but leaves the earlier superseded entry in _conflicts and roster-conflicts.jsonl. Consequently status().coreSecure.keyConflicts and sym keys continue reporting the old certified key as bound and the now-valid key as refused, telling the operator to resolve a conflict that no longer exists; repeated settled conflicts also consume the bounded conflict slots and can prevent subsequent real conflicts from being recorded. Remove this nodeId's supersession conflict and rewrite the conflict file when settling it, as resolveConflict does.
Useful? React with 👍 / 👎.
Why
A nodeId is self-chosen, so "nodeId X has key K" is always a statement some source makes. The R2-1 fix on
release/0.14.3was right that only an unscoped grant whose whole chain is unscopedadmingrants up to the anchor is a key-binding source (bindsKey). It was wrong to make that grant view the weakest source. Under that rule, a squatter that reached a receiver first with its own key under a certified nodeId kept the nodeId. A session proves only that its peer holds some key. The anchor's certification says which key is X's.The design as built
bindsKey), then a proven session (trust on first proven use), then earned and legacy-claim bindings.RosterKeyRegistry.binding()is the one decision, andget,source,expected,_identityKeyand_keySourceall read it.roster-conflicts.jsonl, shown insym status, and raises the metricidentity-conflict.superseded. It is never used to verify.sym keys resolveor a pin or a new certification settles it. A new certification naming the superseded key clears the mark.pin-over-certification). A pin made while a session holds the nodeId under another key closes that session. An invite still never repoints a stored binding that the authority has not superseded (design D5)._attesterRole(att, key)) instead of re-reading the binding.Also in this PR:
certifications-disagree).keyBindings()lists the key that decides each nodeId and leaves a conflicted nodeId out. XMesh'sauthorKeyResolverreads the first row's key.grantRoleaccepts a certified key.sym keyslists a superseded binding as such.Tests
tests/security-0.14.3-cert.test.js(new), 22 tests. 16 fail on 9f6847c; 6 pass there and pin behaviour that already held.roster-keysrows for proven / pinned / grant, and the persistence row.security-0.14.3R2-1 "never refuses a proving session".authority-node§6.6.9 row.--test-concurrency=1(the machine is shared): 1673 tests, 1669 pass, 0 fail, 4 skipped.scripts/gate-two-stock-nodes.mjspassed.npm audit: 0 vulnerabilities.Not changed
lib/relay.jsis not touched.Do not merge before review.