@proofofwork-agency/toolpin 0.2.3
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/CONTRIBUTING.md +117 -0
- package/LICENSE +183 -0
- package/README.md +323 -0
- package/SECURITY.md +61 -0
- package/action.yml +134 -0
- package/dist/canonicalJson.js +38 -0
- package/dist/capabilities.js +139 -0
- package/dist/ci.js +26 -0
- package/dist/cli.js +1843 -0
- package/dist/clientSupport.js +76 -0
- package/dist/codexToml.js +213 -0
- package/dist/config.js +337 -0
- package/dist/constants.js +3 -0
- package/dist/continueYaml.js +76 -0
- package/dist/doctor.js +163 -0
- package/dist/install.js +191 -0
- package/dist/installed.js +405 -0
- package/dist/integrity.js +14 -0
- package/dist/inventory.js +169 -0
- package/dist/packageIntegrity.js +153 -0
- package/dist/plan.js +595 -0
- package/dist/policy.js +310 -0
- package/dist/registry.js +1610 -0
- package/dist/runtimeAdvisory.js +80 -0
- package/dist/safeFetch.js +157 -0
- package/dist/sarif.js +162 -0
- package/dist/scan.js +113 -0
- package/dist/search.js +44 -0
- package/dist/secrets.js +165 -0
- package/dist/signing.js +146 -0
- package/dist/tester.js +240 -0
- package/dist/trust.js +528 -0
- package/dist/tui/app.js +1731 -0
- package/dist/tui/command.js +50 -0
- package/dist/tui/configSnippet.js +11 -0
- package/dist/tui/constants.js +37 -0
- package/dist/tui/format.js +31 -0
- package/dist/tui/installedState.js +23 -0
- package/dist/tui/layout.js +65 -0
- package/dist/tui/selectors.js +282 -0
- package/dist/tui/types.js +1 -0
- package/dist/tui/ui/trust.js +77 -0
- package/dist/tui/views/installed.js +82 -0
- package/dist/tui/views/panels.js +637 -0
- package/dist/tui.js +12 -0
- package/dist/types.js +1 -0
- package/dist/verificationTrust.js +103 -0
- package/dist/verify.js +537 -0
- package/dist/version.js +1 -0
- package/dist/versions.js +127 -0
- package/docs/assets/readme/terminal-demo.svg +174 -0
- package/docs/assets/readme/tui-browse-overview.jpg +0 -0
- package/docs/assets/readme/tui-config-preview.jpg +0 -0
- package/docs/assets/readme/tui-help.jpg +0 -0
- package/docs/assets/readme/tui-installed-inventory.jpg +0 -0
- package/docs/how-to/catch-drift-in-ci.md +189 -0
- package/docs/how-to/custom-registries.md +156 -0
- package/docs/how-to/toolpin-curated-registry.md +153 -0
- package/package.json +76 -0
- package/registry/README.md +92 -0
- package/registry/v0/servers +115 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { safeFetchBuffer, safeFetchJson } from "./safeFetch.js";
|
|
3
|
+
import { TRUSTED_NPM_PACKUMENT_HOSTS, TRUSTED_NPM_TARBALL_HOSTS, trustedNpmPackumentHost, trustedNpmTarballHost } from "./verificationTrust.js";
|
|
4
|
+
const NPM_PACKUMENT_HOST = "registry.npmjs.org";
|
|
5
|
+
const DEFAULT_TARBALL_MAX_BYTES = 128 * 1024 * 1024;
|
|
6
|
+
export async function verifyNpmPackageIntegrity(target, options = {}) {
|
|
7
|
+
if (!target.version || isFloatingVersion(target.version)) {
|
|
8
|
+
return {
|
|
9
|
+
status: "failed",
|
|
10
|
+
source: "npm-registry",
|
|
11
|
+
reason: "npm package target must declare an exact version",
|
|
12
|
+
issueCode: "npm_version_missing",
|
|
13
|
+
trustedAnchor: true,
|
|
14
|
+
trustAnchor: NPM_PACKUMENT_HOST,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
const packumentUrl = npmPackumentUrl(target.identifier);
|
|
18
|
+
const packumentHost = trustedNpmPackumentHost(packumentUrl);
|
|
19
|
+
if (!packumentHost) {
|
|
20
|
+
return {
|
|
21
|
+
status: "failed",
|
|
22
|
+
source: "npm-registry",
|
|
23
|
+
reason: "npm packument host is not a ToolPin trusted anchor",
|
|
24
|
+
issueCode: "npm_packument_untrusted",
|
|
25
|
+
trustedAnchor: false,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
let packument;
|
|
29
|
+
try {
|
|
30
|
+
packument = await safeFetchJson(packumentUrl, {
|
|
31
|
+
allowedHosts: TRUSTED_NPM_PACKUMENT_HOSTS,
|
|
32
|
+
fetch: options.fetch,
|
|
33
|
+
lookup: options.lookup,
|
|
34
|
+
timeoutMs: options.timeoutMs,
|
|
35
|
+
maxBytes: 4 * 1024 * 1024,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
return {
|
|
40
|
+
status: "unavailable",
|
|
41
|
+
source: "npm-registry",
|
|
42
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
43
|
+
trustedAnchor: true,
|
|
44
|
+
trustAnchor: packumentHost,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const version = packument.versions?.[target.version];
|
|
48
|
+
if (!version) {
|
|
49
|
+
return {
|
|
50
|
+
status: "failed",
|
|
51
|
+
source: "npm-registry",
|
|
52
|
+
reason: `npm packument does not contain version ${target.version}`,
|
|
53
|
+
issueCode: "npm_version_missing",
|
|
54
|
+
trustedAnchor: true,
|
|
55
|
+
trustAnchor: packumentHost,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const integrity = version.dist?.integrity;
|
|
59
|
+
if (!integrity) {
|
|
60
|
+
return {
|
|
61
|
+
status: "failed",
|
|
62
|
+
source: "npm-registry",
|
|
63
|
+
reason: `npm packument version ${target.version} does not declare dist.integrity`,
|
|
64
|
+
issueCode: "npm_integrity_missing",
|
|
65
|
+
trustedAnchor: true,
|
|
66
|
+
trustAnchor: packumentHost,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const expected = sha512FromSri(integrity);
|
|
70
|
+
if (!expected) {
|
|
71
|
+
return {
|
|
72
|
+
status: "failed",
|
|
73
|
+
source: "npm-registry",
|
|
74
|
+
expected: integrity,
|
|
75
|
+
reason: "npm dist.integrity does not include a sha512 Subresource Integrity value",
|
|
76
|
+
issueCode: "npm_integrity_missing",
|
|
77
|
+
trustedAnchor: true,
|
|
78
|
+
trustAnchor: packumentHost,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const tarballUrl = version.dist?.tarball;
|
|
82
|
+
const tarballHost = tarballUrl ? trustedNpmTarballHost(tarballUrl) : undefined;
|
|
83
|
+
if (!tarballUrl || !tarballHost) {
|
|
84
|
+
return {
|
|
85
|
+
status: "failed",
|
|
86
|
+
source: "npm-tarball",
|
|
87
|
+
expected: integrity,
|
|
88
|
+
tarballUrl,
|
|
89
|
+
reason: tarballUrl ? "npm tarball host is not a ToolPin trusted anchor" : "npm packument version does not declare dist.tarball",
|
|
90
|
+
issueCode: "npm_tarball_untrusted",
|
|
91
|
+
trustedAnchor: false,
|
|
92
|
+
trustAnchor: tarballUrl ? undefined : packumentHost,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
let bytes;
|
|
96
|
+
try {
|
|
97
|
+
bytes = await safeFetchBuffer(tarballUrl, {
|
|
98
|
+
allowedHosts: TRUSTED_NPM_TARBALL_HOSTS,
|
|
99
|
+
fetch: options.fetch,
|
|
100
|
+
lookup: options.lookup,
|
|
101
|
+
timeoutMs: options.timeoutMs,
|
|
102
|
+
maxBytes: options.maxBytes ?? DEFAULT_TARBALL_MAX_BYTES,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
return {
|
|
107
|
+
status: "unavailable",
|
|
108
|
+
source: "npm-tarball",
|
|
109
|
+
expected: integrity,
|
|
110
|
+
tarballUrl,
|
|
111
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
112
|
+
trustedAnchor: true,
|
|
113
|
+
trustAnchor: tarballHost,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const actual = createHash("sha512").update(bytes).digest("base64");
|
|
117
|
+
if (actual !== expected) {
|
|
118
|
+
return {
|
|
119
|
+
status: "failed",
|
|
120
|
+
source: "npm-tarball",
|
|
121
|
+
expected: integrity,
|
|
122
|
+
actual: `sha512-${actual}`,
|
|
123
|
+
tarballUrl,
|
|
124
|
+
reason: "npm tarball bytes do not match dist.integrity",
|
|
125
|
+
issueCode: "npm_integrity_mismatch",
|
|
126
|
+
trustedAnchor: true,
|
|
127
|
+
trustAnchor: tarballHost,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
status: "passed",
|
|
132
|
+
source: "npm-tarball",
|
|
133
|
+
expected: integrity,
|
|
134
|
+
actual: `sha512-${actual}`,
|
|
135
|
+
tarballUrl,
|
|
136
|
+
trustedAnchor: true,
|
|
137
|
+
trustAnchor: tarballHost,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function npmPackumentUrl(name) {
|
|
141
|
+
return `https://${NPM_PACKUMENT_HOST}/${encodeURIComponent(name)}`;
|
|
142
|
+
}
|
|
143
|
+
function sha512FromSri(integrity) {
|
|
144
|
+
for (const part of integrity.trim().split(/\s+/)) {
|
|
145
|
+
const match = part.match(/^sha512-([A-Za-z0-9+/=]+)$/);
|
|
146
|
+
if (match)
|
|
147
|
+
return match[1];
|
|
148
|
+
}
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
function isFloatingVersion(version) {
|
|
152
|
+
return ["latest", "*"].includes(version.trim().toLowerCase()) || /[~^x*]/i.test(version);
|
|
153
|
+
}
|