mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-11 20:27:44 +02:00
[aidan] fix/affiliate-scan: tolerate future mtimes + cover win32 and all artifact shapes
The Downloads scan rejected any file whose mtime was newer than the scan's Date.now() snapshot (sub-ms fs timestamps, NTP stepping the clock back), and the ambiguity test was green only because both fixtures were skipped by that guard. Allow 60s of future skew, add a win32 end-to-end bind test, and cover every stamped artifact name (mac arm64/x64 dmg, windows setup exe, linux AppImages) plus unstamped negatives in the parser test.
This commit is contained in:
@@ -41,6 +41,10 @@ const FILENAME_ATTRIBUTION_WINDOW_MS =
|
||||
Number(process.env.OPENSWARM_AFFILIATE_FILENAME_WINDOW_MS) || 30 * 24 * 60 * 60 * 1000;
|
||||
const INSTALLER_HASH_RE =
|
||||
/^OpenSwarm(?:-Setup)?-(?:arm64|x64)-([A-Za-z0-9_-]{16,32})(?: \([0-9]+\))?\.(dmg|exe|AppImage)$/i;
|
||||
// A file whose mtime is slightly in the future is NOT suspicious: APFS/NTFS
|
||||
// keep sub-ms timestamps, and NTP can step the clock backwards between the
|
||||
// download and first launch. Only skip files more than a minute ahead.
|
||||
const FUTURE_MTIME_TOLERANCE_MS = 60 * 1000;
|
||||
|
||||
function getStateFilePath(userDataDir) {
|
||||
return path.join(userDataDir, "install.json");
|
||||
@@ -214,7 +218,7 @@ function recentInstallerHashesInDir(dir, nowMs) {
|
||||
try {
|
||||
const st = fs.statSync(fullPath);
|
||||
const ageMs = nowMs - st.mtimeMs;
|
||||
if (ageMs < 0 || ageMs > FILENAME_ATTRIBUTION_WINDOW_MS) continue;
|
||||
if (ageMs < -FUTURE_MTIME_TOLERANCE_MS || ageMs > FILENAME_ATTRIBUTION_WINDOW_MS) continue;
|
||||
out.push({ hash, path: fullPath, mtimeMs: st.mtimeMs });
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
@@ -297,6 +297,56 @@ test("filename parser keeps hyphens inside base64url affiliate hash", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("filename parser covers every stamped artifact shape (mac/win/linux)", () => {
|
||||
const h = "abcDEF1234567890_hash";
|
||||
for (const name of [
|
||||
`OpenSwarm-arm64-${h}.dmg`, // mac Apple Silicon
|
||||
`OpenSwarm-x64-${h}.dmg`, // mac Intel
|
||||
`OpenSwarm-Setup-x64-${h}.exe`, // windows squirrel setup
|
||||
`OpenSwarm-x64-${h}.AppImage`, // linux x64
|
||||
`OpenSwarm-arm64-${h}.AppImage`, // linux arm64
|
||||
]) {
|
||||
assert.equal(affiliateTracking._hashFromInstallerBasename(name), h, name);
|
||||
}
|
||||
// Unstamped artifacts must NOT parse as carrying a hash.
|
||||
for (const name of ["OpenSwarm-arm64.dmg", "OpenSwarm-Setup-x64.exe", "OpenSwarm-x64.AppImage"]) {
|
||||
assert.equal(affiliateTracking._hashFromInstallerBasename(name), null, name);
|
||||
}
|
||||
});
|
||||
|
||||
test("first launch (win32): stamped setup exe in Downloads binds before welcome URL", async () => {
|
||||
const cloud = await makeMockCloud();
|
||||
try {
|
||||
const userDataDir = makeTempUserDataDir();
|
||||
const shell = makeFakeShell();
|
||||
const hash = "abcDEF1234567890_hash";
|
||||
cloud.filenameHashes.set(hash, "windows-affiliate");
|
||||
|
||||
const downloads = path.join(userDataDir, "Downloads");
|
||||
fs.mkdirSync(downloads, { recursive: true });
|
||||
fs.writeFileSync(path.join(downloads, `OpenSwarm-Setup-x64-${hash}.exe`), "");
|
||||
|
||||
process.env.OPENSWARM_AFFILIATE_LANDING_URL = "https://landing.test";
|
||||
process.env.OPENSWARM_AFFILIATE_CLOUD_URL = cloud.url;
|
||||
|
||||
await affiliateTracking.maybeRunFirstLaunchHandshake({
|
||||
shell,
|
||||
userDataDir,
|
||||
isDev: false,
|
||||
isPackaged: true,
|
||||
platform: "win32",
|
||||
homeDir: userDataDir,
|
||||
});
|
||||
|
||||
assert.equal(shell.opened.length, 0, "filename hash bind skips welcome URL");
|
||||
const state = readJson(path.join(userDataDir, "install.json"));
|
||||
assert.equal(state.ref, "windows-affiliate");
|
||||
assert.equal(state.ref_bind_method, "affiliate_filename_hash");
|
||||
} finally {
|
||||
await cloud.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("download scan refuses ambiguous stamped installers", () => {
|
||||
const userDataDir = makeTempUserDataDir();
|
||||
const downloads = path.join(userDataDir, "Downloads");
|
||||
|
||||
Reference in New Issue
Block a user