BigPizzaV3/CodexPlusPlus · error · anyhow::Error
invalid user script key
Error message
invalid user script key
What it means
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.
Source
Thrown at crates/codex-plus-core/src/user_scripts.rs:118
config.enabled = enabled;
self.save_config_unlocked(&config)?;
Ok(config)
}
pub fn set_script_enabled(&self, key: &str, enabled: bool) -> anyhow::Result<UserScriptConfig> {
let _guard = self.config_lock.lock().unwrap();
let mut config = self.load_config_unlocked();
config.scripts.insert(key.to_string(), enabled);
self.save_config_unlocked(&config)?;
Ok(config)
}
pub fn delete_user_script(&self, key: &str) -> anyhow::Result<UserScriptConfig> {
let Some(file_name) = key.strip_prefix("user:").filter(|value| !value.is_empty()) else {
anyhow::bail!("only user scripts can be deleted");
};
if file_name.contains(['/', '\\']) || file_name == "." || file_name == ".." {
anyhow::bail!("invalid user script key");
}
let path = self.user_dir.join(file_name);
let canonical_user_dir = self
.user_dir
.canonicalize()
.or_else(|_| {
fs::create_dir_all(&self.user_dir)?;
self.user_dir.canonicalize()
})
.with_context(|| {
format!(
"failed to resolve user script directory {}",
self.user_dir.display()
)
})?;
if path.exists() {
let canonical_path = path
.canonicalize()View on GitHub (pinned to 1f431ae49b)
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
Example fix
// before
let key = format!("user:{dir}/{file}");
manager.delete_user_script(&key)?;
// after
let key = format!("user:{file}"); // flat layout only
manager.delete_user_script(&key)?; Defensive patterns
Strategy: type-guard
Validate before calling
if !is_safe_user_script_key(&key) {
anyhow::bail!("refusing unsafe user script key: {key}");
}
let config = manager.delete_user_script(&key)?; Type guard
fn is_safe_user_script_key(key: &str) -> bool {
let Some(name) = key.strip_prefix("user:") else {
return false;
};
!name.is_empty()
&& !name.contains('/')
&& !name.contains('\\')
&& name != "."
&& name != ".."
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- refusing to delete script outside user script directory
- zip entry escapes destination: {name}
- only user scripts can be deleted
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16).
Data as JSON: /api/errors/74814a78330eb451.
Report an issue: GitHub.