CVE-2026-63407 Overview
CVE-2026-63407 is a permissive cross-origin resource sharing (CORS) misconfiguration [CWE-942] in the Grav API Plugin, a RESTful API that provides headless access to Grav CMS content. Versions prior to 1.0.0-rc.16 return Access-Control-Allow-Origin: * alongside permissive OPTIONS responses for authenticated /api/v1 endpoints. JavaScript running on any attacker-controlled origin can submit a stolen JSON Web Token (JWT) through the Authorization or X-API-Token header, read authenticated responses, and perform write operations with the token owner's privileges. The flaw enables data exfiltration and account modification when an attacker obtains a valid token.
Critical Impact
Cross-origin authenticated requests allow attackers to exfiltrate content and modify accounts by replaying obtained JWTs from arbitrary origins.
Affected Products
- Grav CMS API Plugin versions prior to 1.0.0-rc.16
- Grav sites exposing /api/v1 endpoints with the vulnerable CorsMiddleware
- Headless Grav deployments relying on JWT authentication
Discovery Timeline
- 2026-08-19 - CVE-2026-63407 published to NVD
- 2026-08-19 - Last updated in NVD database
Technical Details for CVE-2026-63407
Vulnerability Analysis
The Grav API Plugin's CorsMiddleware returns a wildcard Access-Control-Allow-Origin: * header on authenticated /api/v1 responses. The middleware also emits permissive preflight (OPTIONS) responses without validating the requesting origin. Because the plugin authenticates callers using JWT-bearing headers rather than cookies, browsers treat the credentialed cross-origin request as legitimate. Attacker-controlled JavaScript hosted on any domain can therefore read authenticated JSON responses and issue state-changing writes.
The vulnerability is compounded by the plugin honoring a ?token= query parameter fallback for JWT authentication on routes that were not restricted to file-streaming endpoints. This combination allows tokens leaked through URLs, referrers, or logs to be replayed from any origin.
Root Cause
The root cause is a permissive CORS policy [CWE-942] combined with a broadly accepted URL-based token fallback. The preflight handler did not receive the request context, so origin validation could not be enforced per-request.
Attack Vector
An attacker lures an authenticated Grav API user, or obtains a JWT through a secondary leak such as a referrer header or shared URL. The attacker then hosts JavaScript that issues cross-origin fetch calls to the target /api/v1 endpoints, attaching the token via the Authorization or X-API-Token header. Because the server replies with Access-Control-Allow-Origin: *, the browser exposes the response body to the attacker's script, enabling exfiltration and privileged writes.
// Security patch in classes/Api/ApiRouter.php
// Fix cross-origin account takeover via CORS wildcard + JWT in URL
// Handle CORS preflight
if ($request->getMethod() === 'OPTIONS') {
- return (new CorsMiddleware($this->config))->createPreflightResponse();
+ return (new CorsMiddleware($this->config))->createPreflightResponse($request);
}
// Require and apply Grav environment
Source: GitHub Commit 56ae2ca
// Security patch in classes/Api/Auth/JwtAuthenticator.php
// Restrict URL token fallback to file-streaming routes
class JwtAuthenticator implements AuthenticatorInterface
{
+ /**
+ * Path segments/suffixes on which the `?token=` URL fallback is honored.
+ * These are the only routes that stream a file body to a browser element
+ * that can't attach an auth header. See {@see isTokenQueryAllowed()}.
+ */
+ protected const TOKEN_QUERY_ROUTES = [
+ '/download', // e.g. /system/backups/{filename}/download
+ '/thumbnails', // e.g. /thumbnails/{file}
+ ];
+
public function __construct(
protected readonly Grav $grav,
protected readonly Config $config,
Source: GitHub Commit 56ae2ca
Detection Methods for CVE-2026-63407
Indicators of Compromise
- HTTP responses from /api/v1 endpoints containing Access-Control-Allow-Origin: * alongside Authorization or X-API-Token request headers.
- Successful authenticated OPTIONS preflight responses that accept arbitrary Origin values.
- Unexpected write operations (POST, PUT, DELETE) to /api/v1 from user agents whose Referer or Origin headers do not match approved application domains.
Detection Strategies
- Inspect web server and reverse proxy logs for cross-origin requests to /api/v1 with credentialed headers originating outside allowlisted domains.
- Monitor for JWTs appearing in URL query parameters (?token=) on routes other than /download or /thumbnails.
- Correlate account modification events with recent cross-origin API traffic bearing the same token identifier.
Monitoring Recommendations
- Enable verbose logging on the Grav API Plugin and forward events to a centralized analytics platform for correlation.
- Alert on repeated OPTIONS preflight requests to /api/v1 from unusual origins, which typically precede exploitation.
- Track JWT issuance and usage patterns to identify tokens replayed from multiple origins within short time windows.
How to Mitigate CVE-2026-63407
Immediate Actions Required
- Upgrade the Grav API Plugin to version 1.0.0-rc.16 or later without delay.
- Revoke and reissue any JWTs that may have been exposed through URLs, referrers, or third-party logs.
- Audit /api/v1 traffic since deployment for cross-origin write operations and unauthorized account changes.
Patch Information
The fix is released in Grav API Plugin 1.0.0-rc.16. The patch passes the request into CorsMiddleware::createPreflightResponse() so preflight responses can validate the Origin header, and it restricts the ?token= URL fallback to /download and /thumbnails routes. See the GitHub Release 1.0.0-rc.16 and the GitHub Security Advisory GHSA-93px-98wh-6fj2 for full details.
Workarounds
- Place the API behind a reverse proxy that strips or rewrites Access-Control-Allow-Origin to an explicit allowlist until the patch is applied.
- Enforce short JWT lifetimes and require rotation to limit the window during which a stolen token can be replayed cross-origin.
- Block OPTIONS and credentialed /api/v1 requests whose Origin header does not match the site's own domain at the WAF layer.
# Example nginx override enforcing an explicit CORS allowlist for /api/v1
location /api/v1/ {
set $cors_origin "";
if ($http_origin = "https://app.example.com") {
set $cors_origin $http_origin;
}
add_header Access-Control-Allow-Origin $cors_origin always;
add_header Vary Origin always;
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
add_header Access-Control-Allow-Headers "Authorization, X-API-Token, Content-Type" always;
return 204;
}
proxy_pass http://grav_backend;
}
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

