{"record":{"id":"056fd89c7fab3269","repo":"ruvnet/ruflo","slug":"extracted-binary-path-failed-validation-validat","errorCode":null,"errorMessage":"extracted binary path failed validation: ${validation.errors.join('; ') || 'unknown'}","messagePattern":"extracted binary path failed validation: (.+?)","errorType":"exception","errorClass":"ExtractionError","httpStatus":null,"severity":"error","filePath":"v3/@claude-flow/cli/src/proxy/install.ts","lineNumber":165,"sourceCode":"\n    const extractDir = path.join(workDir, 'extracted');\n    await extractArchive(archivePath, extractDir, releaseArchiveExtension(triple));\n\n    const extractedBinaryPath = path.join(extractDir, binaryNameInArchive());\n    if (!fs.existsSync(extractedBinaryPath)) {\n      throw new ExtractionError(`archive did not contain the expected binary at its root: ${binaryNameInArchive()}`);\n    }\n\n    // Defense in depth: confirm the extracted binary genuinely resolves\n    // inside extractDir (catches a symlink swap or similar), even though\n    // we only ever read one specific expected relative path, never an\n    // archive-listed one (so \"zip slip\" via arbitrary archive paths isn't\n    // reachable here in the first place).\n    const { PathValidator } = await import('@claude-flow/security');\n    const validator = new PathValidator({ allowedPrefixes: [extractDir] });\n    const validation = await validator.validate(extractedBinaryPath);\n    if (!validation.isValid) {\n      throw new ExtractionError(`extracted binary path failed validation: ${validation.errors.join('; ') || 'unknown'}`);\n    }\n\n    const finalPath = proxyBinaryPath();\n    fs.mkdirSync(path.dirname(finalPath), { recursive: true, mode: 0o700 });\n    const tmp = `${finalPath}.tmp`;\n    fs.copyFileSync(extractedBinaryPath, tmp);\n    fs.chmodSync(tmp, 0o755);\n    // The activation transaction has already stopped and backed up the old\n    // daemon. Windows rename does not replace an existing executable.\n    fs.rmSync(finalPath, { force: true });\n    fs.renameSync(tmp, finalPath);\n\n    const liveSha = sha256Hex(fs.readFileSync(finalPath));\n    const manifest: InstallManifest = {\n      version: opts.version,\n      sha256: liveSha,\n      verifiedAt: new Date().toISOString(),\n      pubkeyFingerprint: sha256Hex(Buffer.from(PROXY_RELEASE_PUBKEY_PEM)).slice(0, 16),","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/ruvnet/ruflo/blob/29f048fc3b556f857cf2b126d2a84c19d2daa0d0/v3/@claude-flow/cli/src/proxy/install.ts#L147-L183","documentation":"Thrown by ruflo proxy install after archive extraction, when the extracted meta-proxy binary's resolved path fails PathValidator validation against the single allowed prefix extractDir. It is a defense-in-depth check that runs even though only one hard-coded expected relative path is ever read — its purpose is to catch a symlink swap or any case where the path about to be copied does not genuinely resolve inside the extraction directory. The install aborts before the binary is copied to its final location.","triggerScenarios":"Calling installProxy() / `ruflo proxy install` where the resolved real path of extractedBinaryPath escapes extractDir: a symlink inside the archive replacing the expected binary entry, or the extraction directory itself resolving through a symlink so the validated path no longer shares the literal allowedPrefixes prefix.","commonSituations":"macOS where extractDir derives from os.tmpdir() and /tmp is a symlink to /private/tmp (literal prefix vs resolved path mismatch); a repacked or tampered release archive whose binary entry is a symlink; unusual filesystems (network mounts, case-insensitive volumes) where path resolution differs from the constructed path.","solutions":["Re-run `ruflo proxy install` — a one-off tmpdir resolution glitch or race often clears on retry","Set TMPDIR to a non-symlinked directory and retry: `mkdir -p ~/.tmp && TMPDIR=~/.tmp ruflo proxy install`","Manually verify the downloaded archive against the release's SHA256SUMS — a repacked archive that changes symlink behavior may indicate tampering","If reproducible, capture the validation.errors content from the message and report upstream with OS, TMPDIR, and Node version"],"exampleFix":"# before (extractDir behind a symlink, e.g. macOS /tmp)\nruflo proxy install   # extracted binary path failed validation\n# after (normalize TMPDIR to a real path)\nmkdir -p ~/.tmp && TMPDIR=~/.tmp ruflo proxy install","handlingStrategy":"try-catch","validationCode":"import { realpathSync } from 'node:fs';\n// Normalize a symlinked TMPDIR before scripting installs\nconst tmp = process.env.TMPDIR ?? '/tmp';\nif (realpathSync(tmp) !== tmp) process.env.TMPDIR = realpathSync(tmp);","typeGuard":"const isExtractionError = (e: unknown): e is Error & { name: 'ExtractionError' } =>\n  e instanceof Error && e.name === 'ExtractionError';","tryCatchPattern":"try {\n  await installProxy({ version });\n} catch (e) {\n  if (isExtractionError(e)) {\n    // Security-relevant abort: report with the validation errors; do not silently retry\n    console.error('install aborted:', e.message);\n    process.exitCode = 1;\n  } else throw e;\n}","preventionTips":["Keep TMPDIR on a non-symlinked path in CI images","Only install from the official release source; never point installs at repacked archives","Pin a known-good version so archive contents are reproducible","Capture validation.errors from the message in logs — it distinguishes symlink issues from prefix mismatches"],"tags":["security","path-validation","proxy-install","symlink","extraction"],"backgroundTag":"path-traversal-validation","analyzedSha":"29f048fc3b556f857cf2b126d2a84c19d2daa0d0","analyzedAt":"2026-08-18T21:34:22.708Z","contentChangedAt":"2026-08-18T21:34:22.708Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}