From f249e1f32bb1183e5a35991f6237f2b9c684fe57 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Mon, 20 Apr 2026 20:20:33 +0200 Subject: [PATCH 1/2] test(jest-runner): keep corpus lookup off tmp parents Mock the parent directory walk for the missing package.json case so the corpus test stays independent of tmp ancestors on the local machine. Type the readdirSync mock explicitly so TypeScript keeps compiling the test. --- packages/jest-runner/corpus.test.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/jest-runner/corpus.test.ts b/packages/jest-runner/corpus.test.ts index fcec3eae..f089061a 100644 --- a/packages/jest-runner/corpus.test.ts +++ b/packages/jest-runner/corpus.test.ts @@ -95,7 +95,28 @@ describe("Corpus", () => { it("throw error if no package.json was found", () => { const fuzzTest = mockFuzzTest({ generatePackageJson: false }); - expect(() => new Corpus(fuzzTest, [])).toThrow(); + const originalReaddirSync = (directoryPath: fs.PathLike): string[] => + fs.readdirSync(directoryPath); + const readdirSync = jest.spyOn(fs, "readdirSync"); + readdirSync.mockImplementation(((directoryPath: fs.PathLike) => { + if (typeof directoryPath === "string") { + const fuzzTestDir = path.dirname(fuzzTest); + if ( + directoryPath === fuzzTestDir || + directoryPath === path.dirname(fuzzTestDir) || + directoryPath === path.parse(fuzzTestDir).root + ) { + return []; + } + } + return originalReaddirSync(directoryPath); + }) as unknown as typeof fs.readdirSync); + + try { + expect(() => new Corpus(fuzzTest, [])).toThrow(); + } finally { + readdirSync.mockRestore(); + } }); }); }); From fd8d73c0f10710b473879c821504d34fb4576386 Mon Sep 17 00:00:00 2001 From: Peter Samarin Date: Mon, 20 Apr 2026 20:20:33 +0200 Subject: [PATCH 2/2] feat(bug-detectors): add code injection canary findings Replace the brittle substring detector with deterministic canaries for eval and Function. Split findings into canary access and invocation, and emit copy-paste suppression rules for noisy heuristic reads. --- docs/bug-detectors.md | 45 +- .../bug-detectors/internal/code-injection.ts | 437 ++++++++++++++++++ .../internal/remote-code-execution.ts | 71 --- packages/core/cli.ts | 1 + packages/core/core.ts | 7 +- tests/bug-detectors/code-injection.test.js | 197 ++++++++ .../code-injection/disable-access.config.js | 21 + tests/bug-detectors/code-injection/fuzz.js | 94 ++++ .../ignore-access-by-frame.config.js | 24 + .../ignore-access-by-stack.config.js | 23 + .../ignore-invocation.config.js | 25 + .../package.json | 4 +- .../code-injection/tests.fuzz.js | 45 ++ .../remote-code-execution.test.js | 303 ------------ .../remote-code-execution/fuzz.js | 72 --- .../remote-code-execution/tests.fuzz.js | 68 --- 16 files changed, 911 insertions(+), 526 deletions(-) create mode 100644 packages/bug-detectors/internal/code-injection.ts delete mode 100644 packages/bug-detectors/internal/remote-code-execution.ts create mode 100644 tests/bug-detectors/code-injection.test.js create mode 100644 tests/bug-detectors/code-injection/disable-access.config.js create mode 100644 tests/bug-detectors/code-injection/fuzz.js create mode 100644 tests/bug-detectors/code-injection/ignore-access-by-frame.config.js create mode 100644 tests/bug-detectors/code-injection/ignore-access-by-stack.config.js create mode 100644 tests/bug-detectors/code-injection/ignore-invocation.config.js rename tests/bug-detectors/{remote-code-execution => code-injection}/package.json (78%) create mode 100644 tests/bug-detectors/code-injection/tests.fuzz.js delete mode 100644 tests/bug-detectors/remote-code-execution.test.js delete mode 100644 tests/bug-detectors/remote-code-execution/fuzz.js delete mode 100644 tests/bug-detectors/remote-code-execution/tests.fuzz.js diff --git a/docs/bug-detectors.md b/docs/bug-detectors.md index 4c9d5c9e..17c2e1aa 100644 --- a/docs/bug-detectors.md +++ b/docs/bug-detectors.md @@ -98,17 +98,48 @@ using Jest in `.jazzerjsrc.json`: { "disableBugDetectors": ["prototype-pollution"] } ``` -## Remote Code Execution +## Code Injection -Hooks the `eval` and `Function` functions and reports a finding if the fuzzer -was able to pass a special string to `eval` and to the function body of -`Function`. +Installs a canary on `globalThis` and hooks the `eval` and `Function` functions. +The before-hooks guide the fuzzer toward injecting the active canary identifier +into code strings. The detector reports two fatal stages by default: -_Disable with:_ `--disableBugDetectors=remote-code-execution` in CLI mode; or -when using Jest in `.jazzerjsrc.json`: +- `Potential Code Injection (Canary Accessed)` - some code resolved the canary. + This high-recall heuristic catches cases where dynamically produced code reads + or stores the canary before executing it later. +- `Confirmed Code Injection (Canary Invoked)` - the callable canary returned by + the getter was invoked. + +The detector can be configured in the +[custom hooks](./fuzz-settings.md#customhooks--arraystring) file. + +- `disableAccessReporting` - disables the stage-1 access finding while keeping + invocation reporting active. +- `disableInvocationReporting` - disables the stage-2 invocation finding. +- `ignoreAccess(rule)` - suppresses stage-1 findings matching a file, function, + or stack pattern. +- `ignoreInvocation(rule)` - suppresses stage-2 findings matching a file, + function, or stack pattern. + +Here is an example configuration in the +[custom hooks](./fuzz-settings.md#customhooks--arraystring) file: + +```javascript +const { getBugDetectorConfiguration } = require("@jazzer.js/bug-detectors"); + +getBugDetectorConfiguration("code-injection") + ?.ignoreAccess({ + filePattern: /handlebars[\\/]dist[\\/]cjs[\\/]runtime\.js$/, + functionPattern: /^lookupProperty$/, + }) + ?.disableInvocationReporting(); +``` + +_Disable with:_ `--disableBugDetectors=code-injection` in CLI mode; or when +using Jest in `.jazzerjsrc.json`: ```json -{ "disableBugDetectors": ["remote-code-execution"] } +{ "disableBugDetectors": ["code-injection"] } ``` ## Server-Side Request Forgery (SSRF) diff --git a/packages/bug-detectors/internal/code-injection.ts b/packages/bug-detectors/internal/code-injection.ts new file mode 100644 index 00000000..ea2d6801 --- /dev/null +++ b/packages/bug-detectors/internal/code-injection.ts @@ -0,0 +1,437 @@ +/* + * Copyright 2026 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Context } from "vm"; + +import { + getJazzerJsGlobal, + guideTowardsContainment, + reportAndThrowFinding, +} from "@jazzer.js/core"; +import { registerBeforeHook } from "@jazzer.js/hooking"; + +import { bugDetectorConfigurations } from "../configuration"; + +const BASE_CANARY_NAME = "jaz_zer"; +const MAX_STACK_LINES = 8; +const JAZZER_INTERNAL_STACK_MARKERS = [ + "/@jazzer.js/", + "/jazzer.js/packages/", + "/jazzer.js/core/", + "/jazzer.js-commercial/packages/", + "/jazzer.js-commercial/core/", + "../../packages/", +]; + +export interface IgnoreRule { + filePattern?: RegExp; + functionPattern?: RegExp; + stackPattern?: RegExp; +} + +type OriginFrame = { + stackLine: string; + functionName?: string; + filePath?: string; +}; + +export class CodeInjectionConfig { + private _reportAccess = true; + private _reportInvocation = true; + private readonly _ignoredAccessRules: IgnoreRule[] = []; + private readonly _ignoredInvocationRules: IgnoreRule[] = []; + + disableAccessReporting(): this { + this._reportAccess = false; + return this; + } + + disableInvocationReporting(): this { + this._reportInvocation = false; + return this; + } + + ignoreAccess(rule: IgnoreRule): this { + this._ignoredAccessRules.push(rule); + return this; + } + + ignoreInvocation(rule: IgnoreRule): this { + this._ignoredInvocationRules.push(rule); + return this; + } + + shouldReportAccess( + originFrame: OriginFrame | undefined, + stack: string, + ): boolean { + return ( + this._reportAccess && + !this.matchesAnyRule(this._ignoredAccessRules, originFrame, stack) + ); + } + + shouldReportInvocation( + originFrame: OriginFrame | undefined, + stack: string, + ): boolean { + return ( + this._reportInvocation && + !this.matchesAnyRule(this._ignoredInvocationRules, originFrame, stack) + ); + } + + private matchesAnyRule( + rules: IgnoreRule[], + originFrame: OriginFrame | undefined, + stack: string, + ): boolean { + return rules.some((rule) => this.matchesRule(rule, originFrame, stack)); + } + + private matchesRule( + rule: IgnoreRule, + originFrame: OriginFrame | undefined, + stack: string, + ): boolean { + if (rule.stackPattern && !matchesPattern(rule.stackPattern, stack)) { + return false; + } + if (rule.filePattern) { + if (!originFrame?.filePath) { + return false; + } + if (!matchesPattern(rule.filePattern, originFrame.filePath)) { + return false; + } + } + if (rule.functionPattern) { + if (!originFrame?.functionName) { + return false; + } + if (!matchesPattern(rule.functionPattern, originFrame.functionName)) { + return false; + } + } + return Boolean( + rule.stackPattern || rule.filePattern || rule.functionPattern, + ); + } +} + +const config = new CodeInjectionConfig(); +bugDetectorConfigurations.set("code-injection", config); + +const installedCanaries = new WeakMap(); + +ensureKnownCanariesInstalled(); + +registerBeforeHook( + "eval", + "", + false, + function beforeEvalHook( + _thisPtr: unknown, + params: unknown[], + hookId: number, + ) { + ensureKnownCanariesInstalled(); + + const code = params[0]; + if (typeof code === "string") { + guideTowardsContainment(code, getActiveCanaryName(), hookId); + } + }, +); + +registerBeforeHook( + "Function", + "", + false, + function beforeFunctionHook( + _thisPtr: unknown, + params: unknown[], + hookId: number, + ) { + ensureKnownCanariesInstalled(); + if (params.length === 0) return; + + const functionBody = params[params.length - 1]; + if (functionBody == null) return; + + let functionBodySource: string; + try { + functionBodySource = String(functionBody); + } catch { + return; + } + + guideTowardsContainment(functionBodySource, getActiveCanaryName(), hookId); + }, +); + +function ensureKnownCanariesInstalled(): void { + ensureCanaryInstalled(globalThis); + const vmContext = getVmContext(); + if (vmContext) { + ensureCanaryInstalled(vmContext); + } +} + +function getVmContext(): Context | undefined { + return getJazzerJsGlobal("vmContext"); +} + +function getActiveCanaryName(): string { + const vmContext = getVmContext(); + return vmContext + ? ensureCanaryInstalled(vmContext) + : ensureCanaryInstalled(globalThis); +} + +function ensureCanaryInstalled(target: object): string { + const knownCanaryName = installedCanaries.get(target); + if (knownCanaryName) { + return knownCanaryName; + } + + let canaryName = BASE_CANARY_NAME; + let suffix = 0; + while (Object.getOwnPropertyDescriptor(target, canaryName)) { + suffix += 1; + canaryName = `${BASE_CANARY_NAME}_${suffix}`; + } + + Object.defineProperty(target, canaryName, createCanaryDescriptor(canaryName)); + installedCanaries.set(target, canaryName); + return canaryName; +} + +function createCanaryDescriptor(canaryName: string): PropertyDescriptor { + return { + get() { + const accessStack = captureStack(); + const accessOrigin = parseOriginFrame(accessStack); + if (config.shouldReportAccess(accessOrigin, accessStack)) { + reportAndThrowFinding( + buildFindingMessage( + "Potential Code Injection (Canary Accessed)", + `accessed canary: ${canaryName}`, + accessStack, + accessOrigin, + "ignoreAccess", + "If this is a safe heuristic read, suppress it to continue fuzzing for code execution. Add this to your custom hooks:", + ), + false, + ); + } + + return function canaryCall() { + const invocationStack = captureStack(); + const invocationOrigin = parseOriginFrame(invocationStack); + if (config.shouldReportInvocation(invocationOrigin, invocationStack)) { + reportAndThrowFinding( + buildFindingMessage( + "Confirmed Code Injection (Canary Invoked)", + `invoked canary: ${canaryName}`, + invocationStack, + invocationOrigin, + "ignoreInvocation", + "If this execution sink is expected in your test environment, suppress it:", + ), + false, + ); + } + }; + }, + enumerable: false, + configurable: false, + }; +} + +function buildFindingMessage( + title: string, + action: string, + stack: string, + originFrame: OriginFrame | undefined, + suppressionMethod: "ignoreAccess" | "ignoreInvocation", + hint: string, +): string { + const relevantStackLines = getRelevantStackLines(stack).slice( + 0, + MAX_STACK_LINES, + ); + const message = [`${title} -- ${action}`]; + if (relevantStackLines.length > 0) { + message.push(...relevantStackLines); + } + message.push( + "", + `[!] ${hint}`, + "", + buildSuppressionSnippet(suppressionMethod, originFrame, stack), + ); + return message.join("\n"); +} + +function buildSuppressionSnippet( + suppressionMethod: "ignoreAccess" | "ignoreInvocation", + originFrame: OriginFrame | undefined, + stack: string, +): string { + const ruleLines: string[] = []; + const filePattern = + originFrame?.filePath && buildFilePattern(originFrame.filePath); + if (filePattern) { + ruleLines.push(`filePattern: ${filePattern}`); + } + if (originFrame?.functionName) { + ruleLines.push( + `functionPattern: ${buildExactRegex(originFrame.functionName)}`, + ); + } + if (ruleLines.length === 0) { + ruleLines.push(`stackPattern: ${buildStackPattern(stack)}`); + } + + return [ + 'const { getBugDetectorConfiguration } = require("@jazzer.js/bug-detectors");', + "", + 'getBugDetectorConfiguration("code-injection")', + ` .${suppressionMethod}({`, + ...ruleLines.map( + (line, index) => ` ${line}${index < ruleLines.length - 1 ? "," : ""}`, + ), + " });", + ].join("\n"); +} + +function buildFilePattern(filePath: string): string | undefined { + if (filePath.startsWith("node:")) { + return undefined; + } + + const normalized = filePath.replace(/\\/g, "/"); + let parts = normalized.split("/").filter(Boolean); + if (parts.length === 0) { + return undefined; + } + if (/^[A-Za-z]:$/.test(parts[0])) { + parts = parts.slice(1); + } + + const nodeModulesIndex = parts.indexOf("node_modules"); + if (nodeModulesIndex !== -1) { + parts = parts.slice(nodeModulesIndex + 1); + } else { + parts = parts.slice(-Math.min(2, parts.length)); + } + + return `/${parts.map(escapeRegex).join("[\\\\/]")}$/`; +} + +function buildExactRegex(text: string): string { + return `/^${escapeRegex(text)}$/`; +} + +function buildStackPattern(stack: string): string { + const stackLine = getRelevantStackLines(stack)[0] ?? " at "; + const normalized = stackLine + .replace(/:\d+:\d+/g, "") + .replace(/^\s*at\s+/, "") + .trim(); + return `/${escapeRegex(normalized)}/`; +} + +function captureStack(): string { + return new Error().stack ?? ""; +} + +function parseOriginFrame(stack: string): OriginFrame | undefined { + const stackLine = getRelevantStackLines(stack)[0]; + if (!stackLine) { + return undefined; + } + + const withFunctionMatch = stackLine.match(/^\s*at\s+(.+?)\s+\((.+)\)$/); + if (withFunctionMatch) { + return { + stackLine, + functionName: normalizeFunctionName(withFunctionMatch[1]), + filePath: extractFilePath(withFunctionMatch[2]), + }; + } + + const withoutFunctionMatch = stackLine.match(/^\s*at\s+(.+)$/); + if (withoutFunctionMatch) { + return { + stackLine, + functionName: "anonymous", + filePath: extractFilePath(withoutFunctionMatch[1]), + }; + } + + return { stackLine }; +} + +function normalizeFunctionName(functionName: string): string { + const trimmed = functionName.trim(); + if (trimmed.length === 0 || trimmed === "") { + return "anonymous"; + } + return trimmed; +} + +function extractFilePath(location: string): string | undefined { + const evalMatch = location.match( + /eval at .*?\((.+):\d+:\d+\), :\d+:\d+$/, + ); + if (evalMatch) { + return evalMatch[1]; + } + + const directMatch = location.match(/^(.*):\d+:\d+$/); + if (directMatch && !directMatch[1].includes("")) { + return directMatch[1]; + } + + return undefined; +} + +function getRelevantStackLines(stack: string): string[] { + return stack + .split("\n") + .slice(1) + .map((line) => line.replace(/^\s*at\s+/, " at ")) + .filter((line) => line.trim().length > 0) + .filter((line) => !isJazzerInternalStackLine(line)); +} + +function isJazzerInternalStackLine(line: string): boolean { + const normalizedLine = line.replace(/\\/g, "/"); + return JAZZER_INTERNAL_STACK_MARKERS.some((marker) => + normalizedLine.includes(marker), + ); +} + +function matchesPattern(pattern: RegExp, value: string): boolean { + pattern.lastIndex = 0; + return pattern.test(value); +} + +function escapeRegex(value: string): string { + return value.replace(/[|\\{}()[\]^$+*?./]/g, "\\$&"); +} diff --git a/packages/bug-detectors/internal/remote-code-execution.ts b/packages/bug-detectors/internal/remote-code-execution.ts deleted file mode 100644 index fbe724e3..00000000 --- a/packages/bug-detectors/internal/remote-code-execution.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2026 Code Intelligence GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - guideTowardsContainment, - reportAndThrowFinding, -} from "@jazzer.js/core"; -import { callSiteId, registerBeforeHook } from "@jazzer.js/hooking"; - -const targetString = "jaz_zer"; - -registerBeforeHook( - "eval", - "", - false, - function beforeEvalHook(_thisPtr: unknown, params: string[], hookId: number) { - const code = params[0]; - // This check will prevent runtime TypeErrors should the user decide to call Function with - // non-string arguments. - // noinspection SuspiciousTypeOfGuard - if (typeof code === "string" && code.includes(targetString)) { - reportAndThrowFinding( - "Remote Code Execution\n" + ` using eval:\n '${code}'`, - ); - } - - // Since we do not hook eval using the hooking framework, we have to recompute the - // call site ID on every call to eval. This shouldn't be an issue, because eval is - // considered evil and should not be called too often, or even better -- not at all! - guideTowardsContainment(code, targetString, hookId); - }, -); - -registerBeforeHook( - "Function", - "", - false, - function beforeFunctionHook( - _thisPtr: unknown, - params: string[], - hookId: number, - ) { - if (params.length > 0) { - const functionBody = params[params.length - 1]; - - // noinspection SuspiciousTypeOfGuard - if (typeof functionBody === "string") { - if (functionBody.includes(targetString)) { - reportAndThrowFinding( - "Remote Code Execution\n" + - ` using Function:\n '${functionBody}'`, - ); - } - guideTowardsContainment(functionBody, targetString, hookId); - } - } - }, -); diff --git a/packages/core/cli.ts b/packages/core/cli.ts index 13ccdf5b..3ee9a879 100644 --- a/packages/core/cli.ts +++ b/packages/core/cli.ts @@ -160,6 +160,7 @@ yargs(process.argv.slice(2)) "bug detectors are enabled. To disable all, use the '.*' pattern." + "Following bug detectors are available: " + " command-injection\n" + + " code-injection\n" + " path-traversal\n" + " prototype-pollution\n", group: "Fuzzer:", diff --git a/packages/core/core.ts b/packages/core/core.ts index 4e7c0e8f..a865e898 100644 --- a/packages/core/core.ts +++ b/packages/core/core.ts @@ -199,9 +199,10 @@ function getFilteredBugDetectorPaths( const bugDetectorName = path.basename(bugDetectorPath, ".js"); // Checks in the global options if the bug detector should be loaded. - const shouldDisable = disablePatterns.some((pattern) => - pattern.test(bugDetectorName), - ); + const shouldDisable = disablePatterns.some((pattern) => { + pattern.lastIndex = 0; + return pattern.test(bugDetectorName); + }); if (shouldDisable) { console.error( diff --git a/tests/bug-detectors/code-injection.test.js b/tests/bug-detectors/code-injection.test.js new file mode 100644 index 00000000..96fce503 --- /dev/null +++ b/tests/bug-detectors/code-injection.test.js @@ -0,0 +1,197 @@ +/* + * Copyright 2026 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const path = require("path"); + +const { + FuzzTestBuilder, + FuzzingExitCode, + JestRegressionExitCode, +} = require("../helpers.js"); + +const bugDetectorDirectory = path.join(__dirname, "code-injection"); + +const accessFindingMessage = "Potential Code Injection (Canary Accessed)"; +const invocationFindingMessage = "Confirmed Code Injection (Canary Invoked)"; +const okMessage = "can be called just fine"; +let fuzzTestBuilder; + +beforeEach(() => { + fuzzTestBuilder = new FuzzTestBuilder() + .runs(0) + .dir(bugDetectorDirectory) + .sync(true); +}); + +describe("CLI", () => { + const accessFindingCases = [ + "evalAccessesCanary", + "evalIndirectAccessesCanary", + "evalCommaOperatorAccessesCanary", + "evalOptionalChainingAccessesCanary", + "heuristicReadAccessesCanary", + "functionAccessesCanary", + "functionNewAccessesCanary", + "functionWithArgAccessesCanary", + "functionStringCoercibleAccessesCanary", + ]; + + const noFindingCases = [ + "evalSafeCode", + "evalTargetInStringLiteral", + "functionSafeCode", + "functionSafeCodeNew", + "functionTargetInArgName", + "functionTargetInStringLiteral", + "functionStringCoercibleSafe", + ]; + + for (const entryPoint of accessFindingCases) { + it(`${entryPoint} reports potential code injection`, () => { + const fuzzTest = fuzzTestBuilder.fuzzEntryPoint(entryPoint).build(); + expect(() => { + fuzzTest.execute(); + }).toThrow(FuzzingExitCode); + expect(fuzzTest.stderr).toContain(accessFindingMessage); + }); + } + + for (const entryPoint of noFindingCases) { + it(`${entryPoint} stays quiet`, () => { + const fuzzTest = fuzzTestBuilder + .fuzzEntryPoint(entryPoint) + .build() + .execute(); + expect(fuzzTest.stdout).toContain(okMessage); + }); + } + + it("prints a copy-paste access suppression snippet", () => { + const fuzzTest = fuzzTestBuilder + .fuzzEntryPoint("heuristicReadAccessesCanary") + .build(); + expect(() => { + fuzzTest.execute(); + }).toThrow(FuzzingExitCode); + expect(fuzzTest.stderr).toContain( + 'getBugDetectorConfiguration("code-injection")', + ); + expect(fuzzTest.stderr).toContain(".ignoreAccess({"); + expect(fuzzTest.stderr).toContain( + "filePattern: /code-injection[\\\\/]fuzz\\.js$/", + ); + expect(fuzzTest.stderr).toContain( + "functionPattern: /^module\\.exports\\.heuristicReadAccessesCanary$/", + ); + }); + + it("reports confirmed invocation when access reporting is disabled", () => { + const fuzzTest = fuzzTestBuilder + .fuzzEntryPoint("evalAccessesCanary") + .customHooks(["disable-access.config.js"]) + .build(); + expect(() => { + fuzzTest.execute(); + }).toThrow(FuzzingExitCode); + expect(fuzzTest.stderr).toContain(invocationFindingMessage); + expect(fuzzTest.stderr).toContain(".ignoreInvocation({"); + }); + + it("suppresses heuristic access when file and function both match", () => { + const fuzzTest = fuzzTestBuilder + .fuzzEntryPoint("heuristicReadAccessesCanary") + .customHooks(["ignore-access-by-frame.config.js"]) + .build() + .execute(); + expect(fuzzTest.stdout).toContain(okMessage); + expect(fuzzTest.stderr).not.toContain(accessFindingMessage); + }); + + it("reaches invocation reporting when access is ignored by stack pattern", () => { + const fuzzTest = fuzzTestBuilder + .fuzzEntryPoint("evalAccessesCanary") + .customHooks(["ignore-access-by-stack.config.js"]) + .build(); + expect(() => { + fuzzTest.execute(); + }).toThrow(FuzzingExitCode); + expect(fuzzTest.stderr).toContain(invocationFindingMessage); + }); + + it("suppresses invocation when the invocation rule matches", () => { + const fuzzTest = fuzzTestBuilder + .fuzzEntryPoint("evalAccessesCanary") + .customHooks(["ignore-invocation.config.js"]) + .build(); + fuzzTest.execute(); + expect(fuzzTest.stderr).not.toContain(accessFindingMessage); + expect(fuzzTest.stderr).not.toContain(invocationFindingMessage); + }); + + it("Function.prototype should still exist", () => { + const fuzzTest = fuzzTestBuilder + .dryRun(false) + .fuzzEntryPoint("functionPrototypeExists") + .build(); + fuzzTest.execute(); + }); +}); + +describe("Jest", () => { + it("reports potential access", () => { + const fuzzTest = fuzzTestBuilder + .dryRun(false) + .jestTestFile("tests.fuzz.js") + .jestTestName("eval Accesses canary$") + .build(); + expect(() => { + fuzzTest.execute(); + }).toThrow(JestRegressionExitCode); + expect(fuzzTest.stderr).toContain(accessFindingMessage); + }); + + it("reports confirmed invocation when access reporting is disabled", () => { + const fuzzTest = fuzzTestBuilder + .dryRun(false) + .jestTestFile("tests.fuzz.js") + .jestTestName("eval Accesses canary$") + .customHooks(["disable-access.config.js"]) + .build(); + expect(() => { + fuzzTest.execute(); + }).toThrow(JestRegressionExitCode); + expect(fuzzTest.stderr).toContain(invocationFindingMessage); + }); + + it("safe code stays quiet", () => { + const fuzzTest = fuzzTestBuilder + .dryRun(false) + .jestTestFile("tests.fuzz.js") + .jestTestName("eval Safe code - no error$") + .build() + .execute(); + expect(fuzzTest.stdout).toContain(okMessage); + }); + + it("Function.prototype should still exist", () => { + const fuzzTest = fuzzTestBuilder + .dryRun(false) + .jestTestFile("tests.fuzz.js") + .jestTestName("Function Function.prototype still exists$") + .build(); + fuzzTest.execute(); + }); +}); diff --git a/tests/bug-detectors/code-injection/disable-access.config.js b/tests/bug-detectors/code-injection/disable-access.config.js new file mode 100644 index 00000000..30e80836 --- /dev/null +++ b/tests/bug-detectors/code-injection/disable-access.config.js @@ -0,0 +1,21 @@ +/* + * Copyright 2026 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { + getBugDetectorConfiguration, +} = require("../../../packages/bug-detectors"); + +getBugDetectorConfiguration("code-injection")?.disableAccessReporting(); diff --git a/tests/bug-detectors/code-injection/fuzz.js b/tests/bug-detectors/code-injection/fuzz.js new file mode 100644 index 00000000..81e0d64c --- /dev/null +++ b/tests/bug-detectors/code-injection/fuzz.js @@ -0,0 +1,94 @@ +/* + * Copyright 2026 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// --- eval / Function: should report potential access by default --- + +module.exports.evalAccessesCanary = function (_data) { + eval("jaz_zer()"); +}; + +module.exports.evalIndirectAccessesCanary = function (_data) { + const indirectEval = eval; + indirectEval("jaz_zer()"); +}; + +module.exports.evalCommaOperatorAccessesCanary = function (_data) { + (0, eval)("jaz_zer()"); +}; + +module.exports.evalOptionalChainingAccessesCanary = function (_data) { + eval?.("jaz_zer()"); +}; + +module.exports.heuristicReadAccessesCanary = function (_data) { + const propertyName = "jaz_zer"; + void globalThis[propertyName]; + console.log("can be called just fine"); +}; + +module.exports.functionAccessesCanary = function (_data) { + Function("jaz_zer()")(); +}; + +module.exports.functionNewAccessesCanary = function (_data) { + new Function("jaz_zer()")(); +}; + +module.exports.functionWithArgAccessesCanary = function (_data) { + new Function("value", "jaz_zer()")("_"); +}; + +module.exports.functionStringCoercibleAccessesCanary = function (_data) { + const body = { toString: () => "jaz_zer()" }; + Function(body)(); +}; + +// --- eval / Function: should not trigger --- + +module.exports.evalSafeCode = function (_data) { + eval("const a = 10; const b = 20; console.log('can be called just fine')"); +}; + +module.exports.evalTargetInStringLiteral = function (_data) { + eval("const x = 'jaz_zer'; console.log('can be called just fine')"); +}; + +module.exports.functionSafeCode = function (_data) { + Function("console.log('can be called just fine')")(); +}; + +module.exports.functionSafeCodeNew = function (_data) { + new Function("console.log('can be called just fine')")(); +}; + +module.exports.functionTargetInArgName = function (_data) { + new Function("jaz_zer", "console.log('can be called just fine')")("_"); +}; + +module.exports.functionTargetInStringLiteral = function (_data) { + new Function("const x = 'jaz_zer'; console.log('can be called just fine')")(); +}; + +module.exports.functionStringCoercibleSafe = function (_data) { + const body = { + toString: () => "console.log('can be called just fine')", + }; + Function(body)(); +}; + +module.exports.functionPrototypeExists = function (_data) { + console.log(Function.prototype.call.bind); +}; diff --git a/tests/bug-detectors/code-injection/ignore-access-by-frame.config.js b/tests/bug-detectors/code-injection/ignore-access-by-frame.config.js new file mode 100644 index 00000000..12b692fb --- /dev/null +++ b/tests/bug-detectors/code-injection/ignore-access-by-frame.config.js @@ -0,0 +1,24 @@ +/* + * Copyright 2026 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { + getBugDetectorConfiguration, +} = require("../../../packages/bug-detectors"); + +getBugDetectorConfiguration("code-injection")?.ignoreAccess({ + filePattern: /code-injection[\\/]fuzz\.js$/, + functionPattern: /^module\.exports\.heuristicReadAccessesCanary$/, +}); diff --git a/tests/bug-detectors/code-injection/ignore-access-by-stack.config.js b/tests/bug-detectors/code-injection/ignore-access-by-stack.config.js new file mode 100644 index 00000000..5e54927b --- /dev/null +++ b/tests/bug-detectors/code-injection/ignore-access-by-stack.config.js @@ -0,0 +1,23 @@ +/* + * Copyright 2026 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { + getBugDetectorConfiguration, +} = require("../../../packages/bug-detectors"); + +getBugDetectorConfiguration("code-injection")?.ignoreAccess({ + stackPattern: /evalAccessesCanary/, +}); diff --git a/tests/bug-detectors/code-injection/ignore-invocation.config.js b/tests/bug-detectors/code-injection/ignore-invocation.config.js new file mode 100644 index 00000000..4d610abb --- /dev/null +++ b/tests/bug-detectors/code-injection/ignore-invocation.config.js @@ -0,0 +1,25 @@ +/* + * Copyright 2026 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const { + getBugDetectorConfiguration, +} = require("../../../packages/bug-detectors"); + +getBugDetectorConfiguration("code-injection") + ?.disableAccessReporting() + ?.ignoreInvocation({ + functionPattern: /^eval$/, + }); diff --git a/tests/bug-detectors/remote-code-execution/package.json b/tests/bug-detectors/code-injection/package.json similarity index 78% rename from tests/bug-detectors/remote-code-execution/package.json rename to tests/bug-detectors/code-injection/package.json index fcb22af7..3deb157a 100644 --- a/tests/bug-detectors/remote-code-execution/package.json +++ b/tests/bug-detectors/code-injection/package.json @@ -1,7 +1,7 @@ { - "name": "jazzerjs-remote-code-execution-tests", + "name": "jazzerjs-code-injection-tests", "version": "1.0.0", - "description": "Tests for the Remote Code Execution bug detector", + "description": "Tests for the Code Injection bug detector", "scripts": { "test": "jest", "fuzz": "JAZZER_FUZZ=1 jest" diff --git a/tests/bug-detectors/code-injection/tests.fuzz.js b/tests/bug-detectors/code-injection/tests.fuzz.js new file mode 100644 index 00000000..fc18a4e5 --- /dev/null +++ b/tests/bug-detectors/code-injection/tests.fuzz.js @@ -0,0 +1,45 @@ +/* + * Copyright 2026 Code Intelligence GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const tests = require("./fuzz"); + +describe("eval", () => { + it.fuzz("Accesses canary", (data) => { + tests.evalAccessesCanary(data); + }); + + it.fuzz("Safe code - no error", (data) => { + tests.evalSafeCode(data); + }); + + it.fuzz("Target in string literal - no error", (data) => { + tests.evalTargetInStringLiteral(data); + }); +}); + +describe("Function", () => { + it.fuzz("Accesses canary", (data) => { + tests.functionAccessesCanary(data); + }); + + it.fuzz("Safe code - no error", (data) => { + tests.functionSafeCode(data); + }); + + it.fuzz("Function.prototype still exists", (data) => { + tests.functionPrototypeExists(data); + }); +}); diff --git a/tests/bug-detectors/remote-code-execution.test.js b/tests/bug-detectors/remote-code-execution.test.js deleted file mode 100644 index 9d251eb3..00000000 --- a/tests/bug-detectors/remote-code-execution.test.js +++ /dev/null @@ -1,303 +0,0 @@ -/* - * Copyright 2026 Code Intelligence GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const path = require("path"); - -const { - FuzzTestBuilder, - FuzzingExitCode, - JestRegressionExitCode, -} = require("../helpers.js"); - -const bugDetectorDirectory = path.join(__dirname, "remote-code-execution"); - -const findingMessage = "Remote Code Execution"; -const okMessage = "can be called just fine"; -let fuzzTestBuilder; - -beforeEach(() => { - fuzzTestBuilder = new FuzzTestBuilder() - .runs(0) - .dir(bugDetectorDirectory) - .sync(true); -}); - -describe("CLI", () => { - describe("eval ()", () => { - it("Invocation without error", () => { - const fuzzTest = fuzzTestBuilder - .fuzzEntryPoint("invocationWithoutError") - .build() - .execute(); - expect(fuzzTest.stdout).toContain(okMessage); - }); - - it("Direct invocation", () => { - const fuzzTest = fuzzTestBuilder - .fuzzEntryPoint("directInvocation") - .sync(true) - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(FuzzingExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - - it("Indirect invocation", () => { - const fuzzTest = fuzzTestBuilder - .fuzzEntryPoint("indirectInvocation") - .sync(true) - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(FuzzingExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - - it("Indirect invocation using comma operator", () => { - const fuzzTest = fuzzTestBuilder - .fuzzEntryPoint("indirectInvocationUsingCommaOperator") - .sync(true) - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(FuzzingExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - - it("Indirect invocation through optional chaining", () => { - const fuzzTest = fuzzTestBuilder - .fuzzEntryPoint("indirectInvocationThroughOptionalChaining") - .sync(true) - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(FuzzingExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - }); - - describe("Function constructor", () => { - it("Invocation without error, without explicit constructor", () => { - const fuzzTest = fuzzTestBuilder - .fuzzEntryPoint("functionNoErrorNoConstructor") - .sync(true) - .build() - .execute(); - expect(fuzzTest.stdout).toContain(okMessage); - }); - - it("Invocation without error", () => { - const fuzzTest = fuzzTestBuilder - .fuzzEntryPoint("functionNoErrorWithConstructor") - .sync(true) - .build() - .execute(); - expect(fuzzTest.stdout).toContain(okMessage); - }); - - it("Direct invocation", () => { - const fuzzTest = fuzzTestBuilder - .fuzzEntryPoint("functionError") - .sync(true) - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(FuzzingExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - - it("Direct invocation using new", () => { - const fuzzTest = fuzzTestBuilder - .fuzzEntryPoint("functionErrorNew") - .sync(true) - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(FuzzingExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - - it("Target string in variable name - no error", () => { - const fuzzTest = fuzzTestBuilder - .fuzzEntryPoint("functionWithArgNoError") - .sync(true) - .build() - .execute(); - expect(fuzzTest.stdout).toContain(okMessage); - }); - - it("With error - target string in last arg", () => { - const fuzzTest = fuzzTestBuilder - .fuzzEntryPoint("functionWithArgError") - .sync(true) - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(FuzzingExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - - it("Function.prototype should still exist", () => { - const fuzzTest = fuzzTestBuilder - .dryRun(false) - .fuzzEntryPoint("functionPrototypeExists") - .sync(true) - .build(); - fuzzTest.execute(); - }); - }); -}); - -describe("Jest", () => { - describe("eval", () => { - it("Direct invocation", () => { - const fuzzTest = fuzzTestBuilder - .dryRun(false) - .jestTestFile("tests.fuzz.js") - .jestTestName("eval Direct invocation$") - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(JestRegressionExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - - it("Indirect invocation", () => { - const fuzzTest = fuzzTestBuilder - .dryRun(false) - .jestTestFile("tests.fuzz.js") - .jestTestName("eval Indirect invocation$") - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(JestRegressionExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - - it("Indirect invocation using comma operator", () => { - const fuzzTest = fuzzTestBuilder - .dryRun(false) - .jestTestFile("tests.fuzz.js") - .jestTestName("eval Indirect invocation using comma operator$") - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(JestRegressionExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - - it("Indirect invocation using optional chaining", () => { - const fuzzTest = fuzzTestBuilder - .dryRun(false) - .verbose(true) - .jestTestFile("tests.fuzz.js") - .jestTestName("eval Indirect invocation through optional chaining$") - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(JestRegressionExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - - it("No error with absence of the target string", () => { - const fuzzTest = fuzzTestBuilder - .dryRun(false) - .jestTestFile("tests.fuzz.js") - .jestTestName("eval No error$") - .build() - .execute(); - expect(fuzzTest.stdout).toContain(okMessage); - }); - }); - - describe("Function constructor", () => { - it("No error", () => { - const fuzzTest = fuzzTestBuilder - .dryRun(false) - .jestTestFile("tests.fuzz.js") - .jestTestName("Function No error$") - .build(); - fuzzTest.execute(); - expect(fuzzTest.stdout).toContain(okMessage); - }); - - it("No error with constructor", () => { - const fuzzTest = fuzzTestBuilder - .dryRun(false) - .jestTestFile("tests.fuzz.js") - .jestTestName("Function No error with constructor$") - .build(); - fuzzTest.execute(); - expect(fuzzTest.stdout).toContain(okMessage); - }); - - it("With error", () => { - const fuzzTest = fuzzTestBuilder - .dryRun(false) - .jestTestFile("tests.fuzz.js") - .jestTestName("Function With error$") - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(JestRegressionExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - - it("With error with constructor", () => { - const fuzzTest = fuzzTestBuilder - .dryRun(false) - .jestTestFile("tests.fuzz.js") - .jestTestName("Function With error with constructor$") - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(JestRegressionExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - - it("Variable name containing target string should not throw", () => { - const fuzzTest = fuzzTestBuilder - .dryRun(false) - .jestTestFile("tests.fuzz.js") - .jestTestName("Function Target string in variable name - no error$") - .build() - .execute(); - expect(fuzzTest.stdout).toContain(okMessage); - }); - - it("With variable, body contains target string - should throw", () => { - const fuzzTest = fuzzTestBuilder - .dryRun(false) - .jestTestFile("tests.fuzz.js") - .jestTestName("Function With error - target string in last arg$") - .build(); - expect(() => { - fuzzTest.execute(); - }).toThrow(JestRegressionExitCode); - expect(fuzzTest.stderr).toContain(findingMessage); - }); - - it("Function.prototype should still exist", () => { - const fuzzTest = fuzzTestBuilder - .dryRun(false) - .jestTestFile("tests.fuzz.js") - .jestTestName("Function.prototype still exists$") - .build(); - fuzzTest.execute(); - }); - }); -}); diff --git a/tests/bug-detectors/remote-code-execution/fuzz.js b/tests/bug-detectors/remote-code-execution/fuzz.js deleted file mode 100644 index daab6b95..00000000 --- a/tests/bug-detectors/remote-code-execution/fuzz.js +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2026 Code Intelligence GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const printOkMessage = "console.log('can be called just fine')"; - -// eval -module.exports.invocationWithoutError = function (data) { - eval("const a = 10; const b = 20;" + printOkMessage); -}; - -module.exports.directInvocation = function (data) { - eval("const jaz_zer = 10;"); -}; - -module.exports.indirectInvocation = function (data) { - const a = eval; - a("const jaz_zer = 10;"); -}; - -module.exports.indirectInvocationUsingCommaOperator = function (data) { - (0, eval)("const jaz_zer = 10;"); -}; - -module.exports.indirectInvocationThroughOptionalChaining = function (data) { - eval?.("const jaz_zer = 10;"); -}; - -// Function -module.exports.functionNoErrorNoConstructor = function (data) { - Function("const a = 10; const b = 20;" + printOkMessage)(); -}; - -module.exports.functionNoErrorWithConstructor = function (data) { - const fn = new Function("const a = 10; const b = 20;" + printOkMessage); - fn(); -}; - -module.exports.functionError = function (data) { - Function("const jaz_zer = 10;"); -}; - -module.exports.functionErrorNew = function (data) { - new Function("const jaz_zer = 10;")(); -}; - -module.exports.functionWithArgNoError = function (data) { - new Function( - "jaz_zer", - "const foo = 10; console.log('Function can be called just fine')", - )("_"); -}; - -module.exports.functionWithArgError = function (data) { - new Function("foo", "const jaz_zer = 10;")("_"); -}; - -module.exports.functionPrototypeExists = function (data) { - console.log(Function.prototype.call.bind); -}; diff --git a/tests/bug-detectors/remote-code-execution/tests.fuzz.js b/tests/bug-detectors/remote-code-execution/tests.fuzz.js deleted file mode 100644 index a7a7a3a8..00000000 --- a/tests/bug-detectors/remote-code-execution/tests.fuzz.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2026 Code Intelligence GmbH - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -const tests = require("./fuzz"); - -describe("eval", () => { - it.fuzz("No error", (data) => { - tests.invocationWithoutError(data); - }); - - it.fuzz("Direct invocation", (data) => { - tests.directInvocation(data); - }); - - it.fuzz("Indirect invocation", (data) => { - tests.indirectInvocation(data); - }); - - it.fuzz("Indirect invocation using comma operator", (data) => { - tests.indirectInvocationUsingCommaOperator(data); - }); - - it.fuzz("Indirect invocation through optional chaining", (data) => { - tests.indirectInvocationThroughOptionalChaining(data); - }); -}); - -describe("Function", () => { - it.fuzz("No error", (data) => { - tests.functionNoErrorNoConstructor(); - }); - it.fuzz("No error with constructor", (data) => { - tests.functionNoErrorWithConstructor(data); - }); - - it.fuzz("With error", (data) => { - tests.functionError(data); - }); - - it.fuzz("With error with constructor", (data) => { - tests.functionErrorNew(data); - }); - - it.fuzz("Target string in variable name - no error", (data) => { - tests.functionWithArgNoError(data); - }); - - it.fuzz("With error - target string in last arg", (data) => { - tests.functionWithArgError(data); - }); - - it.fuzz("Function.prototype still exists", (data) => { - tests.functionPrototypeExists(data); - }); -});