The Webhook Server's Hidden CORS Problem
The src/webhook-server.mjs file sits at the edge of the application — it is the first thing the internet touches. It accepts incoming webhook payloads, routes them, and hands them off to internal plugin handlers. Because it is built on Hono, a lightweight Node.js web framework, it also inherits Hono's CORS middleware to manage cross-origin browser requests.
That inheritance came with a silent defect: CVE-2026-54290, a HIGH severity vulnerability in which Hono's CORS middleware reflects any Origin header supplied by the caller — including when credentials are involved — instead of validating it against a trusted list. Trivy's dependency scanner flagged hono@4.12.16 in package-lock.json as the carrier. The fix, merged via the PR fix: upgrade hono to 4.12.25 (CVE-2026-54290), upgrades the package to 4.12.34 and pins it so no transitive dependency can silently downgrade it.
The Vulnerability Explained
What CORS Is Supposed to Do
Cross-Origin Resource Sharing (CORS) is the browser mechanism that decides whether JavaScript running on https://evil.com is allowed to read a response from https://api.yourapp.com. The server signals its intent through response headers like:
Access-Control-Allow-Origin: https://trusted-partner.com
Access-Control-Allow-Credentials: true
When Access-Control-Allow-Origin names a specific origin and Access-Control-Allow-Credentials is true, the browser permits the cross-origin request and exposes the response body to the calling script — including cookies, auth tokens, and session data.
What Hono 4.12.16 Did Wrong
When the origin option in Hono's cors() middleware is left at its wildcard default, the vulnerable versions (up to and including 4.12.16) did not respond with a static *. Instead, they reflected the incoming Origin header verbatim back in Access-Control-Allow-Origin. The effective behavior looked like this:
-- Incoming request --
GET /webhooks/payload HTTP/1.1
Origin: https://evil.com
Cookie: session=abc123
-- Hono 4.12.16 response (vulnerable) --
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://evil.com ← reflected!
Access-Control-Allow-Credentials: true
Vary: Origin
The browser sees a specific origin match (not a wildcard) paired with credentials: true, so it considers the request legitimate and hands the full response body — including any session-scoped data — to the script on https://evil.com.
Why This Is Worse Than a Plain Wildcard
A plain Access-Control-Allow-Origin: * is bad, but browsers refuse to combine it with Access-Control-Allow-Credentials: true — that combination is explicitly forbidden by the CORS specification. Origin reflection sidesteps that browser protection entirely: because the value is a specific origin string (not *), the browser applies no such restriction, and credentialed cross-origin reads succeed.
Attack Scenario Against This Webhook Server
- An attacker registers
https://evil-webhooks.ioand hosts a page with:
fetch("https://api.yourapp.com/webhooks/payload", {
credentials: "include", // sends the victim's session cookie
method: "GET"
})
.then(r => r.json())
.then(data => exfiltrate(data)); // sends payload to attacker's server
- The victim visits
https://evil-webhooks.iowhile logged intoyourapp.com. - Hono
4.12.16receives the request, seesOrigin: https://evil-webhooks.io, and reflects it back. - The browser allows the read. The attacker now has the webhook payload, which may contain API keys, internal event data, or PII.
The Fix
What Changed in package-lock.json
The core change replaces the vulnerable resolved tarball with the patched one:
"node_modules/hono": {
- "version": "4.12.16",
- "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.16.tgz",
- "integrity": "sha512-jN0ZewiNAWSe5khM3EyCmBb250+b40wWbwNILNfEvq84VREWwOIkuUsFONk/3i3nqkz7Oe1PcpM2mwQEK2L9Kg==",
+ "version": "4.12.34",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.34.tgz",
+ "integrity": "sha512-GqXJqY/xJkJmuloTrnV1ZEXG3fqte+VjkUqoRNZXcrUidiUOP4fMSIHHY4tsqZBK++kVyWmt/AAfSUuy57/eSA==",
4.12.34 contains the upstream patch that makes the CORS middleware validate the Origin header against the configured allowlist rather than reflecting it blindly.
What Changed in package.json
An overrides entry was added to lock hono to the patched version across the entire dependency tree:
"overrides": {
- "fast-uri": "4.1.2"
+ "fast-uri": "4.1.2",
+ "hono": "4.12.34"
}
This is a critical companion change. Without the override, a transitive dependency that lists hono as a peer or direct dependency could cause npm to resolve a different (potentially older, vulnerable) version. The override acts as a version floor, ensuring 4.12.34 is used regardless of what any other package requests.
Before vs. After Behavior
| Scenario | Hono 4.12.16 (before) | Hono 4.12.34 (after) |
|---|---|---|
origin option not configured |
Reflects any Origin |
Returns * (no credentials) |
origin: '*' with credentials |
Reflects any Origin + credentials |
Rejects the combination per spec |
origin: ['https://trusted.com'] |
Works correctly | Works correctly |
Attacker-supplied Origin |
Reflected, credentials allowed | Not reflected, request blocked |
Prevention & Best Practices
1. Always Explicitly Configure origin in CORS Middleware
Never rely on defaults for security-sensitive middleware options. In Hono (and Express, Fastify, or any other framework), specify an explicit allowlist:
// ✅ Correct: explicit allowlist
app.use('/webhooks/*', cors({
origin: ['https://dashboard.yourapp.com', 'https://partner.example.com'],
credentials: true,
}));
// ❌ Dangerous: wildcard default with credentials
app.use('/webhooks/*', cors({
credentials: true,
// origin not set — vulnerable to reflection in older Hono
}));
2. Pin Security-Critical Dependencies with overrides
npm's overrides field (and Yarn's resolutions) lets you enforce a minimum safe version across your entire dependency tree. Use it for any package that touches request/response handling:
"overrides": {
"hono": ">=4.12.34"
}
3. Run a Dependency Vulnerability Scanner in CI
Trivy detected CVE-2026-54290 automatically. Add it (or a similar tool) to your CI pipeline so vulnerable dependencies are caught before they merge:
# .github/workflows/security.yml
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
scan-type: 'fs'
scan-ref: '.'
severity: 'HIGH,CRITICAL'
exit-code: '1'
4. Understand the CORS Specification Nuances
The CORS specification explicitly forbids Access-Control-Allow-Origin: * combined with Access-Control-Allow-Credentials: true. However, it does not forbid a reflected specific origin combined with credentials — that loophole is what this CVE exploits. Knowing this distinction helps you audit CORS configurations more effectively.
5. Relevant Security Standards
- CWE-942: Permissive Cross-domain Policy with Untrusted Domains
- OWASP A05:2021 – Security Misconfiguration (overly permissive CORS is a canonical example)
- OWASP CORS Cheat Sheet: recommends explicit origin allowlists and warns against reflection patterns
Key Takeaways
hono@4.12.16's default CORS behavior was exploitable: Theoriginwildcard default caused the middleware insrc/webhook-server.mjsto reflect any attacker-suppliedOriginheader, bypassing the browser's credential-with-wildcard protection.- Origin reflection is more dangerous than a plain wildcard: Because it produces a specific-origin response, browsers apply no credential restriction — making session hijacking via cross-origin reads trivially achievable.
- The
overridesentry inpackage.jsonis as important as thepackage-lock.jsonchange: Without it, transitive dependencies could silently re-introduce4.12.16. - Trivy caught this before it was exploited: Automated dependency scanning in the CI pipeline is what surfaced CVE-2026-54290 — not a manual code review or a production incident.
- Never leave CORS
originunconfigured in a production webhook server: Any endpoint that processes credentialed browser requests must enumerate its trusted origins explicitly.
How Orbis AppSec Detected This
- Source: The
OriginHTTP request header supplied by an untrusted browser or HTTP client - Sink: Hono's internal CORS middleware response-header writer in
node_modules/hono(version4.12.16), which copies theOriginvalue directly intoAccess-Control-Allow-Originwithout allowlist validation — affecting every route insrc/webhook-server.mjsthat uses thecors()middleware - Missing control: No explicit
originallowlist was configured; the middleware defaulted to reflection mode instead of rejecting or wildcarding unknown origins - CWE: CWE-942 — Permissive Cross-domain Policy with Untrusted Domains
- Fix:
honowas upgraded from4.12.16to4.12.34inpackage-lock.jsonand pinned via theoverridesfield inpackage.jsonto prevent transitive re-introduction of the vulnerable version
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
CVE-2026-54290 is a reminder that security vulnerabilities do not always live in the code you write — they live in the code you depend on. Hono's CORS middleware, left at its default configuration, silently transformed src/webhook-server.mjs into a credentialed cross-origin read target. The fix was two files and three lines: a version bump in package-lock.json and a version pin in package.json. The lesson is broader: treat CORS configuration as a security control, not a convenience setting; pin your dependencies; and let automated scanners do the work of watching your dependency tree so you can focus on building.