fix(ida): report the regex syntax error for invalid search patterns (#9544)

IDA checks a regex search pattern before it starts the provider. That early
check returned a generic "Supply a valid regular expression." issue, so IDA
callers never saw the parser explanation or the literal-mode recovery that
the shared regex worker gives Kuna and Hopper callers.

Build both refusals with one helper in RegexMatcher.
This commit is contained in:
RRFZ authored and GitHub committed 2026-11-09 21:46:25 +08:00
1 parent ca1674473c
commit 769d92025d
3 files changed
+48 -28

No files matched your search

+6 -7
View File
@@ -2,6 +2,7 @@ import { TOOL_CONTRACTS } from "../contracts/toolContracts.js";
import { AnalysisInputError } from "../domain/analysisErrorCore.js";
import { jsonObjectSchema, type JsonValue } from "../domain/jsonValue.js";
import { err, ok } from "../domain/result.js";
import { invalidRegexPatternError } from "../process/RegexMatcher.js";
import { idaAddressSchema } from "./IdaProtocolValues.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
@@ -65,13 +66,11 @@ export const parseIdaInput = (
);
} catch (cause: unknown) {
return err(
new AnalysisInputError(operation, { cause }, [
{
path: ["pattern"],
reason: "invalid_format",
message: "Supply a valid regular expression.",
},
]),
invalidRegexPatternError(
operation,
cause instanceof Error ? cause.message : String(cause),
cause,
),
);
}
}
+20 -4
View File
@@ -662,10 +662,26 @@ describe("attached IDA function observations", () => {
const found = await client.execute("search_strings", { pattern, mode });
expect(found.ok && found.value.result).toHaveLength(count);
}
expect(
(await client.execute("search_strings", { pattern: "[", mode: "regex" }))
.ok,
).toBe(false);
const invalid = await client.execute("search_strings", {
pattern: "[",
mode: "regex",
});
if (invalid.ok) throw new Error("Expected invalid-pattern refusal");
expect(projectAnalysisError(invalid.error)).toMatchObject({
code: "invalid_request",
message: expect.stringContaining("Invalid regex pattern"),
remediation: { action: expect.stringMatching(/pattern.*mode literal/u) },
details: {
operation: "search_strings",
issues: [
{
path: ["pattern"],
reason: "invalid_format",
message: expect.stringContaining("Invalid regular expression"),
},
],
},
});
await client.close();
});
});
+22 -17
View File
@@ -518,6 +518,27 @@ export const regexWorkerHeapError = (
remediationAction: `${heapBound(heap, request)} Use update_analysis_settings with setting regex.worker_heap_request_mib to change the request for the next worker; launch heap flags can override it. Restart REA after changing --max-old-space-size or ${REGEX_WORKER_HEAP_REQUEST_SETTING} in the launch environment. A launch --max-old-space-size-percentage takes precedence over that flag in either order. Simplify the pattern or use literal mode.`,
},
);
/**
* A pattern the regex engine rejects. The caller sees the engine's syntax
* error on `pattern` and can retry in literal mode. Providers that check the
* pattern before starting share this result with the worker.
*/
export const invalidRegexPatternError = (
operation: string,
reason: string,
cause?: unknown,
): AnalysisInputError =>
new AnalysisInputError(
operation,
{
cause,
userMessage: `Invalid regex pattern: ${reason}`,
remediationAction:
"Correct the pattern using the reported regex syntax error, or select mode literal to search for the text literally, then retry.",
},
[{ path: ["pattern"], reason: "invalid_format", message: reason }],
);
const replySchema = z.discriminatedUnion("status", [
z.strictObject({
status: z.literal("matched"),
@@ -621,23 +642,7 @@ export const matchRegexValues = async (input: {
);
if (reply.data.status === "invalid_pattern")
return finish(
err(
new AnalysisInputError(
input.operation,
{
userMessage: `Invalid regex pattern: ${reply.data.reason}`,
remediationAction:
"Correct the pattern using the reported regex syntax error, or select mode literal to search for the text literally, then retry.",
},
[
{
path: ["pattern"],
reason: "invalid_format",
message: reply.data.reason,
},
],
),
),
err(invalidRegexPatternError(input.operation, reply.data.reason)),
);
if (
reply.data.indices.some(