@clawos-dev/clawd 0.2.539 → 0.2.541

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -50195,11 +50195,12 @@ var DEFAULT_PERSONAS = [
50195
50195
  // 与 app-builder 对齐"调用阿里云的工作方式"(owner 2026-06-17 要求):开发型 profile —— 放开文件/web
50196
50196
  // 工具 + node/pnpm 工具链精确路径 + 出站 TLD 通配白名单。html-slides 需要联网(git clone frontend-slides
50197
50197
  // skill、调阿里云 CLI/SDK)。
50198
- // 阿里云凭证:**复用共享 deploy-kit 单源**($HOME/.clawd/deploy-kit/.secrets/aliyun.env,全 FC persona 共用、
50199
- // seedDeployKit 每次安装都铺),不在 persona 目录另存一份。app-builder 的 FC 部署走 daemon 侧脚本(沙箱外)
50200
- // 故其 profile 无需 carve 这个路径;html-slides 没有 daemon 侧流程、要在 cc 沙箱内直接 `set -a; . $HOME/.clawd/
50201
- // deploy-kit/.secrets/aliyun.env; set +a` 后调 aliyun CLI(env 凭证绕过沙箱外的 ~/.aliyun/),所以这里**额外**把
50202
- // 这个凭证文件加进 allowRead(denyRead '~/' 之上的精确凿洞,单文件、最小暴露,同 ~/.npmrc 先例)。
50198
+ // 阿里云凭证:**复用共享 deploy-kit 单源**($HOME/.clawd/deploy-kit/.secrets/aliyun.env.local,使用者自己写、
50199
+ // 全 FC persona 共用;bundle 不再 ship AK,见 specs/2026-09-18-aliyun-ak-leak-rotate-design.md),不在 persona
50200
+ // 目录另存一份。app-builder 的 FC 部署走 daemon 侧脚本(沙箱外)故其 profile 无需 carve 这个路径;html-slides
50201
+ // 没有 daemon 侧流程、要在 cc 沙箱内直接 `set -a; . $HOME/.clawd/deploy-kit/.secrets/aliyun.env.local; set +a`
50202
+ // 后调 aliyun CLI(env 凭证绕过沙箱外的 ~/.aliyun/),所以这里**额外**把这两个凭证文件加进 allowRead
50203
+ // (denyRead '~/' 之上的精确凿洞,单文件、最小暴露,同 ~/.npmrc 先例;aliyun.env 是老机器上的遗留文件)。
50203
50204
  // excludedCommands: ['aliyun *'] —— 额外把 aliyun CLI 放沙箱外跑(逃出 seatbelt 直接用宿主凭证/网络),
50204
50205
  // 与上面沙箱内 env 凭证方案并存,按运行时实际命令前缀匹配生效。
50205
50206
  sandboxProfile: {
@@ -50209,7 +50210,7 @@ var DEFAULT_PERSONAS = [
50209
50210
  },
50210
50211
  sandbox: {
50211
50212
  filesystem: {
50212
- allowRead: ["~/.npmrc", "~/Library/Preferences/pnpm", "~/.nvm", "~/Library/pnpm", "~/.local/share/pnpm", "~/.local/state/pnpm", "~/.npm", "~/.pnpm-store", "~/.clawd/deploy-kit/.secrets/aliyun.env"],
50213
+ allowRead: ["~/.npmrc", "~/Library/Preferences/pnpm", "~/.nvm", "~/Library/pnpm", "~/.local/share/pnpm", "~/.local/state/pnpm", "~/.npm", "~/.pnpm-store", "~/.clawd/deploy-kit/.secrets/aliyun.env", "~/.clawd/deploy-kit/.secrets/aliyun.env.local"],
50213
50214
  allowWrite: ["~/Library/pnpm", "~/.local/share/pnpm", "~/.local/state/pnpm", "~/.npm", "~/.pnpm-store"]
50214
50215
  },
50215
50216
  network: {
@@ -70910,7 +70911,7 @@ function computeMethodAccess(args) {
70910
70911
  }
70911
70912
 
70912
70913
  // src/version.ts
70913
- var version = "0.2.539".length > 0 ? "0.2.539" : "dev";
70914
+ var version = "0.2.541".length > 0 ? "0.2.541" : "dev";
70914
70915
 
70915
70916
  // src/cli-probe/probe.ts
70916
70917
  var fs78 = __toESM(require("fs"), 1);
@@ -1,4 +1,7 @@
1
- # 阿里云访问凭证(所有 FC persona 共用一份)。复制为同目录 aliyun.env 并填实际值;
2
- # aliyun.env 不进 git(见 .gitignore),daemon 启动不覆盖。
1
+ # 阿里云 RAM AccessKey(publish.sh / remove-extension.sh 经 ensure-credentials.sh source)。
2
+ # clawd 不随包 ship 任何一把 AK:把自己的 AK/SK 写进同目录的 aliyun.env.local(推荐,永不被 OTA 覆盖)
3
+ # 或 aliyun.env;两个都不进 git(clawd/.gitignore 只放行 *.example)。云上不用文件,gateway 经 env 注入。
4
+ # 需要权限(按 90 天 ActionTrail 反推):FC(cn-hangzhou)+ SLS 的 serverless-cn-hangzhou-* project + 证书只读;
5
+ # 没见到 OSS 调用(大代码包是否走 OSS 中转未验证,缺了 s deploy 会在 ActionTrail 里报 NoPermission)。
3
6
  ALIBABA_CLOUD_ACCESS_KEY_ID=replace-me
4
7
  ALIBABA_CLOUD_ACCESS_KEY_SECRET=replace-me
@@ -1,15 +1,12 @@
1
- # 阿里云 RAM AccessKey 个人 override(本机专用)
1
+ # 阿里云 RAM AccessKey(本机专用)
2
2
  #
3
- # 复制这份为同目录下 aliyun.env.local,填自己的 AK/SK,publish.sh / remove-extension.sh 会在
4
- # source 完 shared 的 aliyun.env 之后再 source 这份(.local 里的值覆盖 shared 里的)。
3
+ # 复制这份为同目录下 aliyun.env.local,填自己的 AK/SK。publish.sh / remove-extension.sh 会依次
4
+ # source aliyun.env(如果有)→ aliyun.env.local,后者的值覆盖前者。
5
5
  #
6
- # .local 后缀由 daemon refreshDeployKit 特判:**永远不会被 OTA 覆盖**,跟 shared aliyun.env
7
- # (每次 daemon 启动会被 defaults 里的新版覆盖)是两回事。
6
+ # .local 后缀由 daemon refreshDeployKit 特判:**永远不会被 OTA 覆盖**。
8
7
  #
9
- # 什么时候需要用 .local:
10
- # - 你想用自己的阿里云账号部署 FC(不想让别人用 clawd ship 的 demo AK 部到你账号)
11
- # - 你有权限收敛过的自定义 AK(比如只在某个特定 region 有 FC 权限)
12
- #
13
- # 不需要用 .local 时(大部分情况):删掉这个 .local 文件、直接跑 clawd defaults,OTA 后自动拿到新 demo 凭证。
8
+ # 2026-09-18 起 clawd 不再随包 ship 共享 demo AK(上一把随 npm 包发出去 69 秒就被 TruffleHog 收割,
9
+ # 主账号被阿里云风控锁;见 specs/2026-09-18-aliyun-ak-leak-rotate-design.md)——本机发布必须配这份。
10
+ # 老机器上 OTA 留下的 aliyun.env 里那把 AK 已泄露、不可再用,不用删,配了 .local 就会盖过它。
14
11
  ALIBABA_CLOUD_ACCESS_KEY_ID=replace-me
15
12
  ALIBABA_CLOUD_ACCESS_KEY_SECRET=replace-me
@@ -4,9 +4,9 @@
4
4
  # 被 `source` 进来,再调 ensure_credentials。跟 ensure-toolchain.sh 是一对:
5
5
  # 那个管「命令在不在」,这个管「命令拿什么身份跑」。
6
6
  #
7
- # 硬约束:**全程不碰用户全局 ~/.s**。clawd 发布用的是 ship 出来的共享 demo AK,
8
- # 而 ~/.s/access.yaml 里的 default 是用户自己账号的 Serverless Devs 凭证——它是用户的
9
- # 东西,不是我们的。做法对齐本 kit 处理 aliyun CLI 的方式(凭证只进本次进程的 env,
7
+ # 硬约束:**全程不碰用户全局 ~/.s**。clawd 发布用的是 deploy-kit 自己那份 AK(本机 .secrets/
8
+ # 下使用者写的文件,云上是 gateway 注入的 env),而 ~/.s/access.yaml 里的 default 是用户自己账号的
9
+ # Serverless Devs 凭证——它是用户的东西,不是我们的。做法对齐本 kit 处理 aliyun CLI 的方式(凭证只进本次进程的 env,
10
10
  # 全局 ~/.aliyun/ 一个字节不碰):把 Serverless Devs 的配置家目录整个指到 deploy-kit
11
11
  # 名下,access.yaml 写在那里。
12
12
  # ============================================================
@@ -18,27 +18,27 @@
18
18
  # 里的 access 字段,本改动前 scaffold 的老项目(s.yaml 写着 default)照样走这条。
19
19
  S_ACCESS_ALIAS=clawd-fc
20
20
 
21
- # 分层加载阿里云 AK 进 env:shared demo(OTA 每次启动覆盖)→ 本机 .local override(永不被覆盖)。
21
+ # 分层加载阿里云 AK 进 env,三层平铺、后读的覆盖先读的:
22
+ # 1. `$KIT_DIR/.secrets/aliyun.env` —— 使用者自己写的(或 2026-09-18 之前 OTA 留下的旧文件);
23
+ # 2. `$KIT_DIR/.secrets/aliyun.env.local` —— 使用者自己写的,refreshDeployKit 永不覆盖;
24
+ # 3. 进程 env 里已有的 `ALIBABA_CLOUD_ACCESS_KEY_ID/_SECRET` —— 云上容器那条路:Pod 里没有
25
+ # `.secrets/`(`cloud-host/provision/build-image.mjs` 刻意把它剔出镜像层——镜像要分发,凭据
26
+ # 进层就洗不掉了),AK 由 gateway 经 **MCP server 的 per-server env** 交到调用本脚本的那个
27
+ # 进程里(M1b Step 9)。不落盘是有意的:写成文件的话 agent 一个 `cat` 就看见了。
22
28
  #
23
- # 两种来源,**文件优先、env 兜底**:
24
- # - 本机:`$KIT_DIR/.secrets/aliyun.env`(+ 可选 .local),OTA 刷新;
25
- # - 云上容器:没有 `.secrets/`(`cloud-host/provision/build-image.mjs` 刻意把它剔出镜像层
26
- # ——镜像要分发,凭据进层就洗不掉了),AK 由 gateway 经 **MCP server 的 per-server env**
27
- # 交到调用本脚本的那个进程里(M1b Step 9)。这条分支不落盘是有意的:写成文件的话
28
- # agent 一个 `cat` 就看见了,而 env 只有这个子进程有。
29
+ # clawd **不再随包 ship 任何一把 AK**(2026-09-18 起,见 specs/2026-09-18-aliyun-ak-leak-rotate-design.md:
30
+ # 上一把随 npm 包发出去 69 秒就被 TruffleHog 收割,主账号被风控锁)。三层都空 = 使用者还没配,fail-loud。
29
31
  ensure_aliyun_env() {
30
32
  local kit="${KIT_DIR:?ensure_aliyun_env 需要调用方先设 KIT_DIR}"
31
- if [ -f "$kit/.secrets/aliyun.env" ]; then
32
- set -a; . "$kit/.secrets/aliyun.env"; set +a
33
- # aliyun.env.local: 本机个人 override(永远不被 OTA 覆盖,见 refreshDeployKit)。有就后 source 覆盖 env。
34
- # 写成 if 而不是 `[ -f ] && { }`:后者在函数末尾会把「文件不存在」变成函数返回 1,调用方 set -e 直接中止。
35
- if [ -f "$kit/.secrets/aliyun.env.local" ]; then
36
- set -a; . "$kit/.secrets/aliyun.env.local"; set +a
33
+ local f
34
+ # 写成 if 而不是 `[ -f ] && { }`:后者在函数末尾会把「文件不存在」变成函数返回 1,调用方 set -e 直接中止。
35
+ for f in "$kit/.secrets/aliyun.env" "$kit/.secrets/aliyun.env.local"; do
36
+ if [ -f "$f" ]; then
37
+ set -a; . "$f"; set +a
37
38
  fi
38
- elif [ -n "${ALIBABA_CLOUD_ACCESS_KEY_ID:-}" ] && [ -n "${ALIBABA_CLOUD_ACCESS_KEY_SECRET:-}" ]; then
39
- : # 云上:值已经在本进程 env 里,不落文件
40
- else
41
- echo "❌ 缺 $kit/.secrets/aliyun.env(clawd ship 的共享 demo 凭证,OTA 自动刷新),env 里也没有 ALIBABA_CLOUD_ACCESS_KEY_ID/_SECRET。参考 .secrets/aliyun.env.example 填写。" >&2
39
+ done
40
+ if [ -z "${ALIBABA_CLOUD_ACCESS_KEY_ID:-}" ] || [ -z "${ALIBABA_CLOUD_ACCESS_KEY_SECRET:-}" ]; then
41
+ echo "❌ 没有阿里云 AK:把自己的 AK/SK 写进 $kit/.secrets/aliyun.env.local(参考同目录 aliyun.env.local.example;这个文件不会被 OTA 覆盖),云上则由 gateway 经 env 注入。" >&2
42
42
  return 1
43
43
  fi
44
44
  export ALIBABA_CLOUD_REGION_ID="${REGION:?config.env 缺 REGION}"
@@ -80,7 +80,7 @@ ensure_s_access() {
80
80
  # 把 CLI 原文摆出来,让读的人按原文判断。
81
81
  echo "❌ aliyun sts GetCallerIdentity 失败,拿不到 AccountId。aliyun 原文:" >&2
82
82
  echo "$ident" >&2
83
- echo " 排查顺序:① 上面这行 CLI 原文说的是什么 ② $kit/.secrets/aliyun.env(及 .local)里的 AK 是否有效 ③ config.env 的 REGION" >&2
83
+ echo " 排查顺序:① 上面这行 CLI 原文说的是什么 ② $kit/.secrets/aliyun.env.local(或 aliyun.env)里的 AK 是否有效——2026-09-18 之前 OTA 发的那把已泄露、不可再用,得换自己的 ③ config.env 的 REGION" >&2
84
84
  return 1
85
85
  }
86
86
  # 用 node 不用 python3:目标机器保证有 node(clawd 本身就是 node 跑的),不保证有 python
@@ -78,11 +78,12 @@ beforeEach(() => {
78
78
  mkdirSync(join(home, '.s'), { recursive: true })
79
79
  mkdirSync(bin, { recursive: true })
80
80
 
81
- // 真脚本进沙箱 kit(.secrets 用假凭证,绝不碰 repo 里 ship 的那份)
81
+ // 真脚本进沙箱 kit。.secrets 按生产形状:bundle 只带 *.example(2026-09-18 起 AK 不再随包
82
+ // 分发,见 specs/2026-09-18-aliyun-ak-leak-rotate-design.md),本机凭证是使用者自己写的 .local。
82
83
  cpSync(SCRIPTS_DIR, join(kit, 'scripts'), { recursive: true })
83
84
  writeFileSync(
84
- join(kit, '.secrets', 'aliyun.env'),
85
- 'ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI-SHARED-DEMO\nALIBABA_CLOUD_ACCESS_KEY_SECRET=shared-demo-secret\n',
85
+ join(kit, '.secrets', 'aliyun.env.local'),
86
+ 'ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI-MY-OWN\nALIBABA_CLOUD_ACCESS_KEY_SECRET=my-own-secret\n',
86
87
  )
87
88
  writeFileSync(join(home, '.s', 'access.yaml'), USER_ACCESS_YAML)
88
89
  writeAliyunShim(bin)
@@ -127,8 +128,8 @@ describe('ensure_credentials', () => {
127
128
  runInKit('ensure_credentials')
128
129
  const yaml = kitAccessYaml()
129
130
  expect(yaml).toContain("AccountID: '5566778899001122'")
130
- expect(yaml).toContain('AccessKeyID: LTAI-SHARED-DEMO')
131
- expect(yaml).toContain('AccessKeySecret: shared-demo-secret')
131
+ expect(yaml).toContain('AccessKeyID: LTAI-MY-OWN')
132
+ expect(yaml).toContain('AccessKeySecret: my-own-secret')
132
133
  })
133
134
 
134
135
  it('profile 名是 clawd-fc 而不是 default', () => {
@@ -151,14 +152,26 @@ describe('ensure_credentials', () => {
151
152
  expect(mode.toString(8)).toBe('600')
152
153
  })
153
154
 
154
- it('aliyun.env.local 覆盖 shared 凭证', () => {
155
+ // 存量机器:OTA 停发 aliyun.env 之后 refreshDeployKit 不再覆盖也不删它,旧文件会留在盘上。
156
+ // 有 .local 时 .local 必须盖过它(否则老用户配了自己的 AK 还在用那把已停用的)。
157
+ it('legacy aliyun.env 仍被读,但 .local 覆盖它', () => {
155
158
  writeFileSync(
156
- join(box.kit, '.secrets', 'aliyun.env.local'),
157
- 'ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI-MY-OWN\nALIBABA_CLOUD_ACCESS_KEY_SECRET=my-own-secret\n',
159
+ join(box.kit, '.secrets', 'aliyun.env'),
160
+ 'ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI-STALE-OTA\nALIBABA_CLOUD_ACCESS_KEY_SECRET=stale-ota-secret\n',
158
161
  )
159
162
  runInKit('ensure_credentials')
160
163
  expect(kitAccessYaml()).toContain('AccessKeyID: LTAI-MY-OWN')
161
- expect(kitAccessYaml()).toContain('AccessKeySecret: my-own-secret')
164
+ expect(kitAccessYaml()).not.toContain('LTAI-STALE-OTA')
165
+ })
166
+
167
+ it('只有 aliyun.env 没有 .local 也能用(使用者直接写 aliyun.env 的老习惯)', () => {
168
+ rmSync(join(box.kit, '.secrets', 'aliyun.env.local'))
169
+ writeFileSync(
170
+ join(box.kit, '.secrets', 'aliyun.env'),
171
+ 'ALIBABA_CLOUD_ACCESS_KEY_ID=LTAI-PLAIN-FILE\nALIBABA_CLOUD_ACCESS_KEY_SECRET=plain-file-secret\n',
172
+ )
173
+ runInKit('ensure_credentials')
174
+ expect(kitAccessYaml()).toContain('AccessKeyID: LTAI-PLAIN-FILE')
162
175
  })
163
176
 
164
177
  it('调 aliyun 时显式传 AK 和 region,不靠环境变量的隐式优先级', () => {
@@ -169,8 +182,8 @@ describe('ensure_credentials', () => {
169
182
  writeAliyunShim(box.bin, '5566778899001122', log)
170
183
  runInKit('ensure_credentials')
171
184
  const argv = readFileSync(log, 'utf8')
172
- expect(argv).toContain('--access-key-id LTAI-SHARED-DEMO')
173
- expect(argv).toContain('--access-key-secret shared-demo-secret')
185
+ expect(argv).toContain('--access-key-id LTAI-MY-OWN')
186
+ expect(argv).toContain('--access-key-secret my-own-secret')
174
187
  expect(argv).toContain('--region cn-hangzhou')
175
188
  })
176
189
 
@@ -182,17 +195,17 @@ describe('ensure_credentials', () => {
182
195
  expect(kitAccessYaml()).toContain("AccountID: '5566778899001122'")
183
196
  })
184
197
 
185
- it('缺共享凭证文件、env 里也没有 AK 时 fail-loud', () => {
186
- rmSync(join(box.kit, '.secrets', 'aliyun.env'))
187
- expect(() => runInKit('ensure_credentials')).toThrow()
198
+ it('aliyun.env / .local 都没有、env 里也没有 AK 时 fail-loud,文案指到 .local', () => {
199
+ rmSync(join(box.kit, '.secrets', 'aliyun.env.local'))
200
+ expect(() => runInKit('ensure_credentials')).toThrow(/aliyun\.env\.local/)
188
201
  expect(existsSync(join(box.kit, '.s-home', '.s', 'access.yaml'))).toBe(false)
189
202
  })
190
203
 
191
204
  // 云上分支(persona 上云 M1b Step 9):容器里没有 `.secrets/`——`build-image.mjs` 刻意把它
192
205
  // 剔出镜像层(镜像要分发,凭据进层洗不掉)。AK 由 gateway 经 **MCP server 的 per-server env**
193
206
  // 交到调用本脚本的那个子进程里,不落文件:写成文件的话 agent 一个 `cat` 就看见了。
194
- it('没有 .secrets/aliyun.env 但 env 里有 AK 时用 env(云上容器那条路)', () => {
195
- rmSync(join(box.kit, '.secrets', 'aliyun.env'))
207
+ it('没有 .secrets/ 凭证文件但 env 里有 AK 时用 env(云上容器那条路)', () => {
208
+ rmSync(join(box.kit, '.secrets', 'aliyun.env.local'))
196
209
  const out = execFileSync(
197
210
  'bash',
198
211
  [
@@ -221,7 +234,7 @@ printf 'ok %s\\n' "$ALIBABA_CLOUD_ACCESS_KEY_ID"`,
221
234
  expect(userAccessYaml()).toContain('LTAI-USER-OWN-KEY')
222
235
  })
223
236
 
224
- it('文件在就优先用文件(云上那条只是兜底,不能反过来盖掉本机 shared 凭证)', () => {
237
+ it('文件在就优先用文件(云上那条只是兜底,不能反过来盖掉本机 .local 凭证)', () => {
225
238
  const out = execFileSync(
226
239
  'bash',
227
240
  [
@@ -244,6 +257,7 @@ printf 'id=%s\\n' "$ALIBABA_CLOUD_ACCESS_KEY_ID"`,
244
257
  encoding: 'utf8',
245
258
  },
246
259
  )
260
+ expect(out).toContain('id=LTAI-MY-OWN')
247
261
  expect(out).not.toContain('LTAI-FROM-ENV')
248
262
  })
249
263
  })
@@ -58,7 +58,7 @@ if ! grep -qxF 's.*.yaml' "$EXT_DIR/.gitignore" 2>/dev/null; then
58
58
  echo " 📝 .gitignore 补 s.*.yaml (渲出的 s.int.yaml / s.prod.yaml 含 key,不进仓库;记得把 .gitignore 推上去)"
59
59
  fi
60
60
 
61
- # 2) 工具链 + 阿里云凭证(分层:shared demo + 可选 local override)
61
+ # 2) 工具链 + 阿里云凭证(使用者自己的 .secrets/aliyun.env.local,云上是 env;见 ensure-credentials.sh)
62
62
  source "$KIT_DIR/scripts/ensure-toolchain.sh"
63
63
  ensure_toolchain
64
64
  source "$KIT_DIR/scripts/ensure-credentials.sh"
@@ -28,7 +28,7 @@ TABLE_PREFIX_INT="${TABLE_PREFIX}int_"
28
28
  source "$KIT_DIR/scripts/ensure-toolchain.sh"
29
29
  ensure_toolchain
30
30
 
31
- # 阿里云凭证(分层:shared demo + 可选 local override)+ deploy-kit 私有的 s profile
31
+ # 阿里云凭证(使用者自己的 .secrets/aliyun.env.local,云上是 env)+ deploy-kit 私有的 s profile
32
32
  source "$KIT_DIR/scripts/ensure-credentials.sh"
33
33
  ensure_credentials
34
34
 
@@ -111,12 +111,13 @@
111
111
  - 工具链 `aliyun` CLI + Serverless Devs(`s`)由 `deploy-kit/scripts/ensure-toolchain.sh` 在 publish / remove 前自动检测 + 安装,接管发布失败时**不必再排查「CLI 没装」**
112
112
  - 实时推送用 Supabase Realtime(FC 不维持长连接)
113
113
 
114
- ## 阿里云凭证:在共享 deploy-kit 的 `.secrets/`,分层 override
114
+ ## 阿里云凭证:使用者自带,放共享 deploy-kit 的 `.secrets/`
115
115
 
116
- AK/SK 分两层,都放在 `$HOME/.clawd/deploy-kit/.secrets/`、全 FC persona 共用,不在系统环境变量里:
116
+ clawd **不随包 ship 任何一把 AK**。AK/SK 放在 `$HOME/.clawd/deploy-kit/.secrets/`、全 FC persona 共用,不在系统环境变量里:
117
117
 
118
- - **`aliyun.env`** — clawd ship 的**共享 demo 凭证**(RAM 用户 `clawd-fc-developer`,权限收敛)。每次 daemon 启动会用 defaults 里的最新版覆盖本文件(OTA 即升级)
119
- - **`aliyun.env.local`**(可选)— 本机个人 override,永远不被 OTA 覆盖。用户想用自己的 AK 就复制 `aliyun.env.local.example` → `aliyun.env.local` 填自己的值
118
+ - **`aliyun.env.local`** — 使用者自己的 AK,永远不被 OTA 覆盖。复制 `aliyun.env.local.example` → `aliyun.env.local` 填值
119
+ - **`aliyun.env`**(可有可无)— 也会被读,`.local` 覆盖它。老机器上 2026-09-18 之前 OTA 留下的这份里的 AK 已泄露、不可再用
120
+ - 云上没有这两个文件,AK 由 gateway 经 MCP server 的 env 注入
120
121
 
121
122
  手动排障要跑 `aliyun` / `s` 命令时,在**同一条 Bash** 里加载再用(shell 变量不跨调用留存):
122
123
 
@@ -129,7 +130,7 @@ aliyun_ak fc GET /2023-03-30/custom-domains # aliyun 一律走 aliyun_a
129
130
  s deploy -y -t s.int.yaml --access "$S_ACCESS_ALIAS" # s 一律显式带 profile + 指明哪个环境的 s 文件(s.int.yaml / s.prod.yaml)
130
131
  ```
131
132
 
132
- **红线**:绝不写用户全局 `~/.s/access.yaml`(那里是用户自己账号的凭证,盖上去不可恢复);绝不 `echo` / 打印 / 写进日志或提交记录里暴露 SK;绝不把 `.secrets/` 拷进项目代码;shared `aliyun.env` 为空先停下提醒老板。
133
+ **红线**:绝不写用户全局 `~/.s/access.yaml`(那里是用户自己账号的凭证,盖上去不可恢复);绝不 `echo` / 打印 / 写进日志或提交记录里暴露 SK;绝不把 `.secrets/` 拷进项目代码;`ensure_credentials` 报「没有阿里云 AK」就停下提醒老板配 `aliyun.env.local`,别自己去找别的 AK 填。
133
134
 
134
135
  ## 任务分级与节奏
135
136
 
@@ -6,7 +6,7 @@
6
6
 
7
7
  FC 对应用只有一条要求:**一个监听 `$PORT` 的 HTTP server**。所以固化的是「发布管道」,不是技术栈。
8
8
 
9
- - **共享 deploy-kit(凭证 + 脚本骨架 + bootstrap,全 FC persona 一份)**:`$HOME/.clawd/deploy-kit/`(`scripts/` + `contract/bootstrap` + `.secrets/aliyun.env`)
9
+ - **共享 deploy-kit(凭证 + 脚本骨架 + bootstrap,全 FC persona 一份)**:`$HOME/.clawd/deploy-kit/`(`scripts/` + `contract/bootstrap` + `.secrets/aliyun.env.local`)
10
10
  - **persona 特化(本目录)**:`config.env`(注入值) + `contract/s.yaml.tmpl`(占位符清单) + `examples/`(默认 `nestjs-react`)
11
11
 
12
12
  ## 目录
@@ -14,7 +14,7 @@ FC 对应用只有一条要求:**一个监听 `$PORT` 的 HTTP server**。所以
14
14
  ```
15
15
  # 共享 deploy-kit($HOME/.clawd/deploy-kit/,daemon 维护,全 persona 共用)
16
16
  deploy-kit/
17
- ├── .secrets/aliyun.env 阿里云凭证(单源)
17
+ ├── .secrets/aliyun.env.local 阿里云凭证(单源,使用者自己写;bundle 只带 *.example)
18
18
  ├── contract/bootstrap FC 启动入口(Node 参考实现:find node + cd + exec)
19
19
  └── scripts/
20
20
  ├── new-extension.sh 从示例生成新 extension(纯 copy,daemon 自动跑)
@@ -6,7 +6,7 @@
6
6
 
7
7
  FC 对应用只有一条要求:**一个监听 `$PORT` 的 HTTP server**。所以固化的是「发布管道」,不是技术栈。
8
8
 
9
- - **共享 deploy-kit(凭证 + 脚本骨架 + bootstrap,全 FC persona 一份)**:`$HOME/.clawd/deploy-kit/`(`scripts/` + `contract/bootstrap` + `.secrets/aliyun.env`)
9
+ - **共享 deploy-kit(凭证 + 脚本骨架 + bootstrap,全 FC persona 一份)**:`$HOME/.clawd/deploy-kit/`(`scripts/` + `contract/bootstrap` + `.secrets/aliyun.env.local`)
10
10
  - **persona 特化(本目录)**:`config.env`(dataclaw 注入值) + `contract/s.yaml.tmpl`(占位符清单) + `hooks/pre-deploy.sh`(发布前置) + `examples/`(默认 `nestjs-react`)
11
11
 
12
12
  ## 目录
@@ -14,7 +14,7 @@ FC 对应用只有一条要求:**一个监听 `$PORT` 的 HTTP server**。所以
14
14
  ```
15
15
  # 共享 deploy-kit($HOME/.clawd/deploy-kit/,daemon 维护,全 persona 共用)
16
16
  deploy-kit/
17
- ├── .secrets/aliyun.env 阿里云凭证(单源)
17
+ ├── .secrets/aliyun.env.local 阿里云凭证(单源,使用者自己写;bundle 只带 *.example)
18
18
  ├── contract/bootstrap FC 启动入口(Node 参考实现:find node + cd + exec)
19
19
  └── scripts/
20
20
  ├── new-extension.sh 从示例生成新 extension(纯 copy,daemon 自动跑)
@@ -1,5 +1,5 @@
1
1
  # ===== 平台级固定变量(一次配好,所有 extension 共用)=====
2
- # 阿里云凭证不在这里:从 .secrets/aliyun.env 现读(见 CLAUDE.md)
2
+ # 阿里云凭证不在这里:从 .secrets/aliyun.env.local 现读(见 CLAUDE.md)
3
3
 
4
4
  REGION=cn-hangzhou
5
5
 
@@ -17,8 +17,11 @@ agent 主进程 env 一条都不放。读法与合并顺序见 `../deploy/secret
17
17
 
18
18
  ## 信任层级:这里的每一把 key 都等于公开
19
19
 
20
- 本目录随 npm 包 ship 给所有用户,跟 `deploy-kit/.secrets/aliyun.env` 同级。判断风险时按
21
- 「这把 key 落在所有用户手上」算。把 service-role key 换成受限 role 的收敛方向记在 glossary,
20
+ 本目录随 npm 包 ship 给所有用户。判断风险时按「这把 key 落在所有用户手上」算——不是比喻:
21
+ deploy-kit 的阿里云 AK 曾经也这么 ship,随 npm 包发出去 **69 秒**就被 TruffleHog 收割、90 天内被
22
+ 100+ 个境外 IP 验证约 600 次,最后主账号被阿里云风控锁;2026-09-18 起它不再随包分发
23
+ (`specs/2026-09-18-aliyun-ak-leak-rotate-design.md`)。这里的 supabase 三件套(含 service-role)
24
+ 仍在 ship,是同一类风险。把 service-role key 换成受限 role 的收敛方向记在 glossary,
22
25
  不在 M1b 范围。
23
26
 
24
27
  ## 本机覆盖