cryptoserve 0.1.2 → 0.2.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/lib/scanner.mjs CHANGED
@@ -1,47 +1,30 @@
1
1
  /**
2
- * JavaScript/TypeScript crypto dependency and secret scanner.
2
+ * Cryptographic dependency and secret scanner — orchestrates all sub-scanners.
3
3
  *
4
4
  * Scans projects for:
5
- * 1. Cryptographic dependencies (package.json, imports, algorithm strings)
6
- * 2. Hardcoded secrets (API keys, passwords patterns from secretless-ai)
7
- * 3. Certificate/key files (.pem, .key, .crt, .p12)
5
+ * 1. Cryptographic dependencies (package.json, go.mod, requirements.txt, etc.)
6
+ * 2. Source code crypto patterns (JS/TS, Go, Python, Java, Rust, C/C++)
7
+ * 3. Hardcoded secrets (API keys, passwords — patterns from secretless-ai)
8
+ * 4. Certificate/key files (.pem, .key, .crt, .p12)
9
+ * 5. TLS version issues in config files
10
+ * 6. Binary crypto signatures (optional, via --binary flag)
8
11
  *
9
12
  * Output matches the library inventory format used by pqc-engine.mjs.
10
13
  * Zero dependencies — uses only node:fs and node:path.
11
14
  */
12
15
 
13
- import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
16
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
14
17
  import { join, relative, extname, basename } from 'node:path';
18
+ import { LANGUAGE_PATTERNS, scanSourceFile, detectLanguage, MULTI_LANG_EXTENSIONS } from './scanner-languages.mjs';
19
+ import { scanManifests } from './scanner-manifests.mjs';
20
+ import { scanTlsConfigs } from './scanner-tls.mjs';
21
+ import { lookupAlgorithm } from './algorithm-db.mjs';
22
+ import { lookupNpmPackage } from './crypto-registry.mjs';
23
+ import { walkProject } from './walker.mjs';
24
+ import { loadScannerConfig } from './config.mjs';
15
25
 
16
26
  // ---------------------------------------------------------------------------
17
- // Known crypto packages algorithm mappings
18
- // ---------------------------------------------------------------------------
19
-
20
- const CRYPTO_PACKAGES = {
21
- 'crypto-js': { algorithms: ['AES', 'DES', '3DES', 'MD5', 'SHA-256', 'SHA-512', 'HMAC'], quantumRisk: 'low', category: 'symmetric' },
22
- 'bcrypt': { algorithms: ['bcrypt'], quantumRisk: 'none', category: 'kdf' },
23
- 'bcryptjs': { algorithms: ['bcrypt'], quantumRisk: 'none', category: 'kdf' },
24
- 'jsonwebtoken': { algorithms: ['RS256', 'HS256', 'ES256'], quantumRisk: 'high', category: 'token' },
25
- 'jose': { algorithms: ['RS256', 'ES256', 'EdDSA', 'AES-GCM'], quantumRisk: 'high', category: 'token' },
26
- 'node-forge': { algorithms: ['RSA', 'AES', 'SHA-256', 'HMAC', 'TLS'], quantumRisk: 'high', category: 'tls' },
27
- 'tweetnacl': { algorithms: ['X25519', 'Ed25519', 'XSalsa20'], quantumRisk: 'high', category: 'asymmetric' },
28
- 'libsodium-wrappers': { algorithms: ['X25519', 'Ed25519', 'ChaCha20', 'AES-256-GCM'], quantumRisk: 'high', category: 'asymmetric' },
29
- '@noble/curves': { algorithms: ['ECDSA', 'Ed25519', 'X25519'], quantumRisk: 'high', category: 'asymmetric' },
30
- '@noble/hashes': { algorithms: ['SHA-256', 'SHA-512', 'SHA3', 'Blake2'], quantumRisk: 'low', category: 'hash' },
31
- '@noble/post-quantum': { algorithms: ['ML-KEM', 'ML-DSA', 'SLH-DSA'], quantumRisk: 'none', category: 'pqc' },
32
- 'openpgp': { algorithms: ['RSA', 'ECDSA', 'AES', 'SHA-256'], quantumRisk: 'high', category: 'asymmetric' },
33
- 'elliptic': { algorithms: ['ECDSA', 'ECDHE', 'Ed25519'], quantumRisk: 'high', category: 'asymmetric' },
34
- 'secp256k1': { algorithms: ['ECDSA'], quantumRisk: 'high', category: 'asymmetric' },
35
- 'argon2': { algorithms: ['Argon2'], quantumRisk: 'none', category: 'kdf' },
36
- 'scrypt': { algorithms: ['scrypt'], quantumRisk: 'none', category: 'kdf' },
37
- 'pbkdf2': { algorithms: ['PBKDF2'], quantumRisk: 'none', category: 'kdf' },
38
- 'tls': { algorithms: ['TLS', 'RSA', 'ECDSA'], quantumRisk: 'high', category: 'tls' },
39
- 'ssh2': { algorithms: ['RSA', 'Ed25519', 'ECDSA', 'AES'], quantumRisk: 'high', category: 'asymmetric' },
40
- 'node-rsa': { algorithms: ['RSA'], quantumRisk: 'high', category: 'asymmetric' },
41
- };
42
-
43
- // ---------------------------------------------------------------------------
44
- // Import/require patterns to detect in source code
27
+ // Import/require patterns to detect in JS/TS source code
45
28
  // ---------------------------------------------------------------------------
