CORS 实测 · 《什么时候,Agent 可以住进浏览器》的第二手材料 ================================================================== 日期:2026-10-20 方法:在一台 Mac 上用 curl 冒充来自 https://roam.xiaopingfeng.com 的页面(Origin 头),只测一个来源、一个 User-Agent(桌面 Chrome)、一次请求。机器走本地代理(DNS 返回 fake-IP)。 CORS 头只说明浏览器愿不愿意把响应交给页面,不代表服务端不会因为别的原因拒绝(比如 403 反爬)。样本偏向开发者站点,不是随机抽样。 "机器读 / 人看"的分类是作者按 URL 性质划的。 一、14 个模型接口 ------------------------------------------------------------------ 对每个接口做两件事:带 Access-Control-Request-Headers 的 OPTIONS 预检;带假 key 的 POST,看错误响应有没有 Access-Control-Allow-Origin。 Anthropic 一行是在预检里声明了 anthropic-dangerous-direct-browser-access 之后的结果;不声明时第一次测到的预检没有返回 ACAO。 字段:名称 | 预检状态 | 预检 ACAO | 预检允许的头 | 假 key POST 的状态 | 错误响应里的 ACAO("-" 表示没有) anthropic|preflight=200|ACAO=*|ACAH=x-api-key,anthropic-version,content-type,anthropic-dangerous-direct-browser-acce|POST(bad key)=401|ACAO_on_error=* bigmodel|preflight=200|ACAO=https://roam.xiaopingfeng.com|ACAH=authorization, content-type|POST(bad key)=401|ACAO_on_error=https://roam.xiaopingfeng.com dashscope|preflight=200|ACAO=*|ACAH=authorization, content-type|POST(bad key)=400|ACAO_on_error=* deepseek|preflight=200|ACAO=https://roam.xiaopingfeng.com|ACAH=authorization,content-type|POST(bad key)=401|ACAO_on_error=https://roam.xiaopingfeng.com gemini|preflight=200|ACAO=https://roam.xiaopingfeng.com|ACAH=x-goog-api-key,content-type|POST(bad key)=401|ACAO_on_error=https://roam.xiaopingfeng.com groq|preflight=204|ACAO=*|ACAH=authorization,content-type|POST(bad key)=401|ACAO_on_error=* mistral|preflight=200|ACAO=*|ACAH=Authorization,Content-Type,User-Agent,Cache-Control,Keep-Alive,X-Mistral-User-Ag|POST(bad key)=401|ACAO_on_error=* moonshot|preflight=204|ACAO=https://roam.xiaopingfeng.com|ACAH=authorization,content-type|POST(bad key)=401|ACAO_on_error=https://roam.xiaopingfeng.com openai|preflight=200|ACAO=https://roam.xiaopingfeng.com|ACAH=authorization,content-type|POST(bad key)=401|ACAO_on_error=* openrouter|preflight=204|ACAO=*|ACAH=Authorization,User-Agent,X-Api-Key,X-CSRF-Token,X-Requested-With,Accept,Accept-V|POST(bad key)=401|ACAO_on_error=* siliconflow|preflight=204|ACAO=*|ACAH=*|POST(bad key)=401|ACAO_on_error=* together|preflight=200|ACAO=*|ACAH=authorization,content-type|POST(bad key)=401|ACAO_on_error=* volcano-ark|preflight=200|ACAO=https://roam.xiaopingfeng.com|ACAH=authorization,content-type|POST(bad key)=401|ACAO_on_error=- xai|preflight=200|ACAO=*|ACAH=*|POST(bad key)=400|ACAO_on_error=* 二、31 个公开 URL ------------------------------------------------------------------ 字段:名称 | 最终状态码 | ACAO | 结论(放行 / 无头 / 仅限他域)| 重定向次数 | URL GitHub raw|200|*|放行|0 redirects|https://raw.githubusercontent.com/torvalds/linux/master/README GitHub API|200|*|放行|0 redirects|https://api.github.com/repos/torvalds/linux GitHub 网页|200|-|无头|0 redirects|https://github.com/torvalds/linux npm registry|200|*|放行|0 redirects|https://registry.npmjs.org/left-pad PyPI JSON|200|*|放行|0 redirects|https://pypi.org/pypi/requests/json jsDelivr|200|*|放行|0 redirects|https://cdn.jsdelivr.net/npm/lodash/package.json unpkg|200|*|放行|1 redirects|https://unpkg.com/lodash/package.json Wikipedia REST|200|*|放行|0 redirects|https://en.wikipedia.org/api/rest_v1/page/summary/Linux Wikipedia 网页|200|-|无头|0 redirects|https://en.wikipedia.org/wiki/Linux MDN 文档|200|-|无头|0 redirects|https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API Python 文档|200|-|无头|0 redirects|https://docs.python.org/3/library/asyncio.html Node.js 文档|200|-|无头|0 redirects|https://nodejs.org/api/fs.html Hacker News 网页|200|-|无头|0 redirects|https://news.ycombinator.com/ Hacker News API|200|https://roam.xiaopingfeng.com|放行|0 redirects|https://hacker-news.firebaseio.com/v0/topstories.json Reddit JSON|403|-|无头|0 redirects|https://www.reddit.com/r/programming.json Stack Overflow|403|-|无头|0 redirects|https://stackoverflow.com/questions arXiv 摘要页|200|-|无头|0 redirects|https://arxiv.org/abs/1706.03762 arXiv API|200|-|无头|0 redirects|https://export.arxiv.org/api/query?search_query=all:agent&max_results=1 纽约时报|403|-|无头|0 redirects|https://www.nytimes.com/ BBC News|200|-|无头|0 redirects|https://www.bbc.com/news Hugging Face 模型页|200|-|无头|0 redirects|https://huggingface.co/Qwen/Qwen2.5-7B-Instruct Hugging Face API|200|https://roam.xiaopingfeng.com|放行|0 redirects|https://huggingface.co/api/models/Qwen/Qwen2.5-7B-Instruct Cloudflare 文档|200|*|放行|0 redirects|https://developers.cloudflare.com/workers/ 大麦网|200|www.damai.cn|仅限他域|0 redirects|https://www.damai.cn/ 知乎|200|-|无头|1 redirects|https://www.zhihu.com/ 掘金|200|-|无头|0 redirects|https://juejin.cn/ 36氪|200|-|无头|0 redirects|https://36kr.com/ 百度|200|-|无头|0 redirects|https://www.baidu.com/ 豆瓣|200|-|无头|0 redirects|https://www.douban.com/ httpbin|200|https://roam.xiaopingfeng.com|放行|0 redirects|https://httpbin.org/get example.com|200|-|无头|0 redirects|https://example.com/ 三、脚本 ------------------------------------------------------------------ # models.txt(名称|URL|预检请求的头) anthropic|https://api.anthropic.com/v1/messages|x-api-key,anthropic-version,content-type,anthropic-dangerous-direct-browser-access openai|https://api.openai.com/v1/chat/completions|authorization,content-type gemini|https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent|x-goog-api-key,content-type bigmodel|https://open.bigmodel.cn/api/paas/v4/chat/completions|authorization,content-type deepseek|https://api.deepseek.com/chat/completions|authorization,content-type moonshot|https://api.moonshot.cn/v1/chat/completions|authorization,content-type dashscope|https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions|authorization,content-type volcano-ark|https://ark.cn-beijing.volces.com/api/v3/chat/completions|authorization,content-type siliconflow|https://api.siliconflow.cn/v1/chat/completions|authorization,content-type openrouter|https://openrouter.ai/api/v1/chat/completions|authorization,content-type groq|https://api.groq.com/openai/v1/chat/completions|authorization,content-type mistral|https://api.mistral.ai/v1/chat/completions|authorization,content-type together|https://api.together.xyz/v1/chat/completions|authorization,content-type xai|https://api.x.ai/v1/chat/completions|authorization,content-type # probe_model.sh #!/bin/bash IFS='|' read -r name url hdrs <<< "$1" O="https://roam.xiaopingfeng.com" pf=$(curl -s -m 20 -D - -o /dev/null -X OPTIONS "$url" -H "Origin: $O" -H "Access-Control-Request-Method: POST" -H "Access-Control-Request-Headers: $hdrs" -A "Mozilla/5.0" 2>&1) pf_status=$(echo "$pf" | grep '^HTTP' | tail -1 | tr -d '\r' | awk '{print $2}') pf_acao=$(echo "$pf" | grep -i -m1 '^access-control-allow-origin' | tr -d '\r' | cut -d' ' -f2-) pf_acah=$(echo "$pf" | grep -i -m1 '^access-control-allow-headers' | tr -d '\r' | cut -d' ' -f2- | cut -c1-80) po=$(curl -s -m 20 -D - -o /dev/null -X POST "$url" -H "Origin: $O" -H "content-type: application/json" -H "authorization: Bearer invalid" -H "x-api-key: invalid" -H "anthropic-dangerous-direct-browser-access: true" -H "anthropic-version: 2023-06-01" -d '{}' -A "Mozilla/5.0" 2>&1) po_status=$(echo "$po" | grep '^HTTP' | tail -1 | tr -d '\r' | awk '{print $2}') po_acao=$(echo "$po" | grep -i -m1 '^access-control-allow-origin' | tr -d '\r' | cut -d' ' -f2-) echo "$name|preflight=${pf_status:-none}|ACAO=${pf_acao:--}|ACAH=${pf_acah:--}|POST(bad key)=${po_status:-none}|ACAO_on_error=${po_acao:--}" # sites.txt(名称|URL) GitHub raw|https://raw.githubusercontent.com/torvalds/linux/master/README GitHub API|https://api.github.com/repos/torvalds/linux GitHub 网页|https://github.com/torvalds/linux npm registry|https://registry.npmjs.org/left-pad PyPI JSON|https://pypi.org/pypi/requests/json jsDelivr|https://cdn.jsdelivr.net/npm/lodash/package.json unpkg|https://unpkg.com/lodash/package.json Wikipedia REST|https://en.wikipedia.org/api/rest_v1/page/summary/Linux Wikipedia 网页|https://en.wikipedia.org/wiki/Linux MDN 文档|https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API Python 文档|https://docs.python.org/3/library/asyncio.html Node.js 文档|https://nodejs.org/api/fs.html Hacker News 网页|https://news.ycombinator.com/ Hacker News API|https://hacker-news.firebaseio.com/v0/topstories.json Reddit JSON|https://www.reddit.com/r/programming.json Stack Overflow|https://stackoverflow.com/questions arXiv 摘要页|https://arxiv.org/abs/1706.03762 arXiv API|https://export.arxiv.org/api/query?search_query=all:agent&max_results=1 纽约时报|https://www.nytimes.com/ BBC News|https://www.bbc.com/news Hugging Face 模型页|https://huggingface.co/Qwen/Qwen2.5-7B-Instruct Hugging Face API|https://huggingface.co/api/models/Qwen/Qwen2.5-7B-Instruct Cloudflare 文档|https://developers.cloudflare.com/workers/ 大麦网|https://www.damai.cn/ 知乎|https://www.zhihu.com/ 掘金|https://juejin.cn/ 36氪|https://36kr.com/ 百度|https://www.baidu.com/ 豆瓣|https://www.douban.com/ httpbin|https://httpbin.org/get example.com|https://example.com/ # survey.py import subprocess, sys, concurrent.futures as cf, re O = "https://roam.xiaopingfeng.com" def probe(line): name, url = line.strip().split("|", 1) try: r = subprocess.run(["curl", "-s", "-L", "-m", "20", "-D", "-", "-o", "/dev/null", "-H", f"Origin: {O}", "-A", "Mozilla/5.0 (Macintosh) AppleWebKit/537.36 Chrome/150 Safari/537.36", url], capture_output=True, text=True, timeout=40) except Exception as e: return name, url, "ERR", "-", str(e)[:40] blocks = [b for b in re.split(r"\r?\n\r?\n", r.stdout) if b.strip().startswith("HTTP")] blocks = [b for b in blocks if "Connection established" not in b.splitlines()[0]] if not blocks: return name, url, "none", "-", "" b = blocks[-1] status = b.splitlines()[0].split()[1] m = re.search(r"(?im)^access-control-allow-origin:\s*(.+?)\s*$", b) return name, url, status, (m.group(1) if m else "-"), str(len(blocks) - 1) + " redirects" lines = [l for l in open("sites.txt") if l.strip()] with cf.ThreadPoolExecutor(8) as ex: res = list(ex.map(probe, lines)) for name, url, st, acao, note in res: verdict = "放行" if acao in ("*", O) else ("无头" if acao == "-" else "仅限他域") print(f"{name}|{st}|{acao}|{verdict}|{note}|{url}")