@westbayberry/dg 1.3.2 → 2.0.0

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.
Files changed (126) hide show
  1. package/LICENSE +1 -201
  2. package/NOTICE +1 -4
  3. package/README.md +293 -0
  4. package/dist/api/analyze.js +210 -0
  5. package/dist/audit/deep.js +180 -0
  6. package/dist/audit/detectors.js +247 -0
  7. package/dist/audit/events.js +41 -0
  8. package/dist/audit/rules.js +426 -0
  9. package/dist/audit-ui/AuditApp.js +39 -0
  10. package/dist/audit-ui/components/AuditHeader.js +24 -0
  11. package/dist/audit-ui/components/AuditResultsView.js +307 -0
  12. package/dist/audit-ui/components/DeepStatusRow.js +11 -0
  13. package/dist/audit-ui/export.js +85 -0
  14. package/dist/audit-ui/format.js +34 -0
  15. package/dist/audit-ui/launch.js +34 -0
  16. package/dist/auth/device-login.js +271 -0
  17. package/dist/auth/env-token.js +6 -0
  18. package/dist/auth/login-app.js +156 -0
  19. package/dist/auth/store.js +147 -0
  20. package/dist/bin/dg.js +71 -0
  21. package/dist/commands/audit.js +357 -0
  22. package/dist/commands/completion.js +116 -0
  23. package/dist/commands/config.js +99 -0
  24. package/dist/commands/doctor.js +39 -0
  25. package/dist/commands/explain.js +100 -0
  26. package/dist/commands/guard-commit.js +158 -0
  27. package/dist/commands/help.js +74 -0
  28. package/dist/commands/licenses.js +435 -0
  29. package/dist/commands/login.js +81 -0
  30. package/dist/commands/logout.js +37 -0
  31. package/dist/commands/router.js +98 -0
  32. package/dist/commands/scan.js +18 -0
  33. package/dist/commands/service.js +475 -0
  34. package/dist/commands/setup.js +302 -0
  35. package/dist/commands/status.js +115 -0
  36. package/dist/commands/suggest.js +35 -0
  37. package/dist/commands/types.js +4 -0
  38. package/dist/commands/unavailable.js +11 -0
  39. package/dist/commands/uninstall.js +111 -0
  40. package/dist/commands/update.js +210 -0
  41. package/dist/commands/verify.js +151 -0
  42. package/dist/commands/version.js +22 -0
  43. package/dist/commands/wrap.js +55 -0
  44. package/dist/config/settings.js +302 -0
  45. package/dist/install-ui/LiveInstall.js +24 -0
  46. package/dist/install-ui/block-render.js +83 -0
  47. package/dist/install-ui/live-install-app.js +48 -0
  48. package/dist/install-ui/prompt.js +24 -0
  49. package/dist/launcher/classify.js +116 -0
  50. package/dist/launcher/env.js +53 -0
  51. package/dist/launcher/live-install.js +50 -0
  52. package/dist/launcher/output-redaction.js +77 -0
  53. package/dist/launcher/preflight-prompt.js +139 -0
  54. package/dist/launcher/resolve-real-binary.js +73 -0
  55. package/dist/launcher/run.js +417 -0
  56. package/dist/policy/evaluate.js +128 -0
  57. package/dist/presentation/mode.js +52 -0
  58. package/dist/presentation/theme.js +29 -0
  59. package/dist/proxy/buffer-budget.js +64 -0
  60. package/dist/proxy/ca.js +126 -0
  61. package/dist/proxy/classify-host.js +26 -0
  62. package/dist/proxy/enforcement.js +102 -0
  63. package/dist/proxy/metadata-map.js +336 -0
  64. package/dist/proxy/server.js +909 -0
  65. package/dist/proxy/upstream-proxy.js +102 -0
  66. package/dist/proxy/worker.js +39 -0
  67. package/dist/publish-set/collect.js +51 -0
  68. package/dist/publish-set/no-exec-shell.js +19 -0
  69. package/dist/publish-set/npm.js +109 -0
  70. package/dist/publish-set/pack.js +36 -0
  71. package/dist/publish-set/pypi.js +59 -0
  72. package/dist/runtime/cli.js +17 -0
  73. package/dist/runtime/first-run.js +60 -0
  74. package/dist/runtime/node-version.js +58 -0
  75. package/dist/runtime/nudges.js +105 -0
  76. package/dist/scan/analyze-worker.js +21 -0
  77. package/dist/scan/collect.js +153 -0
  78. package/dist/scan/command.js +159 -0
  79. package/dist/scan/discovery.js +209 -0
  80. package/dist/scan/render.js +240 -0
  81. package/dist/scan/scanner-report.js +82 -0
  82. package/dist/scan/staged.js +173 -0
  83. package/dist/scan/types.js +1 -0
  84. package/dist/scan-ui/LegacyApp.js +156 -0
  85. package/dist/scan-ui/alt-screen.js +84 -0
  86. package/dist/scan-ui/api-aliases.js +1 -0
  87. package/dist/scan-ui/components/ErrorView.js +23 -0
  88. package/dist/scan-ui/components/InteractiveResultsView.js +1166 -0
  89. package/dist/scan-ui/components/ProgressBar.js +89 -0
  90. package/dist/scan-ui/components/ProjectSelector.js +62 -0
  91. package/dist/scan-ui/components/ScoreHeader.js +20 -0
  92. package/dist/scan-ui/components/SetupBanner.js +13 -0
  93. package/dist/scan-ui/components/Spinner.js +4 -0
  94. package/dist/scan-ui/format-helpers.js +40 -0
  95. package/dist/scan-ui/hooks/useExpandAnimation.js +40 -0
  96. package/dist/scan-ui/hooks/useScan.js +113 -0
  97. package/dist/scan-ui/hooks/useTerminalSize.js +24 -0
  98. package/dist/scan-ui/launch.js +27 -0
  99. package/dist/scan-ui/logo.js +91 -0
  100. package/dist/scan-ui/shims.js +30 -0
  101. package/dist/security/sanitize.js +28 -0
  102. package/dist/service/state.js +837 -0
  103. package/dist/service/trust-store.js +234 -0
  104. package/dist/service/worker.js +88 -0
  105. package/dist/setup/git-hook.js +244 -0
  106. package/dist/setup/optional-support.js +58 -0
  107. package/dist/setup/plan.js +899 -0
  108. package/dist/state/cleanup-registry.js +60 -0
  109. package/dist/state/index.js +5 -0
  110. package/dist/state/locks.js +161 -0
  111. package/dist/state/paths.js +24 -0
  112. package/dist/state/sessions.js +170 -0
  113. package/dist/state/store.js +50 -0
  114. package/dist/telemetry/events.js +40 -0
  115. package/dist/util/git.js +20 -0
  116. package/dist/util/tty-prompt.js +43 -0
  117. package/dist/verify/local.js +400 -0
  118. package/dist/verify/package-check.js +240 -0
  119. package/dist/verify/preflight.js +698 -0
  120. package/dist/verify/render.js +184 -0
  121. package/dist/verify/types.js +1 -0
  122. package/package.json +33 -50
  123. package/dist/index.mjs +0 -54141
  124. package/dist/postinstall.mjs +0 -731
  125. package/dist/python-hook/dg_pip_hook.pth +0 -1
  126. package/dist/python-hook/dg_pip_hook.py +0 -130
