{"record":{"id":"ea8dd16e56611093","repo":"tonhowtf/omniget","slug":"plugin-id-contains-an-illegal-character-bad-plugin-id","errorCode":null,"errorMessage":"plugin id contains an illegal character {bad:?}: {plugin_id:?}","messagePattern":"plugin id contains an illegal character (.+?): (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/plugin_loader.rs","lineNumber":242,"sourceCode":"\n/// Aceita apenas `[A-Za-z0-9._-]`, recusando separador de caminho, byte nulo,\n/// componente relativo e id vazio.\n///\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    })?;","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/src/plugin_loader.rs#L224-L260","documentation":"`validate_plugin_id` scans each character and bails if any is not ASCII alphanumeric or one of '.', '_', '-'. This keeps plugin ids safe as directory/file names on all platforms and prevents injection of separators or unicode lookalikes.","triggerScenarios":"An id containing spaces, slashes, colons, accented/CJK characters, or other symbols is passed to plugin_dir/validate_plugin_id.","commonSituations":"Plugin id derived from a display name (e.g. \"My Cool Plugin!\"); id copy-pasted with whitespace; non-ASCII localized ids.","solutions":["Restrict ids to [A-Za-z0-9._-] at creation time","Normalize display names to slug form before using them as ids","Reject the plugin at load time with this validation instead of failing later"],"exampleFix":"// before\nlet id = display_name; // \"My Plugin!\"\n// after\nlet id: String = display_name.to_lowercase().chars().map(|c| if c.is_ascii_alphanumeric() { c } else { '_' }).collect();","handlingStrategy":"validation","validationCode":"fn id_charset_ok(id: &str) -> bool { id.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-')) }","typeGuard":"fn is_ascii_slug(id: &str) -> bool { !id.is_empty() && id.chars().all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | '-')) }","tryCatchPattern":"let dir = plugin_dir(&id).with_context(|| format!(\"illegal chars in plugin id {id:?}\"))?;","preventionTips":["Slugify display names before using them as ids","Restrict id input at UI level","Validate on both creation and load"],"tags":["rust","anyhow","plugin","validation"],"backgroundTag":"invalid-identifier-format","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"}