@itenl/sec 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +703 -2
- package/bin/sec.mjs +16 -0
- package/lib/aliases.mjs +46 -0
- package/lib/auth.mjs +94 -0
- package/lib/cli.mjs +169 -0
- package/lib/config-command.mjs +133 -0
- package/lib/config.mjs +207 -0
- package/lib/engine.mjs +96 -0
- package/lib/policy.mjs +8 -0
- package/lib/project-init.mjs +111 -0
- package/lib/project.mjs +305 -0
- package/lib/report.mjs +210 -0
- package/lib/runtime.mjs +83 -0
- package/lib/scan.mjs +328 -0
- package/lib/setup.mjs +217 -0
- package/lib/tracker.mjs +279 -0
- package/lib/verify.mjs +388 -0
- package/package.json +49 -4
- package/templates/review-report.md +27 -0
- package/templates/security-policy.md +31 -0
package/lib/verify.mjs
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { engineEnvironment, providerArguments, ensureEngine, invokeEngine } from './engine.mjs';
|
|
2
|
+
import {
|
|
3
|
+
readConfig,
|
|
4
|
+
assertOutsideRepository,
|
|
5
|
+
userDirectories,
|
|
6
|
+
repositoryModelSettings
|
|
7
|
+
} from './config.mjs';
|
|
8
|
+
import { execFileSync } from 'node:child_process';
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import {
|
|
11
|
+
lstatSync,
|
|
12
|
+
mkdirSync,
|
|
13
|
+
readFileSync,
|
|
14
|
+
readlinkSync,
|
|
15
|
+
realpathSync,
|
|
16
|
+
writeFileSync
|
|
17
|
+
} from 'node:fs';
|
|
18
|
+
import { dirname, join, resolve } from 'node:path';
|
|
19
|
+
import { fileURLToPath } from 'node:url';
|
|
20
|
+
import { parseArgs } from 'node:util';
|
|
21
|
+
import { createReport, rawReportDirectory } from './report.mjs';
|
|
22
|
+
import {
|
|
23
|
+
readTracker,
|
|
24
|
+
saveTracker,
|
|
25
|
+
renderFindings,
|
|
26
|
+
trackerFile,
|
|
27
|
+
statusNames as names
|
|
28
|
+
} from './tracker.mjs';
|
|
29
|
+
export { readTracker } from './tracker.mjs';
|
|
30
|
+
import {
|
|
31
|
+
readProjectConfig,
|
|
32
|
+
projectPath,
|
|
33
|
+
projectGuard,
|
|
34
|
+
projectPolicy,
|
|
35
|
+
preflightProject,
|
|
36
|
+
publishSummary
|
|
37
|
+
} from './project.mjs';
|
|
38
|
+
import { findPython, prepareScanTemp } from './runtime.mjs';
|
|
39
|
+
|
|
40
|
+
const capture = (command, args, options = {}) =>
|
|
41
|
+
execFileSync(command, args, {
|
|
42
|
+
encoding: 'utf8',
|
|
43
|
+
stdio: 'pipe',
|
|
44
|
+
maxBuffer: 32 * 1024 * 1024,
|
|
45
|
+
...options
|
|
46
|
+
}).trim();
|
|
47
|
+
const digest = (value) => createHash('sha256').update(value).digest('hex');
|
|
48
|
+
|
|
49
|
+
// Snapshot binding includes all tracked/untracked nonignored source, HEAD and the real index.
|
|
50
|
+
// No git add/reset/stash; ignored generated reports do not invalidate their own run.
|
|
51
|
+
export function snapshot(root, env) {
|
|
52
|
+
const git = (...args) => execFileSync('git', ['-C', root, ...args], { env });
|
|
53
|
+
const hash = createHash('sha256');
|
|
54
|
+
try {
|
|
55
|
+
hash.update(git('rev-parse', '--verify', 'HEAD'));
|
|
56
|
+
} catch {
|
|
57
|
+
hash.update('unborn-head');
|
|
58
|
+
}
|
|
59
|
+
hash.update(git('write-tree'));
|
|
60
|
+
const paths = [
|
|
61
|
+
...new Set(
|
|
62
|
+
git('ls-files', '-c', '-o', '--exclude-standard', '-z')
|
|
63
|
+
.toString('utf8')
|
|
64
|
+
.split('\0')
|
|
65
|
+
.filter(Boolean)
|
|
66
|
+
)
|
|
67
|
+
].sort();
|
|
68
|
+
for (const path of paths) {
|
|
69
|
+
hash.update(JSON.stringify(path));
|
|
70
|
+
try {
|
|
71
|
+
const stat = lstatSync(join(root, path));
|
|
72
|
+
hash.update(`${stat.mode}:`);
|
|
73
|
+
hash.update(
|
|
74
|
+
stat.isSymbolicLink()
|
|
75
|
+
? readlinkSync(join(root, path))
|
|
76
|
+
: stat.isFile()
|
|
77
|
+
? readFileSync(join(root, path))
|
|
78
|
+
: 'directory'
|
|
79
|
+
);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error.code !== 'ENOENT') throw error;
|
|
82
|
+
hash.update('missing');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return `sha256:${hash.digest('hex')}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function validateResponse(text, selected, root, code) {
|
|
89
|
+
const response = JSON.parse(text);
|
|
90
|
+
if (
|
|
91
|
+
realpathSync(response.repository) !== realpathSync(root) ||
|
|
92
|
+
!Array.isArray(response.results) ||
|
|
93
|
+
response.results.length !== selected.length
|
|
94
|
+
)
|
|
95
|
+
throw new Error('Verification response target or result count mismatch.');
|
|
96
|
+
for (const [index, result] of response.results.entries()) {
|
|
97
|
+
if (
|
|
98
|
+
result.id !== `finding-${index + 1}` ||
|
|
99
|
+
!['fixed', 'still_vulnerable', 'inconclusive'].includes(result.status) ||
|
|
100
|
+
typeof result.evidence !== 'string' ||
|
|
101
|
+
!result.evidence.trim()
|
|
102
|
+
)
|
|
103
|
+
throw new Error('Missing, unknown or evidence-free verification result.');
|
|
104
|
+
}
|
|
105
|
+
const expected = response.results.some((r) => r.status === 'inconclusive')
|
|
106
|
+
? 2
|
|
107
|
+
: response.results.some((r) => r.status === 'still_vulnerable')
|
|
108
|
+
? 1
|
|
109
|
+
: 0;
|
|
110
|
+
if (code !== expected) throw new Error('Verification exit code conflicts with its results.');
|
|
111
|
+
return response.results.map((result, index) => ({
|
|
112
|
+
...result,
|
|
113
|
+
id: selected[index].id
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function verificationEvidence(item, root, env) {
|
|
118
|
+
if (item.baseline.confirmation !== 'automatically_imported') return item.evidence;
|
|
119
|
+
const ref = item.evidenceRef;
|
|
120
|
+
if (
|
|
121
|
+
!ref ||
|
|
122
|
+
!/^\d{4}-\d{2}-\d{2}T[\d-]+Z-(full|staged|branch)-[a-f0-9-]{36}$/.test(ref.runId) ||
|
|
123
|
+
!Number.isSafeInteger(ref.index) ||
|
|
124
|
+
ref.index < 0 ||
|
|
125
|
+
!/^[a-f0-9]{64}$/.test(ref.sha256)
|
|
126
|
+
)
|
|
127
|
+
throw new Error(`Invalid private evidence reference for ${item.id}; verification not started.`);
|
|
128
|
+
let finding;
|
|
129
|
+
try {
|
|
130
|
+
const path = join(rawReportDirectory(root, env), ref.runId, 'findings.json');
|
|
131
|
+
if (!lstatSync(path).isFile() || lstatSync(path).isSymbolicLink())
|
|
132
|
+
throw new Error('Not a regular file.');
|
|
133
|
+
const document = JSON.parse(readFileSync(path, 'utf8'));
|
|
134
|
+
if (
|
|
135
|
+
document.documentType !== 'codex-security.findings' ||
|
|
136
|
+
document.schemaVersion !== '1.0' ||
|
|
137
|
+
document.scanId !== ref.scanId
|
|
138
|
+
)
|
|
139
|
+
throw new Error('Wrong scan.');
|
|
140
|
+
finding = document.findings[ref.index];
|
|
141
|
+
if (!finding || digest(JSON.stringify(finding)) !== ref.sha256)
|
|
142
|
+
throw new Error('Evidence checksum mismatch.');
|
|
143
|
+
} catch {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Private evidence missing or invalid for ${item.id}; retain pending status. Restore the original private run directory, re-scan to refresh the reference, or have a reviewer curate sanitized evidence first.`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
return finding;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function main(args = process.argv.slice(2)) {
|
|
152
|
+
if (args.includes('--help')) {
|
|
153
|
+
console.log(
|
|
154
|
+
'sec update SEC-001 [SEC-002 ...]:标记待验证,不调用模型。\nsec verify:复核全部待验证项。\nsec verify --list:列出全部状态。\nsec verify --pending SEC-001 [SEC-002 ...]:兼容排队入口。\nsec help:查看全部命令。'
|
|
155
|
+
);
|
|
156
|
+
return 0;
|
|
157
|
+
}
|
|
158
|
+
const env = { ...process.env };
|
|
159
|
+
const root = capture('git', ['rev-parse', '--show-toplevel'], { env });
|
|
160
|
+
const git = (...values) => capture('git', ['-C', root, ...values], { env });
|
|
161
|
+
const project = readProjectConfig(root);
|
|
162
|
+
const policy = projectPolicy(project);
|
|
163
|
+
const path = trackerFile(root, env);
|
|
164
|
+
const { tracker, text } = readTracker(path, true, project?.idPrefix ?? 'SEC');
|
|
165
|
+
if (args[0] === '--list' && args.length === 1) {
|
|
166
|
+
console.log(renderFindings(tracker, { all: true }));
|
|
167
|
+
return 0;
|
|
168
|
+
}
|
|
169
|
+
if (args[0] === '--pending' && args.length > 1) {
|
|
170
|
+
const ids = args.slice(1);
|
|
171
|
+
if (
|
|
172
|
+
new Set(ids).size !== ids.length ||
|
|
173
|
+
ids.some((id) => !tracker.findings.some((item) => item.id === id))
|
|
174
|
+
)
|
|
175
|
+
throw new Error('Unknown or duplicate finding ID; tracker not changed.');
|
|
176
|
+
for (const item of tracker.findings.filter((entry) => ids.includes(entry.id))) {
|
|
177
|
+
item.status = 'pending_verification';
|
|
178
|
+
item.queuedAt = new Date().toISOString();
|
|
179
|
+
item.queuedHead = git('rev-parse', 'HEAD');
|
|
180
|
+
}
|
|
181
|
+
if (project?.trackerFile) projectPath(root, project.trackerFile, { kind: 'file' });
|
|
182
|
+
saveTracker(path, tracker, text);
|
|
183
|
+
console.log(
|
|
184
|
+
`已标记待验证:${ids.join(', ')}。${project?.trackerFile ? '仅更新项目共享台账工作副本,未暂存' : '仅修改本机私有台账'};运行 sec verify 开始复核全部待验证项。`
|
|
185
|
+
);
|
|
186
|
+
const queued = tracker.findings
|
|
187
|
+
.filter((item) => item.status === 'pending_verification')
|
|
188
|
+
.map((item) => item.id);
|
|
189
|
+
console.log(
|
|
190
|
+
`当前待验证队列:${queued.join(', ')}。本操作只追加指定项,不清空其他待验证项;复核期间请勿修改源码、台账或 Git 状态。`
|
|
191
|
+
);
|
|
192
|
+
return 0;
|
|
193
|
+
}
|
|
194
|
+
const { values } = parseArgs({
|
|
195
|
+
args,
|
|
196
|
+
options: { model: { type: 'string' }, effort: { type: 'string' } },
|
|
197
|
+
allowPositionals: false
|
|
198
|
+
});
|
|
199
|
+
const config = readConfig(env);
|
|
200
|
+
const settings = repositoryModelSettings(root, config, values);
|
|
201
|
+
const selected = tracker.findings.filter((item) => item.status === 'pending_verification');
|
|
202
|
+
if (!selected.length) {
|
|
203
|
+
console.log('没有待验证项,不调用模型。修复后用 sec update SEC-xxx 标记;sec help 查看用法。');
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
preflightProject(root, project, git);
|
|
207
|
+
const guard = projectGuard(root, project);
|
|
208
|
+
if (git('ls-files', '-u')) throw new Error('Resolve Git conflicts before verification.');
|
|
209
|
+
const originalEnv = { ...env };
|
|
210
|
+
assertOutsideRepository(root, userDirectories(env).config);
|
|
211
|
+
prepareScanTemp(root, env);
|
|
212
|
+
env.PYTHON = findPython(env, (command, values) => capture(command, values, { env }));
|
|
213
|
+
Object.assign(env, engineEnvironment(env));
|
|
214
|
+
delete env.CODEX_SECURITY_PROJECT_CONFIG;
|
|
215
|
+
const overrides = providerArguments(config, settings);
|
|
216
|
+
const cliVersion = ensureEngine(env);
|
|
217
|
+
for (const name of git('rev-parse', '--local-env-vars').split('\n')) delete env[name];
|
|
218
|
+
const head = git('rev-parse', 'HEAD');
|
|
219
|
+
const before = snapshot(root, originalEnv);
|
|
220
|
+
const run = createReport(root, 'verify', head, null, env);
|
|
221
|
+
run.project = project;
|
|
222
|
+
run.projectInputs = guard.inputs;
|
|
223
|
+
let code = 2,
|
|
224
|
+
results = null;
|
|
225
|
+
const inputHashes = [];
|
|
226
|
+
try {
|
|
227
|
+
// Official CLI accepts regular input files. Keep private evidence out of argv.
|
|
228
|
+
const inputs = selected.map((item) => {
|
|
229
|
+
const { id, title, severity, baseline } = item;
|
|
230
|
+
const evidence = verificationEvidence(item, root, env);
|
|
231
|
+
const input = JSON.stringify({
|
|
232
|
+
id,
|
|
233
|
+
title,
|
|
234
|
+
severity,
|
|
235
|
+
baseline,
|
|
236
|
+
evidence,
|
|
237
|
+
...(project?.policyFile
|
|
238
|
+
? {
|
|
239
|
+
projectPolicy: readFileSync(
|
|
240
|
+
projectPath(root, project.policyFile, { kind: 'file', required: true }),
|
|
241
|
+
'utf8'
|
|
242
|
+
)
|
|
243
|
+
}
|
|
244
|
+
: {})
|
|
245
|
+
});
|
|
246
|
+
inputHashes.push({ id, sha256: digest(input) });
|
|
247
|
+
return input;
|
|
248
|
+
});
|
|
249
|
+
writeFileSync(
|
|
250
|
+
join(run.raw, 'verification-input.json'),
|
|
251
|
+
JSON.stringify(inputs.map(JSON.parse), null, 2),
|
|
252
|
+
{
|
|
253
|
+
mode: 0o600
|
|
254
|
+
}
|
|
255
|
+
);
|
|
256
|
+
const inputFiles = inputs.map((input, index) => {
|
|
257
|
+
const path = join(run.raw, `${selected[index].id}-input.json`);
|
|
258
|
+
writeFileSync(path, input, { flag: 'wx', mode: 0o600 });
|
|
259
|
+
return path;
|
|
260
|
+
});
|
|
261
|
+
guard.check();
|
|
262
|
+
console.log(
|
|
263
|
+
`Codex Security: 定向源码复核 ${selected.map((item) => item.id).join(', ')};不执行全仓扫描,不运行应用/测试或线上请求。`
|
|
264
|
+
);
|
|
265
|
+
console.log(`复核模型:${settings.model};推理强度:${settings.effort}。`);
|
|
266
|
+
const response = await invokeEngine(
|
|
267
|
+
['verify-fix', ...inputFiles, '--verification-mode', 'static', '--json', ...overrides],
|
|
268
|
+
{ cwd: root, env, raw: run.raw, capture: true }
|
|
269
|
+
);
|
|
270
|
+
if (![0, 1, 2].includes(response.code))
|
|
271
|
+
throw new Error(`Verification interrupted or failed (exit ${response.code}).`);
|
|
272
|
+
const parsed = validateResponse(response.output, selected, root, response.code);
|
|
273
|
+
if (snapshot(root, originalEnv) !== before)
|
|
274
|
+
throw new Error(
|
|
275
|
+
'Source, HEAD, index or tracker changed during verification; no statuses updated.'
|
|
276
|
+
);
|
|
277
|
+
preflightProject(root, project, git);
|
|
278
|
+
guard.check();
|
|
279
|
+
const mappings = {
|
|
280
|
+
fixed: 'verified_static',
|
|
281
|
+
still_vulnerable: 'open',
|
|
282
|
+
inconclusive: 'pending_verification'
|
|
283
|
+
};
|
|
284
|
+
for (const result of parsed) {
|
|
285
|
+
const item = tracker.findings.find((entry) => entry.id === result.id);
|
|
286
|
+
item.status = mappings[result.status];
|
|
287
|
+
item.lastVerification = {
|
|
288
|
+
runId: run.runId,
|
|
289
|
+
mode: 'static',
|
|
290
|
+
head,
|
|
291
|
+
sourceDigest: before,
|
|
292
|
+
verdict: result.status,
|
|
293
|
+
completedAt: new Date().toISOString(),
|
|
294
|
+
evidenceArtifact: 'verification.json',
|
|
295
|
+
inputSha256: inputHashes.find((entry) => entry.id === result.id).sha256
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
// Evidence text remains in the private report, not in a tracked JSON file.
|
|
299
|
+
if (project?.trackerFile) projectPath(root, project.trackerFile, { kind: 'file' });
|
|
300
|
+
saveTracker(path, tracker, text);
|
|
301
|
+
results = parsed;
|
|
302
|
+
code = parsed.some((r) => r.status === 'inconclusive')
|
|
303
|
+
? 2
|
|
304
|
+
: parsed.some(
|
|
305
|
+
(r) =>
|
|
306
|
+
r.status === 'still_vulnerable' &&
|
|
307
|
+
policy.blockSeverities.includes(selected.find((item) => item.id === r.id).severity)
|
|
308
|
+
)
|
|
309
|
+
? 1
|
|
310
|
+
: 0;
|
|
311
|
+
for (const result of parsed) console.log(`${result.id}: ${names[mappings[result.status]]}`);
|
|
312
|
+
if (
|
|
313
|
+
parsed.some(
|
|
314
|
+
(r) =>
|
|
315
|
+
r.status === 'still_vulnerable' &&
|
|
316
|
+
policy.warnSeverities.includes(selected.find((item) => item.id === r.id).severity)
|
|
317
|
+
)
|
|
318
|
+
) {
|
|
319
|
+
console.warn(
|
|
320
|
+
`WARNING: ${policy.failOnSeverity === 'medium' ? '低危问题' : `低于 ${policy.failOnSeverity} 的问题`}仍存在,已记录且不阻断命令。`
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
return code;
|
|
324
|
+
} finally {
|
|
325
|
+
const metadata = {
|
|
326
|
+
runId: run.runId,
|
|
327
|
+
mode: 'static',
|
|
328
|
+
head,
|
|
329
|
+
sourceDigest: before,
|
|
330
|
+
inputHashes,
|
|
331
|
+
cliVersion,
|
|
332
|
+
modelSettings: settings,
|
|
333
|
+
project,
|
|
334
|
+
projectInputs: guard.inputs,
|
|
335
|
+
failOnSeverity: policy.failOnSeverity,
|
|
336
|
+
providerArgumentsDigest: digest(JSON.stringify(overrides)),
|
|
337
|
+
status: code,
|
|
338
|
+
results: results?.map(({ id, status }) => ({ id, status })) ?? null,
|
|
339
|
+
completedAt: new Date().toISOString()
|
|
340
|
+
};
|
|
341
|
+
writeFileSync(join(run.raw, 'wrapper-result.json'), JSON.stringify(metadata, null, 2), {
|
|
342
|
+
mode: 0o600
|
|
343
|
+
});
|
|
344
|
+
const lines = [
|
|
345
|
+
'# 定向修复源码复核',
|
|
346
|
+
'',
|
|
347
|
+
`- 执行标识:${run.runId}`,
|
|
348
|
+
`- HEAD:${head}`,
|
|
349
|
+
`- 请求模型:${JSON.stringify(settings.model)};推理强度:${JSON.stringify(settings.effort)}`,
|
|
350
|
+
`- 源码/暂存快照:${before}`,
|
|
351
|
+
`- 退出码:${code}`,
|
|
352
|
+
'',
|
|
353
|
+
'| 编号 | 原扫描 ID | 原规则 | 本次结论 |',
|
|
354
|
+
'| --- | --- | --- | --- |',
|
|
355
|
+
...selected.map(
|
|
356
|
+
(item) =>
|
|
357
|
+
`| ${item.id} | ${item.baseline.scanId} | ${item.baseline.ruleId} | ${results ? names[item.status] : '验证失败,未更新状态'} |`
|
|
358
|
+
),
|
|
359
|
+
'',
|
|
360
|
+
'仅复核列出的历史漏洞及必要关联代码,不执行全仓发现。已通过源码复核不等于完成运行时/线上验证,也不代表整个项目安全。',
|
|
361
|
+
'完整证据保存在仓库外同一执行标识目录中的 verification.json;本摘要不复制自由文本、源码或载荷。',
|
|
362
|
+
`状态变更仅写${project?.trackerFile ?? '本机私有台账'},不自动暂存、提交或修改旧报告。结论仅对上述快照有效;后续改动可能重新引入问题。`,
|
|
363
|
+
...(project
|
|
364
|
+
? [
|
|
365
|
+
`项目规范:${project.policyFile ?? '未指定'};人工报告模板:${project.reportTemplate ?? '未指定'}。`
|
|
366
|
+
]
|
|
367
|
+
: []),
|
|
368
|
+
''
|
|
369
|
+
];
|
|
370
|
+
mkdirSync(dirname(run.summary), { recursive: true });
|
|
371
|
+
writeFileSync(run.summary, lines.join('\n'), { flag: 'wx', mode: 0o600 });
|
|
372
|
+
publishSummary(run);
|
|
373
|
+
console.log(`Codex Security 原始验证证据:${run.raw}\n中文摘要:${run.summary}`);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
378
|
+
main()
|
|
379
|
+
.then((code) => {
|
|
380
|
+
process.exitCode = code;
|
|
381
|
+
})
|
|
382
|
+
.catch((error) => {
|
|
383
|
+
console.error(
|
|
384
|
+
`Codex Security verification failed: ${error.message.startsWith('Command failed') ? 'External command failed; inspect private diagnostics.' : error.message}`
|
|
385
|
+
);
|
|
386
|
+
process.exitCode = 2;
|
|
387
|
+
});
|
|
388
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@itenl/sec",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Security scanning CLI with project policies, shared findings and static fix verification",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sec": "bin/sec.mjs"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"templates/",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": "^22.13.0 || ^24.0.0 || ^26.0.0"
|
|
18
|
+
},
|
|
19
|
+
"os": [
|
|
20
|
+
"darwin",
|
|
21
|
+
"linux"
|
|
22
|
+
],
|
|
23
|
+
"packageManager": "pnpm@10.28.2",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"test": "node --test test/*.test.mjs",
|
|
26
|
+
"check": "node scripts/check.mjs",
|
|
27
|
+
"check:package": "node scripts/check-package.mjs",
|
|
28
|
+
"prepack": "pnpm check",
|
|
29
|
+
"release:check": "node scripts/release-check.mjs",
|
|
30
|
+
"prepublishOnly": "pnpm release:check"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/itenl/sec.git"
|
|
35
|
+
},
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"homepage": "https://github.com/itenl/sec#readme",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/itenl/sec/issues"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"security",
|
|
43
|
+
"cli",
|
|
44
|
+
"code-review",
|
|
45
|
+
"codex-security"
|
|
46
|
+
],
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public",
|
|
49
|
+
"registry": "https://registry.npmjs.org/"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# 安全复核报告
|
|
2
|
+
|
|
3
|
+
## 扫描记录
|
|
4
|
+
|
|
5
|
+
- 日期与时区:
|
|
6
|
+
- 扫描 ID、执行标识:
|
|
7
|
+
- 分支、HEAD、比较基线与快照:
|
|
8
|
+
- 模式、命令、工具及引擎版本:
|
|
9
|
+
- 规范版本、模型与推理强度:
|
|
10
|
+
- 最终状态、退出码、门禁结论与警告:
|
|
11
|
+
- 原始产物名称及扫描 ID(不填个人绝对路径):
|
|
12
|
+
|
|
13
|
+
## 范围与限制
|
|
14
|
+
|
|
15
|
+
记录请求范围、实际覆盖、排除项、验证方法及未验证的部署条件。没有依据的计数不填写;未执行的步骤说明原因。
|
|
16
|
+
|
|
17
|
+
## 本轮发现
|
|
18
|
+
|
|
19
|
+
逐项记录稳定编号、状态、严重级别、置信度、仓库相对位置、影响、利用前提、证据、限制和建议。不包含凭据、完整敏感响应、源码摘录或可直接滥用的完整载荷。
|
|
20
|
+
|
|
21
|
+
## 历史关联与修复复核
|
|
22
|
+
|
|
23
|
+
区分新增、重复、历史未关闭、本轮未确认及指定快照已源码复核的项目。严重级别变化说明依据;未再次报告不自动关闭。记录仍需补充的证据与后续工作。
|
|
24
|
+
|
|
25
|
+
## 交付状态
|
|
26
|
+
|
|
27
|
+
记录实际改动、验证、待确认事项,以及是否暂存、提交或推送。人工复核后保存报告;自动摘要不能代替本报告。
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: project-security
|
|
3
|
+
description: Review this repository's security boundaries and findings when a security scan or vulnerability review is requested.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 项目安全审查规范
|
|
7
|
+
|
|
8
|
+
这是一份通用起点。维护者应依据实际代码补充安全边界、部署约束和业务规则;未填写内容不能视为已确认的安全保障或风险豁免。
|
|
9
|
+
|
|
10
|
+
## 项目上下文
|
|
11
|
+
|
|
12
|
+
记录需要保护的数据与能力、调用方及权限角色、外部依赖与部署边界。列出关键代码入口,使用仓库相对路径。无法从源码确认的保障标为待核实。
|
|
13
|
+
|
|
14
|
+
## 审查标准
|
|
15
|
+
|
|
16
|
+
- 从不可信输入追踪到敏感操作,说明攻击者能力、利用前提、权限边界及实际影响。
|
|
17
|
+
- 结合项目实际能力检查认证授权、输入处理、敏感信息、外部资源访问和输出边界;不因名称或搜索命中直接判定漏洞。
|
|
18
|
+
- 区分源码推断、隔离的本地验证与部署验证;依赖或后端保障需要证据。
|
|
19
|
+
- 严重级别与置信度分别记录。风险接受项须记录理由、适用范围和维护者确认,不自行假定。
|
|
20
|
+
|
|
21
|
+
## 扫描与复核
|
|
22
|
+
|
|
23
|
+
运行 sec branch --dry-run 确认范围,再按任务选择 sec branch、sec diff 或 sec scan。路径、门禁和模型以根目录 sec.config.json 及终端显示的有效设置为准。
|
|
24
|
+
|
|
25
|
+
沿用共享台账编号。修复后用 sec update 指定编号排队,再用 sec verify 定向源码复核;新扫描未报告不等于修复。verified_static 只说明指定快照通过源码复核,不代表线上验证通过。
|
|
26
|
+
|
|
27
|
+
扫描期间保持源码、Git 状态、规范、模板和台账不变。扫描本身不自动修复、暂存、提交、推送或进行线上操作。
|
|
28
|
+
|
|
29
|
+
## 报告
|
|
30
|
+
|
|
31
|
+
使用 sec.config.json 的 reportTemplate 指定的报告模板。报告记录快照、范围、证据和限制,区分本轮发现与历史未关闭项。项目内只保存允许共享的脱敏内容;原始证据及认证保存在仓库外。
|