@lateos/npm-scan 0.8.0 → 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/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
  }
package/cli/cli.js CHANGED
@@ -15,7 +15,7 @@ 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.8.0');
18
+ .version('0.9.0');
19
19
 
20
20
  program
21
21
  .command('scan')
@@ -41,7 +41,7 @@ program
41
41
  const { pkgJson, jsFiles, tmpDir } = await import('../backend/fetch.js').then(m => m.fetchPackage(target));
42
42
  const findings = await import('../backend/detectors/index.js').then(m => m.runAll(pkgJson, jsFiles));
43
43
  const { saveScan } = await import('../backend/db.js');
44
- const scanId = saveScan(target, 'latest', findings);
44
+ const scanId = await saveScan(target, 'latest', findings);
45
45
 
46
46
  let outputFindings = findings;
47
47
  let blocked = false;
@@ -100,8 +100,8 @@ program
100
100
  const { getRecentScans, getFindings, getScan } = await import('../backend/db.js');
101
101
 
102
102
  if (options.id) {
103
- const findings = getFindings(options.id);
104
- const scanInfo = getScan(options.id);
103
+ const findings = await getFindings(options.id);
104
+ const scanInfo = await getScan(options.id);
105
105
  const pkgName = scanInfo?.package_name || 'scan-' + options.id;
106
106
  const pkgVer = scanInfo?.version || 'unknown';
107
107
  const pkg = { name: pkgName, version: pkgVer };
@@ -137,8 +137,8 @@ program
137
137
  console.log(JSON.stringify(findings, null, 2));
138
138
  }
139
139
  } else {
140
- const scans = getRecentScans();
141
- const scansWithFindings = scans.map(s => ({ ...s, findings: getFindings(s.id) }));
140
+ const scans = await getRecentScans();
141
+ const scansWithFindings = await Promise.all(scans.map(async s => ({ ...s, findings: await getFindings(s.id) })));
142
142
 
143
143
  if (options.siem) {
144
144
  requirePremium('siem', licenseKey);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lateos/npm-scan",
3
- "version": "0.8.0",
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.",
3
+ "version": "0.9.0",
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": {
7
7
  "npm-scan": "cli/cli.js"
@@ -34,12 +34,12 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "acorn": "^8.16.0",
37
- "better-sqlite3": "^11.10.0",
38
37
  "commander": "^14.0.3",
39
38
  "glob": "^13.0.6",
40
39
  "js-yaml": "^4.1.1",
41
40
  "node-fetch": "^3.3.2",
42
41
  "pdf-lib": "^1.17.1",
42
+ "sql.js": "^1.11.0",
43
43
  "tar": "^7.5.15"
44
44
  }
45
45
  }
Binary file