pi-codex-marketplace 0.4.0 → 0.5.0
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/README.md
CHANGED
|
@@ -37,7 +37,7 @@ Nine subcommands, no arguments = 總覽:
|
|
|
37
37
|
|
|
38
38
|
| 子命令 | 行為 |
|
|
39
39
|
|--------|------|
|
|
40
|
-
| `add <路徑\|網址>` | 註冊 marketplace(本機資料夾、GitHub 完整網址、`owner/repo` 簡寫皆收),自動偵測 codex/claude 格式並告知 `偵測:<format> marketplace · N plugins`。重複註冊同來源被拒絕並提示下一步。Git 來源以安全線取得(`core.hooksPath=/dev/null`、`GIT_LFS_SKIP_SMUDGE=1`、`GIT_TERMINAL_PROMPT=0`),catalog
|
|
40
|
+
| `add <路徑\|網址>` | 註冊 marketplace(本機資料夾、GitHub 完整網址、`owner/repo` 簡寫皆收),自動偵測 codex/claude 格式並告知 `偵測:<format> marketplace · N plugins`。重複註冊同來源被拒絕並提示下一步。Git 來源以安全線取得(`core.hooksPath=/dev/null`、`GIT_LFS_SKIP_SMUDGE=1`、`GIT_TERMINAL_PROMPT=0`),catalog 解析失敗明示錯誤、不註冊。私有 HTTPS repo 預設 credential-free(有權限也可能 401)——先以 `PI_CODEX_MARKETPLACE_CREDENTIAL_HELPERS` 核准 credential helper 再 add(逐次生效、永不持久化),或改用 SSH 定位器;用法與範例見下方〈私有 Git repo:Credentialed Acquisition〉。 |
|
|
41
41
|
| `list [名稱]` | 列出 plugins(編號/所屬 marketplace/狀態:可安裝・已裝啟用・已裝停用・unavailable+原因),可帶 marketplace 名稱過濾。 |
|
|
42
42
|
| `install <編號\|名稱>` | 裝到**當下最新**並自動啟用+reload。成功話術如 `安裝 "name"(N skills:a, b, c)· 已重新載入生效`;同名衝突列出 `⚠ skill "b" 與既有同名,未投影(名稱衝突)`。**重複安裝=重抓最新覆寫**(重裝=更新,不報錯)。 |
|
|
43
43
|
| `update` | 對全部已註冊 marketplace 重抓最新:有變化的 plugin 升到最新、無變化各自顯示「無變化」;整體以「已重新載入生效」收尾(有變時)。 |
|
|
@@ -53,6 +53,37 @@ Nine subcommands, no arguments = 總覽:
|
|
|
53
53
|
- 安裝成功後由指令層主動要求 reload;reload 失敗不影響已記錄狀態,下次 session start 或 `/reload` 仍生效。
|
|
54
54
|
- `--no-skills` 啟動 Pi 不影響 Bridge 投影。
|
|
55
55
|
|
|
56
|
+
### 私有 Git repo:Credentialed Acquisition(核准式取得)
|
|
57
|
+
|
|
58
|
+
預設 `add`/`update` 對 Git 來源完全 credential-free:不執行任何 credential helper,因此「有權限卻失敗」時,私有 HTTPS repo 會以 401 失敗(並提示核准或改用 SSH)。若要核准 credential helper,以逗號分隔設定環境變數(**逐次生效、永不持久化**,`add` 與 `update` 共用同一核准來源):
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
PI_CODEX_MARKETPLACE_CREDENTIAL_HELPERS='store, !f() { echo "username=${GITHUB_USER}"; echo "password=${GITHUB_TOKEN}"; }; f'
|
|
62
|
+
/codex-marketplace add https://github.com/acme/private-mkt
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Credentialed Acquisition 語意(安全線):
|
|
66
|
+
|
|
67
|
+
- 核准僅限該次呼叫(per-invocation):環境變數逐指令讀取,**不寫入** Bridge State、設定檔或任何持久化位置;重開 session 後需重新設定。
|
|
68
|
+
- 值為 git `credential.helper` 字串,逗號分隔、各項 trim、空項目忽略;未設定或空白=未核准(行為與 credential-free 完全相同,安全線其餘禁制不變)。
|
|
69
|
+
- 憑證與核准清單**永不**進入指令輸出、Bridge State、Canonical Git Locator(定位器)、Validation Snapshot(快照)或 cache identity(快取身份)——取得流程的 identity 判定與憑證完全無關。
|
|
70
|
+
- 已核准 helper 仍被遠端拒絕(401)時,錯誤訊息提示檢查憑證;未核准時提示核准或改用 SSH。
|
|
71
|
+
|
|
72
|
+
#### SSH 定位器:私有 repo 的替代路徑
|
|
73
|
+
|
|
74
|
+
私有 repo 可以完全繞過此環境變數,直接用 SSH 定位器註冊(HTTPS 與 SSH 同屬允許的 credential-free 定位器):
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
/codex-marketplace add git@github.com:acme/private-mkt # scp-like 簡寫(canonical:ssh://git@github.com/acme/private-mkt)
|
|
78
|
+
/codex-marketplace add ssh://git@github.com/acme/private-mkt
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
前提(與既有 Acquisition Trust Base 一致):
|
|
82
|
+
|
|
83
|
+
- host key 必須**預先存在** `~/.ssh/known_hosts`——安全線以 `StrictHostKeyChecking=yes` 只信任既有 host key,遇到未知或變更的主機金鑰直接拒絕,不會提示接受;
|
|
84
|
+
- 憑證由 SSH agent 提供,整個取得過程不互動(`BatchMode=yes`)、無任何提示;
|
|
85
|
+
- SSH 定位器本身仍維持 credential-free:不得內嵌密碼(`user:pass@` 拒絕);憑證只能經由 SSH agent 或 Credentialed Acquisition 到達取得流程。
|
|
86
|
+
|
|
56
87
|
## Bridge State storage
|
|
57
88
|
|
|
58
89
|
Bridge State 是唯一權威,存於**單一 Global Scope 文件** `{getAgentDir()}/codex-marketplace/state.json`(`~/.pi/agent/codex-marketplace/state.json`):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-codex-marketplace",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Bridge Package for Codex and Claude Marketplace compatibility in Pi — 極簡 /codex-marketplace 純文字指令(add/list/install/update/disable/enable/remove/forget)、單一 Global Bridge State、當下最新安裝與即時投影",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
package/src/bridge/command.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { findEntryByManifestName, GIT_FAMILY_UNAVAILABLE_REASON, type Catalog, t
|
|
|
31
31
|
import type { ValidationFinding } from '../registration/findings.js';
|
|
32
32
|
import { normalizeGitLocator } from '../registration/git-locator.js';
|
|
33
33
|
import { acquireGitSource, cleanupAcquisition, type GitExecutor } from '../registration/git-acquisition.js';
|
|
34
|
+
import { CREDENTIAL_HELPERS_ENV, parseCredentialHelpers } from '../registration/credential-helpers.js';
|
|
34
35
|
import { gitSourceKey } from '../registration/source-key.js';
|
|
35
36
|
import { buildGitSnapshot } from '../registration/snapshot.js';
|
|
36
37
|
import { SourceCache } from '../cache/source-cache.js';
|
|
@@ -598,6 +599,13 @@ export async function runCommand(
|
|
|
598
599
|
): Promise<CommandResult> {
|
|
599
600
|
const rawArgs = typeof argv === 'string' ? argv.trim().split(/\s+/).filter(Boolean) : [...argv];
|
|
600
601
|
|
|
602
|
+
// Credentialed Acquisition (#109):逐次核准的 credential helper allowlist。
|
|
603
|
+
// 解析結果只經既有 AcquisitionTrustOptions 傳給 Git 取得;底層不讀環境變數,
|
|
604
|
+
// 未核准(空字串/未設定)時 trust 為 undefined,行為與 credential-free 完全一致。
|
|
605
|
+
const approvedHelpers = parseCredentialHelpers(process.env[CREDENTIAL_HELPERS_ENV]);
|
|
606
|
+
const acquireTrust: { allowedCredentialHelpers: string[] } | undefined =
|
|
607
|
+
approvedHelpers.length > 0 ? { allowedCredentialHelpers: approvedHelpers } : undefined;
|
|
608
|
+
|
|
601
609
|
// Strip leading command token if passed
|
|
602
610
|
if (rawArgs.length > 0 && (rawArgs[0] === '/codex-marketplace' || rawArgs[0] === 'codex-marketplace')) {
|
|
603
611
|
rawArgs.shift();
|
|
@@ -671,6 +679,7 @@ export async function runCommand(
|
|
|
671
679
|
acquireResult = await acquireGitSource({
|
|
672
680
|
locator,
|
|
673
681
|
executor: opts.gitExecutor,
|
|
682
|
+
trust: acquireTrust,
|
|
674
683
|
});
|
|
675
684
|
} catch (e) {
|
|
676
685
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -1089,6 +1098,7 @@ export async function runCommand(
|
|
|
1089
1098
|
acquireResult = await acquireGitSource({
|
|
1090
1099
|
locator: locRes.locator!,
|
|
1091
1100
|
executor: opts.gitExecutor,
|
|
1101
|
+
trust: acquireTrust,
|
|
1092
1102
|
});
|
|
1093
1103
|
} catch (e) {
|
|
1094
1104
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credentialed Acquisition approval (#109) — env-var allowlist parsing.
|
|
3
|
+
* See CONTEXT.md: Credentialed Acquisition; docs/adr/0006-credentialed-acquisition-approval.md.
|
|
4
|
+
*
|
|
5
|
+
* Per-invocation approval of git credential helpers via
|
|
6
|
+
* `PI_CODEX_MARKETPLACE_CREDENTIAL_HELPERS` (comma-separated `credential.helper`
|
|
7
|
+
* strings). Empty/unset means "not approved" — behavior identical to today.
|
|
8
|
+
* This module only parses; the resulting allowlist travels through
|
|
9
|
+
* AcquisitionTrustOptions and never touches Bridge State, snapshots, or cache identity.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const CREDENTIAL_HELPERS_ENV = 'PI_CODEX_MARKETPLACE_CREDENTIAL_HELPERS';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse the approval env var into a helper allowlist.
|
|
16
|
+
* Comma-separated, each entry trimmed, empty entries ignored.
|
|
17
|
+
* Empty/unset → no approval ([]).
|
|
18
|
+
*/
|
|
19
|
+
export function parseCredentialHelpers(raw: string | undefined): string[] {
|
|
20
|
+
if (!raw) return [];
|
|
21
|
+
return raw
|
|
22
|
+
.split(',')
|
|
23
|
+
.map((entry) => entry.trim())
|
|
24
|
+
.filter((entry) => entry.length > 0);
|
|
25
|
+
}
|
|
@@ -52,6 +52,7 @@ export const RULE = {
|
|
|
52
52
|
GIT_TRUST_HOST_KEY: 'GIT-31',
|
|
53
53
|
GIT_TRUST_REDIRECT: 'GIT-32',
|
|
54
54
|
GIT_TRUST_CREDENTIAL_HELPER: 'GIT-33',
|
|
55
|
+
GIT_TRUST_AUTH_REQUIRED: 'GIT-34',
|
|
55
56
|
} as const;
|
|
56
57
|
|
|
57
58
|
export const CODE = {
|
|
@@ -75,6 +76,8 @@ export const CODE = {
|
|
|
75
76
|
GIT_TRUST_HOST_KEY_CHANGED: 'GIT_TRUST_HOST_KEY_CHANGED',
|
|
76
77
|
GIT_TRUST_REDIRECT: 'GIT_TRUST_REDIRECT',
|
|
77
78
|
GIT_TRUST_CREDENTIAL_HELPER: 'GIT_TRUST_CREDENTIAL_HELPER',
|
|
79
|
+
GIT_TRUST_AUTH_REQUIRED: 'GIT_TRUST_AUTH_REQUIRED',
|
|
80
|
+
GIT_REPO_NOT_FOUND: 'GIT_REPO_NOT_FOUND',
|
|
78
81
|
} as const;
|
|
79
82
|
|
|
80
83
|
const CLASS_RANK: Record<FindingClass, number> = { blocking: 0, warning: 1, notice: 2 };
|
|
@@ -15,6 +15,7 @@ import { join } from 'node:path';
|
|
|
15
15
|
|
|
16
16
|
import { CODE, RULE, blocking, type ValidationFinding } from './findings.js';
|
|
17
17
|
import type { CanonicalGitLocator } from './git-locator.js';
|
|
18
|
+
import { CREDENTIAL_HELPERS_ENV } from './credential-helpers.js';
|
|
18
19
|
|
|
19
20
|
export interface GitExecutor {
|
|
20
21
|
(args: string[], opts?: { cwd?: string; env?: Record<string, string> }): Promise<{
|
|
@@ -126,50 +127,106 @@ function isFullHex(s: string): boolean {
|
|
|
126
127
|
return /^[0-9a-f]{40}$/.test(s) || /^[0-9a-f]{64}$/.test(s);
|
|
127
128
|
}
|
|
128
129
|
|
|
130
|
+
/**
|
|
131
|
+
* 共用失敗分類(#110):ls-remote 與 clone 兩條取得路徑採用同一分類與訊息。
|
|
132
|
+
* - auth:伺服器 401(authentication failed)→ GIT-34,訊息依核准狀態分兩變體;
|
|
133
|
+
* - helper:credential source 拒絕(原字串匹配的 helper 拒絕情境)→ GIT-33 保留;
|
|
134
|
+
* - not-found:「repository not found」類字串 → 標明 repo 不存在(保留非 GitHub 情境;
|
|
135
|
+
* GitHub smart-HTTP 對不存在 repo 實測回 401,落入 auth 分支)。
|
|
136
|
+
*/
|
|
137
|
+
type FailureKind =
|
|
138
|
+
| { kind: 'host-key'; isChanged: boolean }
|
|
139
|
+
| { kind: 'redirect' }
|
|
140
|
+
| { kind: 'not-found' }
|
|
141
|
+
| { kind: 'auth'; approved: boolean }
|
|
142
|
+
| { kind: 'helper'; approved: boolean };
|
|
143
|
+
|
|
144
|
+
function classifyFailure(stderr: string, approvedHelpers: boolean): FailureKind | null {
|
|
145
|
+
const lower = stderr.toLowerCase();
|
|
146
|
+
if (lower.includes('host key verification failed') || lower.includes('unknown host key') || lower.includes('offending')) {
|
|
147
|
+
return { kind: 'host-key', isChanged: lower.includes('changed') || lower.includes('offending') || lower.includes('key changed') };
|
|
148
|
+
}
|
|
149
|
+
if (lower.includes('redirect') || lower.includes('moved') || lower.includes('followredirects')) {
|
|
150
|
+
return { kind: 'redirect' };
|
|
151
|
+
}
|
|
152
|
+
if (lower.includes('repository not found') || lower.includes('repo not found') || lower.includes('does not appear to be a git repository') || lower.includes("' not found")) {
|
|
153
|
+
return { kind: 'not-found' };
|
|
154
|
+
}
|
|
155
|
+
if (lower.includes('authentication failed')) {
|
|
156
|
+
return { kind: 'auth', approved: approvedHelpers };
|
|
157
|
+
}
|
|
158
|
+
if (
|
|
159
|
+
lower.includes('could not read username') ||
|
|
160
|
+
lower.includes('could not read password') ||
|
|
161
|
+
lower.includes('terminal prompts disabled') ||
|
|
162
|
+
lower.includes('credential')
|
|
163
|
+
) {
|
|
164
|
+
return { kind: 'helper', approved: approvedHelpers };
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function failureFinding(kind: FailureKind, locator: CanonicalGitLocator, stderr: string): ValidationFinding {
|
|
170
|
+
switch (kind.kind) {
|
|
171
|
+
case 'host-key':
|
|
172
|
+
return trustFinding(
|
|
173
|
+
kind.isChanged ? CODE.GIT_TRUST_HOST_KEY_CHANGED : CODE.GIT_TRUST_HOST_KEY_UNKNOWN,
|
|
174
|
+
RULE.GIT_TRUST_HOST_KEY,
|
|
175
|
+
`Acquisition Trust Base violation: SSH host key ${kind.isChanged ? 'changed' : 'unknown'} for ${locator.host} — ${stderr.trim()} (only pre-established known-host keys are trusted)`,
|
|
176
|
+
);
|
|
177
|
+
case 'redirect':
|
|
178
|
+
return trustFinding(
|
|
179
|
+
CODE.GIT_TRUST_REDIRECT,
|
|
180
|
+
RULE.GIT_TRUST_REDIRECT,
|
|
181
|
+
`Acquisition Trust Base violation: redirect that would change canonical locator (followRedirects disabled) — ${stderr.trim()}`,
|
|
182
|
+
);
|
|
183
|
+
case 'not-found':
|
|
184
|
+
return trustFinding(
|
|
185
|
+
CODE.GIT_REPO_NOT_FOUND,
|
|
186
|
+
RULE.GIT_TRUST_AUTH_REQUIRED,
|
|
187
|
+
`Acquisition Trust Base violation: repository not found — '${locator.canonicalUrl}' does not exist (check the URL or owner/repo name) — ${stderr.trim()}`,
|
|
188
|
+
);
|
|
189
|
+
case 'auth': {
|
|
190
|
+
// 伺服器 401:訊息依核准狀態分兩變體(未核准 → 指出 credential-free 且需認證,指引核准或 SSH;已核准仍 401 → 指引檢查登入)。
|
|
191
|
+
const why = kind.approved
|
|
192
|
+
? `approved credential helper did not provide valid credentials — check your login with 'gh auth status' or your keychain`
|
|
193
|
+
: `repository requires authentication (private or nonexistent) and this acquisition is credential-free — approve a credential helper via ${CREDENTIAL_HELPERS_ENV}, or switch to an SSH locator`;
|
|
194
|
+
return trustFinding(
|
|
195
|
+
CODE.GIT_TRUST_AUTH_REQUIRED,
|
|
196
|
+
RULE.GIT_TRUST_AUTH_REQUIRED,
|
|
197
|
+
`Acquisition Trust Base violation: ${why} — ${stderr.trim()}`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
case 'helper': {
|
|
201
|
+
// GIT-33 保留:credential source 拒絕(could not read Username/Password 等原字串匹配);
|
|
202
|
+
// 訊息依核准狀態分兩變體,未核准時維持原「not approved」措辭。
|
|
203
|
+
const why = kind.approved
|
|
204
|
+
? `approved credential helper failed to supply credentials — check your login with 'gh auth status' or your keychain`
|
|
205
|
+
: `credential helper/agent not approved — set ${CREDENTIAL_HELPERS_ENV} to approve one, or use SSH`;
|
|
206
|
+
return trustFinding(
|
|
207
|
+
CODE.GIT_TRUST_CREDENTIAL_HELPER,
|
|
208
|
+
RULE.GIT_TRUST_CREDENTIAL_HELPER,
|
|
209
|
+
`Acquisition Trust Base: ${why} — ${stderr.trim()}`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
129
215
|
async function resolveHead(
|
|
130
216
|
locator: CanonicalGitLocator,
|
|
131
217
|
executor: GitExecutor,
|
|
132
218
|
env: Record<string, string>,
|
|
133
219
|
configArgs: string[],
|
|
220
|
+
approvedHelpers: boolean,
|
|
134
221
|
): Promise<{ ok: true; sha: string } | { ok: false; findings: ValidationFinding[]; stderr?: string }> {
|
|
135
222
|
const lsArgs = [...configArgs, 'ls-remote', locator.canonicalUrl, 'HEAD'];
|
|
136
223
|
const res = await executor(lsArgs, { env });
|
|
137
224
|
if (res.exitCode !== 0) {
|
|
138
|
-
const
|
|
139
|
-
if (
|
|
140
|
-
const isChanged = stderr.includes('changed') || stderr.includes('offending') || stderr.includes('key changed');
|
|
141
|
-
const code = isChanged ? CODE.GIT_TRUST_HOST_KEY_CHANGED : CODE.GIT_TRUST_HOST_KEY_UNKNOWN;
|
|
142
|
-
return {
|
|
143
|
-
ok: false,
|
|
144
|
-
findings: [
|
|
145
|
-
trustFinding(
|
|
146
|
-
code,
|
|
147
|
-
RULE.GIT_TRUST_HOST_KEY,
|
|
148
|
-
`Acquisition Trust Base violation: SSH host key ${isChanged ? 'changed' : 'unknown'} for ${locator.host} — ${res.stderr.trim()} (only pre-established known-host keys are trusted)`,
|
|
149
|
-
),
|
|
150
|
-
],
|
|
151
|
-
stderr: res.stderr,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
if (stderr.includes('redirect') || stderr.includes('moved') || stderr.includes('followredirects')) {
|
|
155
|
-
return {
|
|
156
|
-
ok: false,
|
|
157
|
-
findings: [
|
|
158
|
-
trustFinding(CODE.GIT_TRUST_REDIRECT, RULE.GIT_TRUST_REDIRECT, `Acquisition Trust Base violation: redirect that would change canonical locator (followRedirects disabled) — ${res.stderr.trim()}`),
|
|
159
|
-
],
|
|
160
|
-
stderr: res.stderr,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
if (stderr.includes('could not read username') || stderr.includes('authentication failed') || stderr.includes('credential')) {
|
|
225
|
+
const kind = classifyFailure(res.stderr || '', approvedHelpers);
|
|
226
|
+
if (kind) {
|
|
164
227
|
return {
|
|
165
228
|
ok: false,
|
|
166
|
-
findings: [
|
|
167
|
-
trustFinding(
|
|
168
|
-
CODE.GIT_ACQUISITION_FAILED,
|
|
169
|
-
RULE.GIT_TRUST_CREDENTIAL_HELPER,
|
|
170
|
-
`Acquisition Trust Base: credential helper/agent not approved — ${res.stderr.trim()}`,
|
|
171
|
-
),
|
|
172
|
-
],
|
|
229
|
+
findings: [failureFinding(kind, locator, res.stderr)],
|
|
173
230
|
stderr: res.stderr,
|
|
174
231
|
};
|
|
175
232
|
}
|
|
@@ -211,7 +268,8 @@ export async function resolveGitRevision(
|
|
|
211
268
|
): Promise<{ ok: true; sha: string } | { ok: false; findings: ValidationFinding[]; stderr?: string }> {
|
|
212
269
|
const env = hardenedEnv(opts.trust, locator);
|
|
213
270
|
const configArgs = hardenedConfigArgs(opts.trust);
|
|
214
|
-
|
|
271
|
+
const approvedHelpers = (opts.trust?.allowedCredentialHelpers?.length ?? 0) > 0;
|
|
272
|
+
return resolveHead(locator, opts.executor ?? defaultGitExecutor(), env, configArgs, approvedHelpers);
|
|
215
273
|
}
|
|
216
274
|
|
|
217
275
|
/**
|
|
@@ -224,7 +282,7 @@ export async function acquireGitSource(opts: AcquireOptions): Promise<AcquireRes
|
|
|
224
282
|
const env = hardenedEnv(trust, locator);
|
|
225
283
|
const configArgs = hardenedConfigArgs(trust);
|
|
226
284
|
|
|
227
|
-
const resolved = await resolveHead(locator, executor, env, configArgs);
|
|
285
|
+
const resolved = await resolveHead(locator, executor, env, configArgs, (trust?.allowedCredentialHelpers?.length ?? 0) > 0);
|
|
228
286
|
if (!resolved.ok) {
|
|
229
287
|
return { ok: false, findings: (resolved as { findings: ValidationFinding[] }).findings, stderr: (resolved as { stderr?: string }).stderr };
|
|
230
288
|
}
|
|
@@ -243,39 +301,12 @@ export async function acquireGitSource(opts: AcquireOptions): Promise<AcquireRes
|
|
|
243
301
|
const cloneRes = await executor(cloneArgs, { env });
|
|
244
302
|
if (cloneRes.exitCode !== 0) {
|
|
245
303
|
const stderr = cloneRes.stderr || '';
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (createdTemp) try { rmSync(dest, { recursive: true, force: true }); } catch {}
|
|
251
|
-
return {
|
|
252
|
-
ok: false,
|
|
253
|
-
findings: [
|
|
254
|
-
trustFinding(
|
|
255
|
-
code,
|
|
256
|
-
RULE.GIT_TRUST_HOST_KEY,
|
|
257
|
-
`Acquisition Trust Base violation: SSH host key ${isChanged ? 'changed' : 'unknown'} — ${stderr.trim()}`,
|
|
258
|
-
),
|
|
259
|
-
],
|
|
260
|
-
stderr,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
if (lower.includes('redirect') || lower.includes('moved permanently') || lower.includes('followredirects')) {
|
|
264
|
-
if (createdTemp) try { rmSync(dest, { recursive: true, force: true }); } catch {}
|
|
265
|
-
return {
|
|
266
|
-
ok: false,
|
|
267
|
-
findings: [
|
|
268
|
-
trustFinding(CODE.GIT_TRUST_REDIRECT, RULE.GIT_TRUST_REDIRECT, `Acquisition Trust Base violation: redirect changing canonical locator — ${stderr.trim()}`),
|
|
269
|
-
],
|
|
270
|
-
stderr,
|
|
271
|
-
};
|
|
272
|
-
}
|
|
304
|
+
const kind = classifyFailure(stderr, (trust?.allowedCredentialHelpers?.length ?? 0) > 0);
|
|
305
|
+
const finding = kind
|
|
306
|
+
? failureFinding(kind, locator, stderr)
|
|
307
|
+
: acquireFinding(`git clone failed: ${stderr.trim() || `exit ${cloneRes.exitCode}`}`);
|
|
273
308
|
if (createdTemp) try { rmSync(dest, { recursive: true, force: true }); } catch {}
|
|
274
|
-
return {
|
|
275
|
-
ok: false,
|
|
276
|
-
findings: [acquireFinding(`git clone failed: ${stderr.trim() || `exit ${cloneRes.exitCode}`}`)],
|
|
277
|
-
stderr,
|
|
278
|
-
};
|
|
309
|
+
return { ok: false, findings: [finding], stderr };
|
|
279
310
|
}
|
|
280
311
|
|
|
281
312
|
if (trust?.allowRedirects !== true) {
|