@@ -25,13 +25,91 @@ import {
2525} from "@jazzer.js/core" ;
2626import { registerBeforeHook } from "@jazzer.js/hooking" ;
2727
28+ import { bugDetectorConfigurations } from "../configuration" ;
2829import { ensureCanary } from "../shared/code-injection-canary" ;
30+ import {
31+ buildGenericSuppressionSnippet ,
32+ captureStack ,
33+ getUserFacingStackLines ,
34+ IgnoreList ,
35+ type IgnoreRule ,
36+ } from "../shared/finding-suppression" ;
37+
38+ export type { IgnoreRule } from "../shared/finding-suppression" ;
2939
3040type PendingAccess = {
3141 canaryName : string ;
42+ stack : string ;
3243 invoked : boolean ;
3344} ;
3445
46+ /**
47+ * Configuration for the Code Injection bug detector.
48+ * Controls the reporting and suppression of dynamic code evaluation findings.
49+ */
50+ export interface CodeInjectionConfig {
51+ /**
52+ * Disables Stage 1 (Access) reporting entirely.
53+ * The detector will no longer report when the canary is merely read.
54+ */
55+ disableAccessReporting ( ) : this;
56+ /**
57+ * Disables Stage 2 (Invocation) reporting entirely.
58+ * The detector will no longer report when the canary is actually executed.
59+ */
60+ disableInvocationReporting ( ) : this;
61+ /**
62+ * Suppresses Stage 1 (Access) findings that match the provided rule.
63+ * Use this to silence safe heuristic reads such as template lookups.
64+ */
65+ ignoreAccess ( rule : IgnoreRule ) : this;
66+ /**
67+ * Suppresses Stage 2 (Invocation) findings that match the provided rule.
68+ * Use this only for known-safe execution sinks in test environments.
69+ */
70+ ignoreInvocation ( rule : IgnoreRule ) : this;
71+ }
72+
73+ class CodeInjectionConfigImpl implements CodeInjectionConfig {
74+ private _reportAccess = true ;
75+ private _reportInvocation = true ;
76+ private readonly _ignoredAccessRules = new IgnoreList ( ) ;
77+ private readonly _ignoredInvocationRules = new IgnoreList ( ) ;
78+
79+ disableAccessReporting ( ) : this {
80+ this . _reportAccess = false ;
81+ return this ;
82+ }
83+
84+ disableInvocationReporting ( ) : this {
85+ this . _reportInvocation = false ;
86+ return this ;
87+ }
88+
89+ ignoreAccess ( rule : IgnoreRule ) : this {
90+ this . _ignoredAccessRules . add ( rule ) ;
91+ return this ;
92+ }
93+
94+ ignoreInvocation ( rule : IgnoreRule ) : this {
95+ this . _ignoredInvocationRules . add ( rule ) ;
96+ return this ;
97+ }
98+
99+ shouldReportAccess ( stack : string ) : boolean {
100+ return this . _reportAccess && ! this . _ignoredAccessRules . matches ( stack ) ;
101+ }
102+
103+ shouldReportInvocation ( stack : string ) : boolean {
104+ return (
105+ this . _reportInvocation && ! this . _ignoredInvocationRules . matches ( stack )
106+ ) ;
107+ }
108+ }
109+
110+ const config = new CodeInjectionConfigImpl ( ) ;
111+ bugDetectorConfigurations . set ( "code-injection" , config ) ;
112+
35113const canaryCache = new WeakMap < object , string > ( ) ;
36114const pendingAccesses : PendingAccess [ ] = [ ] ;
37115
@@ -103,18 +181,35 @@ function ensureActiveCanary(): string {
103181function createCanaryDescriptor ( canaryName : string ) : PropertyDescriptor {
104182 return {
105183 get ( ) {
106- const pendingAccess = { canaryName, invoked : false } ;
107- pendingAccesses . push ( pendingAccess ) ;
184+ const accessStack = captureStack ( ) ;
185+ const pendingAccess = config . shouldReportAccess ( accessStack )
186+ ? {
187+ canaryName,
188+ stack : accessStack ,
189+ invoked : false ,
190+ }
191+ : undefined ;
192+ if ( pendingAccess ) {
193+ pendingAccesses . push ( pendingAccess ) ;
194+ }
108195
109196 return function canaryCall ( ) {
110- pendingAccess . invoked = true ;
111- reportAndThrowFinding (
112- buildFindingMessage (
113- "Confirmed Code Injection (Canary Invoked)" ,
114- `invoked canary: ${ canaryName } ` ,
115- ) ,
116- false ,
117- ) ;
197+ const invocationStack = captureStack ( ) ;
198+ if ( config . shouldReportInvocation ( invocationStack ) ) {
199+ if ( pendingAccess ) {
200+ pendingAccess . invoked = true ;
201+ }
202+ reportAndThrowFinding (
203+ buildFindingMessage (
204+ "Confirmed Code Injection (Canary Invoked)" ,
205+ `invoked canary: ${ canaryName } ` ,
206+ invocationStack ,
207+ "ignoreInvocation" ,
208+ "If this execution sink is expected in your test environment, suppress it:" ,
209+ ) ,
210+ false ,
211+ ) ;
212+ }
118213 } ;
119214 } ,
120215 enumerable : false ,
@@ -131,12 +226,33 @@ function flushPendingAccesses(): void {
131226 buildFindingMessage (
132227 "Potential Code Injection (Canary Accessed)" ,
133228 `accessed canary: ${ pendingAccess . canaryName } ` ,
229+ pendingAccess . stack ,
230+ "ignoreAccess" ,
231+ "If this is a safe heuristic read, suppress it to continue fuzzing for code execution. Add this to your custom hooks:" ,
134232 ) ,
135233 false ,
136234 ) ;
137235 }
138236}
139237
140- function buildFindingMessage ( title : string , action : string ) : string {
141- return `${ title } -- ${ action } ` ;
238+ function buildFindingMessage (
239+ title : string ,
240+ action : string ,
241+ stack : string ,
242+ suppressionMethod : "ignoreAccess" | "ignoreInvocation" ,
243+ hint : string ,
244+ ) : string {
245+ const relevantStackLines = getUserFacingStackLines ( stack ) ;
246+ const message = [ `${ title } -- ${ action } ` ] ;
247+ if ( relevantStackLines . length > 0 ) {
248+ message . push ( ...relevantStackLines ) ;
249+ }
250+ message . push (
251+ "" ,
252+ `[!] ${ hint } ` ,
253+ " Example only: copy/paste it and adapt `stackPattern` to your needs." ,
254+ "" ,
255+ buildGenericSuppressionSnippet ( "code-injection" , suppressionMethod ) ,
256+ ) ;
257+ return message . join ( "\n" ) ;
142258}
0 commit comments