{"record":{"id":"3c007f350d076273","repo":"Hmbown/CodeWhale","slug":"serialized-lane-exit-receipt-exceeds-size-bound","errorCode":null,"errorMessage":"serialized lane exit receipt exceeds size bound","messagePattern":"serialized lane exit receipt exceeds size bound","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/lane/src/runtime.rs","lineNumber":378,"sourceCode":"            bail!(\"invalid lane environment key {key:?}\");\n        }\n    }\n    Ok(environment)\n}\n\nfn write_lane_exit_receipt(\n    receipt_path: &Path,\n    receipt_tmp_path: &Path,\n    lane_id: &str,\n    exit_code: i32,\n) -> Result<()> {\n    let encoded = serde_json::to_vec(&LaneExitReceipt {\n        lane_id: lane_id.to_string(),\n        exit_code,\n    })\n    .context(\"serialize lane exit receipt\")?;\n    if encoded.len() as u64 > MAX_EXIT_RECEIPT_BYTES {\n        bail!(\"serialized lane exit receipt exceeds size bound\");\n    }\n    remove_file_if_present(receipt_tmp_path)?;\n    let mut options = OpenOptions::new();\n    options.create_new(true).write(true);\n    #[cfg(unix)]\n    {\n        use std::os::unix::fs::OpenOptionsExt;\n        options.mode(0o600);\n    }\n    let result = (|| {\n        let mut file = options\n            .open(receipt_tmp_path)\n            .with_context(|| format!(\"create {}\", receipt_tmp_path.display()))?;\n        file.write_all(&encoded)\n            .with_context(|| format!(\"write {}\", receipt_tmp_path.display()))?;\n        file.sync_all()\n            .with_context(|| format!(\"sync {}\", receipt_tmp_path.display()))?;\n        fs::rename(receipt_tmp_path, receipt_path)","sourceCodeStart":360,"sourceCodeEnd":396,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/lane/src/runtime.rs#L360-L396","documentation":"write_lane_exit_receipt serializes LaneExitReceipt {lane_id, exit_code} and refuses to persist it if the JSON exceeds MAX_EXIT_RECEIPT_BYTES (4 KiB, runtime.rs:173). The bound keeps the receipt file a fixed-size, cheap-to-validate artifact that read_lane_exit_receipt can trust. Since the payload is just an id and an integer, exceeding 4 KiB in practice means an absurdly long lane_id or external tampering.","triggerScenarios":"Calling a lane start/stop flow that reaches write_lane_exit_receipt with a lane_id whose UTF-8 JSON encoding plus wrapper exceeds 4096 bytes (thousands of characters).","commonSituations":"Generating lane ids from raw UUIDs concatenated with paths, full descriptions, or user input; fuzzing or adversarial input feeding the lane id; a misconfigured id generator producing megabyte-long strings.","solutions":["Shorten the lane id (a UUID or short slug is plenty) and retry","Cap lane id length where ids are generated, before they reach the runtime","If the id looks normal, check for tampering or corruption in the values feeding the receipt"],"exampleFix":"// before\nlet lane_id = format!(\"{}-{}\", uuid, user_supplied_description); // unbounded\n\n// after\nlet lane_id = format!(\"lane-{}\", uuid.simple());\nassert!(lane_id.len() <= 128);","handlingStrategy":"validation","validationCode":"fn lane_id_within_receipt_bound(lane_id: &str) -> bool {\n    // Receipt = {\"lane_id\":\"...\",\"exit_code\":N}; 4 KiB bound in runtime.rs:173.\n    lane_id.len() + 64 <= 4 * 1024\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Cap lane id length at generation time (128 chars is generous)","Never build lane ids from unbounded user input"],"tags":["rust","lane","exit-receipt","size-limit","validation"],"backgroundTag":"file-size-limit-exceeded","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}