@lateos/npm-scan 0.7.6 โ†’ 0.9.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.
package/.dockerignore ADDED
@@ -0,0 +1,20 @@
1
+ node_modules
2
+ .git
3
+ .env
4
+ *.log
5
+ *.tmp
6
+ *.swp
7
+ coverage
8
+ .nyc_output
9
+ tests
10
+ docs
11
+ docker
12
+ *.md
13
+ !README.md
14
+ .eslintrc*
15
+ .prettierrc*
16
+ tsconfig*
17
+ .vscode
18
+ .idea
19
+ *.test.js
20
+ *.spec.js
package/README.md CHANGED
@@ -1,122 +1,383 @@
1
- # npm-scan
1
+ # @lateos/npm-scan
2
2
 
3
- Powerful npm supply chain security scanner. Detects malicious packages, supply chain attacks, and generates SBOM + compliance reports.
3
+ [![npm version](https://img.shields.io/npm/v/@lateos/npm-scan?style=flat-square)](https://www.npmjs.com/package/@lateos/npm-scan)
4
+ [![License](https://img.shields.io/badge/license-Apache%202.0%20%2B%20Commons%20Clause-blue?style=flat-square)](LICENSING.md)
5
+ [![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen?style=flat-square)](package.json)
4
6
 
5
- ## Quick Start
7
+ **Modern supply chain security for the npm ecosystem.**
8
+ Static + behavioral analysis that catches what npm audit, Snyk, and Socket miss โ€” obfuscated payloads, credential stealers, conditional triggers, sandbox evasion, and worm-like propagation.
9
+
10
+ ---
11
+
12
+ ## ๐Ÿ“Œ The Problem
13
+
14
+ The 2025โ€“2026 wave of npm supply chain attacks proved that traditional tooling is no longer enough.
15
+
16
+ Attackers have moved past simple typosquatting. They now ship **obfuscated preinstall hooks**, **credential harvesters hidden behind environment detection**, **dormant backdoors with time-based activation**, and **worm-style transitive propagation** that spreads through peer dependencies.
17
+
18
+ **npm audit** checks known CVEs. **Snyk** scans for vulnerabilities. **Socket** looks at package behavior. None of them were designed for the generation of attacks that emerged in 2025 โ€” attacks that look benign until they reach production.
19
+
20
+ **@lateos/npm-scan** was built for this moment.
21
+
22
+ ---
23
+
24
+ ## ๐Ÿ”ฌ Why @lateos/npm-scan?
25
+
26
+ | Capability | npm audit | Snyk | Socket | **@lateos/npm-scan** |
27
+ |---|---|---|---|---|
28
+ | Known CVE matching | โœ… | โœ… | โŒ | โœ… |
29
+ | Static analysis | โŒ | โœ… | โœ… | โœ… |
30
+ | Obfuscated payload detection | โŒ | โŒ | โŒ | โœ… |
31
+ | Behavioral / heuristic analysis | โŒ | โŒ | Partial | โœ… |
32
+ | Conditional trigger detection (ATK-009) | โŒ | โŒ | โŒ | โœ… |
33
+ | Sandbox evasion detection (ATK-010) | โŒ | โŒ | โŒ | โœ… |
34
+ | Transitive worm propagation (ATK-011) | โŒ | โŒ | โŒ | โœ… |
35
+ | Attack taxonomy (ATK series) | โŒ | โŒ | โŒ | โœ… |
36
+ | SBOM output (CycloneDX + SPDX) | โŒ | โœ… | โŒ | โœ… |
37
+ | NIST 800-161 compliance reporting | โŒ | โŒ | โŒ | โœ… |
38
+ | EU CRA compliance reporting | โŒ | โŒ | โŒ | โœ… |
39
+ | SIEM export (CEF / ECS / Sentinel / QRadar) | โŒ | โŒ | โŒ | โœ… |
40
+ | Runs entirely locally โ€” no telemetry | โœ… | โŒ | โŒ | โœ… |
41
+ | Policy-as-code (YAML allowlists) | โŒ | โŒ | โŒ | โœ… |
42
+
43
+ > **Privacy first.** All scanning happens on your machine. No code leaves your environment. No telemetry. No cloud dependency.
44
+
45
+ ---
46
+
47
+ ## โœจ Key Features
48
+
49
+ | Icon | Feature | Description |
50
+ |------|---------|-------------|
51
+ | ๐Ÿ•ต๏ธ | **Heuristic static analysis** | AST-level inspection catches obfuscation, eval chains, env probing, and suspicious lifecycle scripts that regex-based tools miss |
52
+ | ๐Ÿง  | **Behavioral detection** | Identifies conditional triggers (time-based, CI-aware), sandbox evasion, and dormant activation patterns |
53
+ | ๐Ÿงฌ | **ATK attack taxonomy** | 11 classified attack types with NIST 800-161 mappings โ€” versioned, documented, and PR-able |
54
+ | ๐Ÿ“ฆ | **SBOM generation** | CycloneDX 1.5 and SPDX 2.3 with findings embedded as vulnerabilities |
55
+ | ๐Ÿงพ | **Compliance reporting** | NIST SP 800-161 traceability matrix + EU Cyber Resilience Act mapping (free tier) |
56
+ | ๐Ÿ”Œ | **SIEM export** | Splunk CEF, Elastic ECS, Microsoft Sentinel, IBM QRadar formats (premium) |
57
+ | ๐Ÿ“œ | **Policy-as-code** | YAML/JSON policy engine with allowlists, severity overrides, suppressions, and fail-on thresholds |
58
+ | ๐Ÿณ | **Docker + GitHub Action** | Multi-arch images, one-command Compose pipeline, PR scan action |
59
+ | ๐Ÿ›ก๏ธ | **Zero telemetry** | No data leaves your machine. No cloud. No callbacks. |
60
+ | ๐Ÿ’พ | **Local scan history** | SQLite-backed persistence, zero external dependencies |
61
+
62
+ ---
63
+
64
+ ## โšก Quick Start
6
65
 
7
66
  ```bash
67
+ # Install globally
8
68
  npm install -g @lateos/npm-scan
69
+
70
+ # Scan a single package
9
71
  npm-scan scan lodash
72
+
73
+ # Scan your lockfile
74
+ npm-scan scan-lockfile
75
+
76
+ # View latest scans
77
+ npm-scan report
10
78
  ```
11
79
 
12
- Or run without install:
80
+ **No install? No problem:**
13
81
 
14
82
  ```bash
15
- npx @lateos/npm-scan scan lodash
83
+ npx @lateos/npm-scan scan commander
16
84
  ```
17
85
 
18
- ## Features
86
+ ---
87
+
88
+ ## ๐Ÿ“– Usage Examples
89
+
90
+ ### Scan a single package
19
91
 
20
- - **Static Analysis** โ€” detects malicious lifecycle scripts, obfuscated payloads, credential harvesting, persistence, network exfiltration, dependency confusion, typosquatting, tarball tampering, conditional triggers, sandbox evasion, and transitive propagation (ATK-001โ€“011)
21
- - **SBOM Output** โ€” CycloneDX 1.5 and SPDX 2.3 with findings mapped as vulnerabilities
22
- - **NIST 800-161 Compliance** โ€” HTML report includes control traceability matrix (SR-2.1 โ†’ SR-11.4)
23
- - **EU CRA Compliance** โ€” report maps findings to Cyber Resilience Act articles and Annex I requirements
24
- - **SIEM Export** โ€” Splunk CEF, Elastic ECS, Microsoft Sentinel, IBM QRadar formats (premium)
25
- - **EU CRA Compliance** โ€” report maps findings to Cyber Resilience Act articles (premium)
26
- - **License Key Gating** โ€” premium features locked behind signed license keys
27
- - **REST API** โ€” FastAPI-based API with webhooks, auth, scan management (premium)
28
- - **SAML SSO** โ€” enterprise single sign-on via Okta, Azure AD, OneLogin, Keycloak (enterprise)
29
- - **Kubernetes / Helm** โ€” Helm chart for deploying the full pipeline on K8s (premium)
30
- - **SQLite Storage** โ€” local scan history, zero external dependencies
31
- - **CLI** โ€” `scan`, `scan-lockfile`, `report --sbom --html --nist --cra --siem`
32
- - **Dynamic Sandbox** โ€” gVisor-based isolation (premium, documented in `docs/sandbox-threat-model.md`)
33
- - **GitHub Action** โ€” scans lockfile on PRs
34
- - **Docker** โ€” multi-arch images via GHCR
92
+ ```bash
93
+ # Default JSON output with all findings
94
+ npm-scan scan axios
35
95
 
36
- ## Commands
96
+ # Generate an SBOM alongside the scan
97
+ npm-scan scan express --sbom # CycloneDX JSON
98
+ npm-scan scan express --sbom xml # CycloneDX XML
99
+ npm-scan scan express --sbom spdx # SPDX 2.3
37
100
 
101
+ # Apply a YAML policy
102
+ npm-scan scan some-package --policy .npm-scan.yml
38
103
  ```
39
- npm-scan scan <package> Scan a package from the npm registry
40
- npm-scan scan <package> --sbom Scan + output CycloneDX SBOM
41
- npm-scan scan <package> --sbom spdx Scan + output SPDX SBOM
42
- npm-scan scan-lockfile Scan a local package-lock.json
43
- npm-scan report List recent scans
44
- npm-scan report -i <id> Show findings for a scan
45
- npm-scan report -i <id> --sbom Generate CycloneDX SBOM
46
- npm-scan report -i <id> --sbom spdx Generate SPDX SBOM
47
- npm-scan report -i <id> --html Generate HTML report (with NIST table)
48
- npm-scan report -i <id> --nist Print NIST 800-161 compliance table
49
- npm-scan report -i <id> --cra Print EU CRA compliance table
50
- npm-scan report -i <id> --siem <fmt> Generate SIEM output (cef|ecs|sentinel|qradar) (premium)
51
- npm-scan report --html Generate HTML report for all scans
52
- npm-scan report --nist Print NIST compliance for all scans
53
- npm-scan report --cra Print EU CRA compliance for all scans (premium)
54
- npm-scan report --siem cef Generate SIEM for all scans (premium)
104
+
105
+ ### Scan a lockfile
106
+
107
+ ```bash
108
+ # Scan the current project's dependencies
109
+ npm-scan scan-lockfile
110
+
111
+ # Scan a specific lockfile
112
+ npm-scan scan-lockfile -f ./path/to/package-lock.json
113
+ ```
114
+
115
+ ### Generate reports
116
+
117
+ ```bash
118
+ # List all recent scans
119
+ npm-scan report
120
+
121
+ # View a specific scan
122
+ npm-scan report -i 42
123
+
124
+ # Generate an HTML report (free) with full findings + NIST table
125
+ npm-scan report -i 42 --html
126
+
127
+ # Print NIST 800-161 compliance table
128
+ npm-scan report -i 42 --nist
129
+
130
+ # Print EU CRA compliance table
131
+ npm-scan report --cra
132
+
133
+ # Text report (free)
134
+ npm-scan report --text
135
+
136
+ # PDF report (premium)
137
+ npm-scan report --pdf --license-key <key>
138
+
139
+ # SIEM export (premium)
140
+ npm-scan report --siem cef # Splunk CEF
141
+ npm-scan report --siem ecs # Elastic ECS
142
+ npm-scan report --siem sentinel # Microsoft Sentinel
143
+ npm-scan report --siem qradar # IBM QRadar
144
+
145
+ # Combine all scans into a single report
146
+ npm-scan report --html # all scans
147
+ npm-scan report --pdf # all scans (premium)
148
+ ```
149
+
150
+ ---
151
+
152
+ ## ๐Ÿงฌ Detection Capabilities (ATK Taxonomy)
153
+
154
+ | ID | Attack Class | Detection Method | Severity | NIST 800-161 |
155
+ |---|---|---|---|---|
156
+ | **ATK-001** | Malicious lifecycle scripts (`preinstall`, `postinstall`, `install`) | Static | ๐Ÿ”ด high | SR-3.1 |
157
+ | **ATK-002** | Obfuscated payload delivery (hex, base64, eval chains) | Static | ๐ŸŸ  medium | SR-4.2 |
158
+ | **ATK-003** | Credential harvesting (env vars, .npmrc, SSH keys) | Static + Dynamic | ๐Ÿ”ด high | SR-5.3 |
159
+ | **ATK-004** | Persistence via editor/config dirs (.vscode, .claude, .cursor) | Static | ๐Ÿ”ด high | SR-6.4 |
160
+ | **ATK-005** | Network exfiltration (GitHub API, DNS tunneling, HTTP C2) | Static + Dynamic | โšซ critical | SR-7.5 |
161
+ | **ATK-006** | Dependency confusion / namespace squatting | Static (lockfile) | ๐ŸŸ  medium | SR-2.2 |
162
+ | **ATK-007** | Typosquatting (edit-distance matching) | Static | ๐ŸŸข low | SR-2.1 |
163
+ | **ATK-008** | Tarball tampering (published โ‰  source) | Static | ๐Ÿ”ด high | SR-8.1 |
164
+ | **ATK-009** | Conditional/dormant triggers (CI detection, time-based) | Behavioral | ๐Ÿ”ด high | SR-9.2 |
165
+ | **ATK-010** | Sandbox evasion / anti-analysis | Behavioral | ๐ŸŸ  medium | SR-10.3 |
166
+ | **ATK-011** | Transitive propagation (worm-style lateral spread) | Behavioral | ๐Ÿ”ด high | SR-11.4 |
167
+
168
+ > **How evasive attacks are caught:** ATK-009 detects packages that check `process.env.CI`, probe hostnames, or use time-based activation. ATK-010 flags `debugger` statements, `os.hostname()` probes, and env fingerprinting. ATK-011 traces peer dependency graphs to detect worm-like propagation patterns.
169
+ > See [`docs/attack-taxonomy.md`](docs/attack-taxonomy.md) for full evasion surface documentation and PoC examples.
170
+
171
+ ---
172
+
173
+ ## ๐Ÿ“Š Output & Reports
174
+
175
+ ### Formats
176
+
177
+ | Format | Availability | Description |
178
+ |--------|-------------|-------------|
179
+ | JSON | โœ… Free | Structured machine-readable findings |
180
+ | HTML | โœ… Free | Rich HTML report with NIST compliance table, severity badges, control matrix |
181
+ | Text | โœ… Free | Clean terminal-friendly text report |
182
+ | CycloneDX SBOM | โœ… Free | Industry-standard SBOM with findings as vulnerabilities |
183
+ | SPDX SBOM | โœ… Free | SPDX 2.3 document format |
184
+ | NIST 800-161 | โœ… Free | Control traceability matrix (SR-2.1 โ†’ SR-11.4) |
185
+ | EU CRA | โœ… Free | Cyber Resilience Act article mapping |
186
+ | PDF | ๐Ÿ” Premium | Multi-page PDF with title page, findings table, NIST compliance matrix |
187
+ | Splunk CEF | ๐Ÿ” Premium | Common Event Format for Splunk ingestion |
188
+ | Elastic ECS | ๐Ÿ” Premium | Elastic Common Schema format |
189
+ | Microsoft Sentinel | ๐Ÿ” Premium | Sentinel-ready formatted output |
190
+ | IBM QRadar | ๐Ÿ” Premium | QRadar DSM-ready format with QID mappings |
191
+
192
+ ### Sample output
193
+
194
+ ```json
195
+ {
196
+ "scanId": 1,
197
+ "findings": [
198
+ {
199
+ "id": "ATK-003",
200
+ "severity": "high",
201
+ "title": "Credential harvesting",
202
+ "evidence": "process.env.NPM_TOKEN detected in postinstall.js:17"
203
+ }
204
+ ]
205
+ }
55
206
  ```
56
207
 
57
- ## Architecture
208
+ ---
209
+
210
+ ## โš™๏ธ Configuration & Advanced Usage
211
+
212
+ ### Policy-as-code
213
+
214
+ Define allowlists, severity overrides, suppressions, and fail thresholds in a YAML file:
215
+
216
+ ```yaml
217
+ # .npm-scan.yml
218
+ allowlist:
219
+ - lodash
220
+ - chalk
58
221
 
222
+ severity_overrides:
223
+ - id: ATK-001
224
+ severity: medium
225
+
226
+ suppress:
227
+ - atk_id: ATK-009
228
+ - package: some-package
229
+
230
+ fail_on: high
231
+ ```
232
+
233
+ ```bash
234
+ npm-scan scan target --policy .npm-scan.yml
59
235
  ```
60
- cli/ Commander.js CLI entrypoint
61
- backend/ Detectors, fetch, SQLite db, SBOM, report, license, SIEM, CRA
62
- api/ FastAPI REST API + webhooks (premium)
63
- docker/ Multi-arch Docker images + compose
64
- deploy/ Kubernetes Helm chart (premium)
65
- docs/ Project plan, attack taxonomy (ATK), sandbox threat model
66
- tests/ Corpus: 5 clean + 33 malicious packages
236
+
237
+ ### Environment variables
238
+
239
+ | Variable | Description | Default |
240
+ |----------|-------------|---------|
241
+ | `NPM_SCAN_LICENSE_KEY` | Premium / enterprise license key | โ€” |
242
+ | `NPM_SCAN_DATA_DIR` | Scan history directory | `./.npm-scan` |
243
+ | `NPM_SCAN_LOG_LEVEL` | Log verbosity | `info` |
244
+
245
+ ### Premium licensing
246
+
247
+ ```bash
248
+ # Generate a development key
249
+ node -e "console.log(require('@lateos/npm-scan/backend/license').generateKey('premium'))"
250
+
251
+ # Use it
252
+ npm-scan scan target --license-key <key>
253
+ npm-scan report --pdf --license-key <key>
254
+ npm-scan report --siem cef --license-key <key>
67
255
  ```
68
256
 
69
- ## Detectors (ATK Taxonomy)
257
+ ---
258
+
259
+ ## ๐Ÿ”— Integrations
70
260
 
71
- | ID | Class | Severity |
72
- |---------|--------------------------------------------|----------|
73
- | ATK-001 | Malicious lifecycle scripts | high |
74
- | ATK-002 | Obfuscated payloads | medium |
75
- | ATK-003 | Credential harvesting | high |
76
- | ATK-004 | Persistence via editor configs | high |
77
- | ATK-005 | Network exfiltration | critical |
78
- | ATK-006 | Dependency confusion | medium |
79
- | ATK-007 | Typosquatting | low |
80
- | ATK-008 | Tarball tampering (published โ‰  source) | high |
81
- | ATK-009 | Conditional/dormant triggers (CI, time) | high |
82
- | ATK-010 | Sandbox evasion / anti-analysis | medium |
83
- | ATK-011 | Transitive propagation (worm) | high |
261
+ ### GitHub Action
84
262
 
85
- See `docs/attack-taxonomy.md` for full NIST 800-161 mappings, evasion surfaces, and PoC examples.
263
+ Scan your lockfile on every PR. Add to `.github/workflows/scan.yml`:
86
264
 
87
- ## Enterprise Features
265
+ ```yaml
266
+ name: npm-scan
267
+ on: [pull_request]
268
+ jobs:
269
+ scan:
270
+ runs-on: ubuntu-latest
271
+ steps:
272
+ - uses: actions/checkout@v4
273
+ - uses: lateos/npm-scan-action@v1
274
+ with:
275
+ lockfile: package-lock.json
276
+ policy: .npm-scan.yml # optional
277
+ license-key: ${{ secrets.NPM_SCAN_LICENSE_KEY }} # optional (premium)
278
+ ```
88
279
 
89
- ### SAML SSO
280
+ ### Docker
90
281
 
91
- SAML 2.0 single sign-on for enterprise deployments. Supports:
282
+ ```bash
283
+ # Pull and run
284
+ docker pull ghcr.io/lateos/npm-scan:cli
285
+ docker run --rm ghcr.io/lateos/npm-scan:cli scan lodash
92
286
 
93
- - **IdPs:** Okta, Azure AD / Entra ID, OneLogin, Keycloak, any SAML 2.0 compliant provider
94
- - **Flow:** SP-initiated SSO redirect โ†’ IdP auth โ†’ assertion validation โ†’ JWT issuance
95
- - **Provisioning:** auto-creates users from SAML attributes with RBAC (admin/editor/viewer)
96
- - **Security:** signed AuthnRequests, verified assertions, HMAC-SHA256 JWTs, Single Logout
287
+ # Full pipeline with Compose (Redis-based queue)
288
+ docker compose --profile pipeline up -d
97
289
 
290
+ # CLI with persistent storage
291
+ docker compose --profile cli up -d
98
292
  ```
99
- GET /api/v1/sso/metadata # SP metadata XML for IdP registration
100
- GET /api/v1/sso/login # Start SSO (redirects to IdP)
101
- POST /api/v1/sso/acs # SAML callback (IdP POSTs here)
102
- POST /api/v1/sso/slo # Single Logout
293
+
294
+ Multi-arch images available for `linux/amd64` and `linux/arm64`.
295
+
296
+ ### CI/CD
297
+
298
+ ```bash
299
+ # Fail the build if critical findings exist
300
+ npm-scan scan express --policy .npm-scan.yml || exit 1
103
301
  ```
104
302
 
105
- Requires enterprise license. Configure via env vars or `api/saml-config.yaml`. See `api/README.md` for full docs.
303
+ ---
106
304
 
107
- ### REST API
305
+ ## ๐Ÿ—บ๏ธ Roadmap & Enterprise Features
108
306
 
109
- FastAPI-based API for the hosted tier. See `api/README.md` for endpoint reference, auth methods, and configuration.
307
+ ### Free tier (shipped)
110
308
 
111
- ## Development
309
+ - All 11 ATK detectors (static + behavioral)
310
+ - SBOM output (CycloneDX + SPDX)
311
+ - HTML, text, and compliance reports (NIST + EU CRA)
312
+ - Policy-as-code engine (YAML)
313
+ - Local SQLite scan history
314
+ - GitHub Action
315
+ - Docker images + Compose pipeline
316
+
317
+ ### Premium (๐Ÿ” license key)
318
+
319
+ - PDF compliance reports with NIST traceability matrix
320
+ - SIEM export (Splunk CEF, Elastic ECS, Microsoft Sentinel, IBM QRadar)
321
+ - Dynamic sandbox (gVisor-based โ€” ATK-008โ€“010)
322
+ - Reachability analysis (call graph filtering)
323
+
324
+ ### Enterprise (๐Ÿข custom license)
325
+
326
+ - SAML 2.0 SSO (Okta, Azure AD, OneLogin, Keycloak)
327
+ - REST API + webhooks (FastAPI)
328
+ - Team RBAC + audit logs
329
+ - Helm chart for Kubernetes deployment
330
+ - PostgreSQL backend for hosted/team tier
331
+ - SLA-backed priority support
332
+
333
+ ---
334
+
335
+ ## ๐Ÿค Contributing
336
+
337
+ We welcome contributions โ€” especially new detectors, improved evasion resistance, and compliance templates.
338
+
339
+ See [`docs/attack-taxonomy.md`](docs/attack-taxonomy.md) for the ATK governance process. Every new detector requires:
340
+
341
+ 1. A proof-of-concept sample
342
+ 2. A detection rule with tests
343
+ 3. False-positive analysis on top-500 npm packages
344
+ 4. NIST 800-161 control mapping
112
345
 
113
346
  ```bash
347
+ git clone https://github.com/lateos/npm-scan.git
114
348
  npm install
115
- npm run dev # CLI stub
116
- npm run test # Unit tests (14)
117
- npm run corpus # False-positive corpus test (33 malicious, 5 clean)
349
+ npm test
118
350
  ```
119
351
 
120
- ## License
352
+ ### Need help?
353
+
354
+ - ๐Ÿ“– Read the [project plan](docs/project-plan.md)
355
+ - ๐Ÿงฌ Review the [attack taxonomy](docs/attack-taxonomy.md)
356
+ - ๐Ÿ› Open an issue or PR
357
+
358
+ ---
359
+
360
+ ## ๐Ÿ“„ License
121
361
 
122
- Apache-2.0 core + Commons Clause premium. See `LICENSING.md`.
362
+ Apache-2.0 core + Commons Clause.
363
+ See [`LICENSING.md`](LICENSING.md) for the exact boundary between free and premium features.
364
+
365
+ ```
366
+ @lateos/npm-scan โ€” npm supply chain security scanner
367
+ Copyright (C) 2026 Lateos
368
+
369
+ Licensed under the Apache License, Version 2.0 (the "License");
370
+ you may not use this file except in compliance with the License.
371
+
372
+ Unless required by applicable law or agreed to in writing, software
373
+ distributed under the License is distributed on an "AS IS" BASIS,
374
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
375
+ ```
376
+
377
+ ---
378
+
379
+ **Scan your first package now:**
380
+
381
+ ```bash
382
+ npx @lateos/npm-scan scan lodash
383
+ ```
package/backend/db.js CHANGED
@@ -1,42 +1,88 @@
1
- import Database from 'better-sqlite3';
1
+ import initSqlJs from 'sql.js';
2
2
  import fs from 'fs';
3
3
  import path from 'path';
4
+ import { fileURLToPath } from 'url';
4
5
 
5
- const DB_PATH = 'npm-scan.db';
6
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
7
+ const DB_PATH = path.join(process.cwd(), 'npm-scan.db');
8
+ const SCHEMA_PATH = path.join(__dirname, 'db', 'schema.sql');
6
9
 
7
- let db;
10
+ let db = null;
11
+ let initPromise = null;
8
12
 
9
- function init() {
10
- db = new Database(DB_PATH);
11
- const schemaPath = path.join(process.cwd(), 'backend', 'db', 'schema.sql');
12
- const schema = fs.readFileSync(schemaPath, 'utf8');
13
- db.exec(schema);
13
+ async function ensureInit() {
14
+ if (db) return;
15
+ if (initPromise) return initPromise;
16
+ initPromise = (async () => {
17
+ const SQL = await initSqlJs();
18
+ if (fs.existsSync(DB_PATH)) {
19
+ db = new SQL.Database(fs.readFileSync(DB_PATH));
20
+ } else {
21
+ db = new SQL.Database();
22
+ }
23
+ if (fs.existsSync(SCHEMA_PATH)) {
24
+ db.run(fs.readFileSync(SCHEMA_PATH, 'utf8'));
25
+ }
26
+ })();
27
+ return initPromise;
14
28
  }
15
29
 
16
- init();
30
+ function queryAll(sql, params = []) {
31
+ const stmt = db.prepare(sql);
32
+ if (params.length) stmt.bind(params);
33
+ const rows = [];
34
+ while (stmt.step()) {
35
+ rows.push(stmt.getAsObject());
36
+ }
37
+ stmt.free();
38
+ return rows;
39
+ }
17
40
 
18
- export function saveScan(pkgName, version = 'latest', findings = []) {
19
- const scanStmt = db.prepare('INSERT INTO scans (package_name, version) VALUES (?, ?)');
20
- const scanId = scanStmt.run(pkgName, version).lastInsertRowid;
21
- const findStmt = db.prepare('INSERT INTO findings (scan_id, atk_id, severity, description, evidence) VALUES (?, ?, ?, ?, ?)');
41
+ function queryOne(sql, params = []) {
42
+ return queryAll(sql, params)[0] || null;
43
+ }
44
+
45
+ function lastId() {
46
+ const r = db.exec("SELECT last_insert_rowid()");
47
+ return Number(r[0].values[0][0]);
48
+ }
49
+
50
+ function persist() {
51
+ fs.writeFileSync(DB_PATH, Buffer.from(db.export()));
52
+ }
53
+
54
+ export async function saveScan(pkgName, version = 'latest', findings = []) {
55
+ await ensureInit();
56
+ db.run("INSERT INTO scans (package_name, version) VALUES (?, ?)", [pkgName, version]);
57
+ const scanId = lastId();
58
+ const stmt = db.prepare("INSERT INTO findings (scan_id, atk_id, severity, description, evidence) VALUES (?, ?, ?, ?, ?)");
22
59
  for (const f of findings) {
23
- findStmt.run(scanId, f.id, f.severity, f.title || f.description, f.evidence || '');
60
+ stmt.run([scanId, f.id, f.severity, f.title || f.description, f.evidence || '']);
24
61
  }
62
+ stmt.free();
63
+ persist();
25
64
  return scanId;
26
65
  }
27
66
 
28
- export function getRecentScans(limit = 10) {
29
- return db.prepare('SELECT * FROM scans ORDER BY scanned_at DESC LIMIT ?').all(limit);
67
+ export async function getRecentScans(limit = 10) {
68
+ await ensureInit();
69
+ return queryAll("SELECT * FROM scans ORDER BY scanned_at DESC LIMIT ?", [limit]);
30
70
  }
31
71
 
32
- export function getFindings(scanId) {
33
- return db.prepare('SELECT * FROM findings WHERE scan_id = ?').all(scanId);
72
+ export async function getFindings(scanId) {
73
+ await ensureInit();
74
+ return queryAll("SELECT * FROM findings WHERE scan_id = ?", [scanId]);
34
75
  }
35
76
 
36
- export function getScan(scanId) {
37
- return db.prepare('SELECT * FROM scans WHERE id = ?').get(scanId);
77
+ export async function getScan(scanId) {
78
+ await ensureInit();
79
+ return queryOne("SELECT * FROM scans WHERE id = ?", [scanId]);
38
80
  }
39
81
 
40
- export function close() {
41
- db.close();
82
+ export async function close() {
83
+ if (db) {
84
+ persist();
85
+ db.close();
86
+ db = null;
87
+ }
42
88
  }