Best use case
SKILL.md - cors-headers is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
## What this skill covers
Teams using SKILL.md - cors-headers should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/cors-headers/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How SKILL.md - cors-headers Compares
| Feature / Agent | SKILL.md - cors-headers | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
## What this skill covers
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# SKILL.md - cors-headers
## What this skill covers
Complete reference for the HTTP headers involved in the CORS protocol, their semantics, how cors-tester reads them, and the analysis logic implemented in `src/tester.ts`.
## CORS request flow
```
Browser / cors-tester Server
| |
| OPTIONS /resource HTTP/1.1 |
| Origin: https://app.com |
| Access-Control-Request-Method: POST|
| Access-Control-Request-Headers: ... |
|------------------------------------>|
| |
| HTTP/1.1 204 No Content |
| Access-Control-Allow-Origin: ... |
| Access-Control-Allow-Methods: ... |
| Access-Control-Allow-Headers: ... |
| Access-Control-Max-Age: ... |
|<------------------------------------|
| |
| POST /resource HTTP/1.1 |
| Origin: https://app.com |
|<------------------------------------|
| HTTP/1.1 200 OK |
| Access-Control-Allow-Origin: ... |
| Vary: Origin, Accept-Encoding |
|<------------------------------------|
```
## Request headers (sent by cors-tester)
### Origin
Sent on every cross-origin request (preflight and actual).
```
Origin: https://app.example.com
```
- Value is `scheme://host[:port]`. No path, query string, or fragment.
- cors-tester always sends this header with the value provided via `--origin`.
### Access-Control-Request-Method
Sent on the OPTIONS preflight to declare the method the actual request will use.
```
Access-Control-Request-Method: POST
```
- cors-tester sets this to the value of `--method` (default: GET).
### Access-Control-Request-Headers
Sent on the OPTIONS preflight to declare headers the actual request will send.
```
Access-Control-Request-Headers: Authorization, Content-Type, X-Request-ID
```
- cors-tester sets this from `--headers` (comma-separated).
- Omitted entirely if no `--headers` flag is provided.
## Response headers (checked by cors-tester)
### Access-Control-Allow-Origin
The most important CORS header. Must be present and match the requesting origin.
```
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Origin: *
```
| Value | Meaning | cors-tester status |
|-------|---------|-------------------|
| Specific origin matching request | Allowed for this origin | `allowed` |
| `*` (wildcard) | Any origin allowed | `allowed` with `WILDCARD_ORIGIN` warning |
| Specific origin not matching request | Browsers block the response | `denied` |
| Missing | CORS not configured | `denied` with `NO_CORS_HEADERS` error |
cors-tester checks this on both the preflight response and the actual response.
### Access-Control-Allow-Methods
Lists the HTTP methods allowed for cross-origin requests.
```
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
```
- Only meaningful in the preflight response.
- cors-tester checks that the method from `--method` appears in this list (case-insensitive).
- If `Access-Control-Allow-Origin: *` is present, cors-tester does not check this separately; simple requests do not require a preflight.
### Access-Control-Allow-Headers
Lists the request headers allowed in cross-origin requests.
```
Access-Control-Allow-Headers: Content-Type, Authorization, X-Request-ID
```
- Only meaningful in the preflight response.
- cors-tester checks that all headers from `--headers` appear in this list (case-insensitive).
### Access-Control-Max-Age
How long (in seconds) the browser may cache the preflight response.
```
Access-Control-Max-Age: 3600
```
Browser cache caps:
- Chrome: 600 seconds (10 minutes)
- Firefox: 7200 seconds (2 hours)
- Safari: 600 seconds
cors-tester emits `LONG_MAX_AGE` warning if the value exceeds 7200.
### Access-Control-Allow-Credentials
Whether the browser may expose the response to JavaScript when credentials (cookies, HTTP auth, TLS client certs) are included.
```
Access-Control-Allow-Credentials: true
```
- When `true`, `Access-Control-Allow-Origin` MUST be a specific origin, not `*`.
- cors-tester emits `WILDCARD_WITH_CREDENTIALS` error when both `*` and `true` are present.
- cors-tester emits `MISSING_CREDENTIALS_HEADER` warning when `--credentials` was passed but this header is absent.
### Access-Control-Expose-Headers
Lists response headers that JavaScript may access. By default, only the CORS-safelisted response headers are accessible.
```
Access-Control-Expose-Headers: X-Request-ID, X-RateLimit-Remaining
```
- cors-tester records this header but does not generate errors or warnings from it.
### Vary
Tells caches which request headers affect the response. Must include `Origin` when the server reflects different `Access-Control-Allow-Origin` values per origin.
```
Vary: Origin, Accept-Encoding
```
cors-tester logic for `MISSING_VARY_ORIGIN`:
```typescript
function checkVaryOrigin(headers: Record<string, string>, allowOrigin: string): Warning | null {
// Only relevant when the server reflects a specific origin (not wildcard)
if (allowOrigin === '*') return null;
const vary = headers['vary'] ?? '';
const parts = vary.split(',').map(p => p.trim().toLowerCase());
if (!parts.includes('origin')) {
return {
code: 'MISSING_VARY_ORIGIN',
message: 'Server reflects specific origin but Vary header does not include "Origin". ' +
'Caches may serve the wrong CORS response to other origins.'
};
}
return null;
}
```
## Analysis logic in tester.ts
### testOrigin() flow
```typescript
async function testOrigin(url: string, origin: string, options: TestOptions): Promise<CorsResult> {
// 1. Send OPTIONS preflight
const preflight = await request({
url,
method: 'OPTIONS',
headers: {
'Origin': origin,
'Access-Control-Request-Method': options.method ?? 'GET',
...(options.requestHeaders?.length
? { 'Access-Control-Request-Headers': options.requestHeaders.join(', ') }
: {}),
},
timeout: options.timeout ?? 5000,
});
// 2. Check preflight for fatal errors
const errors: CorsError[] = [];
const warnings: Warning[] = [];
const allowOrigin = preflight.headers['access-control-allow-origin'];
if (!allowOrigin) {
errors.push({ code: 'NO_CORS_HEADERS', message: '...' });
return { origin, status: 'denied', errors, warnings, ... };
}
// 3. Check for misconfigurations
const allowCredentials = preflight.headers['access-control-allow-credentials'];
if (allowOrigin === '*' && allowCredentials === 'true') {
errors.push({ code: 'WILDCARD_WITH_CREDENTIALS', message: '...' });
} else if (allowOrigin === '*') {
warnings.push({ code: 'WILDCARD_ORIGIN', message: '...' });
}
// 4. Check Vary on preflight
const varyWarn = checkVaryOrigin(preflight.headers, allowOrigin);
if (varyWarn) warnings.push(varyWarn);
// 5. Check max-age
const maxAge = parseInt(preflight.headers['access-control-max-age'] ?? '0', 10);
if (maxAge > 7200) {
warnings.push({ code: 'LONG_MAX_AGE', message: `Value ${maxAge} exceeds browser cache limits.` });
}
// 6. Check credentials header when credentials flag is set
if (options.credentials && allowCredentials !== 'true') {
warnings.push({ code: 'MISSING_CREDENTIALS_HEADER', message: '...' });
}
// 7. Send actual request
const actual = await request({
url,
method: options.method ?? 'GET',
headers: { 'Origin': origin },
timeout: options.timeout ?? 5000,
});
// 8. Check Vary on actual response too
const actualVaryWarn = checkVaryOrigin(actual.headers, allowOrigin);
if (actualVaryWarn && !varyWarn) warnings.push(actualVaryWarn);
// 9. Determine final status
const status = errors.length > 0 ? 'misconfigured'
: warnings.length > 0 ? 'allowed' // status still "allowed" but has warnings
: 'allowed';
return {
origin,
status,
preflightStatus: preflight.status,
actualStatus: actual.status,
durationMs: preflight.durationMs + actual.durationMs,
corsHeaders: extractCorsHeaders(preflight.headers),
warnings,
errors,
};
}
```
## Common misconfigurations
### 1. Wildcard with credentials
```
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
```
Browsers will reject this combination. The preflight may return 204, but the browser's CORS check will fail before the JavaScript can read the response.
Fix:
```
Access-Control-Allow-Origin: https://your-specific-app.com
Access-Control-Allow-Credentials: true
```
### 2. Missing Vary: Origin
A server that dynamically reflects the requesting origin must include `Origin` in `Vary`:
```
# Wrong
Access-Control-Allow-Origin: https://app-a.example.com
Vary: Accept-Encoding
# Correct
Access-Control-Allow-Origin: https://app-a.example.com
Vary: Accept-Encoding, Origin
```
Without `Vary: Origin`, a CDN or proxy may cache the first response and return `Access-Control-Allow-Origin: https://app-a.example.com` to a request from `https://app-b.example.com`, causing that request to fail, or serve the response to an unauthorized origin if the cache does not validate.
### 3. Missing Access-Control-Allow-Headers
If the server does not list a requested header in `Access-Control-Allow-Headers`, the browser will block the request even if `Access-Control-Allow-Origin` matches.
```
# Request
Access-Control-Request-Headers: Authorization, X-Custom-Header
# Response missing X-Custom-Header
Access-Control-Allow-Headers: Content-Type, Authorization
# X-Custom-Header is missing - browser will block
```
### 4. Inconsistent headers between preflight and actual
Some frameworks return CORS headers on the preflight response but not on the actual response. Browsers require `Access-Control-Allow-Origin` on both.
cors-tester checks CORS headers on the actual response in addition to the preflight response.
## Header extraction helper
```typescript
const CORS_HEADER_NAMES = [
'access-control-allow-origin',
'access-control-allow-methods',
'access-control-allow-headers',
'access-control-allow-credentials',
'access-control-max-age',
'access-control-expose-headers',
'vary',
] as const;
function extractCorsHeaders(
headers: Record<string, string>
): Record<string, string> {
return Object.fromEntries(
CORS_HEADER_NAMES
.filter(name => name in headers)
.map(name => [name, headers[name]])
);
}
```Related Skills
SKILL.md - cors-tester
## What this tool does
Skill: Uptime Monitoring
## Overview
Skill: Status Page
## Overview
Skill: unit-conversion
## Overview
Skill: recipe-scaler
## Overview
reading-list
Operate the reading-list API to save, manage, tag, search, and export articles.
email-digest
Configure, test, and troubleshoot the reading-list daily email digest delivered via nodemailer.
websocket-realtime
Use the WebSocket connection in poll-builder to receive live vote updates. Use when you need to stream real-time poll results, monitor a poll for new votes, or build a live dashboard. Triggers include "live results", "real-time updates", "stream votes", "watch poll", or "WebSocket".
poll-builder
Self-hosted poll creation tool with real-time results. Use when you need to create a poll, check vote counts, close a poll, export results, or get the shareable link for a poll. Triggers include "create poll", "vote", "poll results", "survey", "collect votes", "share poll", or any task involving polling or voting.
Skill: personal-finance
## Overview
Skill: csv-import
## Overview
Skill: Syntax Highlighting
## Purpose