@westbayberry/dg 2.0.0 → 2.0.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.
@@ -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);
@@ -12,6 +12,7 @@ import { loadUserConfig, saveUserConfig, setConfigValue } from "../config/settin
12
12
  import { promptYesNo } from "../util/tty-prompt.js";
13
13
  import { authStatus } from "../auth/store.js";
14
14
  import { shouldLaunchAuditTui, launchAuditTui } from "../audit-ui/launch.js";
15
+ import { renderCommandHelp } from "./help.js";
15
16
  export const auditCommand = {
16
17
  name: "audit",
17
18
  summary: "Audit the package you're about to publish for leaked secrets and risky files.",
@@ -95,7 +96,11 @@ export async function maybeAudit(args) {
95
96
  if (args[0] !== "audit") {
96
97
  return { handled: false, result: { exitCode: 0, stdout: "", stderr: "" } };
97
98
  }
98
- const gathered = gather(args.slice(1));
99
+ const sub = args.slice(1);
100
+ if (sub[0] === "--help" || sub[0] === "-h" || sub[0] === "help") {
101
+ return { handled: true, result: { exitCode: 0, stdout: renderCommandHelp(auditCommand), stderr: "" } };
102
+ }
103
+ const gathered = gather(sub);
99
104
  if ("error" in gathered) {
100
105
  return { handled: true, result: gatherError(gathered) };
101
106
  }
@@ -13,6 +13,7 @@ const HEADLINES = {
13
13
  license: "license policy",
14
14
  "hash-mismatch": "artifact integrity mismatch",
15
15
  "private-upload-disabled": "private artifact not scanned",
16
+ "needs-login": "sign-in required",
16
17
  "api-unavailable": "scanner unavailable",
17
18
  "quota-exceeded": "monthly scan limit reached",
18
19
  "api-timeout": "scanner timed out",
@@ -27,6 +28,7 @@ const NEXT_STEP = {
27
28
  license: "Replace the dependency or update your license policy.",
28
29
  "hash-mismatch": "Clear your package cache and retry. If it persists, do not install.",
29
30
  "private-upload-disabled": "Enable private artifact scanning to verify this package.",
31
+ "needs-login": "Run 'dg login' (free) to check packages from the registry before they install.",
30
32
  "quota-exceeded": "Upgrade your plan or wait for your monthly limit to reset. See westbayberry.com/pricing."
31
33
  };
32
34
  export function describeBlockedInstall(decision) {
@@ -34,7 +36,7 @@ export function describeBlockedInstall(decision) {
34
36
  const override = decision.forceOverride && !decision.forceOverride.allowed
35
37
  ? "not allowed by your policy"
36
38
  : "re-run with --dg-force-install";
37
- const nextStep = verifiedBad
39
+ const nextStep = verifiedBad || decision.cause === "needs-login"
38
40
  ? NEXT_STEP[decision.cause]
39
41
  : "Re-check later with 'dg verify', or override if you accept the risk.";
40
42
  return {
@@ -67,13 +69,13 @@ export function renderInstallDecision(decision) {
67
69
  if (decision.dashboardUrl) {
68
70
  lines.push(` Evidence: ${decision.dashboardUrl}`);
69
71
  }
70
- if (decision.unauthenticated) {
72
+ if (decision.unauthenticated && decision.cause !== "needs-login") {
71
73
  lines.push(" Auth: local policy only (run 'dg login' for full coverage)");
72
74
  }
73
75
  lines.push(decision.forceOverride && !decision.forceOverride.allowed
74
76
  ? " Override: not allowed by your policy"
75
77
  : " Override: re-run with --dg-force-install");
76
- const next = verifiedBad
78
+ const next = verifiedBad || decision.cause === "needs-login"
77
79
  ? NEXT_STEP[decision.cause]
78
80
  : "Re-check later with 'dg verify', or override if you accept the risk.";
79
81
  if (next) {
@@ -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,
@@ -655,6 +657,15 @@ async function lookupVerdict(options, target, sha256, upstream, identity) {
655
657
  reason: "You've reached your monthly scan limit. Upgrade at westbayberry.com/pricing or wait for it to reset."
656
658
  };
657
659
  }
660
+ if (response.status === 401) {
661
+ return {
662
+ verdict: "block",
663
+ packageName: artifactDisplayName(identity),
664
+ cause: "needs-login",
665
+ unauthenticated: true,
666
+ reason: "Checking a package from the registry before it installs requires sign-in."
667
+ };
668
+ }
658
669
  if (!response.ok) {
659
670
  return {
660
671
  verdict: "block",
@@ -898,6 +909,7 @@ function isProxyCause(value) {
898
909
  "license",
899
910
  "hash-mismatch",
900
911
  "private-upload-disabled",
912
+ "needs-login",
901
913
  "api-unavailable",
902
914
  "quota-exceeded",
903
915
  "api-timeout",
@@ -37,6 +37,19 @@ function actionBadge(action) {
37
37
  return { label: "Unknown", color: chalk.cyan };
38
38
  return { label: "Pass", color: chalk.green };
39
39
  }
40
+ function isYankedIncomplete(pkg) {
41
+ if (pkg.action !== "analysis_incomplete")
42
+ return false;
43
+ const haystack = [...(pkg.reasons ?? []), ...pkg.findings.map((f) => f.title ?? "")]
44
+ .join(" ")
45
+ .toLowerCase();
46
+ return haystack.includes("unpublish") || haystack.includes("yank") || haystack.includes("removed from the registr");
47
+ }
48
+ export function packageBadge(pkg) {
49
+ if (isYankedIncomplete(pkg))
50
+ return { label: "Removed", color: chalk.yellow };
51
+ return actionBadge(pkg.action);
52
+ }
40
53
  const EVIDENCE_LIMIT = 2;
41
54
  // Fixed lines outside the scrollable group area:
42
55
  // 5 ScoreHeader box | 2 Flagged box top | 2 scroll indicators
@@ -1066,7 +1079,7 @@ export const InteractiveResultsView = ({ result, config: _config, durationMs, on
1066
1079
  const dpGroup = groups[detailPane.groupIndex];
1067
1080
  if (dpGroup) {
1068
1081
  const dpRep = firstPackage(dpGroup);
1069
- const { color: dpColor } = actionBadge(dpRep.action);
1082
+ const { color: dpColor } = packageBadge(dpRep);
1070
1083
  const dpScroll = detailPane.scroll;
1071
1084
  const dpAbove = dpScroll;
1072
1085
  const dpBelow = Math.max(0, detailLines.length - dpScroll - detailContentRows);
@@ -1080,7 +1093,7 @@ export const InteractiveResultsView = ({ result, config: _config, durationMs, on
1080
1093
  const isCursor = globalIdx === clampedCursor;
1081
1094
  const level = getLevel(globalIdx);
1082
1095
  const rep = firstPackage(group);
1083
- const { label, color } = actionBadge(rep.action);
1096
+ const { label, color } = packageBadge(rep);
1084
1097
  const names = groupNames(group);
1085
1098
  const scoreStr = String(rep.score);
1086
1099
  const lcInfo = rep.license;
@@ -17,6 +17,10 @@ export const RC_SENTINEL = "dg-shell-rc-v1";
17
17
  export const GUARD_HOOK_SENTINEL = "dg-git-hook-v1";
18
18
  export const RC_BEGIN = "# >>> dg setup >>>";
19
19
  export const RC_END = "# <<< dg setup <<<";
20
+ const LEGACY_RC_MARKERS = [
21
+ { begin: "# >>> dg-managed >>>", end: "# <<< dg-managed <<<" }
22
+ ];
23
+ const LEGACY_RC_CANDIDATES = [".zshrc", ".bashrc", ".bash_profile", ".profile", join(".config", "fish", "config.fish")];
20
24
  export const SETUP_UNINSTALL_LOCK = "setup-uninstall";
21
25
  export const SETUP_UNINSTALL_LOCK_STALE_MS = 30 * 60 * 1000;
22
26
  export const STALE_SESSION_OLDER_THAN_MS = 24 * 60 * 60 * 1000;
@@ -191,6 +195,7 @@ export function uninstallSetup(options) {
191
195
  reverseGitHookEntry(entry, removed, missing, warnings);
192
196
  }
193
197
  }
198
+ sweepLegacyRcBlocks(paths.homeDir, removed, warnings);
194
199
  if (!options.all && !registryRead.malformed && options.keepConfig) {
195
200
  writeRegistryWithLock(paths, {
196
201
  version: 1,
@@ -481,6 +486,37 @@ function stripRcBlock(existing) {
481
486
  const unterminatedPattern = new RegExp(`${escapeRegex(RC_BEGIN)}\\n[\\s\\S]*$`, "g");
482
487
  return existing.replace(pattern, "").replace(unterminatedPattern, "");
483
488
  }
489
+ function sweepLegacyRcBlocks(homeDir, removed, warnings) {
490
+ for (const rel of LEGACY_RC_CANDIDATES) {
491
+ const rcPath = join(homeDir, rel);
492
+ const existing = readText(rcPath);
493
+ if (!existing) {
494
+ continue;
495
+ }
496
+ let next = existing;
497
+ for (const marker of LEGACY_RC_MARKERS) {
498
+ if (!next.includes(marker.begin)) {
499
+ continue;
500
+ }
501
+ if (!next.includes(marker.end)) {
502
+ warnings.push(`legacy dg block in ${rcPath} is missing its end marker; left untouched`);
503
+ continue;
504
+ }
505
+ const pattern = new RegExp(`${escapeRegex(marker.begin)}\\n[\\s\\S]*?${escapeRegex(marker.end)}\\n?`, "g");
506
+ next = next.replace(pattern, "");
507
+ }
508
+ if (next === existing) {
509
+ continue;
510
+ }
511
+ try {
512
+ writeFileSync(rcPath, next, "utf8");
513
+ removed.push(`${rcPath} (legacy dg block)`);
514
+ }
515
+ catch (error) {
516
+ warnings.push(`could not strip legacy dg block from ${rcPath}: ${error instanceof Error ? error.message : "write error"}`);
517
+ }
518
+ }
519
+ }
484
520
  export function cleanupEntry(kind, path, mode, now, sentinel) {
485
521
  return {
486
522
  kind,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@westbayberry/dg",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "description": "Dependency Guardian supply-chain firewall CLI",
5
5
  "type": "module",
6
6
  "bin": {