GHSA-3h6j-9x8m-rg3g
GHSA-3h6j-9x8m-rg3g is a security vulnerability in j0k3r/graby. O3 Security confirms whether GHSA-3h6j-9x8m-rg3g is actually reachable in your code before you act, and blocks exploitation at runtime until you patch.
Graby has stored XSS via iframe srcdoc Attribute in htmLawed Sanitization Config
Blast Radius
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.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. O3's reachability analysis confirms whether the vulnerable code path is actually invoked in your application, so you act on real exposure instead of every transitive match.
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 pinpoints whether GHSA-3h6j-9x8m-rg3g is reachable in your code and exactly where to fix it, then blocks exploitation in production at runtime until the patched version is deployed.
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 detects GHSA-3h6j-9x8m-rg3g across Packagist dependencies and uses function-level reachability to confirm whether the vulnerable code path is actually reachable — not just present. No false positives.