{"record":{"id":"eb013f550d669cfc","repo":"can1357/oh-my-pi","slug":"archive-path-cannot-contain-eb013f","errorCode":null,"errorMessage":"Archive path cannot contain '..'","messagePattern":"Archive path cannot contain '\\.\\.'","errorType":"exception","errorClass":"ArchiveError","httpStatus":null,"severity":"error","filePath":"packages/utils/src/ar/reader.ts","lineNumber":76,"sourceCode":"\t\tif (resolvedPath === \"\") {\n\t\t\treturn { path: normalizedPath, isDirectory: true, size: 0 };\n\t\t}\n\t\tconst entry = this.#entries.get(resolvedPath);\n\t\tif (!entry) return undefined;\n\t\treturn {\n\t\t\tpath: normalizedPath,\n\t\t\tisDirectory: entry.isDirectory,\n\t\t\tsize: entry.size,\n\t\t\tmtimeMs: entry.mtimeMs,\n\t\t\tmode: entry.mode,\n\t\t};\n\t}\n\n\t/** List one directory's children, sorted case-insensitively by name. */\n\tlistDirectory(subPath?: string): ArchiveDirectoryEntry[] {\n\t\tconst normalizedPath = normalizeArchiveLookupPath(subPath);\n\t\tif (normalizedPath === undefined) {\n\t\t\tthrow new ArchiveError(\"Archive path cannot contain '..'\");\n\t\t}\n\n\t\tconst resolvedPath = normalizedPath\n\t\t\t? resolveArchiveLinkPath(this.#entries, normalizedPath, this.limits.maxLinkDepth)\n\t\t\t: \"\";\n\t\tif (normalizedPath && resolvedPath !== \"\") {\n\t\t\tconst entry = this.#entries.get(resolvedPath);\n\t\t\tif (!entry) {\n\t\t\t\tthrow new ArchiveError(`Archive path '${normalizedPath}' not found`);\n\t\t\t}\n\t\t\tif (!entry.isDirectory) {\n\t\t\t\tthrow new ArchiveError(`Archive path '${normalizedPath}' is not a directory`);\n\t\t\t}\n\t\t}\n\n\t\tconst sourcePrefix = resolvedPath ? `${resolvedPath}/` : \"\";\n\t\tconst children = new Map<string, ArchiveDirectoryEntry>();\n","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/ar/reader.ts#L58-L94","documentation":"Path-safety guard in `listDirectory`: `normalizeArchiveLookupPath(subPath)` returned undefined because the supplied path contains a `..` component, which this library forbids to prevent path-traversal-style lookups. Thrown as ArchiveError before any directory resolution happens (packages/utils/src/ar/reader.ts:76).","triggerScenarios":"Calling `reader.listDirectory('../sibling')`, `'a/../../b'`, or passing any subPath containing '..' segments; also occurs when user-supplied input with '..' flows into listDirectory (directly or via allEntries).","commonSituations":"Building UI paths from user input without sanitizing; joining a user-chosen folder onto a base path such that '..' appears; code that treats archive paths like filesystem-relative paths and walks upward.","solutions":["Remove or reject '..' segments before calling: normalize the requested path (e.g. path.posix.normalize and refuse if it escapes the root).","Call listDirectory with undefined or a rooted relative path ('sub/dir') — top-level listing uses undefined, not '..' or '/'.","Sanitize untrusted input at the boundary: strip leading slashes and collapse '.' segments, then re-check for '..'.","Catch this ArchiveError and show the user that upward traversal is not allowed inside archives."],"exampleFix":"// before: user input passed straight through\nreader.listDirectory(userPath); // '../etc' -> throws\n\n// after: normalize and reject traversal first\nconst clean = path.posix.normalize(userPath.replace(/^\\/+/, ''));\nif (clean.split('/').includes('..')) {\n  throw new Error('Directory traversal is not allowed');\n}\nreader.listDirectory(clean === '.' ? undefined : clean);","handlingStrategy":"validation","validationCode":"// Sanitize user-supplied subPath before calling listDirectory\nfunction safeArchivePath(input: string): string {\n  const clean = path.posix.normalize(input.replace(/^\\/+/, ''));\n  if (clean === '.' || clean === '..' || clean.split('/').includes('..')) {\n    throw new Error(`Path escapes the archive root: ${input}`);\n  }\n  return clean;\n}\nreader.listDirectory(safeArchivePath(userPath));","typeGuard":"function isTraversalFree(p: string): boolean {\n  return !p.split('/').includes('..');\n}","tryCatchPattern":"try {\n  return reader.listDirectory(userPath);\n} catch (err) {\n  if (err instanceof ArchiveError && err.message === \"Archive path cannot contain '..'\") {\n    throw new Error(`Invalid directory request (traversal blocked): ${userPath}`);\n  }\n  throw err;\n}","preventionTips":["Never pass raw user input as archive paths; normalize and reject '..' at the boundary.","Use undefined for root listings instead of '.', '/' or '..'.","Keep a single path-sanitizer helper shared by all archive access points.","Treat archive paths as rooted and forward-slash separated, not OS-relative paths."],"tags":["archive","path-traversal","validation"],"backgroundTag":"path-traversal","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}