46
29
 
47
30
  const IMPORT_PATTERNS = [
@@ -117,71 +100,46 @@ const SECRET_PATTERNS = [
117
100
  // File patterns for cert/key discovery
118
101
  const CERT_EXTENSIONS = new Set(['.pem', '.key', '.crt', '.p12', '.pfx', '.jks', '.keystore']);
119
102
 
120
- // ---------------------------------------------------------------------------
121
- // File walker
122
- // ---------------------------------------------------------------------------
123
-
124
- const SKIP_DIRS = new Set([
125
- 'node_modules', '.git', '.next', 'dist', 'build', 'coverage',
126
- '.cache', '.nuxt', '.output', '.svelte-kit', '__pycache__',
127
- 'vendor', '.venv', 'venv',
128
- ]);
129
-
130
- const SOURCE_EXTENSIONS = new Set(['.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx']);
131
-
132
- function walkFiles(dir, maxFiles = 10000, maxBytes = 500 * 1024 * 1024) {
133
- const files = [];
134
- let totalBytes = 0;
135
-
136
- function walk(currentDir) {
137
- if (files.length >= maxFiles || totalBytes >= maxBytes) return;
138
-
139
- let entries;
140
- try { entries = readdirSync(currentDir, { withFileTypes: true }); }
141
- catch { return; }
142
-
143
- for (const entry of entries) {
144
- if (files.length >= maxFiles || totalBytes >= maxBytes) return;
145
-
146
- if (entry.isDirectory()) {
147
- if (!SKIP_DIRS.has(entry.name) && !entry.name.startsWith('.')) {
148
- walk(join(currentDir, entry.name));
149
- }
150
- continue;
151
- }
152
-
153
- if (!entry.isFile()) continue;
154
-
155
- const filePath = join(currentDir, entry.name);
156
- try {
157
- const stat = statSync(filePath);
158
- if (stat.size > 1024 * 1024) continue; // Skip files >1MB
159
- totalBytes += stat.size;
160
- files.push(filePath);
161
- } catch { continue; }
162
- }
163
- }
164
-
165
- walk(dir);
166
- return files;
167
- }
103
+ const JS_EXTENSIONS = new Set(['.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx']);
168
104
 
169
105
  // ---------------------------------------------------------------------------
170
106
  // Scanner
171
107
  // ---------------------------------------------------------------------------
172
108
 
173
- export function scanProject(projectDir) {
109
+ export function scanProject(projectDir, options = {}) {
174
110
  const results = {
175
111
  libraries: [],
176
112
  secrets: [],
177
113
  weakPatterns: [],
178
114
  certFiles: [],
179
115
  filesScanned: 0,
116
+ // New in v0.2.0
117
+ sourceAlgorithms: [],
118
+ tlsFindings: [],
119
+ binaryFindings: [],
120
+ languagesDetected: new Set(),
121
+ manifestsFound: [],
180
122
  };
181
123
 
182
- // 1. Scan package.json for crypto dependencies
183
- const pkgPath = join(projectDir, 'package.json');
184
- if (existsSync(pkgPath)) {
124
+ // 1. Scan package.json for crypto dependencies (root + monorepo workspaces)
125
+ const pkgPaths = [join(projectDir, 'package.json')];
126
+ const monorepoGlobs = ['apps', 'packages', 'libs', 'modules', 'services'];
127
+ for (const sub of monorepoGlobs) {
128
+ const subDir = join(projectDir, sub);
129
+ try {
130
+ const entries = readdirSync(subDir, { withFileTypes: true });
131
+ for (const entry of entries) {
132
+ if (entry.isDirectory()) {
133
+ const nested = join(subDir, entry.name, 'package.json');
134
+ if (existsSync(nested)) pkgPaths.push(nested);
135
+ }
136
+ }
137
+ } catch { /* dir doesn't exist */ }
138
+ }
139
+
140
+ const seenPkgs = new Set();
141
+ for (const pkgPath of pkgPaths) {
142
+ if (!existsSync(pkgPath)) continue;
185
143
  try {
186
144
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
187
145
  const allDeps = {
@@ -190,46 +148,97 @@ export function scanProject(projectDir) {
190
148
  };
191
149
 
192
150
  for (const [name, version] of Object.entries(allDeps)) {
193
- if (name in CRYPTO_PACKAGES) {
194
- const info = CRYPTO_PACKAGES[name];
151
+ const info = lookupNpmPackage(name);
152
+ if (info && !seenPkgs.has(name)) {
153
+ seenPkgs.add(name);
195
154
  results.libraries.push({
196
155
  name,
197
156
  version: version.replace(/^[\^~]/, ''),
198
157
  algorithms: info.algorithms,
199
158
  quantumRisk: info.quantumRisk,
200
159
  category: info.category,
201
- source: 'package.json',
160
+ source: pkgPath.replace(projectDir + '/', ''),
161
+ ecosystem: 'npm',
202
162
  });
203
163
  }
204
164
  }
165
+ results.manifestsFound.push('package.json');
205
166
  } catch { /* invalid package.json */ }
206
167
  }
207
168
 
208
- // 2. Walk source files
209
- const files = walkFiles(projectDir);
169
+ // 2. Scan non-npm manifests (go.mod, requirements.txt, Cargo.toml, etc.)
170
+ const manifestLibs = scanManifests(projectDir);
171
+ for (const lib of manifestLibs) {
172
+ if (!seenPkgs.has(lib.name)) {
173
+ seenPkgs.add(lib.name);
174
+ results.libraries.push(lib);
175
+ if (!results.manifestsFound.includes(lib.source)) {
176
+ results.manifestsFound.push(lib.source);
177
+ }
178
+ }
179
+ }
180
+
181
+ // 3. Walk source files (single-pass unified walker with config overrides)
182
+ const scannerConfig = loadScannerConfig(projectDir);
183
+ const walked = walkProject(projectDir, {
184
+ skipDirs: scannerConfig.skipDirs.length > 0 ? new Set(scannerConfig.skipDirs) : undefined,
185
+ includeExtensions: scannerConfig.includeExtensions.length > 0 ? new Set(scannerConfig.includeExtensions) : undefined,
186
+ maxFiles: scannerConfig.maxFiles,
187
+ maxBytes: scannerConfig.maxBytes,
188
+ maxFileSize: scannerConfig.maxFileSize,
189
+ maxBinaryFiles: scannerConfig.binary.maxFiles,
190
+ maxBinaryFileSize: scannerConfig.binary.maxFileSize,
191
+ });
210
192
  const seenImports = new Set();
211
193
  const seenAlgos = new Set();
194
+ const seenSourceAlgos = new Set();
212
195
 
213
- for (const filePath of files) {
196
+ // Cert files from walker
197
+ for (const certPath of walked.certFiles) {
198
+ results.certFiles.push(relative(projectDir, certPath));
199
+ }
200
+
201
+ for (const filePath of walked.sourceFiles) {
214
202
  const ext = extname(filePath);
203
+ const relPath = relative(projectDir, filePath);
215
204
 
216
- // Check for cert/key files
217
- if (CERT_EXTENSIONS.has(ext)) {
218
- results.certFiles.push(relative(projectDir, filePath));
205
+ // Multi-language scanning (Go, Python, Java, Rust, C/C++)
206
+ const language = detectLanguage(filePath);
207
+ if (language && !JS_EXTENSIONS.has(ext)) {
208
+ results.filesScanned++;
209
+ results.languagesDetected.add(language);
210
+
211
+ let content;
212
+ try { content = readFileSync(filePath, 'utf-8'); }
213
+ catch { continue; }
214
+
215
+ const langResult = scanSourceFile(filePath, content, language);
216
+ for (const algo of langResult.algorithms) {
217
+ if (!seenSourceAlgos.has(algo.algorithm)) {
218
+ seenSourceAlgos.add(algo.algorithm);
219
+ const dbEntry = lookupAlgorithm(algo.algorithm);
220
+ results.sourceAlgorithms.push({
221
+ algorithm: algo.algorithm,
222
+ category: algo.category,
223
+ language,
224
+ quantumRisk: dbEntry?.quantumRisk || 'unknown',
225
+ isWeak: dbEntry?.isWeak || false,
226
+ });
227
+ }
228
+ }
219
229
  continue;
220
230
  }
221
231
 
222
- // Only scan source files for code patterns
223
- if (!SOURCE_EXTENSIONS.has(ext)) continue;
232
+ // Only scan JS/TS source files for JS-specific code patterns
233
+ if (!JS_EXTENSIONS.has(ext)) continue;
224
234
 
225
235
  results.filesScanned++;
236
+ results.languagesDetected.add('javascript');
226
237
 
227
238
  let content;
228
239
  try { content = readFileSync(filePath, 'utf-8'); }
229
240
  catch { continue; }
230
241
 
231
- const relPath = relative(projectDir, filePath);
232
-
233
242
  // Detect imports/requires
234
243
  for (const { pattern, lib, detail } of IMPORT_PATTERNS) {
235
244
  pattern.lastIndex = 0;
@@ -305,7 +314,6 @@ export function scanProject(projectDir) {
305
314
  }
306
315
 
307
316
  if (nodeCryptoAlgos.length > 0) {
308
- // Determine quantum risk based on detected algorithms
309
317
  const hasAsymmetric = nodeCryptoAlgos.some(a =>
310
318
  ['RSA', 'ECDSA', 'Ed25519', 'RS256', 'ES256', 'DH'].includes(a)
311
319
  );
@@ -316,10 +324,20 @@ export function scanProject(projectDir) {
316
324
  quantumRisk: hasAsymmetric ? 'high' : 'low',
317
325
  category: hasAsymmetric ? 'asymmetric' : 'symmetric',
318
326
  source: 'source-code',
327
+ ecosystem: 'npm',
319
328
  });
320
329
  }
321
330
  }
322
331
 
332
+ // 4. TLS scanning — pass pre-walked source+config files
333
+ const tlsFiles = [...walked.sourceFiles, ...walked.configFiles];
334
+ results.tlsFindings = scanTlsConfigs(projectDir, tlsFiles);
335
+
336
+ // 5. Binary scanning moved to CLI layer (lazy-loaded via dynamic import)
337
+
338
+ // Convert sets to arrays for JSON serialization
339
+ results.languagesDetected = [...results.languagesDetected];
340
+
323
341
  return results;
324
342
  }
325
343
 
@@ -328,12 +346,41 @@ export function scanProject(projectDir) {
328
346
  // ---------------------------------------------------------------------------
329
347
 
330
348
  export function toLibraryInventory(scanResults) {
331
- return scanResults.libraries.map(lib => ({
332
- name: lib.name,
333
- version: lib.version,
334
- algorithms: lib.algorithms,
335
- quantumRisk: lib.quantumRisk,
336
- category: lib.category,
337
- isDeprecated: false,
338
- }));
349
+ const inventory = scanResults.libraries.map(lib => {
350
+ const entry = {
351
+ name: lib.name,
352
+ version: lib.version,
353
+ algorithms: lib.algorithms,
354
+ quantumRisk: lib.quantumRisk,
355
+ category: lib.category,
356
+ isDeprecated: lib.isDeprecated || false,
357
+ };
358
+ // Enrich with algorithm-db data
359
+ for (const algoName of lib.algorithms) {
360
+ const dbEntry = lookupAlgorithm(algoName);
361
+ if (dbEntry?.isWeak && !entry.isDeprecated) {
362
+ entry.isDeprecated = true;
363
+ }
364
+ }
365
+ return entry;
366
+ });
367
+
368
+ // Also add source-detected algorithms as synthetic library entries
369
+ for (const algo of (scanResults.sourceAlgorithms || [])) {
370
+ const alreadyInLib = inventory.some(lib =>
371
+ lib.algorithms.some(a => a.toLowerCase() === algo.algorithm.toLowerCase())
372
+ );
373
+ if (!alreadyInLib) {
374
+ inventory.push({
375
+ name: `${algo.language}:${algo.algorithm}`,
376
+ version: 'source-code',
377
+ algorithms: [algo.algorithm],
378
+ quantumRisk: algo.quantumRisk || 'unknown',
379
+ category: algo.category,
380
+ isDeprecated: algo.isWeak || false,
381
+ });
382
+ }
383
+ }
384
+
385
+ return inventory;
339
386
  }
package/lib/walker.mjs ADDED
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Unified single-pass file walker.
3
+ *
4
+ * Replaces three independent walkers (scanner.mjs, scanner-tls.mjs,
5
+ * scanner-binary.mjs) with a single directory traversal that classifies
6
+ * files into buckets by extension.
7
+ * Zero dependencies — uses only node:fs and node:path.
8
+ */
9
+
10
+ import { readdirSync, statSync } from 'node:fs';
11
+ import { join, extname } from 'node:path';
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Default skip directories (union of all three previous walkers)
15
+ // ---------------------------------------------------------------------------
16
+
17
+ export const DEFAULT_SKIP_DIRS = new Set([
18
+ 'node_modules', '.git', '.next', 'dist', 'build', 'coverage',
19
+ '.cache', '.nuxt', '.output', '.svelte-kit', '__pycache__',
20
+ 'vendor', '.venv', 'venv',
21
+ ]);
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // File classification by extension
25
+ // ---------------------------------------------------------------------------
26
+
27
+ const SOURCE_EXTENSIONS = new Set([
28
+ '.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx',
29
+ '.go', '.py',
30
+ '.java', '.kt', '.scala',
31
+ '.rs',
32
+ '.c', '.h', '.cpp', '.hpp', '.cc', '.cxx',
33
+ ]);
34
+
35
+ const CONFIG_EXTENSIONS = new Set([
36
+ '.conf', '.cfg', '.ini', '.toml', '.yaml', '.yml', '.json', '.xml',
37
+ ]);
38
+
39
+ const CONFIG_NAMES = new Set([
40
+ 'Dockerfile', 'docker-compose.yml', 'docker-compose.yaml',
41
+ 'nginx.conf', 'httpd.conf', 'apache2.conf', 'ssl.conf',
42
+ '.env',
43
+ ]);
44
+
45
+ const BINARY_EXTENSIONS = new Set([
46
+ '.exe', '.dll', '.so', '.dylib', '.wasm',
47
+ '.class', '.jar', '.war',
48
+ '.o', '.a', '.lib',
49
+ '.pyc', '.pyd',
50
+ ]);
51
+
52
+ const CERT_EXTENSIONS = new Set(['.pem', '.key', '.crt', '.p12', '.pfx', '.jks', '.keystore']);
53
+
54
+ // ---------------------------------------------------------------------------
55
+ // Walker
56
+ // ---------------------------------------------------------------------------
57
+
58
+ /**
59
+ * Walk a project directory once, classifying files into buckets.
60
+ *
61
+ * @param {string} dir - Root directory to walk
62
+ * @param {object} options
63
+ * @param {Set<string>} [options.skipDirs] - Extra directory names to skip (merged with defaults)
64
+ * @param {Set<string>} [options.includeExtensions] - Extra source extensions to include
65
+ * @param {number} [options.maxFiles=10000] - Max total files to collect
66
+ * @param {number} [options.maxBytes=524288000] - Max total bytes (500MB)
67
+ * @param {number} [options.maxFileSize=1048576] - Max single file size (1MB) for source/config
68
+ * @param {number} [options.maxBinaryFiles=50] - Max binary files
69
+ * @param {number} [options.maxBinaryFileSize=10485760] - Max binary file size (10MB)
70
+ * @returns {{ sourceFiles: string[], configFiles: string[], binaryFiles: string[], certFiles: string[], totalBytes: number, totalFiles: number }}
71
+ */
72
+ export function walkProject(dir, options = {}) {
73
+ const skipDirs = options.skipDirs
74
+ ? new Set([...DEFAULT_SKIP_DIRS, ...options.skipDirs])
75
+ : DEFAULT_SKIP_DIRS;
76
+ const extraSourceExts = options.includeExtensions
77
+ ? new Set([...SOURCE_EXTENSIONS, ...options.includeExtensions])
78
+ : SOURCE_EXTENSIONS;
79
+ const maxFiles = options.maxFiles || 10000;
80
+ const maxBytes = options.maxBytes || 500 * 1024 * 1024;
81
+ const maxFileSize = options.maxFileSize || 1024 * 1024;
82
+ const maxBinaryFiles = options.maxBinaryFiles || 50;
83
+ const maxBinaryFileSize = options.maxBinaryFileSize || 10 * 1024 * 1024;
84
+
85
+ const sourceFiles = [];
86
+ const configFiles = [];
87
+ const binaryFiles = [];
88
+ const certFiles = [];
89
+ let totalBytes = 0;
90
+ let totalFiles = 0;
91
+
92
+ function walk(currentDir) {
93
+ if (totalFiles >= maxFiles || totalBytes >= maxBytes) return;
94
+
95
+ let entries;
96
+ try { entries = readdirSync(currentDir, { withFileTypes: true }); }
97
+ catch { return; }
98
+
99
+ for (const entry of entries) {
100
+ if (totalFiles >= maxFiles || totalBytes >= maxBytes) return;
101
+
102
+ if (entry.isDirectory()) {
103
+ if (!skipDirs.has(entry.name) && !entry.name.startsWith('.')) {
104
+ walk(join(currentDir, entry.name));
105
+ }
106
+ continue;
107
+ }
108
+
109
+ if (!entry.isFile()) continue;
110
+
111
+ const filePath = join(currentDir, entry.name);
112
+ const ext = extname(entry.name).toLowerCase();
113
+ const name = entry.name;
114
+
115
+ // Classify cert files (no size check needed — just record path)
116
+ if (CERT_EXTENSIONS.has(ext)) {
117
+ certFiles.push(filePath);
118
+ totalFiles++;
119
+ continue;
120
+ }
121
+
122
+ // Classify binary files (separate limit)
123
+ if (BINARY_EXTENSIONS.has(ext)) {
124
+ if (binaryFiles.length < maxBinaryFiles) {
125
+ try {
126
+ const stat = statSync(filePath);
127
+ if (stat.size <= maxBinaryFileSize) {
128
+ binaryFiles.push(filePath);
129
+ totalFiles++;
130
+ }
131
+ } catch { /* skip */ }
132
+ }
133
+ continue;
134
+ }
135
+
136
+ // Check size for source/config files
137
+ let fileSize;
138
+ try {
139
+ const stat = statSync(filePath);
140
+ if (stat.size > maxFileSize) continue;
141
+ fileSize = stat.size;
142
+ } catch { continue; }
143
+
144
+ totalBytes += fileSize;
145
+ totalFiles++;
146
+
147
+ // Classify source files
148
+ if (extraSourceExts.has(ext)) {
149
+ sourceFiles.push(filePath);
150
+ continue;
151
+ }
152
+
153
+ // Classify config files
154
+ if (CONFIG_EXTENSIONS.has(ext) || CONFIG_NAMES.has(name)) {
155
+ configFiles.push(filePath);
156
+ continue;
157
+ }
158
+
159
+ // Other files are walked but not bucketed
160
+ }
161
+ }
162
+
163
+ walk(dir);
164
+
165
+ return { sourceFiles, configFiles, binaryFiles, certFiles, totalBytes, totalFiles };
166
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cryptoserve",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "CryptoServe CLI - Cryptographic scanning, PQC analysis, encryption, and local key management",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,7 +24,12 @@
24
24
  "security",
25
25
  "scanner",
26
26
  "keychain",
27
- "vault"
27
+ "vault",
28
+ "cbom",
29
+ "cyclonedx",
30
+ "spdx",
31
+ "binary-analysis",
32
+ "tls"
28
33
  ],
29
34
  "repository": {
30
35
  "type": "git",