Skip to content

[origins] Weak Referer / Origin validation

This check looks for common mistakes when using $http_origin or $http_referer to gate security behavior (CORS, clickjacking headers, etc.). It focuses on regex-based validation in if conditions and in map-based CORS allowlists that reflect an origin into a response header.

What it detects

Regex that can be bypassed to match an untrusted domain

Examples of bypasses this plugin tries to find:

  • Suffix injection: https://good.example.com.evil.com
  • Prefix injection: http://evil.com/?https://good.example.com
  • Scheme confusion (when you meant to require https): http://good.example.com

Invalid values that should never be treated as a valid Origin/Referer

The plugin reports patterns that accept values that are syntactically invalid for the header being validated:

  • Origin must be: <scheme>://<hostname>[:port] (no path, query, or fragment).
  • Referer should be an absolute URL including scheme and hostname.

It can also flag values that contain uppercase letters or unusual characters in the scheme/host.

Common header typo

$http_referrer is not a valid NGINX variable for the HTTP Referer header. The correct variable is $http_referer.

Why this is a problem

Origin and Referer are attacker-controlled request headers. If your config uses them to decide whether to:

  • set Access-Control-Allow-Origin,
  • set X-Frame-Options / Content-Security-Policy: frame-ancestors,
  • enable credentials (Access-Control-Allow-Credentials: true),

then a slightly-wrong regex can silently turn a strict policy into "trust anything that looks kind of right".

Regex allowlists are especially easy to get wrong when you combine:

  • alternation (|),
  • partial anchoring (^ without $, or vice versa),
  • match-any-character dots (.),
  • optional groups,
  • subdomain handling,
  • ports,
  • scheme handling.

What triggers a finding

You will typically see findings in these patterns:

if-based validation

# Intended: allow only yandex.ru
# Risk: also matches https://metrika-hacked-yandex.ru/
if ($http_referer !~ "^https://([^/])+metrika.*yandex\.ru/") {
    add_header X-Frame-Options SAMEORIGIN;
}

The above case is an example of a MEDIUM finding. The referer regex can be bypassed to match an untrusted domain.

# Invalid for Origin: origin cannot contain a path
if ($http_origin !~ "^https://yandex\.ru/$") {
    add_header X-Frame-Options SAMEORIGIN;
}

The above example is a LOW finding. The regex matches an invalid Origin (the Origin must not include a path, even /).

# Wrong variable name (typo)
if ($http_referrer !~ "^https://yandex\.ru/") {
    add_header X-Frame-Options SAMEORIGIN;
}

The above example is a HIGH finding. The config is using the wrong variable (referer vs. referrer).

map-based CORS allowlists that reflect an origin

The plugin also inspects this common pattern:

# Invalid origin reflected: matches subdomain5example.com
map $http_origin $allow_origin {
    default "";
    ~^https://subdomain.example.com$ $http_origin;
}

add_header Access-Control-Allow-Origin $allow_origin always;

If the map regex can be bypassed (or matches invalid Origin forms), you can end up reflecting a hostile origin.

Note: scanning of this pattern (defining access-control-allow-origin based on a map) occurs only when a full configuration is performed, i.e. when the configuration scanned includes an http { .. } block.

Bad configuration

# Intended to allow only yandex domains, but can also match:
# https://www.yandex.ru.evil.com
if ($http_origin ~* ((^https://www\.yandex\.ru)|(^https://ya\.ru)$)) {
    add_header Access-Control-Allow-Origin "$http_origin";
    add_header Access-Control-Allow-Credentials "true";
}

Common issues here:

  • alternation with uneven anchoring,
  • missing $ anchors,
  • reflecting $http_origin directly when the allowlist is not strict.

Safer configuration patterns

Prefer map with a strict allowlist and controlled reflection

map $http_origin $allow_origin {
    default "";

    # Allow example.com and any subdomain, optional port, https only.
    ~^https://([A-Za-z0-9-]+\.)?example\.com(?::[0-9]{1,5})?$ $http_origin;
}

add_header Access-Control-Allow-Origin $allow_origin always;
add_header Access-Control-Allow-Credentials "true" always;

This is better because:

  • only allowlisted origins are reflected,
  • everything else becomes an empty value,
  • the pattern is fully anchored and describes the full Origin syntax.

Keep Origin rules strict (Origin has no path)

If you need to validate Origin, always anchor the entire value, including any optional port.

Good structure to aim for:

  • ^https://
  • optional subdomain
  • exact registrable domain
  • optional :port
  • $

Notes for Referer validation

If your goal is anti-hotlinking or basic referer checks, consider using valid_referers (from ngx_http_referer_module, here) instead of hand-rolled regex in if. It is not perfect, but it is easier to audit than ad-hoc patterns.

Configuration

This plugin has a few knobs you can use to decide how strict you want it to be.

domains

You can use the domains option to define a trusted allowlist of registrable domains. If the regex can be bypassed to match a different domain, the plugin will flag it as insecure.

By default, this is *, which disables domain allowlisting checks.

CLI

# Only treat origins/referers under example.com and example.org as trusted
gixy --origins-domains "example.com,example.org"

Config

[origins]
; allowlist trusted registrable domains (use "*" to disable allowlisting)
domains = example.com,example.org

https-only

You can use the https-only option to require the https scheme. When enabled, patterns that allow http:// will be flagged as insecure.

By default, this is false.

CLI

# Only allow https origins/referers
gixy --origins-https-only true

Config

[origins]
; require https scheme in origins/referers
https-only = true

lower-hostname

You can use the lower-hostname option to enforce lowercase scheme/hostname expectations. When enabled, patterns that accept uppercase or unusual characters in the scheme/host are treated as invalid.

By default, this is true. Only disable this if you really know what you're doing (hostnames are nearly always case insensitive!)

CLI

# Disable lowercase validation
gixy --origins-lower-hostname false

Config

[origins]
; enforce lowercase scheme/hostname checks
lower-hostname = true

Additional notes

This plugin uses different severities depending on what it finds, and which header is involved. The logic is generally quite simple:

  • If the regex matches an insecure Origin, the severity is HIGH.
  • If the regex matches an invalid Origin or Referer, the severity is LOW.
  • If the regex matches an insecure Referer, the severity is MEDIUM.
  • If $http_referrer is used, the severity is HIGH.

The check for the invalid map-based CORS header is only performed when a full configuration scan occurs, i.e. when the configuration scanned includes an http { .. } block.