diff --git a/src/ida/IdaInput.ts b/src/ida/IdaInput.ts index 832b80c9cc..02eb68821c 100644 --- a/src/ida/IdaInput.ts +++ b/src/ida/IdaInput.ts @@ -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, + ), ); } } diff --git a/src/ida/IdaSessionClient.test.ts b/src/ida/IdaSessionClient.test.ts index e5f8d6be85..91d0f3ad29 100644 --- a/src/ida/IdaSessionClient.test.ts +++ b/src/ida/IdaSessionClient.test.ts @@ -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(); }); }); diff --git a/src/process/RegexMatcher.ts b/src/process/RegexMatcher.ts index d33fc4baad..dccae9340c 100644 --- a/src/process/RegexMatcher.ts +++ b/src/process/RegexMatcher.ts @@ -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(