{"record":{"id":"e9f9c2f3e186087e","repo":"tonhowtf/omniget","slug":"plugin-id-must-not-be-empty","errorCode":null,"errorMessage":"plugin id must not be empty","messagePattern":"plugin id must not be empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/src/plugin_loader.rs","lineNumber":233,"sourceCode":"    /// — registro remoto -> `installed.json` -> frontend -> comando Tauri — e\n    /// nenhum ponto dessa cadeia o sanitizava, entao um id com `..` escapava do\n    /// diretorio de plugins e o `remove_dir_all` do `unregister` apagava\n    /// caminho arbitrario.\n    pub fn plugin_dir(&self, plugin_id: &str) -> anyhow::Result<PathBuf> {\n        validate_plugin_id(plugin_id)?;\n        Ok(self.plugins_dir.join(plugin_id))\n    }\n}\n\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,","sourceCodeStart":215,"sourceCodeEnd":251,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/src/plugin_loader.rs#L215-L251","documentation":"`validate_plugin_id` (public in plugin_loader, called by plugin_dir) first rejects an empty id: an empty string cannot form a valid plugin directory name and would resolve to the parent path. It bails via anyhow.","triggerScenarios":"Calling plugin_dir or validate_plugin_id with \"\" — e.g. a missing manifest `id` field, an empty query param, or a String::new() default reaching the loader.","commonSituations":"Corrupted or hand-edited plugin manifest lacking id; API route with blank id segment; deserialized struct defaulted to empty string.","solutions":["Provide a valid non-empty plugin id matching the known set (courses, study, telegram, convert, misc or other allowlist-compliant ids)","Validate the id before calling plugin_dir/validate_plugin_id","Fix the manifest so `id` is always present"],"exampleFix":"// before\nlet dir = plugin_dir(\"\")?;\n// after\nensure!(!id.is_empty(), \"plugin id required\");\nlet dir = plugin_dir(&id)?;","handlingStrategy":"validation","validationCode":"ensure!(!plugin_id.is_empty(), \"plugin id required\");\nplugin_dir(&plugin_id)?;","typeGuard":"fn non_empty(s: &str) -> bool { !s.is_empty() }","tryCatchPattern":"let dir = plugin_dir(&id).with_context(|| format!(\"resolving plugin dir for {id:?}\"))?;","preventionTips":["Make `id` a required manifest field","Validate ids at the API/CLI boundary","Avoid String::new() defaults for ids"],"tags":["rust","anyhow","plugin","validation"],"backgroundTag":"empty-required-field","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"}