Cursor Cloud Agent 的身份与隔离:它以谁的身份动你的仓库
安全评审桌上最常被问住的一个问题是:那个云端跑的 agent,到底”以谁的身份”在动我的仓库和我的云资源?这其实是两个问题,Cursor 官方文档也把它们放在两页里讲——cursor.com/docs/cloud-agent/security 讲它凭什么能碰到你的代码,cursor.com/docs/cloud-agent/identity 讲 VM 里的进程凭什么能换到你的云端凭证。拆开读容易得出互相矛盾的结论,合起来读边界才清楚。下面按一次 run 的实际路径走一遍,途中把关键字段标出来。
第一段路:它凭什么 clone 得到你的仓库
官方文档《Security overview》页写明,Cloud Agents 通过 Cursor 的 GitHub 或 GitLab App 访问代码,而不是通过某一个人的凭证。这里有两层:管理员在 Cursor 侧和 Git 侧都要有管理员权限,由他把 Cursor 的 app 装到 Git 组织上并只授权挑选出的那些仓库;app 装好后,每个想启动 agent 的用户还要各自连接自己的 Git 账号,这是叠在组织级安装之上的第二层。
真正决定边界的是文档里那句”访问是继承来的,绝不放大”(原文措辞是 access is inherited, never widened):Cloud Agent 只能触达触发它的那个用户本来就能触达的仓库,启动 agent 不会凭空拿到用户原本没有的权限,FAQ 里又把这条正面问了一遍并给出否定答案。想再收一层,文档给了两个企业侧开关:Protected Git Scopes 把某个 Git 组织锁定到你的 Cursor 组织,以及仓库 blocklist 直接排除敏感仓库,两项都在 cursor.com/docs/enterprise/model-and-integration-management 页。
文档还把一次 run 拆成六个阶段:Start(从 web、IDE、CLI、API、Slack 或关联的 issue/PR 启动)、Provision(开一台隔离 VM 并 clone 授权仓库)、Run、Persist(会话状态与产物存到 Cursor 托管存储)、Hand off、Recycle(闲置后按生命周期计时器休眠并删除运行时资源)。注意第五步:文档写明 agent 推分支后开的是 draft pull request,合并这件事仍在人手里。
第二段路:VM 里的进程拿什么身份去调云
仓库那条线走通了,还有一条:agent 在 VM 里跑测试、跑部署脚本时要访问 AWS、GCP 或你自己的内部服务。官方文档《OIDC tokens》页给的方案是不往 Secrets 里塞长期凭证,而是在 VM 内现场 mint 一个短时效的 OIDC JWT。mint 走本机 Unix socket,路径取自环境变量 CURSOR_AGENT_SOCKET,文档写明 Cursor 托管的 VM 上默认值是 /run/cursor/api.sock(这是文档写明的默认值,随版本可能变动)。文档给出的请求原样如下:
curl --unix-socket "${CURSOR_AGENT_SOCKET:-/run/cursor/api.sock}" \
-H 'Content-Type: application/json' \
-d '{"aud":"sts.amazonaws.com"}' \
http://cursor-agent/v1/tokens/oidc
请求是”HTTP over Unix socket”,文档明确说 URL 里的主机名会被忽略——所以 http://cursor-agent 不是要你去解析的域名。verifier 需要 replay 绑定时,再带一个 nonce:
curl --unix-socket "${CURSOR_AGENT_SOCKET:-/run/cursor/api.sock}" \
-H 'Content-Type: application/json' \
-d '{"aud":"https://oidc.example.com","nonce":"unpredictable-value"}' \
http://cursor-agent/v1/tokens/oidc
以上两条为官方文档中的原样示例,我们未做实测,以官方文档与 API 的实际响应为准。
请求体只有三个字段:aud 必填,是你的 verifier 要校验的 audience;nonce 可选,会原样回写进 JWT 的 nonce claim;sub_claim 可选,作用是把某个 claim 以 <name>:<value> 的形式投影进 sub,专门给那种只认 sub 和 aud 的 verifier 用。三个字段都有长度上限,请求体也有大小上限,超了会拿到对应的 400 或 413 错误码。
sub_claim 有个容易踩的地方:文档写明 discovery 里的 x_cursor_sub_claims_supported 列出当前支持的名字,“currently”(文档用词)只有 team_id,不支持的名字会被拒绝;而且这个 claim 对当前 agent 没有值时——比如个人账号根本没有 team_id——mint 直接失败,不会回落到默认 subject。同一段脚本在团队账号下跑得通、在个人账号下硬报错,这不是网络抖动。另外文档里位置靠后但很关键的一句:安装脚本(install scripts)也能在同一个 socket 上 mint。
token 里写了什么,决定你能把授权收多细
JWT 头是 alg=RS256、typ=JWT,外加 kid。claims 表里恒定存在的有 iss、sub、aud、iat、nbf、exp、jti、cloud_agent_id、agent_runtime。文档写明 Cursor 托管的 Cloud Agent VM 签出的 token,agent_runtime 一律是 managed。
真正拿来写策略的是那些”有值才出现”的 claim,几条坑集中在这里:
| Claim | 文档写明的语义与注意点 |
|---|---|
sub | 默认是 user:<id> 或 service_account:<id>;设了 sub_claim 时变成 <claim>:<value>(例如 team_id:123)。文档特意注明:不是邮箱 |
owner_email | 已知时才有。文档建议 allowlist 用 sub 或 owner_user_id,因为邮箱会变 |
repo_url | 主仓库,形如 github.com/acme/widgets,主机名小写,不带 scheme、凭证、端口、query 和 .git 后缀。多仓 agent 下它只代表主仓库 |
repo_urls | 工作区里的全部仓库,主仓库在前其余排序。只有在这个集合已知完整时才出现——缺失代表”集合不确定”,不代表”只有一个仓库” |
repo_count | repo_urls 里的条目数,出现时机与 repo_urls 完全一致。verifier 只能匹配单值时,用 repo_count == 1 配合 repo_url |
turn_id / turn_start | 只有在一个 coding turn 活跃时才有。文档提醒 turn_id 与 cloud_agent_id(即 bcId)不是一回事 |
branch_name | 直到这次 run 记录了分支才有 |
source | agent 是怎么被启动的,文档举的值有 WEBSITE、API、SLACK、AUTOMATIONS |
automation_id | source 是 automations 时才有 |
把 mint 时机和这张表对起来看,就明白同一个 agent 前后两次 mint 出的 token 为什么 claim 不一样:文档明说 token 只包含 mint 那一刻有值的 claim,turn_id、turn_start 在 coding turn 开始前不存在,branch_name 在 run 记录分支前不存在,而 owner、team、repository 这几类从 agent 创建起就有值——所以安装脚本阶段 mint 出的 token 注定拿不到 turn 相关 claim。想把 agent 限制在特定仓库上,文档给的做法是用 repo_urls 钉死完整集合,而不是拿 repo_url 当白名单。
verifier 侧:拒绝错误 audience 是你的活
要发给身份提供方的三个 URL 是 issuer https://api.cursor.com、discovery https://api.cursor.com/.well-known/openid-configuration、JWKS https://api.cursor.com/keys。文档写明至少要校验:RS256 签名与 JWKS 里的 kid、iss、aud、nbf 与 exp(留一点时钟偏移余量,文档注明 nbf 早于 iat 5 秒),以及你策略要用的 sub 或别的 claim。
这里有一句必须划出来:Cursor 不对 audience 做 allowlist,文档原话是你的 verifier 必须拒绝预期之外的 aud 值。discovery 里带 x_cursor_audience_bound: true,意思是每个 token 都是为调用方给的那个 aud 签的。另外因为 token 是在 agent VM 上签出来的,discovery 文档里没有 authorization_endpoint 和 token_endpoint——对着标准 OIDC 清单逐项打勾的人会在这里卡一下。还有一条历史包袱:Cursor 仍在 https://api2.cursor.sh/cloud-agent/identity 提供第二份 discovery 文档,但文档写明新签出的 token 已不再带那个 issuer,verifier 应指向 https://api.cursor.com,存量配置指着旧地址的要单独排一遍。
token 有效期文档写得很短,并且没有 refresh 端点,过期就重新 mint。每台 agent VM 的 mint 有每分钟预算和突发上限,socket 的并发连接数也有上限、且与 agent metadata 接口共享——文档因此建议把 token 缓存到过期而不是每次调用都 mint(具体数值以官方文档为准)。错误处置的口径是:429、503、500、502、504 按退避重试(前两者遵守 Retry-After),403 视为致命错误,表示这个 agent 不被允许 mint。400、404、405、413、415 这类请求错误的响应体里还会附一个 usage 字符串重述完整请求契约,限流和过载类错误则只有错误码。
trust model 这一段建议读两遍
《OIDC tokens》页有一小节叫 Trust model,内容比篇幅重得多。文档写明:token 标识的是这一次 Cloud Agent 的 run,不是 VM 里的某个具体进程;任何能触达那个 socket 的进程都能 mint——agent 本身、agent 跑起来的代码、以及 hooks 都算,处置办法是按”你愿意授予这整次 run 的权限”来划范围。反过来,你也不能选择 token 是给哪个 agent 的,claims 由 Cursor 按当前这次 run 填充,VM 里的进程没法替另一个 agent 签 token。
把这段和《Security overview》里”Autonomy and prompt injection”那一节放在一起,含义就具体了:文档写明 Cloud Agent 会自动执行终端命令,以便迭代测试时不必每步等人批准,这比前台 agent 更自主,也改变了风险模型——有人把指令埋进 agent 会读到的内容里(prompt injection),可能诱导它把代码外传。而 mint 权限属于”整次 run”。这两处放在一起的结论只到这里:给这次 run 的云端权限,等于给这次 run 里跑起来的一切。
文档列出的遏制层有五条:网络出口控制(外发流量限制到默认集合加你的 allowlist,或只到 allowlist,企业管理员可全组织锁定)、Runtime Secrets(标记后其值会从 transcript、工具输出和 commit 中剥掉,不进模型)、用 .cursorignore 把敏感路径排除出上下文、draft PR 的人工把关,以及每个 agent commit 都用 HSM 托管的 Ed25519 key 签名并带 Verified 标记,可满足签名 commit 的分支保护。文档另外建议配合 hooks 在 agent 生命周期点上执行策略与记录活动。这些是文档列出的控制项,文档并没有声称它们能消除上述风险,我们也不做这种判断。
隔离到什么粒度,数据留在哪
文档写明每个 agent 跑在自己的 VM 边界内而不是共享的进程 sandbox,一个 agent 看不到另一个 agent 的代码、环境和状态;运行时工作区跑在基于 Firecracker 的 microVM 基础设施上;Cloud Agent 的 VM 位于与 Cursor 其余生产设施不同的 AWS 账号中。加密上传输用 TLS 1.2 或更高,静态用 AES-256 且每个 agent 一把 key,企业团队可映射自管 KMS key(CMEK/BYOK)。
数据被分成四类各走各的保留规则:运行时工作区在隔离 VM 内、run 闲置后自动回收(追加提示会刷新计时器);VM 快照含 clone 下来的代码,加密存在活动 VM 之外的快照缓存层,按不活动的滚动窗口自动删除、每次启动或恢复都续期;会话状态存在 Cursor 后端并用每 agent 的 key 加密,默认长期保留、可按需删除;secrets 与 token 存在加密凭证库直到你删除。这里有个不对称要记牢:Delete Agent API 能按需移除 transcript 与产物,但文档明说快照不能按需删除,只能等那个不活动窗口到期。另外 Legacy Privacy Mode 对 Cloud Agent 不支持,理由文档自述是 agent 运行期间必须把代码与环境数据存在云上。
Windows 侧的两句话
上面那两条 curl --unix-socket 命令是在 agent 自己的 VM 内部执行的,文档也写明 agent 会用它的终端工具调这个 API,你不需要自己发这些请求。你在自己机器上真正要跑的只有验证侧那两条:
curl -sS https://api.cursor.com/.well-known/openid-configuration
curl -sS https://api.cursor.com/keys
Windows 上要注意的是执行环境本身:PowerShell 里的 curl 与这里说的 curl 不是同一个东西,建议在 Git Bash 或 WSL 里执行上面的命令,或改用你惯用的 HTTP 客户端。至于 --unix-socket 那两条,它连的是 agent VM 内的本机 socket,在你自己的 Windows 机器上没有对应对象,套用没有意义。本段是通用做法,不是 Cursor 官方文档的内容,官方文档里没有针对 Windows 侧执行环境的说明。
最后留一个没被回答的问题
把两页拼起来,有一处是文档没有说明的:如何在 VM 内部限定”哪些进程可以访问那个 socket”。Trust model 那一节反而是从正面写死的——任何能触达 socket 的进程都能 mint。所以这套机制的落点不在”VM 内做进程级隔离”,而是回到 IAM 侧,用 sub、team_id、cloud_agent_id、repo_urls 这些 claim 把可被换取的权限压到最小。文档给的 AWS 例子就是这个思路:AWS 信任策略只匹配 aud 和 sub,要按团队授信就得 mint 时带 "sub_claim":"team_id",让投影后的 subject 去匹配:
"StringEquals": {
"api.cursor.com:aud": "sts.amazonaws.com",
"api.cursor.com:sub": "team_id:123"
}
以上为官方文档中的原样示例片段(其中的 id 是文档示例值),我们未做实测,以官方文档与 AWS 侧的实际校验结果为准。文档同时提醒:mint 只走本机 socket,但拿 JWT 去和 AWS、GCP、Azure 或你自己的服务换凭证仍需要出网权限——启用了网络 allowlist 就别忘了把 sts.amazonaws.com 以及你会调用的区域 STS 主机放进去。排查”token 明明签出来了却换不到角色”时,这条最先该看。
本文依据 Cursor 官方文档(cursor.com/docs 与 cursor.com/help)于 2026-08-18 的公开内容整理。
该产品闭源,本文只复述官方文档写明的机制,不推断其内部实现;
我们没有对文中涉及的功能做过实测,因此不涉及界面外观、操作手感与运行速度的任何描述。
该产品迭代频繁,文中涉及的设置项与命令随版本变动,请以官方文档最新内容为准。
本文不涉及订阅价格、额度与模型清单,相关信息请以官方定价与模型说明页为准。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。
合规与许可条款请以官方原文与你所在组织的要求为准,本文不构成法律意见。