Security
Fixing Cors Misconfiguration without guessing
Stop pasting Access-Control-Allow-Origin wildcards from memory. Confirm the public response grant, tighten the allowlist on the real gateway, and verify with the same Origin probe.
· by Henry Smith

I still catch myself about to paste Access-Control-Allow-Origin: * into a gateway "just to unblock the frontend." That habit fixes the SPA console and creates the finding you will meet again. Guessing header values from memory is how misconfigurations return after the weekend.
SlaySlop's CORS misconfiguration check "Checks whether cross-origin resource sharing allows untrusted sites to read responses." Fixing without guessing means reproducing that grant on a host you control, changing the real allowlist, and verifying the same probe goes quiet for untrusted Origins.
Outcome you want
After the fix, intended front-end Origins still work. Untrusted Origins no longer receive a read grant on the sensitive routes you care about. A follow-up scan no longer flags the same CORS misconfiguration evidence on those resources. CSP and frame protection still get their own triage—you did not "fix Security" in one commit.
Boundaries before you edit
Read-only scanner. You change your own gateway, API framework config, or CDN rules. Permission required on anything you scan. Not a pentest. Not a license to test third-party APIs. CORS is one Security row beside CSP and frame protection.
Loading diagram.
Happy path: reproduce, tighten, verify
- Copy the affected URL from the finding evidence.
- Send an untrusted Origin and an intended Origin with curl.
- Confirm which config layer sets ACAO: app middleware, API gateway, CDN, reverse proxy.
- Change one layer at a time toward an explicit allowlist.
- Purge CDN cache if that layer was involved.
- Re-probe both Origins.
- Rescan the same host in SlaySlop.
# Permissioned verification loop against your API
API="https://api.example.com/v1/me"
probe() {
local origin="$1"
echo "Origin=$origin"
curl -sI -H "Origin: $origin" "$API" \
| awk 'BEGIN{IGNORECASE=1} /^HTTP|^access-control-|^vary:/{print}'
echo
}
probe "https://app.example.com"
probe "https://untrusted.example"I want the first probe to show an expected allowlist hit (or a deliberate non-CORS same-site design). I want the second probe to show no reflected untrusted grant. If credentials are required for the intended front end, name that front end explicitly—do not combine * with credentials and hope.
Illustrative allowlist shape in app config (language varies; idea does not):
allowed_origins:
- https://app.example.com
- https://admin.example.com
reflect_unlisted_origins: false
allow_credentials: trueShip the real syntax your stack uses. The point is explicit origins, not improvisation in a hot patch.
Pitfalls
Fixing staging only. Fixing OPTIONS only. Fixing the marketing site's nginx while the API lives on another host. Fixing by deleting all CORS headers so the SPA breaks, then re-adding * in a panic. I have done the panic version. It feels fast. It is not a fix.
Another trap: treating the finding as a reason to disable auth checks. CORS is not authn. Tightening ACAO does not replace login. Keep the tickets separate so reviewers do not merge them into one confused PR.
Verify both directions
After tightening, confirm the SPA still works from the intended Origin and that the untrusted probe stays unggranted. Teams often verify only the negative probe and ship a broken first-party app, or verify only the SPA and leave the reflection in place. Both directions, same hour, same commit hash in the ticket.
If a partner needs temporary access, issue a dated allowlist entry with an owner and a removal ticket already filed. Temporary without a ticket is permanent. I still reach for curl first when the removal date arrives.
When the finding is intentional
Sometimes a public, non-credentialed read grant is deliberate: open tile JSON, public status endpoints, CDN assets meant for many sites. Do not "fix" those into broken clients. Document the intent next to the route, confirm credentials are not enabled, and move on. The check asks whether untrusted sites can read responses; for some public resources the answer is supposed to be yes.
The guessing failure mode is treating every ACAO as a bug. The engineering failure mode is treating none of them as a bug. Read the resource. Then change config.
After deploy, wait for CDN TTL or purge explicitly before you declare victory. I have re-probed a cached edge, celebrated, then watched the finding persist until purge. Timing is part of the fix.
Related next step
After CORS is tight, glance at CSP and frame protection on the same public hosts so browser controls move together. Soft next read: CORS misconfiguration and Security and secrets. Keep scanning if the API allowlist changes often during launch week.