diff --git a/docs/go-binaries.md b/docs/go-binaries.md index 46b6eddddb..7509d1a24a 100644 --- a/docs/go-binaries.md +++ b/docs/go-binaries.md @@ -200,3 +200,8 @@ those questions require a disassembler. `npm run verify:go:binary` exercises compiler-built fixtures through the public CLI and MCP, comparing their metadata with Go's standard `debug/buildinfo` reader. The verification lane requires Go; normal inspection does not. + +Reader architecture names remain verbatim in the report. Evidence subjects use +the recognized canonical family: `386` maps to `x86`, `amd64` to `x86_64`, and +`ppc` to `powerpc`. Unknown families such as `ppc64` remain unknown rather than +being inferred from a related spelling. diff --git a/docs/rust-binaries.md b/docs/rust-binaries.md index 4f9e4c6b22..407f303783 100644 --- a/docs/rust-binaries.md +++ b/docs/rust-binaries.md @@ -165,3 +165,8 @@ Package and dependency counts are checked against the minimum required JSON representation before graph allocation or schema cloning. Oversized graphs report the output byte budget; the compiler-path capacity reports `maximum_rustc_hashes` in hashes, independently of byte limits. + +Reader architecture names remain verbatim in the report. Evidence subjects use +the recognized canonical family: `386` maps to `x86`, `amd64` to `x86_64`, and +`ppc` to `powerpc`. Unknown families such as `ppc64` remain unknown rather than +being inferred from a related spelling. diff --git a/src/application/go/GoBinaryService.ts b/src/application/go/GoBinaryService.ts index 4013fe3af5..4a923905e4 100644 --- a/src/application/go/GoBinaryService.ts +++ b/src/application/go/GoBinaryService.ts @@ -1,3 +1,4 @@ +import { binaryMetadataArchitecture } from "../../domain/binaryMetadataArchitecture.js"; import { metadataReportSubjectFormat } from "../../domain/apple/machoTargetSelection.js"; import type { ExecutionOptions } from "../AnalysisProvider.js"; import type { GoBinaryPort } from "./GoBinaryPort.js"; @@ -8,7 +9,6 @@ import { } from "../../domain/analysisErrorCore.js"; import { createEvidence, type Evidence } from "../../domain/evidence.js"; import { - goArchitectureForSubject, goBinarySchema, inspectGoBinaryInputSchema, } from "../../domain/go/goBinary.js"; @@ -68,7 +68,7 @@ export class GoBinaryService { ), ); const value = report.data; - const architecture = goArchitectureForSubject(value.architecture); + const architecture = binaryMetadataArchitecture(value.architecture); const completed = completeProviderEvidence( createEvidence( { diff --git a/src/application/rust/RustBinaryService.ts b/src/application/rust/RustBinaryService.ts index 4cf8b7d60e..21dc260de9 100644 --- a/src/application/rust/RustBinaryService.ts +++ b/src/application/rust/RustBinaryService.ts @@ -1,3 +1,4 @@ +import { binaryMetadataArchitecture } from "../../domain/binaryMetadataArchitecture.js"; import { metadataReportSubjectFormat } from "../../domain/apple/machoTargetSelection.js"; import type { ExecutionOptions } from "../AnalysisProvider.js"; import type { RustBinaryPort } from "./RustBinaryPort.js"; @@ -8,7 +9,6 @@ import { } from "../../domain/analysisErrorCore.js"; import { createEvidence, type Evidence } from "../../domain/evidence.js"; import { - rustArchitectureForSubject, rustBinarySchema, type InspectRustBinaryInput, } from "../../domain/rust/rustBinary.js"; @@ -87,7 +87,7 @@ const evidenceFor = ( parameters: InspectRustBinaryInput, value: ReturnType, ): Evidence => { - const architecture = rustArchitectureForSubject(value.architecture); + const architecture = binaryMetadataArchitecture(value.architecture); const section = value.auditable_metadata; return createEvidence( { diff --git a/src/domain/analysisView/goBinaryView.ts b/src/domain/analysisView/goBinaryView.ts index 450f987317..d65fd38176 100644 --- a/src/domain/analysisView/goBinaryView.ts +++ b/src/domain/analysisView/goBinaryView.ts @@ -1,8 +1,8 @@ +import { binaryMetadataArchitecture } from "../binaryMetadataArchitecture.js"; import { metadataReportSubjectFormat } from "../apple/machoTargetSelection.js"; import { z } from "zod"; import type { AnalysisError } from "../analysisErrorBase.js"; import { - goArchitectureForSubject, goBinarySchema, goBuildInfoSchema, goModuleMetadataSchema, @@ -149,7 +149,7 @@ export const projectGoBinaryView = ( parent.subject.digest.sha256 !== report.artifact.sha256 || parent.subject.format !== metadataReportSubjectFormat(report) || parent.subject.architecture !== - (goArchitectureForSubject(report.architecture) ?? null) + (binaryMetadataArchitecture(report.architecture) ?? null) ) return err( analysisViewInputError([ diff --git a/src/domain/analysisView/rustBinaryView.ts b/src/domain/analysisView/rustBinaryView.ts index 898f1edfd2..c2ada29cad 100644 --- a/src/domain/analysisView/rustBinaryView.ts +++ b/src/domain/analysisView/rustBinaryView.ts @@ -1,8 +1,8 @@ +import { binaryMetadataArchitecture } from "../binaryMetadataArchitecture.js"; import { metadataReportSubjectFormat } from "../apple/machoTargetSelection.js"; import { z } from "zod"; import type { AnalysisError } from "../analysisErrorBase.js"; import { - rustArchitectureForSubject, rustBinarySchema, rustAuditableMetadataSchema, rustcCommitSchema, @@ -159,7 +159,7 @@ export const projectRustBinaryView = ( subject.digest.sha256 !== report.artifact.sha256 || subject.format !== metadataReportSubjectFormat(report) || subject.architecture !== - (rustArchitectureForSubject(report.architecture) ?? null) + (binaryMetadataArchitecture(report.architecture) ?? null) ) return err( analysisViewInputError([ diff --git a/src/domain/binaryMetadataArchitecture.ts b/src/domain/binaryMetadataArchitecture.ts new file mode 100644 index 0000000000..3402199e09 --- /dev/null +++ b/src/domain/binaryMetadataArchitecture.ts @@ -0,0 +1,17 @@ +import { BINARY_ARCHITECTURES } from "./binaryTargetTypes.js"; + +/** Preserve reader spellings while retaining recognized Evidence CPU families. */ +export const binaryMetadataArchitecture = (architecture: string) => { + switch (architecture) { + case "386": + return "x86"; + case "amd64": + return "x86_64"; + case "ppc": + return "powerpc"; + default: + return BINARY_ARCHITECTURES.find( + (supported) => supported === architecture, + ); + } +}; diff --git a/src/domain/go/goBinary.ts b/src/domain/go/goBinary.ts index 46c5fea670..a737a21ebf 100644 --- a/src/domain/go/goBinary.ts +++ b/src/domain/go/goBinary.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { BINARY_ARCHITECTURES } from "../binaryTargetTypes.js"; +import { binaryMetadataArchitecture } from "../binaryMetadataArchitecture.js"; import { machoSelectionContainsRange, selectedSliceAgreesWithReport, @@ -13,20 +13,6 @@ import { } from "../localPath.js"; import { canonicalBase64Schema } from "../stringPatterns.js"; -/** Map reader architecture names into Evidence identity; unknown names stay unknown. */ -export const goArchitectureForSubject = (architecture: string) => { - switch (architecture) { - case "386": - return "x86"; - case "amd64": - return "x86_64"; - default: - return BINARY_ARCHITECTURES.find( - (supported) => supported === architecture, - ); - } -}; - const sourceRangeSchema = z.strictObject({ offset: z.int().nonnegative(), bytes: z.int().nonnegative(), @@ -105,7 +91,7 @@ export const goBinarySchema = z (!selectedSliceAgreesWithReport( slice, report.format, - goArchitectureForSubject(report.architecture), + binaryMetadataArchitecture(report.architecture), ) || !machoSelectionContainsRange( slice, diff --git a/src/domain/rust/rustBinary.ts b/src/domain/rust/rustBinary.ts index 982acb5a29..89f2e1db44 100644 --- a/src/domain/rust/rustBinary.ts +++ b/src/domain/rust/rustBinary.ts @@ -4,7 +4,7 @@ import { absoluteLocalPathStringSchema, localPathStringSchema, } from "../localPath.js"; -import { BINARY_ARCHITECTURES } from "../binaryTargetTypes.js"; +import { binaryMetadataArchitecture } from "../binaryMetadataArchitecture.js"; import { machoSelectionContainsRange, selectedSliceAgreesWithReport, @@ -12,20 +12,6 @@ import { machoTargetSelectionSchema, } from "../apple/machoTargetSelection.js"; -/** Preserve the format owner's architecture spelling separately from Evidence vocabulary. */ -export const rustArchitectureForSubject = (architecture: string) => { - switch (architecture) { - case "386": - return "x86"; - case "amd64": - return "x86_64"; - default: - return BINARY_ARCHITECTURES.find( - (supported) => supported === architecture, - ); - } -}; - /** Known revisions used to interpret compatible cargo-auditable documents. */ export const RUST_AUDITABLE_FORMAT_VERSIONS = [0, 1, 8] as const; @@ -331,7 +317,7 @@ const checkSlice = (report: RustBinary, context: z.RefinementCtx): void => { !selectedSliceAgreesWithReport( slice, report.format, - rustArchitectureForSubject(report.architecture), + binaryMetadataArchitecture(report.architecture), ) || ranges.some( ({ offset, bytes }) => diff --git a/tests/boundary/goRustSubjectArchitecture.test.ts b/tests/boundary/goRustSubjectArchitecture.test.ts new file mode 100644 index 0000000000..bc2ff6b02e --- /dev/null +++ b/tests/boundary/goRustSubjectArchitecture.test.ts @@ -0,0 +1,43 @@ +import { createHash } from "node:crypto"; +import { writeFile } from "node:fs/promises"; +import { join } from "node:path"; +import { expect, it } from "vitest"; +import { GoBinaryService } from "../../src/application/go/GoBinaryService.js"; +import { RustBinaryService } from "../../src/application/rust/RustBinaryService.js"; +import { elf } from "../../src/domain/binaryTarget.fixture.js"; +import { GoBinaryProvider } from "../../src/go/GoBinaryProvider.js"; +import { RustBinaryProvider } from "../../src/rust/RustBinaryProvider.js"; +import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js"; + +it.each([ + [1, 1, 20, "ppc", "powerpc"], + [1, 2, 20, "ppc", "powerpc"], + [1, 1, 3, "386", "x86"], + [2, 1, 62, "amd64", "x86_64"], + [2, 2, 21, "ppc64", null], +] as const)( + "preserves ELF class%d byte-order%d machine%d in Go and Rust subjects", + async (bits, endian, machine, local, canonical) => { + const root = await createTestTempDirectory("rea-metadata-subject-"); + const path = join(root, "header.elf"); + const bytes = elf(bits, endian, machine); + await writeFile(path, bytes); + for (const service of [ + new GoBinaryService(new GoBinaryProvider()), + new RustBinaryService(new RustBinaryProvider()), + ]) { + const result = await service.inspect({ path }); + expect(result.ok).toBe(true); + if (!result.ok) throw result.error; + expect(result.value.subject).toMatchObject({ + architecture: canonical, + digest: { sha256: createHash("sha256").update(bytes).digest("hex") }, + }); + expect(result.value.normalized_result).toMatchObject({ + architecture: local, + bits: bits === 1 ? 32 : 64, + byte_order: endian === 1 ? "little" : "big", + }); + } + }, +);