{"record":{"id":"86f984bc9e1b0653","repo":"libnyanpasu/clash-nyanpasu","slug":"invalid-profile-materialization-operation-id","errorCode":null,"errorMessage":"invalid profile materialization operation id","messagePattern":"invalid profile materialization operation id","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/tauri/src/service/profile_file.rs","lineNumber":1119,"sourceCode":"                &journal,\n            )\n        })() {\n            let _ = Self::remove_private_regular(&Self::stage_file_path(&root, &operation_id));\n            let _ = Self::remove_private_regular(&Self::stage_link_path(&root, &operation_id));\n            let _ = Self::remove_private_regular(&Self::backup_file_path(&root, &operation_id));\n            let _ = Self::remove_private_regular(&Self::backup_link_path(&root, &operation_id));\n            return Err(error);\n        }\n        Ok(PreparedMaterialization::new(operation_id))\n    }\n\n    fn locate_materialization(\n        &self,\n        root: &Path,\n        operation_id: &str,\n    ) -> anyhow::Result<Option<(JournalLocation, MaterializationJournal)>> {\n        if !valid_operation_id(operation_id) {\n            bail!(\"invalid profile materialization operation id\");\n        }\n        let mut found = Vec::new();\n        for location in JournalLocation::ALL {\n            let path = Self::journal_path(root, location, operation_id);\n            match std::fs::symlink_metadata(&path) {\n                Ok(metadata) => {\n                    if is_symlink_or_reparse(&metadata) || !metadata.is_file() {\n                        bail!(\n                            \"materialization journal is not a regular file: {}\",\n                            path.display()\n                        );\n                    }\n                    found.push((location, Self::read_journal(&path, operation_id)?, path));\n                }\n                Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}\n                Err(error) => {\n                    return Err(error)\n                        .with_context(|| format!(\"inspect journal {}\", path.display()));","sourceCodeStart":1101,"sourceCodeEnd":1137,"githubUrl":"https://github.com/libnyanpasu/clash-nyanpasu/blob/f7dbce2997c633e484f54788035e770b3ee99773/backend/tauri/src/service/profile_file.rs#L1101-L1137","documentation":"locate_materialization validates the operation_id string before searching journal locations and rejects ids that fail valid_operation_id. Operation ids are fixed-alphabet, fixed-length nanoids; an invalid id means the caller passed arbitrary user input or a corrupted value, and treating it as a path component would risk traversal or lookup errors.","triggerScenarios":"promote/complete/compensate/reconcile called with an operation_id that is empty, contains characters outside the SAFE alphabet, or has the wrong length — typically user-supplied input passed straight from a CLI/UI into the API.","commonSituations":"Unvalidated frontend/API input reaching the materialization layer; manually edited or truncated journal ids; constructing ids by hand instead of via allocate_operation_id.","solutions":["Validate the id before calling (same alphabet/length rules as valid_operation_id) and reject bad input at the boundary.","Obtain operation ids only from allocate_operation_id or stored journals, not from free-form user input.","If the id came from persisted state, check it was not truncated or modified during storage/serialization.","Return a clear user-facing 'invalid operation id' message instead of propagating the raw error."],"exampleFix":"// before\nclient.promote(root, &user_input_id).await?; // arbitrary user string\n// after\nif !valid_operation_id(&user_input_id) {\n    return Err(anyhow!(\"operation id must be a 16-char SAFE-alphabet nanoid\"));\n}\nclient.promote(root, &user_input_id).await?;","handlingStrategy":"validation","validationCode":"fn valid_operation_id(id: &str) -> bool {\n    id.len() == 16 && id.chars().all(|c| nanoid::alphabet::SAFE.contains(&c))\n}\nif !valid_operation_id(&user_supplied_id) {\n    return Err(\"invalid operation id\");\n}","typeGuard":"fn as_operation_id(input: &str) -> Option<&str> {\n    (input.len() == 16 && input.bytes().all(|b| SAFE_ALPHABET.contains(&b))).then_some(input)\n}","tryCatchPattern":"match client.promote(root, &id).await {\n    Err(e) if e.to_string().contains(\"invalid profile materialization operation id\") => {\n        // surface a validation error to the user, do not retry\n        Err(UserError::BadOperationId)\n    }\n    r => r,\n}","preventionTips":["Only pass ids obtained from allocate_operation_id or stored journals.","Validate ids at the CLI/UI boundary before they reach service APIs.","Never build operation ids from user free-text.","Persist ids verbatim and avoid truncation in serialization."],"tags":["validation","input","id"],"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-17T15:17:12.973Z"}