{"record":{"id":"4221467f29fe4069","repo":"libnyanpasu/clash-nyanpasu","slug":"invalid-profile-cleanup-operation-id","errorCode":null,"errorMessage":"invalid profile cleanup operation id","messagePattern":"invalid profile cleanup operation id","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/service/profile_file.rs","lineNumber":1332,"sourceCode":"\n    fn active_managed_paths(profiles: &Profiles) -> HashSet<ManagedProfilePath> {\n        profiles\n            .items\n            .values()\n            .filter_map(|item| {\n                item.definition\n                    .source()\n                    .map(|source| source.materialized().file.clone())\n            })\n            .collect()\n    }\n\n    fn locate_cleanup(\n        root: &Path,\n        operation_id: &str,\n    ) -> anyhow::Result<Option<(CleanupPhase, MaterializationJournal)>> {\n        if !valid_operation_id(operation_id) {\n            bail!(\"invalid profile cleanup operation id\");\n        }\n        let pending_path = Self::cleanup_path(root, CleanupPhase::Pending, operation_id);\n        let ready_path = Self::cleanup_path(root, CleanupPhase::Ready, operation_id);\n        let pending = match std::fs::symlink_metadata(&pending_path) {\n            Ok(metadata) if !is_symlink_or_reparse(&metadata) && metadata.is_file() => {\n                Some(Self::read_journal(&pending_path, operation_id)?)\n            }\n            Ok(_) => bail!(\"pending cleanup journal is not a regular file\"),\n            Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,\n            Err(error) => return Err(error).context(\"inspect pending cleanup journal\"),\n        };\n        let ready = match std::fs::symlink_metadata(&ready_path) {\n            Ok(metadata) if !is_symlink_or_reparse(&metadata) && metadata.is_file() => {\n                Some(Self::read_journal(&ready_path, operation_id)?)\n            }\n            Ok(_) => bail!(\"ready cleanup journal is not a regular file\"),\n            Err(error) if error.kind() == std::io::ErrorKind::NotFound => None,\n            Err(error) => return Err(error).context(\"inspect ready cleanup journal\"),","sourceCodeStart":1314,"sourceCodeEnd":1350,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/service/profile_file.rs#L1314-L1350","documentation":"locate_cleanup validates the operation id before probing the filesystem: ids must match the library's strict format (valid_operation_id, typically a fixed charset/length token). If the caller passes a malformed id, the library bails immediately rather than constructing paths from untrusted input. This is an input-validation guard that also prevents path traversal via crafted ids.","triggerScenarios":"Calling the cleanup-lookup/recovery API with an operation id that is empty, contains characters outside the allowed set (path separators, '..', non-ASCII), or has the wrong length — e.g. passing a user-supplied string, a file name parsed loosely from a directory listing, or a truncated id.","commonSituations":"Feeding a journal file name that still contains a phase prefix or extension into the lookup; logging/telemetry code echoing a user-controlled id into recovery; hand-written tests using ids like \"test\" or \"123\" that don't satisfy the format; upgrading versions where id generation changed.","solutions":["Pass the operation id exactly as it was returned by the API that created the cleanup operation; do not trim, rename, or reformat it.","Validate the id yourself before calling: check it is non-empty and matches the expected token pattern (alphanumeric, fixed length).","If recovering from disk, extract the id with the library's own listing/parsing helpers (e.g. list_operation_ids) instead of string-splitting file names.","Reject or sanitize user-supplied ids at the UI/IPC boundary before they reach recovery code."],"exampleFix":"// before\nlet id = &file_name; // \"pending-abc123.journal\"\nlocate_cleanup(root, id)?;\n// after\nlet id = parse_operation_id(&file_name)?; // extract the bare token\nif !valid_operation_id(&id) { return Err(anyhow!(\"bad id\")); }\nlocate_cleanup(root, &id)?;","handlingStrategy":"validation","validationCode":"fn valid_operation_id(id: &str) -> bool {\n    !id.is_empty()\n        && id.len() <= 64\n        && id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')\n        && !id.starts_with('-')\n}","typeGuard":null,"tryCatchPattern":"match locate_cleanup(root, id) {\n    Err(e) if e.to_string() == \"invalid profile cleanup operation id\" => {\n        // reject the input at the API boundary; do not retry with the same id\n    }\n    other => other,\n}","preventionTips":["Always take operation ids from the API response that created the operation","Parse ids out of file names with the library's helpers, not string splitting","Validate user/IPC-supplied ids before invoking recovery","Regenerate ids rather than trimming or reformatting existing ones"],"tags":["input-validation","operation-id","recovery"],"backgroundTag":"invalid-identifier-format","analyzedSha":"f7dbce2997c633e484f54788035e770b3ee99773","analyzedAt":"2026-09-08T01:24:59.197Z","contentChangedAt":"2026-09-08T01:24:59.197Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}