Skip to content

[audit] Unfiled final-report Infos: I-01, I-02, I-05, I-06, I-07, I-11, I-13, I-14 #124

Description

@petarjutex

Source: Oxorio final audit report (22 June 2026) — Info-severity findings that had no GitHub issue. Grouped here per the "one issue for the unfiled Infos" decision. Each item was re-verified against dev@359dae4 (current HEAD).

Milestone: Pre-mainnet Audit Fixes
Big-picture map + prerequisites: docs/audit-final-status-and-dependencies.md
Report: Oxorio final 22.06.2026

All eight are present on dev. Severity is Info, so most are hardening/strictness/doc; a couple (I-13, I-14) are larger.


☐ I-01 — SignRawMessage signs arbitrary bytes with no domain / version / nonce

Layer: cross-layer (decision) · present
Handler is live in dispatch (server.rs); it only rejects empty input, wraps bytes in the EIP-191 envelope, and signs with the main bridge key (state.sign_evm) — same key that signs fundsOut EIP-712 digests. EIP-191 ≠ tx/EIP-712 encoding so it can't be replayed as a fundsOut/tx; residual risk is app-level. No production listener caller was found ⇒ likely dead surface.
Fix: confirm whether fundsIn-authorization still consumes it. If dead → remove the handler + proto entry + parent CLI/client. If live → bind to a domain-separated schema (domain/version + monotonic nonce) and reject anything that doesn't parse.

✅ I-02 — dev-mode panic-path on short calldata

Done — fixed on dev-ng via #145 (sign_request_digest no longer asserts on short calldata).
Layer: enclave · self-contained
signing/evm.rs::sign_request_digest uses assert!(call_data.len() >= 4). Under a dev-mode build the length guard in validate_evm_request is #[cfg(not(feature="dev-mode"))]-gated, so a SignEvm with call_data.len() < 4 falls through to the assert. panic = "abort" ⇒ the whole enclave dies, not just the connection.
Fix: make sign_request_digest fallible (return a length error) or add a len >= 4 guard that runs on every build before the signing call.

✅ I-05 — COSE verifier accepts DER signatures and does not check alg == ES384

Done — fixed on dev-ng via #126 (COSE requires raw 96-byte sig + asserts alg == ES384).
Layer: enclave (attestation-verify) · self-contained
parse_cose_ecdsa_signature accepts both raw r‖s (96 bytes) and DER; verify_real_document never decodes the protected header to assert alg == ES384 (-35). Not exploitable (key/Signature hardcoded P-384) — pure RFC-8152 strictness/hardening.
Fix: require the raw 96-byte r‖s (Signature::from_slice), drop the DER branch, and assert the protected-header alg == ES384.

☐ I-06 — Amount-binding is non-strict () rather than the spec's strict ==

Layer: enclave (burn leg self-contained) · present
validate_funds_out_burn rejects only burned < calldata_amount (so passes); validate_funds_out_transfer likewise rejects only total_output_amount < calldata_amount. Spec wants strict equality.
Fix: tighten the burn comparison to burned != calldata_amount. The transfer leg's exact == is only meaningful with per-output federation-seal binding (tracked separately as #58), since total_output_amount sums recipient + change. Land with the M-02 OpId binding.

☐ I-07 — BIP-86 account extended private keys are not zeroize-on-drop

Layer: enclave · self-contained
KeyManager.account_xpriv_vanilla / account_xpriv_colored (bitcoin::bip32::Xpriv) are plain fields (unlike seed/evm_secret/btc_secret in SecretBox); there's no impl Drop in the enclave crate, and Xpriv carries a sensitive chain_code. The local master + intermediate xprivs in from_seed are also dropped without a wipe. These re-derive every child signing key and live the whole session.
Fix: bring the account xprivs under the same zeroize discipline (store raw secret material in SecretBox and rebuild on demand, or impl Drop calling non_secure_erase on each private_key + overwriting the chain code); zeroize the local master/intermediates in from_seed.

☐ I-11 — Cross-chain metadata is request/calldata-supplied rather than derived from enclave-validated facts

Layer: cross-layer · present · depends on W-01 + W-05
sourceChainId/destinationChainId/sourceAddress are not bound to validated facts; the proto carries only the EIP-712 domain chain_id; there is no single canonical bridge-intent object committed to the signature.
Fix: assemble one canonical bridge-intent (source/dest chain, bridge contract, asset, amount, recipient, op-id, consignment hash), derive each field from one source of truth (pin → BridgeConfig; asset/amount/source-net → validated consignment; the rest → full ABI decode, i.e. W-01), and reject any calldata value that disagrees. Needs proto intent fields + contract-agreed encoding.

☐ I-13 — Master seed held in memory for the whole enclave lifetime + replicated cluster-wide

Layer: enclave (architectural) · present
KeyManager keeps the raw 64-byte seed (SecretBox) resident for the whole lifetime because the cloning donor re-seals it on demand (expose_seedwith_seedencrypt_seed_for_peer). One seed reconstructs every key and is shared cluster-wide ⇒ any single-enclave memory disclosure has cluster-wide blast radius.
Fix (architectural — likely deferred): minimize residency (unseal only for an active cloning handshake, zeroize after; drop entirely when cloning is disabled by policy), or move to per-enclave key shares / threshold signing. Needs a cloning-handshake redesign — file as tracked-but-deferred, not a quick fix.

☐ I-14 — Enclave cannot operate with tokens using the standard decimals = 18

Layer: cross-layer · present
extract_uint256_as_u64 rejects any amount whose high 24 bytes are non-zero (> u64::MAX1.844e19, i.e. > ~18.44 whole tokens at 18 decimals). The whole pipeline is u64: TransitionSummary.total_output_amount, RGB MS_BURNED_ASSET (rgbstd Amount is u64-backed), and proto calldata_amount/rgb_amount (uint64).
Fix / decision: widen the enclave amount path to u128/U256 and the proto amount fields — but note rgbstd's Amount is u64-backed upstream, so genuinely-large 18-decimal amounts are also bounded at the RGB layer. If real amounts stay ≤ u64 base units, prefer documenting the supported max + fail-closed parsing instead of a full widening. Needs a proto/listener decision.


Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    acknowlegedexternal-audit-findingTracked finding from the external UTEXO security audit (3rd preliminary report)

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions