dsh-code-security 0.3.6 → 0.3.8
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/CHANGELOG.md +29 -0
- package/README.en.md +31 -27
- package/README.md +23 -66
- package/docs/CHANGELOG.en.md +15 -0
- package/docs/ITERATION-2026-10-12.md +53 -0
- package/docs/USAGE.en.md +37 -0
- package/docs/USAGE.md +85 -0
- package/docs/VALIDATION.md +15 -0
- package/lib/config.d.ts +1 -0
- package/lib/config.js +2 -1
- package/lib/diff.d.ts +3 -0
- package/lib/diff.js +112 -34
- package/lib/extra.d.ts +1 -1
- package/lib/extra.js +4 -4
- package/lib/index.d.ts +3 -2
- package/lib/index.js +3 -2
- package/lib/policy.d.ts +1 -1
- package/lib/policy.js +17 -5
- package/lib/runner.d.ts +2 -0
- package/lib/runner.js +4 -2
- package/lib/scanner.d.ts +4 -1
- package/lib/scanner.js +79 -35
- package/lib/state.d.ts +2 -2
- package/lib/state.js +76 -20
- package/lib/tools.d.ts +2 -2
- package/lib/tools.js +37 -13
- package/package.json +10 -4
package/lib/state.js
CHANGED
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import fs from 'node:fs/promises';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
-
import { createHash } from 'node:crypto';
|
|
9
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
10
|
+
import lockfile from 'proper-lockfile';
|
|
10
11
|
const STATE_FILE = 'state.json';
|
|
11
12
|
const HISTORY_LIMIT = 50;
|
|
12
13
|
export function findingFingerprint(finding) {
|
|
13
|
-
const
|
|
14
|
+
const file = finding.file.split(path.sep).join('/').replace(/^(?:\.\/)+/, '');
|
|
15
|
+
const raw = finding.ruleId + '|' + file + '|' + finding.snippet.trim();
|
|
14
16
|
return createHash('sha256').update(raw).digest('hex');
|
|
15
17
|
}
|
|
16
18
|
export function statePath(stateDir) {
|
|
@@ -18,43 +20,96 @@ export function statePath(stateDir) {
|
|
|
18
20
|
}
|
|
19
21
|
export async function loadState(stateDir) {
|
|
20
22
|
try {
|
|
23
|
+
const info = await fs.lstat(statePath(stateDir));
|
|
24
|
+
if (!info.isFile() || info.isSymbolicLink() || info.size > 16 * 1024 * 1024)
|
|
25
|
+
throw new Error('记录不是普通文件或超过 16 MiB');
|
|
21
26
|
const text = await fs.readFile(statePath(stateDir), 'utf8');
|
|
22
27
|
const parsed = JSON.parse(text);
|
|
23
28
|
const obj = (parsed ?? {});
|
|
29
|
+
if (!Array.isArray(obj.history) || obj.history.some(entry => !isEntry(entry)) || obj.last !== null && !isEntry(obj.last) || obj.baseline !== undefined && obj.baseline !== null && !isBaseline(obj.baseline))
|
|
30
|
+
throw new Error('记录结构无效');
|
|
24
31
|
const last = isEntry(obj.last) ? obj.last : null;
|
|
25
32
|
const history = Array.isArray(obj.history) ? obj.history.filter(isEntry) : [];
|
|
26
33
|
const baseline = isBaseline(obj.baseline) ? obj.baseline : null;
|
|
27
34
|
return { last, history, baseline };
|
|
28
35
|
}
|
|
29
|
-
catch {
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error.code !== 'ENOENT')
|
|
38
|
+
throw new Error('安全扫描记录无法读取,原文件已保留:' + (error instanceof Error ? error.message : String(error)));
|
|
30
39
|
return { last: null, history: [], baseline: null };
|
|
31
40
|
}
|
|
32
41
|
}
|
|
33
42
|
function isEntry(value) {
|
|
34
|
-
|
|
43
|
+
if (typeof value !== 'object' || value === null)
|
|
44
|
+
return false;
|
|
45
|
+
const row = value;
|
|
46
|
+
return typeof row.mode === 'string' && typeof row.target === 'string' && typeof row.time === 'string'
|
|
47
|
+
&& Number.isInteger(row.filesScanned) && row.filesScanned >= 0 && validFindings(row.findings)
|
|
48
|
+
&& validFingerprints(row.fingerprints) && validCounts(row.counts);
|
|
35
49
|
}
|
|
36
50
|
function isBaseline(value) {
|
|
37
|
-
|
|
51
|
+
if (typeof value !== 'object' || value === null)
|
|
52
|
+
return false;
|
|
53
|
+
const row = value;
|
|
54
|
+
return typeof row.time === 'string' && typeof row.target === 'string' && typeof row.reason === 'string'
|
|
55
|
+
&& validFindings(row.findings) && validFingerprints(row.fingerprints) && validCounts(row.counts);
|
|
38
56
|
}
|
|
39
|
-
|
|
57
|
+
function validFingerprints(value) { return Array.isArray(value) && value.every(item => typeof item === 'string'); }
|
|
58
|
+
function validCounts(value) {
|
|
59
|
+
if (typeof value !== 'object' || value === null)
|
|
60
|
+
return false;
|
|
61
|
+
const counts = value;
|
|
62
|
+
return ['critical', 'high', 'medium', 'low'].every(key => typeof counts[key] === 'number' && Number.isInteger(counts[key]) && counts[key] >= 0);
|
|
63
|
+
}
|
|
64
|
+
function validFindings(value) {
|
|
65
|
+
return Array.isArray(value) && value.every(item => typeof item === 'object' && item !== null
|
|
66
|
+
&& typeof item.ruleId === 'string' && typeof item.file === 'string' && typeof item.snippet === 'string'
|
|
67
|
+
&& Number.isInteger(item.line) && item.line >= 1 && ['critical', 'high', 'medium', 'low'].includes(item.severity));
|
|
68
|
+
}
|
|
69
|
+
export async function saveState(stateDir, entry, signal) {
|
|
70
|
+
signal?.throwIfAborted();
|
|
40
71
|
await fs.mkdir(stateDir, { recursive: true });
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
72
|
+
const unlock = await lockfile.lock(stateDir, { retries: { retries: 30, minTimeout: 25, maxTimeout: 100 } });
|
|
73
|
+
try {
|
|
74
|
+
signal?.throwIfAborted();
|
|
75
|
+
const state = await loadState(stateDir);
|
|
76
|
+
state.last = entry;
|
|
77
|
+
state.history.unshift(entry);
|
|
78
|
+
state.history = state.history.slice(0, HISTORY_LIMIT);
|
|
79
|
+
return await writeState(stateDir, state, signal);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
await unlock();
|
|
83
|
+
}
|
|
46
84
|
}
|
|
47
|
-
export async function saveBaseline(stateDir, baseline) {
|
|
85
|
+
export async function saveBaseline(stateDir, baseline, signal) {
|
|
86
|
+
signal?.throwIfAborted();
|
|
48
87
|
await fs.mkdir(stateDir, { recursive: true });
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
88
|
+
const unlock = await lockfile.lock(stateDir, { retries: { retries: 30, minTimeout: 25, maxTimeout: 100 } });
|
|
89
|
+
try {
|
|
90
|
+
signal?.throwIfAborted();
|
|
91
|
+
const state = await loadState(stateDir);
|
|
92
|
+
state.baseline = baseline;
|
|
93
|
+
return await writeState(stateDir, state, signal);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
await unlock();
|
|
97
|
+
}
|
|
52
98
|
}
|
|
53
|
-
async function writeState(stateDir, state) {
|
|
99
|
+
async function writeState(stateDir, state, signal) {
|
|
100
|
+
signal?.throwIfAborted();
|
|
54
101
|
const file = statePath(stateDir);
|
|
55
|
-
const tmp = file + '.tmp-' +
|
|
56
|
-
|
|
57
|
-
|
|
102
|
+
const tmp = file + '.tmp-' + randomUUID(), bytes = JSON.stringify(state, null, 2);
|
|
103
|
+
if (Buffer.byteLength(bytes) > 16 * 1024 * 1024)
|
|
104
|
+
throw new Error('安全扫描记录超过 16 MiB,本次没有保存。');
|
|
105
|
+
try {
|
|
106
|
+
await fs.writeFile(tmp, bytes, { flag: 'wx', mode: 0o600, signal });
|
|
107
|
+
signal?.throwIfAborted();
|
|
108
|
+
await fs.rename(tmp, file);
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
await fs.unlink(tmp).catch(() => { });
|
|
112
|
+
}
|
|
58
113
|
return file;
|
|
59
114
|
}
|
|
60
115
|
export function countFindings(findings) {
|
|
@@ -76,7 +131,8 @@ export function compareFingerprints(baseline, current) {
|
|
|
76
131
|
export function splitByBaseline(findings, baseline) {
|
|
77
132
|
if (baseline === null)
|
|
78
133
|
return { fresh: findings, accepted: [] };
|
|
79
|
-
|
|
134
|
+
// Recompute from saved evidence so older "./file" fingerprints remain usable.
|
|
135
|
+
const acceptedSet = new Set(baseline.findings.map(findingFingerprint));
|
|
80
136
|
const fresh = [];
|
|
81
137
|
const accepted = [];
|
|
82
138
|
for (const finding of findings) {
|
package/lib/tools.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 十个面向模型的安全审查工具:
|
|
3
3
|
* secure_scan / secure_diff / secure_fix_verify / secure_report / secure_export /
|
|
4
|
-
* secure_baseline / secure_deps / secure_policy_show / secure_policy_set。
|
|
4
|
+
* secure_baseline / secure_deps / secure_policy_show / secure_policy_set / secure_health。
|
|
5
5
|
*
|
|
6
6
|
* @module dsh-code-security/tools
|
|
7
7
|
*/
|
package/lib/tools.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 十个面向模型的安全审查工具:
|
|
3
3
|
* secure_scan / secure_diff / secure_fix_verify / secure_report / secure_export /
|
|
4
|
-
* secure_baseline / secure_deps / secure_policy_show / secure_policy_set。
|
|
4
|
+
* secure_baseline / secure_deps / secure_policy_show / secure_policy_set / secure_health。
|
|
5
5
|
*
|
|
6
6
|
* @module dsh-code-security/tools
|
|
7
7
|
*/
|
|
8
8
|
import { writeFile } from 'node:fs/promises';
|
|
9
|
+
import { readFileSync } from 'node:fs';
|
|
9
10
|
import path from 'node:path';
|
|
10
11
|
import { pathToFileURL } from 'node:url';
|
|
11
12
|
import { optionalString, requiredString } from './config.js';
|
|
@@ -16,6 +17,7 @@ import { ruleCategory } from './rules.js';
|
|
|
16
17
|
import { scanPath } from './scanner.js';
|
|
17
18
|
import { compareFingerprints, countFindings, findingFingerprint, loadState, saveState, splitByBaseline } from './state.js';
|
|
18
19
|
const SEVERITY_ORDER = { critical: 4, high: 3, medium: 2, low: 1 };
|
|
20
|
+
const PACKAGE_VERSION = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version;
|
|
19
21
|
function compileParameters(spec) {
|
|
20
22
|
const properties = {};
|
|
21
23
|
const required = [];
|
|
@@ -46,6 +48,12 @@ function scanArguments(raw, allowed = ['target']) {
|
|
|
46
48
|
if (Object.hasOwn(args, 'target') && (typeof args.target !== 'string' || args.target.trim() === '')) {
|
|
47
49
|
throw new Error('扫描参数 target 必须是非空的文件或目录字符串。扫描整个工作区请省略 target 或使用 "."。');
|
|
48
50
|
}
|
|
51
|
+
if (Object.hasOwn(args, 'base') && (typeof args.base !== 'string' || args.base.trim() === '' || args.base.trim().startsWith('-') || args.base.includes('\0'))) {
|
|
52
|
+
throw new Error('扫描参数 base 必须是非空的 Git ref,不能是 Git 命令选项。');
|
|
53
|
+
}
|
|
54
|
+
if (Object.hasOwn(args, 'staged') && typeof args.staged !== 'boolean') {
|
|
55
|
+
throw new Error('扫描参数 staged 必须是布尔值 true 或 false。');
|
|
56
|
+
}
|
|
49
57
|
return args;
|
|
50
58
|
}
|
|
51
59
|
const findingSchema = {
|
|
@@ -66,6 +74,7 @@ const scanSchema = {
|
|
|
66
74
|
type: 'object',
|
|
67
75
|
properties: {
|
|
68
76
|
root: { type: 'string' }, filesScanned: { type: 'integer' }, filesSkipped: { type: 'integer' },
|
|
77
|
+
filesChanged: { type: 'integer' }, addedLines: { type: 'integer' },
|
|
69
78
|
findings: { type: 'array', items: findingSchema }, counts: countsSchema, passed: { type: 'boolean' },
|
|
70
79
|
failOn: { type: 'string' }, durationMs: { type: 'integer' },
|
|
71
80
|
newFindings: { type: 'array', items: findingSchema }, newCounts: countsSchema,
|
|
@@ -94,12 +103,26 @@ function renderFindings(_args, value) {
|
|
|
94
103
|
'安全审查:' + (rec.passed === true ? '通过' : '未通过'),
|
|
95
104
|
'文件 ' + String(rec.filesScanned ?? 0) + ' 个,发现 ' + findings.length + ' 个问题(critical ' + String(counts?.critical ?? 0) + ' / high ' + String(counts?.high ?? 0) + ' / medium ' + String(counts?.medium ?? 0) + ' / low ' + String(counts?.low ?? 0) + ');基线外新增 ' + split.fresh.length + ' 个。',
|
|
96
105
|
];
|
|
106
|
+
if (typeof rec.filesSkipped === 'number' && rec.filesSkipped > 0)
|
|
107
|
+
lines.push('已跳过 ' + rec.filesSkipped + ' 项;跳过项不计入已扫描文件数。');
|
|
97
108
|
for (const finding of findings) {
|
|
98
109
|
lines.push('- ' + finding.file + ':' + finding.line + ' [' + finding.severity + '] ' + finding.ruleId + ' ' + finding.title);
|
|
99
110
|
}
|
|
100
111
|
return [{ type: 'text', text: lines.join('\n') }];
|
|
101
112
|
}
|
|
102
113
|
export function buildSecureTools(cfg, cwd, runner) {
|
|
114
|
+
return buildWorkspaceTools(cfg, cwd, runner).map(tool => ({ ...tool, async execute(args, exec) {
|
|
115
|
+
const context = exec;
|
|
116
|
+
const signal = context?.signal;
|
|
117
|
+
signal?.throwIfAborted();
|
|
118
|
+
const workspace = context?.agent?.session?.header?.cwd || cwd;
|
|
119
|
+
const resolved = { ...cfg, stateDir: cfg.stateDirRelative ? path.resolve(workspace, cfg.stateDirRelative) : cfg.stateDir };
|
|
120
|
+
const scopedRunner = { run: (argv, options) => runner.run(argv, { ...options, cwd: workspace, signal }) };
|
|
121
|
+
const selected = buildWorkspaceTools(resolved, workspace, scopedRunner, signal).find(row => row.name === tool.name);
|
|
122
|
+
return selected.execute(args, exec);
|
|
123
|
+
} }));
|
|
124
|
+
}
|
|
125
|
+
function buildWorkspaceTools(cfg, cwd, runner, signal) {
|
|
103
126
|
const stateDir = cfg.stateDir;
|
|
104
127
|
const secureScan = {
|
|
105
128
|
name: 'secure_scan',
|
|
@@ -113,7 +136,7 @@ export function buildSecureTools(cfg, cwd, runner) {
|
|
|
113
136
|
const target = optionalString(args, 'target');
|
|
114
137
|
const policy = await loadPolicy(cwd);
|
|
115
138
|
const failOn = policy.failOn ?? cfg.failOn;
|
|
116
|
-
const result = await scanPath({ cwd, target, maxFiles: cfg.maxFiles, maxFileBytes: cfg.maxFileBytes, policy });
|
|
139
|
+
const result = await scanPath({ cwd, target, maxFiles: cfg.maxFiles, maxFileBytes: cfg.maxFileBytes, policy, signal });
|
|
117
140
|
const state = await loadState(stateDir);
|
|
118
141
|
const counts = countFindings(result.findings);
|
|
119
142
|
const split = splitByBaseline(result.findings, state.baseline);
|
|
@@ -126,14 +149,14 @@ export function buildSecureTools(cfg, cwd, runner) {
|
|
|
126
149
|
fingerprints: result.findings.map(findingFingerprint),
|
|
127
150
|
counts,
|
|
128
151
|
};
|
|
129
|
-
const stateFile = await saveState(stateDir, entry);
|
|
152
|
+
const stateFile = await saveState(stateDir, entry, signal);
|
|
130
153
|
return { ...result, counts, newFindings: split.fresh, newCounts: countFindings(split.fresh), acceptedFindings: split.accepted, baseline: state.baseline, passed: verdictOf(split.fresh, failOn), failOn, stateFile };
|
|
131
154
|
},
|
|
132
155
|
timeoutMs: 120000,
|
|
133
156
|
};
|
|
134
157
|
const secureDiff = {
|
|
135
158
|
name: 'secure_diff',
|
|
136
|
-
description: '
|
|
159
|
+
description: '结合少量上下文审查 git diff,只报告涉及新增行的问题(默认 HEAD,即未提交改动)。base 可用 Git ref;staged=true 审查已暂存改动;target 可限定文件。范围与路径相对当前会话工作区,遵守项目排除策略。结果同样写入状态。',
|
|
137
160
|
parameters: { ...compileParameters({
|
|
138
161
|
base: { type: 'string', description: 'git 基线(默认 HEAD)。' },
|
|
139
162
|
target: { type: 'string', description: '限定文件路径(可选)。' },
|
|
@@ -155,13 +178,13 @@ export function buildSecureTools(cfg, cwd, runner) {
|
|
|
155
178
|
mode: 'diff:' + base,
|
|
156
179
|
target: target ?? '.',
|
|
157
180
|
time: new Date().toISOString(),
|
|
158
|
-
filesScanned: result.
|
|
181
|
+
filesScanned: result.filesScanned,
|
|
159
182
|
findings: result.findings,
|
|
160
183
|
fingerprints: result.findings.map(findingFingerprint),
|
|
161
184
|
counts,
|
|
162
185
|
};
|
|
163
|
-
await saveState(stateDir, entry);
|
|
164
|
-
return { root: base,
|
|
186
|
+
await saveState(stateDir, entry, signal);
|
|
187
|
+
return { root: base, filesChanged: result.filesChanged, filesScanned: result.filesScanned, filesSkipped: result.filesSkipped, addedLines: result.addedLines, findings: result.findings, counts, newFindings: split.fresh, newCounts: countFindings(split.fresh), acceptedFindings: split.accepted, baseline: state.baseline, passed: verdictOf(split.fresh, failOn), failOn, durationMs: 0 };
|
|
165
188
|
},
|
|
166
189
|
timeoutMs: 60000,
|
|
167
190
|
};
|
|
@@ -192,12 +215,12 @@ export function buildSecureTools(cfg, cwd, runner) {
|
|
|
192
215
|
const target = optionalString(args, 'target');
|
|
193
216
|
const policy = await loadPolicy(cwd);
|
|
194
217
|
const state = await loadState(stateDir);
|
|
195
|
-
const result = await scanPath({ cwd, target, maxFiles: cfg.maxFiles, maxFileBytes: cfg.maxFileBytes, policy });
|
|
218
|
+
const result = await scanPath({ cwd, target, maxFiles: cfg.maxFiles, maxFileBytes: cfg.maxFileBytes, policy, signal });
|
|
196
219
|
if (state.last === null) {
|
|
197
220
|
return { baselineMissing: true, baselineTime: '', closedCount: 0, remainingCount: 0, freshCount: result.findings.length, closed: [], remaining: [], fresh: result.findings, passed: false };
|
|
198
221
|
}
|
|
199
222
|
const current = result.findings.map(findingFingerprint);
|
|
200
|
-
const cmp = compareFingerprints(state.last.
|
|
223
|
+
const cmp = compareFingerprints(state.last.findings.map(findingFingerprint), current);
|
|
201
224
|
const byFingerprint = new Map(result.findings.map((finding) => [findingFingerprint(finding), finding]));
|
|
202
225
|
const closed = cmp.closed.map((fp) => state.last.findings.find((finding) => findingFingerprint(finding) === fp)).filter((x) => x !== undefined);
|
|
203
226
|
const remaining = cmp.remaining.map((fp) => byFingerprint.get(fp)).filter((x) => x !== undefined);
|
|
@@ -306,7 +329,7 @@ export function buildSecureTools(cfg, cwd, runner) {
|
|
|
306
329
|
throw new Error('policy 参数不是合法 JSON。');
|
|
307
330
|
}
|
|
308
331
|
const policy = normalizePolicy((parsed ?? {}));
|
|
309
|
-
const file = await savePolicy(cwd, policy);
|
|
332
|
+
const file = await savePolicy(cwd, policy, signal);
|
|
310
333
|
return { file, policy };
|
|
311
334
|
},
|
|
312
335
|
timeoutMs: 10000,
|
|
@@ -337,6 +360,7 @@ export function buildSecureTools(cfg, cwd, runner) {
|
|
|
337
360
|
if (state.last === null)
|
|
338
361
|
throw new Error('尚无扫描结果,请先执行 secure_scan 或 secure_diff。');
|
|
339
362
|
const text = format === 'sarif' ? buildSarif(state.last.findings, cwd) : buildMarkdown(state.last.findings, state.last.target);
|
|
363
|
+
signal?.throwIfAborted();
|
|
340
364
|
if (target !== undefined)
|
|
341
365
|
await writeFile(target, text, 'utf8');
|
|
342
366
|
return { format, path: target ?? '', text, findingCount: state.last.findings.length };
|
|
@@ -369,7 +393,7 @@ export function buildSecureTools(cfg, cwd, runner) {
|
|
|
369
393
|
return { ok: true, plugin: 'dsh-code-security', checks };
|
|
370
394
|
},
|
|
371
395
|
};
|
|
372
|
-
return [secureScan, secureDiff, secureFixVerify, secureReport, secureExport, ...buildExtraTools(cfg, cwd), policyShow, policySet, secureHealth];
|
|
396
|
+
return [secureScan, secureDiff, secureFixVerify, secureReport, secureExport, ...buildExtraTools(cfg, cwd, signal), policyShow, policySet, secureHealth];
|
|
373
397
|
}
|
|
374
398
|
function buildMarkdown(findings, target) {
|
|
375
399
|
const lines = ['# 安全审查报告', '', '目标:' + target, '', '| 严重度 | 规则 | CWE | 文件:行 | 说明 |', '| :-- | :-- | :-- | :-- | :-- |'];
|
|
@@ -394,7 +418,7 @@ function buildSarif(findings, cwd) {
|
|
|
394
418
|
version: '2.1.0',
|
|
395
419
|
$schema: 'https://json.schemastore.org/sarif-2.1.0.json',
|
|
396
420
|
runs: [{
|
|
397
|
-
tool: { driver: { name: 'dsh-code-security', version:
|
|
421
|
+
tool: { driver: { name: 'dsh-code-security', version: PACKAGE_VERSION, informationUri: 'https://github.com/STARDUSTLC666/dsh-code-security', rules: [...rules.values()] } },
|
|
398
422
|
originalUriBaseIds: { ROOTPATH: { uri: pathToFileURL(path.resolve(cwd) + path.sep).href } },
|
|
399
423
|
results,
|
|
400
424
|
}],
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-code-security",
|
|
3
|
-
"version": "0.3.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.8",
|
|
4
|
+
"description": "对代码与 Git 改动进行本地静态安全检查,并追踪修复结果。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"lib",
|
|
18
18
|
"cordis.patch.yml",
|
|
19
19
|
"README.md",
|
|
20
|
-
"README.en.md"
|
|
20
|
+
"README.en.md",
|
|
21
|
+
"docs",
|
|
22
|
+
"CHANGELOG.md"
|
|
21
23
|
],
|
|
22
24
|
"scripts": {
|
|
23
25
|
"build": "tsc -p tsconfig.json",
|
|
@@ -46,6 +48,7 @@
|
|
|
46
48
|
},
|
|
47
49
|
"devDependencies": {
|
|
48
50
|
"@types/node": "^24.0.0",
|
|
51
|
+
"@types/proper-lockfile": "4.1.4",
|
|
49
52
|
"typescript": "^5.6.0"
|
|
50
53
|
},
|
|
51
54
|
"repository": {
|
|
@@ -57,5 +60,8 @@
|
|
|
57
60
|
},
|
|
58
61
|
"homepage": "https://github.com/STARDUSTLC666/dsh-code-security#readme",
|
|
59
62
|
"author": "stardustlc",
|
|
60
|
-
"packageManager": "pnpm@11.7.0"
|
|
63
|
+
"packageManager": "pnpm@11.7.0",
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"proper-lockfile": "4.1.2"
|
|
66
|
+
}
|
|
61
67
|
}
|