{"record":{"id":"f3e6a7dd3c85716a","repo":"jdx/mise","slug":"invalid-tool-version-s-must-not-start-with","errorCode":null,"errorMessage":"invalid tool version {s:?}: must not start with '-'","messagePattern":"invalid tool version (.+?): must not start with '-'","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/toolset/tool_request.rs","lineNumber":718,"sourceCode":"/// install path names and (for vfox plugins) into `ctx.version` / `ctx.rootPath`\n/// values that downstream Lua hooks often interpolate into shell commands.\n///\n/// The deny list is the minimum set of characters that can break out of either\n/// a single- or double-quoted shell string, or that trigger expansion *inside*\n/// double quotes: quotes themselves, backslash, backtick, and `$`. Plus control\n/// 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(());","sourceCodeStart":700,"sourceCodeEnd":736,"githubUrl":"https://github.com/jdx/mise/blob/533346cc374382b41ec5ff70536252b2e96e725c/src/toolset/tool_request.rs#L700-L736","documentation":"mise validates every tool version string before it is used to build a ToolRequest. A version beginning with '-' is rejected because backend install commands (e.g. `mise install foo@-v1`) could otherwise interpret the version as a CLI flag, which is an injection/safety hazard. Empty versions are allowed (they mean 'unspecified'), anything starting with '-' is not.","triggerScenarios":"Calling ToolRequest::new_with_options (or CLI forms like `mise install tool@-x`, `mise use tool@-x`) with a version string whose first character is '-'.","commonSituations":"Scripts interpolating empty/undefined shell variables producing accidental dashes; copy-pasting a diff line ('-1.2.3') as a version; typos like `foo@-latest`; generating version strings programmatically with flag-like values.","solutions":["Correct the version string so it does not start with '-' (e.g. use 1.2.3 not -1.2.3).","If the dash came from shell interpolation, quote/default the variable: ${VER:-1.2.3}.","If you meant a ref, use the explicit prefix form (ref:main, tag:v1) instead of a dash-prefixed value."],"exampleFix":"// before\nmise use node@-18\n// after\nmise use node@18","handlingStrategy":"validation","validationCode":"function validVersion(v) {\n  return typeof v === 'string' && (v === '' || !v.startsWith('-'));\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Quote and default shell variables used in version positions.","Never paste diff hunks or flag-like tokens as versions.","Use explicit ref:/tag: prefixes for non-plain versions."],"tags":["mise","validation","version","cli"],"backgroundTag":"invalid-argument-value","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"}