dsh-vet 0.1.0 → 0.2.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/README.md +25 -3
- package/docs/rules/scan.empty-audit.md +27 -0
- package/lib/index.d.mts +12 -2
- package/lib/index.mjs +75 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# dsh-vet
|
|
2
2
|
|
|
3
|
+
[](https://github.com/rogerdigital/dsh-vet/blob/main/.dsh-vet/report.json)
|
|
4
|
+
|
|
3
5
|
Security vetting for DeepSeek Harness (DSH) plugins: permission & supply-chain
|
|
4
6
|
audits before install, graded via the open [`dsh-vet/v1`](docs/dsh-vet-v1.md)
|
|
5
7
|
report standard.
|
|
6
8
|
|
|
7
|
-
> **Status: v0.1
|
|
8
|
-
>
|
|
9
|
-
>
|
|
9
|
+
> **Status: v0.1 shipped.** [`dsh-vet@0.1.0` is live on npm](https://www.npmjs.com/package/dsh-vet) —
|
|
10
|
+
> reference scanner, 15 calibrated rules, public rule rationales, and an
|
|
11
|
+
> 11-package ecosystem sweep record. v0.2 (author-side CI + badges) is next
|
|
12
|
+
> on the [roadmap](ROADMAP.md).
|
|
10
13
|
|
|
11
14
|
## Install
|
|
12
15
|
|
|
@@ -50,6 +53,25 @@ from the exact tarball that ships (`npm pack` → scan), seams declared in
|
|
|
50
53
|
package.json. It is not an A-by-cheating report — every signal the scanner
|
|
51
54
|
finds in itself is in there.
|
|
52
55
|
|
|
56
|
+
## CI & badge for plugin authors
|
|
57
|
+
|
|
58
|
+
Audit your plugin on every push and PR, and publish your grade from the
|
|
59
|
+
report committed to your repository — shields.io reads the badge straight
|
|
60
|
+
from your repo, so its value is auditable through git history and no badge
|
|
61
|
+
service is involved:
|
|
62
|
+
|
|
63
|
+
```yaml
|
|
64
|
+
- uses: rogerdigital/dsh-vet/action@v0.2.0
|
|
65
|
+
with:
|
|
66
|
+
specifier: '.'
|
|
67
|
+
commit-report: true
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Every run uploads the full report as an artifact; PRs get a single
|
|
71
|
+
edited-in-place findings comment. Badge snippet and all inputs:
|
|
72
|
+
[`action/README.md`](action/README.md). The `dsh-vet badge <report.json>`
|
|
73
|
+
command renders the shields endpoint JSON if you wire CI yourself.
|
|
74
|
+
|
|
53
75
|
## Why
|
|
54
76
|
|
|
55
77
|
DSH's everything-is-a-plugin architecture is its greatest strength and its
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# scan.empty-audit
|
|
2
|
+
|
|
3
|
+
No analyzable JavaScript found in the target.
|
|
4
|
+
|
|
5
|
+
## What it looks for
|
|
6
|
+
|
|
7
|
+
The scanner found zero `.js`/`.mjs`/`.cjs` files (or Node-shebang bin
|
|
8
|
+
scripts) in the audited tree. This rule fires on behalf of the report's
|
|
9
|
+
readers: a scan that audited nothing must not read as a clean pass.
|
|
10
|
+
|
|
11
|
+
## Severity / confidence policy
|
|
12
|
+
|
|
13
|
+
**medium / high.** Nothing was analyzed — that is a fact, and a grade of A
|
|
14
|
+
here would be vacuous. Found live during v0.2 activation: a
|
|
15
|
+
TypeScript-source repository scanned as a local path ships no `.js` in its
|
|
16
|
+
working tree, so `.` audits nothing while the badge reads green.
|
|
17
|
+
|
|
18
|
+
## False positives
|
|
19
|
+
|
|
20
|
+
Targets that genuinely contain no JavaScript at all (a data package, a
|
|
21
|
+
documentation bundle). If that is you, the finding is still correct — you
|
|
22
|
+
are advertising an audit that cannot say anything.
|
|
23
|
+
|
|
24
|
+
## Remediation
|
|
25
|
+
|
|
26
|
+
Audit what actually ships: pass the npm package specifier (or a directory
|
|
27
|
+
containing the built output) instead of the source tree.
|
package/lib/index.d.mts
CHANGED
|
@@ -271,7 +271,7 @@ declare function runRules(analysis: Analysis, only?: string[]): VetFinding[];
|
|
|
271
271
|
//#endregion
|
|
272
272
|
//#region src/scanner.d.ts
|
|
273
273
|
/** Kept in lockstep with package.json; a test asserts they match. */
|
|
274
|
-
declare const SCANNER_VERSION = "0.1
|
|
274
|
+
declare const SCANNER_VERSION = "0.2.1";
|
|
275
275
|
interface ScanOptions extends ResolveOptions {
|
|
276
276
|
/** Injectable clock for deterministic tests/reports. */
|
|
277
277
|
now?: () => string;
|
|
@@ -281,6 +281,16 @@ interface ScanOptions extends ResolveOptions {
|
|
|
281
281
|
declare function scanDirectory(dir: string, options?: ScanOptions): Promise<VetReport>;
|
|
282
282
|
declare function scan(specifier: string, options?: ScanOptions): Promise<VetReport>;
|
|
283
283
|
//#endregion
|
|
284
|
+
//#region src/badge.d.ts
|
|
285
|
+
interface ShieldsEndpointBadge {
|
|
286
|
+
schemaVersion: 1;
|
|
287
|
+
label: string;
|
|
288
|
+
message: string;
|
|
289
|
+
color: string;
|
|
290
|
+
isError?: boolean;
|
|
291
|
+
}
|
|
292
|
+
declare function renderBadge(report: VetReport): ShieldsEndpointBadge;
|
|
293
|
+
//#endregion
|
|
284
294
|
//#region src/cli.d.ts
|
|
285
295
|
/**
|
|
286
296
|
* CLI (ROADMAP T4). Exit semantics per the dsh-vet/v1 spec: `0` for any
|
|
@@ -294,4 +304,4 @@ interface CliIo {
|
|
|
294
304
|
/** Parse and run; returns the process exit code. */
|
|
295
305
|
declare function runCli(argv: string[], io: CliIo): Promise<number>;
|
|
296
306
|
//#endregion
|
|
297
|
-
export { type Analysis, type Capability, type CapabilityUse, type CharcodeCall, type CliIo, type CreateReportInput, type DynamicImportUse, type EncodedLiteral, type EvalUse, type FindingInit, type ImportRef, type NetUse, type PkgJson, RULES, RULE_ID_PATTERN, type ResolveOptions, type ResolvedTarget, type Rule, type RuleContext, SCANNER_VERSION, SCHEMA_ID, type ScanOptions, type SourceFile, type SpecifierKind, type VetConfidence, VetError, type VetEvidence, type VetFinding, type VetGrade, type VetReport, type VetScanner, type VetSeverity, type VetSummary, type VetSummaryCounts, type VetTarget, type VetTargetKind, analyze, classifySpecifier, countFindings, createReport, gradeFor, isGraded, parseNpmSpecifier, reachableFrom, resolveTarget, ruleIds, runCli, runRules, scan, scanDirectory };
|
|
307
|
+
export { type Analysis, type Capability, type CapabilityUse, type CharcodeCall, type CliIo, type CreateReportInput, type DynamicImportUse, type EncodedLiteral, type EvalUse, type FindingInit, type ImportRef, type NetUse, type PkgJson, RULES, RULE_ID_PATTERN, type ResolveOptions, type ResolvedTarget, type Rule, type RuleContext, SCANNER_VERSION, SCHEMA_ID, type ScanOptions, type ShieldsEndpointBadge, type SourceFile, type SpecifierKind, type VetConfidence, VetError, type VetEvidence, type VetFinding, type VetGrade, type VetReport, type VetScanner, type VetSeverity, type VetSummary, type VetSummaryCounts, type VetTarget, type VetTargetKind, analyze, classifySpecifier, countFindings, createReport, gradeFor, isGraded, parseNpmSpecifier, reachableFrom, renderBadge, resolveTarget, ruleIds, runCli, runRules, scan, scanDirectory };
|
package/lib/index.mjs
CHANGED
|
@@ -1601,9 +1601,31 @@ function runRules(analysis, only) {
|
|
|
1601
1601
|
* over the same artifact with the same version are identical.
|
|
1602
1602
|
*/
|
|
1603
1603
|
/** Kept in lockstep with package.json; a test asserts they match. */
|
|
1604
|
-
const SCANNER_VERSION = "0.1
|
|
1604
|
+
const SCANNER_VERSION = "0.2.1";
|
|
1605
|
+
/**
|
|
1606
|
+
* A scan that audited zero JavaScript files must not read as a clean pass —
|
|
1607
|
+
* a TypeScript source tree scanned as a local path has no `.js` to analyze,
|
|
1608
|
+
* and silence there would be an A-by-vacuity. (Found live during v0.2
|
|
1609
|
+
* activation; docs/rules/scan.empty-audit.md.)
|
|
1610
|
+
*/
|
|
1611
|
+
function emptyAuditFinding(targetSpecifier) {
|
|
1612
|
+
return {
|
|
1613
|
+
id: "scan.empty-audit",
|
|
1614
|
+
title: "No analyzable JavaScript found in the target",
|
|
1615
|
+
severity: "medium",
|
|
1616
|
+
confidence: "high",
|
|
1617
|
+
evidence: [{
|
|
1618
|
+
file: ".",
|
|
1619
|
+
note: `zero .js/.mjs/.cjs files (or Node-shebang bin scripts) found under ${targetSpecifier}`
|
|
1620
|
+
}],
|
|
1621
|
+
remediation: "Audit what actually ships: pass the npm package specifier (or a directory containing the built output) instead of the source tree.",
|
|
1622
|
+
references: ["https://github.com/rogerdigital/dsh-vet/blob/main/docs/rules/scan.empty-audit.md"]
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1605
1625
|
async function scanDirectory(dir, options = {}) {
|
|
1606
1626
|
const analysis = analyze(dir);
|
|
1627
|
+
const findings = options.rules ? runRules(analysis, options.rules) : runRules(analysis);
|
|
1628
|
+
if (analysis.files.length === 0) findings.push(emptyAuditFinding(dir));
|
|
1607
1629
|
return createReport({
|
|
1608
1630
|
target: {
|
|
1609
1631
|
kind: "local-path",
|
|
@@ -1614,13 +1636,15 @@ async function scanDirectory(dir, options = {}) {
|
|
|
1614
1636
|
version: SCANNER_VERSION,
|
|
1615
1637
|
ranAt: options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
1616
1638
|
},
|
|
1617
|
-
findings
|
|
1639
|
+
findings
|
|
1618
1640
|
});
|
|
1619
1641
|
}
|
|
1620
1642
|
async function scan(specifier, options = {}) {
|
|
1621
1643
|
const resolved = await resolveTarget(specifier, options);
|
|
1622
1644
|
try {
|
|
1623
1645
|
const analysis = analyze(resolved.rootDir);
|
|
1646
|
+
const findings = options.rules ? runRules(analysis, options.rules) : runRules(analysis);
|
|
1647
|
+
if (analysis.files.length === 0) findings.push(emptyAuditFinding(specifier));
|
|
1624
1648
|
return createReport({
|
|
1625
1649
|
target: resolved.target,
|
|
1626
1650
|
scanner: {
|
|
@@ -1628,13 +1652,33 @@ async function scan(specifier, options = {}) {
|
|
|
1628
1652
|
version: SCANNER_VERSION,
|
|
1629
1653
|
ranAt: options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
1630
1654
|
},
|
|
1631
|
-
findings
|
|
1655
|
+
findings
|
|
1632
1656
|
});
|
|
1633
1657
|
} finally {
|
|
1634
1658
|
resolved.cleanup();
|
|
1635
1659
|
}
|
|
1636
1660
|
}
|
|
1637
1661
|
//#endregion
|
|
1662
|
+
//#region src/badge.ts
|
|
1663
|
+
const GRADE_COLOR = {
|
|
1664
|
+
A: "brightgreen",
|
|
1665
|
+
B: "green",
|
|
1666
|
+
C: "yellow",
|
|
1667
|
+
D: "orange",
|
|
1668
|
+
F: "red",
|
|
1669
|
+
X: "lightgrey"
|
|
1670
|
+
};
|
|
1671
|
+
function renderBadge(report) {
|
|
1672
|
+
const grade = report.summary.grade;
|
|
1673
|
+
return {
|
|
1674
|
+
schemaVersion: 1,
|
|
1675
|
+
label: "dsh-vet",
|
|
1676
|
+
message: grade === "X" ? "scan failed" : `grade ${grade}`,
|
|
1677
|
+
color: GRADE_COLOR[grade],
|
|
1678
|
+
...grade === "X" ? { isError: true } : {}
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
//#endregion
|
|
1638
1682
|
//#region src/cli.ts
|
|
1639
1683
|
/**
|
|
1640
1684
|
* CLI (ROADMAP T4). Exit semantics per the dsh-vet/v1 spec: `0` for any
|
|
@@ -1642,6 +1686,7 @@ async function scan(specifier, options = {}) {
|
|
|
1642
1686
|
* `--strict`/`--fail-on` turn threshold breaches into exit code 1.
|
|
1643
1687
|
*/
|
|
1644
1688
|
const USAGE = `usage: dsh-vet <specifier> [options]
|
|
1689
|
+
dsh-vet badge <report.json>
|
|
1645
1690
|
|
|
1646
1691
|
specifier npm package (name[@version]), git URL, or local path
|
|
1647
1692
|
|
|
@@ -1650,7 +1695,11 @@ const USAGE = `usage: dsh-vet <specifier> [options]
|
|
|
1650
1695
|
--fail-on <sev> override the --strict threshold (critical|high|medium|low)
|
|
1651
1696
|
--rules <ids> comma-separated rule ids to run
|
|
1652
1697
|
--version print version
|
|
1653
|
-
--help this text
|
|
1698
|
+
--help this text
|
|
1699
|
+
|
|
1700
|
+
badge render a shields.io endpoint badge (JSON) from a
|
|
1701
|
+
dsh-vet/v1 report file; used by CI to publish a grade
|
|
1702
|
+
badge whose value is the committed report`;
|
|
1654
1703
|
const SEVERITY_ORDER = [
|
|
1655
1704
|
"critical",
|
|
1656
1705
|
"high",
|
|
@@ -1685,6 +1734,7 @@ function humanSummary(report) {
|
|
|
1685
1734
|
}
|
|
1686
1735
|
/** Parse and run; returns the process exit code. */
|
|
1687
1736
|
async function runCli(argv, io) {
|
|
1737
|
+
if (argv[0] === "badge") return runBadge(argv.slice(1), io);
|
|
1688
1738
|
let args;
|
|
1689
1739
|
try {
|
|
1690
1740
|
args = parseArgs({
|
|
@@ -1737,5 +1787,25 @@ async function runCli(argv, io) {
|
|
|
1737
1787
|
return 2;
|
|
1738
1788
|
}
|
|
1739
1789
|
}
|
|
1790
|
+
function runBadge(argv, io) {
|
|
1791
|
+
const path = argv[0];
|
|
1792
|
+
if (!path || argv.length > 1 || path === "--help") {
|
|
1793
|
+
io.stderr(`usage: dsh-vet badge <report.json>\n\nRender a shields.io endpoint badge from a dsh-vet/v1 report.`);
|
|
1794
|
+
return 2;
|
|
1795
|
+
}
|
|
1796
|
+
let report;
|
|
1797
|
+
try {
|
|
1798
|
+
report = JSON.parse(readFileSync(path, "utf8"));
|
|
1799
|
+
} catch (err) {
|
|
1800
|
+
io.stderr(`dsh-vet badge: cannot read report: ${err.message}`);
|
|
1801
|
+
return 2;
|
|
1802
|
+
}
|
|
1803
|
+
if (report.schema !== "dsh-vet/v1") {
|
|
1804
|
+
io.stderr(`dsh-vet badge: not a ${SCHEMA_ID} report (schema: ${String(report.schema)})`);
|
|
1805
|
+
return 2;
|
|
1806
|
+
}
|
|
1807
|
+
io.stdout(JSON.stringify(renderBadge(report)));
|
|
1808
|
+
return 0;
|
|
1809
|
+
}
|
|
1740
1810
|
//#endregion
|
|
1741
|
-
export { RULES, RULE_ID_PATTERN, SCANNER_VERSION, SCHEMA_ID, VetError, analyze, classifySpecifier, countFindings, createReport, gradeFor, isGraded, parseNpmSpecifier, reachableFrom, resolveTarget, ruleIds, runCli, runRules, scan, scanDirectory };
|
|
1811
|
+
export { RULES, RULE_ID_PATTERN, SCANNER_VERSION, SCHEMA_ID, VetError, analyze, classifySpecifier, countFindings, createReport, gradeFor, isGraded, parseNpmSpecifier, reachableFrom, renderBadge, resolveTarget, ruleIds, runCli, runRules, scan, scanDirectory };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vet",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Security vetting for DeepSeek Harness (DSH) plugins: permission & supply-chain audits before install, graded via the open dsh-vet/v1 report standard.",
|
|
5
5
|
"dsh": {
|
|
6
6
|
"seams": ["fs", "shell", "web"]
|