{"record":{"id":"c9138b6bc635b472","repo":"jdx/mise","slug":"the-packslip-names-an-artifact-which-is-not","errorCode":null,"errorMessage":"the packslip names an artifact {:?}, which is not a plain file name","messagePattern":"the packslip names an artifact (.+?), which is not a plain file name","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/backend/packslip.rs","lineNumber":1235,"sourceCode":"        let mut commands = BTreeMap::new();\n        if let Some(req) = &artifact.requires {\n            for bin in &req.bin {\n                // Spawnable, not merely present: the probe below runs the\n                // path with `--version`, so a shebang-only script or a `.ps1`\n                // would be chosen and then fail to start.\n                if let Some(path) = ctx.ts.which_bin_spawnable(&ctx.config, &bin.name).await {\n                    commands.insert(bin.name.clone(), path);\n                }\n            }\n        }\n        crate::packslip_requirements::check(&artifact, &commands)\n            .await\n            .enforce(raw_opts.get(\"ignore_requirements\") == Some(\"true\"))?;\n        let Some(url) = artifact.url.clone() else {\n            bail!(\"the packslip gives no download URL for {}\", artifact.name);\n        };\n        if !file::is_plain_file_name(&artifact.name) {\n            bail!(\n                \"the packslip names an artifact {:?}, which is not a plain file name\",\n                artifact.name\n            );\n        }\n        let file_path = tv.download_path().join(&artifact.name);\n        ctx.pr.next_operation();\n        ctx.pr.set_message(format!(\"download {}\", artifact.name));\n        HTTP.download_file_with_headers(\n            &url,\n            &file_path,\n            &headers_for(&url)?,\n            Some(ctx.pr.as_ref()),\n        )\n        .await?;\n\n        // The signed digest and size first, then what the lockfile remembers:\n        // a lock entry written from an earlier packslip keeps its checksum and\n        // is compared, so a newly signed manifest cannot quietly replace what","sourceCodeStart":1217,"sourceCodeEnd":1253,"githubUrl":"https://github.com/jdx/mise/blob/afd2eddd3a50c16190efc1c7e94404b48f72af57/src/backend/packslip.rs#L1217-L1253","documentation":"Thrown by `install_payload` in the packslip backend when the manifest names an artifact whose `name` is not a plain file name (per `file::is_plain_file_name` — e.g. it contains path separators, `..`, or otherwise looks like a path). mise rejects such names before constructing the download path, preventing path traversal or unsafe file placement inside the install directory.","triggerScenarios":"Installing a packslip-backed tool where the manifest's artifact `name` for your platform contains characters like `/`, `\\`, or leading directories (e.g. `\"bin/tool.tar.gz\"` or `\"../tool\"`) so `is_plain_file_name` returns false.","commonSituations":"An upstream project publishes signed manifests where artifact names include subpaths; a maliciously or accidentally crafted manifest; platform-specific artifacts named with directory prefixes; manifest format drift after a publisher tooling change.","solutions":["Upgrade or downgrade the tool version so the packslip manifest uses plain artifact file names (`mise use packslip:owner/repo@<version>`).","Inspect the manifest's artifact names for your platform; if they embed paths, report the malformed packslip to the upstream project.","Use an alternate backend with well-formed asset names (`mise use aqua:owner/repo` or `mise use github:owner/repo`).","If you control the packslip generation, fix the publisher to emit bare file names for each artifact."],"exampleFix":"// upstream packslip manifest artifact entry\n// before\n{ \"name\": \"dist/mytool-linux-amd64.tar.gz\", \"url\": \"...\" }\n// after\n{ \"name\": \"mytool-linux-amd64.tar.gz\", \"url\": \"...\" }","handlingStrategy":"validation","validationCode":"// reject artifact names with path separators before installing\nconst isPlain = name && !name.includes('/') && !name.includes('\\\\') && name !== '.' && name !== '..';\nif (!isPlain) throw new Error(`artifact name ${name} is not a plain file name`);","typeGuard":"function isPlainFileName(name) { return typeof name === 'string' && /^[^/\\\\]+$/u.test(name) && name !== '.' && name !== '..'; }","tryCatchPattern":"try { await $`mise install` } catch (e) { if (String(e).includes('not a plain file name')) { /* switch backend or report upstream manifest */ } else throw e; }","preventionTips":["Audit packslip manifests for path-like artifact names","Report upstream manifests embedding directory prefixes","Use github/aqua backends as a safer fallback"],"tags":["packslip","install","path-safety","manifest","rust"],"backgroundTag":"path-traversal-blocked","analyzedSha":"afd2eddd3a50c16190efc1c7e94404b48f72af57","analyzedAt":"2026-09-09T01:38:25.179Z","contentChangedAt":"2026-09-09T01:38:25.179Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}