{"record":{"id":"f750603578b767e4","repo":"BloopAI/vibe-kanban","slug":"approval-path-should-be-valid","errorCode":null,"errorMessage":"Approval path should be valid","messagePattern":"Approval path should be valid","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"crates/services/src/services/events/patches.rs","lineNumber":172,"sourceCode":"                    serde_json::to_value(info).unwrap_or(serde_json::Value::Null),\n                )\n            })\n            .collect();\n\n        Patch(vec![PatchOperation::Replace(ReplaceOperation {\n            path: PENDING_PATH\n                .try_into()\n                .expect(\"Pending approvals path should be valid\"),\n            value: serde_json::Value::Object(pending),\n        })])\n    }\n\n    pub fn created(info: &crate::services::approvals::ApprovalInfo) -> Patch {\n        let value = serde_json::to_value(info).unwrap_or(serde_json::Value::Null);\n        Patch(vec![PatchOperation::Replace(ReplaceOperation {\n            path: pending_path(&info.approval_id)\n                .try_into()\n                .expect(\"Approval path should be valid\"),\n            value,\n        })])\n    }\n\n    pub fn resolved(approval_id: &str) -> Patch {\n        Patch(vec![PatchOperation::Remove(RemoveOperation {\n            path: pending_path(approval_id)\n                .try_into()\n                .expect(\"Approval path should be valid\"),\n        })])\n    }\n}\n","sourceCodeStart":154,"sourceCodeEnd":185,"githubUrl":"https://github.com/BloopAI/vibe-kanban/blob/4deb7eca8f381f7cbc1f9d15515a9ab8f8009053/crates/services/src/services/events/patches.rs#L154-L185","documentation":"This panic fires when the dynamically built path \"/pending/{approval_id}\" fails `try_into()` to a `json_patch` JSON Pointer. Parsing fails if the string doesn't start with '/' or contains unescaped '~' or '/' characters. The code already escapes the approval_id via escape_pointer_segment, so the only real trigger is an approval_id containing characters that break the pointer after (mis)escaping, or a regression in the path builder.","triggerScenarios":"Calling `approvals_patch::created(&ApprovalInfo { approval_id, .. })` where `approval_id` is empty, or where it yields an invalid pointer segment — e.g. an id containing '~' or '/' that wasn't run through escape_pointer_segment, or if pending_path were changed to drop the leading PENDING_PATH ('/') giving a bare relative pointer.","commonSituations":"IDs generated upstream with unusual characters; a refactor that drops escape_pointer_segment; an empty approval_id passed through from an approval service that failed to assign an id.","solutions":["Ensure every dynamic segment is escaped: keep `escape_pointer_segment(&info.approval_id)` inside pending_path before formatting.","Validate/sanitize approval_id at approval-creation time (non-empty, UUID or alphanumerics only).","Use error handling instead of expect if ids can be untrusted: match on try_into() and log/skip the patch rather than panicking the event loop.","Add a unit test asserting pending_path(id).try_into::<Pointer>() is Ok for edge-case ids (\"~\", \"a/b\", \"\")."],"exampleFix":"// before\nfn pending_path(approval_id: &str) -> String {\n    format!(\"{}/{}\", PENDING_PATH, approval_id)\n}\n// after\nfn pending_path(approval_id: &str) -> String {\n    format!(\"{}/{}\", PENDING_PATH, escape_pointer_segment(approval_id))\n}","handlingStrategy":"validation","validationCode":"// Validate id before building the patch\nfn valid_approval_id(id: &str) -> bool {\n    !id.is_empty() && !id.contains(|c: char| c == '~' || c == '/') && json_patch::Pointer::try_from(format!(\"/pending/{}\", id)).is_ok()\n}","typeGuard":"fn is_safe_pointer_segment(s: &str) -> bool {\n    !s.is_empty() && !s.contains('~') && !s.contains('/')\n}","tryCatchPattern":"// Avoid expect on untrusted ids:\nlet path = pending_path(&info.approval_id);\nlet Ok(ptr) = json_patch::Pointer::try_from(path) else {\n    log::error!(\"skipping approval patch: invalid id {:?}\", info.approval_id);\n    return Patch(vec![]);\n};","preventionTips":["Escape every dynamic segment with escape_pointer_segment (escapes ~ then /).","Constrain approval_id to UUID format at creation time.","Write property tests for pending_path with adversarial ids.","Never format raw user/DB strings into JSON Pointers."],"tags":["rust","json-patch","json-pointer","panic","input-validation"],"backgroundTag":"invalid-json-pointer","analyzedSha":"4deb7eca8f381f7cbc1f9d15515a9ab8f8009053","analyzedAt":"2026-08-29T09:24:13.446Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}