@westbayberry/dg 2.0.1 → 2.0.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/dist/api/analyze.js +8 -0
- package/dist/bin/dg.js +7 -2
- package/dist/install-ui/LiveInstall.js +3 -3
- package/dist/proxy/server.js +3 -1
- package/dist/setup/plan.js +89 -5
- package/package.json +1 -1
package/dist/api/analyze.js
CHANGED
|
@@ -182,6 +182,14 @@ function resolveApiBaseUrl(env) {
|
|
|
182
182
|
function resolveToken(env) {
|
|
183
183
|
return envAuthToken(env) ?? readAuthStateSafe(env)?.token;
|
|
184
184
|
}
|
|
185
|
+
export function identityHeaders(env) {
|
|
186
|
+
const headers = { "X-Device-Id": getOrCreateDeviceId(env) };
|
|
187
|
+
const token = resolveToken(env);
|
|
188
|
+
if (token) {
|
|
189
|
+
headers.Authorization = `Bearer ${token}`;
|
|
190
|
+
}
|
|
191
|
+
return headers;
|
|
192
|
+
}
|
|
185
193
|
function readAuthStateSafe(env) {
|
|
186
194
|
try {
|
|
187
195
|
return readAuthState(env);
|
package/dist/bin/dg.js
CHANGED
|
@@ -66,6 +66,11 @@ else {
|
|
|
66
66
|
// The auth flows (browser login, paid-verify gate, deep audit upload) already
|
|
67
67
|
// tell the user exactly what to do; the throttled nudges would just be noise.
|
|
68
68
|
if (!deviceLogin.handled && !verifyPackage.handled && !audit.handled) {
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
try {
|
|
70
|
+
const { maybeShowNudges } = await import("../runtime/nudges.js");
|
|
71
|
+
maybeShowNudges(args);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// dg deleted its own files mid-run (uninstall of itself); nudges are cosmetic.
|
|
75
|
+
}
|
|
71
76
|
}
|
|
@@ -9,12 +9,12 @@ function rule() {
|
|
|
9
9
|
return "─".repeat(width);
|
|
10
10
|
}
|
|
11
11
|
export const LiveInstall = ({ view }) => {
|
|
12
|
+
if (view.phase === "scanning") {
|
|
13
|
+
return (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: "cyan", children: _jsx(InkSpinner, { type: "dots" }) }), _jsxs(Text, { children: [" ", view.total === 0 ? "DG starting protection…" : `DG verifying ${packageCount(view.total)}…`] }), view.current ? _jsxs(Text, { dimColor: true, children: [" ", view.current] }) : null] }));
|
|
14
|
+
}
|
|
12
15
|
if (view.total === 0 && !view.blocked) {
|
|
13
16
|
return null;
|
|
14
17
|
}
|
|
15
|
-
if (view.phase === "scanning") {
|
|
16
|
-
return (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { color: "cyan", children: _jsx(InkSpinner, { type: "dots" }) }), _jsxs(Text, { children: [" DG verifying ", packageCount(view.total), "\u2026"] }), view.current ? _jsxs(Text, { dimColor: true, children: [" ", view.current] }) : null] }));
|
|
17
|
-
}
|
|
18
18
|
if (view.blocked) {
|
|
19
19
|
const blocked = view.blocked;
|
|
20
20
|
return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "gray", children: rule() }), blocked.kind === "blocked" ? (_jsxs(Text, { color: "red", children: ["\u2718 DG blocked install \u2014 ", blocked.headline] })) : (_jsxs(Text, { color: "yellow", children: ["? DG could not verify ", blocked.packageName, " \u2014 ", blocked.headline] })), _jsxs(Text, { children: [" ", blocked.packageName, " ", blocked.reason] }), blocked.override ? _jsxs(Text, { dimColor: true, children: [" ", "Override: ", blocked.override] }) : null, blocked.nextStep ? _jsxs(Text, { dimColor: true, children: [" ", "Next: ", blocked.nextStep] }) : null] }));
|
package/dist/proxy/server.js
CHANGED
|
@@ -15,6 +15,7 @@ import { artifactDisplayName, artifactUrlHash, extractRegistryMetadataIdentities
|
|
|
15
15
|
import { authorityFor, connectViaUpstreamProxy, selectUpstreamProxy } from "./upstream-proxy.js";
|
|
16
16
|
import { redactSecrets } from "../launcher/output-redaction.js";
|
|
17
17
|
import { envAuthToken } from "../auth/env-token.js";
|
|
18
|
+
import { identityHeaders } from "../api/analyze.js";
|
|
18
19
|
export async function startProductionHttpProxy(options) {
|
|
19
20
|
const ca = createEphemeralCertificateAuthority(options.session.files.ca);
|
|
20
21
|
const state = {
|
|
@@ -629,7 +630,8 @@ async function lookupVerdict(options, target, sha256, upstream, identity) {
|
|
|
629
630
|
const response = await fetch(`${options.apiBaseUrl}/v1/install-verdict`, {
|
|
630
631
|
method: "POST",
|
|
631
632
|
headers: {
|
|
632
|
-
"Content-Type": "application/json"
|
|
633
|
+
"Content-Type": "application/json",
|
|
634
|
+
...identityHeaders(options.env)
|
|
633
635
|
},
|
|
634
636
|
body: JSON.stringify({
|
|
635
637
|
manager: options.classification.manager,
|
package/dist/setup/plan.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { accessSync, constants, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { accessSync, constants, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { basename, delimiter, dirname, join, resolve } from "node:path";
|
|
3
3
|
import { chmodSync } from "node:fs";
|
|
4
4
|
import { createTheme } from "../presentation/theme.js";
|
|
@@ -21,6 +21,9 @@ const LEGACY_RC_MARKERS = [
|
|
|
21
21
|
{ begin: "# >>> dg-managed >>>", end: "# <<< dg-managed <<<" }
|
|
22
22
|
];
|
|
23
23
|
const LEGACY_RC_CANDIDATES = [".zshrc", ".bashrc", ".bash_profile", ".profile", join(".config", "fish", "config.fish")];
|
|
24
|
+
const LEGACY_PYTHON_HOOK_PY = "dg_pip_hook.py";
|
|
25
|
+
const LEGACY_PYTHON_HOOK_PTH = "dg_pip_hook.pth";
|
|
26
|
+
const LEGACY_PYTHON_HOOK_MARKER = "Dependency Guardian pip-install interceptor";
|
|
24
27
|
export const SETUP_UNINSTALL_LOCK = "setup-uninstall";
|
|
25
28
|
export const SETUP_UNINSTALL_LOCK_STALE_MS = 30 * 60 * 1000;
|
|
26
29
|
export const STALE_SESSION_OLDER_THAN_MS = 24 * 60 * 60 * 1000;
|
|
@@ -38,6 +41,7 @@ const DOCTOR_GROUP_BY_NAME = {
|
|
|
38
41
|
telemetry: "setup",
|
|
39
42
|
shims: "setup",
|
|
40
43
|
"shell-rc": "setup",
|
|
44
|
+
"python-hook-drift": "setup",
|
|
41
45
|
path: "setup",
|
|
42
46
|
"stale-sessions": "setup",
|
|
43
47
|
service: "setup",
|
|
@@ -53,6 +57,7 @@ const DOCTOR_FIX_BY_NAME = {
|
|
|
53
57
|
telemetry: "fix ~/.dg config",
|
|
54
58
|
shims: "dg setup",
|
|
55
59
|
"shell-rc": "dg setup",
|
|
60
|
+
"python-hook-drift": "dg uninstall, or re-run dg setup, to remove the stale pip hook",
|
|
56
61
|
path: "reload your shell after setup",
|
|
57
62
|
"stale-sessions": "clears on the next protected run",
|
|
58
63
|
auth: "dg login"
|
|
@@ -142,6 +147,7 @@ export function applySetupPlan(plan, now = new Date()) {
|
|
|
142
147
|
});
|
|
143
148
|
writeFileSync(plan.rcPath, withRcBlock(readText(plan.rcPath), plan), "utf8");
|
|
144
149
|
entries.push(cleanupEntry("rc", plan.rcPath, "mode1", now, RC_SENTINEL));
|
|
150
|
+
sweepLegacyPythonHooks(plan.paths.homeDir, [], []);
|
|
145
151
|
const registry = withRegistryLock(plan.paths, () => {
|
|
146
152
|
const merged = mergeRegistry(readRegistry(plan.paths).registry, entries);
|
|
147
153
|
writeRegistry(plan.paths, merged);
|
|
@@ -196,6 +202,7 @@ export function uninstallSetup(options) {
|
|
|
196
202
|
}
|
|
197
203
|
}
|
|
198
204
|
sweepLegacyRcBlocks(paths.homeDir, removed, warnings);
|
|
205
|
+
sweepLegacyPythonHooks(paths.homeDir, removed, warnings);
|
|
199
206
|
if (!options.all && !registryRead.malformed && options.keepConfig) {
|
|
200
207
|
writeRegistryWithLock(paths, {
|
|
201
208
|
version: 1,
|
|
@@ -271,6 +278,14 @@ export function doctorReport(options = {}) {
|
|
|
271
278
|
status: rcEntries.length > 0 && missingRc.length === 0 ? "pass" : "warn",
|
|
272
279
|
message: rcEntries.length === 0 ? "No dg shell rc block is registered" : `Registered shell rc blocks: ${rcEntries.length}`
|
|
273
280
|
});
|
|
281
|
+
const staleHookSites = legacyPythonHookSites(paths.homeDir);
|
|
282
|
+
checks.push({
|
|
283
|
+
name: "python-hook-drift",
|
|
284
|
+
status: staleHookSites.length === 0 ? "pass" : "warn",
|
|
285
|
+
message: staleHookSites.length === 0
|
|
286
|
+
? "No legacy dg pip hooks in user site-packages"
|
|
287
|
+
: `Legacy dg pip hooks break pip in: ${staleHookSites.join(", ")}`
|
|
288
|
+
});
|
|
274
289
|
checks.push(pathPrecedenceCheck(env, shimDir));
|
|
275
290
|
checks.push({
|
|
276
291
|
name: "stale-sessions",
|
|
@@ -453,11 +468,31 @@ function reloadInstructions(shell) {
|
|
|
453
468
|
}
|
|
454
469
|
return ["start a new shell or run: source ~/.zshrc", "clear cached command paths with: rehash"];
|
|
455
470
|
}
|
|
456
|
-
//
|
|
457
|
-
//
|
|
458
|
-
//
|
|
471
|
+
// The baked absolute dg path stops a PATH-shadowing dg from hijacking the shim;
|
|
472
|
+
// the fallbacks make it fail open — a removed or moved dg (uninstall, mid-upgrade)
|
|
473
|
+
// runs the real manager instead of bricking it.
|
|
459
474
|
export function shimSource(command) {
|
|
460
|
-
|
|
475
|
+
const dg = escapeDoubleQuotedSh(dgEntrypoint());
|
|
476
|
+
const nonce = '"${DG_SHIM_ACTIVE:+$DG_SHIM_ACTIVE,}' + `${command}:$$"`;
|
|
477
|
+
return [
|
|
478
|
+
"#!/bin/sh",
|
|
479
|
+
`# ${SHIM_SENTINEL}`,
|
|
480
|
+
`if [ -x "${dg}" ]; then`,
|
|
481
|
+
` DG_SHIM_ACTIVE=${nonce} exec "${dg}" ${command} "$@"`,
|
|
482
|
+
"fi",
|
|
483
|
+
`dg_path=$(printf '%s' "$PATH" | awk -v RS=':' -v ORS=':' '$0 != ENVIRON["HOME"] "/.dg/shims"' | sed 's/:$//')`,
|
|
484
|
+
`dg_bin=$(PATH="$dg_path" command -v dg 2>/dev/null)`,
|
|
485
|
+
`if [ -n "$dg_bin" ]; then`,
|
|
486
|
+
` DG_SHIM_ACTIVE=${nonce} exec "$dg_bin" ${command} "$@"`,
|
|
487
|
+
"fi",
|
|
488
|
+
`real_bin=$(PATH="$dg_path" command -v ${command} 2>/dev/null)`,
|
|
489
|
+
`if [ -n "$real_bin" ]; then`,
|
|
490
|
+
` exec "$real_bin" "$@"`,
|
|
491
|
+
"fi",
|
|
492
|
+
`echo "dg: protection unavailable and no real ${command} found on PATH" >&2`,
|
|
493
|
+
"exit 127",
|
|
494
|
+
""
|
|
495
|
+
].join("\n");
|
|
461
496
|
}
|
|
462
497
|
function escapeDoubleQuotedSh(value) {
|
|
463
498
|
return value.replace(/[\\"$`]/g, "\\$&");
|
|
@@ -517,6 +552,55 @@ function sweepLegacyRcBlocks(homeDir, removed, warnings) {
|
|
|
517
552
|
}
|
|
518
553
|
}
|
|
519
554
|
}
|
|
555
|
+
export function legacyPythonHookSites(homeDir) {
|
|
556
|
+
return candidateSitePackagesDirs(homeDir).filter((dir) => existsSync(join(dir, LEGACY_PYTHON_HOOK_PTH)) || readText(join(dir, LEGACY_PYTHON_HOOK_PY)).includes(LEGACY_PYTHON_HOOK_MARKER));
|
|
557
|
+
}
|
|
558
|
+
function sweepLegacyPythonHooks(homeDir, removed, warnings) {
|
|
559
|
+
for (const dir of candidateSitePackagesDirs(homeDir)) {
|
|
560
|
+
const pyPath = join(dir, LEGACY_PYTHON_HOOK_PY);
|
|
561
|
+
const pthPath = join(dir, LEGACY_PYTHON_HOOK_PTH);
|
|
562
|
+
const pyIsHook = readText(pyPath).includes(LEGACY_PYTHON_HOOK_MARKER);
|
|
563
|
+
const pthPresent = existsSync(pthPath);
|
|
564
|
+
if (!pyIsHook && !pthPresent) {
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
if (pthPresent) {
|
|
568
|
+
removePythonHookFile(pthPath, removed, warnings);
|
|
569
|
+
}
|
|
570
|
+
if (pyIsHook) {
|
|
571
|
+
removePythonHookFile(pyPath, removed, warnings);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
function removePythonHookFile(path, removed, warnings) {
|
|
576
|
+
try {
|
|
577
|
+
rmSync(path, { force: true });
|
|
578
|
+
removed.push(`${path} (legacy dg pip hook)`);
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
warnings.push(`could not remove legacy dg pip hook ${path}: ${error instanceof Error ? error.message : "remove error"}`);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
function candidateSitePackagesDirs(homeDir) {
|
|
585
|
+
const dirs = [];
|
|
586
|
+
for (const version of safeReaddir(join(homeDir, "Library", "Python"))) {
|
|
587
|
+
dirs.push(join(homeDir, "Library", "Python", version, "lib", "python", "site-packages"));
|
|
588
|
+
}
|
|
589
|
+
for (const entry of safeReaddir(join(homeDir, ".local", "lib"))) {
|
|
590
|
+
if (entry.startsWith("python")) {
|
|
591
|
+
dirs.push(join(homeDir, ".local", "lib", entry, "site-packages"));
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return dirs;
|
|
595
|
+
}
|
|
596
|
+
function safeReaddir(dir) {
|
|
597
|
+
try {
|
|
598
|
+
return readdirSync(dir);
|
|
599
|
+
}
|
|
600
|
+
catch {
|
|
601
|
+
return [];
|
|
602
|
+
}
|
|
603
|
+
}
|
|
520
604
|
export function cleanupEntry(kind, path, mode, now, sentinel) {
|
|
521
605
|
return {
|
|
522
606
|
kind,
|