{"record":{"id":"6ca5f0679d17dd8b","repo":"jdx/mise","slug":"invalid-tool-version-s-contains-forbidden-cha","errorCode":null,"errorMessage":"invalid tool version {s:?}: contains forbidden character {c:?}","messagePattern":"invalid tool version (.+?): contains forbidden character (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/toolset/tool_request.rs","lineNumber":724,"sourceCode":"/// characters (newlines split shell tokens) and `..` (filesystem traversal).\n/// Everything else is allowed so legitimate version vocabulary (npm-style\n/// semver ranges like `>=20 <21 || >=22` or `^1.0.0`, dates, channel names,\n/// `lts/hydrogen`, etc.) continues to work — those characters are only\n/// dangerous in *unquoted* shell context, which cannot occur without one of\n/// the rejected expansion characters appearing first. Leading dashes are also\n/// rejected so backend install tools cannot mistake a version for a CLI flag.\nfn validate_version_string(s: &str) -> Result<()> {\n    if s.is_empty() {\n        return Ok(());\n    }\n    if s.starts_with('-') {\n        bail!(\"invalid tool version {s:?}: must not start with '-'\");\n    }\n    if s.contains(\"..\") {\n        bail!(\"invalid tool version {s:?}: contains path-traversal sequence\");\n    }\n    if let Some(c) = s.chars().find(|c| is_forbidden_version_char(*c)) {\n        bail!(\"invalid tool version {s:?}: contains forbidden character {c:?}\");\n    }\n    Ok(())\n}\n\n/// Validate `ref:`/`branch:`/`tag:`/`rev:` values. Same character rules as\n/// version strings: branch/tag names already use the same broad vocabulary\n/// (`/`, `+`, `-`, etc.), so only shell-quote-breaking characters and leading\n/// dashes need rejection. Kept as a separate function for distinct error\n/// messages.\nfn validate_ref_string(s: &str) -> Result<()> {\n    if s.is_empty() {\n        return Ok(());\n    }\n    if s.starts_with('-') {\n        bail!(\"invalid tool ref {s:?}: must not start with '-'\");\n    }\n    if s.contains(\"..\") {\n        bail!(\"invalid tool ref {s:?}: contains path-traversal sequence\");","sourceCodeStart":706,"sourceCodeEnd":742,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/toolset/tool_request.rs#L706-L742","documentation":"mise enforces an allowlist of characters in version strings via is_forbidden_version_char. Any version containing a forbidden character (characters that could break shell commands, paths, or backend argument construction) is rejected with this message naming the offending character.","triggerScenarios":"ToolRequest::new_with_options receiving a version containing a character rejected by is_forbidden_version_char — e.g. spaces, quotes, '$', ';', '|', '&', backticks, newlines.","commonSituations":"Unquoted shell interpolation carrying whitespace or shell metacharacters into `mise install foo@...`; YAML/TOML values with trailing spaces; pasted versions with invisible characters or trailing newline.","solutions":["Remove or replace the flagged character with an allowed one (check the reported character in the message).","Quote variables in shell scripts and trim input: mise install \"foo@${VER// /}\".","If the value is not a version, use the correct prefix (ref:, tag:, path:), which has its own validation."],"exampleFix":"// before (script)\nmise install node@$VERSION   # VERSION=\"20.x beta\"\n// after\nVERSION=\"20.11.0\"; mise install \"node@$VERSION\"","handlingStrategy":"validation","validationCode":"function validVersion(v) {\n  return typeof v === 'string' && !v.startsWith('-') && !v.includes('..') && !/[\\s'\";$|&`<>]/.test(v);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Quote all interpolated values in shell scripts building mise commands.","Trim templated outputs (no trailing newline/space) before writing configs.","Restrict versions to [A-Za-z0-9._+~-] minus a leading dash."],"tags":["mise","validation","version","input-validation"],"backgroundTag":"invalid-identifier-format","analyzedSha":"533346cc374382b41ec5ff70536252b2e96e725c","analyzedAt":"2026-09-17T13:35:38.149Z","contentChangedAt":"2026-09-17T13:35:38.149Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}