{"record":{"id":"e6d276fbdb60b4ff","repo":"can1357/oh-my-pi","slug":"invalid-tar-member-path-formatarchivepathforerr","errorCode":null,"errorMessage":"Invalid tar member path '${formatArchivePathForError(rawPath)}'","messagePattern":"Invalid tar member path '(.+?)'","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/tar.ts","lineNumber":745,"sourceCode":"\t\t\teffectiveSplit[1],\n\t\t\tsize <= MAX_OCTAL_SIZE ? size : 0,\n\t\t\tmtime,\n\t\t\tdirectory ? 0o755 : 0o644,\n\t\t\tdirectory ? 0x35 : 0x30,\n\t\t),\n\t);\n\tif (!directory) appendPayload(parts, payload);\n}\n\n/** Encode files as a deterministic ustar archive, using PAX records for overflow paths. */\nexport async function encodeTar(members: Iterable<readonly [string, Uint8Array]>): Promise<Uint8Array> {\n\tconst parts: Uint8Array[] = [];\n\tconst kinds = new Map<string, \"directory\" | \"file\">();\n\tlet sequence = 0;\n\tfor (const [rawPath, bytes] of members) {\n\t\tconst directory = rawPath.endsWith(\"/\") || rawPath.endsWith(\"\\\\\");\n\t\tconst normalized = normalizeArchiveEntryPath(rawPath);\n\t\tif (!normalized) throw new ArchiveError(`Invalid tar member path '${formatArchivePathForError(rawPath)}'`);\n\t\tconst pathBytes = TEXT_ENCODER.encode(normalized);\n\t\tif (pathBytes.byteLength === 0) throw new ArchiveError(\"Invalid empty tar member path\");\n\t\tif (directory && bytes.byteLength !== 0) {\n\t\t\tthrow new ArchiveError(`Tar directory '${formatArchivePathForError(normalized)}' cannot contain file data`);\n\t\t}\n\t\tconst segments = normalized.split(\"/\");\n\t\tfor (let index = 1; index < segments.length; index++) {\n\t\t\tconst parent = segments.slice(0, index).join(\"/\");\n\t\t\tconst kind = kinds.get(parent);\n\t\t\tif (kind === \"file\")\n\t\t\t\tthrow new ArchiveError(`Tar member '${formatArchivePathForError(parent)}' is not a directory`);\n\t\t\tif (kind === \"directory\") continue;\n\t\t\tkinds.set(parent, \"directory\");\n\t\t\tappendTarEntry(parts, parent, new Uint8Array(0), true, sequence++);\n\t\t}\n\t\tconst existing = kinds.get(normalized);\n\t\tif (existing) {\n\t\t\tif (directory && existing === \"directory\") continue;","sourceCodeStart":727,"sourceCodeEnd":763,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/tar.ts#L727-L763","documentation":"normalizeArchiveEntryPath sanitizes a tar member path (resolving ., .., drive/backslash forms); when it returns empty the raw path was invalid (e.g. only traversal segments, null bytes, or nothing usable). The library throws rather than writing a header for an unusable path, and includes the formatted raw path in the message.","triggerScenarios":"createTar called with a key like '../etc/passwd', '.', '..', an empty string, a path containing NUL, or a backslash-only/Windows-style path that normalizes to nothing.","commonSituations":"Building archives from untrusted ZIP-style entry names; copying an in-memory virtual FS where root or '.' was included as a member; joining paths so a key became '..'.","solutions":["Sanitize or re-key members to clean relative paths like 'dir/file.txt' before calling createTar","Strip leading slashes and resolve '..' segments yourself","Skip empty, '.', '..', and null-containing keys"],"exampleFix":"// before\nconst members = new Map([[\"../escape.txt\", data]]);\nawait createTar(members);\n// after\nconst members = new Map([[\"safe/escape.txt\", data]]);\nawait createTar(members);","handlingStrategy":"validation","validationCode":"function isSafeMemberPath(p: string): boolean {\n\tif (p.length === 0 || p.includes(\"\\0\")) return false;\n\tconst parts = p.replace(/\\\\/g, \"/\").split(\"/\");\n\treturn !parts.some((s) => s === \"..\" || s === \".\" || s === \"\");\n}","typeGuard":"function isSafeMemberPath(p: string): boolean {\n\tif (p.length === 0 || p.includes(\"\\0\")) return false;\n\tconst parts = p.replace(/\\\\/g, \"/\").split(\"/\");\n\treturn !parts.some((s) => s === \"..\" || s === \".\" || s === \"\");\n}","tryCatchPattern":"try {\n\treturn await createTar(members);\n} catch (err) {\n\tif (err instanceof ArchiveError && err.message.startsWith(\"Invalid tar member path\")) {\n\t\t// log rejected path from err.message and sanitize keys\n\t}\n\tthrow err;\n}","preventionTips":["Sanitize entry names from untrusted sources (strips '..', absolute prefixes, NULs)","Never key members with '.', '..', or empty strings","Normalize backslash paths to forward slashes yourself"],"tags":["archive","tar","path-validation"],"backgroundTag":"invalid-archive-entry-path","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}