CORS misconfiguration modeling with CodeQL
Cross-origin resource sharing (CORS) misconfigurations are a rich source of authentication bypass vulnerabilities. When frameworks or homemade implementations set headers like Access-Control-Allow-Origin incorrectly, attackers can ride on a logged-in user's session or reach intranet services. Static analysis with CodeQL can catch these mistakes before they ship, provided the relevant frameworks are modeled first.
The technique applies beyond CORS. Modeling the structures and functions a framework uses for security-relevant settings lets CodeQL's existing queries reason about your codebase. For Go, that means understanding how its CORS middleware packages configure policies.
Starting with response headers
The most direct CORS setup is writing response headers manually. CodeQL already ships CORS queries for many languages that detect when the Access-Control-Allow-Origin and Access-Control-Allow-Credentials headers are present. To extend coverage to another framework, model the write operations that set these headers.
In Go's net/http, the Set method on headers is a typical target. The HTTP.qll module defines a HeaderWrite class that aggregates all such security-sensitive header writes. Subclassing it lets a new framework's writes participate in all existing header-related queries. Useful methods like getHeaderName and getHeaderValue make it straightforward to check for vulnerable values.
Two common failure modes matter. First, setting Access-Control-Allow-Origin to * without credentials exposes unauthenticated endpoints to any website. For locally hosted tools, that can be disastrous because every dangerous endpoint becomes reachable. More severe is reflecting the request's Origin header while also allowing Access-Control-Allow-Credentials: true. A malicious site can then make authenticated requests on the user's behalf, compromising the whole application.
To make queries precise, define classes that identify writes of specific headers. Extending HTTP::HeaderWrite, a subclass like AllowCredentialsHeaderWrite finds every write where the header name is Access-Control-Allow-Credentials. Credential-related checks in queries can then source from this class. Collecting all header-write paths in a framework is a solid first contribution to CodeQL's model of that framework.
Modeling CORS middleware frameworks
Most Go applications use a CORS middleware rather than raw headers. Common packages attach middleware in the router so every response gets the configured headers. Modeling these frameworks centers on the configuration struct and the methods that apply it. The typical flow is constructing a config and registering it with middleware via something like router.Use(cors.New(config)).
Consider a typical Gin CORS example:
config := cors.DefaultConfig()
config.AllowOrigins = []string{"https://example.com"}
config.AllowCredentials = true
router.Use(cors.New(config))
A CodeQL model for this must track the cors.Config structure. Modeling the type with SSAWithFields—a single-static-assignment representation that retains field information—enables tracking where the config variable flows. The getSourceVariable() method returns the variable holding the config, making it possible to follow writes to its fields.
Once a config variable is identified, find all the property assignments that affect the fields relevant to CORS security: AllowOrigins, AllowCredentials, and AllowOriginFunc. The model returns a GinConfig object that guarantees field writes observed in queries belong to the same configuration structure. This prevents false positives where one config has a vulnerable origin and another enables credentials; a row is interesting only when a single config combines both.
By having each framework's origin and credentials writes extend universal classes like UniversalOriginWrite and UniversalCredentialsWrite, a single misconfiguration query can consume models from multiple frameworks.
Queries for CORS vulnerabilities
CORS issues split into two families: those without credentials, where * or null origins are a problem, and those with credentials, where origin reflection or null creates danger. The Go CORS query focuses on the credentialed case because it affects all applications. The query checks whether credentials are enabled and whether the origin comes from an untrusted remote source or is hardcoded as null.
To avoid false positives, the query requires that no string-comparison guard sits between the remote source and the header write. The predicate allowCredentialsIsSetToTrue performs the credential evaluation. It first examines header writes via AllowCredentialsHeaderWrite; if no header writes exist, it switches to framework models with UniversalAllowCredentialsWrite. Additionally, the not keyword filters out any config that writes * to origins, since that combination is not exploitable in the credentialed case. Similar predicates for flowsFromUntrustedToAllowOrigin and allowOriginIsNull confirm the origin is genuinely vulnerable.
A one-size-fits-all query won't work across frameworks because each middleware has different semantics. For instance, Gin CORS supports an AllowOriginFunc that effectively overrides AllowOrigins. A dedicated query could look for functions that always return true—high severity when combined with credentials. Framework-specific behaviors demand framework-specific checks.
Once a framework's security-relevant structures are modeled, CodeQL shifts from brittle pattern matching to flow-aware reasoning. This lowers the chance of CORS mistakes landing in production and makes it practical to scan large codebases at review time.



