@westbayberry/dg 2.3.1 → 2.3.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/dist/launcher/agent-check.js +148 -6
- package/dist/launcher/classify.js +39 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +18 -2
|
@@ -68,8 +68,18 @@ const PIP_VALUE_FLAGS = new Set([
|
|
|
68
68
|
"--extra-index-url", "-f", "--find-links", "-t", "--target", "--platform",
|
|
69
69
|
"--python-version", "--implementation", "--abi", "--prefix", "--root", "--no-binary",
|
|
70
70
|
"--only-binary", "--progress-bar",
|
|
71
|
+
// Global value-flags that can precede the `install` subcommand; without them
|
|
72
|
+
// the flag's value is mistaken for the verb and the real package is missed.
|
|
73
|
+
"--log", "--cache-dir", "--proxy", "--timeout", "--retries", "--cert", "--client-cert",
|
|
74
|
+
"--trusted-host", "--log-file", "--python",
|
|
75
|
+
]);
|
|
76
|
+
const NPM_VALUE_FLAGS = new Set([
|
|
77
|
+
"--registry", "--prefix", "-C", "--workspace", "-w", "--tag", "--otp",
|
|
78
|
+
// Global value-flags that can precede the subcommand.
|
|
79
|
+
"--loglevel", "--cache", "--userconfig", "--globalconfig", "--node-options", "--script-shell",
|
|
80
|
+
"--before", "--omit", "--include", "--https-proxy", "--proxy", "--cafile", "--cert", "--key",
|
|
81
|
+
"--user-agent", "--fetch-timeout", "--fetch-retries", "--maxsockets", "--network-timeout",
|
|
71
82
|
]);
|
|
72
|
-
const NPM_VALUE_FLAGS = new Set(["--registry", "--prefix", "-C", "--workspace", "-w", "--tag", "--otp"]);
|
|
73
83
|
function analyzeEcosystem(eco) {
|
|
74
84
|
if (eco === "javascript")
|
|
75
85
|
return "npm";
|
|
@@ -78,14 +88,123 @@ function analyzeEcosystem(eco) {
|
|
|
78
88
|
return null;
|
|
79
89
|
}
|
|
80
90
|
function splitSegments(line) {
|
|
81
|
-
// Split on shell control operators
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
|
|
91
|
+
// Split on shell control operators (`&&`, `||`, `;`, `|`, newline, and a
|
|
92
|
+
// backgrounding `&`), but only OUTSIDE single/double quotes and backslash
|
|
93
|
+
// escapes — the shell treats a quoted `;`/`&&`/`|` as literal data, so a
|
|
94
|
+
// separator inside a curl header, a search query, or a JSON body must not
|
|
95
|
+
// shear the argument into a phantom command segment (a quoted `npm install`
|
|
96
|
+
// would otherwise be screened as a real install). A `$(…)`/backtick command
|
|
97
|
+
// substitution inside quotes is still reached: collectSegments extracts its
|
|
98
|
+
// body separately. A bare `&` glued to a `>` (`2>&1`, `&>file`) is part of a
|
|
99
|
+
// redirection, not a separator.
|
|
100
|
+
const segments = [];
|
|
101
|
+
let buf = "";
|
|
102
|
+
let quote = null;
|
|
103
|
+
let ansiC = false;
|
|
104
|
+
const push = () => {
|
|
105
|
+
const t = buf.trim();
|
|
106
|
+
if (t) {
|
|
107
|
+
segments.push(t);
|
|
108
|
+
}
|
|
109
|
+
buf = "";
|
|
110
|
+
};
|
|
111
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
112
|
+
const ch = line[i] ?? "";
|
|
113
|
+
if (ansiC) {
|
|
114
|
+
// ANSI-C `$'…'`: a backslash escapes the next char, so `\'` does NOT close
|
|
115
|
+
// the span — track it the way lexSegment/decodeAnsiC and bash do, or the
|
|
116
|
+
// quote state desyncs and a real `;`/`&&` after it is mis-split.
|
|
117
|
+
if (ch === "\\" && i + 1 < line.length) {
|
|
118
|
+
buf += ch + line[i + 1];
|
|
119
|
+
i += 1;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
buf += ch;
|
|
123
|
+
if (ch === "'") {
|
|
124
|
+
ansiC = false;
|
|
125
|
+
}
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (quote === "'") {
|
|
129
|
+
buf += ch;
|
|
130
|
+
if (ch === "'") {
|
|
131
|
+
quote = null;
|
|
132
|
+
}
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (quote === '"') {
|
|
136
|
+
if (ch === "\\" && i + 1 < line.length) {
|
|
137
|
+
buf += ch + line[i + 1];
|
|
138
|
+
i += 1;
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
buf += ch;
|
|
142
|
+
if (ch === '"') {
|
|
143
|
+
quote = null;
|
|
144
|
+
}
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
if (ch === "$" && line[i + 1] === "'") {
|
|
148
|
+
ansiC = true;
|
|
149
|
+
buf += "$'";
|
|
150
|
+
i += 1;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (ch === "\\" && i + 1 < line.length) {
|
|
154
|
+
buf += ch + line[i + 1];
|
|
155
|
+
i += 1;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (ch === "'" || ch === '"') {
|
|
159
|
+
quote = ch;
|
|
160
|
+
buf += ch;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (ch === "#" && (i === 0 || /\s/.test(line[i - 1] ?? ""))) {
|
|
164
|
+
// A bash comment begins at an unquoted `#` on a word boundary and runs to
|
|
165
|
+
// end of line; the shell never executes that text, so splitting it into a
|
|
166
|
+
// phantom install segment would be a false block.
|
|
167
|
+
push();
|
|
168
|
+
while (i < line.length && line[i] !== "\n" && line[i] !== "\r") {
|
|
169
|
+
i += 1;
|
|
170
|
+
}
|
|
171
|
+
i -= 1;
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (ch === "\n" || ch === "\r" || ch === ";") {
|
|
175
|
+
push();
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (ch === "|") {
|
|
179
|
+
push();
|
|
180
|
+
if (line[i + 1] === "|") {
|
|
181
|
+
i += 1;
|
|
182
|
+
}
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
if (ch === "&") {
|
|
186
|
+
if (line[i + 1] === "&") {
|
|
187
|
+
push();
|
|
188
|
+
i += 1;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (line[i - 1] === ">" || line[i + 1] === ">") {
|
|
192
|
+
buf += ch;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
push();
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
buf += ch;
|
|
199
|
+
}
|
|
200
|
+
push();
|
|
201
|
+
return segments;
|
|
85
202
|
}
|
|
86
203
|
function substitutionBodies(line) {
|
|
87
204
|
const bodies = [];
|
|
88
|
-
|
|
205
|
+
// `$(…)`, backtick, and process substitution `<(…)` / `>(…)` all run their
|
|
206
|
+
// body as a command, so an install hidden inside one must be re-screened.
|
|
207
|
+
const patterns = [/\$\(([^)]*)\)/g, /`([^`]*)`/g, /<\(([^)]*)\)/g, />\(([^)]*)\)/g];
|
|
89
208
|
for (const pattern of patterns) {
|
|
90
209
|
let m;
|
|
91
210
|
while ((m = pattern.exec(line)) !== null) {
|
|
@@ -332,6 +451,13 @@ const WRAPPERS = new Map([
|
|
|
332
451
|
["xargs", { valueFlags: new Set(["-a", "--arg-file", "-d", "--delimiter", "-E", "-e", "--eof", "-I", "-i", "--replace", "-L", "-l", "--max-lines", "-n", "--max-args", "-P", "--max-procs", "-s", "--max-chars"]) }],
|
|
333
452
|
]);
|
|
334
453
|
const SHELL_EXEC = new Set(["sh", "bash", "zsh", "dash", "ash"]);
|
|
454
|
+
// Control-flow keywords that introduce a command (`if …; then <cmd>`, `for …;
|
|
455
|
+
// do <cmd>`, `! <cmd>`). The segmenter splits on `;`, so the command after one
|
|
456
|
+
// lands at the head of its segment behind the keyword — strip it or a real
|
|
457
|
+
// `do npm install <pkg>` is read as the no-op command `do`.
|
|
458
|
+
const SHELL_KEYWORDS = new Set(["then", "do", "else", "elif", "!"]);
|
|
459
|
+
// A `case … in <pat>) <cmd> ;;` arm label, e.g. `a)`, `*)`, `*.js)`, `a|b)`.
|
|
460
|
+
const CASE_LABEL = /^[A-Za-z0-9_*?.\][|@!+-]+\)$/;
|
|
335
461
|
// Package managers dg doesn't statically screen (no shim, no classifier). A
|
|
336
462
|
// recognized install verb for one of these can't be screened by the hook, so it
|
|
337
463
|
// defers to the runtime gate rather than waving it through silently.
|
|
@@ -374,6 +500,22 @@ function commandTokens(segment) {
|
|
|
374
500
|
while (tokens.length > 0 && ENV_ASSIGNMENT.test(tokens[0] ?? "")) {
|
|
375
501
|
tokens = tokens.slice(1);
|
|
376
502
|
}
|
|
503
|
+
const kw = tokens[0] ?? "";
|
|
504
|
+
if (SHELL_KEYWORDS.has(kw)) {
|
|
505
|
+
tokens = tokens.slice(1);
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
if (kw === "case") {
|
|
509
|
+
const close = tokens.findIndex((t) => t.endsWith(")"));
|
|
510
|
+
if (close > 0) {
|
|
511
|
+
tokens = tokens.slice(close + 1);
|
|
512
|
+
continue;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (CASE_LABEL.test(kw)) {
|
|
516
|
+
tokens = tokens.slice(1);
|
|
517
|
+
continue;
|
|
518
|
+
}
|
|
377
519
|
const head = commandBasename(tokens[0] ?? "");
|
|
378
520
|
if (head === "eval" && tokens.length > 1) {
|
|
379
521
|
return { tokens, ok: lexed.ok, shellScript: tokens.slice(1).join(" "), ...x() };
|
|
@@ -7,8 +7,43 @@ const yarnProtected = new Set(["add", "install", "upgrade", "dlx", "create"]);
|
|
|
7
7
|
const pipProtected = new Set(["install", "download", "wheel"]);
|
|
8
8
|
const pipxProtected = new Set(["install", "upgrade", "inject", "run"]);
|
|
9
9
|
const uvProtected = new Set(["add", "sync"]);
|
|
10
|
-
|
|
10
|
+
// Only the dependency-set-changing verbs. `build`/`test`/`run`/`check` compile
|
|
11
|
+
// local code (the npm `run`/`build` analog, which is passthrough) and crates are
|
|
12
|
+
// not analyzable yet, so gating them just interrupts every Rust build with no
|
|
13
|
+
// screening value.
|
|
14
|
+
const cargoProtected = new Set(["add", "install", "fetch", "update"]);
|
|
11
15
|
const passthrough = new Set(["help", "--help", "-h", "version", "--version", "-v", "list", "ls", "show", "freeze"]);
|
|
16
|
+
// Recognized read-only / local / script subcommands across the managers. Used
|
|
17
|
+
// only to locate the real subcommand when a global value-flag puts a non-verb
|
|
18
|
+
// token first (`npm --prefix /tmp install x` → action `/tmp`, missing the
|
|
19
|
+
// install). A flag value (a path, URL, or log level) is never a recognized
|
|
20
|
+
// verb, so the scan steps over it; a script-runner verb (`run`, `view`) is
|
|
21
|
+
// matched before any later install-looking token, so `npm run install` is not
|
|
22
|
+
// mis-screened. Must contain no install verb (those live in the protected sets).
|
|
23
|
+
const READ_ONLY_VERBS = new Set([
|
|
24
|
+
"run", "run-script", "start", "stop", "restart", "test", "build", "watch", "serve", "lint", "format",
|
|
25
|
+
"ls", "list", "ll", "la", "view", "show", "info", "outdated", "why", "fund", "audit", "doctor", "ping",
|
|
26
|
+
"config", "get", "cache", "whoami", "org", "team", "access", "token", "profile", "search", "dist-tag",
|
|
27
|
+
"docs", "repo", "home", "bin", "root", "prefix", "completion", "help", "version", "explain", "star", "unstar",
|
|
28
|
+
"deprecate", "sbom", "query", "explore", "pack", "publish", "link", "unlink", "prune", "rebuild",
|
|
29
|
+
"logout", "login", "adduser", "owner", "set", "init", "licenses",
|
|
30
|
+
"freeze", "check", "uninstall", "debug", "hash", "inspect", "index", "tree", "lock", "venv", "export", "python", "self",
|
|
31
|
+
]);
|
|
32
|
+
function findAction(args, protectedCommands) {
|
|
33
|
+
let firstPositional;
|
|
34
|
+
for (const arg of args) {
|
|
35
|
+
if (arg.startsWith("-")) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (firstPositional === undefined) {
|
|
39
|
+
firstPositional = arg;
|
|
40
|
+
}
|
|
41
|
+
if (protectedCommands.has(arg) || READ_ONLY_VERBS.has(arg)) {
|
|
42
|
+
return arg;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return firstPositional ?? "";
|
|
46
|
+
}
|
|
12
47
|
// pip3, pip3.12, python3, python3.11 are the canonical install commands on
|
|
13
48
|
// macOS/Homebrew and many Linux distros (see BINARY_FALLBACKS in
|
|
14
49
|
// resolve-real-binary.ts). The classifier keys on the base name, so a
|
|
@@ -61,8 +96,8 @@ export function classifyPackageManagerInvocation(rawManager, args) {
|
|
|
61
96
|
return classifyByCommand(manager, args, "cargo", cargoProtected, "Cargo command can fetch crates", "rust");
|
|
62
97
|
}
|
|
63
98
|
function classifyByCommand(manager, args, realBinaryName, protectedCommands, protectedReason, ecosystem) {
|
|
64
|
-
const action =
|
|
65
|
-
if (protectedCommands.has(action) ||
|
|
99
|
+
const action = findAction(args, protectedCommands);
|
|
100
|
+
if (protectedCommands.has(action) || initFetchesPackage(action, args, protectedCommands)) {
|
|
66
101
|
return {
|
|
67
102
|
manager,
|
|
68
103
|
ecosystem,
|
|
@@ -96,7 +131,7 @@ function classifyUv(args) {
|
|
|
96
131
|
// fetch-and-execute path that the bare `run` verb otherwise waves through.
|
|
97
132
|
return protectedClassification("uv", args, "uv", "uv run --with fetches and runs a package");
|
|
98
133
|
}
|
|
99
|
-
if (uvProtected.has(action)
|
|
134
|
+
if (uvProtected.has(action)) {
|
|
100
135
|
return protectedClassification("uv", args, "uv", "uv install/fetch command");
|
|
101
136
|
}
|
|
102
137
|
return {
|
|
@@ -148,6 +183,3 @@ function initFetchesPackage(action, args, protectedCommands) {
|
|
|
148
183
|
export function uvRunFetchesPackage(args) {
|
|
149
184
|
return args.some((a) => a === "--with" || a === "--with-editable" || a === "--with-requirements" || a.startsWith("--with="));
|
|
150
185
|
}
|
|
151
|
-
function containsFetchSpec(args) {
|
|
152
|
-
return args.some((arg) => /^https?:\/\//.test(arg) || /^git\+https?:\/\//.test(arg) || /\.t(ar\.)?gz(?:$|[#?])/.test(arg));
|
|
153
|
-
}
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@westbayberry/dg",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@westbayberry/dg",
|
|
9
|
-
"version": "2.3.
|
|
9
|
+
"version": "2.3.2",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"chalk": "4.1.2",
|
package/package.json
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@westbayberry/dg",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "Dependency Guardian supply-chain firewall CLI",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"supply-chain-security",
|
|
7
|
+
"supply-chain",
|
|
8
|
+
"npm-security",
|
|
9
|
+
"pypi-security",
|
|
10
|
+
"malware-scanner",
|
|
11
|
+
"malware-detection",
|
|
12
|
+
"dependency-scanner",
|
|
13
|
+
"npm-audit-alternative",
|
|
14
|
+
"install-time-security",
|
|
15
|
+
"typosquatting",
|
|
16
|
+
"postinstall",
|
|
17
|
+
"ai-agent-security",
|
|
18
|
+
"claude-code",
|
|
19
|
+
"security"
|
|
20
|
+
],
|
|
5
21
|
"repository": {
|
|
6
22
|
"type": "git",
|
|
7
23
|
"url": "git+https://github.com/WestBayBerry/DG_CLI.git"
|
|
@@ -9,7 +25,7 @@
|
|
|
9
25
|
"bugs": {
|
|
10
26
|
"url": "https://github.com/WestBayBerry/DG_CLI/issues"
|
|
11
27
|
},
|
|
12
|
-
"homepage": "https://
|
|
28
|
+
"homepage": "https://westbayberry.com",
|
|
13
29
|
"type": "module",
|
|
14
30
|
"bin": {
|
|
15
31
|
"dg": "./dist/bin/dg.js"
|