mirror of
https://github.com/morluto/rea.git
synced 2026-10-18 20:10:13 -05:00
fix(rust): retain compatible cargo-auditable revisions
Admit documented compatible format revisions, preserve the original uint32 revision and disclose unverified fallback for unknown revisions. Keep malformed package and compressed-input rejection. Co-authored-by: Dragolone <810170966qq@gmail.com>
This commit is contained in:
1 parent
66536d0d2e
commit
36c91428fc
6 files changed
+108
-20
No files matched your search
@@ -55,6 +55,11 @@ dependency sections are typed failures. A cycle is reported and is not walked.
|
||||
|
||||
A missing section returns success with `auditable_metadata: null` and a
|
||||
limitation. Absence of `.dep-v0` does not mean the binary has no dependencies.
|
||||
Unknown embedded format revisions are retained in `format_version` and
|
||||
interpreted using the highest known preceding revision, following the
|
||||
cargo-auditable schema. The output includes a limitation that identifies this
|
||||
fallback and notes that the particular revision has not been independently
|
||||
verified. An omitted revision remains `null` and follows revision 0 semantics.
|
||||
Embedded names and versions are file content. They do not authenticate the
|
||||
packages or the compiler.
|
||||
|
||||
|
||||
@@ -2,7 +2,8 @@ import { z } from "zod";
|
||||
import { digestSchema } from "../digests.js";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
|
||||
export const RUST_AUDITABLE_FORMAT_VERSION = 1;
|
||||
/** Known revisions used to interpret compatible cargo-auditable documents. */
|
||||
export const RUST_AUDITABLE_FORMAT_VERSIONS = [0, 1, 8] as const;
|
||||
|
||||
const index = z.number().int().nonnegative();
|
||||
const rustcHash = z.string().regex(/^[0-9a-f]{40}$/u);
|
||||
@@ -29,7 +30,8 @@ export const rustAuditableMetadataSchema = z.strictObject({
|
||||
section_bytes: z.number().int().positive(),
|
||||
compressed_bytes: z.number().int().positive(),
|
||||
uncompressed_bytes: z.number().int().positive(),
|
||||
format_version: z.literal(RUST_AUDITABLE_FORMAT_VERSION).nullable(),
|
||||
/** Original embedded revision; null means the source omitted the field. */
|
||||
format_version: z.number().int().nonnegative().max(0xffff_ffff).nullable(),
|
||||
packages: z.array(rustPackageSchema),
|
||||
root_package_index: index.nullable(),
|
||||
dependency_edges: z.array(dependencyEdgeSchema),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { deflateSync } from "node:zlib";
|
||||
import { expect, it } from "vitest";
|
||||
import { rustAuditableMetadataSchema } from "../domain/rust/rustBinary.js";
|
||||
import {
|
||||
RUST_SECTION_BYTES,
|
||||
readAuditableSection,
|
||||
@@ -153,14 +154,54 @@ it("stops an inflate that exceeds the section ratio", () => {
|
||||
).toThrow(/1 MiB/);
|
||||
});
|
||||
|
||||
it.each([{ packages: [] }, { packages: [{ future_package_field: true }] }])(
|
||||
"rejects an unknown format before interpreting its packages: %j",
|
||||
({ packages }) => {
|
||||
expect(() => section({ format: 99, packages })).toThrow(
|
||||
it.each([0, 8])("reads documented cargo-auditable revision %i", (format) => {
|
||||
const parsed = section({
|
||||
format,
|
||||
packages: [
|
||||
{ name: "sample", version: "0.1.0", source: "local", root: true },
|
||||
],
|
||||
});
|
||||
expect(parsed).toMatchObject({
|
||||
format_version: format,
|
||||
root_package_index: 0,
|
||||
});
|
||||
expect(rustAuditableMetadataSchema.parse(parsed)).toEqual(parsed);
|
||||
});
|
||||
|
||||
it.each([{ source: 4 }, { source: 9 }, { source: 99 }])(
|
||||
"preserves unknown revision $source for the documented preceding-revision interpretation",
|
||||
({ source }) => {
|
||||
const parsed = section({
|
||||
format: source,
|
||||
packages: [
|
||||
{ name: "sample", version: "0.1.0", source: "local", root: true },
|
||||
],
|
||||
});
|
||||
expect(parsed).toMatchObject({
|
||||
format_version: source,
|
||||
});
|
||||
expect(rustAuditableMetadataSchema.parse(parsed)).toEqual(parsed);
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects unknown package fields even for a forward-compatible revision", () => {
|
||||
expect(() =>
|
||||
section({
|
||||
format: 99,
|
||||
packages: [
|
||||
{ name: "sample", version: "0.1.0", source: "local", future: true },
|
||||
],
|
||||
}),
|
||||
).toThrow(/package schema/);
|
||||
});
|
||||
|
||||
it.each([-1, 1.5, 0x1_0000_0000])(
|
||||
"rejects invalid cargo-auditable format value %s",
|
||||
(format) => {
|
||||
expect(() => section({ format, packages: [] })).toThrow(
|
||||
expect.objectContaining({
|
||||
name: "RustAuditableFormatFailure",
|
||||
kind: "unsupported",
|
||||
message: "Unsupported cargo-auditable format version 99",
|
||||
kind: "malformed",
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { deflateSync, inflateSync } from "node:zlib";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
RUST_AUDITABLE_FORMAT_VERSION,
|
||||
cyclicDependencyIndices,
|
||||
type RustAuditableMetadata,
|
||||
type RustPackage,
|
||||
@@ -28,11 +27,10 @@ const packageSchema = z.strictObject({
|
||||
root: z.boolean().optional(),
|
||||
});
|
||||
const documentVersionSchema = z.object({
|
||||
format: z.number().int().nonnegative().optional(),
|
||||
format: z.number().int().nonnegative().max(0xffff_ffff).optional(),
|
||||
});
|
||||
const documentSchema = documentVersionSchema
|
||||
.extend({
|
||||
format: z.literal(RUST_AUDITABLE_FORMAT_VERSION).optional(),
|
||||
packages: z.array(packageSchema),
|
||||
})
|
||||
.strict();
|
||||
@@ -239,14 +237,6 @@ const parseDocument = (bytes: Buffer): z.infer<typeof documentSchema> => {
|
||||
"Dependency JSON does not match the format schema",
|
||||
{ cause: version.error },
|
||||
);
|
||||
if (
|
||||
version.data.format !== undefined &&
|
||||
version.data.format !== RUST_AUDITABLE_FORMAT_VERSION
|
||||
)
|
||||
throw new RustAuditableFormatFailure(
|
||||
"unsupported",
|
||||
`Unsupported cargo-auditable format version ${version.data.format}`,
|
||||
);
|
||||
admitGraphExpansion(value);
|
||||
// One malformed package must fail before Zod retains an issue for every field.
|
||||
rejectMalformedPackages(value);
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
AnalysisUnsupportedTargetError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import {
|
||||
RUST_AUDITABLE_FORMAT_VERSIONS,
|
||||
rustBinarySchema,
|
||||
type InspectRustBinaryInput,
|
||||
type RustBinary,
|
||||
@@ -97,6 +98,7 @@ const limitationsFor = (
|
||||
"No .dep-v0 section was found. Absence of .dep-v0 does not mean the binary has no dependencies.",
|
||||
]
|
||||
: []),
|
||||
...unknownFormatLimitation(image.auditable_metadata?.format_version),
|
||||
...(image.auditable_metadata?.packages.some((pkg) => pkg.kind === null)
|
||||
? [
|
||||
"An omitted package kind is left null. The embedded format treats an omitted kind as runtime, and that default was not stored in the section.",
|
||||
@@ -111,6 +113,20 @@ const limitationsFor = (
|
||||
...rustcLimitation(image.rustc_commit),
|
||||
];
|
||||
|
||||
const unknownFormatLimitation = (
|
||||
format: number | null | undefined,
|
||||
): string[] => {
|
||||
if (format === null || format === undefined) return [];
|
||||
let preceding = 0;
|
||||
for (const known of RUST_AUDITABLE_FORMAT_VERSIONS) {
|
||||
if (known === format) return [];
|
||||
if (known < format) preceding = known;
|
||||
}
|
||||
return [
|
||||
`cargo-auditable format revision ${format} is unknown. Its schema directs readers to interpret it using the highest known preceding revision (${preceding}); this specific revision has not been independently verified.`,
|
||||
];
|
||||
};
|
||||
|
||||
const rustcLimitation = (
|
||||
commit: ReturnType<typeof readRustBinaryImage>["rustc_commit"],
|
||||
): string[] => {
|
||||
|
||||
@@ -3,7 +3,10 @@ import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect } from "vitest";
|
||||
import { toolContract } from "../../src/contracts/toolContracts.js";
|
||||
import { createRustImage } from "../fixtures/rust/image.js";
|
||||
import {
|
||||
createRustImage,
|
||||
rustDependencyPayload,
|
||||
} from "../fixtures/rust/image.js";
|
||||
import { createTestTempDirectory } from "../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../support/cli/cliFixture.js";
|
||||
|
||||
@@ -49,6 +52,37 @@ cliTest("returns null metadata when .dep-v0 is absent", async ({ cli }) => {
|
||||
);
|
||||
});
|
||||
|
||||
cliTest(
|
||||
"preserves an unknown cargo-auditable revision and reports its fallback",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-rust-cli-unknown-format-");
|
||||
const path = join(root, "sample");
|
||||
const payload = rustDependencyPayload({
|
||||
format: 4,
|
||||
packages: [
|
||||
{ name: "sample", version: "0.1.0", source: "local", root: true },
|
||||
],
|
||||
});
|
||||
await writeFile(path, createRustImage({ format: "elf", payload }));
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-rust-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
const evidence = toolContract("inspect_rust_binary").outputSchema.parse(
|
||||
result.json,
|
||||
);
|
||||
expect(evidence.normalized_result.auditable_metadata?.format_version).toBe(
|
||||
4,
|
||||
);
|
||||
expect(evidence.normalized_result.limitations.join("\n")).toContain(
|
||||
"highest known preceding revision (1)",
|
||||
);
|
||||
expect(evidence.normalized_result.limitations.join("\n")).toContain(
|
||||
"this specific revision has not been independently verified",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
cliTest("reports malformed zlib as invalid input", async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-rust-cli-zlib-");
|
||||
const path = join(root, "broken.elf");
|
||||
|
||||
Reference in new issue
Block a user