{"record":{"id":"0250d54915a82c31","repo":"tonhowtf/omniget","slug":"external-data-cache-plugin-id-namespace-must-not-contain","errorCode":null,"errorMessage":"external_data_cache: plugin_id/namespace must not contain path separators or null bytes","messagePattern":"external_data_cache: plugin_id/namespace must not contain path separators or null bytes","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/plugin_host.rs","lineNumber":122,"sourceCode":"        which::which(&bin_name).ok()\n    }\n\n    fn default_output_dir(&self) -> PathBuf {\n        dirs::download_dir()\n            .or_else(dirs::home_dir)\n            .unwrap_or_else(|| PathBuf::from(\".\"))\n    }\n\n    fn external_data_cache(&self, plugin_id: &str, namespace: &str) -> anyhow::Result<PathBuf> {\n        if plugin_id.is_empty() {\n            anyhow::bail!(\"external_data_cache: plugin_id must not be empty\");\n        }\n        if namespace.is_empty() {\n            anyhow::bail!(\"external_data_cache: namespace must not be empty\");\n        }\n        if plugin_id.contains(['/', '\\\\', ':', '\\0']) || namespace.contains(['/', '\\\\', ':', '\\0'])\n        {\n            anyhow::bail!(\n                \"external_data_cache: plugin_id/namespace must not contain path separators or null bytes\"\n            );\n        }\n\n        // portable installs keep every file next to the app, so the cache\n        // lives under the app data dir instead of the OS cache dir\n        let base = if std::env::var(\"OMNIGET_PORTABLE\").is_ok() {\n            omniget_core::core::paths::app_data_dir()\n                .ok_or_else(|| anyhow::anyhow!(\"external_data_cache: app data dir unavailable\"))?\n                .join(\"cache\")\n        } else {\n            dirs::cache_dir()\n                .ok_or_else(|| {\n                    anyhow::anyhow!(\n                        \"external_data_cache: OS cache dir unavailable on this platform\"\n                    )\n                })?\n                .join(\"wtf.tonho.omniget\")","sourceCodeStart":104,"sourceCodeEnd":140,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/src/plugin_host.rs#L104-L140","documentation":"`external_data_cache` rejects plugin_id or namespace strings containing '/', '\\\\', ':' or NUL, because these values are interpolated into filesystem paths and could traverse out of the cache directory or create invalid paths. This is a path-traversal guard.","triggerScenarios":"A plugin id or namespace containing a path separator, drive colon, or null byte is passed to external_data_cache — usually from untrusted manifest fields or externally supplied plugin identifiers.","commonSituations":"Malicious or buggy plugin manifest with id like '../evil' or 'a/b'; namespace built from user input including ':'; Windows drive letters embedded in ids.","solutions":["Sanitize plugin_id/namespace to [A-Za-z0-9._-] before calling cache APIs","Run ids through validate_plugin_id (plugin_loader) which enforces the same charset","Treat any plugin supplying such ids as invalid and refuse to load it"],"exampleFix":"// before\nhost.external_data_cache(&raw_id, &ns)?;\n// after\nlet safe_id: String = raw_id.chars().filter(|c| c.is_ascii_alphanumeric() || matches!(c, '.'|'_'|'-')).collect();\nensure!(safe_id == raw_id, \"illegal chars in plugin id\");\nhost.external_data_cache(&safe_id, &ns)?;","handlingStrategy":"validation","validationCode":"fn safe_component(s: &str) -> bool { !s.is_empty() && !s.contains(['/', '\\\\', ':', '\\0']) }\nensure!(safe_component(plugin_id) && safe_component(namespace), \"unsafe cache path component\");","typeGuard":"fn is_path_safe(s: &str) -> bool { !s.is_empty() && !s.contains(['/', '\\\\', ':', '\\0']) }","tryCatchPattern":"let cache = host.external_data_cache(&id, &ns)\n    .with_context(|| format!(\"unsafe cache id {id:?}/{ns:?}\"))?;","preventionTips":["Whitelist charset [A-Za-z0-9._-] for all plugin identifiers","Never trust manifest fields in path construction","Run ids through validate_plugin_id before any filesystem use"],"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"}