feat(signing): ironclaw_attested_runtime — reborn AttestedResumePort + signer continuation + ship-gate (attested-signing PR10/12) - #3994
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 51ee75159d
ℹ️ 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".
There was a problem hiding this comment.
Code Review
This pull request introduces the ironclaw_chain_signing and ironclaw_attested_runtime crates, completing the attested-signing substrate with custodial key management, WalletConnect v2 support, and multi-chain signing capabilities. The implementation features a robust security model using one-shot grants, idempotency ledgers, and HSM/KMS ship-gates. Feedback focuses on enhancing the signer-continuation driver to support job recovery by ignoring existing ledger rows, ensuring the custodial path verifies user authorization proofs, and handling poisoned mutexes explicitly. Further improvements include using zeroizing types for sensitive key material and centralizing duplicated hex utility logic to minimize the maintenance surface and adhere to project rules.
Zeroize the transient hex-encoded private key in SecretsKeyStore::bind after encryption consumes it. The hex copy lived in a plain String that does not zeroize on drop, leaving key material in process memory longer than necessary. Scrub it unconditionally (success or error) using the zeroize re-export already provided by secrecy — no new dependency. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
henrypark133
left a comment
There was a problem hiding this comment.
Code Review (multi-agent)
Intent: Wire attested-signing substrate into reborn runtime via new ironclaw_attested_runtime crate with resume port, continuation driver, and ship-gate
Stats: 23 findings (from 27 raw) across 12 files. Reviewers run: security, bugs, performance, tests, conventions, local-patterns, maintainability, pattern-refactor. Reviewers failed: none. Body-only: 8
Security (7)
-
High NEAR FunctionCall scope validation relaxed — fail-closed check removed (
crates/ironclaw_wallet_external/src/near_redirect/mod.rs:307-340, confidence 100) — anchor:crates/ironclaw_wallet_external/src/near_redirect/mod.rs:323(no diff position — body only)
The diff removes the fail-closed check in validate_access_key_scope that rejected FunctionCall access keys when bound.receiver_id was None. An attacker with a FunctionCall key restricted to receiver A could authorize a transaction targeting receiver B.
Fix:Restore the fail-closed check: when bound.receiver_id is None, reject FunctionCall scopes. -
High NEAR redirect verifies signature against callback-supplied key instead of gate-bound key (
crates/ironclaw_wallet_external/src/near_redirect/mod.rs:258-280, confidence 100) — anchor:crates/ironclaw_wallet_external/src/near_redirect/mod.rs:270(no diff position — body only)
The diff removes the expected_access_key field and changes verify_signature_over_hash to use the callback-supplied key instead of the gate-bound key. An attacker can supply their own keypair and pass verification.
Fix:Restore the expected_access_key field. Verify the signature against the gate-bound key. -
Medium Hex decode can panic on non-ASCII input (DoS) (
crates/ironclaw_wallet_external/src/near_redirect/mod.rs:415-428, confidence 75) — anchor:crates/ironclaw_wallet_external/src/near_redirect/mod.rs:417(no diff position — body only)
hex_bytes::hex_decode uses u8::from_str_radix on &str slices which panics on non-ASCII at odd byte offsets.
Fix:Revert to byte-based decoder using as_bytes().chunks_exact(2). -
Medium Hex parse in signer.rs can panic on non-ASCII bound_account (DoS) (
crates/ironclaw_wallet_external/src/walletconnect/signer.rs:195-231, confidence 75) — anchor:crates/ironclaw_wallet_external/src/walletconnect/signer.rs:197
parse_evm_address and parse_ed25519_pubkey use u8::from_str_radix on &str slices which panics on non-ASCII.
Fix:Use byte-based parsing with as_bytes().chunks_exact(2). -
Medium encode_walletconnect_proof silently returns empty bytes on serialization failure (
crates/ironclaw_wallet_external/src/walletconnect/mod.rs:271-273, confidence 75) — anchor:crates/ironclaw_wallet_external/src/walletconnect/mod.rs:271
encode_walletconnect_proof uses serde_json::to_vec(payload).unwrap_or_default().
Fix:Return Result<Vec<u8>, SigningProviderError> and propagate the error. -
Medium No input size limit on proof payload deserialization (
crates/ironclaw_wallet_external/src/walletconnect/proof.rs:52-57, confidence 50) — anchor:crates/ironclaw_wallet_external/src/walletconnect/proof.rs:52
decode_walletconnect_proof calls serde_json::from_slice on arbitrary input without size limit.
Fix:Add a size check before deserialization. -
Low Hardcoded local-dev master key in source code (
crates/ironclaw_reborn_composition/src/runtime.rs:917-930, confidence 50) — anchor:crates/ironclaw_reborn_composition/src/runtime.rs:925
build_attested_composition hardcodes the local-dev master key.
Fix:Generate a random key at startup or read from config.
Bugs (3)
-
Medium SessionBindingStore::record silently drops binding on poisoned lock (
crates/ironclaw_wallet_external/src/walletconnect/session.rs:59-63, confidence 75) — anchor:crates/ironclaw_wallet_external/src/walletconnect/session.rs:60
If the Mutex is poisoned, record() silently does nothing.
Fix:Panic or return an error when the lock is poisoned.
Also flagged by: tests/Medium
Also flagged by: security/Medium -
High External-wallet path advances ledger to Signing before verify_resume, leaving it stuck on failure (
crates/ironclaw_attested_runtime/src/driver.rs:344-353, confidence 100) — anchor:crates/ironclaw_attested_runtime/src/driver.rs:344
The ledger is advanced to Signing BEFORE provider.verify_resume is called. If verify_resume fails, the ledger is stuck at Signing.
Fix:Move the ledger.advance call to after verify_resume succeeds.
Also flagged by: tests/High -
Medium recover_unknown silently swallows non-InvalidTransition errors in release builds (
crates/ironclaw_attested_runtime/src/driver.rs:536-551, confidence 75) — anchor:crates/ironclaw_attested_runtime/src/driver.rs:544
debug_assert! is a no-op in release, so any other error is silently swallowed.
Fix:Replace debug_assert! with tracing::warn!.
Performance (3)
-
Critical hex_decode panics on non-ASCII input — callback DoS (
crates/ironclaw_wallet_external/src/walletconnect/mod.rs:293-302, confidence 90) — anchor:crates/ironclaw_wallet_external/src/walletconnect/mod.rs:293
hex_decode uses &str slicing which panics when the input contains non-ASCII bytes at an odd character boundary. Proof payloads come from external wallets.
Fix:Replace str-slicing with byte-chunk decoding. -
Medium SessionBindingStore::record silently drops errors on poisoned lock (
crates/ironclaw_wallet_external/src/walletconnect/session.rs:61-65, confidence 75) — anchor:crates/ironclaw_wallet_external/src/walletconnect/session.rs:61
If the Mutex is poisoned, record() silently discards the binding update.
Fix:Return Result<()> from record(). -
Medium hex_lower allocates String on every sync resume in contended mutex path (
crates/ironclaw_attested_runtime/src/port.rs:113-114, confidence 65) — anchor:crates/ironclaw_attested_runtime/src/port.rs:113
verify_attested_resume calls hex_lower which allocates a new String inside the turn store resume mutex.
Fix:Compare bytes directly without hex encoding.
Also flagged by: maintainability/Low
Tests (5)
-
Medium recovery_id_from_v invalid v values and parse_evm_address edge cases untested (
crates/ironclaw_wallet_external/src/walletconnect/signer.rs:170-184, confidence 75) — anchor:crates/ironclaw_wallet_external/src/walletconnect/signer.rs:170
Invalid v values and parse_evm_address edge cases are never tested.
Fix:tests::walletconnect::signer::recovery_id_from_v_invalid -
Medium BindingChainMismatch error path untested at driver level (
crates/ironclaw_attested_runtime/src/driver.rs:395-397, confidence 75) — anchor:crates/ironclaw_attested_runtime/src/driver.rs:395
The driver-level chain mismatch re-check has no test.
Fix:tests::driver::driver_rejects_binding_chain_mismatch -
Medium Contradictory broadcaster behavior paths untested (
crates/ironclaw_attested_runtime/src/driver.rs:486-516, confidence 75) — anchor:crates/ironclaw_attested_runtime/src/driver.rs:486
Two contradictory broadcaster cases have no tests.
Fix:tests::driver::submitting_broadcaster_returns_not_broadcast_unknown -
Medium EIP-2930 rebuild roundtrip and all RebuildError variants untested (
crates/ironclaw_attested_runtime/src/driver/rebuild.rs:79-88, confidence 75) — anchor:crates/ironclaw_attested_runtime/src/driver/rebuild.rs:79
No test for EIP-2930 rebuild or any error path.
Fix:tests::driver::rebuild::eip2930_rebuild_roundtrips_signature_hash -
Medium CustodialMainnetShipGate::from_env() env-var parsing untested (
crates/ironclaw_attested_runtime/src/ship_gate.rs:39-48, confidence 75) — anchor:crates/ironclaw_attested_runtime/src/ship_gate.rs:39
from_env() parses CUSTODIAL_MAINNET_ENABLED. No test verifies the parsing logic.
Fix:tests::ship_gate::from_env_truthy_values
Local Patterns (3)
-
Low Orphaned // follow-up: comment interrupts doc-comment block (
crates/ironclaw_attested_runtime/src/driver.rs:450-454, confidence 75) — anchor:crates/ironclaw_attested_runtime/src/driver.rs:450
A regular comment sits between doc-comment paragraphs.
Fix:Move the follow-up note into the doc-comment block. -
Medium New crate missing AGENTS.md that every sibling crate has (
crates/ironclaw_attested_runtime/:1-1, confidence 100) — anchor:crates/AGENTS.md(no diff position — body only)
The new ironclaw_attested_runtime crate has no AGENTS.md.
Fix:Add crates/ironclaw_attested_runtime/AGENTS.md. -
Medium Error enums use manual Display+Error impls instead of thiserror derive (
crates/ironclaw_attested_runtime/src/binding.rs:24-69, confidence 75) — anchor:crates/ironclaw_chain_signing/src/error.rs:8-13
BindingError, ContinuationError, and RebuildError use manual impls while neighboring crates use thiserror.
Fix:Add thiserror and replace manual impls.
Maintainability (2)
-
Medium BroadcastDisposition and BroadcastOutcome are structurally identical and map 1:1 (
crates/ironclaw_attested_runtime/src/driver.rs:84-118, confidence 75) — anchor:crates/ironclaw_attested_runtime/src/driver.rs:478
Two types with identical shape and a 1:1 mapping.
Fix:Unify into a single enum. -
Low CustodialSignerLike trait has one method and one impl (
crates/ironclaw_attested_runtime/src/driver.rs:555-580, confidence 50) — anchor:crates/ironclaw_attested_runtime/src/driver.rs:568
The trait exists to avoid naming generic parameters.
Fix:Either make the driver generic or document the tradeoff.
… hardening) NEAR redirect (ironclaw_wallet_external): - High: verify the ed25519 signature against the GATE-BOUND access key, not the callback-supplied one. The bound NEAR identity now carries the expected access-key pubkey (account_id:hex); a callback that substitutes its own key fails closed with SignerMismatch (threat #4), mirroring the Solana provider. - High: restore fail-closed FunctionCall scope validation. Until the borsh tx receiver decode lands, a restricted FunctionCall key cannot be proven to cover the bound operation, so it is refused rather than accepted (threat #22). - Critical/Medium: byte-based hex decoding everywhere (near_redirect, walletconnect mod/signer, solana) so non-ASCII callback input can no longer panic (DoS). - Medium: proof encoders return Result instead of silently emitting empty bytes; decoders reject oversized payloads before deserialization. - Medium: SessionBindingStore recovers a poisoned mutex instead of silently dropping the binding. Attested runtime driver (ironclaw_attested_runtime): - High: verify-before-advance on the external-wallet path — the ledger only advances to Signing after verify_resume succeeds, so a rejected proof leaves the row at Approved instead of stranding it at Signing (one-shot deadlock). - Medium: recover_unknown logs at warn! on a non-benign ledger error instead of a release-compiled-out debug_assert. - Tests: external-wallet verify-fail/success/provider-mismatch, driver-level BindingChainMismatch, contradictory broadcaster paths, EIP-2930 rebuild + rebuild error paths, and ship_gate from_env parsing. Local-dev composition (ironclaw_reborn_composition / ironclaw_secrets): - Low: replace the hardcoded local-dev master key with a random per-process key (SecretsCrypto::generate); in-memory stores need no stable key. Preserves the sealed one-shot grant, ApprovedTxHash binding, broadcast idempotency, deterministic resume, ship-gate, and openssl-free invariants. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Review response — henrypark133 CHANGES_REQUESTEDPushed Highest-priority (auth-bypass class)NEAR signature verified against the gate-bound key — GENUINE, fixed. This was real, not stale. The verifier previously checked the ed25519 signature against the callback-supplied NEAR FunctionCall scope fail-closed — fixed. Restored: when the bound operation's receiver is unknown (borsh tx decode not yet available), a restricted FunctionCall key cannot be proven to cover the operation, so it is now refused ( External-wallet ledger verify-before-advance — fixed. The driver now runs Disposition summary
The load-bearing invariants are preserved: sealed one-shot grant CAS, ApprovedTxHash binding, broadcast idempotency, deterministic resume (no LLM re-entry), ship-gate, TenantId-first isolation, and crypto-free |
…<hex> (#3993) Security-critical: the NEAR signer string is committed into ApprovedTxHash, so -08 and -10 MUST agree on its format or a proof valid on one branch is invalid/forgeable on another at the rebase cascade. Adopt -10's canonical shape (attested-signing-10-reborn-runtime): the expected ed25519 access-key pubkey is bound INSIDE SigningContext.key_or_account_id as `account_id:<64-char lowercase-hex 32-byte pubkey>`, parsed by BoundNearIdentity::parse, and the signature is verified against the BOUND key (never the callback-supplied one). This is strictly stronger than -08's prior out-of-band `expected_access_key: Vec<u8>` held on the provider, since the key is now part of the SigningContext and thus committed into ApprovedTxHash. near_redirect/{mod,state,verify}.rs are now byte-identical to -10: - mod.rs: BoundNearIdentity + verify_resume (account+key binding), the Result-returning encode_near_redirect_proof, decode size ceiling, and the local hex_bytes module — verbatim from -10. - Removed expected_access_key field + with_expected_access_key constructor (the weaker out-of-band path). - Reverted the -08-only crate::hex_codec extraction (deleted hex_codec.rs; restored per-module local hex_bytes/opt_hex_bytes) so the module dedupes cleanly against -10, which has no hex_codec. - Restored NearRedirectState/encode_state/decode_state exports to match -10. Web ingress (src/channels/web/.../attested.rs): - verify_near_redirect_proof drops the expected_access_key arg (the key now rides in context.key_or_account_id) and constructs via the plain NearRedirectSigningProvider::new. - near_proof_from_input maps the new fallible encode to a 500. - Tests build the canonical `account_id:<hex>` bound identity; added verify_near_redirect_proof_rejects_malformed_bound_identity (caller-level fail-closed on a non-canonical, account-only binding). Tests: tests/near_redirect.rs is byte-identical to -10 (includes attacker_supplied_key_is_signer_mismatch). New -08-only tests/near_redirect_binding.rs covers BoundNearIdentity::parse edge cases via verify_resume (missing ':', empty account, non-hex pubkey, wrong length, rsplit-on-last-colon), kept separate so the shared file stays identical for clean dedup. Verify (IRONCLAW_DISABLE_OS_KEYCHAIN=1): cargo test -p ironclaw_wallet_external (19 lib + 12 + 5 + 10 integration green), web attested tests green, cargo fmt --check clean, cargo clippy -p ironclaw_wallet_external --all-features --tests zero warnings. near_redirect/mod.rs confirmed byte-identical to attested-signing-10-reborn-runtime. Cross-ref #3994 (NEAR auth-bypass findings). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2e98c5b to
6da381e
Compare
…+ signer continuation + ship-gate (attested-signing PR10) Squashed for stack integration (runtime feat + PR10 review: verify-before-advance, immutable CAS binding, broadcast-failure recovery + #3994 auth-bypass/DoS hardening). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2fd8c94 to
b5f0f6f
Compare
| /// In-memory [`ResumeGuard`]. A single mutex makes claim atomic. | ||
| #[derive(Debug, Default)] | ||
| pub struct InMemoryResumeGuard { | ||
| claimed: Mutex<HashSet<String>>, |
There was a problem hiding this comment.
Medium — InMemoryResumeGuard and InMemoryAttestedGateBindingStore grow unboundedly; claimed gate_refs and bindings are never evicted. The guard's HashSet<String> accumulates every gate_ref ever resolved (one String per gate, forever). The binding store's HashMap accumulates every binding ever persisted. These are in-memory only and PR12 defers durable backends — but even for a multi-day local-dev session with moderate gate volume this is a process-lifetime leak. The deferred PR12 note covers the durable backend but should explicitly acknowledge that the in-memory stores need a TTL/eviction policy. Suggested: add a // TODO(PR12): these in-memory stores have no eviction; a process-lifetime leak for long-running local-dev sessions. The durable backends in PR12 must include a TTL-based cleanup strategy. comment on both types, and consider a bounded LRU for the guard (old resolved gates are definitionally inert).
There was a problem hiding this comment.
Still real — leaving open. Both InMemoryResumeGuard.claimed (Mutex<HashSet<String>>, port.rs:50) and the in-memory binding store grow without bound: a claimed gate_ref is never evicted, so a long-running local runtime accumulates one entry per attested gate forever.
Bounded by what these types are — in-memory dev/test backends; the durable Postgres/libSQL stores land in #3996 and are the production answer. But "the dev backend leaks memory proportional to gates processed" is still a real defect in a runtime that is meant to run for days locally.
The eviction rule needs care rather than a simple cap, which is why I'm not fixing it inline: entries can only be dropped once the corresponding gate is terminal in the ledger, otherwise eviction silently restores replayability — the exact property the guard exists to provide. Tracked in #6532 alongside the durable-store work.
There was a problem hiding this comment.
Now fixed for the resume guard, with the safety argument made explicit rather than assumed.
InMemoryResumeGuard is bounded at MAX_CLAIMED_GATES = 8192 with FIFO eviction. The reason eviction is safe here — and the reason I wouldn't have done it blindly — is that the guard is defense in depth, not the primary control: the module doc states the resume guard and the sealed-grant CAS are two independent one-shot controls and either alone fails a replay closed. An evicted gate that is replayed still hits the authoritative sealed-grant CAS in the driver and is refused. That reasoning is now recorded on the constant so a future reader doesn't raise the cap or drop the eviction without understanding what backs it.
Pinned by resume_guard_is_one_shot_and_bounded (one-shot still holds; the set stays bounded past the cap).
The binding store is not bounded — eviction there is only safe once the gate is ledger-terminal, and the store has no ledger access. Leaving that half open; it belongs with the durable stores (#3996).
|
Architectural note (not a finding) — Gate ownership user_id check (deferred from PR7): RESOLVED by the turns store The review brief asked to check whether the gate ownership
if record.scope != request.scope {
return Err(TurnError::ScopeNotFound);
}
if record.actor != request.actor {
return Err(TurnError::Unauthorized);
}
Verdict: the deferred gate ownership check is correctly owned by the turns store, not the port. The PR10 port is correctly scoped to crypto verification only. This was architecturally sound and the concern from PR7 is closed. |
|
Architectural note — WalletConnect
Recommendation for PR11: the |
henrypark133
left a comment
There was a problem hiding this comment.
PR10/10 attested-signing terminal PR — Approved with Medium findings
PR10 completes the attested-signing substrate with clean composition-layer glue: ironclaw_attested_runtime (new crate), the production AttestedResumePort, the deterministic AttestedSignerContinuationDriver, and the CustodialMainnetShipGate env-gate. All six lenses reviewed.
Mission isolation / sandbox escape: clean. ironclaw_turns stays crypto-free (dependency direction is attested_runtime → turns, never reverse). The binary-boundary rule is enforced.
Key/grant carry-through: correct. Grant CAS is one-shot, shared store between custodial signer and external-wallet providers, no double-claim path found.
Gate ownership user_id check (PR7 deferred): resolved — turns store enforces record.actor != request.actor (Unauthorized) and record.scope != request.scope before the port is called. Port correctly omits user_id.
Session boundaries: sound. Ledger is one-shot-create per gate_ref; broadcast-before-unknown path documented and tested.
Runtime privilege escalation: the ship-gate (#18) is fail-closed. Custodial mainnet requires both opt-in AND KMS backend; hot-key-only is testnet/dev-only.
Threats #1/#3/#5/#6/#7/#16/#18: all covered by end-to-end tests through the real composition path ("Test Through the Caller" rule honored).
Findings posted inline:
- Medium (3): unzeroized hex string in
SecretsCrypto::generate();unwrap_orinhex_lower(unreachable but masks intent);InMemoryResumeGuard/ binding store unbounded growth (no eviction) - Medium (1):
BroadcastSubmittedas pre-submit marker — acknowledged in code, deferredBroadcastingstate needed in PR12/14 - Low (1):
ProviderRegistry::with_providersilently overwrites duplicate ProviderId - Architectural notes (2): gate ownership user_id check confirmed resolved; WalletConnect empty directive recommendation for PR11
No Critical or High findings. Approving.
6da381e to
a95f8eb
Compare
b5f0f6f to
106cbdf
Compare
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
…dcast (#3965, squashed for cascade)
# Conflicts: # Cargo.lock # Cargo.toml # crates/ironclaw_architecture/tests/attested_signing_boundaries.rs
# Conflicts: # Cargo.lock # crates/ironclaw_wallet_external/src/lib.rs
a95f8eb to
f3afe32
Compare
106cbdf to
52dfc4a
Compare
|
Superseded by #6769, part of consolidating the 20-PR Verified before closing: this branch is an ancestor of the old stack tip, so its content is carried forward in full. The consolidation also re-based everything onto current Closing to keep the queue honest. The review history stays on this PR and remains readable; reopen if the consolidation is rejected. |
What this PR is
ironclaw_attested_runtime— the rebornAttestedResumePortimplementation, the signer-continuation driver, the attested gate-binding store, and the custodial-mainnet ship gate — plus the composition glue (RebornAttestedComposition) that assembles them. Lives outsidesrc/per the binary-boundary rule.Cascade changes (structural — please review)
-06(chain_signing) and-09(WalletConnect) onto-08. Those sibling merges captured stale snapshots (each sibling had since gained round-2 fixes). The base was rebuilt by merging the ported siblings onto ported-08, then replaying this PR's single integration commit on top. The boundary test now carries all three assertions (wallet_external purity, chain_signing-carries-SDK, openssl-free).main's store graph. The original wired the port intoInMemoryTurnStateStore, whichmaindeleted.production_turn_state_storegained anOption<Arc<dyn AttestedResumePort>>parameter; local-dev buildsRuntimeAttestedResumePortover the shared binding store and injects it via thewith_attested_resume_portseam added in feat(signing): turns BlockedAttested gate + AttestedResumePort + deterministic resume split (attested-signing PR5/10) #3966. The production/owner store builders passNone(durable backends are a later hop).build_attested_compositionassembles the composition (in-memory custodial keystore, ship gate from env so mainnet custodial signing stays refused, empty provider registry) and it is held asRebornServices.attested_signing(Somelocal-dev /Noneproduction) with aRebornRuntime::attested_signing()accessor.continue_after_resolvedcall here is in tests. Production dispatch belongs to the gate/resolve ingress PR (feat(signing): reborn webui attested gate/resolve ingress (attested-signing PR11/12) #3995), which is where the composition stored here gets driven.LedgerKey(tenant sourced frombinding.context.tenant;recover_unknowngained a tenant parameter);SecretsCrypto::generate()moved tomain's rand 0.9 API (SysRng::try_fill_byteswithrng().fillfallback).Verification
31
ironclaw_attested_runtimetests pass — including the full 21-test threat matrix. Composition e2e test driving the realRebornAttestedCompositionpasses. Whole workspace builds (incl. all test targets); boundary test green; clippy clean on the composition.Carried forward
tests/resume_through_store.rsis temporarily set aside — it drives the deleted in-memory store and needs the same row-store retarget as #3966's tests. Its security properties are covered by the threat matrix and #3966's new attested tests; it should be folded back in with #3995, where the ingress/dispatch it assumes actually exists.