@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,417 @@
1
+ import { spawn } from "node:child_process";
2
+ import { existsSync, writeFileSync } from "node:fs";
3
+ import { constants as osConstants } from "node:os";
4
+ import { fileURLToPath } from "node:url";
5
+ import { EXIT_UNAVAILABLE } from "../commands/types.js";
6
+ import { loadUserConfig } from "../config/settings.js";
7
+ import { describeBlockedInstall, renderInstallDecision } from "../install-ui/block-render.js";
8
+ import { enforceProtectedInstall } from "../proxy/enforcement.js";
9
+ import { isCiEnv } from "../presentation/mode.js";
10
+ import { readProxySessionState } from "../proxy/server.js";
11
+ import { cleanupSessionSync, createSessionSync, resolveDgPaths } from "../state/index.js";
12
+ import { classifyPackageManagerInvocation } from "./classify.js";
13
+ import { buildProxyChildEnv } from "./env.js";
14
+ import { createStreamRedactor, redactSecrets } from "./output-redaction.js";
15
+ import { resolveRealBinary } from "./resolve-real-binary.js";
16
+ export const EXIT_INSTALL_BLOCKED = 2;
17
+ export function createLaunchPlan(manager, args, env = process.env) {
18
+ const classification = classifyPackageManagerInvocation(manager, args);
19
+ const realBinary = resolveRealBinary({
20
+ name: classification.realBinaryName,
21
+ env
22
+ });
23
+ return {
24
+ classification,
25
+ realBinary,
26
+ startsProxy: classification.kind === "protected",
27
+ childEnv: {
28
+ ...env,
29
+ DG_SHIM_ACTIVE: shimNonce(manager, env)
30
+ }
31
+ };
32
+ }
33
+ export async function runPackageManager(manager, args, options = {}) {
34
+ const plan = createLaunchPlan(manager, args, options.env ?? process.env);
35
+ const invoked = ["dg", manager, ...args].join(" ");
36
+ if (plan.classification.kind === "unsupported") {
37
+ return unavailable(invoked, plan.classification.reason);
38
+ }
39
+ if (!plan.realBinary.path) {
40
+ return unavailable(invoked, `real ${plan.classification.realBinaryName} binary was not found outside dg shims`);
41
+ }
42
+ if (plan.startsProxy) {
43
+ if (!options.proxyVerdict) {
44
+ return runWithProductionProxy(plan, args, options);
45
+ }
46
+ const decision = enforceProtectedInstall({
47
+ classification: plan.classification,
48
+ env: options.env ?? process.env,
49
+ proxyVerdict: options.proxyVerdict,
50
+ ...(options.forceOverride ? { forceOverride: options.forceOverride } : {}),
51
+ ...(options.now ? { now: options.now } : {})
52
+ });
53
+ const rendered = renderDecisions([decision], options.env ?? process.env);
54
+ if (decision.action === "block") {
55
+ return {
56
+ exitCode: EXIT_INSTALL_BLOCKED,
57
+ stdout: "",
58
+ stderr: rendered
59
+ };
60
+ }
61
+ const child = await spawnPackageManager(plan, args, options);
62
+ return {
63
+ exitCode: child.exitCode,
64
+ stdout: streamedOut(child.stdout, options),
65
+ stderr: `${rendered}${streamedErr(child.stderr, options)}`
66
+ };
67
+ }
68
+ const child = await spawnPackageManager(plan, args, options);
69
+ return {
70
+ exitCode: child.exitCode,
71
+ stdout: streamedOut(child.stdout, options),
72
+ stderr: streamedErr(child.stderr, options)
73
+ };
74
+ }
75
+ async function runWithProductionProxy(plan, args, options) {
76
+ const env = options.env ?? process.env;
77
+ const proxy = await startProxyWorker(plan.classification, env, options.forceOverride);
78
+ if ("decision" in proxy) {
79
+ return {
80
+ exitCode: EXIT_INSTALL_BLOCKED,
81
+ stdout: "",
82
+ stderr: redactSecrets(renderInstallDecision(proxy.decision))
83
+ };
84
+ }
85
+ const restoreSignalHandlers = installProxySignalHandlers(proxy);
86
+ try {
87
+ const proxiedPlan = {
88
+ ...plan,
89
+ childEnv: buildProxyChildEnv({
90
+ manager: plan.classification.manager,
91
+ baseEnv: plan.childEnv,
92
+ proxyUrl: proxy.proxyUrl,
93
+ caBundlePath: proxy.session.files.ca,
94
+ cacheDir: `${proxy.session.dir}/pm-cache`
95
+ })
96
+ };
97
+ const child = await spawnPackageManager(proxiedPlan, args, options);
98
+ const proxyState = readProxySessionState(proxy.session);
99
+ const rendered = renderDecisions(proxyState.decisions, env);
100
+ const blocked = proxyState.decisions.find((decision) => decision.action === "block");
101
+ if (blocked) {
102
+ return {
103
+ exitCode: EXIT_INSTALL_BLOCKED,
104
+ stdout: streamedOut(child.stdout, options),
105
+ stderr: `${rendered}${streamedErr(child.stderr, options)}`
106
+ };
107
+ }
108
+ if (proxyState.decisions.length === 0) {
109
+ // The proxy saw no artifact verdicts. If the wrapped command itself exited
110
+ // with an error (e.g. pip's PEP 668 externally-managed error, a resolution
111
+ // error, a crash before fetch), it failed for its own reasons — nothing was
112
+ // installed, so there is nothing to verify. Keep its non-zero exit (still
113
+ // fail-closed: no unverified install can SUCCEED) but propagate the
114
+ // command's own error instead of a misleading "block / override" decision.
115
+ if (child.exitCode !== 0) {
116
+ return {
117
+ exitCode: child.exitCode,
118
+ stdout: streamedOut(child.stdout, options),
119
+ stderr: `${streamedErr(child.stderr, options)}\n ? dg did not check this install — ${plan.classification.manager} exited with an error before any package was fetched.\n`
120
+ };
121
+ }
122
+ return {
123
+ exitCode: EXIT_INSTALL_BLOCKED,
124
+ stdout: streamedOut(child.stdout, options),
125
+ stderr: `${streamedErr(child.stderr, options)}${cacheOnlyNotice(plan.classification.manager)}`
126
+ };
127
+ }
128
+ return {
129
+ exitCode: child.exitCode,
130
+ stdout: streamedOut(child.stdout, options),
131
+ stderr: `${rendered}${streamedErr(child.stderr, options)}`
132
+ };
133
+ }
134
+ finally {
135
+ restoreSignalHandlers();
136
+ stopProxyWorker(proxy);
137
+ }
138
+ }
139
+ export function deriveLiveView(state, phase) {
140
+ const verified = state.decisions.filter((decision) => decision.action === "pass").length;
141
+ const flagged = state.decisions.filter((decision) => decision.action === "warn").length;
142
+ const blocked = state.decisions.find((decision) => decision.action === "block");
143
+ const current = state.inflight[state.inflight.length - 1];
144
+ return {
145
+ phase,
146
+ total: state.decisions.length + state.inflight.length,
147
+ verified,
148
+ flagged,
149
+ ...(current ? { current } : {}),
150
+ ...(blocked ? { blocked: describeBlockedInstall(blocked) } : {})
151
+ };
152
+ }
153
+ export async function runWithProductionProxyLive(plan, args, options, onView) {
154
+ if (options.onStdout || options.onStderr) {
155
+ throw new Error("live install mode renders its own UI and owns the terminal; streaming output callbacks are not supported");
156
+ }
157
+ const env = options.env ?? process.env;
158
+ const proxy = await startProxyWorker(plan.classification, env, options.forceOverride);
159
+ if ("decision" in proxy) {
160
+ return { exitCode: EXIT_INSTALL_BLOCKED, stdout: "", stderr: redactSecrets(renderInstallDecision(proxy.decision)) };
161
+ }
162
+ const restoreSignalHandlers = installProxySignalHandlers(proxy);
163
+ try {
164
+ const childEnv = buildProxyChildEnv({
165
+ manager: plan.classification.manager,
166
+ baseEnv: plan.childEnv,
167
+ proxyUrl: proxy.proxyUrl,
168
+ caBundlePath: proxy.session.files.ca,
169
+ cacheDir: `${proxy.session.dir}/pm-cache`
170
+ });
171
+ const spawner = options.spawner ?? defaultSpawner;
172
+ const poll = setInterval(() => {
173
+ onView(deriveLiveView(readProxySessionState(proxy.session), "scanning"));
174
+ }, 90);
175
+ let finished;
176
+ try {
177
+ finished = await spawner({
178
+ binary: plan.realBinary.path ?? "",
179
+ args,
180
+ env: childEnv
181
+ });
182
+ }
183
+ finally {
184
+ clearInterval(poll);
185
+ }
186
+ const proxyState = readProxySessionState(proxy.session);
187
+ onView(deriveLiveView(proxyState, "done"));
188
+ if (proxyState.decisions.some((decision) => decision.action === "block")) {
189
+ return { exitCode: EXIT_INSTALL_BLOCKED, stdout: "", stderr: "" };
190
+ }
191
+ if (proxyState.decisions.length === 0) {
192
+ if (finished.exitCode !== 0) {
193
+ return {
194
+ exitCode: finished.exitCode,
195
+ stdout: finished.stdout,
196
+ stderr: `${finished.stderr}\n ? dg did not check this install — ${plan.classification.manager} exited with an error before any package was fetched.\n`
197
+ };
198
+ }
199
+ return { exitCode: EXIT_INSTALL_BLOCKED, stdout: installOutcome(finished.stdout), stderr: cacheOnlyNotice(plan.classification.manager) };
200
+ }
201
+ return { exitCode: finished.exitCode, stdout: installOutcome(finished.stdout), stderr: "" };
202
+ }
203
+ finally {
204
+ restoreSignalHandlers();
205
+ stopProxyWorker(proxy);
206
+ }
207
+ }
208
+ function cacheOnlyNotice(manager) {
209
+ return ` ? dg could not verify this install — no download reached the firewall.\n ${manager} used a local cache or file, or the packages were already installed, so nothing was fetched to check. The install itself ran; these are not covered by per-command protection. Run 'dg scan' to verify your installed packages.\n`;
210
+ }
211
+ function installOutcome(stdout) {
212
+ const lines = stdout.split("\n").filter((line) => /^Successfully installed /.test(line) ||
213
+ /^(added|changed|removed|updated) \d/.test(line.trim()));
214
+ return lines.length > 0 ? `${lines.join("\n")}\n` : "";
215
+ }
216
+ async function spawnPackageManager(plan, args, options) {
217
+ if (!plan.realBinary.path) {
218
+ return {
219
+ exitCode: EXIT_UNAVAILABLE,
220
+ stdout: "",
221
+ stderr: `real ${plan.classification.realBinaryName} binary was not found outside dg shims\n`
222
+ };
223
+ }
224
+ const spawner = options.spawner ?? defaultSpawner;
225
+ return spawner({
226
+ binary: plan.realBinary.path,
227
+ args,
228
+ env: plan.childEnv,
229
+ onStdout: options.onStdout,
230
+ onStderr: options.onStderr
231
+ });
232
+ }
233
+ const defaultSpawner = (request) => new Promise((resolve) => {
234
+ const child = spawn(request.binary, [...request.args], {
235
+ env: request.env,
236
+ stdio: ["inherit", "pipe", "pipe"]
237
+ });
238
+ const stdout = [];
239
+ const stderr = [];
240
+ const stdoutRedactor = createStreamRedactor((chunk) => {
241
+ stdout.push(chunk);
242
+ request.onStdout?.(chunk);
243
+ });
244
+ const stderrRedactor = createStreamRedactor((chunk) => {
245
+ stderr.push(chunk);
246
+ request.onStderr?.(chunk);
247
+ });
248
+ child.stdout?.on("data", (chunk) => stdoutRedactor.write(chunk.toString("utf8")));
249
+ child.stderr?.on("data", (chunk) => stderrRedactor.write(chunk.toString("utf8")));
250
+ const settle = (exitCode, extraStderr) => {
251
+ stdoutRedactor.flush();
252
+ stderrRedactor.flush();
253
+ if (extraStderr) {
254
+ const redacted = redactSecrets(extraStderr);
255
+ stderr.push(redacted);
256
+ request.onStderr?.(redacted);
257
+ }
258
+ resolve({
259
+ exitCode,
260
+ stdout: redactSecrets(stdout.join("")),
261
+ stderr: redactSecrets(stderr.join(""))
262
+ });
263
+ };
264
+ child.on("error", (error) => settle(EXIT_UNAVAILABLE, `${error.message}\n`));
265
+ child.on("close", (code, signal) => settle(code ?? signalExitCode(signal)));
266
+ });
267
+ function signalExitCode(signal) {
268
+ if (!signal) {
269
+ return EXIT_UNAVAILABLE;
270
+ }
271
+ const signalNumber = osConstants.signals[signal];
272
+ return signalNumber ? 128 + signalNumber : EXIT_UNAVAILABLE;
273
+ }
274
+ function streamedOut(captured, options) {
275
+ return options.onStdout ? "" : captured;
276
+ }
277
+ function streamedErr(captured, options) {
278
+ return options.onStderr ? "" : captured;
279
+ }
280
+ function unavailable(invoked, reason) {
281
+ return {
282
+ exitCode: EXIT_UNAVAILABLE,
283
+ stdout: "",
284
+ stderr: `${redactSecrets(invoked)} cannot run yet: ${redactSecrets(reason)}.\n`
285
+ };
286
+ }
287
+ function renderDecisions(decisions, env) {
288
+ const visible = isCiEnv(env) ? decisions.filter((decision) => decision.action !== "pass") : decisions;
289
+ return visible.map((decision) => redactSecrets(renderInstallDecision(decision))).join("");
290
+ }
291
+ async function startProxyWorker(classification, env, forceOverride) {
292
+ const paths = resolveDgPaths(env);
293
+ const session = createSessionSync(paths);
294
+ const sessionBootstrapPath = `${session.dir}/session.json`;
295
+ const workerPath = fileURLToPath(new URL("../proxy/worker.js", import.meta.url));
296
+ try {
297
+ writeFileSync(sessionBootstrapPath, `${JSON.stringify(session)}\n`, {
298
+ encoding: "utf8",
299
+ mode: 0o600
300
+ });
301
+ if (!existsSync(workerPath)) {
302
+ throw new Error("production proxy worker is not built");
303
+ }
304
+ const config = loadUserConfig(env);
305
+ const child = spawn(process.execPath, [workerPath, sessionBootstrapPath, config.api.baseUrl], {
306
+ env: {
307
+ ...env,
308
+ DG_PROXY_CLASSIFICATION: JSON.stringify(classification),
309
+ ...(forceOverride ? { DG_FORCE_OVERRIDE_REQUEST: JSON.stringify(forceOverride) } : {})
310
+ },
311
+ stdio: ["pipe", "pipe", "pipe"]
312
+ });
313
+ const ready = await waitForProxyReady(session);
314
+ if (!ready.ready || ready.port <= 0) {
315
+ terminateProxyProcess(child);
316
+ throw new Error("production proxy did not become ready");
317
+ }
318
+ return {
319
+ process: child,
320
+ session,
321
+ proxyUrl: `http://127.0.0.1:${ready.port}`
322
+ };
323
+ }
324
+ catch (error) {
325
+ cleanupSessionSync(session);
326
+ return {
327
+ decision: enforceProtectedInstall({
328
+ classification,
329
+ env,
330
+ proxyVerdict: {
331
+ verdict: "block",
332
+ cause: "proxy-setup-failure",
333
+ reason: error instanceof Error ? error.message : "production proxy startup failed"
334
+ }
335
+ })
336
+ };
337
+ }
338
+ }
339
+ async function waitForProxyReady(session) {
340
+ const deadline = Date.now() + 2_000;
341
+ while (Date.now() < deadline) {
342
+ const state = readProxySessionState(session);
343
+ if (state.ready && state.port > 0) {
344
+ return state;
345
+ }
346
+ await delay(25);
347
+ }
348
+ return readProxySessionState(session);
349
+ }
350
+ function stopProxyWorker(proxy) {
351
+ try {
352
+ proxy.process.stdin.end();
353
+ }
354
+ catch {
355
+ // The worker may already be gone after a package-manager crash or signal.
356
+ }
357
+ terminateProxyProcess(proxy.process);
358
+ cleanupSessionSync(proxy.session);
359
+ }
360
+ function installProxySignalHandlers(proxy) {
361
+ const registrations = ["SIGINT", "SIGTERM"].map((signal) => {
362
+ const handler = () => {
363
+ stopProxyWorker(proxy);
364
+ process.exit(signal === "SIGINT" ? 130 : 143);
365
+ };
366
+ process.once(signal, handler);
367
+ return {
368
+ handler,
369
+ signal
370
+ };
371
+ });
372
+ return () => {
373
+ for (const registration of registrations) {
374
+ process.off(registration.signal, registration.handler);
375
+ }
376
+ };
377
+ }
378
+ function terminateProxyProcess(child) {
379
+ if (!isProcessAlive(child)) {
380
+ return;
381
+ }
382
+ child.kill("SIGTERM");
383
+ waitForProcessExitSync(child, 500);
384
+ if (isProcessAlive(child)) {
385
+ child.kill("SIGKILL");
386
+ waitForProcessExitSync(child, 500);
387
+ }
388
+ }
389
+ function waitForProcessExitSync(child, timeoutMs) {
390
+ const deadline = Date.now() + timeoutMs;
391
+ while (Date.now() < deadline && isProcessAlive(child)) {
392
+ sleepSync(25);
393
+ }
394
+ }
395
+ function isProcessAlive(child) {
396
+ if (!child.pid || child.exitCode !== null || child.signalCode !== null) {
397
+ return false;
398
+ }
399
+ try {
400
+ process.kill(child.pid, 0);
401
+ return true;
402
+ }
403
+ catch {
404
+ return false;
405
+ }
406
+ }
407
+ function delay(ms) {
408
+ return new Promise((resolve) => setTimeout(resolve, ms));
409
+ }
410
+ function sleepSync(ms) {
411
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
412
+ }
413
+ function shimNonce(manager, env) {
414
+ const existing = env.DG_SHIM_ACTIVE;
415
+ const next = `${manager}:${process.pid}`;
416
+ return existing ? `${existing},${next}` : next;
417
+ }
@@ -0,0 +1,128 @@
1
+ import { emitWebhookEvent, recordAuditEvent } from "../audit/events.js";
2
+ import { DEFAULT_CONFIG } from "../config/settings.js";
3
+ export function resolveEffectivePolicy(options) {
4
+ if (options.orgPolicy) {
5
+ return {
6
+ mode: options.orgPolicy.mode ?? "strict",
7
+ trustProjectAllowlists: options.orgPolicy.trustProjectAllowlists ?? false,
8
+ allowForceOverride: options.orgPolicy.allowForceOverride ?? false,
9
+ scriptHardening: options.orgPolicy.scriptHardening ?? true,
10
+ source: "org"
11
+ };
12
+ }
13
+ if (options.userConfig) {
14
+ return {
15
+ ...options.userConfig.policy,
16
+ source: "user"
17
+ };
18
+ }
19
+ return {
20
+ ...DEFAULT_CONFIG.policy,
21
+ source: "built-in"
22
+ };
23
+ }
24
+ export function evaluatePackagePolicy(options) {
25
+ const trustedAllowlist = findTrustedAllowlist(options.packageName, options.policy, options.allowlists ?? []);
26
+ if (trustedAllowlist) {
27
+ return {
28
+ action: "pass",
29
+ source: "allowlist",
30
+ reason: `${trustedAllowlist.trustedBy} allowlist: ${trustedAllowlist.reason}`
31
+ };
32
+ }
33
+ if (options.policy.mode === "off") {
34
+ return {
35
+ action: "pass",
36
+ source: options.policy.source,
37
+ reason: "policy mode off"
38
+ };
39
+ }
40
+ if (options.verdict === "pass") {
41
+ return {
42
+ action: "pass",
43
+ source: options.policy.source,
44
+ reason: "verdict pass"
45
+ };
46
+ }
47
+ if (options.policy.mode === "warn") {
48
+ return {
49
+ action: "warn",
50
+ source: options.policy.source,
51
+ reason: `policy warn mode for ${options.verdict} verdict`
52
+ };
53
+ }
54
+ if (options.policy.mode === "strict" && options.verdict === "warn") {
55
+ return {
56
+ action: "block",
57
+ source: options.policy.source,
58
+ reason: "strict mode upgrades warn verdicts to blocks"
59
+ };
60
+ }
61
+ return {
62
+ action: options.verdict,
63
+ source: options.policy.source,
64
+ reason: `${options.policy.mode} mode keeps ${options.verdict} verdict`
65
+ };
66
+ }
67
+ export function applyForceOverride(options, env = process.env) {
68
+ if (!options.force) {
69
+ return {
70
+ allowed: false,
71
+ reason: "force override was not requested",
72
+ webhookQueued: false
73
+ };
74
+ }
75
+ if (options.currentAction !== "block") {
76
+ return {
77
+ allowed: false,
78
+ reason: "force override is only valid for block decisions",
79
+ webhookQueued: false
80
+ };
81
+ }
82
+ if (!options.policy.allowForceOverride) {
83
+ return {
84
+ allowed: false,
85
+ reason: "force override is disabled by policy",
86
+ webhookQueued: false
87
+ };
88
+ }
89
+ const event = {
90
+ type: "install.force_override",
91
+ packageName: options.packageName,
92
+ reason: "developer override via --dg-force-install",
93
+ policyMode: options.policy.mode,
94
+ createdAt: (options.now ?? new Date()).toISOString()
95
+ };
96
+ recordAuditEvent(event, env);
97
+ return {
98
+ allowed: true,
99
+ reason: event.reason,
100
+ webhookQueued: emitWebhookEvent(event, env)
101
+ };
102
+ }
103
+ export function scriptPolicyArgs(options) {
104
+ if (!options.policy.scriptHardening && options.policy.mode !== "strict") {
105
+ return options.existingArgs;
106
+ }
107
+ if (hasScriptDisableFlag(options.existingArgs)) {
108
+ return options.existingArgs;
109
+ }
110
+ if (options.manager === "yarn") {
111
+ return [...options.existingArgs, "--ignore-scripts"];
112
+ }
113
+ return [...options.existingArgs, "--ignore-scripts"];
114
+ }
115
+ function findTrustedAllowlist(packageName, policy, allowlists) {
116
+ return allowlists.find((entry) => {
117
+ if (entry.packageName !== packageName) {
118
+ return false;
119
+ }
120
+ if (entry.trustedBy === "project") {
121
+ return policy.trustProjectAllowlists;
122
+ }
123
+ return true;
124
+ });
125
+ }
126
+ function hasScriptDisableFlag(args) {
127
+ return args.some((arg) => arg === "--ignore-scripts" || arg === "--ignore-scripts=true");
128
+ }
@@ -0,0 +1,52 @@
1
+ export const CI_MARKERS = [
2
+ "GITHUB_ACTIONS",
3
+ "GITLAB_CI",
4
+ "BUILDKITE",
5
+ "CIRCLECI",
6
+ "TRAVIS",
7
+ "TEAMCITY_VERSION"
8
+ ];
9
+ function truthyEnv(value) {
10
+ return value !== undefined && value !== "" && value !== "0" && value !== "false";
11
+ }
12
+ export function isCiEnv(env = process.env) {
13
+ if (truthyEnv(env.CI)) {
14
+ return true;
15
+ }
16
+ return CI_MARKERS.some((marker) => env[marker] !== undefined && env[marker] !== "");
17
+ }
18
+ export function colorEnabled(input) {
19
+ if (input.forceColorFlag) {
20
+ return true;
21
+ }
22
+ if (truthyEnv(input.env.FORCE_COLOR)) {
23
+ return true;
24
+ }
25
+ if (input.noColorFlag) {
26
+ return false;
27
+ }
28
+ if (input.env.NO_COLOR !== undefined && input.env.NO_COLOR !== "") {
29
+ return false;
30
+ }
31
+ if (input.env.DG_NO_COLOR !== undefined && input.env.DG_NO_COLOR !== "") {
32
+ return false;
33
+ }
34
+ if (input.env.TERM === "dumb") {
35
+ return false;
36
+ }
37
+ return Boolean(input.stream.isTTY);
38
+ }
39
+ export function resolvePresentation(options) {
40
+ const env = options?.env ?? process.env;
41
+ const stream = options?.stream ?? process.stdout;
42
+ const isTTY = Boolean(stream.isTTY);
43
+ const isCI = isCiEnv(env);
44
+ const color = colorEnabled({
45
+ stream,
46
+ env,
47
+ noColorFlag: options?.noColorFlag,
48
+ forceColorFlag: options?.forceColorFlag
49
+ });
50
+ const mode = isTTY && !isCI ? "rich" : "plain";
51
+ return { mode, color, isTTY, isCI };
52
+ }
@@ -0,0 +1,29 @@
1
+ const ANSI = {
2
+ block: "",
3
+ warn: "",
4
+ pass: "",
5
+ unknown: "",
6
+ muted: "",
7
+ accent: ""
8
+ };
9
+ const RESET = "";
10
+ const BADGES = {
11
+ block: { word: "BLOCK", glyph: "✘", role: "block" },
12
+ warn: { word: "WARN", glyph: "⚠", role: "warn" },
13
+ pass: { word: "PASS", glyph: "✓", role: "pass" },
14
+ analysis_incomplete: { word: "UNKNOWN", glyph: "?", role: "unknown" }
15
+ };
16
+ export function severityBadge(action) {
17
+ return BADGES[action];
18
+ }
19
+ export function createTheme(color) {
20
+ const paint = (role, text) => color ? `${ANSI[role]}${text}${RESET}` : text;
21
+ return {
22
+ color,
23
+ paint,
24
+ badge(action) {
25
+ const { word, glyph, role } = BADGES[action];
26
+ return paint(role, `${glyph} ${word}`);
27
+ }
28
+ };
29
+ }
@@ -0,0 +1,64 @@
1
+ export class BufferBudgetError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = "BufferBudgetError";
5
+ }
6
+ }
7
+ const DEFAULT_MAX_ARTIFACT_BYTES = 1024 * 1024 * 1024;
8
+ const DEFAULT_MAX_TOTAL_BUFFERED_BYTES = 2 * 1024 * 1024 * 1024;
9
+ let totalBufferedBytes = 0;
10
+ export function maxArtifactBytes(env = process.env) {
11
+ return parseLimit(env.DG_PROXY_MAX_ARTIFACT_BYTES, DEFAULT_MAX_ARTIFACT_BYTES);
12
+ }
13
+ export function maxTotalBufferedBytes(env = process.env) {
14
+ return parseLimit(env.DG_PROXY_MAX_BUFFERED_BYTES, DEFAULT_MAX_TOTAL_BUFFERED_BYTES);
15
+ }
16
+ export function currentBufferedBytes() {
17
+ return totalBufferedBytes;
18
+ }
19
+ export function collectBounded(stream, options) {
20
+ const env = options.env ?? process.env;
21
+ const limit = options.limitBytes ?? maxArtifactBytes(env);
22
+ const totalLimit = maxTotalBufferedBytes(env);
23
+ return new Promise((resolve, reject) => {
24
+ const chunks = [];
25
+ let size = 0;
26
+ let settled = false;
27
+ const settle = (finish) => {
28
+ if (settled) {
29
+ return;
30
+ }
31
+ settled = true;
32
+ totalBufferedBytes -= size;
33
+ finish();
34
+ };
35
+ stream.on("data", (chunk) => {
36
+ if (settled) {
37
+ return;
38
+ }
39
+ size += chunk.length;
40
+ totalBufferedBytes += chunk.length;
41
+ if (size > limit) {
42
+ settle(() => reject(new BufferBudgetError(`${options.label} exceeded the ${limit}-byte artifact buffer limit`)));
43
+ stream.destroy();
44
+ return;
45
+ }
46
+ if (totalBufferedBytes > totalLimit) {
47
+ settle(() => reject(new BufferBudgetError(`proxy buffered-bytes budget exhausted while fetching ${options.label}`)));
48
+ stream.destroy();
49
+ return;
50
+ }
51
+ chunks.push(chunk);
52
+ });
53
+ stream.once("end", () => settle(() => resolve(Buffer.concat(chunks))));
54
+ stream.once("error", (error) => settle(() => reject(error)));
55
+ stream.once("close", () => settle(() => reject(new BufferBudgetError(`${options.label} connection closed before the response completed`))));
56
+ });
57
+ }
58
+ function parseLimit(raw, fallback) {
59
+ if (!raw) {
60
+ return fallback;
61
+ }
62
+ const parsed = Number.parseInt(raw, 10);
63
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
64
+ }