{"record":{"id":"5d89dbc51c4d6287","repo":"zeroclaw-labs/zeroclaw","slug":"invalid-skill-name-name","errorCode":null,"errorMessage":"Invalid skill name: {name}","messagePattern":"Invalid skill name: (.+?)","errorType":"console","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/skills/mod.rs","lineNumber":381,"sourceCode":"                        \"cli-skills-install-into-bundle\",\n                        &[(\"alias\", alias)],\n                    )\n                ),\n                SkillLocation::Global { .. } => println!(\n                    \"{}\",\n                    get_required_cli_string(\"cli-skills-install-global-note\")\n                ),\n            }\n            Ok(())\n        }\n        crate::SkillCommands::Remove {\n            name,\n            agent,\n            bundle,\n        } => {\n            // Reject path traversal attempts\n            if name.contains(\"..\") || name.contains('/') || name.contains('\\\\') {\n                anyhow::bail!(\"Invalid skill name: {name}\");\n            }\n            let status = console::style(\"✓\").green().bold().to_string();\n\n            if let Some(ref a) = agent\n                && config.agent(a).is_none()\n            {\n                anyhow::bail!(\n                    \"{}\",\n                    get_required_cli_string_with_args(\n                        \"cli-skills-agent-not-configured\",\n                        &[(\"alias\", a)],\n                    )\n                );\n            }\n\n            // Explicit bundle: archive through the service (recoverable).\n            if let Some(ref b) = bundle {\n                let service = SkillsService::new(config, config.install_root_dir());","sourceCodeStart":363,"sourceCodeEnd":399,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/src/skills/mod.rs#L363-L399","documentation":"`skills remove <name>` joins the name onto bundle/global skill directories to locate and delete them, so any name containing '..', '/', or '\\' is rejected up front as a path-traversal payload. Only a bare skill directory name is accepted; location scoping belongs to the --bundle and --agent flags, never to the name argument.","triggerScenarios":"Passing a nested path (`skills remove bundles/foo`), a traversal string (`skills remove ../../somewhere`), or a Windows-style path; shell tab-completion inserting a slash into the name.","commonSituations":"Users used to path-based CLIs; older scripts written against a path-style interface; untrusted input forwarded verbatim from an external source into the command.","solutions":["Pass only the skill's directory name: `skills remove foo`","Scope the search with --bundle <alias> or --agent <alias> instead of embedding a path in the name","Quote the argument so the shell neither expands nor splits it","If you genuinely need to remove a nested path, delete it manually after inspecting it"],"exampleFix":"# before\nzeroclaw skills remove shared/skills/foo\n\n# after\nzeroclaw skills remove foo --bundle shared","handlingStrategy":"validation","validationCode":"fn is_bare_skill_name(name: &str) -> bool {\n    !name.is_empty() && !name.contains(\"..\") && !name.contains('/') && !name.contains('\\\\')\n}\n\nif !is_bare_skill_name(&name) {\n    eprintln!(\"rejecting skill name {name:?}: pass a bare name plus --bundle/--agent\");\n}","typeGuard":"fn is_bare_skill_name(name: &str) -> bool {\n    !name.is_empty() && !name.contains(\"..\") && !name.contains('/') && !name.contains('\\\\')\n}","tryCatchPattern":null,"preventionTips":["Never build the name argument from user or network input without the bare-name check","Use --bundle/--agent for scoping, never path fragments","Quote skill names in shell scripts to prevent tab-completion or glob insertion of slashes"],"tags":["cli","skills","security","path-traversal","input-validation"],"backgroundTag":"path-traversal-guard","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}