<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Claude on 逍遥笔记</title><link>https://blog.7qiang.com/tags/claude/</link><description>Recent content in Claude on 逍遥笔记</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><copyright>逍遥笔记</copyright><lastBuildDate>Sun, 26 Jul 2026 04:49:00 +0800</lastBuildDate><atom:link href="https://blog.7qiang.com/tags/claude/index.xml" rel="self" type="application/rss+xml"/><item><title>焚决 Claude — 一个油猴脚本如何撬开 Anthropic 的支付大门</title><link>https://blog.7qiang.com/posts/claude-cassia-mock-payment-bypass-analysis/</link><pubDate>Sun, 26 Jul 2026 04:49:00 +0800</pubDate><guid>https://blog.7qiang.com/posts/claude-cassia-mock-payment-bypass-analysis/</guid><description>&lt;img src="https://blog.7qiang.com/blog-cover-cyber-mask.gif" alt="Featured image of post 焚决 Claude — 一个油猴脚本如何撬开 Anthropic 的支付大门" /&gt;&lt;section class="xiaoyao-contact-card" aria-label="联系逍遥"&gt;
 &lt;img class="xiaoyao-contact-card__qr" src="https://blog.7qiang.com/contact-qr.png" width="690" height="690" loading="lazy" decoding="async" alt="逍遥微信二维码"&gt;
 &lt;div class="xiaoyao-contact-card__content"&gt;
 &lt;h3 class="xiaoyao-contact-card__title"&gt;联系逍遥&lt;/h3&gt;
 &lt;p class="xiaoyao-contact-card__description"&gt;扫码添加微信，或点击下方按钮复制联系方式。&lt;/p&gt;
 &lt;div class="xiaoyao-contact-buttons" data-contact-buttons&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--wechat" type="button" data-copy-contact="plus6566" data-copy-label="微信号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;微信&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;plus6566&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--qq" type="button" data-copy-contact="826215906" data-copy-label="QQ 号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;QQ&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;826215906&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;span class="xiaoyao-contact-feedback" role="status" aria-live="polite"&gt;&lt;/span&gt;
&lt;/div&gt;

 &lt;/div&gt;
&lt;/section&gt;

&lt;p&gt;进微信群请联系博主，各位觉得文章对你有帮助的话可否打赏一些呀~&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;⚠️ 时效性提醒&lt;/strong&gt;：本文发布于 2026 年 7 月，文中涉及的 API 端点、支付流参数均为发布时有效值。Anthropic 随时可能修改 &lt;code&gt;checkout_capabilities&lt;/code&gt; 响应结构或在后端追加校验逻辑，届时本方法将失效。本文保留仅供安全研究与客户端安全防御参考。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="前言"&gt;前言
&lt;/h2&gt;&lt;p&gt;最近圈子里流传着一个叫**「焚决」**的东西，名字取得很玄，搞得好像什么了不得的秘术。拿到手一看——是一段不到 200 行的 Tampermonkey 油猴脚本。&lt;/p&gt;
&lt;p&gt;但别小看这 200 行代码。它干了一件非常漂亮的事情：在你打开 Claude 网页版准备付费订阅的瞬间，在浏览器内部悄无声息地篡改了一个 API 响应，把你看到的支付页面从信用卡表单换成了 SEPA 银行转账。然后你去 &lt;code&gt;randomiban.com&lt;/code&gt; 随便生成一个德国银行账号，填进去，提交——&lt;strong&gt;Claude Max 到手，扣款金额 0 元。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这玩意在外面传得沸沸扬扬，各路卡网卖的廉价 Claude 会员，相当一部分就是靠这个批量生产的。&lt;/p&gt;
&lt;p&gt;今天咱们把它彻底拆开。不光讲它怎么工作，更要讲&lt;strong&gt;为什么能成&lt;/strong&gt;、&lt;strong&gt;它利用了支付系统的哪个结构性缺陷&lt;/strong&gt;、以及&lt;strong&gt;这种攻击模式怎么迁移到其他平台&lt;/strong&gt;。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="一攻击全景一个-api-字段改写引发的连锁反应"&gt;一、攻击全景：一个 API 字段改写引发的连锁反应
&lt;/h2&gt;&lt;h3 id="正常的-claude-订阅流程"&gt;正常的 Claude 订阅流程
&lt;/h3&gt;&lt;p&gt;打开 &lt;code&gt;claude.ai&lt;/code&gt;，点击升级订阅，前端第一件事不是弹出支付表单——它先问后端一个问题：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GET /api/organizations/{org_id}/subscription/checkout_capabilities
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这个接口的职责很简单：&lt;strong&gt;告诉前端，当前这个用户应该走哪条支付通道。&lt;/strong&gt; 后端会综合 IP 地理位置、账号注册区域、浏览器 Accept-Language 等信号，返回一个支付流标识：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;checkout_flow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;stripe&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;前端拿到 &lt;code&gt;stripe&lt;/code&gt;，渲染信用卡表单——卡号、有效期、CVV、3DS 二次验证，一套标准流程。信用卡支付是同步授权的，银行实时校验，过不了就是过不了。&lt;/p&gt;
&lt;h3 id="劫持后的流程"&gt;劫持后的流程
&lt;/h3&gt;&lt;p&gt;脚本做的事情只有一件：&lt;strong&gt;把所有命中 &lt;code&gt;checkout_capabilities&lt;/code&gt; 的响应体替换为&lt;/strong&gt;：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;checkout_flow&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cassia&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;cassia&lt;/code&gt; 是 Anthropic 内部用于欧洲 SEPA 银行转账的支付流标识。前端一看返回值变了，渲染逻辑直接切换——不再是信用卡表单，而是一个 IBAN 输入框。&lt;/p&gt;
&lt;p&gt;这时候去 randomiban.com 生成一个德国 IBAN（格式：&lt;code&gt;DE&lt;/code&gt; + 2 位校验码 + 8 位银行代码 + 10 位账号，共 22 位），填进去，点提交。&lt;/p&gt;
&lt;p&gt;前端校验？只检查 IBAN 格式是否合法（MOD 97-10 校验通过即可）。&lt;/p&gt;
&lt;p&gt;后端校验？SEPA 体系下，&lt;strong&gt;提交时不做实时账户验证&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;结果：Claude 立刻给你开通了订阅。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="二sepa-直接借记为什么填个假账号也能过"&gt;二、SEPA 直接借记——为什么填个假账号也能过
&lt;/h2&gt;&lt;p&gt;这是整个攻击的核心利用点，不理解 SEPA 的清算机制，就不可能理解这个漏洞为什么成立。&lt;/p&gt;
&lt;h3 id="先搞清楚-sepa-是什么"&gt;先搞清楚 SEPA 是什么
&lt;/h3&gt;&lt;p&gt;SEPA（Single Euro Payments Area，单一欧元支付区）覆盖 36 个欧洲国家和地区，是欧盟推动的统一支付基础设施。其中的 &lt;strong&gt;SEPA Direct Debit&lt;/strong&gt;（直接借记，德语叫 Lastschriftverfahren）允许商家向消费者的银行账户&amp;quot;拉钱&amp;quot;——不是你给商家转账，而是商家拿着你的授权凭证去你的银行把钱取走。&lt;/p&gt;
&lt;p&gt;听起来很方便对吧？问题就出在这个&amp;quot;拉钱&amp;quot;的流程设计上。&lt;/p&gt;
&lt;h3 id="信用卡-vs-sepa-直接借记两套完全不同的信任模型"&gt;信用卡 vs SEPA 直接借记：两套完全不同的信任模型
&lt;/h3&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;信用卡（Stripe 标准流）&lt;/th&gt;
 &lt;th&gt;SEPA 直接借记（cassia 流）&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;授权模式&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;同步在线授权&lt;/td&gt;
 &lt;td&gt;异步离线扣款&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;交易发起时校验&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;卡号 Luhn 校验 + CVV 验证 + 3DS 二次认证 + 银行实时授权&lt;/td&gt;
 &lt;td&gt;IBAN MOD 97-10 格式校验，&lt;strong&gt;仅此而已&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;银行参与时机&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;交易发起的瞬间&lt;/td&gt;
 &lt;td&gt;交易发起后的 1-3 个工作日&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;资金确认&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;毫秒级返回授权结果&lt;/td&gt;
 &lt;td&gt;银行不返回任何确认，排队等清算&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;消费者保护&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;Chargeback 争议流程&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;8 周无条件撤回权&lt;/strong&gt;（SEPA CORE 规则）&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;看到关键区别了吗？信用卡在你点击支付的&lt;strong&gt;那一瞬间&lt;/strong&gt;就跟银行确认了你有没有钱、卡号对不对、你本人是不是同意的。而 SEPA 直接借记&lt;strong&gt;完全不做这些&lt;/strong&gt;——它只检查 IBAN 的格式，然后把扣款请求扔进银行间的批量清算队列里，等着慢慢处理。&lt;/p&gt;
