@lateos/npm-scan 0.9.6 → 0.9.9

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/CHANGELOG.md ADDED
@@ -0,0 +1,199 @@
1
+ # Changelog
2
+
3
+ All notable changes to [@lateos/npm-scan](https://github.com/lateos-ai/npm-scan) are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+ - `scan --file <path>` flag to analyze local `.tgz` tarballs without fetching from npm registry
11
+ - `scan --fail-on <level>` flag to exit with code 1 when findings >= severity (CI/CD integration)
12
+ - `scan --sarif [file]` to output SARIF v2.1 format for GitHub Advanced Security, VS Code, Azure DevOps
13
+ - `scan --csv [file]` and `report --csv [file]` to export tabular CSV for Excel/Sheets import
14
+ - `scan --score-only` to output only risk score (0-10), auto-added to JSON output
15
+ - Government/SOC 2 features: `--audit-log`, `--fips`, `--stig`, `--cache-dir` for air-gapped/federal compliance
16
+
17
+ ## [0.9.7] — 2026-05-12
18
+
19
+ - Sigstore provenance attestation on every publish via new GitHub Actions workflow
20
+ - Fix duplicate Docker section in README.md
21
+ - Add SECURITY.md with vulnerability disclosure policy and PGP key
22
+
23
+ ## [0.9.6] — 2026-05-12
24
+
25
+ - Add Docker badge (`ghcr.io/lateos/npm-scan`) to all 5 READMEs
26
+ - Add dedicated Docker quick-start section in all languages
27
+ - Replace duplicate Docker pull instructions in Integrations with cross-references
28
+
29
+ ## [0.9.5] — 2026-05-12
30
+
31
+ - Fix literal `\n` escape sequences in LICENSING.md (replaced with real newlines)
32
+
33
+ ## [0.9.4] — 2026-05-11
34
+
35
+ - Fix language badge links to use absolute GitHub URLs so they work from npm web UI
36
+ - Fix GitHub organization links from `lateos` to `lateos-ai` across all READMEs
37
+
38
+ ## [0.9.3] — 2026-05-11
39
+
40
+ - Add multi-language README: Chinese (`README.zh.md`), Japanese (`README.ja.md`), French (`README.fr.md`), German (`README.de.md`)
41
+ - Language-switcher badges with absolute GitHub URLs in all 5 READMEs
42
+
43
+ ## [0.9.2] — 2026-05-11
44
+
45
+ - **222 tests across 8 test files** (212 passing, 10 skipped for known FPs)
46
+ - **85% line coverage** with Node.js native test runner
47
+ - New test files: `test/db.test.js`, `test/detectors-edge-cases.test.js`, `test/detectors-corpus.test.js`, `test/report-snapshots.test.js`, `test/fetch.test.js`, `test/policy-edge-cases.test.js`, `test/cli.test.js`, `test/fixtures/mock-data.js`
48
+ - `backend/db.js:close()` resets `initPromise = null` for test isolation
49
+ - GitHub Actions CI with Node 18/20/22 matrix, corpus tests, and self-scan
50
+ - GitHub Actions PR lockfile scanner with `fail-on: high`
51
+
52
+ ## [0.9.1] — 2026-05-11
53
+
54
+ - Remove `node-fetch` import and dependency (replaced in 0.9.0)
55
+
56
+ ## [0.9.0] — 2026-05-11
57
+
58
+ - **Replace `node-fetch` with native `fetch`** (Node 18+) — removes external HTTP dependency
59
+ - **Replace `better-sqlite3` with `sql.js`** (WASM) — zero native compilation, fixes `npx` silent failure on systems without build tools
60
+ - Add 404 check in `backend/fetch.js` for robust registry lookups
61
+ - Reduce ATK-009 false positives on `lodash`/`axios`/`express`
62
+ - Fix ATK-002/011 false positives — stricter eval+decode rules, remove self-referential checks
63
+ - Fix ATK-008 `knownRepos` for `vue`
64
+
65
+ ## [0.8.0] — 2026-05-11
66
+
67
+ - **YAML/JSON policy-as-code engine** — allowlists, severity overrides, suppressions, `fail_on` threshold
68
+ - **Text report generator** (free tier)
69
+ - **PDF report generator** (premium, via `pdf-lib`)
70
+ - **Docker**: multi-stage builds, Compose profiles, health checks, validation script, Makefile
71
+ - Comprehensive README rewrite with comparison table, ATK taxonomy, usage examples, integrations
72
+ - `.npmignore` cleanup for smaller package
73
+
74
+ ## [0.7.6] — 2026-05-10
75
+
76
+ - **GitHub Action** (`action.yml`) — scan on push/PR with lockfile or package mode, fail-on severity threshold, SIEM/SBOM output support
77
+ - **28 comprehensive tests** covering SIEM exporters (CEF, ECS, Sentinel, QRadar), EU CRA compliance, SBOM (CycloneDX + SPDX), License key gen/validation/edition/tamper/expiry, Report/NIST (HTML, SR-series table, severity badges, all 11 ATK IDs)
78
+ - Fix tampered key test determinism
79
+
80
+ ## [0.7.5] — 2026-05-10
81
+
82
+ - Add Elastic ECS, Microsoft Sentinel, and IBM QRadar SIEM exporters
83
+
84
+ ## [0.7.4] — 2026-05-10
85
+
86
+ - Version bump only; no functional changes
87
+
88
+ ## [0.7.3] — 2026-05-10
89
+
90
+ - Version bump only; no functional changes
91
+
92
+ ## [0.7.2] — 2026-05-10
93
+
94
+ - Fix duplicate Enterprise Features section in README
95
+
96
+ ## [0.7.1] — 2026-05-10
97
+
98
+ - Add SAML SSO and REST API sections to README
99
+
100
+ ## [0.7.0] — 2026-05-10
101
+
102
+ - **Enterprise SAML SSO integration**
103
+
104
+ ## [0.6.0] — 2026-05-10
105
+
106
+ - **License key enforcement** — HMAC-signed keys with community/premium/enterprise editions
107
+ - Feature gating for SIEM, CRA, REST API, Helm, PostgreSQL backend, SSO, audit logs
108
+ - **PostgreSQL schema** — teams, users, RBAC, audit log, webhooks, API keys, materialized `package_risk` view
109
+ - **FastAPI REST API** — scan/list/retrieve endpoints, webhook CRUD with HMAC-signed dispatch
110
+ - **Webhook engine** — event dispatch with retry, signature verification header
111
+ - **Helm chart** — API + worker + PostgreSQL deployments, secrets, ingress, PVC
112
+ - CLI hardened: premium features blocked without valid license key
113
+
114
+ ## [0.5.0] — 2026-05-10
115
+
116
+ - **ATK-011 (Transitive Propagation)** detector
117
+ - **SIEM CEF export** for Splunk and ArcSight integration
118
+ - **EU CRA compliance report** — EU Cyber Resilience Act readiness assessment
119
+ - Phase 3 enterprise foundation
120
+
121
+ ## [0.4.1] — 2026-05-10
122
+
123
+ - Update README for Phase 3 (ATK-011, SIEM, CRA)
124
+
125
+ ## [0.4.0] — 2026-05-10
126
+
127
+ - **ATK-008 (Tarball Tampering)**, **ATK-009 (Dormant Trigger)**, **ATK-010 (Sandbox Evasion)** detectors
128
+ - **SPDX 2.3 SBOM** support alongside CycloneDX
129
+ - **NIST SP 800-161 compliance report** — supply chain risk management controls
130
+ - Sandbox threat model and gVisor isolation strategy
131
+
132
+ ## [0.3.3] — 2026-05-10
133
+
134
+ - Fix report HTML/SBOM generation to use `atk_id`, description, package name, dynamic version
135
+
136
+ ## [0.3.2] — 2026-05-10
137
+
138
+ - Update README for Phase 2 (ATK-008–010, SPDX, NIST)
139
+
140
+ ## [0.3.1] — 2026-05-10
141
+
142
+ - Fix schema literal newlines
143
+ - Fix CLI SBOM defaults
144
+ - Fix SBOM finding IDs
145
+
146
+ ## [0.3.0] — 2026-05-10
147
+
148
+ - **ATK-001 (Lifecycle Script)** detector — detects `preinstall`, `postinstall`, `preuninstall` hooks with suspicious commands
149
+ - **ATK-002 (Obfuscated Payload)** detector — hex/base64/decode-driven eval, regex obfuscation
150
+ - **ATK-003 (Credential Harvester)** detector — env var exfiltration, filesystem credential scraping
151
+ - **ATK-004 (Persistence Mechanism)** detector — cron jobs, startup scripts, `postinstall` service installs
152
+ - **ATK-005 (Data Exfiltration)** detector — DNS tunneling, HTTP beaconing, unexpected network calls
153
+ - **ATK-006 (Dependency Confusion)** detector — internal package name heuristics
154
+ - **ATK-007 (Typosquatting)** detector — edit-distance based package name similarity
155
+
156
+ ## [0.2.5] — 2026-05-10
157
+
158
+ - Fix `.npmignore` to exclude corpus tarballs from published package
159
+
160
+ ## [0.2.4] — 2026-05-10
161
+
162
+ - Version bump only; no functional changes
163
+
164
+ ## [0.2.2] — 2026-05-10
165
+
166
+ - **Corpus test suite** — 50 clean packages (0% FP) + 22 malicious PoC (100% detect rate)
167
+ - **HTML report generator** with CLI `--html` flag
168
+ - ATK-007 edit-distance typosquatting implementation
169
+ - Switch from `adm-zip` to `tar` for tgz extraction
170
+ - ATK detectors hardened for fewer false positives
171
+ - `README.md`, `.gitignore`, corpus download scripts
172
+ - **Phase 1 exit**: FP < 2%, passes unit tests + corpus
173
+
174
+ ## [0.2.1] — 2026-05-10
175
+
176
+ - Version bump only; no functional changes
177
+
178
+ ## [0.2.0] — 2026-05-10
179
+
180
+ - **Commander.js CLI** with `scan`, `scan-lockfile`, `report` commands
181
+ - **ATK-001–007 detector stubs** via `backend/detectors/index.js` (`runAll`)
182
+ - **SQLite persistence** via `better-sqlite3` — scan auto-save, report by ID/recent
183
+ - **CycloneDX SBOM** — JSON and XML output with ATK vulnerability references
184
+ - `.github/workflows/scan.yml` — GitHub Action example for PR scanning
185
+ - Dependencies: `commander`, `adm-zip`, `acorn`, `node-fetch`
186
+
187
+ ## [0.1.0] — 2026-05-09
188
+
189
+ - **Initial foundation**
190
+ - Monorepo structure (`cli/`, `backend/`, `docker/`, `docs/`)
191
+ - `LICENSING.md` — Apache-2.0 core + Commons Clause for premium features
192
+ - `CONTRIBUTING.md`
193
+ - `docs/attack-taxonomy.md` — ATK-001 through ATK-011 stubs
194
+ - `backend/license.js` skeleton for HMAC-signed license key gating
195
+ - `backend/db/schema.sql`
196
+ - `docker/Dockerfile.cli` + `docker-compose.yml`
197
+ - npm scripts (lint, test stubs)
198
+ - `.github/workflows/ci.yml`
199
+ - `AGENTS.md` — project instructions
package/README.de.md CHANGED
@@ -108,6 +108,25 @@ Kein Node.js. Kein `npm install`. Keine globalen Pakete. Funktioniert auf jedem
108
108
 
109
109
  ---
110
110
 
111
+ ## 🛡️ Behörden- & SOC 2 L2-bereit
112
+
113
+ | Funktion | SOC 2 | NIST 800-161 | STIG/FedRAMP |
114
+ |----------|-------|--------------|--------------|
115
+ | Audit-Protokolle (--audit-log) | CC6.8 | AU-2 | ✓ |
116
+ | FIPS-Krypto (--fips) | CC6.1 | SC-13 | ✓ |
117
+ | STIG-Bericht (--stig) | CC7.3 | RA-5 | ✓ |
118
+ | Offline-Cache (--cache-dir) | A1.2 | SC-8 | ✓ |
119
+ | Sigstore-Herleitung | CC6.2 | SI-7 | ✓ |
120
+ | SBOM (SPDX/CycloneDX) | CC7.4 | SA-10 | ✓ |
121
+
122
+ ```bash
123
+ # Vollständig konformer Scan in luftdichten Umgebungen
124
+ npm-scan scan-lockfile --cache-dir /offline/cache --audit-log /var/log/npm-scan.audit --fips
125
+ npm-scan report --stig
126
+ ```
127
+
128
+ ---
129
+
111
130
  ## 📖 Verwendungsbeispiele
112
131
 
113
132
  ### Ein einzelnes Paket scannen
@@ -123,6 +142,9 @@ npm-scan scan express --sbom spdx # SPDX 2.3
123
142
 
124
143
  # Eine YAML-Policy anwenden
125
144
  npm-scan scan some-package --policy .npm-scan.yml
145
+
146
+ # Lokales Tarball scannen (kein Registry-Abruf nötig)
147
+ npm-scan scan --file path/to/malicious-package.tgz
126
148
  ```
127
149
 
128
150
  ### Eine Lock-Datei scannen
@@ -133,6 +155,18 @@ npm-scan scan-lockfile
133
155
 
134
156
  # Eine bestimmte Lock-Datei scannen
135
157
  npm-scan scan-lockfile -f ./path/to/package-lock.json
158
+
159
+ # CI/CD bei hohen oder kritischen Problemen fehlschlagen (Exit-Code 1)
160
+ npm-scan scan-lockfile --fail-on high
161
+
162
+ # Bei allen Erkenntnissen fehlschlagen (low und höher)
163
+ npm-scan scan-lockfile --fail-on low
164
+
165
+ # SARIF v2.1-Ausgabe für GitHub Advanced Security / VS Code generieren
166
+ npm-scan scan-lockfile --sarif results.sarif
167
+
168
+ # Nur Risiko-Score ausgeben (0-10) für Dashboards/Schwellenwerte
169
+ npm-scan scan-lockfile --score-only
136
170
  ```
137
171
 
138
172
  ### Berichte generieren
@@ -153,6 +187,10 @@ npm-scan report -i 42 --nist
153
187
  # EU-CRA-Compliance-Tabelle ausgeben
154
188
  npm-scan report --cra
155
189
 
190
+ # CSV-Export für Excel / Sheets (audit-bereit)
191
+ npm-scan report --csv risks.csv
192
+ npm-scan scan lodash --csv # CSV nach stdout
193
+
156
194
  # Textbericht (kostenlos)
157
195
  npm-scan report --text
158
196
 
@@ -576,6 +614,7 @@ node --test test/detectors-corpus.test.js
576
614
 
577
615
  ### Hilfe benötigt?
578
616
 
617
+ - 🔒 Siehe [Sicherheitsrichtlinie](SECURITY.md) für die Offenlegung von Schwachstellen
579
618
  - 📖 Lesen Sie den [Projektplan](docs/project-plan.md)
580
619
  - 🧬 Überprüfen Sie die [Angriffstaxonomie](docs/attack-taxonomy.md)
581
620
  - 🐛 Öffnen Sie ein Issue oder PR
@@ -587,6 +626,19 @@ node --test test/detectors-corpus.test.js
587
626
  Apache-2.0 Core + Commons Clause.
588
627
  Siehe [`LICENSING.md`](LICENSING.md) für die genaue Grenze zwischen kostenlosen und Premium-Funktionen.
589
628
 
629
+ ---
630
+
631
+ ## 👤 Über den Maintainer
632
+
633
+ **Roongrunchai Chongolnee** — Ersteller und Maintainer von `@lateos/npm-scan`. Zertifizierter Sicherheitsexperte (CISSP, CEH, Cisco Security, AWS Cloud Practitioner) mit einem Jahrzehnt Erfahrung in Infrastruktur- und Anwendungssicherheit bei Philips. Ich habe dieses Tool entwickelt, um der Open-Source-Community eine praktische, detektorgesteuerte Abwehr gegen Supply-Chain-Malware zu bieten — und ich bin bestrebt, es transparent, gemeinschaftseigen und kontinuierlich verbessert zu halten.
634
+
635
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-0A66C2?style=flat-square&logo=linkedin)](https://www.linkedin.com/in/roongrunchai-chong-c-ab9742108/)
636
+ [![GitHub](https://img.shields.io/badge/GitHub-lateos--ai-181717?style=flat-square&logo=github)](https://github.com/lateos-ai/npm-scan)
637
+
638
+ Issues, Ideen und Pull-Requests sind immer willkommen — Sicherheit ist am stärksten, wenn wir zusammenarbeiten.
639
+
640
+ ---
641
+
590
642
  ```
591
643
  @lateos/npm-scan — npm supply chain security scanner
592
644
  Copyright (C) 2026 Lateos
package/README.fr.md CHANGED
@@ -108,6 +108,25 @@ Pas de Node.js. Pas de `npm install`. Pas de paquets globaux. Fonctionne sur tou
108
108
 
109
109
  ---
110
110
 
111
+ ## 🛡️ Prêt pour le Gouvernement et SOC 2 L2
112
+
113
+ | Fonctionnalité | SOC 2 | NIST 800-161 | STIG/FedRAMP |
114
+ |----------------|-------|--------------|--------------|
115
+ | Journaux d'audit (--audit-log) | CC6.8 | AU-2 | ✓ |
116
+ | Crypto FIPS (--fips) | CC6.1 | SC-13 | ✓ |
117
+ | Rapport STIG (--stig) | CC7.3 | RA-5 | ✓ |
118
+ | Cache hors ligne (--cache-dir) | A1.2 | SC-8 | ✓ |
119
+ | Provenance Sigstore | CC6.2 | SI-7 | ✓ |
120
+ | SBOM (SPDX/CycloneDX) | CC7.4 | SA-10 | ✓ |
121
+
122
+ ```bash
123
+ # Scan conforme en environnement hermétique
124
+ npm-scan scan-lockfile --cache-dir /offline/cache --audit-log /var/log/npm-scan.audit --fips
125
+ npm-scan report --stig
126
+ ```
127
+
128
+ ---
129
+
111
130
  ## 📖 Exemples d'utilisation
112
131
 
113
132
  ### Scanner un seul paquet
@@ -123,6 +142,9 @@ npm-scan scan express --sbom spdx # SPDX 2.3
123
142
 
124
143
  # Appliquer une politique YAML
125
144
  npm-scan scan some-package --policy .npm-scan.yml
145
+
146
+ # Scanner un fichier tarball local (pas de téléchargement depuis le registre)
147
+ npm-scan scan --file path/to/malicious-package.tgz
126
148
  ```
127
149
 
128
150
  ### Scanner un fichier de verrouillage
@@ -133,6 +155,18 @@ npm-scan scan-lockfile
133
155
 
134
156
  # Scanner un fichier de verrouillage spécifique
135
157
  npm-scan scan-lockfile -f ./path/to/package-lock.json
158
+
159
+ # Échouer en CI/CD sur les découvertes de severity haute ou critique (code de sortie 1)
160
+ npm-scan scan-lockfile --fail-on high
161
+
162
+ # Échouer sur toute découverte (low et au-delà)
163
+ npm-scan scan-lockfile --fail-on low
164
+
165
+ # Générer une sortie SARIF v2.1 pour GitHub Advanced Security / VS Code
166
+ npm-scan scan-lockfile --sarif results.sarif
167
+
168
+ # Afficher uniquement le score de risque (0-10) pour les tableaux de bord/seuils
169
+ npm-scan scan-lockfile --score-only
136
170
  ```
137
171
 
138
172
  ### Générer des rapports
@@ -153,6 +187,10 @@ npm-scan report -i 42 --nist
153
187
  # Afficher le tableau de conformité EU CRA
154
188
  npm-scan report --cra
155
189
 
190
+ # Export CSV pour Excel / Sheets (prêt pour audit)
191
+ npm-scan report --csv risks.csv
192
+ npm-scan scan lodash --csv # CSV vers stdout
193
+
156
194
  # Rapport texte (gratuit)
157
195
  npm-scan report --text
158
196
 
@@ -576,6 +614,7 @@ node --test test/detectors-corpus.test.js
576
614
 
577
615
  ### Besoin d'aide ?
578
616
 
617
+ - 🔒 Voir la [politique de sécurité](SECURITY.md) pour la divulgation des vulnérabilités
579
618
  - 📖 Lire le [plan du projet](docs/project-plan.md)
580
619
  - 🧬 Consulter la [taxonomie des attaques](docs/attack-taxonomy.md)
581
620
  - 🐛 Ouvrir une issue ou une PR
@@ -587,6 +626,19 @@ node --test test/detectors-corpus.test.js
587
626
  Apache-2.0 core + Commons Clause.
588
627
  Voir [`LICENSING.md`](LICENSING.md) pour la limite exacte entre les fonctionnalités gratuites et premium.
589
628
 
629
+ ---
630
+
631
+ ## 👤 À propos du mainteneur
632
+
633
+ **Roongrunchai Chongolnee** — créateur et mainteneur de `@lateos/npm-scan`. Professionnel de la sécurité certifié (CISSP, CEH, Cisco Security, AWS Cloud Practitioner) avec une décennie d'expérience en sécurité des infrastructures et des applications chez Philips. J'ai construit cet outil pour offrir à la communauté open-source une défense pratique et pilotée par des détecteurs contre les logiciels malveillants de la chaîne d'approvisionnement — et je m'engage à le maintenir transparent, détenu par la communauté et en amélioration continue.
634
+
635
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-0A66C2?style=flat-square&logo=linkedin)](https://www.linkedin.com/in/roongrunchai-chong-c-ab9742108/)
636
+ [![GitHub](https://img.shields.io/badge/GitHub-lateos--ai-181717?style=flat-square&logo=github)](https://github.com/lateos-ai/npm-scan)
637
+
638
+ Les issues, idées et pull requests sont toujours les bienvenus — la sécurité est plus forte quand nous collaborons.
639
+
640
+ ---
641
+
590
642
  ```
591
643
  @lateos/npm-scan — npm supply chain security scanner
592
644
  Copyright (C) 2026 Lateos
package/README.ja.md CHANGED
@@ -108,6 +108,25 @@ Node.js不要。`npm install`不要。グローバルパッケージ不要。Doc
108
108
 
109
109
  ---
110
110
 
111
+ ## 🛡️ 政府機関・SOC 2 L2 対応
112
+
113
+ | 機能 | SOC 2 | NIST 800-161 | STIG/FedRAMP |
114
+ |------|-------|--------------|--------------|
115
+ | 監査ログ (--audit-log) | CC6.8 | AU-2 | ✓ |
116
+ | FIPS暗号化 (--fips) | CC6.1 | SC-13 | ✓ |
117
+ | STIGレポート (--stig) | CC7.3 | RA-5 | ✓ |
118
+ | オフラインキャッシュ (--cache-dir) | A1.2 | SC-8 | ✓ |
119
+ | Sigstoreプロvenes | CC6.2 | SI-7 | ✓ |
120
+ | SBOM (SPDX/CycloneDX) | CC7.4 | SA-10 | ✓ |
121
+
122
+ ```bash
123
+ # エアギャップ環境での完全なコンプライアンススキャンを実行
124
+ npm-scan scan-lockfile --cache-dir /offline/cache --audit-log /var/log/npm-scan.audit --fips
125
+ npm-scan report --stig
126
+ ```
127
+
128
+ ---
129
+
111
130
  ## 📖 使用例
112
131
 
113
132
  ### 単一パッケージのスキャン
@@ -123,6 +142,9 @@ npm-scan scan express --sbom spdx # SPDX 2.3
123
142
 
124
143
  # YAMLポリシーを適用
125
144
  npm-scan scan some-package --policy .npm-scan.yml
145
+
146
+ # ローカルtarballをスキャン(レジストリからの取得不要)
147
+ npm-scan scan --file path/to/malicious-package.tgz
126
148
  ```
127
149
 
128
150
  ### ロックファイルのスキャン
@@ -133,6 +155,18 @@ npm-scan scan-lockfile
133
155
 
134
156
  # 特定のロックファイルをスキャン
135
157
  npm-scan scan-lockfile -f ./path/to/package-lock.json
158
+
159
+ # 高重大または致命的な問題でCI/CDを失敗させる(終了コード1)
160
+ npm-scan scan-lockfile --fail-on high
161
+
162
+ # 任何の発見項目でビルドを失敗させる(low以上)
163
+ npm-scan scan-lockfile --fail-on low
164
+
165
+ # SARIF v2.1出力を生成(GitHub Advanced Security / VS Code向け)
166
+ npm-scan scan-lockfile --sarif results.sarif
167
+
168
+ # リスクスコアのみを出力(0-10)(ダッシュボード/閾値向け)
169
+ npm-scan scan-lockfile --score-only
136
170
  ```
137
171
 
138
172
  ### レポートの生成
@@ -576,6 +610,7 @@ node --test test/detectors-corpus.test.js
576
610
 
577
611
  ### ヘルプが必要ですか?
578
612
 
613
+ - 🔒 [セキュリティポリシー](SECURITY.md)で脆弱性の開示方法を確認
579
614
  - 📖 [プロジェクト計画](docs/project-plan.md)を読む
580
615
  - 🧬 [攻撃分類](docs/attack-taxonomy.md)を確認
581
616
  - 🐛 IssueまたはPRを開く
@@ -587,6 +622,19 @@ node --test test/detectors-corpus.test.js
587
622
  Apache-2.0コア+Commons Clause。
588
623
  無料版とプレミアム版機能の正確な境界については[`LICENSING.md`](LICENSING.md)を参照してください。
589
624
 
625
+ ---
626
+
627
+ ## 👤 メンテナーについて
628
+
629
+ **Roongrunchai Chongolnee** — `@lateos/npm-scan` の作成者兼メンテナー。CISSP、CEH、Cisco Security、AWS Cloud Practitioner の認定を持つセキュリティ専門家で、Philips で10年間のインフラおよびアプリケーションセキュリティの経験があります。このツールは、オープンソースコミュニティに実用的で検出器駆動型のサプライチェーン型マルウェア防御を提供するために構築しました。透明性、コミュニティ所有、継続的改善に取り組んでいます。
630
+
631
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-0A66C2?style=flat-square&logo=linkedin)](https://www.linkedin.com/in/roongrunchai-chong-c-ab9742108/)
632
+ [![GitHub](https://img.shields.io/badge/GitHub-lateos--ai-181717?style=flat-square&logo=github)](https://github.com/lateos-ai/npm-scan)
633
+
634
+ Issue、アイデア、PRはいつでも歓迎します——セキュリティは協力によって最も強力になります。
635
+
636
+ ---
637
+
590
638
  ```
591
639
  @lateos/npm-scan — npm supply chain security scanner
592
640
  Copyright (C) 2026 Lateos
package/README.md CHANGED
@@ -107,17 +107,25 @@ No Node.js. No `npm install`. No global packages. Works on any system with Docke
107
107
 
108
108
  ---
109
109
 
110
- ## 🐳 Run @lateos/npm-scan anywhere with Docker — zero installation
110
+ ## 🛡️ Government & SOC 2 L2 Ready
111
111
 
112
- ```bash
113
- # Pull and run a single scan — no Node.js or npm required
114
- docker run --rm ghcr.io/lateos/npm-scan:cli scan lodash
112
+ | Feature | SOC 2 | NIST 800-161 | STIG/FedRAMP |
113
+ |---------|-------|--------------|--------------|
114
+ | Audit logs (--audit-log) | CC6.8 | AU-2 | ✓ |
115
+ | FIPS crypto (--fips) | CC6.1 | SC-13 | ✓ |
116
+ | STIG report (--stig) | CC7.3 | RA-5 | ✓ |
117
+ | Offline cache (--cache-dir) | A1.2 | SC-8 | ✓ |
118
+ | Sigstore provenance | CC6.2 | SI-7 | ✓ |
119
+ | SBOM (SPDX/CycloneDX) | CC7.4 | SA-10 | ✓ |
115
120
 
116
- # Full pipeline with persistent storage and Compose
117
- docker compose --profile pipeline up -d
121
+ ```bash
122
+ # Air-gapped scan with full compliance
123
+ npm-scan scan-lockfile --cache-dir /offline/cache --audit-log /var/log/npm-scan.audit --fips
124
+ npm-scan report --stig
118
125
  ```
119
126
 
120
- No Node.js. No `npm install`. No global packages. Works on any system with Docker — CI servers, air-gapped environments, Kubernetes clusters. Multi-arch images for `linux/amd64` and `linux/arm64`.
127
+ [![SOC 2 L2](https://img.shields.io/badge/SOC%202-L2-green?style=flat-square&logo=aicpa)](https://www.aicpa.org/interestareas/frc/assuranceadvisoryservices/sorhome.html)
128
+ [![FedRAMP](https://img.shields.io/badge/FedRAMP-Moderate-blue?style=flat-square)](https://fedramp.gov/)
121
129
 
122
130
  ---
123
131
 
@@ -136,6 +144,9 @@ npm-scan scan express --sbom spdx # SPDX 2.3
136
144
 
137
145
  # Apply a YAML policy
138
146
  npm-scan scan some-package --policy .npm-scan.yml
147
+
148
+ # Scan a local tarball (no registry fetch needed)
149
+ npm-scan scan --file path/to/malicious-package.tgz
139
150
  ```
140
151
 
141
152
  ### Scan a lockfile
@@ -146,6 +157,18 @@ npm-scan scan-lockfile
146
157
 
147
158
  # Scan a specific lockfile
148
159
  npm-scan scan-lockfile -f ./path/to/package-lock.json
160
+
161
+ # Fail CI/CD on high or critical findings (exit code 1)
162
+ npm-scan scan-lockfile --fail-on high
163
+
164
+ # Fail on any findings (low and above)
165
+ npm-scan scan-lockfile --fail-on low
166
+
167
+ # Generate SARIF v2.1 output for GitHub Advanced Security / VS Code
168
+ npm-scan scan-lockfile --sarif results.sarif
169
+
170
+ # Output only risk score (0-10) for dashboards/thresholds
171
+ npm-scan scan-lockfile --score-only
149
172
  ```
150
173
 
151
174
  ### Generate reports
@@ -166,6 +189,10 @@ npm-scan report -i 42 --nist
166
189
  # Print EU CRA compliance table
167
190
  npm-scan report --cra
168
191
 
192
+ # CSV export for Excel / Sheets (audit-ready)
193
+ npm-scan report --csv risks.csv
194
+ npm-scan scan lodash --csv # CSV to stdout
195
+
169
196
  # Text report (free)
170
197
  npm-scan report --text
171
198
 
@@ -589,6 +616,7 @@ node --test test/detectors-corpus.test.js
589
616
 
590
617
  ### Need help?
591
618
 
619
+ - 🔒 See [security policy](SECURITY.md) for vulnerability disclosure
592
620
  - 📖 Read the [project plan](docs/project-plan.md)
593
621
  - 🧬 Review the [attack taxonomy](docs/attack-taxonomy.md)
594
622
  - 🐛 Open an issue or PR
@@ -600,6 +628,19 @@ node --test test/detectors-corpus.test.js
600
628
  Apache-2.0 core + Commons Clause.
601
629
  See [`LICENSING.md`](LICENSING.md) for the exact boundary between free and premium features.
602
630
 
631
+ ---
632
+
633
+ ## 👤 About the Maintainer
634
+
635
+ **Roongrunchai Chongolnee** — creator and maintainer of `@lateos/npm-scan`. Certified security professional (CISSP, CEH, Cisco Security, AWS Cloud Practitioner) with a decade of infrastructure and application security experience at Philips. I built this tool to give the open-source community a practical, detector-driven defense against supply-chain malware — and I'm committed to keeping it transparent, community-owned, and continuously improved.
636
+
637
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-0A66C2?style=flat-square&logo=linkedin)](https://www.linkedin.com/in/roongrunchai-chong-c-ab9742108/)
638
+ [![GitHub](https://img.shields.io/badge/GitHub-lateos--ai-181717?style=flat-square&logo=github)](https://github.com/lateos-ai/npm-scan)
639
+
640
+ Issues, ideas, and pull requests are always welcome — security is strongest when we collaborate.
641
+
642
+ ---
643
+
603
644
  ```
604
645
  @lateos/npm-scan — npm supply chain security scanner
605
646
  Copyright (C) 2026 Lateos
package/README.zh.md CHANGED
@@ -108,6 +108,25 @@ docker compose --profile pipeline up -d
108
108
 
109
109
  ---
110
110
 
111
+ ## 🛡️ 政府与 SOC 2 L2 就绪
112
+
113
+ | 功能 | SOC 2 | NIST 800-161 | STIG/FedRAMP |
114
+ |------|-------|--------------|--------------|
115
+ | 审计日志 (--audit-log) | CC6.8 | AU-2 | ✓ |
116
+ | FIPS 加密 (--fips) | CC6.1 | SC-13 | ✓ |
117
+ | STIG 报告 (--stig) | CC7.3 | RA-5 | ✓ |
118
+ | 离线缓存 (--cache-dir) | A1.2 | SC-8 | ✓ |
119
+ | Sigstore 溯源 | CC6.2 | SI-7 | ✓ |
120
+ | SBOM (SPDX/CycloneDX) | CC7.4 | SA-10 | ✓ |
121
+
122
+ ```bash
123
+ # 气隙环境下的完整合规扫描
124
+ npm-scan scan-lockfile --cache-dir /offline/cache --audit-log /var/log/npm-scan.audit --fips
125
+ npm-scan report --stig
126
+ ```
127
+
128
+ ---
129
+
111
130
  ## 📖 使用示例
112
131
 
113
132
  ### 扫描单个包
@@ -123,6 +142,9 @@ npm-scan scan express --sbom spdx # SPDX 2.3
123
142
 
124
143
  # 应用 YAML 策略
125
144
  npm-scan scan some-package --policy .npm-scan.yml
145
+
146
+ # 扫描本地 tarball(无需从注册表获取)
147
+ npm-scan scan --file path/to/malicious-package.tgz
126
148
  ```
127
149
 
128
150
  ### 扫描锁定文件
@@ -133,6 +155,18 @@ npm-scan scan-lockfile
133
155
 
134
156
  # 扫描特定锁定文件
135
157
  npm-scan scan-lockfile -f ./path/to/package-lock.json
158
+
159
+ # 在高危或严重问题时使 CI/CD 失败(退出码 1)
160
+ npm-scan scan-lockfile --fail-on high
161
+
162
+ # 任何发现项都使构建失败(low 及以上)
163
+ npm-scan scan-lockfile --fail-on low
164
+
165
+ # 生成 SARIF v2.1 输出,用于 GitHub Advanced Security / VS Code
166
+ npm-scan scan-lockfile --sarif results.sarif
167
+
168
+ # 仅输出风险分数(0-10)用于仪表板/阈值
169
+ npm-scan scan-lockfile --score-only
136
170
  ```
137
171
 
138
172
  ### 生成报告
@@ -153,6 +187,10 @@ npm-scan report -i 42 --nist
153
187
  # 打印 EU CRA 合规表格
154
188
  npm-scan report --cra
155
189
 
190
+ # CSV 导出用于 Excel / Sheets(审计就绪)
191
+ npm-scan report --csv risks.csv
192
+ npm-scan scan lodash --csv # CSV 输出到标准输出
193
+
156
194
  # 文本报告(免费)
157
195
  npm-scan report --text
158
196
 
@@ -576,6 +614,7 @@ node --test test/detectors-corpus.test.js
576
614
 
577
615
  ### 需要帮助?
578
616
 
617
+ - 🔒 查看[安全策略](SECURITY.md)了解漏洞披露流程
579
618
  - 📖 阅读[项目计划](docs/project-plan.md)
580
619
  - 🧬 查看[攻击分类](docs/attack-taxonomy.md)
581
620
  - 🐛 提交 issue 或 PR
@@ -587,6 +626,19 @@ node --test test/detectors-corpus.test.js
587
626
  Apache-2.0 核心 + Commons Clause。
588
627
  请参阅 [`LICENSING.md`](LICENSING.md) 了解免费版和高级版功能之间的确切界限。
589
628
 
629
+ ---
630
+
631
+ ## 👤 关于维护者
632
+
633
+ **Roongrunchai Chongolnee** — `@lateos/npm-scan` 的创建者和维护者。持有 CISSP、CEH、思科安全、AWS 云从业者认证的安全专业人士,在飞利浦拥有十年的基础设施和应用安全经验。我构建这个工具是为了让开源社区拥有一个实用、检测器驱动的供应链恶意软件防御方案——我致力于保持其透明、社区拥有和持续改进。
634
+
635
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-0A66C2?style=flat-square&logo=linkedin)](https://www.linkedin.com/in/roongrunchai-chong-c-ab9742108/)
636
+ [![GitHub](https://img.shields.io/badge/GitHub-lateos--ai-181717?style=flat-square&logo=github)](https://github.com/lateos-ai/npm-scan)
637
+
638
+ 欢迎提交 issue、想法和 PR——安全在协作中最强大。
639
+
640
+ ---
641
+
590
642
  ```
591
643
  @lateos/npm-scan — npm supply chain security scanner
592
644
  Copyright (C) 2026 Lateos
package/SECURITY.md ADDED
@@ -0,0 +1,73 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ Only the **latest published minor version** on npm receives security patches. Keep `@lateos/npm-scan` up to date:
6
+
7
+ ```bash
8
+ npm update -g @lateos/npm-scan
9
+ ```
10
+
11
+ | Version | Supported |
12
+ |---------|-----------|
13
+ | 0.9.x | ✅ Active |
14
+ | < 0.9 | ❌ |
15
+
16
+ ## Reporting a Vulnerability
17
+
18
+ Use **GitHub Private Vulnerability Reporting**:
19
+
20
+ 1. Go to [github.com/lateos-ai/npm-scan/security/advisories/new](https://github.com/lateos-ai/npm-scan/security/advisories/new)
21
+ 2. Describe the vulnerability in detail (ideally with a proof of concept)
22
+ 3. Allow **72 hours** for an initial acknowledgment
23
+
24
+ For encrypted follow-up outside of GitHub, use our PGP key:
25
+
26
+ ```
27
+ Fingerprint: 1BC6 998B 879B BDE0 D778 629E D9CF F5EF 1F7C 557B
28
+ Key ID: 1F7C557B
29
+ Email: leo@lateos.ai
30
+ ```
31
+
32
+ ## Scope
33
+
34
+ **In scope:**
35
+ - Detector logic (ATK-001 through ATK-011)
36
+ - Code execution in the scanner engine (`backend/fetch.js`, `cli/cli.js`)
37
+ - CI/CD pipeline and publish process (provenance bypass, supply chain)
38
+ - Configuration injection via `policy.yaml` or command-line flags
39
+
40
+ **Out of scope:**
41
+ - CVEs in third-party dependencies — report upstream
42
+ - Vulnerabilities in the npm registry itself — report to npm
43
+ - Malicious packages detected by the scanner (that's working as designed)
44
+
45
+ ## Security Practices
46
+
47
+ `@lateos/npm-scan` follows these practices to protect its own supply chain:
48
+
49
+ - **Sigstore provenance** on every npm publish — verifiable via `npm view @lateos/npm-scan provenance`
50
+ - **Self-scanning in CI** — every commit scans the project's own `package-lock.json` for the full ATK taxonomy
51
+ - **SBOM per release** — CycloneDX and SPDX 2.3 Bill of Materials published with every version
52
+ - **2FA** enforced on the npm publisher account
53
+ - **Docker multi-arch images** signed and pushed via CI, not manually
54
+ - **All code public** — no security-by-obscurity
55
+
56
+ ## Self-Scanning
57
+
58
+ As a supply chain security scanner, `@lateos/npm-scan` dogfoods its own detectors. Every CI run executes:
59
+
60
+ ```bash
61
+ npx @lateos/npm-scan scan-lockfile --fail-on medium
62
+ ```
63
+
64
+ If a future update to a dependency triggers one of our detectors (e.g., typosquat, obfuscated lifecycle script), the build **fails** before the change reaches npm.
65
+
66
+ ## Safe Harbor
67
+
68
+ We consider security research conducted under this policy as authorized and will not pursue legal action against researchers who:
69
+
70
+ - Report vulnerabilities through GitHub Private Vulnerability Reporting
71
+ - Do not access or modify user data beyond what's necessary to demonstrate the vulnerability
72
+ - Do not exploit the vulnerability beyond demonstrating it
73
+ - Act in good faith to improve the security of the project
package/backend/fetch.js CHANGED
@@ -6,7 +6,18 @@ import zlib from 'zlib';
6
6
  import { Readable } from 'stream';
7
7
  import { pipeline } from 'stream/promises';
8
8
 
9
- export async function fetchPackage(target) {
9
+ export async function fetchPackage(target, options = {}) {
10
+ const { cacheDir, cacheTTL = 604800, cacheMaxSize = 1000000000 } = options;
11
+
12
+ // Check cache if enabled
13
+ if (cacheDir) {
14
+ const cached = getFromCache(cacheDir, target, cacheTTL);
15
+ if (cached) {
16
+ const tmpDir = path.join(os.tmpdir(), 'npm-scan-cache-' + Date.now());
17
+ return { ...(await extractTarball(cached, tmpDir)), meta: null };
18
+ }
19
+ }
20
+
10
21
  const metaRes = await fetch(`https://registry.npmjs.org/${target}/latest`);
11
22
  const meta = await metaRes.json();
12
23
 
@@ -19,7 +30,95 @@ export async function fetchPackage(target) {
19
30
  const buffer = Buffer.from(await tarRes.arrayBuffer());
20
31
  if (buffer.length > 500 * 1024 * 1024) throw new Error('Tarball too large');
21
32
 
33
+ // Save to cache if enabled
34
+ if (cacheDir) {
35
+ saveToCache(cacheDir, target, buffer, cacheTTL, cacheMaxSize);
36
+ }
37
+
22
38
  const tmpDir = path.join(os.tmpdir(), 'npm-scan-' + Date.now());
39
+ return { ...(await extractTarball(buffer, tmpDir)), meta };
40
+ }
41
+
42
+ function getFromCache(cacheDir, target, ttl) {
43
+ const cachePath = path.join(cacheDir, `${target.replace('/', '-')}.tgz`);
44
+ const metaPath = path.join(cacheDir, `${target.replace('/', '-')}.meta.json`);
45
+
46
+ try {
47
+ if (!fs.existsSync(cachePath) || !fs.existsSync(metaPath)) return null;
48
+
49
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf8'));
50
+ const age = (Date.now() - meta.timestamp) / 1000;
51
+
52
+ if (age > ttl) {
53
+ fs.unlinkSync(cachePath);
54
+ fs.unlinkSync(metaPath);
55
+ return null;
56
+ }
57
+
58
+ return fs.readFileSync(cachePath);
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ function saveToCache(cacheDir, target, buffer, ttl, maxSize) {
65
+ try {
66
+ if (!fs.existsSync(cacheDir)) {
67
+ fs.mkdirSync(cacheDir, { recursive: true });
68
+ }
69
+
70
+ // Prune if needed
71
+ pruneCache(cacheDir, maxSize);
72
+
73
+ const safeName = target.replace('/', '-');
74
+ const cachePath = path.join(cacheDir, `${safeName}.tgz`);
75
+ const metaPath = path.join(cacheDir, `${safeName}.meta.json`);
76
+
77
+ fs.writeFileSync(cachePath, buffer);
78
+ fs.writeFileSync(metaPath, JSON.stringify({ timestamp: Date.now(), size: buffer.length }));
79
+ } catch (e) {
80
+ // Cache write failure - continue without caching
81
+ }
82
+ }
83
+
84
+ function pruneCache(cacheDir, maxSize) {
85
+ try {
86
+ const files = fs.readdirSync(cacheDir).filter(f => f.endsWith('.meta.json'));
87
+ let totalSize = 0;
88
+ const fileInfos = [];
89
+
90
+ for (const f of files) {
91
+ const meta = JSON.parse(fs.readFileSync(path.join(cacheDir, f), 'utf8'));
92
+ const tarFile = f.replace('.meta.json', '.tgz');
93
+ const size = meta.size || 0;
94
+ totalSize += size;
95
+ fileInfos.push({ tarFile, metaFile: f, timestamp: meta.timestamp, size });
96
+ }
97
+
98
+ if (totalSize > maxSize) {
99
+ // Sort by oldest first and remove until under limit
100
+ fileInfos.sort((a, b) => a.timestamp - b.timestamp);
101
+ for (const info of fileInfos) {
102
+ if (totalSize <= maxSize * 0.8) break; // Leave 20% margin
103
+ try {
104
+ fs.unlinkSync(path.join(cacheDir, info.tarFile));
105
+ fs.unlinkSync(path.join(cacheDir, info.metaFile));
106
+ totalSize -= info.size;
107
+ } catch {}
108
+ }
109
+ }
110
+ } catch {
111
+ // Prune failure - ignore
112
+ }
113
+ }
114
+
115
+ export async function scanLocalTarball(filePath) {
116
+ const buffer = fs.readFileSync(filePath);
117
+ const tmpDir = path.join(os.tmpdir(), 'npm-scan-local-' + Date.now());
118
+ return await extractTarball(buffer, tmpDir);
119
+ }
120
+
121
+ async function extractTarball(buffer, tmpDir) {
23
122
  fs.mkdirSync(tmpDir, { recursive: true });
24
123
 
25
124
  const stream = Readable.from(buffer);
package/backend/report.js CHANGED
@@ -155,4 +155,101 @@ function generateNistTable(scans) {
155
155
  <thead><tr><th>NIST Control</th><th>Control Title</th><th>Status</th><th>ATK ID</th></tr></thead>
156
156
  <tbody>${rows}</tbody>
157
157
  </table>`;
158
+ }
159
+
160
+ export function generateSARIF(scan, format = 'json') {
161
+ const findings = scan.findings || [];
162
+ const runs = [{
163
+ tool: {
164
+ driver: {
165
+ name: 'npm-scan',
166
+ version: '0.9.7',
167
+ informationUri: 'https://github.com/lateos-ai/npm-scan',
168
+ rules: Array.from(new Set(findings.map(f => f.id))).map(id => ({
169
+ id,
170
+ name: `ATK-${id.replace('ATK-', '')}`,
171
+ shortDescription: { text: findings.find(f => f.id === id)?.title || id },
172
+ fullDescription: { text: findings.find(f => f.id === id)?.description || '' },
173
+ defaultConfiguration: { enabled: true }
174
+ }))
175
+ }
176
+ },
177
+ results: findings.map(f => {
178
+ const severityMap = { critical: 'error', high: 'error', medium: 'warning', low: 'note' };
179
+ return {
180
+ ruleId: f.id,
181
+ level: severityMap[f.severity] || 'note',
182
+ message: { text: f.description || f.title },
183
+ locations: [{
184
+ physicalLocation: {
185
+ artifactLocation: { uri: f.evidence || 'unknown' },
186
+ region: { startLine: 1, startColumn: 1 }
187
+ }
188
+ }]
189
+ };
190
+ })
191
+ }];
192
+
193
+ const sarif = {
194
+ version: '2.1.0',
195
+ schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
196
+ runs
197
+ };
198
+
199
+ return format === 'pretty' ? JSON.stringify(sarif, null, 2) : JSON.stringify(sarif);
200
+ }
201
+
202
+ export function generateCSV(scans) {
203
+ const headers = 'id,severity,title,description,evidence,package_name,version\n';
204
+ const rows = (scans || []).flatMap(s =>
205
+ (s.findings || []).map(f => [
206
+ f.id,
207
+ f.severity || '',
208
+ (f.title || '').replace(/,/g, ';'),
209
+ (f.description || '').replace(/,/g, ';'),
210
+ (f.evidence || '').replace(/,/g, ';'),
211
+ s.package_name || '',
212
+ s.version || ''
213
+ ].join(','))
214
+ ).join('\n');
215
+ return headers + rows;
216
+ }
217
+
218
+ export function calculateRiskScore(findings, totalPackages = 1) {
219
+ const weights = { low: 1, medium: 3, high: 7, critical: 10 };
220
+ const rawScore = findings.reduce((sum, f) => sum + (weights[f.severity] || 0), 0) / totalPackages;
221
+ return Math.min(rawScore, 10).toFixed(1);
222
+ }
223
+
224
+ const STIG_MAP = {
225
+ 'SRG-APP-000141': { title: 'Application Malware Detection', atk: 'ATK-001', desc: 'Lifecycle script detection' },
226
+ 'SRG-APP-000142': { title: 'Application Code Obfuscation', atk: 'ATK-002', desc: 'Obfuscated payload detection' },
227
+ 'SRG-APP-000143': { title: 'Credential Harvesting', atk: 'ATK-003', desc: 'Credential exfiltration detection' },
228
+ 'SRG-APP-000144': { title: 'Persistence Mechanisms', atk: 'ATK-004', desc: 'Malicious persistence detection' },
229
+ 'SRG-APP-000145': { title: 'Data Exfiltration', atk: 'ATK-005', desc: 'Network exfiltration detection' },
230
+ 'SRG-APP-000146': { title: 'Dependency Confusion', atk: 'ATK-006', desc: 'Internal package detection' },
231
+ 'SRG-APP-000147': { title: 'Typosquatting', atk: 'ATK-007', desc: 'Malicious package name detection' },
232
+ 'SRG-APP-000148': { title: 'Tarball Tampering', atk: 'ATK-008', desc: 'Modified package detection' },
233
+ 'SRG-APP-000149': { title: 'Dormant Triggers', atk: 'ATK-009', desc: 'Conditional execution detection' },
234
+ 'SRG-APP-000150': { title: 'Sandbox Evasion', atk: 'ATK-010', desc: 'Environment detection evasion' },
235
+ 'SRG-APP-000151': { title: 'Transitive Propagation', atk: 'ATK-011', desc: 'Dependency chain attacks' }
236
+ };
237
+
238
+ export function generateSTIG(scans) {
239
+ const rows = [];
240
+ for (const [stigId, info] of Object.entries(STIG_MAP)) {
241
+ const findings = scans.flatMap(s => (s.findings || []).filter(f => f.id === info.atk));
242
+ const status = findings.length > 0 ? 'NOT APPLICABLE' : 'COMPLETE';
243
+ const findingsList = findings.map(f => `${f.severity.toUpperCase()}: ${f.title}`).join('; ') || 'None';
244
+ rows.push(`| ${stigId} | ${info.title} | ${status} | ${findingsList} |`);
245
+ }
246
+ return `# STIG Compliance Report
247
+ Generated: ${new Date().toISOString()}
248
+
249
+ | STIG ID | Control Title | Status | Findings |
250
+ |---------|--------------|--------|----------|
251
+ ${rows.join('\n')}
252
+
253
+ ---
254
+ *This report maps application security controls to DISA STIG requirements.*`;
158
255
  }
package/cli/cli.js CHANGED
@@ -15,51 +15,124 @@ function requirePremium(feature, licenseKey) {
15
15
  const program = new Command()
16
16
  .name('npm-scan')
17
17
  .description('npm supply chain security scanner')
18
- .version('0.9.0');
18
+ .version('0.9.7');
19
19
 
20
20
  program
21
21
  .command('scan')
22
22
  .description('Scan a package')
23
- .argument('<target>', 'package name')
23
+ .argument('[target]', 'package name')
24
+ .option('-f, --file <path>', 'local tarball path')
24
25
  .option('-l, --license-key <key>', 'Premium license')
25
26
  .option('--sbom [format]', 'Generate SBOM (json/xml/spdx)')
26
27
  .option('-p, --policy <path>', 'Policy file (YAML/JSON)')
28
+ .option('--fail-on <level>', 'Exit with code 1 if findings >= level (low|medium|high|critical)', 'none')
29
+ .option('--sarif [file]', 'Output SARIF v2.1 format to file or stdout')
30
+ .option('--csv [file]', 'Output CSV format to file or stdout')
31
+ .option('--score-only', 'Output only the risk score (0-10)')
32
+ .option('--audit-log <file>', 'Append scan record to immutable audit log (JSONL format)')
33
+ .option('--fips', 'Enable FIPS 140-2/3 crypto mode (requires FIPS-enabled Node.js)')
34
+ .option('--cache-dir <path>', 'Cache directory for offline/air-gapped scans')
35
+ .option('--cache-ttl <seconds>', 'Cache TTL in seconds (default: 604800 = 7 days)', '604800')
36
+ .option('--cache-size <bytes>', 'Max cache size in bytes (default: 1GB)', '1000000000')
27
37
  .action(async (target, options) => {
28
38
  try {
39
+ if (options.fips) {
40
+ process.env.NODE_OPTIONS = (process.env.NODE_OPTIONS || '') + ' --enable-fips';
41
+ }
42
+
43
+ const fetchOptions = {
44
+ cacheDir: options.cacheDir,
45
+ cacheTTL: parseInt(options.cacheTtl || '604800'),
46
+ cacheMaxSize: parseInt(options.cacheSize || '1000000000')
47
+ };
48
+
49
+ if (!target && !options.file) {
50
+ console.error('Error: specify a package name or --file <path>');
51
+ process.exit(1);
52
+ }
53
+
29
54
  const policy = options.policy
30
55
  ? await import('../backend/policy.js').then(m => m.loadPolicy(options.policy))
31
56
  : null;
32
57
 
33
58
  if (policy) {
34
59
  const { isAllowed } = await import('../backend/policy.js');
35
- if (isAllowed(target, policy)) {
60
+ if (target && isAllowed(target, policy)) {
36
61
  console.log(JSON.stringify({ scanId: null, findings: [], skipped: true, reason: `Package '${target}' is in policy allowlist` }));
37
62
  return;
38
63
  }
39
64
  }
40
65
 
41
- const { pkgJson, jsFiles, tmpDir } = await import('../backend/fetch.js').then(m => m.fetchPackage(target));
66
+ const { pkgJson, jsFiles, tmpDir } = options.file
67
+ ? await import('../backend/fetch.js').then(m => m.scanLocalTarball(options.file))
68
+ : await import('../backend/fetch.js').then(m => m.fetchPackage(target, fetchOptions));
69
+ const pkgName = target || pkgJson.name || 'unknown';
42
70
  const findings = await import('../backend/detectors/index.js').then(m => m.runAll(pkgJson, jsFiles));
43
71
  const { saveScan } = await import('../backend/db.js');
44
- const scanId = await saveScan(target, 'latest', findings);
72
+ const scanId = await saveScan(pkgName, 'latest', findings);
45
73
 
46
74
  let outputFindings = findings;
47
75
  let blocked = false;
48
76
 
49
77
  if (policy) {
50
78
  const { applyPolicy } = await import('../backend/policy.js');
51
- const result = applyPolicy(findings, target, policy);
79
+ const result = applyPolicy(findings, pkgName, policy);
52
80
  outputFindings = result.findings;
53
81
  blocked = result.blocked;
54
82
  }
55
83
 
56
- if (options.sbom) {
84
+ const { calculateRiskScore } = await import('../backend/report.js');
85
+ const riskScore = calculateRiskScore(outputFindings);
86
+
87
+ if (options.scoreOnly) {
88
+ console.log(riskScore);
89
+ import('../backend/fetch.js').then(m => m.cleanup(tmpDir));
90
+ return;
91
+ }
92
+
93
+ if (options.sarif) {
94
+ const { generateSARIF } = await import('../backend/report.js');
95
+ const scan = { package_name: pkgName, version: pkgJson.version || 'latest', findings: outputFindings };
96
+ const sarifOutput = generateSARIF(scan);
97
+ if (options.sarif === true || !options.sarif) {
98
+ console.log(sarifOutput);
99
+ } else {
100
+ const { writeFileSync } = await import('fs');
101
+ writeFileSync(options.sarif, sarifOutput);
102
+ console.log(`SARIF output written to ${options.sarif}`);
103
+ }
104
+ } else if (options.csv) {
105
+ const { generateCSV } = await import('../backend/report.js');
106
+ const scan = { package_name: pkgName, version: pkgJson.version || 'latest', findings: outputFindings };
107
+ const csvOutput = generateCSV([scan]);
108
+ if (options.csv === true || !options.csv) {
109
+ console.log(csvOutput);
110
+ } else {
111
+ const { writeFileSync } = await import('fs');
112
+ writeFileSync(options.csv, csvOutput);
113
+ console.log(`CSV output written to ${options.csv}`);
114
+ }
115
+ } else if (options.sbom) {
57
116
  const { generateSBOM } = await import('../backend/sbom.js');
58
- const pkg = { name: target, version: pkgJson.version || 'latest' };
117
+ const pkg = { name: pkgName, version: pkgJson.version || 'latest' };
59
118
  const sbom = generateSBOM(pkg, outputFindings, options.sbom === true ? 'json' : options.sbom);
60
119
  console.log(sbom);
61
120
  } else {
62
- console.log(JSON.stringify({scanId, findings: outputFindings, blocked}, null, 2));
121
+ console.log(JSON.stringify({scanId, findings: outputFindings, blocked, riskScore}, null, 2));
122
+ }
123
+
124
+ if (options.auditLog) {
125
+ const { writeFileSync, appendFileSync } = await import('fs');
126
+ const entry = {
127
+ timestamp: new Date().toISOString(),
128
+ command: `scan ${target || options.file}`,
129
+ package: pkgName,
130
+ version: pkgJson.version || 'latest',
131
+ riskScore,
132
+ findingsCount: outputFindings.length,
133
+ exitCode: 0
134
+ };
135
+ appendFileSync(options.auditLog, JSON.stringify(entry) + '\n');
63
136
  }
64
137
 
65
138
  if (blocked) {
@@ -67,6 +140,16 @@ program
67
140
  process.exit(1);
68
141
  }
69
142
 
143
+ if (options.failOn !== 'none') {
144
+ const severityLevels = { low: 1, medium: 2, high: 3, critical: 4 };
145
+ const failLevel = severityLevels[options.failOn] || 0;
146
+ const hasBlockingFindings = outputFindings.some(f => (severityLevels[f.severity] || 0) >= failLevel);
147
+ if (hasBlockingFindings) {
148
+ console.error(`Fail: findings with severity >= ${options.failOn} detected`);
149
+ process.exit(1);
150
+ }
151
+ }
152
+
70
153
  import('../backend/fetch.js').then(m => m.cleanup(tmpDir));
71
154
  } catch (e) {
72
155
  console.error(e.message);
@@ -78,6 +161,9 @@ program
78
161
  .command('scan-lockfile')
79
162
  .description('Scan package-lock.json')
80
163
  .option('-f, --file <path>', 'lockfile path', 'package-lock.json')
164
+ .option('--fail-on <level>', 'Exit with code 1 if findings >= level (low|medium|high|critical)', 'none')
165
+ .option('--csv [file]', 'Output CSV format to file or stdout')
166
+ .option('--sarif [file]', 'Output SARIF v2.1 format to file or stdout')
81
167
  .action((options) => {
82
168
  console.log('Scanning lockfile:', options.file);
83
169
  });
@@ -89,8 +175,10 @@ program
89
175
  .option('--sbom [format]', 'SBOM format (json/xml/spdx)')
90
176
  .option('--html', 'HTML report')
91
177
  .option('--text', 'Plain text report')
178
+ .option('--csv [file]', 'CSV export to file or stdout')
92
179
  .option('--nist', 'NIST 800-161 compliance report')
93
180
  .option('--cra', 'EU CRA compliance report')
181
+ .option('--stig', 'STIG compliance report (DISA SRG-APP)')
94
182
  .option('--siem <format>', 'SIEM format (cef|ecs|sentinel|qradar)')
95
183
  .option('--pdf', 'PDF report (premium)')
96
184
  .option('-o, --output <path>', 'Output file path')
@@ -133,6 +221,10 @@ program
133
221
  const { generateHTML } = await import('../backend/report.js');
134
222
  const html = generateHTML(scan ? [scan] : []);
135
223
  console.log(html);
224
+ } else if (options.stig) {
225
+ const { generateSTIG } = await import('../backend/report.js');
226
+ const stig = generateSTIG(scan ? [scan] : []);
227
+ console.log(stig);
136
228
  } else {
137
229
  console.log(JSON.stringify(findings, null, 2));
138
230
  }
@@ -163,6 +255,10 @@ program
163
255
  const { generateHTML } = await import('../backend/report.js');
164
256
  const html = generateHTML(scansWithFindings);
165
257
  console.log(html);
258
+ } else if (options.stig) {
259
+ const { generateSTIG } = await import('../backend/report.js');
260
+ const stig = generateSTIG(scansWithFindings);
261
+ console.log(stig);
166
262
  } else {
167
263
  console.log('Recent scans:', JSON.stringify(scans, null, 2));
168
264
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lateos/npm-scan",
3
- "version": "0.9.6",
3
+ "version": "0.9.9",
4
4
  "description": "Modern npm supply chain security scanner — detects obfuscated payloads, credential stealers, conditional triggers, sandbox evasion, and worm-like propagation. 11 attack types, SBOM, NIST/EU CRA compliance reporting.",
5
5
  "main": "backend/index.js",
6
6
  "bin": {