mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 06:46:10 +00:00
fix(artifacts): name the ASAR header admission budget (#6172)
Keep the 16 MiB ASAR header/index ceiling fixed and report that budget, the lack of a runtime override, and that source selection does not bypass header admission. Refs #5910 Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <morluto@users.noreply.github.com>
This commit is contained in:
11 files changed
+202
-15
No files matched your search
@@ -188,6 +188,7 @@ const artifactFailureToOperationError = (
|
||||
...(cause.partialObservation === undefined
|
||||
? {}
|
||||
: { partialObservation: cause.partialObservation }),
|
||||
...(cause.admission === undefined ? {} : { admission: cause.admission }),
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -32,6 +32,9 @@ export const translateArtifactFailure = (
|
||||
...(cause.partialObservation === undefined
|
||||
? {}
|
||||
: { partialObservation: cause.partialObservation }),
|
||||
...(cause.admission === undefined
|
||||
? {}
|
||||
: { admission: cause.admission }),
|
||||
},
|
||||
);
|
||||
bindArtifactFileCleanup(cause, error);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { ArtifactAdmissionLimit } from "../domain/artifactOperationError.js";
|
||||
import type {
|
||||
ArtifactIntegrityClaim,
|
||||
ArtifactIntegrityDetails,
|
||||
@@ -28,6 +29,8 @@ export interface ArtifactReaderFailureOptions extends ErrorOptions {
|
||||
readonly partialObservation?: AnalysisPartialObservation | undefined;
|
||||
/** The constrained resource bound for a limit failure. */
|
||||
readonly boundary?: ArtifactReaderBoundary;
|
||||
/** Named admission whose recovery must not be inferred from other limit text. */
|
||||
readonly admission?: ArtifactAdmissionLimit;
|
||||
}
|
||||
|
||||
/** Archive-neutral entry metadata. Reader adapters never choose output paths. */
|
||||
@@ -192,6 +195,8 @@ export class ArtifactReaderFailure extends Error {
|
||||
readonly partialObservation: AnalysisPartialObservation | undefined;
|
||||
/** The constrained bound for a `limit` reason, when the reader knows it. */
|
||||
readonly boundary: ArtifactReaderBoundary | undefined;
|
||||
/** Named admission for a limit whose public recovery is owned by the producer. */
|
||||
readonly admission: ArtifactAdmissionLimit | undefined;
|
||||
|
||||
constructor(
|
||||
readonly reason:
|
||||
@@ -217,6 +222,7 @@ export class ArtifactReaderFailure extends Error {
|
||||
options?.cleanupLeases ?? (this.cleanup === undefined ? [] : undefined);
|
||||
this.partialObservation = options?.partialObservation;
|
||||
this.boundary = options?.boundary;
|
||||
this.admission = options?.admission;
|
||||
}
|
||||
|
||||
/** Preserve the primary failure and observation. Supplied leases must cover all merged cleanup. */
|
||||
@@ -261,6 +267,9 @@ export class ArtifactReaderFailure extends Error {
|
||||
...(primary.boundary === undefined
|
||||
? {}
|
||||
: { boundary: primary.boundary }),
|
||||
...(primary.admission === undefined
|
||||
? {}
|
||||
: { admission: primary.admission }),
|
||||
...(mergedObservation === undefined
|
||||
? {}
|
||||
: { partialObservation: mergedObservation }),
|
||||
|
||||
@@ -5,9 +5,37 @@ import { assertArtifactReadActive } from "./ArtifactFileOperation.js";
|
||||
import { ArtifactReaderFailure } from "./ArtifactReader.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
|
||||
/** Shared resource budget for the eagerly decoded ASAR header, not member bytes. */
|
||||
/**
|
||||
* Fixed header/index admission for this release. The header is decoded before
|
||||
* any member path can be selected. There is no caller or configuration override.
|
||||
*/
|
||||
export const MAX_ASAR_HEADER_BYTES = 16 * 1024 * 1024;
|
||||
|
||||
const MEBIBYTE = 1024 * 1024;
|
||||
|
||||
/** User-visible recovery for the fixed ASAR header/index admission budget. */
|
||||
export const asarHeaderAdmissionRecovery = (): string => {
|
||||
const bytes = MAX_ASAR_HEADER_BYTES;
|
||||
const mebibytes = bytes / MEBIBYTE;
|
||||
const allowance = Number.isInteger(mebibytes)
|
||||
? `${String(mebibytes)} MiB / ${String(bytes)} bytes`
|
||||
: `${String(bytes)} bytes`;
|
||||
return [
|
||||
`ASAR header/index admission is limited to ${allowance}.`,
|
||||
"This release has no runtime override.",
|
||||
"Selecting fewer sources or narrowing an internal path does not bypass header admission, because the archive index is read before any member can be selected.",
|
||||
"Raising max_heap_mb or available host memory does not raise it.",
|
||||
"Analyze an extracted application directory, which does not admit an ASAR header, or supply an archive whose header fits this allowance.",
|
||||
"Repeating the unchanged request fails the same way.",
|
||||
].join(" ");
|
||||
};
|
||||
|
||||
/** Refuse an ASAR whose header/index exceeds the fixed admission budget. */
|
||||
export const asarHeaderAdmissionFailure = (): ArtifactReaderFailure =>
|
||||
new ArtifactReaderFailure("limit", asarHeaderAdmissionRecovery(), {
|
||||
admission: { kind: "asar-header", maximumBytes: MAX_ASAR_HEADER_BYTES },
|
||||
});
|
||||
|
||||
/** Absolute archive prefix required to inspect an admitted header. */
|
||||
export const MAX_ASAR_HEADER_PROBE_BYTES = MAX_ASAR_HEADER_BYTES + 8;
|
||||
|
||||
@@ -345,10 +373,7 @@ export const admitAsarHeader = async (
|
||||
const parsed = parseAsarHeaderExtents(prefix, observed.size);
|
||||
if (!parsed.ok) throw parsed.error;
|
||||
if (parsed.value.headerBytes > MAX_ASAR_HEADER_BYTES)
|
||||
throw new ArtifactReaderFailure(
|
||||
"limit",
|
||||
`ASAR header exceeds the ${String(MAX_ASAR_HEADER_BYTES)} byte decode budget`,
|
||||
);
|
||||
throw asarHeaderAdmissionFailure();
|
||||
return {
|
||||
archiveBytes: observed.size,
|
||||
...parsed.value,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { hasGodotPckHeader } from "../../domain/godotPckFormat.js";
|
||||
import type { ArtifactClassificationEvidence } from "../ArtifactClassificationProbe.js";
|
||||
import { ArtifactReaderFailure } from "../ArtifactReader.js";
|
||||
import { asarHeaderAdmissionFailure } from "../AsarHeader.js";
|
||||
import { hasDestinationPath } from "../ArtifactPaths.js";
|
||||
import { compareUnicodeCodePoints } from "../../domain/unicodeCodePointOrder.js";
|
||||
import { isJavaScriptSourcePath } from "../../domain/javascript/javascriptArtifactFiles.js";
|
||||
@@ -529,11 +529,7 @@ export const classifyArtifactContent = (
|
||||
path.toLowerCase().endsWith(".asar") &&
|
||||
asarHeader.state !== "rejected"
|
||||
) {
|
||||
if (asarHeader.state === "limit")
|
||||
throw new ArtifactReaderFailure(
|
||||
"limit",
|
||||
"ASAR header exceeds the bounded classification or decode budget",
|
||||
);
|
||||
if (asarHeader.state === "limit") throw asarHeaderAdmissionFailure();
|
||||
if (asarHeader.state === "matched")
|
||||
return { kind: "container", format: "asar" };
|
||||
}
|
||||
|
||||
@@ -374,6 +374,12 @@ const artifactStateErrorDetails = (
|
||||
reason: error.reason,
|
||||
...(error.reason === "limit" ? { truncated: true } : {}),
|
||||
...(error.detail === undefined ? {} : { detail: error.detail }),
|
||||
...(error.admission === undefined
|
||||
? {}
|
||||
: {
|
||||
admission: error.admission.kind,
|
||||
maximum_header_bytes: error.admission.maximumBytes,
|
||||
}),
|
||||
};
|
||||
if (error instanceof UnknownRegistryError) return { reason: error.reason };
|
||||
if (error instanceof EvidenceFileError)
|
||||
|
||||
@@ -7,9 +7,17 @@ import {
|
||||
type AnalysisErrorOptions,
|
||||
} from "./analysisErrorBase.js";
|
||||
|
||||
/** Fixed admission named by the producing reader, with its effective byte ceiling. */
|
||||
export interface ArtifactAdmissionLimit {
|
||||
readonly kind: "asar-header";
|
||||
readonly maximumBytes: number;
|
||||
}
|
||||
|
||||
/** Artifact inventory or extraction failed a typed safety boundary. */
|
||||
export class ArtifactOperationError extends AnalysisError {
|
||||
readonly _tag = "ArtifactOperationError";
|
||||
override readonly userMessage: string | undefined;
|
||||
readonly admission: ArtifactAdmissionLimit | undefined;
|
||||
|
||||
constructor(
|
||||
readonly operation:
|
||||
@@ -37,7 +45,10 @@ export class ArtifactOperationError extends AnalysisError {
|
||||
readonly artifactDetails?: ArtifactIntegrityDetails,
|
||||
/** The specific constraint that failed, such as the colliding path. */
|
||||
readonly detail?: string,
|
||||
options?: AnalysisErrorOptions,
|
||||
options?: AnalysisErrorOptions & {
|
||||
readonly userMessage?: string;
|
||||
readonly admission?: ArtifactAdmissionLimit;
|
||||
},
|
||||
) {
|
||||
super(
|
||||
artifactDetails === undefined
|
||||
@@ -45,6 +56,10 @@ export class ArtifactOperationError extends AnalysisError {
|
||||
: artifactDetailsMessage(operation, reason, artifactDetails),
|
||||
options,
|
||||
);
|
||||
this.admission = options?.admission;
|
||||
this.userMessage =
|
||||
options?.userMessage ??
|
||||
(options?.admission === undefined ? undefined : detail);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user