@optima-chat/dev-skills 0.16.8 → 0.16.10
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/.claude/skills/yzsgo-e2e/SKILL.md +6 -1
- package/.claude/skills/yzsgo-e2e/SYNC.md +39 -1
- package/.claude/skills/yzsgo-e2e/chat_driver.py +862 -64
- package/.claude/skills/yzsgo-e2e/run_e2e.py +7 -1
- package/.codex/skills/yzsgo-e2e/SKILL.md +6 -1
- package/.codex/skills/yzsgo-e2e/SYNC.md +39 -1
- package/.codex/skills/yzsgo-e2e/chat_driver.py +862 -64
- package/.codex/skills/yzsgo-e2e/run_e2e.py +7 -1
- package/bin/helpers/billing-http.ts +10 -13
- package/bin/helpers/cn-deploy.ts +2 -1
- package/bin/helpers/db-utils.ts +16 -10
- package/bin/helpers/infisical-secrets.ts +8 -7
- package/bin/helpers/query-db.ts +11 -4
- package/bin/helpers/safe-exec.ts +252 -0
- package/bin/helpers/show-env.ts +6 -5
- package/dist/bin/helpers/billing-http.js +7 -6
- package/dist/bin/helpers/cn-deploy.js +2 -1
- package/dist/bin/helpers/db-utils.js +15 -6
- package/dist/bin/helpers/infisical-secrets.js +8 -4
- package/dist/bin/helpers/query-db.js +9 -1
- package/dist/bin/helpers/safe-exec.js +321 -0
- package/dist/bin/helpers/show-env.js +6 -2
- package/package.json +1 -1
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isSecretName = isSecretName;
|
|
4
|
+
exports.redactArgv = redactArgv;
|
|
5
|
+
exports.scrub = scrub;
|
|
6
|
+
exports.sanitizeExecError = sanitizeExecError;
|
|
7
|
+
exports.runCurl = runCurl;
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
/**
|
|
10
|
+
* 子进程调用的「报错不回显命令参数」封装。
|
|
11
|
+
*
|
|
12
|
+
* 背景:Node 的 execSync/execFileSync 失败时,错误对象的 message 是 `Command failed: <完整命令行>`,
|
|
13
|
+
* 且带 `cmd` / `spawnargs` / `stderr` 等字段。命令行里若有请求体、认证头、`-u user:pass`、URL 里的
|
|
14
|
+
* 凭据参数,调用方一句 `console.error(err.message)` 或把报错原文落盘,就把它们带了出去。
|
|
15
|
+
*
|
|
16
|
+
* 做法:失败时**不复用**原始 error——抛一个全新的 Error,只含排错需要的东西(退出码/信号/errno、
|
|
17
|
+
* 目标主机名、子进程 stderr),且 stderr 先过 `scrub`(按字面值 + 常见凭据形态双重清洗)。
|
|
18
|
+
*/
|
|
19
|
+
const MASK = '[REDACTED]';
|
|
20
|
+
/** 值整体敏感的 curl 选项(下一个 argv 元素或 `--opt=value` 的 value 整体打码)。 */
|
|
21
|
+
const VALUE_IS_SECRET = new Set([
|
|
22
|
+
'-d', '--data', '--data-raw', '--data-binary', '--data-urlencode', '--data-ascii', '--json',
|
|
23
|
+
'-u', '--user', '-U', '--proxy-user', '--oauth2-bearer',
|
|
24
|
+
'-x', '--proxy', '--preproxy', // 代理 URL 常带 user:pass@,且可能无 scheme
|
|
25
|
+
'-F', '--form', '--form-string', '-b', '--cookie', '--pass', '--key',
|
|
26
|
+
'--tlspassword', '--proxy-tlspassword', '--proxy-pass', '--proxy-key',
|
|
27
|
+
'-E', '--cert', '--proxy-cert', // `<file>:<passphrase>`
|
|
28
|
+
'--variable', '--aws-sigv4',
|
|
29
|
+
]);
|
|
30
|
+
/** 带值的短选项字母:解析 `-sSu user:pass` / `-XPOST` 这类合写簇时,遇到它簇就结束(其后是值)。 */
|
|
31
|
+
const SHORT_WITH_VALUE = new Set('dubFHxeAXoKEUmTrCQYyzwPtcDh'.split('').filter((c) => c !== 'h'));
|
|
32
|
+
const HEADER_OPTS = new Set(['-H', '--header', '--proxy-header']);
|
|
33
|
+
/** 头值白名单:只有这些无害头保留原值,其余一律整值打码(黑名单永远列不全)。 */
|
|
34
|
+
const SAFE_HEADER_RE = /^(content-type|content-length|content-encoding|accept|accept-encoding|accept-language|user-agent|cache-control|connection|host|origin)$/i;
|
|
35
|
+
/** 参数/字段名像凭据 ⇒ 值打码。`secret` 单独判:`secretPath` / `expandSecretReferences` 这类是路径与开关,不是凭据。 */
|
|
36
|
+
const SECRET_NAME_RE = /(pass(?:word|wd)?|pwd|token|jwt|hmac|assertion|api[-_]?key|access[-_]?key|private[-_]?key|signature|credential|session|(?:^|[-_])(?:sig|sign|auth|code|key|otp|pw|ticket)$)/i;
|
|
37
|
+
const SECRET_WORD_RE = /secret/i;
|
|
38
|
+
/**
|
|
39
|
+
* 「名字像凭据、其实不是」的排除——**必须锚定**:无锚点的子串排除会把 `provider_secret`(prov-id-er)、
|
|
40
|
+
* `model_secret`(mode-l)这类真凭据放过去。只认两种形态:
|
|
41
|
+
* - 整名是已知的路径/开关参数;
|
|
42
|
+
* - 以「凭据词 + 明确的非凭据后缀」结尾(`secret_id` / `token_type` / `max_tokens` …)。
|
|
43
|
+
*/
|
|
44
|
+
const KNOWN_NON_SECRET = new Set(['secretpath', 'expandsecretreferences', 'sshpass', 'tokenizer', 'passthrough', 'bypass']);
|
|
45
|
+
const NON_SECRET_SUFFIX_RE = /(?:secret|token|key)s?[-_]?(?:path|references?|names?|ids?|version|types?|mode|enabled|count|limit|length|ttl|expiry|expires(?:[-_]?(?:at|in))?)$|(?:^|[-_])(?:max|min|num|total|input|output|prompt|completion)[-_]?tokens$/i;
|
|
46
|
+
function isSecretName(name) {
|
|
47
|
+
const n = name.trim();
|
|
48
|
+
if (KNOWN_NON_SECRET.has(n.toLowerCase()) || NON_SECRET_SUFFIX_RE.test(n))
|
|
49
|
+
return false;
|
|
50
|
+
return SECRET_NAME_RE.test(n) || SECRET_WORD_RE.test(n);
|
|
51
|
+
}
|
|
52
|
+
function redactUrl(raw) {
|
|
53
|
+
let u;
|
|
54
|
+
try {
|
|
55
|
+
u = new URL(raw);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return raw;
|
|
59
|
+
}
|
|
60
|
+
if (!/^https?:$/.test(u.protocol))
|
|
61
|
+
return raw;
|
|
62
|
+
if (u.username || u.password) {
|
|
63
|
+
u.username = MASK;
|
|
64
|
+
u.password = '';
|
|
65
|
+
}
|
|
66
|
+
for (const key of [...u.searchParams.keys()]) {
|
|
67
|
+
if (isSecretName(key))
|
|
68
|
+
u.searchParams.set(key, MASK);
|
|
69
|
+
}
|
|
70
|
+
if (u.hash.length > 1)
|
|
71
|
+
u.hash = MASK; // fragment 常见 `#access_token=…`,对排错无用,整段打码
|
|
72
|
+
return u.toString().replace(/%5BREDACTED%5D/g, MASK);
|
|
73
|
+
}
|
|
74
|
+
function redactHeader(h) {
|
|
75
|
+
const i = h.indexOf(':');
|
|
76
|
+
if (i <= 0)
|
|
77
|
+
return h;
|
|
78
|
+
const name = h.slice(0, i).trim();
|
|
79
|
+
return SAFE_HEADER_RE.test(name) ? h : `${name}: ${MASK}`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* 返回脱敏后的 argv 副本(仅用于展示/排错;绝不改传给子进程的真参数)。
|
|
83
|
+
* 不覆盖:藏在 URL **路径段**里的凭据(webhook 型 URL)与 User-Agent 里夹带的值——这类请走请求头/请求体。
|
|
84
|
+
*/
|
|
85
|
+
function redactArgv(args) {
|
|
86
|
+
const out = [];
|
|
87
|
+
for (let i = 0; i < args.length; i++) {
|
|
88
|
+
const a = args[i];
|
|
89
|
+
// --opt=value
|
|
90
|
+
const eq = a.startsWith('--') ? a.indexOf('=') : -1;
|
|
91
|
+
if (eq > 0) {
|
|
92
|
+
const opt = a.slice(0, eq);
|
|
93
|
+
if (opt === '--url') {
|
|
94
|
+
out.push(`${opt}=${redactUrl(a.slice(eq + 1))}`);
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (VALUE_IS_SECRET.has(opt)) {
|
|
98
|
+
out.push(`${opt}=${MASK}`);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (HEADER_OPTS.has(opt)) {
|
|
102
|
+
out.push(`${opt}=${redactHeader(a.slice(eq + 1))}`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// 选项与值分开
|
|
107
|
+
if (VALUE_IS_SECRET.has(a)) {
|
|
108
|
+
out.push(a);
|
|
109
|
+
if (i + 1 < args.length) {
|
|
110
|
+
out.push(MASK);
|
|
111
|
+
i++;
|
|
112
|
+
}
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (HEADER_OPTS.has(a)) {
|
|
116
|
+
out.push(a);
|
|
117
|
+
if (i + 1 < args.length) {
|
|
118
|
+
out.push(redactHeader(args[i + 1]));
|
|
119
|
+
i++;
|
|
120
|
+
}
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
// 短选项簇:-Hfoo / -dbody / -uuser:pass / -sSu user:pass / -XPOST
|
|
124
|
+
if (/^-[^-]/.test(a) && a.length > 2) {
|
|
125
|
+
let j = 1;
|
|
126
|
+
while (j < a.length && !SHORT_WITH_VALUE.has(a[j]))
|
|
127
|
+
j++; // 跳过不带值的开关字母
|
|
128
|
+
if (j < a.length) {
|
|
129
|
+
const opt = `-${a[j]}`, head = a.slice(0, j + 1), rest = a.slice(j + 1);
|
|
130
|
+
const sticky = rest.length > 0; // 值粘在簇里,否则是下一个 argv
|
|
131
|
+
const value = sticky ? rest : args[i + 1];
|
|
132
|
+
if (VALUE_IS_SECRET.has(opt) || HEADER_OPTS.has(opt)) {
|
|
133
|
+
const masked = value === undefined ? undefined : (HEADER_OPTS.has(opt) ? redactHeader(value) : MASK);
|
|
134
|
+
if (sticky)
|
|
135
|
+
out.push(`${head}${masked}`);
|
|
136
|
+
else {
|
|
137
|
+
out.push(head);
|
|
138
|
+
if (masked !== undefined) {
|
|
139
|
+
out.push(masked);
|
|
140
|
+
i++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (/^https?:\/\//i.test(a)) {
|
|
148
|
+
out.push(redactUrl(a));
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
// 无 scheme 的 `user:pass@host…`(curl 接受)
|
|
152
|
+
out.push(a.replace(/^[^\s/@:]+:[^\s/@]+@/, `${MASK}@`));
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
/** 从 argv 里收集「字面值就是秘密」的串,供 scrub 在任意文本里逐字清除。 */
|
|
157
|
+
function secretLiterals(args) {
|
|
158
|
+
const red = redactArgv(args);
|
|
159
|
+
const lits = [];
|
|
160
|
+
for (let i = 0; i < args.length; i++) {
|
|
161
|
+
if (!red[i].includes(MASK))
|
|
162
|
+
continue; // 按「是否真的打了码」判,不按字符串是否相等(URL 规范化也会让它不等)
|
|
163
|
+
const a = args[i].replace(/^--url=/, '');
|
|
164
|
+
// 子进程 stderr 可能只回显参数的一段,所以除整串外还要收内部片段——但**只收名字像凭据的值**:
|
|
165
|
+
// 把 URL 里所有 k=v 都当秘密,会让 `prod` / `true` 这类普通词把报错原文洗得没法读。
|
|
166
|
+
if (/^https?:\/\//i.test(a)) {
|
|
167
|
+
try {
|
|
168
|
+
const u = new URL(a);
|
|
169
|
+
if (u.password)
|
|
170
|
+
lits.push(u.password, decodeURIComponent(u.password));
|
|
171
|
+
if (u.username && u.password)
|
|
172
|
+
lits.push(`${u.username}:${u.password}`);
|
|
173
|
+
for (const [k, v] of u.searchParams)
|
|
174
|
+
if (isSecretName(k))
|
|
175
|
+
lits.push(v);
|
|
176
|
+
if (u.hash.length > 1) { // fragment:`#access_token=…`
|
|
177
|
+
lits.push(u.hash.slice(1));
|
|
178
|
+
for (const [, v] of new URLSearchParams(u.hash.slice(1)))
|
|
179
|
+
if (v)
|
|
180
|
+
lits.push(v);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch { /* 非法 URL:redactUrl 也不会动它,走不到这里 */ }
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
lits.push(a); // 非 URL 的敏感参数(请求体 / -u / 认证头 / 代理):整串即秘密
|
|
187
|
+
for (const m of a.matchAll(/"([^"]*)"\s*:\s*"([^"]+)"/g))
|
|
188
|
+
if (isSecretName(m[1]))
|
|
189
|
+
lits.push(m[2]); // JSON 字段
|
|
190
|
+
for (const m of a.matchAll(/(?:^|&)([^=&\s]+)=([^&\s]+)/g)) { // form k=v
|
|
191
|
+
if (isSecretName(m[1])) {
|
|
192
|
+
lits.push(m[2]);
|
|
193
|
+
try {
|
|
194
|
+
lits.push(decodeURIComponent(m[2]));
|
|
195
|
+
}
|
|
196
|
+
catch { /* 裸 % */ }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const header = /^[A-Za-z0-9-]+:\s*(.+)$/.exec(a);
|
|
200
|
+
if (header) { // Header: value
|
|
201
|
+
lits.push(header[1].trim());
|
|
202
|
+
const scheme = /^(?:Bearer|Basic|Token)\s+(\S+)/i.exec(header[1].trim());
|
|
203
|
+
if (scheme)
|
|
204
|
+
lits.push(scheme[1]);
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
const up = /^[^\s/@:]*:([^\s@]+)(?:@|$)/.exec(a); // user:pass[@host]
|
|
208
|
+
if (up)
|
|
209
|
+
lits.push(up[1]);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// 长的先替换,避免短片段把长串切碎后漏网;太短的不收(误伤普通文本)
|
|
213
|
+
return [...new Set(lits.filter((s) => s && s.length >= 4))].sort((x, y) => y.length - x.length);
|
|
214
|
+
}
|
|
215
|
+
/** 清洗任意文本:先按字面值,再按常见凭据形态(认证头、Bearer/Basic、URL 凭据、k=v、JSON 字段)。 */
|
|
216
|
+
function scrub(text, literals = []) {
|
|
217
|
+
let out = String(text ?? '');
|
|
218
|
+
for (const lit of [...literals].filter((s) => s && s.length >= 4).sort((a, b) => b.length - a.length)) {
|
|
219
|
+
out = out.split(lit).join(MASK);
|
|
220
|
+
const enc = encodeURIComponent(lit);
|
|
221
|
+
if (enc !== lit)
|
|
222
|
+
out = out.split(enc).join(MASK);
|
|
223
|
+
}
|
|
224
|
+
return out
|
|
225
|
+
.replace(/((?:proxy-)?authorization\s*[:=]\s*)(?:(?:bearer|basic|token)\s+)?[^\s"'\\]+/gi, `$1${MASK}`)
|
|
226
|
+
.replace(/\b(bearer|basic)\s+[A-Za-z0-9._~+/=-]{8,}/gi, `$1 ${MASK}`)
|
|
227
|
+
.replace(/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/:]+:[^\s/]*@/gi, `$1${MASK}@`) // 贪婪到最后一个 @:密码本身可含 @
|
|
228
|
+
// 三条「名字 → 值」规则统一用 isSecretName 判(与 argv 脱敏同一口径)
|
|
229
|
+
// 值类必须排除 `?`:否则非凭据键(`url=http://h/cb?token=X`)会把后面的凭据对整段吞进自己的值里原样返回。
|
|
230
|
+
.replace(/(^|[?&;,(\s"'])(-{0,2}[A-Za-z_][\w.-]*)(\s*=\s*)(?:"[^"]*"|'[^']*'|[^&?;,)\s"']+)/g, (m, pre, key, eq) => (isSecretName(key.replace(/^-+/, '')) ? `${pre}${key}${eq}${MASK}` : m))
|
|
231
|
+
.replace(/"([A-Za-z_][\w-]*)"(\s*:\s*)"[^"]*"/g, (m, key, sep) => (isSecretName(key) ? `"${key}"${sep}"${MASK}"` : m))
|
|
232
|
+
.replace(/\b((?:cookie|set-cookie|x-[a-z0-9-]*(?:key|token|secret|auth|session)[a-z0-9-]*|[a-z0-9-]*api-?key)\s*:\s*)[^\r\n"']+/gi, `$1${MASK}`)
|
|
233
|
+
// `name: value`(yaml / 单引号 dict)。值须像一个 token(≥6 且含数字或符号),否则 `password: must be …`
|
|
234
|
+
// 这类普通报错句子会被误洗。
|
|
235
|
+
.replace(/(^|[\s{,])(["']?)([A-Za-z_][\w-]*)\2(\s*:\s*)('[^']*'|[^\s,}"']+)/g, (m, pre, q, key, sep, val) => {
|
|
236
|
+
const bare = val.replace(/^'|'$/g, '');
|
|
237
|
+
const tokenish = bare.length >= 6 && /[\d_\-+/=.]/.test(bare);
|
|
238
|
+
return isSecretName(key) && tokenish ? `${pre}${q}${key}${q}${sep}${MASK}` : m;
|
|
239
|
+
})
|
|
240
|
+
.replace(/(\bsshpass\s+-p\s*)("[^"]*"|'[^']*'|\S+)/g, `$1${MASK}`);
|
|
241
|
+
}
|
|
242
|
+
/** 值是 URL 但不是请求目标的选项(代理 / referer)。 */
|
|
243
|
+
const URL_VALUED_OPTS = new Set(['-x', '--proxy', '--preproxy', '-e', '--referer']);
|
|
244
|
+
function hostOf(args) {
|
|
245
|
+
for (let i = 0; i < args.length; i++) {
|
|
246
|
+
const a = args[i].replace(/^--url=/, '');
|
|
247
|
+
if (!/^https?:\/\//i.test(a) || (i > 0 && URL_VALUED_OPTS.has(args[i - 1])))
|
|
248
|
+
continue;
|
|
249
|
+
try {
|
|
250
|
+
return new URL(a).host;
|
|
251
|
+
}
|
|
252
|
+
catch { /* 不是合法 URL:继续找 */ }
|
|
253
|
+
}
|
|
254
|
+
return 'unknown';
|
|
255
|
+
}
|
|
256
|
+
/** 会让 curl 把请求头/响应头/响应体细节写进输出的选项:响应头里的凭据(Set-Cookie 等)没法按字面值清洗,直接不许用。 */
|
|
257
|
+
function findVerboseOpt(args) {
|
|
258
|
+
for (let i = 0; i < args.length; i++) {
|
|
259
|
+
const a = args[i];
|
|
260
|
+
if (a.startsWith('--')) {
|
|
261
|
+
const name = a.split('=')[0];
|
|
262
|
+
// curl 接受长选项的唯一前缀(`--verbos`),所以按前缀判
|
|
263
|
+
if (/^--(verb|trace|incl|dump-h)/.test(name))
|
|
264
|
+
return name;
|
|
265
|
+
if (!a.includes('=') && (VALUE_IS_SECRET.has(name) || HEADER_OPTS.has(name) || URL_VALUED_OPTS.has(name)))
|
|
266
|
+
i++; // 跳过它的值
|
|
267
|
+
continue;
|
|
268
|
+
}
|
|
269
|
+
if (!/^-[^-]/.test(a))
|
|
270
|
+
continue;
|
|
271
|
+
let j = 1;
|
|
272
|
+
for (; j < a.length; j++) {
|
|
273
|
+
if ('viD'.includes(a[j]))
|
|
274
|
+
return `-${a[j]}`;
|
|
275
|
+
if (SHORT_WITH_VALUE.has(a[j]))
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
if (j < a.length && j === a.length - 1)
|
|
279
|
+
i++; // 簇以带值选项结尾:下一个 argv 是值,跳过
|
|
280
|
+
}
|
|
281
|
+
return undefined;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* 把 child_process 抛的 error 换成一个干净的新 Error。
|
|
285
|
+
* 只取 status/signal/code 与(清洗后的)stderr;**不复制** message/cmd/spawnargs/stdout/output。
|
|
286
|
+
*/
|
|
287
|
+
function sanitizeExecError(label, raw, literals = [], context = '') {
|
|
288
|
+
const e = (raw ?? {});
|
|
289
|
+
const parts = [];
|
|
290
|
+
if (typeof e.status === 'number')
|
|
291
|
+
parts.push(`exit=${e.status}`);
|
|
292
|
+
if (e.signal)
|
|
293
|
+
parts.push(`signal=${e.signal}`);
|
|
294
|
+
if (typeof e.code === 'string')
|
|
295
|
+
parts.push(`code=${e.code}`); // ENOENT / ETIMEDOUT / ENOBUFS…
|
|
296
|
+
if (context)
|
|
297
|
+
parts.push(context);
|
|
298
|
+
const stderr = scrub(e.stderr == null ? '' : String(e.stderr), literals).trim().slice(0, 2000);
|
|
299
|
+
return new Error(`${label} failed (${parts.join(' ') || 'unknown error'})${stderr ? `: ${stderr}` : ''}`);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* 同步跑 curl,返回 stdout。参数数组直传(不经 shell)。
|
|
303
|
+
* 失败时抛的 Error 不含 argv:只有退出码/信号/errno、目标主机名、清洗过的 curl stderr。
|
|
304
|
+
* `-sS`:静默进度条但保留 curl 自己的错误行(`curl: (7) Failed to connect…`),否则失败时两眼一抹黑。
|
|
305
|
+
*/
|
|
306
|
+
function runCurl(args, opts = {}) {
|
|
307
|
+
const verbose = findVerboseOpt(args);
|
|
308
|
+
if (verbose)
|
|
309
|
+
throw new Error(`runCurl: option ${verbose} is not allowed (it writes header/body details to the output)`);
|
|
310
|
+
try {
|
|
311
|
+
return (0, child_process_1.execFileSync)(opts.bin ?? 'curl', ['-sS', ...args], {
|
|
312
|
+
encoding: 'utf-8',
|
|
313
|
+
stdio: ['ignore', 'pipe', 'pipe'], // stderr 收进 error 对象(由我们清洗后再露出),不直通父进程
|
|
314
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
315
|
+
...(opts.timeoutMs ? { timeout: opts.timeoutMs } : {}),
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
catch (raw) {
|
|
319
|
+
throw sanitizeExecError('curl', raw, secretLiterals(args), `host=${hostOf(args)}`);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
const
|
|
4
|
+
const safe_exec_1 = require("./safe-exec");
|
|
5
5
|
const db_utils_1 = require("./db-utils");
|
|
6
6
|
// 支持的服务列表(Infisical 路径为 /services/<service-name>)
|
|
7
7
|
const SUPPORTED_SERVICES = [
|
|
@@ -43,7 +43,11 @@ function isSupportedEnv(env) {
|
|
|
43
43
|
// NOTE: getInfisicalSecrets is kept local because it encodes secretPath (encodeURIComponent),
|
|
44
44
|
// unlike db-utils' raw-path variant; getGitHubVariable/getInfisicalConfig/getInfisicalToken are shared from db-utils.
|
|
45
45
|
function getInfisicalSecrets(config, token, environment, secretPath) {
|
|
46
|
-
|
|
46
|
+
// runCurl:参数数组直传(不经 shell),失败时的报错不回显命令参数。
|
|
47
|
+
const response = (0, safe_exec_1.runCurl)([
|
|
48
|
+
`${config.url}/api/v3/secrets/raw?workspaceId=${config.projectId}&environment=${environment}&secretPath=${encodeURIComponent(secretPath)}`,
|
|
49
|
+
'-H', `Authorization: Bearer ${token}`,
|
|
50
|
+
]);
|
|
47
51
|
const data = JSON.parse(response);
|
|
48
52
|
const secrets = {};
|
|
49
53
|
for (const secret of data.secrets || []) {
|