{"record":{"id":"01db34b85858505d","repo":"tonhowtf/omniget","slug":"plugin-id-must-not-contain-plugin-id","errorCode":null,"errorMessage":"plugin id must not contain '..': {plugin_id:?}","messagePattern":"plugin id must not contain '\\.\\.': (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/plugin_loader.rs","lineNumber":245,"sourceCode":"///\n/// Allowlist e nao blocklist de proposito: o conjunto de ids reais e pequeno e\n/// conhecido (`courses`, `study`, `telegram`, `convert`, `misc`), e uma\n/// blocklist erra por omissao a cada codificacao nova.\npub fn validate_plugin_id(plugin_id: &str) -> anyhow::Result<()> {\n    if plugin_id.is_empty() {\n        anyhow::bail!(\"plugin id must not be empty\");\n    }\n    if plugin_id == \".\" || plugin_id == \"..\" || plugin_id.starts_with('.') {\n        anyhow::bail!(\"plugin id must not be a relative path component: {plugin_id:?}\");\n    }\n    if let Some(bad) = plugin_id\n        .chars()\n        .find(|c| !c.is_ascii_alphanumeric() && !matches!(c, '.' | '_' | '-'))\n    {\n        anyhow::bail!(\"plugin id contains an illegal character {bad:?}: {plugin_id:?}\");\n    }\n    if plugin_id.contains(\"..\") {\n        anyhow::bail!(\"plugin id must not contain '..': {plugin_id:?}\");\n    }\n    Ok(())\n}\n\nfn load_single_plugin(\n    plugin_dir: &Path,\n    host: Arc<dyn PluginHost>,\n) -> Result<LoadedPlugin, PluginLoadError> {\n    let manifest_path = plugin_dir.join(\"plugin.json\");\n    let manifest_str = fs::read_to_string(&manifest_path).map_err(|e| {\n        PluginLoadError::simple(\"manifest_read\", format!(\"Cannot read plugin.json: {e}\"))\n    })?;\n    let manifest: PluginManifest = serde_json::from_str(&manifest_str).map_err(|e| {\n        PluginLoadError::simple(\"manifest_parse\", format!(\"Invalid plugin.json: {e}\"))\n    })?;\n\n    let lib_path =\n        find_native_lib(plugin_dir, manifest.rust_crate.as_deref()).ok_or_else(|| {","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/src/plugin_loader.rs#L227-L263","documentation":"Even if each character is legal, an id containing the substring \"..\" is rejected because it can form traversal sequences when concatenated into paths (e.g. 'a..b' plus separators). validate_plugin_id bails with the offending id embedded.","triggerScenarios":"Passing any id containing \"..\" (e.g. \"foo..bar\", \"a/../b\" already caught earlier) to plugin_dir/validate_plugin_id.","commonSituations":"User-supplied ids attempting traversal; accidental double-dot typos in manifest ids; concatenated path fragments used as ids.","solutions":["Remove '..' from the id before use","Validate with validate_plugin_id before constructing any path from the id","Reject the manifest/plugin whose id contains '..'"],"exampleFix":"// before\nlet dir = plugin_dir(\"foo..bar\")?; // bails\n// after\nensure!(!id.contains(\"..\"), \"plugin id must not contain '..'\");\nlet dir = plugin_dir(&id)?;","handlingStrategy":"validation","validationCode":"ensure!(!plugin_id.contains(\"..\"), \"plugin id must not contain '..': {plugin_id:?}\");","typeGuard":"fn no_dotdot(id: &str) -> bool { !id.contains(\"..\") }","tryCatchPattern":"let dir = plugin_dir(&id).with_context(|| format!(\"traversal-risk plugin id {id:?}\"))?;","preventionTips":["Reject '..' substrings in any path-bound identifier","Always call validate_plugin_id before path joins","Audit manifest ids in CI"],"tags":["rust","anyhow","plugin","path-traversal","security"],"backgroundTag":"path-traversal-blocked","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}