Repository navigation
security: beta pre-audit fixes (combined CORE, STORAGE, SOFI) - #1096
Merged
Merged
Conversation
Security pre-audit, item 10, from the storage side. Each bound has a test that holds the resource the way a hostile client would, and a mutation control that turns it red. - One count of requests in flight for the whole node. A router applies a layer to each route on its own, so ConcurrencyLimitLayer gave every route its own limit; GlobalConcurrencyLimitLayer shares one. (one_count_of_requests_in_flight_holds_across_every_route; per-route limit → red) - A request time, outside the limit, so waiting for a slot is bounded too. `[http] request_timeout_secs`, 60 by default; answered 408. (a_request_whose_body_never_arrives_is_answered_408; disabled → red) - A header-read bound on every connection, with the timer the HTTP library needs in order to keep any bound. axum-server never gave it one. `[http] header_read_timeout_secs`, 10 by default. (a_connection_that_never_finishes_its_headers_is_closed. Red without the bound, and red without the timer: hyper then drops the connection at once, which the test refuses as too early.) - The pool is bounded: 32 connections, a 10 s wait. Closers queue on the node's own lock before they take a connection, so queued closers hold none. (closers_waiting_their_turn_leave_the_pool_to_everyone_else; a lock taken and dropped → red) - A set-mate's answer is read no further than the largest ByteCommit encoding (221 bytes, derived from the fields). (a_set_mate_whose_answer_never_ends_is_read_no_further_than_a_byte_commit; uncapped → red) - The store-wide SMT builds for a close and for a proof run off the request threads (spawn_blocking). - The mirror write is durable like every other write. - Health answers "postgres unavailable" and logs the error; it no longer sends the driver's message to whoever asks. (health test; old text → red) - Spool keys are kept under their one canonical Base32 Crockford spelling, so a writer's spelling cannot put a message where its recipient never reads. (a_message_sent_under_another_spelling_of_its_key_reaches_its_spool; raw key → red) - The immutable put no longer logs the caller's header text. - Deploy: SSH host keys are accept-new, not ignored. node.key is owned by the uid and gid the image runs as, at 600, not 644. Both services' logs rotate (5 × 50 MB). The timeouts are Durations handed to the HTTP and pool libraries. No node source reads a clock; no_clock_reads stays green. Node suite (release, Postgres): lib 39, bin 4, nine integration suites including resource_limits 4/0 and bytecommit_chain 9/0. fmt, node clippy, the guard and the clock gate are clean.
insert_immutable_object_if_absent ran SERIALIZABLE (SELECT … FOR UPDATE, then INSERT) with no retry. Concurrent puts of different objects failed with serialization failures (40001), which the node answered as 500. SOFI measured 6 of 16 on the rig node set. The put is now read committed and idempotent: - INSERT … ON CONFLICT (addr_b32) DO NOTHING; - when nothing was inserted, the held tuple is read to tell an identical copy from a conflicting one. Different objects never contend, and puts at one address wait for each other at the insert. It runs through begin_durable_write, like every other write. Test: puts_made_at_once_are_each_taken. Three rounds of 64 distinct objects put at once, each taken, and 16 identical puts at once, each acknowledged under one address. Mutation control: the serializable put → red, 29 of 64 refused with 500 in round 0. The corruption test still reports a different tuple at an address. immutable_store_round_trip: 8/0.
… K_root(p), never a claim P carries Pre-audit item 2 (Critical), audited at main 076792e. The flaw: - A trader could name, as P's parent, a single-root claim it signed for itself, with a root it never held and invented pre-balances under it. - Conformance verified that claim under the key it carries. - The facts mapped every single-root parent to compatible. - So the swap realized, and walkers took the vault's key as consumed: a drain, and a mint on Close for a vault-owning attacker. The fix: - A single-root parent now counts only as what the trader's lineage holds at p: the claim final at K_root(p) and its root. The verifier establishes this itself, from its own admitted position or the A8 walk (trader_root_at -> peer_root_at). No separate mechanism is added. - ResolvedParent carries the claim that holds K_root(p). - ParentPosition::SingleRoot { named, held, held_root }: the ladder computes compatibility (held exactly as named, at P's root) and impossibility (its negation), so the old SingleRoot => true/false arms are gone. - The S15 resolver hands over the walk's own authenticated parent for both kinds of claim. Tests: - Adversarial: a claim the trader doesn't hold at p, and the claim at another root, each make the position Invalid and the key Skipped. These were Ok(Realized) before the fix. - A parent that is not established is no fact. - Three mutations turn the tests red. - Lean: ParentState.singleNotHeld, with theorem single_root_parent_not_held_is_invalid. It fails to check if the case is dropped from parentImpossible. Suites: dsm sofi 240/0, SDK node suites 80/0, lint 0, guard clean. Still depends on pre-audit item 1 (CORE: claims at K_root bound to the DevID key).
… lineage holds at K_root(p) (§6.62)
…; junk cannot wedge an inbox
Security pre-audit, items 7, 8 and 11 from the storage side. Each gate has
a test and a mutation control that turns it red.
- https:// only, in the env config loader and in every member client
(storage_node_sdk::require_https). Tests:
network::tests::an_endpoint_that_is_not_https_is_refused and
a_member_endpoint_that_is_not_https_gets_no_client. Mutation: http
accepted → both red.
- A member client trusts only the CAs the env config names, not the system
store. It accepts only a certificate naming the member id as a DNS
subject alternative name, whatever address it dialled (NamesTheMember).
MemberClient::new(member_id, endpoint) builds its own client, so a
member's name and its connection's identity cannot disagree. The inbox
client holds one such client per member endpoint. The five fleet certs
were read in a handshake and each carries DNS:dsm-node-N for its member.
Test: a_members_latest_bytecommit_is_its_own_or_there_is_none (a client
for dsm-node-2 at dsm-node-1's node is refused on the certificate).
Mutation: verify the dialled address → red.
- The SDK test harness serves its nodes over TLS from a per-set CA, each
certificate naming its member and 127.0.0.1, as the fleet's do. There is
no localhost exception. point_sdk_at names the CA as the bundled config
does.
- Transport errors carry their causes, so a refused certificate says why.
- Inbox:
- a node-supplied spool position is used only if a spool could hold it.
u64::MAX + 1 panicked the poller, and a position past i64::MAX aborted
the read (a_position_past_what_a_spool_can_hold_is_none; unchecked →
red).
- an envelope that will not open with this device's key is passed over,
not stopped at for good
(what_never_opens_for_this_device_holds_up_nothing_behind_it; no
advance → red). The key is derived once per read.
- the poller's running flag is cleared however its task ends
(a_poller_that_panics_is_no_longer_running; never cleared → red).
CONFORMANCE §6.62 records items 7, 8, 10 and 11, the decisions left to the
owner (paging, write budgets, the mirror cap, committing the CA) and the
sync-limit question left to SOFI.
dsm_sdk whole suite (release, Postgres, TLS harness): 1093/0. Clippy for
dsm_sdk and dsm_storage_node --all-targets -D warnings is clean. fmt, the
guard and conformance_evidence are clean.
Pre-audit item 6 (P0), audited at main 076792e. The flaw: the send screen chose a contact by its device id but sent the contact's alias. wallet.sendSmart resolved that alias to the first matching row, and aliases are not unique, so a send to the second "Bob" paid the first. The fix: - OnlineTransferSmartRequest reserves field 1 (recipient) and carries bytes recipient_device_id = 5 [(dsm_fixed_len)=32]. - The SDK takes those 32 bytes or refuses the request, and resolves nothing by alias. get_contact_by_alias had no caller left and is removed. - The frontend sends the chosen contact's device id, the key SendTab selects it by. - Practice mode finds recipients by device id. Test: a_send_pays_the_contact_chosen_not_the_first_of_its_alias. Two contacts share an alias, and each is paid what was sent to it. It was red on the old wire. Resolving by alias turns it red again. Frontend: type-check clean, 7 suites, 51 tests. make lint exit 0.
…audit STORAGE's pre-audit work, tip 5afda74: item 10 (node resource bounds), the node's concurrent immutable-put abort (read committed, ON CONFLICT DO NOTHING), items 7 and 8 (https only, members known by certificate) and item 11 (inbox junk). Its CONFORMANCE section is renumbered §6.64.
…ependencies STORAGE's branch added dsm_sdk -> axum-server (a dev-dependency) and dsm_storage_node -> hyper-util, plus tower-http's timeout feature. These were recorded in the root lock only. The client workspace builds dsm_storage_node as a dev-dependency, so its lock carries the same three edges. No version changed.
Owner ruling, 2026-10-01, on the audit finding that anyone could occupy
any identity's next root cell: "If an object can win/occupy a root
position, the object itself proves authority for that position."
Every object recognized at K_root(q) is now signed by a key that, with the
AttA the object carries, derives the device it names. Nothing is fetched.
- **Root claim.** `EconomicRootClaimBody` moves to schema 2 and carries
`claimant_att_a`. `decode_and_verify_economic_root_claim` refuses a claim
whose key and AttA do not derive `trader_devid`
(`KeyIsNotTheNamedDevices`).
- **C_q.** It occupies K_root(q) only as the trader-signed
`SignedSofiResolutionClaim` (class 0x0062, signed under
`DSM/sofi/resolution-claim-sign/v1`). A bare C_q is refused by name.
`sofi::signature::verify_resolution_claim` checks the signature and the
binding, and `sign_resolution_claim` refuses a key that does not derive
the named device.
- **Identity at a root cell.** A conditional claim is identified by its
derived body, so readers keep comparing `derive::resolution_claim(P, F)`.
- **Producers.** The admission flow signs its root claim with this device's
AttA. `sofi_register` signs the trader's C_q. The relay carries the
trader's signed C_q from the cell, because it cannot author one.
- **Tests.** The SoFi fixture device and the peer device have ids derived
from their keys. Adversarial tests:
- a root claim and a C_q signed by another device (with its own AttA or
the victim's) name no cell;
- an unsigned C_q names no cell;
- a squatter's claims written first at every seat leave the cell open
until the trader's own claim holds it.
…parent_named Item 2 (§6.62) replaced resolve.rs's parent_named with held_at, which counts a walked root only at the position it reached, and moved the comparison of the claim P names with the claim held into the facts (establish_ground). The manifest row still named parent_named, so the requirement map refused the root query and #1096's Code map failed before producing a map. The row now names held_at, evidenced by what_a_walk_holds_counts_only_at_the_position_it_reached. Mutation control (2026-10-01, restored): held_at taking a root of another position turns that test red. The matrix and the §8 row say what parent_named's test covered that no test covers now, a conditional parent held by another fulfillment, which pre-audit item 12 restores.
The owner's item-1 rule reaches the fulfillment cell: an F at K_ful(q) can win the position, so it proves its own authority for it. - `TraderFulfillmentBody` carries `claimant_att_a`. - `registration::fulfillment_proves_the_device` checks that F's key with that AttA derives the cell's device, from F's bytes alone. `names_fulfillment_key` applies it before it looks up P, so a foreign-key F neither holds the cell nor makes a reader fetch a P it names. - Its P key equality then binds P's key too. - The SDK's `build_fulfillment` takes this device's AttA and refuses a P whose key does not derive the device it names. - Test: a squatter's own P and F naming the victim's device, verifying under the squatter's key, name no cell with either AttA, even with that P in hand. The trader's own F names it. - The independent encoder and every F fixture carry the AttA.
- **DSM Amendment A10.** It follows the explainer's root-claim paragraph.
"Only Alice can sign one" is enforced from the bytes in hand: every
occupant of a device's position cell carries its key and AttA, and is
recognized only when its signature verifies and `derive_devid(key, AttA)`
is the cell's DevID.
- **SoFi Amendment S20.** It sits at §17.4.
- K_root(q) holds the trader-signed C_q (class 0x0062).
- F carries AttA, and is bound before P is fetched.
- The exercise carries the trader's signed C_q.
- A relayer completes the pair with the trader's C_q and never authors
one.
- It names every section it amends.
- **MASTER.** §1 re-pinned. MR-DSM-0277 and MR-SOFI-0360 to MR-SOFI-0362
added from the amendments. §7.1 entry added.
- **CONFORMANCE §6.65 (item 1).** MR-DSM-0277, MR-SOFI-0360 and
MR-SOFI-0361 are Met with mutation controls. MR-SOFI-0362 is Missing: the
exercise's C_q lands on #1096. §7 totals regenerated.
- **VERIFICATION_MATRIX.** Rows for the three gates and their mutations.
- **Code.**
- `RootClaimSettlement::Lost` boxes its holder: the claim grew by its
AttA.
- The node test's re-aimed F carries the honest F's AttA.
Board: dsm lib 1381/0 (four runs), dsm integration all pass, SDK
integration all pass, make lint 0. SDK lib: 1045/4 under load; the four
(funding writes Preserved, not final, while the shared Postgres was busy)
pass 4/0 when rerun alone.
The negative-test column of MR-SOFI-0361 put P and F in backticks inside its prose, which the evidence check read as test names. Plain words now; conformance evidence passes.
…ion for good Pre-audit item 12. Four paths by which cheap junk froze a vault or a trader's position, each with a node or unit test that went red on the old code: - The walk's budget ended walks. Conformance item 5 read K^(0) to K^(min(a,16)-1), though it needs K^(a-1) alone, so a fulfillment at attempt 17 never had the key it names. Extending a chain and a leg's liveness walk both stopped at Continue. Junk exercises at the first 17 keys of a root therefore froze the vault there. Item 5 now reads exactly K^(a-1), and every caller resumes at the cursor (SoFi §23.6). - Recognition counted an exercise's closure objects and checked nothing else, so a copy of an honest exercise with other closure bytes, written first at a leg's leader, held the key and could never be classified. Each closure object must now be the object its reference names, by conformance item 8's rule (§17.5). - A registration read fetched the precommit of every fulfillment any seat held at K_ful(q), and failed when one could not be read, so a value at any member, written at any time, stopped every read of that position. Only the leader's log decides the cell: precommits are fetched only for its copies, in arrival order, until one names the key. route_chain::leader_copies is the one rule the leader link and this read share. - A conditional parent another claim holds at p was "not established" forever, so no key its exercise held was ever skipped. It selects no root, ever: ConditionalNoRoot, impossible, skipped without evidence (§23.3). Tests: junk_at_more_attempt_keys_than_one_walk_examines_is_walked_past, an_exercise_carrying_other_bytes_than_its_closure_holds_no_key, nothing_but_the_leaders_first_fulfillment_decides_k_ful (node); a_conditional_parent_another_claim_holds_selects_no_root (facts). Seven mutation controls, one per gate, each red and restored.
…onship
Security pre-audit items 15 and 5.
- **Operation decoder.** `dec_vec_bytes` no longer preallocates from a
sender-written count. A recovery operation claiming u32::MAX authority
signatures asked for ~103 GB before decoding any: a panic on 32-bit,
and an abort under a refusing allocator. The vector now grows only as
elements decode.
- **Proof decoders.** `DevTreeProof` and `SmtInclusionProof` compute their
length checks in u64. On 32-bit, `count * 32` wrapped for count 2^27
and the copies indexed past the input.
- **Nonce scope.** A transfer nonce is spent under
`relationship_nonce_hash(rel_key, nonce)` (`DSM/relationship-nonce/v1`)
in the canonical apply's identity, in `spent_nonces` and in staging.
- Before, a nonce was spent device-wide. Nonces derive from public
inputs, so one contact could spend another contact's predicted nonce
and have that contact's real transfer dropped as decided.
- Replay is still refused: the same transfer carries the same nonce in
the same relationship, and the (relationship, parent) record holds.
- The unused device-wide `is_nonce_spent` and `mark_nonce_spent` are
deleted.
- **Tests:** the three hostile decoder inputs, and a nonce spent in one
relationship staying unspent in another.
…t items 15, 5) - Item 15. Each fix and its test are recorded. So is what this 64-bit macOS host cannot show: the old failures were 32-bit or allocator-specific, so no mutation turns these tests red here. Out of this round's scope: recovery capsule counts. The phone's response sizes are STORAGE's. - Item 5. The cross-sender nonce finding, its relationship-scoped fix, and its mutation control (the scope removed: the cross-relationship test red).
…nt (A8)
Security pre-audit item 3. Owner ruling 2026-10-01, "Segment now,
proofs later".
- **The gap.** A credit's source step was validated, but the source's own
history was authenticated by root claims alone. "Admitted" means only
that the source signed its root, so two wallets of one owner could
create value one hop back: C registers an invented root, pays its own A
out of it, and A pays honest B.
- **The fix.** `source_step` validates the source's segment from this
receiver's frontier for it with `chain_through`, exactly as a payer's
segment is validated. That segment's own credits' sources are held to
account the same way, back to frontiers this verifier already
validated. The source step itself is a debit that names no source.
- **Fail closed.** When the ancestry exceeds the shared walk budget before
reaching a frontier, the result is Incomplete, never weaker evidence.
- **Renames.** The provenance types now say what they do: `SourceAncestry`,
`DebitHasNoSources`, `StepRole::Source`, `source_step`.
- **Tests.**
- The source's step at 1 is validated from its witness and successor
evidence. This was the old "one hop and no further" test, now
inverted.
- A source's segment stops at the frontier the receiver holds: nothing
of C at or behind it is read.
…hing Owner ruling 2026-10-01, pre-audit item 3, test 4: a source's ancestry that exceeds the walk budget before a frontier this verifier holds is Incomplete, never accepted on weaker evidence.
…ecords one A frontier becomes trusted only once the whole segment to it has passed (A8, owner ruling 2026-10-01, pre-audit item 3 test 5).
…old a key (§6.66) §6.66 records item 12's findings apart from their fixes: - 12a–12d closed by `96b870fdd`, each with its adversarial test, red on the old code, and its mutation controls. - 12j: a fulfillment's registration at K_ful(q) depended on when the precommit it names is published. A trader can make early verifiers see its trade realized and later ones see the position unresolvable, with the vault key frozen. Confirmed by a probe on the nodes. The closure is agreed with CORE: decide K_ful(q) from F's bytes alone, per the item-1 ruling. In progress. - Open: 12e (the exercise carries the signed C_q, S20), 12f (a parent on a lineage known invalid), 12g and 12h (owner rulings), and 12i (locators computable in advance). §8 corrections: - MR-SOFI-0244 was Met on the pure walk while three of its callers stopped at the budget. - MR-SOFI-0213, MR-SOFI-0170, MR-STOR-0132 and MR-SOFI-0241 name the new gates and tests. - MR-SOFI-0234 is Partial on 12f. §7 is regenerated. Matrix rows for items 2, 6 and 12, the first two of which had none.
…y/beta-pre-audit CORE's item 1 (f5cd5d9): a position cell's occupant proves its own authority (DSM Amendment A10, SoFi Amendment S20). Resolutions: - CONFORMANCE §6: §6.65 (item 1) sits between §6.64 and §6.66. - §7 is regenerated. - The item-12 test helper `signed_by_b` passes the AttA that F now carries.
…ems 15, 5) CORE's items 15 and 5: core decoders are bounded by their input, and a transfer nonce is spent only in its own relationship. Resolutions: - CONFORMANCE §6: §6.67 follows §6.66. - §7 is regenerated. - The domain tag count is 354. Both branches raised it from 352 to 353, which git merged as one change. The tags are the signed resolution claim's digest (S20) and the relationship nonce (item 5); `the_declared_domain_tag_count_is_the_expected_one` counts 354.
…ce (A8) Each credit source's segment that passes whole leaves a frontier at the source step. The walk reuses it for later sources of the same identity, and the accept transaction records it beside the sender's, so the next credit from that source validates only the suffix (owner ruling 2026-10-01: "Cache/update validated frontiers so subsequent interactions validate only the new suffix"). validate_peer_lineage returns ValidatedPeerLineage.
…ment; §6.68 Owner ruling 2026-10-01 (pre-audit item 3, "Segment now, proofs later"), quoted in A8. The One hop bullet becomes Credit sources; S15's SetupValid wording follows. MR-DSM-0273-0275 and MR-SOFI-0347 rewritten; §1 re-pinned. Tests 1 and 2 of the ruling recorded as owed (hostile-device harness).
…tes (12j)
A reader passed over an F whose P it did not hold yet, so an F naming an
unpublished P, written ahead of the F a trader traded with, flipped the
position between verifiers reading before and after the P appeared, and
froze the vault key the trade's exercise held (SOFI's node probe).
Applying the item-1 ruling ("the object itself proves authority for that
position"): K_ful(q) is decided from F's bytes alone; the pair is matched
by FulfillmentId(F); an F whose K_ful(q) another F holds in any state is
lost; the C_q body is compared with derive(P, F) where P is in hand, a
difference losing F and making the position Invalid. MR-SOFI-0168, 0343,
0360, 0361 rewritten and Partial until SOFI's change lands on #1096.
…em 3) CORE's item 3: a credit's source is held to account through its own segment back to a frontier the receiver holds; validated source frontiers are recorded with the acceptance (§6.68). Resolutions: - MASTER §1 pins are recomputed from the merged specs (explainer 871fb1a…/4323, SoFi 49cd9eb…/2689). The pin note names both amendments. - The verification matrix keeps both sides' rows and drops the one-hop-source row that item 3 replaced. - peer_lineage's new source-step test uses `peer_d()`, the derived device item 1 put in place of the `PEER_D` constant.
…extended for 12j) CORE's S20 extension: K_ful(q) is decided from F's bytes alone. The pair is matched by FulfillmentId(F). A position is lost when another F holds the K_ful(q) leader link in any state. The C_q body is checked against derive(P, F) where P is in hand. MR-SOFI-0168, 0343, 0360 and 0361 are Partial until SOFI's registration change lands. Resolutions: - MASTER §1's SoFi pin is recomputed from the merged spec (8360842…/2693), and the pin note names the extension. - §7 is regenerated.
…enever it is read (12j) Pre-audit item 12, 12j (SoFi Amendment S20, extended). Recognizing a fulfillment at K_ful(q) used to need the precommit it names, and a precommit not published yet (Resolved::None) made the reader pass over that fulfillment. A trader could write F_a, naming an unpublished P_a, ahead of the F_b it traded with: - verifiers reading before P_a appeared saw F_b registered and the trade realized; - verifiers reading after saw F_a holding the cell and F_b neither registered nor lost, with the vault key frozen. Now: - names_fulfillment_key decides from F's bytes alone: it verifies under its own key, names q, and its key and AttA derive the cell's DevID. No precommit is read, so read_registration fetches none, and NAMED_PRECOMMIT_BUDGET and PrecommitLookup go. - The pair is matched by FulfillmentId(F). Registration::Held names the fulfillment that holds K_ful(q)'s leader link before the pair is final, and RegistrationRead carries the id of the claim at K_root(q). - RegistrationRead::standing_of (P in hand) loses F to any other fulfillment holding K_ful(q)'s leader link, in any state (§23.1), and to any claim at K_root(q) other than derive(P, F). That covers rule 4: a claim naming F with another body. The facts read the pair through it. - The S15 resolver compares the held claim with derive(P, F) once P is in hand: Invalid when they differ. - route_chain's public leader_copies wrapper had no caller left and goes; leader_link keeps its iterator. Tests: - a_position_reads_the_same_before_and_after_a_precommit_is_published (node): red on the old code by the probe, where the read flipped and the key froze. - a_fulfillment_is_lost_to_any_other_leader_and_to_any_claim_not_its_own (unit). - Mutations: another fulfillment's leader link losing nothing turns both red; skipping the body check of a registered pair turns the unit test red.
…d pair can be registered from it (12e) SoFi Amendment S20 makes C_q trader-signed, so no relayer can author it. A trader that wrote its exercise at a vault's key and withheld its position pair held that key for good: its F could never register, rung 0 waited, and every later trade at the vault waited behind it. - SofiExercise carries the trader's signed C_q (class 0x0062) after F. - recognize_exercise requires F to prove P's device from its own bytes, and the carried claim to be recognized exactly as K_root(q) recognizes it (root_claim_naming) with the body derive(P, F). An exercise failing either is nothing at the key. - The install returns the signed C_q it wrote, and the exercise carries those bytes. - sofi_relay::relay_exercise registers the fulfillment whose exercise holds a vault key from the exercise's bytes alone, and carries the exercise. relay_fulfillment carries the pair with the claim K_root(q) holds, or the one its exercise carries. relay_position_pair had no caller and is gone. - The cell value cap rises from 256 KiB to 320 KiB (route_chain::MAX_VALUE_LEN, the proto bounds, MAX_EXERCISE_BYTES): a two-leg exercise measured 218,735 bytes before C_q, about 269 KB with it. STORAGE agreed; the node's cap follows MAX_ENTRY_BYTES. Tests: node test an_exercise_whose_trader_withholds_its_pair_is_registered_from_its_own_bytes; unit tests an_exercise_carries_the_traders_signed_claim_of_its_own_p_and_f and an_exercise_whose_fulfillment_does_not_prove_the_traders_device_is_nothing. Mutations, each red: the relay ignoring the exercise's claim; the body comparison dropped; the claim's verification dropped; the device check dropped.
Conflicts resolved as an integration, not by picking a side: - tour/practiceMode.ts: main's ERA-at-two-decimals work (PRACTICE_ERA_HELD, the faucet amount as people count ERA, ready() before every read, debits in turn, the refusal shape) with #1096's item 6 (the online send names its recipient by device id; a simulated send finds its contact by device id only). The stale wholeAmount line from before #1095 is gone. - practiceMode.test.ts: main's new tests send to the practice contact's device id. - CONFORMANCE_GAPS.md: main's §6.60 and §6.61 (the perf PR's reserved numbers) ahead of §6.62 to §6.68; §7 regenerated. Also: ci/sofi_relay_is_party_neutral.sh checks relay_exercise's entry (committed set and vault key) where it checked relay_position_pair, which 12e removed; MR-DSM-0213 cites relay_exercise. Main's frontend fails the real-code guard on its own: 21 lines from #1095 await the owner's baseline. This merge carries them unchanged.
…over TLS #1094's a_fetch_passes_over_a_member_that_answers_first_with_other_bytes stood up member 0 as a plain-HTTP responder at the scheme of the member's endpoint. Under https-only member clients that scheme is https, so the device's TLS ClientHello reached a responder that read it as an HTTP head ("the request ended before its head did"). The responder never answered, the relays never opened, and the fetch returned None. The test was red at #1096's head. - The node set keeps its CA, and NodeSet::tls_for (with Fleet::tls_for) issues the certificate a member serves, naming it, for a test that stands a member up at another address. - The substitute serves that certificate (tokio-rustls, already an SDK dependency), so the device reaches it as it reaches the member, and the test again exercises fetch_verified. - Mutation control: fetch_verified keeping the first answer whatever it hashes to → red ("other bytes" kept). The test is load-bearing again. storage_node_sdk tests: 8/0 (release, Postgres, TLS harness). fmt and dsm_sdk clippy --all-targets are clean.
…om its exercise before passing the key (12e) Owner ruling, 2026-10-01: "If a vault key is held by an exercise whose pair is not yet registered, the relayer MUST use the signed C_q carried by that exercise to register the trader's missing pair before advancing past that held key. ... After successful pair registration, the relayer retries progression." - The walk a trade, route or close makes for its live attempt (walk_for_attempt) checks each held key it would pass: when the exercise's pair stands Pending (neither registered nor lost), it registers the pair with relay_exercise, carrying the trader's bytes exactly, and walks again. A registered or lost pair, or one the reads do not decide, is not written. - A trade and a close walk each vault's chain past withheld pairs before drafting (chain_past_withheld_pairs), so they draft at the head those registrations leave. Tests: - an_exercise_whose_trader_withholds_its_pair_is_registered_from_its_own_bytes now has the vault's owner close the vault. A claim A signs for B's pair occupies nothing; A's close registers B's pair from B's exercise, closes at the head B's trade left, and B's position realizes. - a_held_key_whose_pair_is_registered_is_passed_without_writing_the_pair: every member holds the same values at B's two cells before and after A's close. Mutations, each red: the walk never relays (A's close is RetriesExhausted behind the key); a registered pair counts as withheld (A's close is refused after relaying it round after round).
… security/beta-pre-audit # Conflicts: # dsm_client/deterministic_state_machine/dsm_sdk/src/handlers/node_e2e_tests.rs
…a withheld pair - §6.66 12e: the record (exploit, closure, the owner's 2026-10-01 ruling "Choose \"Next trader.\" ... MUST ... before advancing past that held key", the cell value cap raised to 320 KiB and why), and six mutation controls. - §8 MR-SOFI-0362 Missing -> Met. MR-DSM-0213 cites relay_exercise. - §6.64: the new per-entry maximum beside the cell GET paging item (STORAGE). - §6.65: its open note points at the closure. - VERIFICATION_MATRIX: the 12e row. - §7 regenerated.
cryptskii
marked this pull request as ready for review
October 2, 2026 02:55
cryptskii
added a commit
that referenced
this pull request
Oct 2, 2026
…r, and names §17.5 and §33 Owner ruling, 2026-10-01 (pre-audit 12e), quoted verbatim in S20 as the producer's MUST: the next operation at a vault registers a withheld pair from the signed C_q its exercise carries before advancing past the key, and retries. S20's amends list now names §17.5 (the exercise's new field) and §33. Written on #1096 at CORE's request. MASTER: §1 re-pinned (SoFi 7792794…, 2694 lines); MR-SOFI-0362 carries the MUST.
cryptskii
added a commit
that referenced
this pull request
Oct 2, 2026
…ce id After #1096 an online send names its recipient by device id, never by alias, and practice mode finds the contact the same way. The sandbox test's practice send now goes to PRACTICE_CONTACT_DEVICE_ID.
This was referenced Oct 2, 2026
cryptskii
added a commit
that referenced
this pull request
Oct 2, 2026
#1096 carried the owner's correction to DSM Amendment A8 for sources: a credit's source is validated through its own segment, not one hop back and no further. It renamed the frontier test to match: a_credits_source_is_validated_one_hop_back_and_no_further → a_credits_source_is_validated_through_its_own_segment. The two manifest rows citing it still named the old test, so their evidence named no test and neither row could be repinned. MASTER's text for both rows already states the corrected rule. The pins are taken in the repin commit that follows, with the evidence change accepted.
cryptskii
added a commit
that referenced
this pull request
Oct 2, 2026
…med unpinned #1096 merged before its repin, so main's Code map read 622 PIN_STALE, 1 UNPINNED and 1 ORPHAN_PIN. Taken from this PR's own Code map (run 36963250731, at 79048b7) and an evidence log of the same commit: 326 tests, 326 passed. Each key goes through intent_pins' own rules (prepare, judge_repin, refuse_evidence, seal_new), with the map loaded once: - 622 repinned. All code-class except four: - MR-DSM-0273 and MR-DSM-0275: their evidence names the test #1096 renamed (accepted: evidence). - MR-SOFI-0234, two keys: its §8 status moved Met → Partial in #1096, recorded at §6.66 (12d) (accepted: status). - MR-SOFI-0356 held_at pinned; the parent_named orphan unpinned. make requirement-map-intent MAP=… INTENT_BUILT=android,node: 0 failing rows, 0 failing pins, 635 PINNED.
cryptskii
pushed a commit
that referenced
this pull request
Oct 2, 2026
The Code map read 2 sentinels lost, so the real map was not clean before planting and four mutation cases failed with it. Both subjects changed on purpose in #1096, so the rows change here, as the sentinel file says: - OneHop became SourceAncestry (a credit's source is validated through its own segment, the corrected DSM A8). It is built in the same function, the same way, and dispatched through the same &dyn ProvenanceResolver, so its anchored_policy_bytes reads as OneHop's did: REACHED_VIA_DISPATCH. - PrecommitLookup and its BTreeMap impl are gone (12j: registration reads no P at all). The row is removed. The map still reads 14 other IND_AMBIGUOUS_SELF_TYPE definitions on android, and the rule keeps its fixture case (one-twin-is-renamed). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y4wDTToHmJfkYYJvMKXt3u
cryptskii
pushed a commit
that referenced
this pull request
Oct 2, 2026
rules::every_rule_has_a_row_and_every_citation_exists failed on the previous commit: two rule rows cited the sentinels it changed. - trait-dispatch cites SourceAncestry's anchored_policy_bytes, OneHop's successor. - IND_AMBIGUOUS_SELF_TYPE cites none, with the gap saying why: its one real sentinel, PrecommitLookup's impl on BTreeMap, went with #1096 (12j). The map still reads this code on android; a sentinel is taken from a real reading of it at the next map. Its unit tests, fixture readings and mutation case stand. requirement_map's unit tests: 99 passed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y4wDTToHmJfkYYJvMKXt3u
cryptskii
added a commit
that referenced
this pull request
Oct 2, 2026
…ade real, repin after #1096 (#1098) - Android instrumented tests: the instrumented config names https://127.0.0.1:8080; the SDK's loader refuses any endpoint that is not https (#1096). - Real-code guard: #1095's 21 flagged frontend lines made real (fixture hunks reversed to the baseline's lines; practice refusals and protocolDefined computed from Rust's answers). - Repin after #1096: 622 code moves, held_at pinned, the parent_named orphan unpinned; MR-DSM-0273 and MR-DSM-0275 cite the test #1096 renamed. - Code map sentinels: OneHop's row follows it to SourceAncestry; PrecommitLookup's row is removed with #1096 (12j), and rules.tsv records the gap for IND_AMBIGUOUS_SELF_TYPE.
cryptskii
added a commit
that referenced
this pull request
Oct 2, 2026
… beta (#1097) - Practice mode is contained at the bridge: while it runs, only reads (READ_ROUTES, read bridge methods, display preferences) reach native code; writes are answered by practice or refused. - The guided tour covers the online beta; its practice contact is a code Rust reads (identity_routes test pins the frontend's copy). - A practice send names its recipient by device id, as #1096 requires. - Practice addContact answers accepted from the contact it holds (real-code guard). - Merged with #1098: practice ERA's protocolDefined is Rust's balance.list row, so a practice send reads balance.list as well as wallet.amount.
This was referenced Oct 2, 2026
cryptskii
added a commit
that referenced
this pull request
Oct 10, 2026
…r, and names §17.5 and §33 Owner ruling, 2026-10-01 (pre-audit 12e), quoted verbatim in S20 as the producer's MUST: the next operation at a vault registers a withheld pair from the signed C_q its exercise carries before advancing past the key, and retries. S20's amends list now names §17.5 (the exercise's new field) and §33. Written on #1096 at CORE's request. MASTER: §1 re-pinned (SoFi 7792794…, 2694 lines); MR-SOFI-0362 carries the MUST.
cryptskii
added a commit
that referenced
this pull request
Oct 10, 2026
…ard lines are real Main at 9ba1684 was red on four jobs. This commit fixes three; the fourth (Code map) is the repin that follows. - Android instrumented tests. androidTest/assets/dsm_env_config.instrumented.toml named http://127.0.0.1:8080. The SDK's config loader now refuses any endpoint that is not https:// (#1096), so initSdk never installed the app router and every instrumented test failed. The suite never reaches a node, so the endpoint only has to pass the loader: https://127.0.0.1:8080. - Real-code guard and frontend type check (the type check runs the guard first; tsc itself was clean). #1095 left 21 flagged lines: - In four frontend tests (EnhancedWalletScreen.events, offlineCash, faucetClaim, wallet), #1095 edited grandfathered fixture lines to write ERA at two decimals. These tests check that the frontend passes through what Rust renders, and the frontend does no decimal math, so #1095's hunks in those files are reversed. Their lines are again the ones the baseline holds, and the tests check the same mapping. #1096's change to the wallet screen test is kept. - practiceMode's two refusal answers state success/accepted as whether the debit went through: !('refused' in result). - Practice ERA's protocolDefined is Rust's: ERA's row in the router's balance.list, which lists ERA at any balance. The frontend never decides it from a ticker. The ingress test that writes the practice wallet's record of Rust's answers now records balance.list too, and asserts Rust lists ERA as protocol-defined; the record is rewritten from the live ingress. practiceMode.test checks practice ERA against Rust's row. Mutation: practice ERA not taking Rust's value → red. Full-tree guard: exit 0. tsc: 0 errors. jest over the tour, wallet screen, offline cash, faucet and wallet tests: 75 passed plus the new practice test. The dsm_sdk ingress test passes against the rewritten record. fmt is clean.
cryptskii
added a commit
that referenced
this pull request
Oct 10, 2026
#1096 carried the owner's correction to DSM Amendment A8 for sources: a credit's source is validated through its own segment, not one hop back and no further. It renamed the frontier test to match: a_credits_source_is_validated_one_hop_back_and_no_further → a_credits_source_is_validated_through_its_own_segment. The two manifest rows citing it still named the old test, so their evidence named no test and neither row could be repinned. MASTER's text for both rows already states the corrected rule. The pins are taken in the repin commit that follows, with the evidence change accepted.
cryptskii
added a commit
that referenced
this pull request
Oct 10, 2026
…med unpinned #1096 merged before its repin, so main's Code map read 622 PIN_STALE, 1 UNPINNED and 1 ORPHAN_PIN. Taken from this PR's own Code map (run 36963250731, at f0c1af8) and an evidence log of the same commit: 326 tests, 326 passed. Each key goes through intent_pins' own rules (prepare, judge_repin, refuse_evidence, seal_new), with the map loaded once: - 622 repinned. All code-class except four: - MR-DSM-0273 and MR-DSM-0275: their evidence names the test #1096 renamed (accepted: evidence). - MR-SOFI-0234, two keys: its §8 status moved Met → Partial in #1096, recorded at §6.66 (12d) (accepted: status). - MR-SOFI-0356 held_at pinned; the parent_named orphan unpinned. make requirement-map-intent MAP=… INTENT_BUILT=android,node: 0 failing rows, 0 failing pins, 635 PINNED.
cryptskii
pushed a commit
that referenced
this pull request
Oct 10, 2026
The Code map read 2 sentinels lost, so the real map was not clean before planting and four mutation cases failed with it. Both subjects changed on purpose in #1096, so the rows change here, as the sentinel file says: - OneHop became SourceAncestry (a credit's source is validated through its own segment, the corrected DSM A8). It is built in the same function, the same way, and dispatched through the same &dyn ProvenanceResolver, so its anchored_policy_bytes reads as OneHop's did: REACHED_VIA_DISPATCH. - PrecommitLookup and its BTreeMap impl are gone (12j: registration reads no P at all). The row is removed. The map still reads 14 other IND_AMBIGUOUS_SELF_TYPE definitions on android, and the rule keeps its fixture case (one-twin-is-renamed).
cryptskii
pushed a commit
that referenced
this pull request
Oct 10, 2026
rules::every_rule_has_a_row_and_every_citation_exists failed on the previous commit: two rule rows cited the sentinels it changed. - trait-dispatch cites SourceAncestry's anchored_policy_bytes, OneHop's successor. - IND_AMBIGUOUS_SELF_TYPE cites none, with the gap saying why: its one real sentinel, PrecommitLookup's impl on BTreeMap, went with #1096 (12j). The map still reads this code on android; a sentinel is taken from a real reading of it at the next map. Its unit tests, fixture readings and mutation case stand. requirement_map's unit tests: 99 passed.
cryptskii
added a commit
that referenced
this pull request
Oct 10, 2026
…ce id After #1096 an online send names its recipient by device id, never by alias, and practice mode finds the contact the same way. The sandbox test's practice send now goes to PRACTICE_CONTACT_DEVICE_ID.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The owner's beta security pre-audit, combined into one PR across the CORE, STORAGE and SOFI sessions. Audited at main
076792eac.specs/requirements/CONFORMANCE_GAPS.mdis the ledger: each finding is recorded with its exploit, its closure condition and its evidence, and is closed only with an adversarial test and a mutation control.Landed so far
Item 2 (Critical): SoFi parent-root authentication (§6.62, SOFI)
The flaw.
The fix.
p: the claim final atK_root(p)and its root.peer_root_at). It is the same check A8 and S15 already use; nothing new is added ineconomic/.ParentPosition::SingleRoot { named, held, held_root }: the ladder computes compatibility from the evidence instead of asserting it.Evidence.
Adversarial tests:
pgives Invalid, and the key is Skipped;On the old code the first two resolved
Ok(Realized).Three mutations turn the tests red.
Lean:
ParentState.singleNotHeldand theoremsingle_root_parent_not_held_is_invalid. Dropping the case fromparentImpossiblemakes the theorem fail to check.Still depends on item 1 (CORE): claims at
K_rootbound to the DevID key.Item 6 (P0): a send names its recipient by device id (§6.63, SOFI)
The flaw.
wallet.sendSmartresolved the recipient by alias. Aliases are not unique, so a send could pay whichever contact the database returned first under that alias.The fix.
OnlineTransferSmartRequestreserves field 1 (recipient) and carriesbytes recipient_device_id = 5, exactly 32 bytes.get_contact_by_aliashad no caller left and is removed.Evidence.
dsm_sdk::handlers::sender_admission_tests::a_send_pays_the_contact_chosen_not_the_first_of_its_alias: two contacts share an alias, and each is paid what was sent to it. On the old wire the chosen contact received 0.Items 7, 8, 10 and 11: storage (§6.64, STORAGE)
Merged from
fix/storage-dos-and-transport-hardening(5afda74d6):https://only, in the env config and in every member client. Each member client trusts only the configured CA and accepts only a certificate naming the member id. The SDK harness serves its nodes over TLS.Every gate was mutation-tested. Open items for the owner are listed in §6.64.
Client workspace lock
dsm_client/deterministic_state_machine/Cargo.lockgains the three dependency edges the merge introduced:axum-serverunderdsm_sdk,hyper-utilunderdsm_storage_node, andtokioundertower-http. No versions changed. Without it,cargo test --lockedin the client workspace refused to build.Item 1 (P0): a position cell's occupant proves its own authority (§6.65, CORE)
DSM Amendment A10, SoFi Amendment S20, per the owner's ruling: "If an object can win/occupy a root position, the object itself proves authority for that position."
Items 15 and 5: decoders bounded by their input; a nonce spent only in its relationship (§6.67, CORE)
Item 12 (P1): junk can no longer exhaust a SoFi walk or hold a key (§6.66, SOFI)
Closed (
96b870fdd), each with an adversarial node or unit test red on the old code, and 7 mutation controls:K^(a-1). Junk at 17 keys used to freeze a vault.In progress, 12j (critical, found by an adversarial pass). Which fulfillment holds K_ful(q) depended on when the precommit it names is published. A trader could make early verifiers see its trade realized and later ones see the position unresolvable, with the vault key frozen. A probe on the nodes confirmed it. The fix, agreed with CORE, decides K_ful(q) from F's bytes alone and pairs the cells by F's id. CORE is writing the S20 text; SOFI implements it with the probe as the test.
Open: 12e (the exercise carries the signed C_q, MR-SOFI-0362), 12f (a parent on a lineage known invalid), and 12g, 12h and 12i (owner rulings: a defeated route's other legs, withheld evidence, locators that can be flooded in advance).
Merged tree, at
2de9fa75bdsmlib (--locked --release)make lintKnown harness flake: under load, the two-device harness's faucet funding claim can find the previous reserve release not yet final, and the harness treats that as fatal. The device resumes it; the harness should too. The fix goes in
test_support(SOFI).Folding in next
Pins
INTENT_PINS will be repinned once, from this PR's Code map, after the folds.