fix(download): ignore OS-managed root artifacts when claiming a base (#7268)

ensureManagedBase() rejected a managed download base as not-owned
whenever any file sat at its root before the first claim, including
.DS_Store, which Finder writes automatically the moment that folder
is ever viewed. This is the same defect class fixed for the desktop
updater's own store root in #7259 (apps/desktop/src/main/updater/store.ts),
but in a separate, unpatched directoryIsEmpty() in this package's
own fs-io.ts. It's reachable from the same desktop updater via
downloadsRoot (downloadCopyAndClear -> managedDownload -> ensureManagedBase).

Add a package-local OS_MANAGED_ROOT_ARTIFACTS allowlist (duplicated
rather than shared with apps/desktop, since apps/desktop depends on
this package and not the reverse) and apply it before deciding a
fresh base has real, blocking content. Verified via two independent
adversarial reviews (Claude Code and Codex on gpt-5.6-terra) that no
other rejection path in this package (remove.ts, prune.ts, run.ts,
manifest.ts, lock.ts, transfer.ts) has the same unpatched pattern.

Fixes #7266.
This commit is contained in:
Seungjae Jay Lee
2026-08-25 06:18:05 +00:00
committed by GitHub
parent 8d70b98277
commit 79dee13d8a
2 changed files with 66 additions and 3 deletions
+23 -2
View File
@@ -13,7 +13,26 @@ import { join } from "node:path";
import { LOCK_DIR, PARTIAL_DIR, STATE_DIR, STORE_KIND, STORE_SCHEMA_VERSION, STORE_SENTINEL } from "./constants.js";
import { MANAGED_DOWNLOAD_ERROR_CODES, ManagedDownloadError } from "./errors.js";
import { directoryIsEmpty, readJson, writeJson } from "./fs-io.js";
import { readJson, writeJson } from "./fs-io.js";
/**
* @internal Filesystem-browser artifacts the OS drops into any directory it
* renders, independent of anything this package does. Their mere presence
* must never be treated as "not empty": Finder writes .DS_Store the moment a
* managed base is viewed even once, and Explorer does the same with
* Thumbs.db/desktop.ini. Mirrors apps/desktop's isOsManagedRootArtifact —
* duplicated rather than shared since apps/desktop depends on this package,
* not the reverse.
*/
const OS_MANAGED_ROOT_ARTIFACTS = new Set([".DS_Store", "Thumbs.db", "desktop.ini", ".localized"]);
/**
* @internal Whether `name` is a benign, OS-managed root artifact that should
* never count as real content when deciding whether a directory is claimable.
*/
function isOsManagedRootArtifact(name: string): boolean {
return OS_MANAGED_ROOT_ARTIFACTS.has(name);
}
/**
* @internal On-disk ownership marker written at the root of a managed base.
@@ -84,7 +103,9 @@ export async function ensureManagedBase(basePath: string): Promise<void> {
const sentinelPath = join(basePath, STORE_SENTINEL);
const sentinel = await readJson<unknown>(sentinelPath);
if (sentinel == null) {
if (!(await directoryIsEmpty(basePath))) {
const entries = await readdir(basePath);
const content = entries.filter((name) => !isOsManagedRootArtifact(name));
if (content.length > 0) {
throw new ManagedDownloadError(MANAGED_DOWNLOAD_ERROR_CODES.STORE_NOT_OWNED, `download base is not empty and has no ownership marker: ${basePath}`);
}
await writeSentinel(basePath);
+43 -1
View File
@@ -1,7 +1,7 @@
import { createHash } from "node:crypto";
import { spawnSync } from "node:child_process";
import { createServer, type IncomingMessage, type ServerResponse } from "node:http";
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { setTimeout as sleep } from "node:timers/promises";
@@ -439,4 +439,46 @@ describe("managed download package", () => {
await fixture.close();
}
});
it.each([".DS_Store", "Thumbs.db", "desktop.ini", ".localized"])(
"claims a fresh managed base that contains only the OS-managed artifact %s",
async (artifactName) => {
const root = tmpRoot("os-artifact-fresh-claim");
const basePath = join(root, "downloads");
mkdirSync(basePath, { recursive: true });
writeFileSync(join(basePath, artifactName), "binary-os-junk");
const pruned = await pruneManagedDownloads({ basePath });
expect(pruned.removed).toBe(0);
expect(existsSync(join(basePath, ".open-design-download-root.json"))).toBe(true);
expect(existsSync(join(basePath, artifactName))).toBe(true);
},
);
it("still rejects a fresh managed base containing a genuine stray file alongside .DS_Store", async () => {
const root = tmpRoot("os-artifact-stray-file");
const basePath = join(root, "downloads");
mkdirSync(basePath, { recursive: true });
writeFileSync(join(basePath, ".DS_Store"), "binary-finder-junk");
writeFileSync(join(basePath, "stray-file.bin"), "junk");
await expect(pruneManagedDownloads({ basePath })).rejects.toMatchObject({
code: MANAGED_DOWNLOAD_ERROR_CODES.STORE_NOT_OWNED,
});
expect(existsSync(join(basePath, ".open-design-download-root.json"))).toBe(false);
});
it("does not extend the OS-artifact allowance to arbitrary directories such as a stray .git", async () => {
const root = tmpRoot("os-artifact-stray-dir");
const basePath = join(root, "downloads");
mkdirSync(basePath, { recursive: true });
writeFileSync(join(basePath, ".DS_Store"), "binary-finder-junk");
mkdirSync(join(basePath, ".git"));
await expect(pruneManagedDownloads({ basePath })).rejects.toMatchObject({
code: MANAGED_DOWNLOAD_ERROR_CODES.STORE_NOT_OWNED,
});
expect(existsSync(join(basePath, ".open-design-download-root.json"))).toBe(false);
});
});