can1357/oh-my-pi · critical · ToolError

Archive path cannot contain '..'

Error message

Archive path cannot contain '..'

What it means

Path traversal is forbidden inside archive targets: any '..' segment in the in-archive sub-path is rejected outright to prevent writing outside the archive entry namespace (zip-slip style attacks).

Source

Thrown at packages/coding-agent/src/tools/write.ts:473

	if (isEnoent(error)) return true;
	return typeof error === "object" && error !== null && "code" in error && error.code === "ENOTDIR";
}

function normalizeArchiveWriteSubPath(rawPath: string): string {
	const normalized = rawPath.replace(/\\/g, "/");
	if (normalized.length === 0) {
		throw new ToolError("Archive write path must target a file inside the archive");
	}
	if (normalized.endsWith("/")) {
		throw new ToolError("Archive write path must target a file, not a directory");
	}

	const parts = normalized.split("/");
	const normalizedParts: string[] = [];
	for (const part of parts) {
		if (!part || part === ".") continue;
		if (part === "..") {
			throw new ToolError("Archive path cannot contain '..'");
		}
		normalizedParts.push(part);
	}

	if (normalizedParts.length === 0) {
		throw new ToolError("Archive write path must target a file inside the archive");
	}

	return normalizedParts.join("/");
}

function parseSqliteWriteTarget(subPath: string, queryString: string): { table: string; key?: string } {
	if (queryString.trim().length > 0) {
		throw new ToolError("SQLite write paths do not support query parameters");
	}

	const normalized = subPath.replace(/^:+/, "").trim();
	if (!normalized) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Remove '..' segments; archive entries are always relative to the archive root
  2. Sanitize/normalize user-supplied entry names before composing the write target

Example fix

// before
write({ path: "bundle.zip/../escape.txt", content: "x" })
// after
write({ path: "bundle.zip/escape.txt", content: "x" })
Defensive patterns

Strategy: validation

Validate before calling

function assertNoTraversal(entry: string): void {
  const parts = entry.replace(/\\/g, "/").split("/");
  if (parts.includes("..")) throw new Error(`archive path traversal rejected: ${entry}`);
}
assertNoTraversal(inArchiveSubPath);

Try / catch

try {
  await write({ path: `bundle.zip/${entry}`, content });
} catch (e) {
  if (e instanceof ToolError && e.message.includes("cannot contain '..'")) {
    throw new Error(`Refusing unsafe archive entry: ${entry}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: write({ path: "bundle.zip/../evil.txt", content }) or any target whose sub-path contains a '..' segment.

Common situations: Relative-path mistakes when constructing archive entry names; attempts to escape the archive root, possibly via untrusted input.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/6150bb3f7242e15c. Report an issue: GitHub.