&lt;h3 id="sepa-core-清算流程"&gt;SEPA CORE 清算流程
&lt;/h3&gt;&lt;p&gt;实际的清算过程是这样的：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;你提交 IBAN
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;支付网关做 MOD 97-10 校验（纯数学公式，检查 IBAN 格式是否合法）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;校验通过 → 支付网关返回&amp;#34;交易成功&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Anthropic 收到成功信号 → 开通你的 Claude Max 订阅
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;... T+1 到 T+3 工作日 ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;扣款请求进入 SEPA CORE 清算系统
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;商家银行 → 消费者银行：请从账号 DEXX XXXX XXXX XXXX XXXX XX 扣 $20
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;消费者银行查询账号 → 账号不存在 / 余额不足 / 未授权
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;返回 R 代码拒绝（R02=无效账号, R04=账户已关, R05=被授权人撤销...）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Anthropic 收到扣款失败通知 → 关闭你的订阅
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;重点来了：从你提交 IBAN 到银行清算出结果，中间有一个 1-3 个工作日的真空期。&lt;/strong&gt; 在这段时间里，你的 Claude Max 订阅是完全生效的——能用 Opus、能用所有高级功能、流量不限。&lt;/p&gt;
&lt;h3 id="mod-97-10格式校验的局限性"&gt;MOD 97-10：格式校验的局限性
&lt;/h3&gt;&lt;p&gt;IBAN 的校验算法叫 ISO 7064 MOD 97-10，原理其实很简单：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;把 IBAN 的国家代码和校验码移到末尾&lt;/li&gt;
&lt;li&gt;把字母转换为数字（A=10, B=11, …, Z=35）&lt;/li&gt;
&lt;li&gt;对整个大整数做 MOD 97 运算&lt;/li&gt;
&lt;li&gt;结果等于 1 就合法&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这个算法能检出什么？&lt;strong&gt;格式错误&lt;/strong&gt;——打错了某一位、漏了一位、国家代码不对。&lt;/p&gt;
&lt;p&gt;这个算法检不出什么？&lt;strong&gt;一切跟真实世界有关的东西&lt;/strong&gt;——账户是否存在、余额是否充足、持有人是不是你。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;randomiban.com&lt;/code&gt; 生成的 IBAN 就是利用了这一点：它按照 MOD 97-10 算法逆向构造，生成的 IBAN &lt;strong&gt;格式 100% 合法&lt;/strong&gt;，能通过任何基于此算法的前端和后端校验。至于这个账号在不在现实中存在——那是 3 天后银行清算时才会发现的事情。&lt;/p&gt;
&lt;h3 id="为什么偏偏是德国-iban"&gt;为什么偏偏是德国 IBAN？
&lt;/h3&gt;&lt;p&gt;SEPA 覆盖 36 个国家，为什么脚本选了德国（DE）？&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;格式最简单&lt;/strong&gt;：DE 开头的 IBAN 固定 22 位，结构是 &lt;code&gt;DE + 2位校验码 + 8位银行代码(BLZ) + 10位账号&lt;/code&gt;，没有额外的国家级校验叠加&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;通过率最高&lt;/strong&gt;：德国 IBAN 在 Stripe 等主流支付网关的接受度最好，不会触发额外的地区风控&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;银行代码空间大&lt;/strong&gt;：8 位 BLZ 有大量有效的银行代码段，随机生成碰上有效 BLZ 前缀的概率不低&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对比一下法国 IBAN（27 位，还有额外的 RIB 密钥校验）或者西班牙 IBAN（24 位，有 DC 校验位），德国确实是阻力最小的选择。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="三脚本逐行技术拆解"&gt;三、脚本逐行技术拆解
&lt;/h2&gt;&lt;p&gt;理解了 SEPA 的机制，再来看脚本本身就清晰多了。每一个设计决策都有明确的技术理由。&lt;/p&gt;
&lt;h3 id="31-元数据抢在一切之前"&gt;3.1 元数据——抢在一切之前
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// @name TestExample Cassia Response Mock
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// @match *://claude.ai/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// @match *://*.claude.ai/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// @run-at document-start
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// @grant none
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// @sandbox raw
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;三个关键设定：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@run-at document-start&lt;/code&gt;&lt;/strong&gt;：页面 DOM 还没开始构建，脚本已经注入完毕。这不是可选项——如果等到 &lt;code&gt;document-idle&lt;/code&gt;，&lt;code&gt;checkout_capabilities&lt;/code&gt; 的请求可能已经发出去了，Hook 就晚了。整个攻击成立的前提就是&lt;strong&gt;比目标请求更早完成注入&lt;/strong&gt;。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@sandbox raw&lt;/code&gt;&lt;/strong&gt;：绕过 Tampermonkey 的安全沙箱。默认沙箱会隔离脚本的执行上下文，导致你覆盖的 &lt;code&gt;window.fetch&lt;/code&gt; 和页面实际用的 &lt;code&gt;window.fetch&lt;/code&gt; 不是同一个。&lt;code&gt;raw&lt;/code&gt; 模式下脚本直接在页面上下文中执行，Hook 才能真正生效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@grant none&lt;/code&gt;&lt;/strong&gt;：不申请 &lt;code&gt;GM_*&lt;/code&gt; 系列 API 权限。一方面减少 Tampermonkey 的安全提示弹窗，另一方面 &lt;code&gt;@sandbox raw&lt;/code&gt; 本身就不兼容 &lt;code&gt;GM_*&lt;/code&gt; API。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="32-精确制导只改一个接口"&gt;3.2 精确制导——只改一个接口
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TARGET_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sr"&gt;/^\/api\/organizations\/[^/]+\/subscription\/checkout_capabilities\/?$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;正则匹配精确到路径段，连末尾有没有斜杠都考虑了。&lt;strong&gt;只拦截这一个接口，其他所有请求原样放行。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;为什么是这个接口？因为它是 Claude 前端支付流的&lt;strong&gt;单一决策点&lt;/strong&gt;——前端根据它的返回值决定渲染 Stripe 信用卡表单还是 SEPA IBAN 输入框。控制了这个响应，就控制了用户看到的整个支付界面。&lt;/p&gt;
&lt;p&gt;这种精准度也是为了隐蔽性——如果你 Hook 了所有请求，前端的行为会出各种异常，容易暴露。只改一个接口，前端其他部分的运行完全正常。&lt;/p&gt;
&lt;h3 id="33-fetch-拦截先发后改"&gt;3.3 Fetch 拦截——先发后改
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nativeFetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fetch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targetUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getTargetUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;nativeFetch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;targetUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;originalResponse&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;createMockResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalResponse&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意调用顺序：&lt;strong&gt;先用原生 &lt;code&gt;fetch&lt;/code&gt; 把真实请求正常发出去，等拿到响应后再替换返回值。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是这个脚本最巧妙的地方。&lt;/p&gt;
&lt;p&gt;如果直接拦截请求不发出去，构造一个假响应返回呢？功能上没问题。但从服务端的角度看——你打开了支付页面，前端应该调 &lt;code&gt;checkout_capabilities&lt;/code&gt;，结果后端日志里根本没看到这个请求。这就是一个异常信号，可以被 Anthropic 的安全团队用于检测。&lt;/p&gt;
&lt;p&gt;先发后改的策略完美回避了这个问题：服务端日志里看到了正常的 GET 请求、正常的 200 响应，一切如常。篡改发生在浏览器内部，从 HTTPS 加密通道往外看什么都没变。&lt;/p&gt;
&lt;h3 id="34-response-重构细节决定成败"&gt;3.4 Response 重构——细节决定成败
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;createMockResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalResponse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;originalResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;content-length&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;content-encoding&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;etag&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;content-md5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;content-type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;application/json; charset=utf-8&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;content-length&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MOCK_LENGTH&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;cache-control&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;no-store&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MOCK_BODY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;statusText&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;OK&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;逐个分析删掉的响应头：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;content-encoding&lt;/code&gt;&lt;/strong&gt;：原始响应大概率是 gzip 或 br 压缩的。我们构造的 JSON 是明文，如果不删这个头，浏览器会拿着明文 JSON 去做 gzip 解压——直接炸。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;content-length&lt;/code&gt;&lt;/strong&gt;：原始响应体大小和我们构造的不一样，不改的话浏览器会截断或报错。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;etag&lt;/code&gt; / &lt;code&gt;content-md5&lt;/code&gt;&lt;/strong&gt;：完整性校验头。原始响应的哈希和我们篡改后的内容对不上。虽然浏览器通常不强制校验这些头，但删掉更保险——万一前端代码拿这些值做了缓存校验呢。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;cache-control: no-store&lt;/code&gt;&lt;/strong&gt;：强制不缓存。如果浏览器缓存了我们的假响应，下次刷新页面不走 Hook 了就拿到缓存的假数据——反而弄巧成拙。反过来，如果缓存了之前的真响应，前端可能直接用缓存不发请求，Hook 就没机会触发。&lt;code&gt;no-store&lt;/code&gt; 确保每次都走网络请求，每次都经过 Hook。&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="35-xhr-双通道保险"&gt;3.5 XHR 双通道保险
&lt;/h3&gt;&lt;p&gt;脚本不光 Hook 了 &lt;code&gt;fetch&lt;/code&gt;，还对 &lt;code&gt;XMLHttpRequest&lt;/code&gt; 做了一套完整的拦截——覆盖了 &lt;code&gt;responseText&lt;/code&gt;、&lt;code&gt;response&lt;/code&gt;、&lt;code&gt;status&lt;/code&gt;、&lt;code&gt;statusText&lt;/code&gt;、&lt;code&gt;getResponseHeader&lt;/code&gt;、&lt;code&gt;getAllResponseHeaders&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;为什么要两套？现代 Web 应用大多用 &lt;code&gt;fetch&lt;/code&gt;，但你无法保证 Anthropic 的前端代码——或者它依赖的第三方库——不会在某些场景下 fallback 到 XHR。少 Hook 一个通道就是留了一条漏网之鱼。&lt;/p&gt;
&lt;p&gt;XHR 的 Hook 方式也值得一看：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;replaceXhrGetter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;propertyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;descriptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getOwnPropertyDescriptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;XhrPrototype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;propertyName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;descriptor&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;function&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configurable&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`[Cassia Mock] 无法接管 XHR.&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;propertyName&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nativeGetter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defineProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;XhrPrototype&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;propertyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;descriptor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;get&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;getMatchedXhr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;nativeGetter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;replacement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;nativeGetter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;用 &lt;code&gt;Object.getOwnPropertyDescriptor&lt;/code&gt; 先取原始的 property descriptor，再通过 &lt;code&gt;Object.defineProperty&lt;/code&gt; 精确替换 getter。比直接赋值靠谱得多——它保留了原始 descriptor 的所有属性（&lt;code&gt;configurable&lt;/code&gt;、&lt;code&gt;enumerable&lt;/code&gt;），而且会先检查 &lt;code&gt;configurable&lt;/code&gt; 是否为 &lt;code&gt;false&lt;/code&gt;，如果属性被冻结就优雅降级而不是静默失败。&lt;/p&gt;
&lt;h3 id="36-状态指示器"&gt;3.6 状态指示器
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;badge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;div&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cassia-mock-badge&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;badge&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Cassia Mock ON&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;页面右下角一个绿色小角标，确认脚本已激活。实际攻击中这是个安慰剂——真正的确认是看支付页面有没有从信用卡变成 IBAN 输入框。但对于批量操作的卡商来说，这个视觉反馈能提高操作效率。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="四横向迁移这不是一个漏洞是一类漏洞"&gt;四、横向迁移：这不是一个漏洞，是一类漏洞
&lt;/h2&gt;&lt;p&gt;分析完焚决，我们跳出来看一个更有价值的问题：&lt;strong&gt;这类攻击是可复用的。&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="漏洞本质"&gt;漏洞本质
&lt;/h3&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;客户端信任边界缺失&lt;/strong&gt;：服务端将支付流的决策权——“这个用户该走哪条支付通道”——交给了一个客户端可以任意篡改的 API 响应。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;这不是孤例。回顾我们之前分析过的一系列支付漏洞，它们共享同一个底层模式：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;漏洞&lt;/th&gt;
 &lt;th&gt;被篡改的对象&lt;/th&gt;
 &lt;th&gt;信任边界裂缝&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;焚决（本例）&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;checkout_capabilities&lt;/code&gt; 响应&lt;/td&gt;
 &lt;td&gt;前端信任 API 返回值决定支付通道&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;GPT Plus offerToken 注入&lt;/td&gt;
 &lt;td&gt;Google Play Billing 内存中的 offerToken&lt;/td&gt;
 &lt;td&gt;服务端不校验 token 与账号资格的绑定关系&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;GPT iOS 收据复用&lt;/td&gt;
 &lt;td&gt;Base64 App Store 收据&lt;/td&gt;
 &lt;td&gt;服务端不验证收据与提交者账号的对应关系&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cloudflare Pro 竞态&lt;/td&gt;
 &lt;td&gt;并发请求的状态窗口&lt;/td&gt;
 &lt;td&gt;权限发放与支付确认之间存在可利用的时序差&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;RevenueCat 回调劫持&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;fetch_token&lt;/code&gt; 跨账号提交&lt;/td&gt;
 &lt;td&gt;第三方回调接口缺乏调用方鉴权&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="通用攻击模型"&gt;通用攻击模型
&lt;/h3&gt;&lt;p&gt;把这些漏洞抽象一下，可以提取出一个四步攻击框架：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1. 定位决策接口
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 找到决定支付流 / 功能开关 / 定价计算的 API 端点
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 信号关键词：checkout_capabilities, payment_methods, available_plans,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; eligible_promotions, feature_flags, entitlements
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 注入篡改数据
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 通过油猴脚本（Web）、MITM（移动端）、Frida Hook（Native）等
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 方式替换响应内容
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 选择确认延迟最长的路径
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SEPA 直接借记 &amp;gt; 银行转账 &amp;gt; PayPal &amp;gt; 信用卡
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 延迟越长，时间窗口越大
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. 利用&amp;#34;前端校验 + 后端真空&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 前端校验通过后，后端在异步确认完成前不暂停服务发放
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;这个框架可以复用到任何订阅制平台&lt;/strong&gt;，只要目标满足：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;支付方式的选择依赖客户端可控状态&lt;/li&gt;
&lt;li&gt;存在至少一种异步确认的支付方式&lt;/li&gt;
&lt;li&gt;“服务开通&amp;quot;发生在&amp;quot;支付确认&amp;quot;之前&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在漏洞挖掘实战中，打开 DevTools Network 面板，搜索 &lt;code&gt;checkout&lt;/code&gt;、&lt;code&gt;payment&lt;/code&gt;、&lt;code&gt;billing&lt;/code&gt;、&lt;code&gt;plan&lt;/code&gt;、&lt;code&gt;subscription&lt;/code&gt; 这些关键词，找到所有影响支付逻辑的端点，逐个测试响应注入——这就是最直接的攻击面枚举方式。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="五防御视角"&gt;五、防御视角
&lt;/h2&gt;&lt;h3 id="给服务端开发者"&gt;给服务端开发者
&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;支付流决策必须服务端闭环&lt;/strong&gt;。&lt;code&gt;checkout_flow&lt;/code&gt; 的值应该在后端根据用户地区、账号状态、风控信号独立计算，前端只是一个渲染器——它不应该有能力选择自己走哪条支付通道。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异步支付必须延迟开通&lt;/strong&gt;。SEPA、银行转账这类异步支付方式，在银行返回扣款成功确认之前，不应该开通任何付费功能。可以给用户一个&amp;quot;支付处理中&amp;quot;的状态，等清算完成后再激活。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API 响应签名&lt;/strong&gt;。对关键 API 响应做 HMAC 签名（密钥存服务端），前端消费前验签。虽然不能根治（攻击者可以 Hook 验签逻辑），但大幅提高了攻击门槛——从改一个 JSON 字段变成了还得逆向验签实现。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IBAN 实时银行验证&lt;/strong&gt;。接入 SWIFT gpi 或各国央行的 IBAN 验证服务（如德国 Bundesbank 的 IBAN 验证接口），在提交时做实时的账户存在性校验，而不是只靠本地 MOD 97。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="给安全研究者"&gt;给安全研究者
&lt;/h3&gt;&lt;p&gt;这类漏洞的攻击面很清晰——任何一个返回了影响支付逻辑数据的 API 端点，且这个数据可以被前端篡改，都是潜在的攻击入口。&lt;/p&gt;
&lt;p&gt;一些高价值的&amp;quot;信号接口”：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;checkout_capabilities&lt;/code&gt; / &lt;code&gt;payment_methods&lt;/code&gt; / &lt;code&gt;available_plans&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;region&lt;/code&gt; / &lt;code&gt;locale&lt;/code&gt; / &lt;code&gt;country_detection&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eligible_promotions&lt;/code&gt; / &lt;code&gt;offers&lt;/code&gt; / &lt;code&gt;discounts&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;feature_flags&lt;/code&gt; / &lt;code&gt;entitlements&lt;/code&gt; / &lt;code&gt;subscription_tiers&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="六实战翻车现场验证失败并清除旧会话"&gt;六、实战翻车现场：“验证失败并清除旧会话”
&lt;/h2&gt;&lt;p&gt;讲完了攻击原理和防御思路，再来看一个真实场景——&lt;strong&gt;焚决并不是每次都能成功的&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;在实际操作中（不管是焚决的 SEPA 路径还是正常的 Stripe 信用卡路径），你大概率会遇到这样一幕：&lt;/p&gt;
&lt;p&gt;&lt;img alt="Claude支付失败截图" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px"&gt;&lt;/p&gt;
&lt;p&gt;两条红色报错同时弹出来：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;“未提交付款：发生了意外错误。请检查表单后重试。”&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;“验证失败并清除旧会话”&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;第一次看到这个，很多人会以为是卡的问题、网络的问题、脚本的问题。其实都不是——&lt;strong&gt;这是会话状态失效的问题&lt;/strong&gt;，而且它能帮我们理解 Anthropic 的支付架构到底是怎么做验证的。&lt;/p&gt;
&lt;h3 id="为什么会出现这个错误"&gt;为什么会出现这个错误？
&lt;/h3&gt;&lt;p&gt;两条错误同时出现，意味着&lt;strong&gt;会话死在了支付提交的瞬间&lt;/strong&gt;。理解这一点需要知道 Claude 支付提交时后端做了什么：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;用户点击&amp;#34;提交支付&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;前端将 Stripe token / IBAN 提交到 Anthropic 后端
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;后端第一件事：校验当前会话（session）是否有效
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;会话失效 → 返回&amp;#34;验证失败&amp;#34; → 前端清除旧 session → 支付自然失败
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;关键点在于：Anthropic 在处理支付请求之前，会先验证你的登录会话。&lt;/strong&gt; 如果会话已经过期或被吊销，支付请求根本不会到达 Stripe——它在 session 校验这一步就被打回来了。&lt;/p&gt;
&lt;h3 id="五种常见的会话失效原因"&gt;五种常见的会话失效原因
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;① SK Cookie 过期（最常见）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude.ai 使用 &lt;code&gt;__Secure-next-auth.session-token&lt;/code&gt;（业内简称 SK Cookie）维持登录态。这个 cookie 有 TTL，通常几个小时后过期。&lt;/p&gt;
&lt;p&gt;如果你在打开支付页面之前获取的 cookie，到填完表单点提交的时候刚好过期——恭喜，踩中了时间窗口。自动化场景下尤其常见：任务在队列里排了一段时间，等轮到你的时候 cookie 已经不新鲜了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② Session 被踢（多端互踢）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;同一个账号如果在多个地方同时登录（比如你开了好几个 Worker 共用一个 cookie），后登录的会话会使前一个失效。你在 Worker A 上正准备提交支付，Worker B 刚好用同一个 cookie 做了一次请求刷新了 session——A 的 session 就死了。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ 代理 IP 切换（Session-IP 绑定）&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Anthropic 很可能在 session 上绑定了创建时的 IP 或 IP 段。如果你的代理在支付流程中间断线重连，换了一个出口 IP，后端一比对——这个 session 是从 &lt;code&gt;45.67.xx.xx&lt;/code&gt; 创建的，现在请求来自 &lt;code&gt;89.12.xx.xx&lt;/code&gt;？验证失败。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;④ Stripe PaymentIntent 过期&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;即使 session 没问题，Stripe 侧也有自己的超时机制。&lt;code&gt;PaymentIntent&lt;/code&gt; 和对应的 &lt;code&gt;client_secret&lt;/code&gt; 有有效期（通常数小时，但在高风控场景下可能更短）。如果从打开支付页面到实际提交间隔太长，Intent 过期了，Stripe 返回错误，前端展示为&amp;quot;发生了意外错误&amp;quot;。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⑤ Checkout Session 并发冲突&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;同一个 Organization 如果同时发起了多个订阅操作（比如一个在升级 Pro，另一个在改支付方式），后一个操作创建的 &lt;code&gt;checkout session&lt;/code&gt; 会使前一个失效。前一个提交的时候引用的 session ID 已经不存在了。&lt;/p&gt;
&lt;h3 id="应对策略"&gt;应对策略
&lt;/h3&gt;&lt;p&gt;针对这类会话失效问题，无论是做自动化还是做安全防护，都有几条通用原则：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;从攻击者/自动化角度看：&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;策略&lt;/th&gt;
 &lt;th&gt;做法&lt;/th&gt;
 &lt;th&gt;解决的问题&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Session 预检&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;进支付页之前先调 &lt;code&gt;/api/auth/session&lt;/code&gt; 确认 cookie 存活&lt;/td&gt;
 &lt;td&gt;Cookie 过期&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;快进快出&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;打开支付页后立刻完成填写和提交，压缩停留时间&lt;/td&gt;
 &lt;td&gt;Intent 超时&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;代理钉住&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;支付全流程内锁定同一个代理出口，不允许中途切换&lt;/td&gt;
 &lt;td&gt;IP 绑定&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Cookie 生命周期管理&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;记录每个 cookie 的获取时间，临近 TTL 时主动刷新&lt;/td&gt;
 &lt;td&gt;Cookie 过期&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;错误分类重试&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;检测到&amp;quot;清除旧会话&amp;quot;后标记为 session 失效，重新获取 cookie 再试&lt;/td&gt;
 &lt;td&gt;所有会话类问题&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;单账号串行化&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;同一个 org 同时只跑一个支付操作&lt;/td&gt;
 &lt;td&gt;并发冲突&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;从防御者角度看：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这个报错机制本身就是 Anthropic 的一层防线——在处理支付之前先验会话，避免在认证不确定的情况下创建订阅。这个设计是对的。&lt;/p&gt;
&lt;p&gt;但如果要更进一步：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Session 绑定 + 支付确认二次绑定&lt;/strong&gt;：不光 session 绑 IP，&lt;code&gt;PaymentIntent&lt;/code&gt; 也应该绑定创建时的 session ID。Session 变了，Intent 就失效——即使攻击者拿到了一个新的有效 session，也得重新走一遍支付页面，不能复用之前的 Intent。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;支付提交时的实时 Session 刷新&lt;/strong&gt;：在提交支付的瞬间，后端不仅校验 session 是否有效，还可以强制刷新一次 token，确保提交时的 session 是&amp;quot;活跃&amp;quot;的而不仅仅是&amp;quot;未过期&amp;quot;的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;异常模式检测&lt;/strong&gt;：如果一个 org 短时间内反复出现&amp;quot;session 校验通过 → 进入支付页 → 提交时 session 失效&amp;quot;的模式，说明有人在用自动化工具——标记为可疑行为。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="小结"&gt;小结
&lt;/h3&gt;&lt;p&gt;这个&amp;quot;验证失败&amp;quot;的报错看着烦人，但它揭示了一个重要的架构细节：&lt;strong&gt;Anthropic 的支付流不是单纯的前端-Stripe 双边交互，中间有一层 session 校验网关。&lt;/strong&gt; 这层网关既是防线（挡住了过期/伪造的会话），也是自动化场景下最常踩的坑。&lt;/p&gt;
&lt;p&gt;理解了这个机制，不管你是想加固自己平台的支付安全，还是想搞清楚为什么自己的自动化流程老是翻车——这个报错里都有答案。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="七写在最后"&gt;七、写在最后
&lt;/h2&gt;&lt;p&gt;焚决这个东西，技术上说穿了就是一个&lt;strong&gt;客户端状态注入&lt;/strong&gt;工具。它不攻击 Claude 的认证系统，不碰 Anthropic 的服务器，攻击目标是&lt;strong&gt;前端与后端之间那条看不见的信任边界&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;它的精妙不在于代码多复杂——说真的 200 行 JS 连个正经项目的单元测试都撑不满——而在于它精准地捏住了两个系统设计缺陷的交叉点：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Anthropic 的前端支付流由一个可篡改的 API 响应驱动&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SEPA 直接借记在清算完成前不验证账户真实性&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;两个缺陷单独拿出来，各自都不算致命。但组合在一起，就变成了一条从&amp;quot;修改一个 JSON 字段&amp;quot;到&amp;quot;零元订阅 Claude Max&amp;quot;的完整攻击链。&lt;/p&gt;
&lt;p&gt;而这类&amp;quot;信任边界错位 + 异步确认间隙&amp;quot;的组合漏洞，绝不仅限于 Claude。任何一个同时提供信用卡和 SEPA 支付的 SaaS 平台，如果支付通道的选择权交给了前端，都可能面临同样的问题。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;工具可以被修复，模式值得被记住。&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="相关阅读"&gt;相关阅读
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.7qiang.com/posts/gpt-plus-exploit-revenuecat-vulnerability/" target="_blank" rel="noopener"
 &gt;GPT Plus 订阅漏洞深度分析 — Google Play Billing 鉴权缺失导致 0 元订阅&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.7qiang.com/posts/gpt-plus-receipt-vulnerability-2026/" target="_blank" rel="noopener"
 &gt;GPT Plus 收据复用漏洞 — iOS 收据验证缺陷&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.7qiang.com/posts/manual-sec-20260519-cloudflare-billing-race-condition/" target="_blank" rel="noopener"
 &gt;Cloudflare 计费逻辑缺陷 — 请求重放绕过订阅支付&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;标签&lt;/strong&gt;: #Claude #支付安全 #API劫持 #客户端安全 #SEPA #Tampermonkey #漏洞分析 #焚决&lt;/p&gt;</description></item><item><title>Google Play 内购 CC Max 漏洞深度拆解 — 一个 Bundle.putInt 就能白嫖 $250/月的 Claude Max</title><link>https://blog.7qiang.com/posts/google-play-ccmax-proration-vulnerability-2026/</link><pubDate>Wed, 22 Jul 2026 10:00:00 +0800</pubDate><guid>https://blog.7qiang.com/posts/google-play-ccmax-proration-vulnerability-2026/</guid><description>&lt;img src="https://blog.7qiang.com/blog-cover-cyber-mask.gif" alt="Featured image of post Google Play 内购 CC Max 漏洞深度拆解 — 一个 Bundle.putInt 就能白嫖 $250/月的 Claude Max" /&gt;&lt;section class="xiaoyao-contact-card" aria-label="联系逍遥"&gt;
 &lt;img class="xiaoyao-contact-card__qr" src="https://blog.7qiang.com/contact-qr.png" width="690" height="690" loading="lazy" decoding="async" alt="逍遥微信二维码"&gt;
 &lt;div class="xiaoyao-contact-card__content"&gt;
 &lt;h3 class="xiaoyao-contact-card__title"&gt;联系逍遥&lt;/h3&gt;
 &lt;p class="xiaoyao-contact-card__description"&gt;扫码添加微信，或点击下方按钮复制联系方式。&lt;/p&gt;
 &lt;div class="xiaoyao-contact-buttons" data-contact-buttons&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--wechat" type="button" data-copy-contact="plus6566" data-copy-label="微信号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;微信&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;plus6566&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--qq" type="button" data-copy-contact="826215906" data-copy-label="QQ 号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;QQ&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;826215906&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;span class="xiaoyao-contact-feedback" role="status" aria-live="polite"&gt;&lt;/span&gt;
&lt;/div&gt;

 &lt;/div&gt;
&lt;/section&gt;

&lt;p&gt;进微信群请联系博主，各位觉得文章对你有帮助的话可否打赏一些呀~&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;⚠️ 免责声明&lt;/strong&gt;：本文仅供安全研究与技术讨论。本文描述的漏洞&lt;strong&gt;已于 2026 年 7 月被修复&lt;/strong&gt;。任何利用类似手段对正式服务进行未授权操作属违法行为，后果自负。&lt;/p&gt;
&lt;p&gt;部分思路来自 &lt;a class="link" href="https://cctest.ai" target="_blank" rel="noopener"
 &gt;cctest.ai&lt;/a&gt; 作者，在此致谢。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="0x00-背景20-的-pro250-的-max中间差了一个-hook"&gt;0x00 背景：$20 的 Pro，$250 的 Max，中间差了一个 hook
&lt;/h2&gt;&lt;p&gt;Claude 的 Android 客户端走 Google Play 内购（Google Play Billing Library）处理订阅。价格梯度大概是这样：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;方案&lt;/th&gt;
 &lt;th&gt;月费&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Pro&lt;/td&gt;
 &lt;td&gt;$20&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Max 5x&lt;/td&gt;
 &lt;td&gt;$125&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Max 20x&lt;/td&gt;
 &lt;td&gt;$250&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;正常流程：你是 Pro 用户，想升级到 Max，Google Play 会按剩余天数计算差价，立刻从你的信用卡上扣。&lt;/p&gt;
&lt;p&gt;但如果我告诉你，有个参数决定了 Google Play 对这笔&amp;quot;升级&amp;quot;收不收钱呢？&lt;/p&gt;
&lt;p&gt;而且这个参数，就是 Android Bundle 里一个普普通通的 &lt;code&gt;putInt&lt;/code&gt; 调用？&lt;/p&gt;
&lt;h2 id="0x01-prorationmodegoogle-play-里最被低估的攻击面"&gt;0x01 prorationMode：Google Play 里最被低估的攻击面
&lt;/h2&gt;&lt;p&gt;Google Play Billing Library 在处理订阅升/降级时，有个参数叫 &lt;code&gt;prorationMode&lt;/code&gt;（比例分配模式），定义在 &lt;code&gt;BillingFlowParams.SubscriptionUpdateParams&lt;/code&gt; 里。它决定了用户切换订阅方案时的扣费逻辑：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;值&lt;/th&gt;
 &lt;th&gt;常量名&lt;/th&gt;
 &lt;th&gt;行为&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;0&lt;/td&gt;
 &lt;td&gt;UNKNOWN_SUBSCRIPTION_UPGRADE_DOWNGRADE_POLICY&lt;/td&gt;
 &lt;td&gt;未知&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;WITH_TIME_PRORATION&lt;/td&gt;
 &lt;td&gt;按剩余时间折算差价，&lt;strong&gt;立刻扣费&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;CHARGE_PRORATED_PRICE&lt;/td&gt;
 &lt;td&gt;按比例收取差价&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;3&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;WITHOUT_PRORATION&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;立刻升级，本周期不收费，下个周期按新价扣&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;CHARGE_FULL_PRICE&lt;/td&gt;
 &lt;td&gt;立刻全额扣费&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;td&gt;DEFERRED&lt;/td&gt;
 &lt;td&gt;延迟到下个周期才生效&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;重点关注 &lt;code&gt;3 — WITHOUT_PRORATION&lt;/code&gt;：Google 官方文档里写得很清楚，这个模式的设计意图是&amp;quot;让用户立刻获得高级功能，但推迟扣费到下个计费周期&amp;quot;。本来是给某些业务场景用的（比如试用期升级），但如果被滥用……&lt;/p&gt;
&lt;p&gt;Claude 的客户端代码里，升级订阅时用的是模式 &lt;code&gt;1&lt;/code&gt;（WITH_TIME_PRORATION），也就是正常的按比例扣差价。它通过 &lt;code&gt;Bundle.putInt(&amp;quot;prorationMode&amp;quot;, 1)&lt;/code&gt; 把这个值传给 Google Play 的计费 Activity。&lt;/p&gt;
&lt;p&gt;这个 &lt;code&gt;Bundle.putInt&lt;/code&gt; 调用发生在客户端侧。客户端侧的东西，你懂的。&lt;/p&gt;
&lt;h2 id="0x02-攻击链frida-一条龙"&gt;0x02 攻击链：Frida 一条龙
&lt;/h2&gt;&lt;p&gt;整个攻击链其实非常简洁，所有操作都在模拟器上完成。用到的工具：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MuMu 模拟器&lt;/strong&gt;（x86_64，因为要跑 Frida server 的 x86 版本）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;KernelSU&lt;/strong&gt;（MuMu 自带，用来获取 root）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Frida 17.16.0&lt;/strong&gt;（动态插桩框架）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;特定版本的 Claude APK&lt;/strong&gt;（v1.260330.27，在 Uptodown 可以下到历史版本）&lt;/li&gt;
&lt;li&gt;一个能扣 $20 的美区 Google 账号&lt;/li&gt;
&lt;li&gt;家宽 IP（避免 Google 风控拦截）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;步骤拆解：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第一步：搭环境&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;MuMu 模拟器装好后，用它自带的 Google 安装器装上 Google 套件 + Play 商店。打开 KernelSU Manager 给 Shell 授权 root。这步没啥技术含量，MuMu 对 Google 服务兼容性比 LDPlayer 好不少。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第二步：准备 Claude APK&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这里有个前置问题——你不能直接从 Play 商店装 Claude。&lt;/p&gt;
&lt;p&gt;现在的 Android 应用普遍使用 App Bundle（AAB）格式发布，Play 商店下发的是 split APK：base.apk 加一堆按设备拆分的 config splits（ABI、密度、语言）。x86_64 模拟器拿到的 splits 跟 ARM 真机不一样，而且 split APK 不能直接 &lt;code&gt;adb install&lt;/code&gt; 侧载。&lt;/p&gt;
&lt;p&gt;所以需要一个&lt;strong&gt;经过修补、移除了 split 限制的完整 APK&lt;/strong&gt;。实测可用的版本是 &lt;code&gt;v1.260330.27&lt;/code&gt;，从 Uptodown 等第三方市场可以找到历史版本的完整包。关键词：“已修补 split 限制”——就是把 App Bundle 重新打包成单个 APK，绕过 Play 商店的分发限制。&lt;/p&gt;
&lt;p&gt;还有一个连锁问题：修补过的 APK 签名跟 Google Play 原版不一致（相当于 debug 签名），&lt;strong&gt;Google OAuth 登录会直接拒绝&lt;/strong&gt;——点 “Continue with Google” 没反应或者报错。解决办法很简单：用邮箱登录（Continue with email），输入 Claude 账号的邮箱和密码，完全不走 OAuth。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第三步：部署 Frida&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb push frida-server-17.16.0-android-x86_64 /data/local/tmp/frida-server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb shell chmod &lt;span class="m"&gt;755&lt;/span&gt; /data/local/tmp/frida-server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb shell su -c &lt;span class="s2"&gt;&amp;#34;setenforce 0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb shell su -c &lt;span class="s2"&gt;&amp;#34;/data/local/tmp/frida-server -D &amp;amp;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;MuMu 有个坑：它的 ADB 端口不是标准 5555，而是 5557（多开的话 5559、5561 递增）。另外 MuMu 用 houdini 做 ARM 翻译，所以 Frida 必须用 &lt;strong&gt;attach 模式&lt;/strong&gt;，不能用 spawn 模式（&lt;code&gt;-f&lt;/code&gt; 参数），否则 houdini 翻译层直接 SIGSEGV 崩掉。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第四步：注入 Hook&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;核心的 hook 脚本其实就干了一件事——拦截 &lt;code&gt;Bundle.putInt&lt;/code&gt;，把 key 为 &lt;code&gt;prorationMode&lt;/code&gt; 的值从 1 改成 3：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;Bundle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Java&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;android.os.Bundle&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;putInt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;overload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;java.lang.String&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;int&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;implementation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;prorationMode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[*] 拦截 prorationMode: &amp;#34;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34; → 3 [WITHOUT_PRORATION]&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;putInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// 就这一行，价值 $230/月
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;putInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;注意启动方式——必须先手动打开 Claude，再用 &lt;code&gt;-H&lt;/code&gt; 连接（而不是 &lt;code&gt;-U&lt;/code&gt;），因为 MuMu 的 USB 层跟标准 Android 不一样。而且 Frida 的进程名参数必须用&lt;strong&gt;显示名 &lt;code&gt;Claude&lt;/code&gt;&lt;/strong&gt;，不能用包名 &lt;code&gt;com.anthropic.claude&lt;/code&gt;——MuMu 上 &lt;code&gt;-H&lt;/code&gt; 模式下 Frida 按包名是找不到进程的，只认 app_process 里注册的显示名：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;adb forward tcp:27042 tcp:27042
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;frida -H 127.0.0.1:27042 Claude -l hook-replacement-mode.js
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 注意：用 com.anthropic.claude 会报 &amp;#34;unable to find process&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里还有个细节：脚本额外拦截了 &lt;code&gt;SentryNdk.loadNativeLibraries&lt;/code&gt; 和 &lt;code&gt;SentryAndroidNdk&lt;/code&gt; 的初始化方法，因为 Sentry 的 native crash 上报模块在 houdini 翻译层下也会崩。不拦截的话 App 直接闪退。&lt;/p&gt;
&lt;p&gt;除了核心的 prorationMode 拦截，脚本还 hook 了 &lt;code&gt;PurchaseReceipt&lt;/code&gt; 的构造函数，打印 &lt;code&gt;purchase_token&lt;/code&gt; 和 &lt;code&gt;organization_id&lt;/code&gt;。这个 org_id 是 Anthropic 后端用来标识组织/用户的唯一 ID（格式类似 &lt;code&gt;6a085918-8383-44e5-9538-c4c81c6e122b&lt;/code&gt;），在规模化操作时用于追踪哪个 Claude 账号绑定了哪笔 Google Play 订阅——一个 Google 账号只能给一个 Claude 账号开通订阅，org_id 就是这个映射关系的 key。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第五步：走一遍正常支付&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;hook 注入成功后（控制台会显示 &lt;code&gt;Frida Hook 注入成功!&lt;/code&gt;，App 里也会弹 Toast），正常操作：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;用邮箱登录 Claude 账号（不能用 Google OAuth，原因见上）&lt;/li&gt;
&lt;li&gt;在模拟器里绑定支付卡（需要能扣 $20 的真实/虚拟信用卡）&lt;/li&gt;
&lt;li&gt;通过 Google Play 订阅 Pro（$20/月）—— 这笔钱是真扣的&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;第六步：触发升级&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pro 订阅成功后，直接在 App 里点&amp;quot;升级到 Max&amp;quot;。这时候 hook 就发挥作用了：&lt;/p&gt;
&lt;p&gt;控制台输出：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[*] 拦截 prorationMode: 1 [WITH_TIME_PRORATION] → 3 [WITHOUT_PRORATION]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;同时 &lt;code&gt;PurchaseReceipt&lt;/code&gt; 的 hook 会打印出新订阅的 org_id，确认升级已经走通了 Anthropic 后端。&lt;/p&gt;
&lt;p&gt;Google Play 的计费页面会显示：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;产品：Claude Max&lt;/li&gt;
&lt;li&gt;价格：$250.00/month（日区显示 JP¥42,500/month）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;开始日期：2026年8月20日&lt;/strong&gt;（注意，是下个月！）&lt;/li&gt;
&lt;li&gt;“我们将于 2026年8月20日向您收取第一笔费用”&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;看到了吗？因为 prorationMode 被改成了 &lt;code&gt;WITHOUT_PRORATION&lt;/code&gt;，Google Play 认为&amp;quot;当前不收费，下个计费周期再扣&amp;quot;。点击订阅，立刻升级到 Max，卡上不会多扣一分钱。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;第七步：善后&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;升级成功后，去 &lt;a class="link" href="https://payments.google.com" target="_blank" rel="noopener"
 &gt;payments.google.com&lt;/a&gt; 把这个订阅的支付方式换成余额钱包（Google Play 余额一般是 0）或者一张空的虚拟卡，然后解绑原来的支付卡。&lt;/p&gt;
&lt;p&gt;下个月续费的时候，余额不够或者虚拟卡扣费失败，订阅自然过期。但这一个月的 Max 你已经用上了。&lt;/p&gt;
&lt;p&gt;$20 的成本，享受 $250 的服务。利润率 1150%。&lt;/p&gt;
&lt;p&gt;有一个规模化的硬约束：&lt;strong&gt;一个 Google 账号只能给一个 Claude 账号开通订阅&lt;/strong&gt;。想批量撸就得不断囤 Google 号——这也是为什么这条灰产链的瓶颈不在技术，而在号源。&lt;/p&gt;
&lt;h2 id="0x03-为什么这个-bug-能成立"&gt;0x03 为什么这个 bug 能成立
&lt;/h2&gt;&lt;p&gt;从技术层面分析，这个漏洞能成立需要几个条件同时满足：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. prorationMode 由客户端决定&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;这是最根本的问题。Google Play Billing Library 的设计是把 &lt;code&gt;prorationMode&lt;/code&gt; 作为客户端参数传递给 Google Play 的计费系统。Google Play 本身不会校验&amp;quot;这个 App 有没有资格用 WITHOUT_PRORATION 模式&amp;quot;——它无条件信任客户端传来的值。&lt;/p&gt;
&lt;p&gt;这是一个经典的&lt;strong&gt;信任边界错误&lt;/strong&gt;：把安全关键参数的决定权放在了不可信的客户端侧。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. 服务端验证缺位&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Claude 的后端在收到 Google Play 的订阅回调时，主要验证的是&amp;quot;这个 purchase token 是否有效&amp;quot;和&amp;quot;订阅是否 active&amp;quot;。它不会去检查 &lt;code&gt;prorationMode&lt;/code&gt; 是什么——因为这个信息在服务端回调里根本就没有。Google Play 的 &lt;code&gt;purchases.subscriptions&lt;/code&gt; API 返回的数据里不包含当初用的是哪个 proration mode。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. 历史版本 APK 的可用性&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;新版 Claude 可能已经做了混淆或者改变了参数传递方式，但旧版本在 Uptodown 等第三方市场永远可以下到。只要 Google Play 的 Billing API 版本还兼容，旧 APK 照样能完成订阅操作。&lt;/p&gt;
&lt;h2 id="0x04-不用-hook-的路子速刷"&gt;0x04 不用 hook 的路子：速刷
&lt;/h2&gt;&lt;p&gt;值得一提的是，prorationMode hook 并不是唯一的利用方式。圈子里还流传一种叫&amp;quot;速刷&amp;quot;的打法——不需要 Frida，不需要 hook，纯靠旧版本客户端的某些行为差异来打时间差。&lt;/p&gt;
&lt;p&gt;具体原理不在本文展开（毕竟还没被修），但核心思路类似：利用 Google Play Billing Library 旧版本在处理订阅变更时的窗口期，在扣费确认到达之前完成订阅切换。跟 prorationMode hook 相比，速刷的门槛更低（不需要 root、不需要 Frida），但成功率也更低，而且强依赖特定的历史版本 APK。&lt;/p&gt;
&lt;p&gt;这两种方法本质上攻击的是同一个面：&lt;strong&gt;Google Play 对客户端行为的过度信任&lt;/strong&gt;。&lt;/p&gt;
&lt;h2 id="0x05-修复状态与后续"&gt;0x05 修复状态与后续
&lt;/h2&gt;&lt;p&gt;截至 2026 年 7 月，prorationMode hook 这个特定的攻击路径已被修复。从修复思路上猜测，可能的措施包括：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;客户端侧&lt;/strong&gt;：新版 Claude APK 不再使用 Bundle 明文传递 prorationMode，可能改用了加密或者服务端控制&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务端侧&lt;/strong&gt;：在处理升级回调时增加了额外验证（比如检查 &lt;code&gt;linkedPurchaseToken&lt;/code&gt; 的订阅金额是否合理）&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Google Play 侧&lt;/strong&gt;：Google 可能限制了 WITHOUT_PRORATION 模式的使用场景（需要应用显式在 Google Play Console 配置）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;但说实话，&lt;strong&gt;Google Play Billing 的这个攻击面远没有被堵死&lt;/strong&gt;。&lt;code&gt;prorationMode&lt;/code&gt; 只是其中一个参数，&lt;code&gt;BillingFlowParams&lt;/code&gt; 里还有其他可操控的字段。只要&amp;quot;客户端构建支付参数 → 传给 Google Play → Google Play 无条件执行&amp;quot;这个模型不变，同类问题就会持续出现。&lt;/p&gt;
&lt;p&gt;据了解，目前仍有针对其他参数或其他 App 的类似攻击手法在流通。Google Play 内购系统的安全模型，说白了就是建立在&amp;quot;客户端可信&amp;quot;这个脆弱假设上的。&lt;/p&gt;
&lt;h2 id="0x06-技术启示"&gt;0x06 技术启示
&lt;/h2&gt;&lt;p&gt;如果你是开发者，从这个漏洞可以学到：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;永远不要信任客户端传来的支付参数&lt;/strong&gt;。即使是通过 Google Play 这种&amp;quot;可信&amp;quot;中间人，底层的 &lt;code&gt;BillingFlowParams&lt;/code&gt; 仍然是客户端构造的。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;服务端必须独立验证订阅状态&lt;/strong&gt;。不要只检查 token 有效性，还要验证订阅的实际扣费金额（通过 &lt;code&gt;purchases.subscriptions&lt;/code&gt; 的 &lt;code&gt;priceAmountMicros&lt;/code&gt; 字段）与预期是否一致。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pin APK 版本&lt;/strong&gt;。如果你依赖客户端逻辑做安全控制，服务端应该检查客户端版本号，拒绝过旧版本的请求。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果你是安全研究者，这个 case 是一个很好的&amp;quot;API 信任边界&amp;quot;研究样本。Google Play Billing Library 把自己定位成&amp;quot;安全的支付中间件&amp;quot;，但它实际上信任了太多来自客户端的输入。类似 Frida hook &lt;code&gt;Bundle.putInt&lt;/code&gt; 这种低成本的攻击手段，在整个 Android 内购生态里适用面很广。&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;本文记录的是一个已修复的安全问题，旨在帮助开发者理解 Google Play 内购系统的安全边界。&lt;/p&gt;
&lt;p&gt;如果你对 Android 安全、支付系统攻防感兴趣，欢迎关注博客，后续会持续更新更多分析。&lt;/p&gt;

 &lt;/blockquote&gt;</description></item><item><title>Windsurf 开源反代工具：59 个 AI 模型一站式接入</title><link>https://blog.7qiang.com/posts/windsurf-api-proxy/</link><pubDate>Sun, 19 Apr 2026 14:45:00 +0800</pubDate><guid>https://blog.7qiang.com/posts/windsurf-api-proxy/</guid><description>&lt;img src="https://blog.7qiang.com/blog-cover-cyber-mask.gif" alt="Featured image of post Windsurf 开源反代工具：59 个 AI 模型一站式接入" /&gt;&lt;section class="xiaoyao-contact-card" aria-label="联系逍遥"&gt;
 &lt;img class="xiaoyao-contact-card__qr" src="https://blog.7qiang.com/contact-qr.png" width="690" height="690" loading="lazy" decoding="async" alt="逍遥微信二维码"&gt;
 &lt;div class="xiaoyao-contact-card__content"&gt;
 &lt;h3 class="xiaoyao-contact-card__title"&gt;联系逍遥&lt;/h3&gt;
 &lt;p class="xiaoyao-contact-card__description"&gt;扫码添加微信，或点击下方按钮复制联系方式。&lt;/p&gt;
 &lt;div class="xiaoyao-contact-buttons" data-contact-buttons&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--wechat" type="button" data-copy-contact="plus6566" data-copy-label="微信号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;微信&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;plus6566&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;button class="xiaoyao-contact-button xiaoyao-contact-button--qq" type="button" data-copy-contact="826215906" data-copy-label="QQ 号"&gt;
 &lt;span class="xiaoyao-contact-button__label"&gt;QQ&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__value"&gt;826215906&lt;/span&gt;
 &lt;span class="xiaoyao-contact-button__status" data-copy-status&gt;点击复制&lt;/span&gt;
 &lt;/button&gt;
 &lt;span class="xiaoyao-contact-feedback" role="status" aria-live="polite"&gt;&lt;/span&gt;
&lt;/div&gt;

 &lt;/div&gt;
&lt;/section&gt;

&lt;p&gt;进微信群请联系博主，各位觉得文章对你有帮助的话可否打赏一些呀~&lt;/p&gt;
&lt;hr&gt;

 &lt;blockquote&gt;
 &lt;p&gt;一分钟速读：WindsurfAPI 把 Windsurf IDE 的后端 Language Server 独立部署，封装成 OpenAI 兼容接口，支持 59 个大模型调用，自带账号管理和负载均衡。&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="项目背景"&gt;项目背景
&lt;/h2&gt;&lt;p&gt;Windsurf 是一款专注于 AI 辅助编程的集成开发环境（之前叫 Codeium），在开发者圈子里口碑不错。它内置的 Language Server 负责与各种大模型通信，让开发者能在 IDE 里直接调用 Claude、GPT 等模型的能力。&lt;/p&gt;
&lt;p&gt;一位 GitHub 开发者（@dwgx）发现，这个 Language Server 其实可以独立运行。于是他做了个有趣的尝试——把它从 IDE 里抽离出来，包装成标准的 OpenAI API 格式。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;项目仓库&lt;/strong&gt;：https://github.com/dwgx/WindsurfAPI&lt;/p&gt;
&lt;p&gt;这样一来，你不需要打开 Windsurf 编辑器，也能通过 HTTP 接口调用那些 AI 模型。任何支持 OpenAI 协议的工具或 SDK，改个配置就能接入。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="核心价值"&gt;核心价值
&lt;/h2&gt;&lt;h3 id="1-统一接口多模型切换"&gt;1. 统一接口，多模型切换
&lt;/h3&gt;&lt;p&gt;不同 AI 厂商的 API 格式各不相同，想换个模型用用，得重新研究文档、调整代码。WindsurfAPI 把这些差异都屏蔽了，你只需要改一个参数：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;model: &lt;span class="s2"&gt;&amp;#34;claude-opus-4-7-max&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# 切换模型&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;支持的模型阵容：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;厂商&lt;/th&gt;
 &lt;th&gt;可用模型&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Anthropic&lt;/td&gt;
 &lt;td&gt;Claude Opus 4.7 Max、4.6&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;OpenAI&lt;/td&gt;
 &lt;td&gt;GPT-5.4、4o-mini&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Google&lt;/td&gt;
 &lt;td&gt;Gemini 2.5 Flash&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;其他&lt;/td&gt;
 &lt;td&gt;DeepSeek、Grok、通义千问、Kimi 2.5、SWE&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="2-账号池与负载均衡"&gt;2. 账号池与负载均衡
&lt;/h3&gt;&lt;p&gt;单个账号的调用次数有限制，团队协作或高频使用时很容易触顶。WindsurfAPI 允许多个账号组成资源池：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;自动在账号间轮换请求&lt;/li&gt;
&lt;li&gt;某个账号额度不足时自动切换&lt;/li&gt;
&lt;li&gt;模型级别的故障不影响其他调用&lt;/li&gt;
&lt;li&gt;实时监控账号状态，提前预警&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从调用方的角度看，就像有一个「无限额度」的账号在背后支撑。&lt;/p&gt;
&lt;h3 id="3-零外部依赖"&gt;3. 零外部依赖
&lt;/h3&gt;&lt;p&gt;这个项目最让人印象深刻的是代码风格——完全不依赖第三方 npm 包，全部使用 Node.js 原生模块：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP 服务：&lt;code&gt;http&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;文件系统：&lt;code&gt;fs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;路径处理：&lt;code&gt;path&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;环境变量：&lt;code&gt;process.env&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;gRPC 通信：内置实现&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;在 JavaScript 生态里，这种「洁癖式」的写法相当罕见。好处也很明显：没有供应链攻击风险，部署简单，维护成本低。&lt;/p&gt;
&lt;h3 id="4-web-管理界面"&gt;4. Web 管理界面
&lt;/h3&gt;&lt;p&gt;访问 &lt;code&gt;http://你的服务器:3003/dashboard&lt;/code&gt; 可以打开管理后台：&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;账号管理功能&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;添加或删除 Windsurf 账号&lt;/li&gt;
&lt;li&gt;启用/禁用特定账号&lt;/li&gt;
&lt;li&gt;查看各账号的使用统计&lt;/li&gt;
&lt;li&gt;批量导入 Token&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;监控仪表盘&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实时请求量&lt;/li&gt;
&lt;li&gt;平均响应时间&lt;/li&gt;
&lt;li&gt;错误率变化趋势&lt;/li&gt;
&lt;li&gt;各模型调用占比&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;代理与限流&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;全局 HTTP/SOCKS5 代理配置&lt;/li&gt;
&lt;li&gt;单账号独立代理设置&lt;/li&gt;
&lt;li&gt;模型访问白名单/黑名单&lt;/li&gt;
&lt;li&gt;请求频率限制&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;对于需要管理多个账号的场景，这个界面能省不少事。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="技术架构"&gt;技术架构
&lt;/h2&gt;&lt;p&gt;系统分为四个层次：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;客户端（OpenAI SDK / curl）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;WindsurfAPI（Node.js 服务，3003 端口）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Language Server（gRPC，42100 端口）
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Windsurf 官方后端
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关键接口 &lt;code&gt;/v1/chat/completions&lt;/code&gt; 和 &lt;code&gt;/v1/models&lt;/code&gt; 都已实现，兼容所有基于 OpenAI API 构建的工具：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OpenAI 官方 SDK（Python、Node.js、Go 等语言）&lt;/li&gt;
&lt;li&gt;LangChain 框架&lt;/li&gt;
&lt;li&gt;LlamaIndex&lt;/li&gt;
&lt;li&gt;其他第三方封装库&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Python 调用示例：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;openai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;任意值&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# 实际验证由 WindsurfAPI 处理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;http://你的服务器:3003/v1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;completions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;claude-opus-4-7-max&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;role&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;user&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;你好&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="部署指南"&gt;部署指南
&lt;/h2&gt;&lt;h3 id="前置条件"&gt;前置条件
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Node.js 20 或更高版本&lt;/li&gt;
&lt;li&gt;至少一个 Windsurf 账号（免费版即可）&lt;/li&gt;
&lt;li&gt;Language Server 可执行文件（需自行提取）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="提取-language-server"&gt;提取 Language Server
&lt;/h4&gt;&lt;p&gt;该文件不包含在项目仓库中，需要从 Windsurf 官方安装包中提取：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 下载安装包&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget https://downloads.windsurf.com/windsurf-linux.deb
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 解压&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;dpkg-deb -x windsurf-linux.deb windsurf-extracted
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 定位文件&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;find windsurf-extracted -name &lt;span class="s2"&gt;&amp;#34;language_server_linux_x64&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="安装流程"&gt;安装流程
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 1. 获取项目&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://github.com/dwgx/WindsurfAPI.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; WindsurfAPI
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 2. 部署 Language Server&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p /opt/windsurf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cp language_server_linux_x64 /opt/windsurf/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod +x /opt/windsurf/language_server_linux_x64
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 3. 准备数据目录&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p /opt/windsurf/data/db
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 4. 设置环境变量&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt; .env &lt;span class="s"&gt;&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;PORT=3003
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;API_KEY=
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;DEFAULT_MODEL=gpt-4o-mini
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;MAX_TOKENS=8192
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;LOG_LEVEL=info
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;LS_BINARY_PATH=/opt/windsurf/language_server_linux_x64
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;LS_PORT=42100
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;DASHBOARD_PASSWORD=你的密码
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 5. 运行服务&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;node src/index.js
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;服务启动后监听 &lt;code&gt;http://0.0.0.0:3003&lt;/code&gt;。&lt;/p&gt;
&lt;h4 id="环境变量参考"&gt;环境变量参考
&lt;/h4&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;变量名&lt;/th&gt;
 &lt;th&gt;说明&lt;/th&gt;
 &lt;th&gt;默认值&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;PORT&lt;/td&gt;
 &lt;td&gt;HTTP 服务端口&lt;/td&gt;
 &lt;td&gt;3003&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;API_KEY&lt;/td&gt;
 &lt;td&gt;API 访问密钥（可选）&lt;/td&gt;
 &lt;td&gt;空&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;DEFAULT_MODEL&lt;/td&gt;
 &lt;td&gt;默认使用的模型&lt;/td&gt;
 &lt;td&gt;gpt-4o-mini&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;MAX_TOKENS&lt;/td&gt;
 &lt;td&gt;单次请求最大 token 数&lt;/td&gt;
 &lt;td&gt;8192&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;LOG_LEVEL&lt;/td&gt;
 &lt;td&gt;日志详细程度&lt;/td&gt;
 &lt;td&gt;info&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;LS_BINARY_PATH&lt;/td&gt;
 &lt;td&gt;Language Server 文件路径&lt;/td&gt;
 &lt;td&gt;必填&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;LS_PORT&lt;/td&gt;
 &lt;td&gt;Language Server 端口&lt;/td&gt;
 &lt;td&gt;42100&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;DASHBOARD_PASSWORD&lt;/td&gt;
 &lt;td&gt;管理后台登录密码&lt;/td&gt;
 &lt;td&gt;必填&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="进程守护pm2"&gt;进程守护（PM2）
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 安装 PM2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install -g pm2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 启动应用&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pm2 start src/index.js --name windsurf-api
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 持久化配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pm2 save
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 配置开机自启&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pm2 startup
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="重启的正确方式"&gt;重启的正确方式
&lt;/h4&gt;&lt;p&gt;&lt;strong&gt;不要使用 &lt;code&gt;pm2 restart&lt;/code&gt;&lt;/strong&gt;，这会导致 Language Server 进程残留成为僵尸进程。&lt;/p&gt;
&lt;p&gt;推荐做法：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pm2 stop windsurf-api
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pm2 delete windsurf-api
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fuser -k 3003/tcp 2&amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sleep &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pm2 start src/index.js --name windsurf-api --cwd /root/WindsurfAPI
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;先完全停止并删除进程，清理端口占用，再重新启动。&lt;/p&gt;
&lt;h3 id="网络配置"&gt;网络配置
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Ubuntu 防火墙&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ufw allow 3003/tcp
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# CentOS 防火墙&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;firewall-cmd --add-port&lt;span class="o"&gt;=&lt;/span&gt;3003/tcp --permanent
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;firewall-cmd --reload
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 云服务器：在安全组规则中开放 3003 端口&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="配置-windsurf-账号"&gt;配置 Windsurf 账号
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 单个账号（使用 Token）&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -X POST http://localhost:3003/auth/login &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s2"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;token&amp;#34;: &amp;#34;你的 windsurf-token&amp;#34;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 批量添加&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -X POST http://localhost:3003/auth/login &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s2"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;accounts&amp;#34;: [{&amp;#34;token&amp;#34;: &amp;#34;token1&amp;#34;}, {&amp;#34;token&amp;#34;: &amp;#34;token2&amp;#34;}]}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="获取-token"&gt;获取 Token
&lt;/h4&gt;&lt;ol&gt;
&lt;li&gt;启动 Windsurf IDE&lt;/li&gt;
&lt;li&gt;进入设置（Settings）&lt;/li&gt;
&lt;li&gt;找到 Account 或 Profile 页面&lt;/li&gt;
&lt;li&gt;复制 Token 字符串&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;如果找不到 Token 选项，也可以使用账号密码方式登录，但 Token 方式更安全便捷。&lt;/p&gt;
&lt;h3 id="验证部署"&gt;验证部署
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl http://localhost:3003/v1/chat/completions &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s2"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;model&amp;#34;: &amp;#34;gpt-4o-mini&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;messages&amp;#34;: [{&amp;#34;role&amp;#34;: &amp;#34;user&amp;#34;, &amp;#34;content&amp;#34;: &amp;#34;你好&amp;#34;}],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; &amp;#34;stream&amp;#34;: false
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;收到正常响应即表示部署成功。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="使用须知"&gt;使用须知
&lt;/h2&gt;&lt;h3 id="开源协议"&gt;开源协议
&lt;/h3&gt;&lt;p&gt;项目采用 MIT 许可证，但作者在 README 中附加了额外条款：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;禁止商业用途（除非获得书面授权）&lt;/li&gt;
&lt;li&gt;禁止转售或代部署服务&lt;/li&gt;
&lt;li&gt;个人学习和研究不受限制&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;使用前请仔细阅读相关条款。&lt;/p&gt;
&lt;h3 id="安全建议"&gt;安全建议
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;管理后台密码必须设置&lt;/strong&gt;：&lt;code&gt;DASHBOARD_PASSWORD&lt;/code&gt; 环境变量不要留空，否则任何人都能访问你的后台并查看账号信息。&lt;/p&gt;
&lt;h3 id="代理支持"&gt;代理支持
&lt;/h3&gt;&lt;p&gt;如果服务器网络需要代理，可以在管理后台配置：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 全局代理配置&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -X POST http://localhost:3003/proxy/config &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s2"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;type&amp;#34;: &amp;#34;http&amp;#34;, &amp;#34;host&amp;#34;: &amp;#34;proxy.example.com&amp;#34;, &amp;#34;port&amp;#34;: 8080}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 单账号代理&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -X POST http://localhost:3003/account/1/proxy &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -H &lt;span class="s2"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -d &lt;span class="s1"&gt;&amp;#39;{&amp;#34;type&amp;#34;: &amp;#34;socks5&amp;#34;, &amp;#34;host&amp;#34;: &amp;#34;proxy.example.com&amp;#34;, &amp;#34;port&amp;#34;: 1080}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;支持 HTTP 和 SOCKS5 两种协议，可以全局配置也可以为单个账号单独设置。&lt;/p&gt;
&lt;h3 id="模型访问权限"&gt;模型访问权限
&lt;/h3&gt;&lt;p&gt;Windsurf 对不同订阅等级的账号开放了不同的模型：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;免费账号&lt;/strong&gt;：gpt-4o-mini、gemini-2.5-flash 等基础模型&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pro 订阅&lt;/strong&gt;：Claude 系列、GPT-5.4、Kimi 2.5 等高级模型&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;具体可用模型列表可在管理后台查看。&lt;/p&gt;
&lt;h3 id="调用限额"&gt;调用限额
&lt;/h3&gt;&lt;p&gt;Windsurf 对账号实行周限额制度：&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;账号类型&lt;/th&gt;
 &lt;th&gt;周调用次数&lt;/th&gt;
 &lt;th&gt;备注&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;免费版&lt;/td&gt;
 &lt;td&gt;约 300 次&lt;/td&gt;
 &lt;td&gt;仅限基础模型&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Pro 版&lt;/td&gt;
 &lt;td&gt;约 2000 次&lt;/td&gt;
 &lt;td&gt;全部模型可用&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;多账号池的设计初衷就是为了解决这个问题。&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="结语"&gt;结语
&lt;/h2&gt;&lt;p&gt;开源社区的魅力在于，总有人能发现工具的新用法。&lt;/p&gt;
&lt;p&gt;把 IDE 的后端独立出来变成通用 API 代理，这种思路值得借鉴。如果你对这类项目感兴趣，不妨去 GitHub 上给作者点个 Star。&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;项目仓库&lt;/strong&gt;：https://github.com/dwgx/WindsurfAPI&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;本文仅做技术分享，请遵守开源协议和相关法律法规。&lt;/p&gt;</description></item></channel></rss>