Passive/active script rules might lose their metadata
Nobody has claimed this yet.
- Dominant language
- Java
- Stars
- 15.8k
- Forks
- 2.6k
- Avg merge
- 13h 16m
- Merged PRs (30d)
- 17
Description
Describe the bug
The ScriptEngineCleaner introduced in graaljs 0.13.0 (fix for #9230) intermittently breaks getMetadata() for script-based scan rules. When the Cleaner fires between script registrations, it closes the polyglot Context, causing ScanRuleMetadata class initialization to fail permanently for the JVM process.
Scripts still execute — scanNode() and scan() work fine — but they fall back to the generic wrapper IDs (50001 for passive, 50000 for active) instead of registering with their declared rule IDs via getMetadata().
Root cause
ScriptEngineCleaner wraps GraalJSScriptEngine and uses java.lang.ref.Cleaner to track objects returned by eval(). When ZAP calls getMetadata() during script registration:
eval()returns aScanRuleMetadataobject — it becomes the only tracked reference- ZAP stores the metadata result and the reference goes out of scope
- GC collects it →
Cleanerdaemon thread fires →scriptEngine.close()→ polyglotContextdestroyed - JVM caches the class init failure as
ExceptionInInitializerError - All subsequent
Java.type("...ScanRuleMetadata")calls fail permanently withNoClassDefFoundError
The behavior is non-deterministic because it depends on GC/Cleaner timing:
- If all
getMetadata()calls complete before any Cleaner fires → all scripts register with real IDs ✓ - If a Cleaner fires between registrations → subsequent scripts fall back to wrapper IDs ✗
Steps to reproduce
- Start ZAP 2.17.0 in daemon mode with
-addoninstall communityScripts - Check script state:
GET /JSON/script/view/listScripts/ - Observe
Find Emails.js— sometimeserror: false, sometimeserror: truewithlastError: Could not initialize class org.zaproxy.addon.commonlib.scanrules.ScanRuleMetadata - When
error: true, passive scan alerts report underpluginId: 50001instead of the script's declared ID100009 - Restart and repeat — the error state is non-deterministic across runs
Expected behavior
Scripts with getMetadata() should consistently register with their declared rule IDs, regardless of GC timing.
Affected scripts
Any script using getMetadata() with Java.type("...ScanRuleMetadata"):
Find Emails.js(communityScripts, passive, ID 100009)SwaggerSecretDetector.js(openapi add-on, active, ID 100043)- All scripts following the GraalJS 0.6.0
getMetadata()template pattern
Software versions
- ZAP: 2.17.0
- GraalJS: 0.13.0 and 0.14.0 (both affected)
- commonlib: 1.39.0–1.40.0
- Scripts: 45.17.0–45.18.0
- OS: Linux (Docker
ghcr.io/zaproxy/zaproxy:2.17.0-bare)
Suggested fix
Similar approach to PR #9236 ("Prevent engine close in Check For ZAP Errors") which explicitly prevents the Cleaner from closing the engine for certain scripts.
For scan rule scripts specifically: when getMetadata() returns a non-null result, the engine should be kept alive (or the metadata result should not be tracked by the Cleaner) so that ScanRuleMetadata class initialization is not invalidated.
Related
- #9230 — Original memory leak issue
- zaproxy/zap-extensions#7088 — The
ScriptEngineCleanercommit (f15630f) - #9236 — Follow-up fix for a similar Cleaner side-effect (leaked script to prevent engine close)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the ScriptEngineCleaner behavior described in the issue and compare it with the approach in PR #9236, then reproduce registration using Find Emails.js or SwaggerSecretDetector.js. Done means getMetadata() remains reliable across registrations and scripts consistently use their declared rule IDs without ScanRuleMetadata initialization errors.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- java, javascript
- Domain
- security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100