@westbayberry/dg 2.3.2 → 2.3.4
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 +95 -208
- package/dist/agents/copilot-cli.js +7 -2
- package/dist/agents/persistence.js +83 -8
- package/dist/agents/registry.js +5 -2
- package/dist/agents/routing.js +31 -14
- package/dist/api/analyze.js +138 -41
- package/dist/audit/deep.js +2 -1
- package/dist/audit/detectors.js +23 -2
- package/dist/audit/rules.js +4 -1
- package/dist/audit-ui/export.js +5 -4
- package/dist/auth/device-login.js +33 -11
- package/dist/auth/login-app.js +17 -12
- package/dist/auth/store.js +65 -9
- package/dist/bin/dg.js +1 -1
- package/dist/commands/audit.js +12 -20
- package/dist/commands/{guard-commit.js → commit-guard.js} +12 -12
- package/dist/commands/cooldown.js +2 -1
- package/dist/commands/decisions.js +8 -7
- package/dist/commands/help.js +11 -3
- package/dist/commands/licenses.js +17 -8
- package/dist/commands/login.js +58 -21
- package/dist/commands/router.js +12 -2
- package/dist/commands/sbom.js +20 -5
- package/dist/commands/scan.js +5 -4
- package/dist/commands/service.js +1 -1
- package/dist/commands/setup.js +12 -12
- package/dist/commands/status.js +4 -4
- package/dist/commands/uninstall.js +1 -1
- package/dist/commands/update.js +20 -0
- package/dist/commands/verify.js +4 -4
- package/dist/config/settings.js +81 -12
- package/dist/export-ui/ExportDialog.js +1 -1
- package/dist/gate/cooldown-request.js +22 -0
- package/dist/gate/egress-guard.js +185 -0
- package/dist/gate/host-match.js +48 -0
- package/dist/gate/verdict-core.js +101 -0
- package/dist/gate/verdict-fetch.js +214 -0
- package/dist/launcher/agent-check.js +1096 -42
- package/dist/launcher/agent-hook-exec.js +8 -1
- package/dist/launcher/agent-hook-io.js +5 -3
- package/dist/launcher/classify.js +110 -26
- package/dist/launcher/env.js +84 -18
- package/dist/launcher/install-preflight.js +26 -3
- package/dist/launcher/live-install.js +4 -2
- package/dist/launcher/manifest-screen.js +88 -7
- package/dist/launcher/output-redaction.js +153 -15
- package/dist/launcher/preflight-prompt.js +1 -1
- package/dist/launcher/resolve-real-binary.js +8 -3
- package/dist/launcher/run.js +237 -40
- package/dist/origin/artifact-cache.js +55 -0
- package/dist/origin/cargo-config.js +12 -0
- package/dist/origin/gate-mode.js +7 -0
- package/dist/origin/platform-paths.js +14 -0
- package/dist/origin/precedence.js +345 -0
- package/dist/origin/server.js +611 -0
- package/dist/origin/spike-worker.js +35 -0
- package/dist/origin/worker.js +116 -0
- package/dist/presentation/package-page.js +21 -1
- package/dist/presentation/safe-version.js +11 -0
- package/dist/presentation/sarif.js +109 -0
- package/dist/project/dgfile.js +54 -17
- package/dist/project/override-trust.js +0 -0
- package/dist/proxy/enforcement.js +27 -7
- package/dist/proxy/metadata-map.js +33 -7
- package/dist/proxy/preverified.js +0 -3
- package/dist/proxy/server.js +104 -446
- package/dist/proxy/upstream-proxy.js +23 -4
- package/dist/proxy/worker.js +3 -2
- package/dist/publish-set/collect.js +6 -0
- package/dist/runtime/fatal.js +2 -1
- package/dist/runtime/first-run.js +5 -1
- package/dist/runtime/node-version.js +51 -0
- package/dist/runtime/nudges.js +27 -1
- package/dist/sbom/cyclonedx.js +120 -33
- package/dist/sbom/graph.js +236 -0
- package/dist/sbom/spdx.js +53 -0
- package/dist/sbom-ui/SbomApp.js +5 -3
- package/dist/sbom-ui/inventory.js +8 -10
- package/dist/scan/analyze-worker.js +3 -1
- package/dist/scan/collect.js +38 -8
- package/dist/scan/command.js +77 -27
- package/dist/scan/discovery.js +18 -7
- package/dist/scan/installed.js +202 -0
- package/dist/scan/manifest-coverage.js +128 -0
- package/dist/scan/render.js +125 -40
- package/dist/scan/scanner-report.js +92 -22
- package/dist/scan/staged.js +68 -15
- package/dist/scan/types.js +7 -1
- package/dist/scan-ui/LegacyApp.js +3 -3
- package/dist/scan-ui/components/InteractiveResultsView.js +17 -14
- package/dist/scan-ui/hooks/useScan.js +37 -11
- package/dist/scan-ui/launch.js +2 -2
- package/dist/scan-ui/logo.js +6 -2
- package/dist/scan-ui/shims.js +6 -6
- package/dist/scripts/gate.js +53 -35
- package/dist/security/csv.js +9 -0
- package/dist/security/sanitize.js +11 -2
- package/dist/service/state.js +120 -37
- package/dist/service/trust-refresh.js +12 -1
- package/dist/service/trust-store.js +36 -5
- package/dist/service/worker.js +5 -7
- package/dist/setup/git-hook.js +2 -2
- package/dist/setup/install-method.js +36 -0
- package/dist/setup/plan.js +119 -57
- package/dist/setup/uninstall-standalone.js +7 -2
- package/dist/setup-ui/gate.js +1 -8
- package/dist/setup-ui/offer.js +14 -7
- package/dist/setup-ui/selector.js +1 -1
- package/dist/setup-ui/wizard.js +73 -78
- package/dist/standalone/uninstall.mjs +1114 -386
- package/dist/state/cleanup-registry.js +14 -3
- package/dist/state/cooldown-held.js +36 -13
- package/dist/state/locks.js +239 -34
- package/dist/state/sessions.js +2 -2
- package/dist/util/sh-escape.js +6 -0
- package/dist/util/tty-prompt.js +13 -4
- package/dist/verify/local.js +60 -10
- package/dist/verify/package-check.js +35 -49
- package/dist/verify/preflight.js +59 -33
- package/dist/verify/render.js +11 -12
- package/npm-shrinkwrap.json +714 -213
- package/package.json +4 -2
|
@@ -3,6 +3,7 @@ import { resolve } from "node:path";
|
|
|
3
3
|
import { analyzePackages, AnalyzeError } from "../api/analyze.js";
|
|
4
4
|
import { createTheme } from "../presentation/theme.js";
|
|
5
5
|
import { provenanceLabel, provenanceDowngradeLine } from "../presentation/provenance.js";
|
|
6
|
+
import { safeVersionLine } from "../presentation/safe-version.js";
|
|
6
7
|
import { packagePageUrl } from "../presentation/package-page.js";
|
|
7
8
|
import { resolvePresentation } from "../presentation/mode.js";
|
|
8
9
|
import { isRemotePackageSpec, isSupportedLockfilePath } from "./preflight.js";
|
|
@@ -27,17 +28,23 @@ function parseSpec(target) {
|
|
|
27
28
|
if (!rest) {
|
|
28
29
|
return { error: `missing package name. ${DEEP_VERIFY_HINT}` };
|
|
29
30
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
if (ecosystem === "npm") {
|
|
32
|
+
const at = rest.lastIndexOf("@");
|
|
33
|
+
if (at <= 0) {
|
|
34
|
+
return { ecosystem, name: rest, version: null };
|
|
35
|
+
}
|
|
36
|
+
return { ecosystem, name: rest.slice(0, at), version: rest.slice(at + 1).trim() || null };
|
|
37
|
+
}
|
|
38
|
+
// PyPI names contain no '@' or '=='; accept either the registry-style '@' the
|
|
39
|
+
// help advertises or the PEP 508 '==' pin, whichever comes first.
|
|
40
|
+
const eq = rest.indexOf("==");
|
|
41
|
+
const at = rest.indexOf("@");
|
|
42
|
+
const cut = eq >= 0 && (at < 0 || eq < at) ? eq : at;
|
|
43
|
+
if (cut <= 0) {
|
|
36
44
|
return { ecosystem, name: rest, version: null };
|
|
37
45
|
}
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
return { ecosystem, name, version };
|
|
46
|
+
const version = rest.slice(cut + (cut === eq ? 2 : 1)).trim() || null;
|
|
47
|
+
return { ecosystem, name: rest.slice(0, cut), version };
|
|
41
48
|
}
|
|
42
49
|
export async function resolveLatest(ecosystem, name, fetchImpl) {
|
|
43
50
|
try {
|
|
@@ -77,8 +84,7 @@ function provenanceLines(version, result, theme, withPredicate) {
|
|
|
77
84
|
function reasonGlyph(action, theme) {
|
|
78
85
|
return action === "block" ? theme.paint("block", "✘") : action === "warn" ? theme.paint("warn", "⚠") : theme.paint("muted", "·");
|
|
79
86
|
}
|
|
80
|
-
function renderResult(spec, version, result, theme, verbose) {
|
|
81
|
-
const action = result.action ?? "pass";
|
|
87
|
+
function renderResult(spec, version, result, action, theme, verbose) {
|
|
82
88
|
const badge = theme.badge(action);
|
|
83
89
|
const lines = [`${badge} ${result.name}@${version} (${spec.ecosystem}) ${theme.paint("muted", `score ${result.score}`)}`];
|
|
84
90
|
lines.push(...provenanceLines(version, result, theme, verbose));
|
|
@@ -100,40 +106,13 @@ function renderResult(spec, version, result, theme, verbose) {
|
|
|
100
106
|
if (result.recommendation) {
|
|
101
107
|
lines.push(` ${theme.paint("muted", result.recommendation)}`);
|
|
102
108
|
}
|
|
109
|
+
lines.push(...safeVersionLine(result, theme, spec.ecosystem));
|
|
103
110
|
const page = packagePageUrl(spec.ecosystem, result.name);
|
|
104
111
|
if (page) {
|
|
105
112
|
lines.push(` ${theme.paint("muted", `→ ${page}`)}`);
|
|
106
113
|
}
|
|
107
114
|
return `${lines.join("\n")}\n`;
|
|
108
115
|
}
|
|
109
|
-
const FREE_REASON_CAP = 3;
|
|
110
|
-
const FREE_SCANS_FOOTER_THRESHOLD = 5_000;
|
|
111
|
-
function renderFreeResult(spec, version, result, theme, freeScansRemaining) {
|
|
112
|
-
const action = result.action ?? "pass";
|
|
113
|
-
const lines = [`${theme.badge(action)} ${result.name}@${version} (${spec.ecosystem})`];
|
|
114
|
-
lines.push(...provenanceLines(version, result, theme, false));
|
|
115
|
-
if (action === "pass") {
|
|
116
|
-
lines.push(` ${theme.paint("muted", result.reasons[0] ?? "no risk signals")}`);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
const reasons = result.reasons.slice(0, FREE_REASON_CAP);
|
|
120
|
-
for (const reason of reasons) {
|
|
121
|
-
lines.push(` ${reasonGlyph(action, theme)} ${reason}`);
|
|
122
|
-
}
|
|
123
|
-
if (result.reasons.length > reasons.length) {
|
|
124
|
-
lines.push(` ${theme.paint("muted", `… ${result.reasons.length - reasons.length} more — sign in to see all:`)} ${theme.paint("accent", "dg login")}`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
const page = packagePageUrl(spec.ecosystem, result.name);
|
|
128
|
-
if (page) {
|
|
129
|
-
lines.push(` ${theme.paint("muted", `→ ${page}`)}`);
|
|
130
|
-
}
|
|
131
|
-
lines.push(` ${theme.paint("muted", "full findings, license, JSON output:")} ${theme.paint("accent", "dg login")}`);
|
|
132
|
-
if (freeScansRemaining !== undefined && freeScansRemaining < FREE_SCANS_FOOTER_THRESHOLD) {
|
|
133
|
-
lines.push(` ${theme.paint("muted", `${freeScansRemaining.toLocaleString()} free package checks left this month`)}`);
|
|
134
|
-
}
|
|
135
|
-
return `${lines.join("\n")}\n`;
|
|
136
|
-
}
|
|
137
116
|
function encodeNpmPackagePath(name) {
|
|
138
117
|
return name.split("/").map((segment) => encodeURIComponent(segment)).join("%2f");
|
|
139
118
|
}
|
|
@@ -155,12 +134,12 @@ export async function runPackageCheck(target, io = {}, options = {}) {
|
|
|
155
134
|
const accent = (text) => theme.paint("accent", text);
|
|
156
135
|
const muted = (text) => theme.paint("muted", text);
|
|
157
136
|
const authenticated = authStatus(io.env).authenticated;
|
|
158
|
-
if (!authenticated
|
|
137
|
+
if (!authenticated) {
|
|
159
138
|
return {
|
|
160
139
|
exitCode: EXIT_UNAVAILABLE,
|
|
161
140
|
stdout: "",
|
|
162
|
-
stderr: `\n ${theme.paint("warn", "⚠")} ${muted("
|
|
163
|
-
` ${
|
|
141
|
+
stderr: `\n ${theme.paint("warn", "⚠")} ${muted("Sign in to run dg verify.")}\n` +
|
|
142
|
+
` ${muted("Run")} ${accent("dg login")}\n\n`
|
|
164
143
|
};
|
|
165
144
|
}
|
|
166
145
|
const parsed = parseSpec(target);
|
|
@@ -186,7 +165,6 @@ export async function runPackageCheck(target, io = {}, options = {}) {
|
|
|
186
165
|
}
|
|
187
166
|
}
|
|
188
167
|
let result;
|
|
189
|
-
let freeScansRemaining;
|
|
190
168
|
try {
|
|
191
169
|
const response = await analyzePackages([{ name: parsed.name, version }], {
|
|
192
170
|
ecosystem: parsed.ecosystem,
|
|
@@ -194,7 +172,6 @@ export async function runPackageCheck(target, io = {}, options = {}) {
|
|
|
194
172
|
...(io.env ? { env: io.env } : {})
|
|
195
173
|
});
|
|
196
174
|
result = response.packages.find((entry) => entry.name === parsed.name) ?? response.packages[0];
|
|
197
|
-
freeScansRemaining = response.freeScansRemaining;
|
|
198
175
|
}
|
|
199
176
|
catch (error) {
|
|
200
177
|
if (error instanceof AnalyzeError && error.code === "quota_exceeded") {
|
|
@@ -225,11 +202,10 @@ export async function runPackageCheck(target, io = {}, options = {}) {
|
|
|
225
202
|
reasons: result.reasons,
|
|
226
203
|
findings: result.findings,
|
|
227
204
|
...(result.provenance ? { provenance: result.provenance } : {}),
|
|
228
|
-
...(result.recommendation ? { recommendation: result.recommendation } : {})
|
|
205
|
+
...(result.recommendation ? { recommendation: result.recommendation } : {}),
|
|
206
|
+
...(result.safeVersion ? { safeVersion: result.safeVersion } : {})
|
|
229
207
|
}, null, 2)}\n`
|
|
230
|
-
:
|
|
231
|
-
? renderResult(parsed, version, result, theme, options.verbose ?? false)
|
|
232
|
-
: renderFreeResult(parsed, version, result, theme, freeScansRemaining);
|
|
208
|
+
: renderResult(parsed, version, result, action, theme, options.verbose ?? false);
|
|
233
209
|
if (options.outputPath) {
|
|
234
210
|
try {
|
|
235
211
|
writeFileSync(resolve(options.outputPath), rendered, "utf8");
|
|
@@ -262,6 +238,7 @@ export async function maybeVerifyPackage(args) {
|
|
|
262
238
|
let outputPath = null;
|
|
263
239
|
let outputFlagMissingPath;
|
|
264
240
|
let target;
|
|
241
|
+
let extraPositional;
|
|
265
242
|
let unknownFlag;
|
|
266
243
|
for (let index = 0; index < rest.length; index += 1) {
|
|
267
244
|
const arg = rest[index];
|
|
@@ -290,8 +267,11 @@ export async function maybeVerifyPackage(args) {
|
|
|
290
267
|
else if (arg.startsWith("-")) {
|
|
291
268
|
unknownFlag = unknownFlag ?? arg;
|
|
292
269
|
}
|
|
270
|
+
else if (target === undefined) {
|
|
271
|
+
target = arg;
|
|
272
|
+
}
|
|
293
273
|
else {
|
|
294
|
-
|
|
274
|
+
extraPositional = extraPositional ?? arg;
|
|
295
275
|
}
|
|
296
276
|
}
|
|
297
277
|
if (!target) {
|
|
@@ -315,6 +295,12 @@ export async function maybeVerifyPackage(args) {
|
|
|
315
295
|
result: { exitCode: EXIT_USAGE_VERDICT, stdout: "", stderr: `dg verify: unknown option '${unknownFlag}'. Run 'dg verify --help'.\n` }
|
|
316
296
|
};
|
|
317
297
|
}
|
|
298
|
+
if (extraPositional) {
|
|
299
|
+
return {
|
|
300
|
+
handled: true,
|
|
301
|
+
result: { exitCode: EXIT_USAGE_VERDICT, stdout: "", stderr: `dg verify: unexpected argument '${extraPositional}' — verify accepts one target. Run 'dg verify --help'.\n` }
|
|
302
|
+
};
|
|
303
|
+
}
|
|
318
304
|
if (sarif) {
|
|
319
305
|
return {
|
|
320
306
|
handled: true,
|
package/dist/verify/preflight.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
|
-
import { basename, dirname, isAbsolute,
|
|
2
|
+
import { basename, dirname, isAbsolute, relative, resolve, sep } from "node:path";
|
|
3
3
|
import { loadUserConfig } from "../config/settings.js";
|
|
4
4
|
import { evaluatePackagePolicy, resolveEffectivePolicy } from "../policy/evaluate.js";
|
|
5
5
|
const LOCKFILE_NAMES = new Set([
|
|
@@ -124,6 +124,7 @@ export function parseLockfilePackages(targetPath) {
|
|
|
124
124
|
}
|
|
125
125
|
const context = createParseContext(fileName, absoluteTarget);
|
|
126
126
|
const observations = parseLockfile(text, context);
|
|
127
|
+
recordBlockedObservationSkips(observations, context);
|
|
127
128
|
return {
|
|
128
129
|
packages: observations
|
|
129
130
|
.filter((observation) => observation.verdict !== "block" && observation.identity.sourceKind === "lockfile")
|
|
@@ -132,24 +133,49 @@ export function parseLockfilePackages(targetPath) {
|
|
|
132
133
|
parseError: context.errors[0] ?? null
|
|
133
134
|
};
|
|
134
135
|
}
|
|
136
|
+
// A block-verdict entry is excluded from the scannable set; without an itemized
|
|
137
|
+
// skip it would vanish from the scan with no verdict and no notice. Record one for
|
|
138
|
+
// each blocked entry the per-parser path did not already skip.
|
|
139
|
+
function recordBlockedObservationSkips(observations, context) {
|
|
140
|
+
const alreadySkipped = new Set(context.skipped.map((skip) => skip.location));
|
|
141
|
+
for (const observation of observations) {
|
|
142
|
+
if (observation.verdict !== "block" || observation.identity.ecosystem === "unknown") {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const location = observation.identity.requested;
|
|
146
|
+
if (alreadySkipped.has(location)) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
alreadySkipped.add(location);
|
|
150
|
+
recordSkip(context, packageDisplayName(observation.identity), blockedObservationReason(observation.identity), location);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function blockedObservationReason(identity) {
|
|
154
|
+
if (identity.resolvedUrl && isUnsafeResolvedUrl(identity.resolvedUrl)) {
|
|
155
|
+
return "git";
|
|
156
|
+
}
|
|
157
|
+
// Off-registry hosts and URL fallbacks are labeled direct-url, as the per-parser skip path already does.
|
|
158
|
+
return "direct-url";
|
|
159
|
+
}
|
|
135
160
|
function createParseContext(fileName, filePath) {
|
|
136
161
|
return {
|
|
137
162
|
fileName,
|
|
138
163
|
filePath,
|
|
139
|
-
registryHosts: expectedRegistryHosts(fileName
|
|
164
|
+
registryHosts: expectedRegistryHosts(fileName),
|
|
140
165
|
skipped: [],
|
|
141
166
|
errors: []
|
|
142
167
|
};
|
|
143
168
|
}
|
|
144
|
-
|
|
169
|
+
// Only the canonical public registries are trusted here. A repo-local .npmrc is
|
|
170
|
+
// attacker-controlled in a cloned repo, so it must not widen this allowlist (a
|
|
171
|
+
// hostile repo must never relax screening); legit private registries are fronted
|
|
172
|
+
// by the proxy door at transport time, not blessed by a static preflight.
|
|
173
|
+
function expectedRegistryHosts(fileName) {
|
|
145
174
|
const hosts = new Set();
|
|
146
175
|
if (NPM_LOCKFILE_NAMES.has(fileName)) {
|
|
147
176
|
for (const host of DEFAULT_NPM_REGISTRY_HOSTS) {
|
|
148
177
|
hosts.add(host);
|
|
149
178
|
}
|
|
150
|
-
for (const host of npmrcRegistryHosts(filePath)) {
|
|
151
|
-
hosts.add(host);
|
|
152
|
-
}
|
|
153
179
|
}
|
|
154
180
|
else if (PYPI_LOCKFILE_NAMES.has(fileName)) {
|
|
155
181
|
for (const host of DEFAULT_PYPI_REGISTRY_HOSTS) {
|
|
@@ -158,32 +184,6 @@ function expectedRegistryHosts(fileName, filePath) {
|
|
|
158
184
|
}
|
|
159
185
|
return hosts;
|
|
160
186
|
}
|
|
161
|
-
function npmrcRegistryHosts(lockfilePath) {
|
|
162
|
-
if (!lockfilePath) {
|
|
163
|
-
return [];
|
|
164
|
-
}
|
|
165
|
-
let text;
|
|
166
|
-
try {
|
|
167
|
-
text = readLockfileText(join(dirname(lockfilePath), ".npmrc"));
|
|
168
|
-
}
|
|
169
|
-
catch {
|
|
170
|
-
return [];
|
|
171
|
-
}
|
|
172
|
-
const hosts = [];
|
|
173
|
-
for (const line of text.split(/\r?\n/u)) {
|
|
174
|
-
const value = /^\s*(?:@[^=\s:]+:)?registry\s*=\s*(\S+)/u.exec(line)?.[1];
|
|
175
|
-
if (!value) {
|
|
176
|
-
continue;
|
|
177
|
-
}
|
|
178
|
-
try {
|
|
179
|
-
hosts.push(new URL(value).hostname.toLowerCase());
|
|
180
|
-
}
|
|
181
|
-
catch {
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
return hosts;
|
|
186
|
-
}
|
|
187
187
|
function untrustedResolvedHost(value, context) {
|
|
188
188
|
if (context.registryHosts.size === 0 || !/^https?:\/\//iu.test(value)) {
|
|
189
189
|
return null;
|
|
@@ -478,8 +478,13 @@ function npmAliasVersion(version) {
|
|
|
478
478
|
}
|
|
479
479
|
return { name: spec.slice(0, at), version: spec.slice(at + 1) || null };
|
|
480
480
|
}
|
|
481
|
-
function parseYarnLock(
|
|
481
|
+
function parseYarnLock(rawText, context) {
|
|
482
482
|
const observations = [];
|
|
483
|
+
// The block-split lookahead matches a header terminated by ":\n"; a yarn.lock
|
|
484
|
+
// written with CRLF endings terminates headers with ":\r\n", so without this
|
|
485
|
+
// normalization the whole file collapses to one unparsed block and every
|
|
486
|
+
// package silently vanishes.
|
|
487
|
+
const text = rawText.replace(/\r\n/gu, "\n");
|
|
483
488
|
const blocks = text.split(/\n(?=(?:"?@?[^"\s].*"?):\n)/u);
|
|
484
489
|
for (const block of blocks) {
|
|
485
490
|
const lines = block.split(/\r?\n/u);
|
|
@@ -688,6 +693,27 @@ function collectRequirements(text, baseDir, state, context, observations) {
|
|
|
688
693
|
recordSkip(context, target, "local", line);
|
|
689
694
|
continue;
|
|
690
695
|
}
|
|
696
|
+
const indexOverride = /^(?:-i|--index-url|--extra-index-url|--find-links)[=\s]+(\S+)/u.exec(line);
|
|
697
|
+
if (indexOverride?.[1]) {
|
|
698
|
+
const indexUrl = indexOverride[1];
|
|
699
|
+
recordSkip(context, indexUrl, "index-override", line);
|
|
700
|
+
observations.push(packageObservation({
|
|
701
|
+
ecosystem: "pypi",
|
|
702
|
+
name: indexUrl,
|
|
703
|
+
version: null,
|
|
704
|
+
requested: line,
|
|
705
|
+
sourceKind: "lockfile-url-fallback",
|
|
706
|
+
resolvedUrl: indexUrl,
|
|
707
|
+
integrity: null,
|
|
708
|
+
license: null
|
|
709
|
+
}, "warn", {
|
|
710
|
+
id: "unverified-package-index",
|
|
711
|
+
title: "Unverified package index override",
|
|
712
|
+
message: `requirements resolve from '${indexUrl}', a non-default index that dg does not verify against the canonical registry`,
|
|
713
|
+
location: line
|
|
714
|
+
}));
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
691
717
|
if (line.startsWith("-")) {
|
|
692
718
|
continue;
|
|
693
719
|
}
|
package/dist/verify/render.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createTheme } from "../presentation/theme.js";
|
|
2
2
|
import { packagePageUrl } from "../presentation/package-page.js";
|
|
3
|
+
import { automationCategory, findingFingerprint, sarifLocationEntry } from "../presentation/sarif.js";
|
|
3
4
|
import { sanitizeDeep } from "../security/sanitize.js";
|
|
4
5
|
const STATUS_ROLE = {
|
|
5
6
|
pass: "pass",
|
|
@@ -160,30 +161,28 @@ export function renderVerifySarif(report) {
|
|
|
160
161
|
rules
|
|
161
162
|
}
|
|
162
163
|
},
|
|
164
|
+
automationDetails: {
|
|
165
|
+
id: automationCategory(report.target)
|
|
166
|
+
},
|
|
167
|
+
properties: {
|
|
168
|
+
dgVerdict: report.status
|
|
169
|
+
},
|
|
163
170
|
results: report.findings.map((finding) => ({
|
|
164
171
|
ruleId: finding.id,
|
|
165
172
|
level: finding.severity === "block" ? "error" : "warning",
|
|
166
173
|
message: {
|
|
167
174
|
text: finding.message
|
|
168
175
|
},
|
|
169
|
-
|
|
170
|
-
{
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
uri: artifactUri(finding.location)
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
]
|
|
176
|
+
partialFingerprints: {
|
|
177
|
+
dgFindingHash: findingFingerprint(`${finding.id} ${finding.location}`)
|
|
178
|
+
},
|
|
179
|
+
locations: [sarifLocationEntry(finding.location, report.target)]
|
|
178
180
|
}))
|
|
179
181
|
}
|
|
180
182
|
]
|
|
181
183
|
};
|
|
182
184
|
return `${JSON.stringify(sarif, null, 2)}\n`;
|
|
183
185
|
}
|
|
184
|
-
function artifactUri(location) {
|
|
185
|
-
return location.replace(/:\d+$/, "");
|
|
186
|
-
}
|
|
187
186
|
function uniqueFindings(findings) {
|
|
188
187
|
const seen = new Set();
|
|
189
188
|
const unique = [];
|