{"record":{"id":"f0dcd2e2eac5d5c5","repo":"can1357/oh-my-pi","slug":"unsafe-embedded-addon-filename-file-filename","errorCode":null,"errorMessage":"Unsafe embedded addon filename: ${file.filename}","messagePattern":"Unsafe embedded addon filename: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/natives/native/loader-state.js","lineNumber":501,"sourceCode":"\tconst tempPath = `${targetPath}.tmp.${process.pid}.${Date.now()}`;\n\ttry {\n\t\tfs.writeFileSync(tempPath, content, { mode: 0o755 });\n\t\tfs.renameSync(tempPath, targetPath);\n\t} catch (err) {\n\t\ttry {\n\t\t\tfs.unlinkSync(tempPath);\n\t\t} catch {\n\t\t\t// Best-effort cleanup only.\n\t\t}\n\t\tthrow err;\n\t}\n}\n\nexport function extractEmbeddedAddonArchive({ archivePath, files, targetDir }) {\n\tconst pending = new Map();\n\tfor (const file of files) {\n\t\tif (!isSafeEmbeddedAddonFilename(file.filename)) {\n\t\t\tthrow new Error(`Unsafe embedded addon filename: ${file.filename}`);\n\t\t}\n\t\tconst targetPath = path.join(targetDir, file.filename);\n\t\tif (!isEmbeddedAddonFileCurrent(targetPath, file)) {\n\t\t\tpending.set(file.filename, file);\n\t\t}\n\t}\n\tif (pending.size === 0) return [];\n\n\tconst archive = zlib.gunzipSync(fs.readFileSync(archivePath));\n\tconst writtenPaths = [];\n\tlet offset = 0;\n\n\twhile (offset + 512 <= archive.length) {\n\t\tif (isZeroTarBlock(archive, offset)) break;\n\t\tconst header = archive.subarray(offset, offset + 512);\n\t\tconst filename = getTarEntryName(header);\n\t\tconst size = readTarOctal(header, 124, 12);\n\t\tconst typeflag = header[156] === 0 ? \"0\" : String.fromCharCode(header[156]);","sourceCodeStart":483,"sourceCodeEnd":519,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/natives/native/loader-state.js#L483-L519","documentation":"extractEmbeddedAddonArchive validates every filename in the embedded-addon manifest before doing any I/O. A filename is unsafe if it is empty, contains a path separator ('/' or '\\\\'), or differs from its own path.basename — i.e. it could escape the target directory. The library throws this to prevent path traversal when writing files extracted from the bundled tar.gz into the native version directory.","triggerScenarios":"Calling extractEmbeddedAddonArchive({archivePath, files, targetDir}) where any entry of files has file.filename that is empty, absolute, or contains '/' or '\\\\' (e.g. '../evil.node' or 'sub/dir/x.node'). The check runs before the archive is even read, so a single bad manifest entry aborts extraction.","commonSituations":"A corrupted or hand-edited embedded-addon manifest; a custom build pipeline that records filenames with directory prefixes; a tampered/partially overwritten compiled binary's embedded metadata; tests feeding synthetic manifest data with paths.","solutions":["Fix the manifest so each files[].filename is a bare filename with no directory components (e.g. 'pi_natives-linux-x64.node').","Rebuild/reinstall the compiled binary so the embedded addon metadata is regenerated from the official build.","If you construct the manifest yourself, normalize entries with path.basename(filename) and reject entries where the result differs from the input."],"exampleFix":"// before\nextractEmbeddedAddonArchive({ archivePath, files: [{ filename: \"sub/pi_natives.node\" }], targetDir });\n// after\nconst files = [{ filename: \"pi_natives.node\" }];\nif (!files.every(f => f.filename && path.basename(f.filename) === f.filename)) throw new Error(\"bad manifest\");\nextractEmbeddedAddonArchive({ archivePath, files, targetDir });","handlingStrategy":"validation","validationCode":"import * as path from \"node:path\";\nfunction assertSafeAddonFilenames(files) {\n  for (const file of files) {\n    if (!file.filename || path.basename(file.filename) !== file.filename ||\n        file.filename.includes(\"/\") || file.filename.includes(\"\\\\\")) {\n      throw new Error(`Unsafe embedded addon filename: ${file.filename}`);\n    }\n  }\n}\nassertSafeAddonFilenames(files); // run before extractEmbeddedAddonArchive","typeGuard":"function isSafeAddonFilename(filename) {\n  return typeof filename === \"string\" && filename.length > 0 &&\n    path.basename(filename) === filename &&\n    !filename.includes(\"/\") && !filename.includes(\"\\\\\");\n}","tryCatchPattern":"try {\n  extractEmbeddedAddonArchive({ archivePath, files, targetDir });\n} catch (err) {\n  if (String(err.message).startsWith(\"Unsafe embedded addon filename:\")) {\n    // reject/treat manifest as untrusted; skip embedded path and fall back to disk lookup\n  } else throw err;\n}","preventionTips":["Always derive manifest filenames from path.basename at packaging time.","Validate any externally sourced file lists against a basename-only allowlist before use.","Treat embedded archive metadata as untrusted input; never concatenate paths from it without basename checks.","Add a packaging CI check that fails if any manifest filename contains a separator."],"tags":["path-traversal","security","native-addon","validation"],"backgroundTag":"unsafe-archive-entry-name","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}