@westbayberry/dg 1.0.59 → 1.0.60

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 (3) hide show
  1. package/dist/index.mjs +72 -13
  2. package/package.json +1 -1
  3. package/README.md +0 -129
package/dist/index.mjs CHANGED
@@ -29172,6 +29172,8 @@ var init_esm5 = __esm({
29172
29172
  init_logger();
29173
29173
  init_instrument();
29174
29174
  init_sdk();
29175
+ init_onuncaughtexception();
29176
+ init_onunhandledrejection();
29175
29177
  init_addOriginToSpan();
29176
29178
  init_getRequestUrl();
29177
29179
  init_nodeVersion();
@@ -46144,6 +46146,7 @@ var init_esm6 = __esm({
46144
46146
  "node_modules/@sentry/node/build/esm/index.js"() {
46145
46147
  init_sdk2();
46146
46148
  init_esm3();
46149
+ init_esm5();
46147
46150
  }
46148
46151
  });
46149
46152
 
@@ -46158,6 +46161,18 @@ function initTelemetry(version) {
46158
46161
  // TODO: reduce sample rate when CLI usage exceeds ~10k DAU
46159
46162
  sampleRate: 1,
46160
46163
  sendDefaultPii: false,
46164
+ // Sentry's default Node integrations patch `fs`, which collides
46165
+ // with node-sqlite3-wasm's NODEFS layer — every Database open
46166
+ // returned "unable to open database file" and the local scan
46167
+ // cache stayed empty (0 rows even after thousands of scans).
46168
+ // Opt out of the default integrations entirely; we keep crash +
46169
+ // unhandled-rejection reporting via the two explicit integrations
46170
+ // below, which don't touch fs.
46171
+ defaultIntegrations: false,
46172
+ integrations: [
46173
+ onUncaughtExceptionIntegration({ exitEvenIfOtherHandlersAreRegistered: false }),
46174
+ onUnhandledRejectionIntegration()
46175
+ ],
46161
46176
  beforeSend(event) {
46162
46177
  if (event.exception?.values?.some(
46163
46178
  (ex) => ex.value && /\.(invalid|test|example|localhost)\b/i.test(ex.value)
@@ -84516,7 +84531,9 @@ var init_local_cache = __esm({
84516
84531
  // src/api/client.ts
84517
84532
  var client_exports = {};
84518
84533
  __export(client_exports, {
84534
+ ANON_BATCH_SIZE: () => ANON_BATCH_SIZE,
84519
84535
  APIError: () => APIError,
84536
+ BATCH_SIZE: () => BATCH_SIZE,
84520
84537
  ClientOutdatedError: () => ClientOutdatedError,
84521
84538
  TrialExhaustedError: () => TrialExhaustedError,
84522
84539
  callAnalyzeAPI: () => callAnalyzeAPI,
@@ -85087,8 +85104,8 @@ var init_client3 = __esm({
85087
85104
  this.name = "ClientOutdatedError";
85088
85105
  }
85089
85106
  };
85090
- BATCH_SIZE = 200;
85091
- ANON_BATCH_SIZE = 200;
85107
+ BATCH_SIZE = 50;
85108
+ ANON_BATCH_SIZE = 50;
85092
85109
  MAX_RETRIES = 2;
85093
85110
  RETRY_DELAY_MS = 5e3;
85094
85111
  DEFAULT_BATCH_CONCURRENCY = 4;
@@ -85143,6 +85160,19 @@ async function scanProjectAtPath(cwd2, config3, onProgress) {
85143
85160
  cumulativeDone += pyPackages.length;
85144
85161
  }
85145
85162
  const allPackages = responses.flatMap((r) => r.packages);
85163
+ if (allPackages.length === 0) {
85164
+ const elapsed = elapsedMs();
85165
+ return {
85166
+ status: "api_returned_empty",
85167
+ result: {
85168
+ result: { score: 0, action: "pass", packages: [], safeVersions: {}, durationMs: elapsed },
85169
+ durationMs: elapsed,
85170
+ scannedCount: 0,
85171
+ skippedCount: discovery.skipped?.length ?? 0
85172
+ },
85173
+ message: `Analyze API returned 0 results for ${totalDiscovered} discovered packages (npm: ${npmPackages.length}, pypi: ${pyPackages.length}). This is not a 'nothing to scan' state \u2014 the API call succeeded but returned an empty packages list. Check API status and re-run.`
85174
+ };
85175
+ }
85146
85176
  const maxScore = allPackages.length > 0 ? Math.max(0, ...allPackages.map((p) => p.score)) : 0;
85147
85177
  const anyIncomplete = responses.some((r) => r.action === "analysis_incomplete") || allPackages.some((p) => Array.isArray(p.findings) && p.findings.some((f) => f?.id === "analysis_incomplete"));
85148
85178
  const action = maxScore >= 70 ? "block" : maxScore >= 60 ? "warn" : anyIncomplete ? "analysis_incomplete" : "pass";
@@ -86029,18 +86059,26 @@ function useInit(opts = {}) {
86029
86059
  const maxScore = allPackages.length > 0 ? Math.max(0, ...allPackages.map((p) => p.score)) : 0;
86030
86060
  const action = maxScore >= 70 ? "block" : maxScore >= 60 ? "warn" : "pass";
86031
86061
  if (totalScanned === 0) {
86032
- dispatch({
86033
- type: "scan_done",
86034
- outcome: {
86035
- status: "no_packages",
86036
- result: {
86037
- result: { score: 0, action: "pass", packages: [], safeVersions: {}, durationMs: totalDuration },
86038
- durationMs: totalDuration,
86039
- scannedCount: 0,
86040
- skippedCount: 0
86062
+ const errOutcome = allOutcomes.find((o) => o.status === "error");
86063
+ const emptyApiOutcome = allOutcomes.find((o) => o.status === "api_returned_empty");
86064
+ const trialOutcome = allOutcomes.find((o) => o.status === "trial_exhausted");
86065
+ const meaningful = errOutcome ?? emptyApiOutcome ?? trialOutcome;
86066
+ if (meaningful) {
86067
+ dispatch({ type: "scan_done", outcome: meaningful });
86068
+ } else {
86069
+ dispatch({
86070
+ type: "scan_done",
86071
+ outcome: {
86072
+ status: "no_packages",
86073
+ result: {
86074
+ result: { score: 0, action: "pass", packages: [], safeVersions: {}, durationMs: totalDuration },
86075
+ durationMs: totalDuration,
86076
+ scannedCount: 0,
86077
+ skippedCount: 0
86078
+ }
86041
86079
  }
86042
- }
86043
- });
86080
+ });
86081
+ }
86044
86082
  } else {
86045
86083
  dispatch({
86046
86084
  type: "scan_done",
@@ -89122,6 +89160,12 @@ var init_ScanResultCard = __esm({
89122
89160
  score = 0;
89123
89161
  packages = [];
89124
89162
  extraNote = "Nothing to scan yet. I'll catch new packages as you add them.";
89163
+ } else if (o.status === "api_returned_empty") {
89164
+ verdict = "EMPTY";
89165
+ verdictColor = "yellow";
89166
+ score = 0;
89167
+ packages = [];
89168
+ extraNote = o.message ?? "Analyze API returned no results for discovered packages.";
89125
89169
  } else if (o.status === "trial_exhausted") {
89126
89170
  verdict = "EMPTY";
89127
89171
  verdictColor = "yellow";
@@ -89874,6 +89918,7 @@ var init_InitApp = __esm({
89874
89918
  return "happy";
89875
89919
  }
89876
89920
  if (r.status === "no_packages") return "happy";
89921
+ if (r.status === "api_returned_empty") return "alert";
89877
89922
  return "idle";
89878
89923
  })();
89879
89924
  const content = (() => {
@@ -90284,6 +90329,7 @@ var init_InitApp = __esm({
90284
90329
  const r = state.scanResult;
90285
90330
  const okScan = r?.status === "ok" && r.result;
90286
90331
  const empty = r?.status === "no_packages";
90332
+ const apiEmpty = r?.status === "api_returned_empty";
90287
90333
  const resultBody = [
90288
90334
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, children: "Scan complete." }, "hdr"),
90289
90335
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: " " }, "sp1"),
@@ -90291,6 +90337,10 @@ var init_InitApp = __esm({
90291
90337
  "I scanned ",
90292
90338
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "cyan", children: projectName }),
90293
90339
  "."
90340
+ ] }, "line") : apiEmpty ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { children: [
90341
+ "I found packages in ",
90342
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "cyan", children: projectName }),
90343
+ " but the analyze API returned nothing back. This is a server-side problem, not your project."
90294
90344
  ] }, "line") : empty ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(Text, { children: [
90295
90345
  "I didn't find any packages to scan in ",
90296
90346
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { bold: true, color: "cyan", children: projectName }),
@@ -90302,6 +90352,13 @@ var init_InitApp = __esm({
90302
90352
  ] }, "line"),
90303
90353
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: " " }, "sp2")
90304
90354
  ];
90355
+ if (apiEmpty && r?.message) {
90356
+ resultBody.push(
90357
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "yellow", children: r.message }, "api-empty-detail")
90358
+ );
90359
+ resultBody.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { dimColor: true, children: "Check westbayberry.com/status and re-run." }, "api-empty-hint"));
90360
+ resultBody.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { children: " " }, "api-empty-sp"));
90361
+ }
90305
90362
  if (state.scanResult) {
90306
90363
  resultBody.push(
90307
90364
  /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ScanResultCard, { maxWidth: cardMaxWidth, outcome: { kind: "real", outcome: state.scanResult } }, "card")
@@ -90644,6 +90701,8 @@ var init_InitApp = __esm({
90644
90701
  scan.result?.durationMs ?? 0,
90645
90702
  "ms)"
90646
90703
  ] }, "scan-empty"));
90704
+ } else if (scan.status === "api_returned_empty") {
90705
+ scanLine.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "yellow", children: "\u26A0 Analyze API returned no results \u2014 try again or check status" }, "scan-api-empty"));
90647
90706
  } else if (scan.status === "trial_exhausted") {
90648
90707
  scanLine.push(/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Text, { color: "yellow", children: "\u26A0 Free trial scans used up \u2014 `dg login` to continue" }, "scan-trial"));
