-
Path, size and CRC inventory
+
Decompressed content evidence
Compare packages
-
{changed.length} differences
+
{attention.length} require attention
{left.name} ↔ {right.name}
+
+ Matching sizes and CRC declarations are never treated as proof of equal
+ content. Verification re-reads bounded entries, checks ZIP integrity and
+ hashes decompressed bytes with SHA-256. Structured summaries are inert
+ and bounded.
+
+
+
+ {busy ? (
+
+ ) : null}
+
+ {progress ? {progress}
: null}
+ {error ? (
+
+ {error}
+
+ ) : null}
@@ -707,10 +931,11 @@ function CompareView({
| Path |
{left.name} |
{right.name} |
+ Evidence |
- {changed.slice(0, 5000).map((item) => (
+ {attention.slice(0, 5000).map((item) => (
|
{item.path} |
{item.left ? formatBytes(item.left.size) : "—"} |
{item.right ? formatBytes(item.right.size) : "—"} |
+
+ {item.evidence?.leftSha256 && item.evidence.rightSha256 ? (
+ <>
+
+ {item.evidence.leftSha256.slice(0, 12)} ↔{" "}
+ {item.evidence.rightSha256.slice(0, 12)}
+
+ {item.evidence.semantic ? (
+
+ {item.evidence.semantic.summary}
+ {item.evidence.semantic.differences.length ? (
+
+ {item.evidence.semantic.differences.map(
+ (difference) => (
+ - {difference}
+ ),
+ )}
+
+ ) : null}
+
+ ) : null}
+ >
+ ) : (
+ (item.evidence?.skipped ??
+ (item.status === "unverified"
+ ? "Not hashed yet"
+ : "Metadata proves a difference"))
+ )}
+ |
))}
- {changed.length === 0 && (
- The bounded inventories match.
+ {attention.length === 0 && (
+
+ Every comparable file has matching decompressed SHA-256 evidence.
+
)}
);
diff --git a/src/package/analyze.ts b/src/package/analyze.ts
index 93659cd..6e8677b 100644
--- a/src/package/analyze.ts
+++ b/src/package/analyze.ts
@@ -12,9 +12,11 @@ import type {
EntryAnnotation,
EntryPreview,
PackageDiagnostic,
+ PackageDependency,
PackageDocument,
PackageKind,
PackageMetadata,
+ PackageLicense,
PackageRelationship,
PackageSignature,
} from "./types";
@@ -111,8 +113,10 @@ export async function analyzePackage(
else if (kind === "chrome-extension" || kind === "firefox-extension")
analyzeExtension(state, manifestValue, kind);
+ const { dependencies, licenses } = analyzeSupplyChain(state);
+
applyReferences(state);
- const signatures = inventorySignatures(parsed.entries, kind);
+ const signatures = inventorySignatures(parsed.entries, kind, texts);
for (const signature of signatures)
state.annotations[entryByPath.get(signature.path)?.id ?? ""] = {
...(state.annotations[entryByPath.get(signature.path)?.id ?? ""] ?? {
@@ -134,6 +138,8 @@ export async function analyzePackage(
relationships: state.relationships,
metadata: state.metadata,
signatures,
+ dependencies,
+ licenses,
annotations: state.annotations,
sourceBytes: file.size,
expandedBytes: parsed.expandedBytes,
@@ -269,7 +275,13 @@ function isMetadataPath(path: string): boolean {
lower.endsWith(".xml") ||
lower.endsWith(".json") ||
lower.endsWith("manifest.mf") ||
- lower.endsWith(".sf")
+ lower.endsWith(".sf") ||
+ lower === "package.json" ||
+ /(?:^|\/)requirements[^/]*\.txt$/u.test(lower) ||
+ /(?:^|\/)pom\.properties$/u.test(lower) ||
+ /(?:^|\/)(?:licen[cs]e|copying|notice|third[_-]party)(?:\.[^/]*)?$/u.test(
+ lower,
+ )
);
}
@@ -715,7 +727,7 @@ function analyzeApk(state: AnalysisState): void {
diagnostic(
"info",
"APK_BINARY_MANIFEST",
- "AndroidManifest.xml and resources.arsc are binary formats; v0.1 inventories them without decoding.",
+ "AndroidManifest.xml and resources.arsc are binary formats; v0.2 inventories them without decoding.",
),
);
state.diagnostics.push(
@@ -834,9 +846,149 @@ function parseExtensionManifest(
}
}
+function analyzeSupplyChain(state: AnalysisState): {
+ dependencies: PackageDependency[];
+ licenses: PackageLicense[];
+} {
+ const dependencies: PackageDependency[] = [];
+ const licenses: PackageLicense[] = [];
+ const dependencyKeys = new Set