GHSA-3h6j-9x8m-rg3g — j0k3r/graby
Fix: j0k3r/graby@0295d82GHSA-3h6j-9x8m-rg3g is a security vulnerability in j0k3r/graby. A fix is available for j0k3r/graby — see the affected versions and patch details below.
Graby has stored XSS via iframe srcdoc Attribute in htmLawed Sanitization Config
Real-World Exposure
j0k3r/grabyReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Packagist packages — download data is not available via public APIs for these ecosystems.
Description
Summary
Graby's cleanupXss() function configures htmLawed with conflicting settings: safe=1 (which removes <iframe>) combined with 'elements' => '*+iframe-meta' (which re-enables <iframe>). htmLawed does not sanitize the srcdoc attribute, allowing injection of arbitrary JavaScript that executes when the content is rendered via |raw in templates.
Root Cause
src/Graby.php lines 1038-1048:
htmLawed($html, [
'safe' => 1, // removes <iframe>
'elements' => '*+iframe-meta', // re-adds <iframe>, overrides safe=1
'deny_attribute' => 'style', // srcdoc is NOT denied
]);
The safe=1 and +iframe combination is a conflict: safe mode is designed to strip dangerous elements, but the elements override re-enables <iframe> without also blocking the dangerous srcdoc attribute.
Proof of Concept
Input to cleanupXss():
<iframe srcdoc="<script>alert(document.domain)</script>"></iframe>
Output (unchanged — htmLawed passes it through):
<iframe srcdoc="<script>alert(document.domain)</script>"></iframe>
When rendered via {{ content|raw }} in a template, srcdoc executes in an about:srcdoc frame with the same origin as the page. Confirmed via Puppeteer/Chromium headless: alert(document.domain) fires.
Validated on Wallabag (which uses Graby) via Docker: entry created via API with iframe-only content body triggers Readability failure → falls through to cleanupXss() path.
Impact
- Stored XSS in any application rendering Graby-sanitized content via
|raw - In Wallabag: affects both authenticated views and public share pages (unauthenticated)
- No CSP headers in default Wallabag config — no secondary mitigation
Suggested Fix
Either remove +iframe from the elements config to keep iframes blocked:
'elements' => '*-iframe-meta',
Or explicitly deny the srcdoc attribute:
'deny_attribute' => 'style srcdoc',
Credit
Discovered by @tikket1, 2026-03-25. Redirected from wallabag/wallabag advisory by @j0k3r.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 🐘Packagist | j0k3r/graby | all versions | 2.5.1composer require j0k3r/graby:^2.5.1 |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for j0k3r/graby, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update j0k3r/graby to 2.5.1 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms GHSA-3h6j-9x8m-rg3g is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like GHSA-3h6j-9x8m-rg3g can be triaged on real exposure rather than presence alone.
Tailored to GHSA-3h6j-9x8m-rg3g. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is GHSA-3h6j-9x8m-rg3g in your dependencies?
O3 Security finds GHSA-3h6j-9x8m-rg3g across Packagist dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.