mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-11-07 06:19:43 -10:00
fix(verify-pr): refuse nested repositories in the credential scan
Round-4 verification at 19b14b1a8f left three Suggestions.
- A nested git repository is now refused instead of silently skipped: git
does not descend into it, so a .env under tmp/pr-clone/ would pass. A
vendored .git under node_modules/dist stays excluded like the files there.
- isExcluded is removed: the pathspecs already exclude those directories,
and the surviving JS filter was unpinnable.
- The spaced Connection-nomination list is pinned; dropping trim made the
proxy answer 502.
- The node_modules/dist credential exclusions are documented as deliberate.
Tests: 152 passed; two mutations verified red (nested refusal, trim).
This commit is contained in:
1 parent
19b14b1a8f
commit
fed6afb717
6 files changed
+80
-22
No files matched your search
@@ -340,9 +340,11 @@ certify the round.
|
||||
'
|
||||
```
|
||||
|
||||
The scan names credential-shaped ignored files (nested ones included) while
|
||||
leaving installed dependencies and build output alone. Exit 4 means this is
|
||||
not a boundary: stop and run nothing else.
|
||||
The scan names credential-shaped ignored files (nested ones included),
|
||||
leaves installed dependencies and build output alone, and refuses a nested
|
||||
git repository outright: git does not descend into it, so a credential
|
||||
file inside one would otherwise pass silently. Exit 4 means this is not a
|
||||
boundary: stop and run nothing else.
|
||||
|
||||
6. **Run the round inside**, installing the CLI into the fresh home because
|
||||
the container is not root. Each `docker exec` is a new shell, so the
|
||||
|
||||
@@ -97,9 +97,11 @@ the real key.
|
||||
and the CLI's JSON coordinates.
|
||||
- Credential scan: a nested ignored `.env`, `*.pem`, `credentials*`, `.npmrc`
|
||||
or `.netrc` is refused; installed `node_modules`/`dist` are ignored at any
|
||||
depth, including inside a workspace package; enumeration is path-limited so
|
||||
a large ignored dependency tree is never buffered; a missing `--ignored`
|
||||
mode or a dropped pattern turns its test red.
|
||||
depth, including inside a workspace package, and their contents are a
|
||||
documented exclusion; enumeration is path-limited so a large ignored
|
||||
dependency tree is never buffered; a nested git repository is refused
|
||||
because git does not descend into it; a missing `--ignored` mode or a
|
||||
dropped pattern turns its test red.
|
||||
- Checker: `--context` overrides the writable artifact copy, refuses an
|
||||
unusable snapshot instead of falling back (with a usable writable copy
|
||||
seeded), and keeps its existing exit codes.
|
||||
|
||||
@@ -35,7 +35,7 @@ CI 车道保留内联代理:那份副本运行在 base-pinned workflow 的 roo
|
||||
## 验收
|
||||
|
||||
- 代理:bearer 拒绝、精确路径白名单、base path 前缀、真实 key 注入、chunked 请求体下 hop-by-hop 剥离(固定集合与 `Connection` 点名的头)、gzip 上游响应(头被剥离)、响应头前断连中止、下游暂停存活、慢但健康的流、上游重置存活、IPv6 监听格式化,以及 CLI 的 JSON 坐标。
|
||||
- 凭据扫描:嵌套 ignored 的 `.env`、`*.pem`、`credentials*`、`.npmrc`、`.netrc` 被拒绝;`node_modules`/`dist` 在任意深度(含 workspace 包内)被忽略;枚举由 pathspec 限制,不会先缓冲整棵依赖树;去掉 `--ignored` 模式或删掉某条模式会让测试变红。
|
||||
- 凭据扫描:嵌套 ignored 的 `.env`、`*.pem`、`credentials*`、`.npmrc`、`.netrc` 被拒绝;`node_modules`/`dist` 在任意深度(含 workspace 包内)被忽略,其内容属文档化取舍;枚举由 pathspec 限制,不会先缓冲整棵依赖树;嵌套 git 仓库被直接拒绝(git 不会进入其中);去掉 `--ignored` 模式或删掉某条模式会让测试变红。
|
||||
- 检查器:`--context` 覆盖可写产物副本、不可用快照时拒绝而不是回退(产物副本先种一个合法 head),并保持既有退出码。
|
||||
- 配方:每个脚本都点名可信 checkout、base 提交的 `.qwen` 只读挂载、两个凭据都来自文件而非命令行、导出 token 前等待代理坐标就绪、宣誓在轮次 shell 但不在 `docker run` 行;任一被还原都会让配方 pin 变红。
|
||||
- 变异证据:移除上述任一守卫,对应 pin 必须变红。
|
||||
|
||||
@@ -144,6 +144,20 @@ describe('scanIgnoredCredentials', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('refuses a nested repository instead of silently skipping it', () => {
|
||||
const dir = makeRepo(['tmp/pr-clone/.env'], ['tmp/']);
|
||||
execFileSync('git', ['init', '-q', '.'], {
|
||||
cwd: join(dir, 'tmp', 'pr-clone'),
|
||||
env: gitEnv,
|
||||
});
|
||||
expect(() => scanIgnoredCredentials(dir)).toThrow(
|
||||
/nested git repository under tmp\/pr-clone/,
|
||||
);
|
||||
const result = run(dir);
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr).toContain('nested git repository');
|
||||
});
|
||||
|
||||
it('stays silent on a clean tree', () => {
|
||||
const dir = makeRepo(['src/app.ts'], ['node_modules/']);
|
||||
const result = run(dir);
|
||||
@@ -168,6 +182,8 @@ describe('scanIgnoredCredentials', () => {
|
||||
tmpDirs.push(notRepo);
|
||||
const result = run(notRepo);
|
||||
expect(result.status).toBe(1);
|
||||
expect(result.stderr).toContain('could not list ignored files');
|
||||
expect(result.stderr).toMatch(
|
||||
/could not list (nested repositories|ignored files)/,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -253,7 +253,7 @@ describe('verify-pr-model-proxy', () => {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
authorization: `Bearer ${TOKEN}`,
|
||||
connection: 'x-client-hop',
|
||||
connection: ' x-client-hop , keep-alive ',
|
||||
'x-client-hop': 'client-secret',
|
||||
'proxy-authorization': 'Basic client-proxy-secret',
|
||||
'transfer-encoding': 'chunked',
|
||||
@@ -622,6 +622,10 @@ describe('local container recipe pins', () => {
|
||||
expect(skill).toContain('npm install -g --prefix "$HOME/.local" /work');
|
||||
});
|
||||
|
||||
it('refuses a nested repository in the scan description', () => {
|
||||
expect(skill).toMatch(/nested\s+git repository outright/);
|
||||
});
|
||||
|
||||
it('pins the checker head from the host snapshot', () => {
|
||||
expect(skill).toContain('--context ~/.cache/qwen-verify/pr<n>/pr.json');
|
||||
});
|
||||
|
||||
@@ -22,7 +22,10 @@
|
||||
* The pathspecs both restrict enumeration to credential-shaped names and
|
||||
* exclude those directories, so an installed tree's multi-megabyte ignored
|
||||
* listing never reaches the output buffer. A credential file placed in the
|
||||
* project (including a nested ignored directory) is still reported.
|
||||
* project (including a nested ignored directory) is still reported. Two
|
||||
* exclusions are deliberate: credentials inside `node_modules`/`dist` are
|
||||
* accepted as installed/build content, and a nested git repository is refused
|
||||
* outright because git will not descend into it — scan that clone separately.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/verify-pr-credential-scan.mjs <root>
|
||||
@@ -34,8 +37,6 @@ import { spawnSync } from 'node:child_process';
|
||||
import { basename } from 'node:path';
|
||||
import { isEntryPoint } from './lib/entry-point.js';
|
||||
|
||||
const EXCLUDED_SEGMENTS = new Set(['node_modules', 'dist', '.git']);
|
||||
|
||||
const CREDENTIAL_BASENAME =
|
||||
/^(?:\.env(?:\..*)?|.*\.env|.*\.pem|.*\.key|credentials.*|\.npmrc|\.netrc)$/;
|
||||
|
||||
@@ -55,19 +56,21 @@ const CREDENTIAL_PATHSPECS = [
|
||||
':(exclude,glob)**/dist/**',
|
||||
];
|
||||
|
||||
// A nested clone is a repository git does not descend into, so a credential
|
||||
// file inside it would silently pass an ignored-file scan. Dependencies and
|
||||
// build output keep their own exclusion: a vendored `.git` is no more a
|
||||
// project credential than a vendored `.env`.
|
||||
const NESTED_REPO_PATHSPECS = [
|
||||
':(glob)**/.git',
|
||||
':(exclude,glob)**/node_modules/**',
|
||||
':(exclude,glob)**/dist/**',
|
||||
];
|
||||
|
||||
/** True for a path whose basename is credential-shaped. */
|
||||
export function isCredentialShaped(path) {
|
||||
return CREDENTIAL_BASENAME.test(basename(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* True for a path under an installed/build directory at any depth, including
|
||||
* a workspace package's own `node_modules` and `dist`.
|
||||
*/
|
||||
export function isExcluded(path) {
|
||||
return path.split('/').some((segment) => EXCLUDED_SEGMENTS.has(segment));
|
||||
}
|
||||
|
||||
export function gitScanArgs(root) {
|
||||
return [
|
||||
'-C',
|
||||
@@ -82,15 +85,47 @@ export function gitScanArgs(root) {
|
||||
];
|
||||
}
|
||||
|
||||
/** Returns the nested repositories found under `root`, sorted. */
|
||||
export function nestedGitRepos(root, { maxBuffer = 8 * 1024 * 1024 } = {}) {
|
||||
const result = spawnSync(
|
||||
'git',
|
||||
[
|
||||
'-C',
|
||||
root,
|
||||
'ls-files',
|
||||
'--others',
|
||||
'--ignored',
|
||||
'--exclude-standard',
|
||||
'-z',
|
||||
'--',
|
||||
...NESTED_REPO_PATHSPECS,
|
||||
],
|
||||
{ encoding: 'utf8', maxBuffer },
|
||||
);
|
||||
if (result.status !== 0) {
|
||||
throw new Error(
|
||||
`could not list nested repositories: ${(result.stderr || '').trim() || 'git failed'}`,
|
||||
);
|
||||
}
|
||||
return result.stdout.split('\0').filter(Boolean).sort();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ignored credential-shaped files under `root`, sorted. An empty
|
||||
* array is the clean state; a git failure is thrown so the caller can refuse
|
||||
* rather than treat it as clean.
|
||||
* rather than treat it as clean, and a nested repository is refused because a
|
||||
* scan of the outer tree cannot see into it.
|
||||
*/
|
||||
export function scanIgnoredCredentials(
|
||||
root,
|
||||
{ maxBuffer = 8 * 1024 * 1024 } = {},
|
||||
) {
|
||||
const nested = nestedGitRepos(root, { maxBuffer });
|
||||
if (nested.length > 0) {
|
||||
throw new Error(
|
||||
`nested git repository under ${nested[0]} — scan that clone separately or remove it`,
|
||||
);
|
||||
}
|
||||
const result = spawnSync('git', gitScanArgs(root), {
|
||||
encoding: 'utf8',
|
||||
maxBuffer,
|
||||
@@ -103,7 +138,6 @@ export function scanIgnoredCredentials(
|
||||
return result.stdout
|
||||
.split('\0')
|
||||
.filter(Boolean)
|
||||
.filter((path) => !isExcluded(path))
|
||||
.filter((path) => isCredentialShaped(path))
|
||||
.sort();
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user