@@ -0,0 +1,50 @@
1
+ import { createLaunchPlan, runWithProductionProxyLive } from "./run.js";
2
+ import { isSupportedPackageManager } from "./classify.js";
3
+ import { isCiEnv, resolvePresentation } from "../presentation/mode.js";
4
+ const FALL_THROUGH = { handled: false };
5
+ export async function maybeRunLiveInstall(args, options = {}) {
6
+ const env = options.env ?? process.env;
7
+ if (!process.stdout.isTTY || isCiEnv(env) || resolvePresentation().mode !== "rich") {
8
+ return FALL_THROUGH;
9
+ }
10
+ const [manager, ...rest] = args;
11
+ if (!manager || !isSupportedPackageManager(manager)) {
12
+ return FALL_THROUGH;
13
+ }
14
+ const { childArgs, forceOverride } = stripControlArgs(rest);
15
+ const plan = createLaunchPlan(manager, childArgs, env);
16
+ if (plan.classification.kind !== "protected" || !plan.realBinary.path) {
17
+ return FALL_THROUGH;
18
+ }
19
+ const runOptions = {
20
+ env,
21
+ ...(forceOverride ? { forceOverride } : {})
22
+ };
23
+ const { renderLiveInstall } = await import("../install-ui/live-install-app.js");
24
+ try {
25
+ const result = await renderLiveInstall((onView) => runWithProductionProxyLive(plan, childArgs, runOptions, onView));
26
+ return { handled: true, result };
27
+ }
28
+ catch (error) {
29
+ return {
30
+ handled: true,
31
+ result: {
32
+ exitCode: 1,
33
+ stdout: "",
34
+ stderr: `dg protection failed: ${error instanceof Error ? error.message : "unknown error"}\n`
35
+ }
36
+ };
37
+ }
38
+ }
39
+ function stripControlArgs(args) {
40
+ const childArgs = [];
41
+ let force = false;
42
+ for (const arg of args) {
43
+ if (arg === "--dg-force-install") {
44
+ force = true;
45
+ continue;
46
+ }
47
+ childArgs.push(arg);
48
+ }
49
+ return force ? { childArgs, forceOverride: { force: true } } : { childArgs };
50
+ }
@@ -0,0 +1,77 @@
1
+ const credentialUrlPattern = /\b([a-z][a-z0-9+.-]{0,31}:\/\/)([^/\s:@]+):([^/\s@]+)@/gi;
2
+ const authHeaderPattern = /\b(proxy-authorization|authorization):\s*[^\r\n]+/gi;
3
+ const tokenAssignmentPattern = /\b((?:npm_|pypi_|dg_)?(?:token|authToken|password|secret))=([^\s]+)/gi;
4
+ const npmrcAuthPattern = /(_authToken|_auth|_password)\s*=\s*("[^"]*"|[^\s;,]+)/gi;
5
+ const bearerPattern = /\bBearer\s+[A-Za-z0-9._~+/=-]{8,}/g;
6
+ const knownTokenShapePattern = /\b(npm_[A-Za-z0-9]{36}|gh[pousr]_[A-Za-z0-9]{36,255}|pypi-[A-Za-z0-9_-]{20,})\b/g;
7
+ export function redactSecrets(text) {
8
+ return text
9
+ .replace(credentialUrlPattern, "$1<redacted>@")
10
+ .replace(authHeaderPattern, (_match, header) => `${header}: <redacted>`)
11
+ .replace(npmrcAuthPattern, "$1=<redacted>")
12
+ .replace(tokenAssignmentPattern, "$1=<redacted>")
13
+ .replace(bearerPattern, "Bearer <redacted>")
14
+ .replace(knownTokenShapePattern, "<redacted>");
15
+ }
16
+ const STREAM_FLUSH_QUIET_MS = 80;
17
+ const SECRET_TAIL_SCAN_CHARS = 80;
18
+ const secretTailPattern = /(Bearer\s+[\w.~+/=-]*|npm_[A-Za-z0-9]*|gh[pousr]_[A-Za-z0-9]*|pypi-[\w-]*|(?:_authToken|_auth|_password|token|authToken|password|secret)=\S*)$/i;
19
+ export function createStreamRedactor(emit) {
20
+ let pending = "";
21
+ let timer;
22
+ let tailHeldOnce = false;
23
+ const clearTimer = () => {
24
+ if (timer) {
25
+ clearTimeout(timer);
26
+ timer = undefined;
27
+ }
28
+ };
29
+ const emitPending = () => {
30
+ clearTimer();
31
+ if (pending) {
32
+ emit(redactSecrets(pending));
33
+ pending = "";
34
+ }
35
+ tailHeldOnce = false;
36
+ };
37
+ const onQuietTimer = () => {
38
+ timer = undefined;
39
+ if (!pending) {
40
+ tailHeldOnce = false;
41
+ return;
42
+ }
43
+ const scanStart = Math.max(0, pending.length - SECRET_TAIL_SCAN_CHARS);
44
+ const tailMatch = secretTailPattern.exec(pending.slice(scanStart));
45
+ if (tailMatch && !tailHeldOnce) {
46
+ const matchStart = scanStart + tailMatch.index;
47
+ if (matchStart > 0) {
48
+ emit(redactSecrets(pending.slice(0, matchStart)));
49
+ }
50
+ pending = pending.slice(matchStart);
51
+ tailHeldOnce = true;
52
+ timer = setTimeout(onQuietTimer, STREAM_FLUSH_QUIET_MS);
53
+ timer.unref?.();
54
+ return;
55
+ }
56
+ emit(redactSecrets(pending));
57
+ pending = "";
58
+ tailHeldOnce = false;
59
+ };
60
+ return {
61
+ write(chunk) {
62
+ pending += chunk;
63
+ tailHeldOnce = false;
64
+ const boundary = Math.max(pending.lastIndexOf("\n"), pending.lastIndexOf("\r"));
65
+ if (boundary >= 0) {
66
+ emit(redactSecrets(pending.slice(0, boundary + 1)));
67
+ pending = pending.slice(boundary + 1);
68
+ }
69
+ clearTimer();
70
+ if (pending) {
71
+ timer = setTimeout(onQuietTimer, STREAM_FLUSH_QUIET_MS);
72
+ timer.unref?.();
73
+ }
74
+ },
75
+ flush: emitPending
76
+ };
77
+ }
@@ -0,0 +1,139 @@
1
+ import { analyzePackages } from "../api/analyze.js";
2
+ import { isCiEnv } from "../presentation/mode.js";
3
+ import { renderInstallDecision } from "../install-ui/block-render.js";
4
+ import { promptYesNo, defaultPromptIo } from "../install-ui/prompt.js";
5
+ import { enforceProtectedInstall } from "../proxy/enforcement.js";
6
+ import { classifyPackageManagerInvocation, isSupportedPackageManager } from "./classify.js";
7
+ import { redactSecrets } from "./output-redaction.js";
8
+ const ECOSYSTEM_BY_MANAGER = {
9
+ npm: "npm",
10
+ pnpm: "npm",
11
+ yarn: "npm",
12
+ pip: "pypi",
13
+ pipx: "pypi",
14
+ uv: "pypi"
15
+ };
16
+ const FALL_THROUGH = { handled: false };
17
+ export async function maybePreflightInstallPrompt(args, options = {}) {
18
+ const env = options.env ?? process.env;
19
+ const io = options.io ?? defaultPromptIo();
20
+ if (!io.isTTY || isCiEnv(env)) {
21
+ return FALL_THROUGH;
22
+ }
23
+ const [manager, ...rest] = args;
24
+ if (!manager || !isSupportedPackageManager(manager)) {
25
+ return FALL_THROUGH;
26
+ }
27
+ const ecosystem = ECOSYSTEM_BY_MANAGER[manager];
28
+ if (!ecosystem) {
29
+ return FALL_THROUGH;
30
+ }
31
+ const { childArgs, forceOverride } = stripControlArgs(rest);
32
+ const classification = classifyPackageManagerInvocation(manager, childArgs);
33
+ if (classification.kind !== "protected") {
34
+ return FALL_THROUGH;
35
+ }
36
+ const specs = pinnedSpecs(childArgs, ecosystem);
37
+ if (specs.length === 0) {
38
+ return FALL_THROUGH;
39
+ }
40
+ let worst;
41
+ try {
42
+ const response = await (options.analyze ?? analyzePackages)(specs.map((spec) => ({ name: spec.name, version: spec.version })), { ecosystem, env });
43
+ for (const spec of specs) {
44
+ const pkg = response.packages.find((entry) => entry.name === spec.name && entry.version === spec.version);
45
+ const action = pkg?.action ?? "pass";
46
+ if (!worst || rank(action) > rank(worst.action)) {
47
+ worst = {
48
+ action,
49
+ spec,
50
+ reason: pkg?.reasons[0] ?? pkg?.findings[0]?.title ?? "flagged by the scanner"
51
+ };
52
+ }
53
+ }
54
+ }
55
+ catch {
56
+ return FALL_THROUGH;
57
+ }
58
+ if (!worst || worst.action === "pass") {
59
+ return FALL_THROUGH;
60
+ }
61
+ if (worst.action === "block") {
62
+ const decision = enforceProtectedInstall({
63
+ classification,
64
+ env,
65
+ proxyVerdict: {
66
+ verdict: "block",
67
+ packageName: `${ecosystem}:${worst.spec.name}@${worst.spec.version}`,
68
+ cause: "malware",
69
+ reason: worst.reason,
70
+ ...(worst.dashboardUrl ? { dashboardUrl: worst.dashboardUrl } : {})
71
+ },
72
+ ...(forceOverride ? { forceOverride } : {})
73
+ });
74
+ if (decision.action === "block") {
75
+ return {
76
+ handled: true,
77
+ result: { exitCode: 2, stdout: "", stderr: redactSecrets(renderInstallDecision(decision)) }
78
+ };
79
+ }
80
+ return FALL_THROUGH;
81
+ }
82
+ const label = `${worst.spec.name}@${worst.spec.version}`;
83
+ const proceed = await promptYesNo(`⚠ DG flagged ${label} (warn) — ${worst.reason}. Proceed?`, io);
84
+ if (proceed) {
85
+ return FALL_THROUGH;
86
+ }
87
+ return {
88
+ handled: true,
89
+ result: { exitCode: 1, stdout: "", stderr: "Declined. Nothing was installed.\n" }
90
+ };
91
+ }
92
+ function rank(action) {
93
+ return { pass: 0, analysis_incomplete: 1, warn: 2, block: 3 }[action];
94
+ }
95
+ function stripControlArgs(args) {
96
+ const childArgs = [];
97
+ let force = false;
98
+ for (const arg of args) {
99
+ if (arg === "--dg-force-install") {
100
+ force = true;
101
+ continue;
102
+ }
103
+ childArgs.push(arg);
104
+ }
105
+ return force ? { childArgs, forceOverride: { force: true } } : { childArgs };
106
+ }
107
+ function pinnedSpecs(args, ecosystem) {
108
+ const specs = [];
109
+ for (const arg of args) {
110
+ if (arg.startsWith("-")) {
111
+ continue;
112
+ }
113
+ const spec = ecosystem === "npm" ? parseNpmSpec(arg) : parsePypiSpec(arg);
114
+ if (spec) {
115
+ specs.push(spec);
116
+ }
117
+ }
118
+ return specs;
119
+ }
120
+ function parseNpmSpec(token) {
121
+ const scoped = token.startsWith("@");
122
+ const at = token.indexOf("@", scoped ? 1 : 0);
123
+ if (at <= 0) {
124
+ return undefined;
125
+ }
126
+ const name = token.slice(0, at);
127
+ const version = token.slice(at + 1);
128
+ if (!name || !/^\d/.test(version)) {
129
+ return undefined;
130
+ }
131
+ return { name, version };
132
+ }
133
+ function parsePypiSpec(token) {
134
+ const match = /^([A-Za-z0-9._-]+)==([0-9][^\s;]*)$/.exec(token);
135
+ if (!match || !match[1] || !match[2]) {
136
+ return undefined;
137
+ }
138
+ return { name: match[1], version: match[2] };
139
+ }
@@ -0,0 +1,73 @@
1
+ import { accessSync, constants, readFileSync } from "node:fs";
2
+ import { delimiter, join, resolve, sep } from "node:path";
3
+ import { resolveDgPaths } from "../state/index.js";
4
+ // Many systems (homebrew/macOS, some Linux) ship only the version-suffixed
5
+ // pip3/python3, not a bare pip/python. Fall back to those instead of reporting
6
+ // "real pip binary was not found".
7
+ const BINARY_FALLBACKS = {
8
+ pip: ["pip3"],
9
+ python: ["python3"]
10
+ };
11
+ export function resolveRealBinary(options) {
12
+ const env = options.env ?? process.env;
13
+ const shimDir = join(resolveDgPaths(env).homeDir, ".dg", "shims");
14
+ const skipDirs = new Set([resolve(shimDir), ...(options.extraSkipDirs ?? []).map((dir) => resolve(dir))]);
15
+ const searched = [];
16
+ const skipped = [];
17
+ const extensions = process.platform === "win32" ? ["", ".cmd", ".exe", ".bat"] : [""];
18
+ const pathDirs = (env.PATH ?? "").split(delimiter).filter(Boolean);
19
+ const candidateNames = [options.name, ...(BINARY_FALLBACKS[options.name] ?? [])].filter((name, index, all) => all.indexOf(name) === index);
20
+ for (const name of candidateNames) {
21
+ for (const rawDir of pathDirs) {
22
+ const dir = resolve(rawDir);
23
+ if (isSkippedDir(dir, skipDirs)) {
24
+ skipped.push(rawDir);
25
+ continue;
26
+ }
27
+ for (const extension of extensions) {
28
+ const candidate = join(rawDir, `${name}${extension}`);
29
+ searched.push(candidate);
30
+ if (isExecutable(candidate) && !isDgShim(candidate)) {
31
+ return {
32
+ path: candidate,
33
+ skipped,
34
+ searched
35
+ };
36
+ }
37
+ if (isDgShim(candidate)) {
38
+ skipped.push(candidate);
39
+ }
40
+ }
41
+ }
42
+ }
43
+ return {
44
+ path: null,
45
+ skipped,
46
+ searched
47
+ };
48
+ }
49
+ function isSkippedDir(dir, skipDirs) {
50
+ for (const skipped of skipDirs) {
51
+ if (dir === skipped || dir.startsWith(`${skipped}${sep}`)) {
52
+ return true;
53
+ }
54
+ }
55
+ return false;
56
+ }
57
+ function isExecutable(path) {
58
+ try {
59
+ accessSync(path, constants.X_OK);
60
+ return true;
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ function isDgShim(path) {
67
+ try {
68
+ return readFileSync(path, "utf8").slice(0, 160).includes("dg-shim-v1");
69
+ }
70
+ catch {
71
+ return false;
72
+ }
73
+ }