Q124: the file scan covers the whole file and says what it did and did not check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 5.5 committed 2026-10-08 20:09:03 -04:00
1 parent 91fa6b5f1e
commit 7a9059fbea
9 files changed
+175 -31

No files matched your search

+43
View File
@@ -0,0 +1,43 @@
import { describe, expect, test } from "vitest";
import { scanFileContent } from "../../src/main/services/files/file-object-store";
const MIB = 1024 * 1024;
describe("the file scan", () => {
test("checks the whole file, not just its first MiB, including a secret that straddles a window edge", () => {
const padding = "x".repeat(3 * MIB);
expect(scanFileContent("notes.txt", Buffer.from(`${padding}\nAKIAABCDEFGHIJKLMNOP\n`)).state).toBe("blocked");
const straddling = Buffer.concat([Buffer.alloc(MIB - 10, "y"), Buffer.from("-----BEGIN RSA PRIVATE KEY-----")]);
expect(scanFileContent("key.txt", straddling).labels).toContain("dlp:private-key");
expect(scanFileContent("eicar.txt", Buffer.from(`${padding}X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*`)).labels).toContain("av:eicar");
});
test("no pattern match can stretch past the window overlap", () => {
const edge = Buffer.concat([Buffer.alloc(MIB - 20, "z"), Buffer.from(`password:${"\n".repeat(5000)}${"A".repeat(32)}`)]);
// Whitespace between a key name and its value is bounded; a value 5,000 lines away is not that key's value.
expect(scanFileContent("far.txt", edge).labels).not.toContain("dlp:credential");
const long = Buffer.concat([Buffer.alloc(MIB - 10, "z"), Buffer.from(` ghp_${"a".repeat(9000)} `)]);
expect(scanFileContent("token.txt", long).labels).toContain("dlp:github-token");
expect(scanFileContent("plan.md", Buffer.from("# Plan")).labels).toContain("scan:whole-file");
});
test("a window cut does not invent a word boundary", () => {
const inWord = Buffer.concat([Buffer.alloc(MIB - 4, "z"), Buffer.from("NOT_AKIAABCDEFGHIJKLMNOP\n")]);
expect(scanFileContent("a.txt", inWord).state).toBe("approved");
const alone = Buffer.concat([Buffer.alloc(MIB - 1, "z"), Buffer.from(" AKIAABCDEFGHIJKLMNOP\n")]);
expect(scanFileContent("b.txt", alone).state).toBe("blocked");
// The far edge too: a longer word whose first 20 characters end exactly at a window's last byte is not a key.
const atTail = Buffer.concat([Buffer.alloc(MIB + 4096 - 20, "z"), Buffer.from(" AKIAABCDEFGHIJKLMNOPZ\n")]);
expect(scanFileContent("c.txt", atTail).state).toBe("approved");
const atOwnEnd = Buffer.concat([Buffer.alloc(MIB - 20, "z"), Buffer.from(" AKIAABCDEFGHIJKLMNOPZ\n")]);
expect(scanFileContent("d.txt", atOwnEnd).state).toBe("approved");
});
test("a container whose contents are compressed or encoded is labelled as not inspected inside", () => {
expect(scanFileContent("report.docx", Buffer.from("PK\u0003\u0004rest")).labels).toContain("scan:contents-not-inspected");
expect(scanFileContent("deck.pdf", Buffer.from("%PDF-1.7 ...")).labels).toContain("scan:contents-not-inspected");
expect(scanFileContent("archive.gz", Buffer.from([0x1f, 0x8b, 0x08, 0x00])).labels).toContain("scan:contents-not-inspected");
expect(scanFileContent("photo.png", Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a])).labels).toContain("scan:contents-not-inspected");
expect(scanFileContent("plan.md", Buffer.from("# Plan")).labels).not.toContain("scan:contents-not-inspected");
});
});
+26 -1
View File
@@ -5,7 +5,7 @@ import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { preserveRecordedFiles, recordWorkspaceFile } from "../../src/main/services/files/file-object-store";
import { preserveRecordedFiles, recordWorkspaceFile, rescanLegacyFiles } from "../../src/main/services/files/file-object-store";
import { ChatService } from "../../src/main/services/chat-service";
import { readAttachmentTool } from "../../src/main/services/agent-attachment-tools";
@@ -81,3 +81,28 @@ describe("opening a recorded file", () => {
connection.close();
});
});
describe("files scanned before whole-file scanning", () => {
test("are scanned again when their bytes are intact, and marked as sampled when they are not", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-rescan-${randomUUID()}.db`));
let orgId = "";
connection.transaction(() => {
orgId = new OrganizationService(connection.db).createOrganization({ organizationName: "Acme", employeeName: "Dana", mission: "m", goal: "g" });
});
const path = join(tmpdir(), `ac-rescan-${randomUUID()}.txt`);
const content = `${"x".repeat(2 * 1024 * 1024)}\nAKIAABCDEFGHIJKLMNOP\n`;
writeFileSync(path, content);
const insert = connection.db.prepare(`
insert into file_objects(id, org_id, name, kind, size_bytes, sha256, storage_path, source_path, owner_type, owner_id, pinned, scan_state, scan_labels_json, created_at)
values (?, ?, 'notes.txt', 'txt', 1, ?, ?, ?, 'agent', null, 0, 'approved', '["classification:internal"]', ?)
`);
insert.run("fil_intact", orgId, createHash("sha256").update(content).digest("hex"), path, path, new Date().toISOString());
insert.run("fil_gone", orgId, "0".repeat(64), join(tmpdir(), "missing.txt"), join(tmpdir(), "missing.txt"), new Date().toISOString());
rescanLegacyFiles(connection.db);
const row = (id: string) => connection.db.prepare("select scan_state, scan_labels_json from file_objects where id = ?").get(id) as { scan_state: string; scan_labels_json: string };
expect(row("fil_intact").scan_state).toBe("blocked");
expect(JSON.parse(row("fil_intact").scan_labels_json)).toContain("scan:whole-file");
expect(row("fil_gone")).toEqual({ scan_state: "approved", scan_labels_json: '["classification:internal","scan:legacy-sample"]' });
connection.close();
});
});