feat(settings): read per-call REA_ limits from the operation snapshot

Part of #9881. Move the limits checked on each call into update_analysis_settings, keep their REA_ seeds and bounds, and reject updates outside the allowed range.
This commit is contained in:
Cursor Agent authored and morluto committed 2026-11-11 20:16:24 +08:00
1 parent 9924bf3445
commit b0541c80b3
34 files changed
+704 -254

No files matched your search

+84 -2
View File
@@ -1,5 +1,11 @@
import { expect, it } from "vitest";
import { AnalysisSettings } from "./AnalysisSettings.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import {
applyWindowsChangeTimeSnapshot,
resolveWindowsChangeTimeSettle,
} from "../config/windowsChangeTimeSettle.js";
import { pwntoolsInputBytes } from "../native/pwntools/PwntoolsInputLimit.js";
import {
runtimeAnalysisConfig,
runtimeAsarHeaderAdmission,
@@ -159,7 +165,7 @@ it("exposes shared provider controls and preserves the explicit unbounded decomp
store
.inspect({ provider: "ghidra", operation: "search_strings" })
.settings.map(({ setting }) => setting),
).toEqual(["ghidra.startup_timeout_ms"]);
).toEqual(["ghidra.startup_timeout_ms", "bridge.max_request_bytes"]);
expect(
store
.inspect({ provider: "ida", operation: "search_strings" })
@@ -169,7 +175,13 @@ it("exposes shared provider controls and preserves the explicit unbounded decomp
store
.inspect({ operation: "inventory_artifact" })
.settings.map(({ setting }) => setting),
).toEqual(["asar.max_listing_bytes", "asar.max_header_bytes"]);
).toEqual([
"asar.max_listing_bytes",
"asar.max_header_bytes",
"windows.change_time_drift_ms",
"windows.change_time_settle_poll_ms",
"windows.change_time_settle_max_polls",
]);
expect(
store.update({
changes: [
@@ -217,3 +229,73 @@ it("exposes shared provider controls and preserves the explicit unbounded decomp
).toBe(true);
expect(config.ghidraDecompileTimeoutSeconds).toBe(7);
});
it("rejects an out-of-range per-call limit and reads an accepted value from the operation snapshot", () => {
const store = new AnalysisSettings({});
const rejected = store.update({
changes: [
{ setting: "windows.change_time_drift_ms", action: "set", value: 0 },
],
});
expect(rejected.ok).toBe(false);
if (!rejected.ok)
expect(projectAnalysisError(rejected.error)).toMatchObject({
code: "invalid_request",
});
expect(
store.update({
changes: [
{ setting: "bridge.max_request_bytes", action: "set", value: 1 },
],
}).ok,
).toBe(false);
expect(
store.update({
changes: [
{
setting: "windows.change_time_settle_max_polls",
action: "set",
value: 1001,
},
],
}).ok,
).toBe(false);
const accepted = store.update({
changes: [
{ setting: "windows.change_time_drift_ms", action: "set", value: 5000 },
{ setting: "pwntools.max_input_bytes", action: "set", value: 65536 },
{ setting: "bridge.max_request_bytes", action: "set", value: 33554432 },
],
});
expect(accepted.ok).toBe(true);
if (!accepted.ok) return;
expect(accepted.value).toMatchObject({
effect: "immediate",
rea_restart_required: false,
});
const parsed = parseConfig({});
if (!parsed.ok) throw parsed.error;
const live = runtimeAnalysisConfig(parsed.value, store.read);
store.run(() => {
const windows = resolveWindowsChangeTimeSettle({});
if (!windows.ok) throw windows.error;
expect(applyWindowsChangeTimeSnapshot(windows.value)).toMatchObject({
driftMs: 5000,
driftSource: "runtime",
pollSource: "default",
});
expect(pwntoolsInputBytes({})).toMatchObject({
configured: true,
bytes: 65536,
source: "runtime",
});
expect(live.bridgeMaxRequestBytes).toMatchObject({
maximumBytes: 33554432,
source: "runtime",
});
});
expect(pwntoolsInputBytes({})).toMatchObject({
bytes: 32 * 1024 * 1024,
source: "default",
});
});
+4 -1
View File
@@ -1,3 +1,4 @@
import { runWithAnalysisOperationSnapshot } from "../domain/analysisOperationSnapshot.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import { launchAnalysisSetting } from "../config/analysisSettings.js";
import { AsyncLocalStorage } from "node:async_hooks";
@@ -42,7 +43,9 @@ export class AnalysisSettings {
readonly read = (): AnalysisSettingsSnapshot =>
this.#accepted.getStore() ?? this.#current;
run<Value>(operation: () => Value): Value {
return this.#accepted.run(this.#current, operation);
return this.#accepted.run(this.#current, () =>
runWithAnalysisOperationSnapshot(this.#current, operation),
);
}
inspect(input: z.infer<typeof inspectAnalysisSettingsInputSchema> = {}) {
return {