90649
90708
  } else if (scan.status === "error") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@westbayberry/dg",
3
- "version": "1.0.59",
3
+ "version": "1.0.60",
4
4
  "description": "Supply chain security scanner for npm and Python dependencies — 35 behavioral detectors catch zero-day attacks CVE databases miss. 99.66% catch rate on 155K packages.",
5
5
  "bin": {
6
6
  "dependency-guardian": "dist/index.mjs",
package/README.md DELETED
@@ -1,129 +0,0 @@
1
- # @westbayberry/dg
2
-
3
- Behavioral supply chain security for npm and Python. Reads what packages
4
- actually do — install hooks, credential access, network exfiltration,
5
- obfuscation — and gates risky installs before they touch your machine.
6
-
7
- **53 detectors · 95.20% npm / 93.88% PyPI catch rate on 17,874 packages · 0.44% npm / 0.29% PyPI FPR** —
8
- [published methodology](https://westbayberry.com/benchmark).
9
-
10
- ## What `dg` actually does
11
-
12
- | Surface | What it does | Default mode |
13
- |---|---|---|
14
- | `dg npm install <pkg>` | Resolves the full dependency tree (top-level + transitive) without running install scripts, scans every package, then invokes the real `npm install` only if the verdict permits. Top-level versions are pinned to the exact version we scanned. Bare `dg npm install` honors `package-lock.json` pinned versions. | **block** |
15
- | `dg pip install <pkg>` | Resolves the full pip tree via `pip install --dry-run --report=-` (pip ≥ 23.0); scans the full set before invoking real `pip install`. On older pip, transitive scan is reported as unavailable and (in block mode) the install is refused. | **block** |
16
- | `dg scan` | Read-only audit. Diffs lockfiles and reports findings. Does **not** block anything by default. | warn |
17
- | `dg hook install` | Adds a pre-commit hook that runs `dg scan --mode block` when a lockfile change is staged. Quiet when nothing is wrong. | block |
18
-
19
- For maximum protection in CI/build pipelines, add `--strict` to the install
20
- wrapper. `--strict` implies `--dg-no-scripts` and refuses any partial-coverage
21
- scan (transitive enumeration must succeed).
22
-
23
- DG only activates when you intentionally invoke it, install the hook, or
24
- run it in CI. It does not globally hijack `npm`/`pip`.
25
-
26
- ## Install
27
-
28
- ```bash
29
- npm install -g @westbayberry/dg
30
- ```
31
-
32
- Requires Node.js 18+.
33
-
34
- ## Quick Start
35
-
36
- ```bash
37
- dg login
38
- dg scan # read-only audit of the current project
39
- ```
40
-
41
- Scope-truthful output: a clean project shows
42
- `✓ Dependency Guardian checked 142 packages. No risky behavior found.`
43
- A flagged install shows only the warn/block packages with one-line
44
- reasons and a clear next step.
45
-
46
- Supports `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`,
47
- `requirements.txt`, `Pipfile.lock`, and `poetry.lock`.
48
-
49
- ## Commands
50
-
51
- ```
52
- dg scan Read-only audit of the project's lockfiles
53
- dg status Show your auth state, plan tier, and whether protection is on
54
- dg login / logout Manage authentication
55
- dg npm install <pkg> Protective install wrapper (default mode: block)
56
- dg pip install <pkg> Protective install wrapper (default mode: block)
57
- dg protect Opt this project in to auto-protection on every install
58
- dg protect off Disable opt-in protection in this project
59
- dg wrap Print the shell-alias snippet without writing a project marker
60
- dg hook install Install a git pre-commit hook that gates lockfile diffs
61
- dg hook uninstall Remove the git pre-commit hook
62
- dg publish-check Self-scan before `npm publish` / `pip upload`
63
- dg update Check for and install the latest version
64
- ```
65
-
66
- Full reference: <https://westbayberry.com/docs/cli-reference>
67
-
68
- ## Common Options
69
-
70
- | Flag | Default | Description |
71
- |------|---------|-------------|
72
- | `--mode <mode>` | `warn` for `scan`, `block` for install wrappers | `block` / `warn` / `off` |
73
- | `--details` | off | Verbose table view (the older format) |
74
- | `--explain` | off | Plain-English explanation of findings |
75
- | `--json` | off | Stable machine-readable JSON (CI) |
76
- | `--ci` | auto when `CI=1` | Deterministic output, no spinners |
77
- | `--dg-force` | off | Bypass a block (visible in output, audit-friendly) |
78
- | `--dg-no-scripts` | off | Pass `--ignore-scripts` to the real install |
79
- | `--strict` | off | Refuse partial-coverage scans + auto `--dg-no-scripts` |
80
- | `--quiet` | off | Suppress login + update nudges (still prints findings) |
81
- | `--workspace <dir>` | | Scan a specific workspace subdirectory |
82
-
83
- ## Exit Codes
84
-
85
- | Code | Meaning |
86
- |------|---------|
87
- | `0` | Pass |
88
- | `1` | Warning |
89
- | `2` | Block |
90
- | `3` | Error |
91
-
92
- ## CI
93
-
94
- ```bash
95
- npx @westbayberry/dg login
96
- npx @westbayberry/dg scan --mode block --json
97
- ```
98
-
99
- Wire exit code `2` into your pipeline to fail the build.
100
-
101
- ## Configuration
102
-
103
- Settings come from CLI flags, environment variables, or a `.dgrc.json` file in the project or home directory. CLI flags take precedence.
104
-
105
- ```json
106
- {
107
- "apiKey": "dg_...",
108
- "mode": "block"
109
- }
110
- ```
111
-
112
- ## Telemetry
113
-
114
- The CLI sends anonymous crash reports to Sentry (error message, stack trace, Node version, OS, CLI version). It never sends package names, file paths, API keys, or scan results. API keys and home directory paths are redacted before transmission.
115
-
116
- Opt out:
117
-
118
- ```bash
119
- export DG_TELEMETRY=0 # or
120
- export DO_NOT_TRACK=1
121
- ```
122
-
123
- ## Links
124
-
125
- - [Dashboard](https://westbayberry.com/dashboard)
126
- - [Documentation](https://westbayberry.com/docs) — setup walkthrough
127
- - [CLI reference](https://westbayberry.com/docs/cli-reference) — every command and flag
128
- - [Pricing](https://westbayberry.com/pricing)
129
- - [Changelog](https://github.com/WestBayBerry/dependency-guardian-action/blob/main/CHANGELOG.md)