Compare commits

..
Author SHA1 Message Date
morluto a90f365b8c fix(validation): stop lexical failures before semantic conversions 2026-11-10 21:36:48 -05:00
10 changed files with 78 additions and 104 deletions

No files matched your search

@@ -157,60 +157,64 @@ it("retains the acquired observation when cancellation arrives during inspection
}
});
it.each(["different-path", "invalid-digest", "outside-range"])(
"rejects provider evidence at the next boundary: %s",
async (problem) => {
const fixture = binaryLayoutFixture();
if (problem === "different-path") fixture.artifact.path = "/different.elf";
if (problem === "invalid-digest") fixture.artifact.sha256 = "unobserved";
if (problem === "outside-range")
fixture.segments.push({
index: 0,
type: "PT_LOAD",
offset: "0x80",
header_location: { offset: "0x0", bytes: "0x38" },
file_backing: "file",
file_size: "0x1",
memory_size: "0x1",
virtual_address: "0x20000000000001",
physical_address: "0x0",
alignment: "0x1",
flags: "0x5",
permissions: { read: true, write: false, execute: true },
});
const service = new BinaryLayoutService({
inspectAndClose(input, options) {
return this.inspect(input, options);
},
identity: BINARY_LAYOUT_TEST_PROVIDER,
inspect: () => Promise.resolve(ok(fixture)),
it.each([
"different-path",
"invalid-digest",
"outside-range",
"invalid-hex",
"fractional-bytes",
])("rejects provider evidence at the next boundary: %s", async (problem) => {
const fixture = binaryLayoutFixture();
if (problem === "different-path") fixture.artifact.path = "/different.elf";
if (problem === "invalid-digest") fixture.artifact.sha256 = "unobserved";
if (problem === "fractional-bytes") fixture.artifact.bytes = 1.5;
if (problem === "outside-range" || problem === "invalid-hex")
fixture.segments.push({
index: 0,
type: "PT_LOAD",
offset: problem === "invalid-hex" ? "not-hex" : "0x80",
header_location: { offset: "0x0", bytes: "0x38" },
file_backing: "file",
file_size: "0x1",
memory_size: "0x1",
virtual_address: "0x20000000000001",
physical_address: "0x0",
alignment: "0x1",
flags: "0x5",
permissions: { read: true, write: false, execute: true },
});
expect(
await service.inspect({ path: "/artifacts/source-owned.elf" }),
).toMatchObject({
ok: false,
error: {
_tag: "AnalysisOutputError",
capturedOutput: fixture.diagnostics,
},
});
const controller = new AbortController();
const lateInvalid = await new BinaryLayoutService({
inspectAndClose(input, options) {
return this.inspect(input, options);
},
identity: BINARY_LAYOUT_TEST_PROVIDER,
inspect: () => {
controller.abort();
return Promise.resolve(ok(fixture));
},
}).inspect(
{ path: "/artifacts/source-owned.elf" },
{ signal: controller.signal },
);
expect(lateInvalid).toMatchObject({
ok: false,
error: { _tag: "AnalysisOutputError" },
});
},
);
const service = new BinaryLayoutService({
inspectAndClose(input, options) {
return this.inspect(input, options);
},
identity: BINARY_LAYOUT_TEST_PROVIDER,
inspect: () => Promise.resolve(ok(fixture)),
});
expect(
await service.inspect({ path: "/artifacts/source-owned.elf" }),
).toMatchObject({
ok: false,
error: {
_tag: "AnalysisOutputError",
capturedOutput: fixture.diagnostics,
},
});
const controller = new AbortController();
const lateInvalid = await new BinaryLayoutService({
inspectAndClose(input, options) {
return this.inspect(input, options);
},
identity: BINARY_LAYOUT_TEST_PROVIDER,
inspect: () => {
controller.abort();
return Promise.resolve(ok(fixture));
},
}).inspect(
{ path: "/artifacts/source-owned.elf" },
{ signal: controller.signal },
);
expect(lateInvalid).toMatchObject({
ok: false,
error: { _tag: "AnalysisOutputError" },
});
});
@@ -169,6 +169,8 @@ it.each([
"outside-range",
"owner-base64",
"descriptor-base64",
"invalid-hex",
"fractional-bytes",
])("rejects provider evidence at the next boundary: %s", async (problem) => {
const fixture = recordedCrashFixture();
if (problem === "different-path") fixture.artifact.path = "/different.elf";
@@ -176,6 +178,8 @@ it.each([
if (problem === "owner-base64") fixture.notes[0]!.owner_bytes_base64 = "A";
if (problem === "descriptor-base64")
fixture.notes[0]!.descriptor_bytes_base64 = "A";
if (problem === "invalid-hex") fixture.segments[0]!.offset = "not-hex";
if (problem === "fractional-bytes") fixture.artifact.bytes = 1.5;
if (problem === "outside-range")
fixture.segments.push({
index: 0,
+1 -4
View File
@@ -369,10 +369,7 @@ export class TsharkCaptureProvider implements PacketCapturePort {
resourcePath = "path" in input ? input.path : command;
// TShark resolves `-e` aliases before emitting canonical field names;
// the command arguments, not output names, own field selection.
const matchingPackets = parsePacketOutput(
output.stdout.text,
input.packet_limit,
);
const matchingPackets = parsePacketOutput(output.stdout.text);
const start = Math.min(input.match_offset, matchingPackets.length);
const end =
input.match_limit === null
+1 -6
View File
@@ -6,7 +6,7 @@ import { packetFieldsSchema } from "../domain/captures/packetCapture.js";
const producerSchema = z.array(
z.object({ _source: z.object({ layers: packetFieldsSchema }) }),
);
export const parsePacketOutput = (text: string, packetLimit: number | null) => {
export const parsePacketOutput = (text: string) => {
const decoded: unknown = JSON.parse(text);
const packets = producerSchema
.parse(decoded)
@@ -19,11 +19,6 @@ export const parsePacketOutput = (text: string, packetLimit: number | null) => {
"inspect_packet_capture",
"TShark returned unordered frames.",
);
if (packetLimit !== null && frame > BigInt(packetLimit))
throw new AnalysisOutputError(
"inspect_packet_capture",
"TShark returned a frame beyond the selected input packet limit.",
);
previous = frame;
}
return packets;
+2
View File
@@ -260,6 +260,8 @@ describe("build bundle download installs", () => {
},
}).success;
expect(install({})).toBe(true);
expect(install({ url: "not-a-url" })).toBe(false);
expect(install({ url: "https://" })).toBe(false);
expect(install({ program: "../bin/sh" })).toBe(false);
expect(install({ program: "/usr/bin/notes" })).toBe(false);
expect(install({ program: "notes//notes" })).toBe(false);
+1 -1
View File
@@ -7,7 +7,7 @@ export const COMPUTER_ARCHITECTURES = ["amd64", "arm64"] as const;
/** A URL agentenv downloads from: http or https in lowercase, without a user name or password. */
export const downloadUrlSchema = z
.url({ protocol: /^https?$/u })
.url({ protocol: /^https?$/u, abort: true })
.regex(/^https?:\/\//u)
.refine(
(url) => {
+3 -1
View File
@@ -6,7 +6,9 @@ import { pwntoolsProducerLimitsField } from "./pwntoolsResourceLimits.js";
import { absoluteLocalPathStringSchema } from "../localPath.js";
import { canonicalBase64Schema } from "../stringPatterns.js";
const unsignedHex = z.string().regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/);
const unsignedHex = z
.string()
.regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/, { abort: true });
const index = z.int().nonnegative();
const scalar = z.union([z.string(), z.int()]);
const range = z.strictObject({ offset: unsignedHex, bytes: unsignedHex });
+3 -1
View File
@@ -7,7 +7,9 @@ import { absoluteLocalPathStringSchema } from "../localPath.js";
import { canonicalBase64Schema } from "../stringPatterns.js";
import { recordedNoteCoverageIssues } from "./recordedCrashNoteCoverage.js";
const hex = z.string().regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/);
const hex = z
.string()
.regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/, { abort: true });
const index = z.int().nonnegative();
const signed32 = z.int().min(-2147483648).max(2147483647);
const base64 = canonicalBase64Schema;
@@ -247,26 +247,6 @@ it("preserves TShark canonical output for a caller-selected field alias", async
expect(report.tool.arguments).toContain("bootp.type");
});
it("refuses producer frames outside the selected input read scope before paging", async () => {
const f = await fixture("many");
const request = { path: f.path, fields: ["ip.addr"], match_limit: 1 };
expect(
await f.service.inspect({ ...request, packet_limit: 10 }),
).toMatchObject({
ok: false,
error: { _tag: "AnalysisOutputError", operation: "inspect_packet_capture" },
});
const admitted = await f.service.inspect({ ...request, packet_limit: 90 });
if (!admitted.ok) throw admitted.error;
const report = packetCaptureSchema.parse(admitted.value.normalized_result);
expect(report.output_window).toEqual({
matching_packets: 90,
returned_packets: 1,
has_more: true,
});
expect(report.packets[0]?.["frame.number"]).toEqual(["1"]);
});
it("returns canonical alias output through the public packet-capture CLI", async () => {
const f = await fixture("alias-canonical");
const cli = Cli.create("capture-alias-test", { sync: false });
+3 -15
View File
@@ -17,7 +17,7 @@ it("preserves repeated field values and missing fields while discarding export i
},
{ _source: { layers: { "frame.number": ["7"] } } },
]);
expect(parsePacketOutput(output, 7)).toEqual([
expect(parsePacketOutput(output)).toEqual([
{ "frame.number": ["2"], "ip.addr": ["192.0.2.1", "192.0.2.2"] },
{ "frame.number": ["7"] },
]);
@@ -49,7 +49,7 @@ it.each([
])("rejects malformed producer fields: $layers", ({ layers, issue }) => {
let failure: unknown;
try {
parsePacketOutput(JSON.stringify([{ _source: { layers } }]), null);
parsePacketOutput(JSON.stringify([{ _source: { layers } }]));
} catch (error) {
failure = error;
}
@@ -72,7 +72,6 @@ it.each([
_source: { layers: { "frame.number": frame } },
})),
),
null,
);
} catch (error) {
failure = error;
@@ -89,18 +88,7 @@ it("preserves canonical field names emitted for a selected alias", () => {
},
},
]);
expect(parsePacketOutput(output, null)).toEqual([
expect(parsePacketOutput(output)).toEqual([
{ "frame.number": ["1"], "dhcp.type": ["1"] },
]);
});
it("validates every frame against the input read limit while allowing filtered gaps", () => {
const output = JSON.stringify(
[2, 7].map((frame) => ({
_source: { layers: { "frame.number": [String(frame)] } },
})),
);
expect(parsePacketOutput(output, 7)).toHaveLength(2);
expect(() => parsePacketOutput(output, 6)).toThrow(AnalysisOutputError);
expect(parsePacketOutput(output, null)).toHaveLength(2);
});