{"record":{"id":"fe27b12c0c3289c0","repo":"astrid-runtime/astrid","slug":"refusing-to-seed-canonical-from-a-non-content-addr","errorCode":null,"errorMessage":"refusing to seed canonical from a non-content-address contracts pin","messagePattern":"refusing to seed canonical from a non-content-address contracts pin","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/contracts.rs","lineNumber":323,"sourceCode":"/// superseding whatever this seeded. Seeding is first-writer-wins and never\n/// overwrites, so a running daemon's baseline is never clobbered by a later\n/// side-loaded install (that install warns via [`contracts_skew`] instead).\n///\n/// Best-effort: the caller logs any failure and proceeds; retention of\n/// the canonical must never break an otherwise-successful install.\npub fn seed_canonical_contracts_if_absent<S: BuildHasher>(\n    home: &AstridHome,\n    wit_files: &HashMap<String, String, S>,\n) -> anyhow::Result<()> {\n    let Some(pin) = contracts_pin(wit_files) else {\n        return Ok(());\n    };\n    // Defense in depth: `pin` builds the `wit/store/<pin>.wit` lookup path\n    // below. The install caller passes a freshly content-addressed pin, but\n    // validate the BLAKE3-hex shape at the boundary so no caller can traverse\n    // out of the store (mirrors `daemon_fleet_contracts_pin`).\n    if !is_blake3_pin(pin) {\n        anyhow::bail!(\"refusing to seed canonical from a non-content-address contracts pin\");\n    }\n\n    let canonical = canonical_contracts_path(home);\n    if canonical.exists() {\n        return Ok(());\n    }\n\n    let blob = home.wit_store_dir().join(format!(\"{pin}.wit\"));\n    let content = std::fs::read(&blob)\n        .with_context(|| format!(\"failed to read contracts blob {}\", blob.display()))?;\n\n    // Create-if-absent (atomic), not write-or-replace: the `exists()` fast path\n    // above is a cheap common-case skip, but two installs racing that check\n    // must not clobber each other's canonical. `create_canonical_if_absent`\n    // returning `Ok(false)` means a racing installer won — first-writer-wins\n    // holds either way, so both outcomes are `Ok`.\n    create_canonical_if_absent(&canonical, &content)\n        .map(|_created| ())","sourceCodeStart":305,"sourceCodeEnd":341,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/contracts.rs#L305-L341","documentation":"seed_canonical_contracts_if_absent copies a supplied pin-named contracts blob to the canonical path (wit/astrid-contracts.wit). Because the pin is used to build the wit/store/<pin>.wit lookup path, a non-BLAKE3-hex pin could traverse out of the store (e.g. \"../evil\"). As defense in depth the function validates the pin's BLAKE3-hex shape at the boundary and refuses anything else, mirroring daemon_fleet_contracts_pin.","triggerScenarios":"Calling seed_canonical_contracts_if_absent (directly, or via the first-writer-wins / vendor-no-contracts test flows) with a pin argument that is not a valid 64-char lowercase BLAKE3 hex string — a short hash, a sha256, a path, or an empty/placeholder string.","commonSituations":"Passing short_hash()'s 12-char display prefix instead of the full pin; passing a filename or relative path as the pin; wiring a legacy pin format from an older store into the seeding path.","solutions":["Pass the full 64-character BLAKE3 hex pin as computed by blake3::hash(..).to_hex() for the contracts blob.","If you only have short_hash output, retrieve the full pin from the capsule's meta.json wit_files entry before seeding.","Guard your call site with the same is_blake3_pin check and surface a clear caller-side error instead of reaching this bail."],"exampleFix":"// before\nseed_canonical_contracts_if_absent(&home, short_hash(&pin), &blob)?;\n// after\nseed_canonical_contracts_if_absent(&home, &pin, &blob)?; // full blake3 hex","handlingStrategy":"validation","validationCode":"fn is_blake3_pin(pin: &str) -> bool {\n    pin.len() == 64 && pin.chars().all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())\n}\n// guard your call:\nassert!(is_blake3_pin(&pin), \"seed requires full blake3 hex pin\");","typeGuard":null,"tryCatchPattern":"match seed_canonical_contracts_if_absent(&home, &pin, &blob) {\n    Err(e) if e.to_string().contains(\"non-content-address\") => {\n        eprintln!(\"pin {:?} is not blake3 hex; fetch the full pin from meta.json\", short_hash(&pin));\n    }\n    other => other?,\n}","preventionTips":["Pass the full pin from blake3::hash(..).to_hex(), never short_hash output.","Treat display prefixes as presentation-only.","Sanitize any pin sourced from external input before seeding."],"tags":["rust","validation","path-traversal","blake3"],"backgroundTag":"path-traversal-blocked","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}