trust-npm 0.1.0 → 0.1.2
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 +2 -1
- package/dist/cli.js +14 -2
- package/dist/commands/init.js +23 -0
- package/dist/commands/install.js +1 -1
- package/dist/core/npm.js +5 -1
- package/dist/core/risk.js +48 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ This will:
|
|
|
38
38
|
|
|
39
39
|
- create/update `.trust-npm.json` from `package-lock.json`
|
|
40
40
|
- persist alias `npm -> trust-npm` in your shell profile
|
|
41
|
+
- add/update `AGENTS.md` with trust-npm usage policy
|
|
41
42
|
|
|
42
43
|
You can force shell target:
|
|
43
44
|
|
|
@@ -51,7 +52,7 @@ trust-npm init --shell zsh
|
|
|
51
52
|
|
|
52
53
|
### `trust-npm init`
|
|
53
54
|
|
|
54
|
-
Initializes `.trust-npm.json
|
|
55
|
+
Initializes `.trust-npm.json`, sets shell alias, and writes agent guidance.
|
|
55
56
|
|
|
56
57
|
```bash
|
|
57
58
|
trust-npm init
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
3
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
7
|
const commander_1 = require("commander");
|
|
8
|
+
const package_json_1 = __importDefault(require("../package.json"));
|
|
5
9
|
const init_1 = require("./commands/init");
|
|
6
10
|
const install_1 = require("./commands/install");
|
|
7
11
|
const approve_1 = require("./commands/approve");
|
|
@@ -10,7 +14,7 @@ const program = new commander_1.Command();
|
|
|
10
14
|
program
|
|
11
15
|
.name("trust-npm")
|
|
12
16
|
.description("Secure wrapper around npm install with trust baseline checks")
|
|
13
|
-
.version(
|
|
17
|
+
.version(package_json_1.default.version);
|
|
14
18
|
program
|
|
15
19
|
.command("init")
|
|
16
20
|
.description("Create .trust-npm.json and auto-alias npm -> trust-npm")
|
|
@@ -28,6 +32,14 @@ program
|
|
|
28
32
|
const rawInstallArgs = getRawInstallArgs(process.argv);
|
|
29
33
|
await (0, install_1.runInstall)(rawInstallArgs);
|
|
30
34
|
});
|
|
35
|
+
program
|
|
36
|
+
.command("i [packages...]")
|
|
37
|
+
.description("Alias for install")
|
|
38
|
+
.allowUnknownOption(true)
|
|
39
|
+
.action(async () => {
|
|
40
|
+
const rawInstallArgs = getRawInstallArgs(process.argv);
|
|
41
|
+
await (0, install_1.runInstall)(rawInstallArgs);
|
|
42
|
+
});
|
|
31
43
|
program
|
|
32
44
|
.command("approve <packages...>")
|
|
33
45
|
.description("Approve one or more packages into .trust-npm.json")
|
|
@@ -50,7 +62,7 @@ program.parseAsync(process.argv).catch((error) => {
|
|
|
50
62
|
process.exitCode = 1;
|
|
51
63
|
});
|
|
52
64
|
function getRawInstallArgs(argv) {
|
|
53
|
-
const installIndex = argv.findIndex((token) => token === "install");
|
|
65
|
+
const installIndex = argv.findIndex((token) => token === "install" || token === "i");
|
|
54
66
|
if (installIndex < 0) {
|
|
55
67
|
return [];
|
|
56
68
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -24,6 +24,8 @@ async function runInit(options = {}) {
|
|
|
24
24
|
const storePath = (0, trustStore_1.trustStorePath)(projectRoot);
|
|
25
25
|
console.log(`Initialized trust store: ${storePath}`);
|
|
26
26
|
console.log(`Trusted packages imported: ${trustedFromLockfile.size}`);
|
|
27
|
+
ensureAgentGuidance(projectRoot);
|
|
28
|
+
console.log("Agent guidance added: AGENTS.md updated with trust-npm policy.");
|
|
27
29
|
if (!options.skipAlias) {
|
|
28
30
|
const shell = detectShell(options.shell);
|
|
29
31
|
const profilePath = ensureAliasInProfile(shell);
|
|
@@ -38,6 +40,27 @@ async function runInit(options = {}) {
|
|
|
38
40
|
console.log(" Set-Alias npm trust-npm");
|
|
39
41
|
}
|
|
40
42
|
}
|
|
43
|
+
function ensureAgentGuidance(projectRoot) {
|
|
44
|
+
const agentsPath = path_1.default.join(projectRoot, "AGENTS.md");
|
|
45
|
+
const startMarker = "<!-- trust-npm policy start -->";
|
|
46
|
+
const endMarker = "<!-- trust-npm policy end -->";
|
|
47
|
+
const block = `${startMarker}
|
|
48
|
+
## trust-npm Policy
|
|
49
|
+
|
|
50
|
+
Use \`trust-npm\` for dependency installs and approvals in this repo.
|
|
51
|
+
|
|
52
|
+
- Always run \`trust-npm install ...\`, never plain \`npm install ...\`.
|
|
53
|
+
- For blocked packages, use \`trust-npm approve <package>\` only after review.
|
|
54
|
+
- Use \`trust-npm status\` to inspect trust state.
|
|
55
|
+
|
|
56
|
+
${endMarker}
|
|
57
|
+
`;
|
|
58
|
+
if (!fs_1.default.existsSync(agentsPath)) {
|
|
59
|
+
fs_1.default.writeFileSync(agentsPath, `${block}\n`, "utf8");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
appendBlockIfMissing(agentsPath, startMarker, block);
|
|
63
|
+
}
|
|
41
64
|
function detectShell(requested) {
|
|
42
65
|
if (requested) {
|
|
43
66
|
return requested;
|
package/dist/commands/install.js
CHANGED
|
@@ -49,7 +49,7 @@ function extractRequestedPackages(args) {
|
|
|
49
49
|
function printBlockedPackages(analyses) {
|
|
50
50
|
for (const analysis of analyses) {
|
|
51
51
|
const header = analysis.isHighRisk ? "BLOCKED (high risk)" : "BLOCKED (untrusted package)";
|
|
52
|
-
console.error(`\n
|
|
52
|
+
console.error(`\n${header}: ${analysis.packageName}`);
|
|
53
53
|
console.error("Reason:");
|
|
54
54
|
if (analysis.reasons.length > 0) {
|
|
55
55
|
for (const reason of analysis.reasons) {
|
package/dist/core/npm.js
CHANGED
|
@@ -7,7 +7,11 @@ async function runNpm(args) {
|
|
|
7
7
|
return new Promise((resolve, reject) => {
|
|
8
8
|
const child = (0, child_process_1.spawn)(npmCommand, args, {
|
|
9
9
|
stdio: "inherit",
|
|
10
|
-
shell: false
|
|
10
|
+
shell: false,
|
|
11
|
+
env: {
|
|
12
|
+
...process.env,
|
|
13
|
+
TRUST_NPM_ACTIVE: "1"
|
|
14
|
+
}
|
|
11
15
|
});
|
|
12
16
|
child.on("error", (error) => reject(error));
|
|
13
17
|
child.on("close", (code) => resolve(code ?? 1));
|
package/dist/core/risk.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.analyzePackageRisk = analyzePackageRisk;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
4
8
|
const registry_1 = require("./registry");
|
|
5
9
|
const NEW_PACKAGE_DAYS_THRESHOLD = 7;
|
|
6
10
|
const LOW_DOWNLOADS_THRESHOLD = 100;
|
|
@@ -8,27 +12,49 @@ async function analyzePackageRisk(packageName, threshold) {
|
|
|
8
12
|
let score = 0;
|
|
9
13
|
const reasons = [];
|
|
10
14
|
const metadata = {};
|
|
11
|
-
const [
|
|
15
|
+
const [pkgResult, downloadsResult] = await Promise.allSettled([
|
|
12
16
|
(0, registry_1.fetchPackageMetadata)(packageName),
|
|
13
17
|
(0, registry_1.fetchWeeklyDownloads)(packageName)
|
|
14
18
|
]);
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
let weeklyDownloads;
|
|
20
|
+
if (pkgResult.status === "fulfilled") {
|
|
21
|
+
const ageDays = getPackageAgeDays(pkgResult.value.createdAt);
|
|
22
|
+
metadata.publishedAt = pkgResult.value.createdAt;
|
|
23
|
+
metadata.ageDays = ageDays;
|
|
24
|
+
if (typeof ageDays === "number" && ageDays < NEW_PACKAGE_DAYS_THRESHOLD) {
|
|
25
|
+
score += 40;
|
|
26
|
+
reasons.push(`Published ${ageDays} day(s) ago`);
|
|
27
|
+
}
|
|
28
|
+
const hasRepo = hasRepository(pkgResult.value.repository);
|
|
29
|
+
metadata.hasRepository = hasRepo;
|
|
30
|
+
if (!hasRepo) {
|
|
31
|
+
score += 20;
|
|
32
|
+
reasons.push("Missing repository field");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else if (isNotFoundError(pkgResult.reason)) {
|
|
36
|
+
score += 100;
|
|
37
|
+
reasons.push("Package not found in npm registry");
|
|
26
38
|
}
|
|
27
|
-
|
|
28
|
-
metadata.hasRepository = hasRepo;
|
|
29
|
-
if (!hasRepo) {
|
|
39
|
+
else {
|
|
30
40
|
score += 20;
|
|
31
|
-
reasons.push("
|
|
41
|
+
reasons.push("Could not verify package metadata");
|
|
42
|
+
}
|
|
43
|
+
if (downloadsResult.status === "fulfilled") {
|
|
44
|
+
weeklyDownloads = downloadsResult.value.downloads;
|
|
45
|
+
metadata.weeklyDownloads = weeklyDownloads;
|
|
46
|
+
}
|
|
47
|
+
else if (isNotFoundError(downloadsResult.reason)) {
|
|
48
|
+
weeklyDownloads = 0;
|
|
49
|
+
metadata.weeklyDownloads = weeklyDownloads;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
score += 10;
|
|
53
|
+
reasons.push("Could not verify weekly downloads");
|
|
54
|
+
}
|
|
55
|
+
if (typeof weeklyDownloads === "number" && weeklyDownloads < LOW_DOWNLOADS_THRESHOLD) {
|
|
56
|
+
score += 30;
|
|
57
|
+
reasons.push(`${weeklyDownloads} weekly downloads`);
|
|
32
58
|
}
|
|
33
59
|
const suspiciousName = hasSuspiciousName(packageName);
|
|
34
60
|
metadata.suspiciousName = suspiciousName;
|
|
@@ -45,6 +71,12 @@ async function analyzePackageRisk(packageName, threshold) {
|
|
|
45
71
|
metadata
|
|
46
72
|
};
|
|
47
73
|
}
|
|
74
|
+
function isNotFoundError(error) {
|
|
75
|
+
if (!axios_1.default.isAxiosError(error)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return error.response?.status === 404;
|
|
79
|
+
}
|
|
48
80
|
function getPackageAgeDays(createdAt) {
|
|
49
81
|
if (!createdAt) {
|
|
50
82
|
return undefined;
|