mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
3af3811ab0
* fix(reference): resolve emitted extensions to TypeScript source Signed-off-by: Rudy Celekli <rudy@gradiahq.com> * test(reference): retain legitimate declaration import counterparts Signed-off-by: Rudy Celekli <rudy@gradiahq.com> * fix(mcp): recognize implicit form elicitation capability Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com> * fix(ghidra): reject contradictory NativeAOT reports * fix(native): preserve thin architectures across echoed filename lines Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> * fix(native): preserve app bundle executable filename whitespace Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> * fix(doctor): validate Node-wrapped client policy Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com> * fix(reference): preserve observed entry failure reasons Signed-off-by: Rudy Celekli <rudy@gradiahq.com> * fix(artifacts): verify opaque bytes before reusing inventory identities Signed-off-by: Rudy Celekli <rudy@gradiahq.com> * fix(browser): resolve DOM destinations against document base URL Signed-off-by: Rudy Celekli <rudy@gradiahq.com> * fix: recover serialized native NIB view hierarchy Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com> * ci: exercise native compiled NIB hierarchy regressions Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com> * fix(deps): remediate reachable archive-parsing advisories Two high-severity advisories are reachable from untrusted target content: - @xmldom/xmldom 0.9.10 (via plist) parses XML plist bytes from analyzed app bundles in the keyed-archive reader. Refreshed to 0.9.12. - brace-expansion 5.0.7 (via @electron/asar -> minimatch) is reached while analyzing untrusted ASAR archives. Refreshed to 5.0.12. Both are in-range patch updates (plist declares ^0.9.10, minimatch declares ^5.0.5), so no source or API change is required and no direct dependency declaration moves. Lockfile-only change. The remaining smol-toml advisory is not included: it parses operator-owned client configuration rather than untrusted target content, and its fix moves outside the declared range and changes leading-BOM handling. * fix(semantics): link wrapped arrow Promise returns * fix(reconstruction): preserve unknown graph limitations * fix(native): preserve exact code signing identifiers Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> * fix(native): recognize only whole otool command headers Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> * fix(native): retain nm-proven literal export names Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> * fix(javascript): project decoded file URL paths Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> * fix: preserve explicit source-relative URL observation spelling Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> * fix(native): project Mach-O evidence into container file offsets Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> * fix(browser): normalize referrer policy fallback lists Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> --------- Signed-off-by: Rudy Celekli <rudy@gradiahq.com> Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com> Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com> Co-authored-by: Rudy Celekli <rudy@gradiahq.com> Co-authored-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com> Co-authored-by: Kaoru0822 <aisakamimaki@gmail.com> Co-authored-by: DeryFerd <deryferdikao125@gmail.com>
142 lines
5.2 KiB
TypeScript
142 lines
5.2 KiB
TypeScript
import { mkdir, rm, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
|
|
import { createPackage } from "@electron/asar";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { scanArtifactInventory } from "../../../src/application/ArtifactInventory.js";
|
|
import { readJavaScriptArtifactFiles } from "../../../src/application/JavaScriptArtifactFiles.js";
|
|
import { AsarArtifactReader } from "../../../src/artifacts/AsarArtifactReader.js";
|
|
import { DirectoryArtifactReader } from "../../../src/artifacts/DirectoryArtifactReader.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
|
|
const fixture = async () => {
|
|
const root = await createTestTempDirectory("rea-ingestion-content-identity-");
|
|
const source = join(root, "source");
|
|
const bundle = join(root, "bundle");
|
|
await Promise.all([mkdir(source), mkdir(bundle)]);
|
|
await writeFile(join(source, "main.js"), "module.exports = 42;\n");
|
|
await writeFile(join(source, "asset.txt"), "original asset");
|
|
const archive = join(bundle, "app.asar");
|
|
await createPackage(source, archive);
|
|
return { source, bundle, archive };
|
|
};
|
|
|
|
describe("JavaScript artifact ingestion content identities", () => {
|
|
it.each([
|
|
["changed-asset", "directory"],
|
|
["added-asset", "directory"],
|
|
["changed-asset", "asar"],
|
|
["added-asset", "asar"],
|
|
])(
|
|
"rejects a rebuilt container with %s for a %s input and unchanged source",
|
|
async (change, format) => {
|
|
const { source, bundle, archive } = await fixture();
|
|
const snapshot = await scanArtifactInventory(
|
|
format === "asar" ? archive : bundle,
|
|
);
|
|
await writeFile(
|
|
join(source, change === "changed-asset" ? "asset.txt" : "new.txt"),
|
|
"rebuilt asset",
|
|
);
|
|
await createPackage(source, archive);
|
|
const reader =
|
|
format === "asar"
|
|
? new AsarArtifactReader(archive)
|
|
: new DirectoryArtifactReader(bundle);
|
|
try {
|
|
await expect(
|
|
readJavaScriptArtifactFiles(reader, snapshot),
|
|
).rejects.toMatchObject({ reason: "integrity" });
|
|
} finally {
|
|
await reader.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it("rejects changed opaque native-addon bytes after inventory", async () => {
|
|
const root = await createTestTempDirectory("rea-ingestion-native-bytes-");
|
|
const addon = join(root, "addon.node");
|
|
await writeFile(addon, Buffer.from([0xcf, 0xfa, 0xed, 0xfe, 1]));
|
|
const snapshot = await scanArtifactInventory(root);
|
|
await writeFile(addon, Buffer.from([0xcf, 0xfa, 0xed, 0xfe, 2]));
|
|
const reader = new DirectoryArtifactReader(root);
|
|
try {
|
|
await expect(
|
|
readJavaScriptArtifactFiles(reader, snapshot),
|
|
).rejects.toMatchObject({ reason: "integrity" });
|
|
} finally {
|
|
await reader.close();
|
|
}
|
|
});
|
|
|
|
it("preserves an unchanged direct ASAR raw identity and source", async () => {
|
|
const { archive } = await fixture();
|
|
const snapshot = await scanArtifactInventory(archive);
|
|
const reader = new AsarArtifactReader(archive);
|
|
try {
|
|
const files = await readJavaScriptArtifactFiles(reader, snapshot);
|
|
expect(files.containers).toEqual([]);
|
|
expect(files.files.find(({ path }) => path === "main.js")).toMatchObject({
|
|
container_sha256: snapshot.manifest.root_sha256,
|
|
text: { included: true, value: "module.exports = 42;\n" },
|
|
});
|
|
} finally {
|
|
await reader.close();
|
|
}
|
|
});
|
|
|
|
it("preserves typed cancellation when the direct ASAR filesystem stream aborts", async () => {
|
|
const { archive } = await fixture();
|
|
const snapshot = await scanArtifactInventory(archive);
|
|
const reader = new AsarArtifactReader(archive);
|
|
const controller = new AbortController();
|
|
try {
|
|
const reading = readJavaScriptArtifactFiles(
|
|
reader,
|
|
snapshot,
|
|
controller.signal,
|
|
);
|
|
controller.abort();
|
|
await expect(reading).rejects.toMatchObject({ reason: "cancelled" });
|
|
} finally {
|
|
await reader.close();
|
|
}
|
|
});
|
|
|
|
it("retains native filesystem errors for outer IO classification", async () => {
|
|
const { archive } = await fixture();
|
|
const snapshot = await scanArtifactInventory(archive);
|
|
await rm(archive);
|
|
const reader = new AsarArtifactReader(archive);
|
|
try {
|
|
await expect(
|
|
readJavaScriptArtifactFiles(reader, snapshot),
|
|
).rejects.toMatchObject({ code: "ENOENT" });
|
|
} finally {
|
|
await reader.close();
|
|
}
|
|
});
|
|
|
|
it("preserves unchanged container and opaque addon identities", async () => {
|
|
const { bundle } = await fixture();
|
|
await writeFile(join(bundle, "addon.node"), Buffer.from([1, 2, 3]));
|
|
const snapshot = await scanArtifactInventory(bundle);
|
|
const reader = new DirectoryArtifactReader(bundle);
|
|
try {
|
|
const files = await readJavaScriptArtifactFiles(reader, snapshot);
|
|
expect(files.containers).toHaveLength(1);
|
|
expect(
|
|
files.files.find(({ path }) => path === "addon.node"),
|
|
).toMatchObject({
|
|
text: { included: false, reason: "not-applicable" },
|
|
});
|
|
expect(
|
|
files.files.find(({ path }) => path === "app.asar/main.js")?.text,
|
|
).toEqual({ included: true, value: "module.exports = 42;\n" });
|
|
} finally {
|
|
await reader.close();
|
|
}
|
|
});
|
|
});
|