{"record":{"id":"74814a78330eb451","repo":"BigPizzaV3/CodexPlusPlus","slug":"invalid-user-script-key","errorCode":null,"errorMessage":"invalid user script key","messagePattern":"invalid user script key","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/user_scripts.rs","lineNumber":118,"sourceCode":"        config.enabled = enabled;\n        self.save_config_unlocked(&config)?;\n        Ok(config)\n    }\n\n    pub fn set_script_enabled(&self, key: &str, enabled: bool) -> anyhow::Result<UserScriptConfig> {\n        let _guard = self.config_lock.lock().unwrap();\n        let mut config = self.load_config_unlocked();\n        config.scripts.insert(key.to_string(), enabled);\n        self.save_config_unlocked(&config)?;\n        Ok(config)\n    }\n\n    pub fn delete_user_script(&self, key: &str) -> anyhow::Result<UserScriptConfig> {\n        let Some(file_name) = key.strip_prefix(\"user:\").filter(|value| !value.is_empty()) else {\n            anyhow::bail!(\"only user scripts can be deleted\");\n        };\n        if file_name.contains(['/', '\\\\']) || file_name == \".\" || file_name == \"..\" {\n            anyhow::bail!(\"invalid user script key\");\n        }\n        let path = self.user_dir.join(file_name);\n        let canonical_user_dir = self\n            .user_dir\n            .canonicalize()\n            .or_else(|_| {\n                fs::create_dir_all(&self.user_dir)?;\n                self.user_dir.canonicalize()\n            })\n            .with_context(|| {\n                format!(\n                    \"failed to resolve user script directory {}\",\n                    self.user_dir.display()\n                )\n            })?;\n        if path.exists() {\n            let canonical_path = path\n                .canonicalize()","sourceCodeStart":100,"sourceCodeEnd":136,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/user_scripts.rs#L100-L136","documentation":"After stripping the user: prefix in delete_user_script, the remainder is treated as a file name inside the user scripts directory. It must not contain '/' or '\\' and must not be \".\" or \"..\"; violations bail with \"invalid user script key\". This is the traversal guard that keeps the delete operation inside user_dir even for adversarial keys, complementing the later canonicalization check.","triggerScenarios":"delete_user_script(\"user:../config.json\") or delete_user_script(\"user:scripts/foo.js\") — any key whose post-prefix part contains a path separator or is a dot component.","commonSituations":"UI bugs concatenating a directory and file name into the key; imported/migrated configs holding path-like keys; hostile automation or tampered config files trying to escape the scripts directory.","solutions":["Build keys as \"user:\" + bare file name only — subdirectories under user_dir are not supported by delete","Sanitize keys at the boundary where they enter your system (reject '/' and '\\\\')","If scripts must live in subfolders, flatten the layout; that structure is unsupported by design"],"exampleFix":"// before\nlet key = format!(\"user:{dir}/{file}\");\nmanager.delete_user_script(&key)?;\n\n// after\nlet key = format!(\"user:{file}\"); // flat layout only\nmanager.delete_user_script(&key)?;","handlingStrategy":"type-guard","validationCode":"if !is_safe_user_script_key(&key) {\n    anyhow::bail!(\"refusing unsafe user script key: {key}\");\n}\nlet config = manager.delete_user_script(&key)?;","typeGuard":"fn is_safe_user_script_key(key: &str) -> bool {\n    let Some(name) = key.strip_prefix(\"user:\") else {\n        return false;\n    };\n    !name.is_empty()\n        && !name.contains('/')\n        && !name.contains('\\\\')\n        && name != \".\"\n        && name != \"..\"\n}","tryCatchPattern":null,"preventionTips":["Reject '/' and '\\\\' in script keys at the API/UI boundary where keys enter","Store scripts flat in the user directory; subdirectories are unsupported by delete","Sanitize migrated/imported config keys before replaying delete operations on them"],"tags":["user-scripts","path-traversal","key-validation","security"],"backgroundTag":"path-traversal-guard","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}