verimu 0.0.2 → 0.0.4

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/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { execSync } from 'child_process';
2
+
1
3
  /**
2
4
  * Core types for the Verimu scanning pipeline.
3
5
  *
@@ -6,7 +8,7 @@
6
8
  * → CveSource → Vulnerability[]
7
9
  */
8
10
  /** Supported package ecosystems */
9
- type Ecosystem = 'npm' | 'nuget' | 'cargo' | 'maven' | 'pip' | 'go';
11
+ type Ecosystem = 'npm' | 'nuget' | 'cargo' | 'maven' | 'pip' | 'go' | 'ruby';
10
12
  /** Supported CI/CD providers */
11
13
  type CiProvider = 'github' | 'gitlab' | 'bitbucket';
12
14
  /** A single resolved dependency from a lockfile */
@@ -208,15 +210,30 @@ interface GenerateSbomResult {
208
210
  */
209
211
  declare function generateSbom(input: GenerateSbomInput): GenerateSbomResult;
210
212
 
213
+ /** Result of uploading scan results to the Verimu platform */
214
+ interface UploadResult {
215
+ projectId: string;
216
+ projectCreated: boolean;
217
+ totalDependencies: number;
218
+ vulnerableDependencies: number;
219
+ dashboardUrl: string;
220
+ }
211
221
  /**
212
222
  * Main scan pipeline — orchestrates the full Verimu workflow:
213
223
  * 1. Detect ecosystem & parse lockfile
214
224
  * 2. Generate CycloneDX SBOM
215
- * 3. Check dependencies for CVEs
225
+ * 3. Check dependencies for CVEs (via OSV)
216
226
  * 4. Produce report
217
- * 5. Optionally upload snapshot to Verimu API
227
+ * 5. Upload to Verimu platform (if API key provided)
218
228
  */
219
229
  declare function scan(config: VerimuConfig): Promise<VerimuReport>;
230
+ /**
231
+ * Uploads scan results to the Verimu platform.
232
+ *
233
+ * 1. Upserts the project (create-if-not-exists by name)
234
+ * 2. POSTs the SBOM for dependency tracking + CVE scanning
235
+ */
236
+ declare function uploadToVerimu(report: VerimuReport, config: VerimuConfig): Promise<UploadResult>;
220
237
  /**
221
238
  * Determines if the scan should fail CI based on severity threshold.
222
239
  */
@@ -248,6 +265,71 @@ declare class ApiKeyRequiredError extends VerimuError {
248
265
  constructor(feature: string);
249
266
  }
250
267
 
268
+ /**
269
+ * Verimu API client — communicates with the Verimu backend.
270
+ *
271
+ * Used by the CLI and scan pipeline to:
272
+ * 1. Upsert a project (create-if-not-exists)
273
+ * 2. Upload SBOM + trigger CVE scan
274
+ */
275
+
276
+ interface UpsertProjectResponse {
277
+ project: {
278
+ id: string;
279
+ name: string;
280
+ ecosystem: string;
281
+ repository_url: string | null;
282
+ platform: string | null;
283
+ };
284
+ created: boolean;
285
+ }
286
+ interface ScanResponse {
287
+ project: {
288
+ id: string;
289
+ name: string;
290
+ };
291
+ scan_results: Array<{
292
+ dependency_id: string;
293
+ dependency_name: string;
294
+ version: string;
295
+ vulnerabilities: Array<{
296
+ cve_id: string;
297
+ severity: string;
298
+ summary: string;
299
+ fixed_version: string | null;
300
+ }>;
301
+ }>;
302
+ summary: {
303
+ total_dependencies: number;
304
+ vulnerable_dependencies: number;
305
+ };
306
+ }
307
+ declare class VerimuApiClient {
308
+ private readonly baseUrl;
309
+ private readonly apiKey;
310
+ constructor(apiKey: string, baseUrl?: string);
311
+ /**
312
+ * Upsert a project — finds by name or creates it.
313
+ * Used so `npx verimu` auto-registers projects without manual dashboard setup.
314
+ */
315
+ upsertProject(opts: {
316
+ name: string;
317
+ ecosystem: Ecosystem;
318
+ repositoryUrl?: string;
319
+ platform?: string;
320
+ }): Promise<UpsertProjectResponse>;
321
+ /**
322
+ * Upload a CycloneDX SBOM to a project and trigger CVE scanning.
323
+ */
324
+ uploadSbom(projectId: string, sbomContent: string): Promise<ScanResponse>;
325
+ private headers;
326
+ /**
327
+ * Maps internal ecosystem names to what the backend expects.
328
+ * Currently 1:1, but keeps the mapping explicit.
329
+ */
330
+ private mapEcosystem;
331
+ }
332
+
251
333
  /**
252
334
  * Interface for language/ecosystem-specific dependency scanners.
253
335
  *
@@ -307,6 +389,342 @@ declare class NpmScanner implements DependencyScanner {
307
389
  private parseDependenciesV1;
308
390
  }
309
391
 
392
+ /**
393
+ * C# / NuGet dependency scanner.
394
+ *
395
+ * Parses `packages.lock.json` (NuGet lock file, generated by
396
+ * `dotnet restore --use-lock-file`) to extract the full resolved
397
+ * dependency tree. The lock file itself tracks Direct vs Transitive.
398
+ *
399
+ * Lock file format (NuGet v1):
400
+ * ```json
401
+ * {
402
+ * "version": 1,
403
+ * "dependencies": {
404
+ * "net8.0": {
405
+ * "PackageName": {
406
+ * "type": "Direct" | "Transitive",
407
+ * "resolved": "13.0.3",
408
+ * "contentHash": "..."
409
+ * }
410
+ * }
411
+ * }
412
+ * }
413
+ * ```
414
+ */
415
+ declare class NugetScanner implements DependencyScanner {
416
+ readonly ecosystem: Ecosystem;
417
+ readonly lockfileNames: string[];
418
+ detect(projectPath: string): Promise<string | null>;
419
+ scan(projectPath: string, lockfilePath: string): Promise<ScanResult>;
420
+ /**
421
+ * Parses packages.lock.json and extracts dependencies across all
422
+ * target frameworks. Deduplicates by package name (keeps highest version
423
+ * if the same package appears under multiple frameworks).
424
+ */
425
+ private parseLockfile;
426
+ /**
427
+ * Builds a purl for a NuGet package.
428
+ * NuGet purls are straightforward: pkg:nuget/Name@Version
429
+ */
430
+ private buildPurl;
431
+ }
432
+
433
+ /**
434
+ * Python / pip dependency scanner.
435
+ *
436
+ * Supports multiple Python dependency file formats (in priority order):
437
+ * 1. `requirements.txt` — flat list of pinned dependencies
438
+ * 2. `Pipfile.lock` — Pipenv lock file with exact versions
439
+ *
440
+ * For `requirements.txt`, all listed packages are treated as direct
441
+ * dependencies (the file doesn't distinguish direct vs transitive).
442
+ * For `Pipfile.lock`, `default` packages are direct and `develop`
443
+ * packages are dev dependencies.
444
+ *
445
+ * Limitation: `requirements.txt` doesn't capture transitive deps unless
446
+ * generated with `pip freeze`. If using `pip freeze` output, all deps
447
+ * are listed but we can't distinguish direct vs transitive.
448
+ */
449
+ declare class PipScanner implements DependencyScanner {
450
+ readonly ecosystem: Ecosystem;
451
+ readonly lockfileNames: string[];
452
+ detect(projectPath: string): Promise<string | null>;
453
+ scan(projectPath: string, lockfilePath: string): Promise<ScanResult>;
454
+ /**
455
+ * Parses `requirements.txt` format.
456
+ *
457
+ * Supports:
458
+ * - `package==1.2.3` (pinned)
459
+ * - `package>=1.2.0` (minimum — uses the specified version)
460
+ * - `package~=1.2.0` (compatible release)
461
+ * - Comments (`#`) and blank lines are skipped
462
+ * - `-r other-file.txt` (include directive) — skipped for now
463
+ * - `--index-url` and other pip flags — skipped
464
+ */
465
+ private parseRequirementsTxt;
466
+ /**
467
+ * Parses `Pipfile.lock` (JSON format from Pipenv).
468
+ *
469
+ * Structure:
470
+ * ```json
471
+ * {
472
+ * "_meta": { ... },
473
+ * "default": {
474
+ * "requests": { "version": "==2.31.0", ... }
475
+ * },
476
+ * "develop": {
477
+ * "pytest": { "version": "==7.4.0", ... }
478
+ * }
479
+ * }
480
+ * ```
481
+ */
482
+ private parsePipfileLock;
483
+ /**
484
+ * Extracts the version number from a pip version specifier.
485
+ * "1.2.3" → "1.2.3"
486
+ * "1.2.3,<2.0" → "1.2.3"
487
+ */
488
+ private extractVersion;
489
+ /**
490
+ * Normalizes a pip package name per PEP 503.
491
+ * Converts to lowercase and replaces any run of [-_.] with a single hyphen.
492
+ */
493
+ private normalizePipName;
494
+ /**
495
+ * Builds a purl for a PyPI package.
496
+ * Per purl spec, the type is "pypi" (not "pip").
497
+ */
498
+ private buildPurl;
499
+ }
500
+
501
+ /**
502
+ * Rust / Cargo dependency scanner.
503
+ *
504
+ * Parses `Cargo.lock` (TOML format) to extract the full resolved
505
+ * dependency tree. Reads `Cargo.toml` to determine which packages
506
+ * are direct dependencies vs transitive.
507
+ *
508
+ * Cargo.lock format (v3):
509
+ * ```toml
510
+ * [[package]]
511
+ * name = "serde"
512
+ * version = "1.0.195"
513
+ * source = "registry+https://github.com/rust-lang/crates.io-index"
514
+ * checksum = "abc123..."
515
+ * dependencies = [
516
+ * "serde_derive",
517
+ * ]
518
+ * ```
519
+ *
520
+ * Note: We use a simple TOML parser since Cargo.lock has a very
521
+ * regular structure (just [[package]] entries). No need for a full
522
+ * TOML library.
523
+ */
524
+ declare class CargoScanner implements DependencyScanner {
525
+ readonly ecosystem: Ecosystem;
526
+ readonly lockfileNames: string[];
527
+ detect(projectPath: string): Promise<string | null>;
528
+ scan(projectPath: string, lockfilePath: string): Promise<ScanResult>;
529
+ /**
530
+ * Parses Cargo.lock by splitting on [[package]] blocks.
531
+ * This is a lightweight parser that handles the regular structure
532
+ * of Cargo.lock without needing a full TOML parser.
533
+ */
534
+ private parseLockfile;
535
+ /**
536
+ * Extracts a string field value from a TOML block.
537
+ * Handles: `name = "value"` format.
538
+ */
539
+ private extractField;
540
+ /**
541
+ * Parses Cargo.toml to extract direct dependency names.
542
+ * Looks for [dependencies] and [dev-dependencies] sections.
543
+ */
544
+ private parseCargoToml;
545
+ /**
546
+ * Builds a purl for a Cargo (crates.io) package.
547
+ */
548
+ private buildPurl;
549
+ }
550
+
551
+ /**
552
+ * Java / Maven dependency scanner.
553
+ *
554
+ * Maven doesn't have a lockfile. This scanner uses two strategies:
555
+ *
556
+ * 1. **Primary (auto)**: If `mvn` is on `$PATH`, runs
557
+ * `mvn dependency:list -DoutputType=text` to get the resolved
558
+ * dependency tree including transitive dependencies.
559
+ *
560
+ * 2. **Fallback (pre-generated)**: Looks for a `dependency-tree.txt`
561
+ * file in the project root. Users can generate this with:
562
+ * ```
563
+ * mvn dependency:list -DoutputFile=dependency-tree.txt -DoutputType=text
564
+ * ```
565
+ *
566
+ * The scanner detects a Maven project by the presence of `pom.xml`.
567
+ *
568
+ * Maven dependency:list output format (one per line):
569
+ * ```
570
+ * com.google.guava:guava:jar:32.1.3-jre:compile
571
+ * org.slf4j:slf4j-api:jar:2.0.9:compile
572
+ * junit:junit:jar:4.13.2:test
573
+ * ```
574
+ * Fields: groupId:artifactId:type:version:scope
575
+ */
576
+ declare class MavenScanner implements DependencyScanner {
577
+ readonly ecosystem: Ecosystem;
578
+ readonly lockfileNames: string[];
579
+ /** Allow injection for testing */
580
+ private execSyncFn;
581
+ constructor(execSyncImpl?: typeof execSync);
582
+ detect(projectPath: string): Promise<string | null>;
583
+ scan(projectPath: string, _lockfilePath: string): Promise<ScanResult>;
584
+ /**
585
+ * Parses Maven `dependency:list` output.
586
+ *
587
+ * Each dependency line has the format:
588
+ * groupId:artifactId:type:version:scope
589
+ * groupId:artifactId:type:classifier:version:scope
590
+ *
591
+ * Lines are typically indented with leading whitespace.
592
+ */
593
+ private parseDependencyList;
594
+ /** Checks if `mvn` is available on PATH */
595
+ private isMavenAvailable;
596
+ /**
597
+ * Runs `mvn dependency:list` and returns the output.
598
+ */
599
+ private runMavenDependencyList;
600
+ /**
601
+ * Builds a purl for a Maven package.
602
+ * Format: pkg:maven/groupId/artifactId@version
603
+ */
604
+ private buildPurl;
605
+ private buildResult;
606
+ }
607
+
608
+ /**
609
+ * Go module dependency scanner.
610
+ *
611
+ * Parses `go.sum` to extract the full resolved dependency list, and
612
+ * cross-references `go.mod` to distinguish direct vs indirect (transitive)
613
+ * dependencies.
614
+ *
615
+ * go.sum format (one or two lines per module):
616
+ * ```
617
+ * github.com/gin-gonic/gin v1.9.1 h1:abc123...=
618
+ * github.com/gin-gonic/gin v1.9.1/go.mod h1:def456...=
619
+ * ```
620
+ *
621
+ * Lines ending in `/go.mod` are checksums of the module's go.mod file —
622
+ * we skip those and only keep the `h1:` lines (source archive checksums).
623
+ *
624
+ * go.mod `require` block format:
625
+ * ```
626
+ * require (
627
+ * github.com/gin-gonic/gin v1.9.1
628
+ * golang.org/x/text v0.14.0 // indirect
629
+ * )
630
+ * ```
631
+ *
632
+ * Dependencies marked `// indirect` are transitive.
633
+ */
634
+ declare class GoScanner implements DependencyScanner {
635
+ readonly ecosystem: Ecosystem;
636
+ readonly lockfileNames: string[];
637
+ detect(projectPath: string): Promise<string | null>;
638
+ scan(projectPath: string, lockfilePath: string): Promise<ScanResult>;
639
+ /**
640
+ * Parses go.sum and extracts unique module dependencies.
641
+ *
642
+ * Each module may appear twice in go.sum (once for the source archive,
643
+ * once for go.mod). We deduplicate by module path + version, keeping
644
+ * only the `h1:` entry (not the `/go.mod` entry).
645
+ */
646
+ private parseGoSum;
647
+ /**
648
+ * Parses go.mod to extract direct and indirect dependency names.
649
+ *
650
+ * Handles both single-line and block `require` directives:
651
+ * ```
652
+ * require github.com/pkg/errors v0.9.1
653
+ *
654
+ * require (
655
+ * github.com/gin-gonic/gin v1.9.1
656
+ * golang.org/x/text v0.14.0 // indirect
657
+ * )
658
+ * ```
659
+ */
660
+ private parseGoMod;
661
+ /**
662
+ * Builds a purl for a Go module.
663
+ *
664
+ * Per purl spec, the type is "golang" and the module path
665
+ * uses `/` separators (no encoding needed for path segments).
666
+ *
667
+ * Example: `pkg:golang/github.com/gin-gonic/gin@v1.9.1`
668
+ */
669
+ private buildPurl;
670
+ }
671
+
672
+ /**
673
+ * Ruby dependency scanner (Bundler).
674
+ *
675
+ * Parses `Gemfile.lock` to extract the full resolved dependency list,
676
+ * and cross-references the `DEPENDENCIES` section to distinguish
677
+ * direct vs transitive gems.
678
+ *
679
+ * Gemfile.lock format:
680
+ * ```
681
+ * GEM
682
+ * remote: https://rubygems.org/
683
+ * specs:
684
+ * actioncable (7.1.2)
685
+ * actionpack (= 7.1.2)
686
+ * activesupport (= 7.1.2)
687
+ * rack (3.0.8)
688
+ *
689
+ * PLATFORMS
690
+ * ruby
691
+ *
692
+ * DEPENDENCIES
693
+ * puma (>= 5.0)
694
+ * rails (~> 7.1.2)
695
+ *
696
+ * BUNDLED WITH
697
+ * 2.5.3
698
+ * ```
699
+ *
700
+ * The `GEM > specs:` section lists all resolved gems with exact versions.
701
+ * The `DEPENDENCIES` section lists direct gems (from the Gemfile).
702
+ */
703
+ declare class RubyScanner implements DependencyScanner {
704
+ readonly ecosystem: Ecosystem;
705
+ readonly lockfileNames: string[];
706
+ detect(projectPath: string): Promise<string | null>;
707
+ scan(projectPath: string, lockfilePath: string): Promise<ScanResult>;
708
+ /**
709
+ * Parses the GEM > specs section to extract all resolved gems.
710
+ *
711
+ * Gems at the top level of the specs section (indented 4 spaces) are
712
+ * resolved packages. Their sub-dependencies (indented 6+ spaces) are
713
+ * constraints, not separate entries — those sub-deps appear as their
714
+ * own top-level spec entries elsewhere.
715
+ *
716
+ * Format: ` gem-name (1.2.3)`
717
+ */
718
+ private parseSpecs;
719
+ /**
720
+ * Parses the DEPENDENCIES section to get direct dependency names.
721
+ *
722
+ * Format: ` gem-name (>= 1.0)` or ` gem-name`
723
+ * The version constraint is optional and we only need the name.
724
+ */
725
+ private parseDependencies;
726
+ }
727
+
310
728
  /**
311
729
  * Registry of all available dependency scanners.
312
730
  * Auto-detects the correct scanner for a given project.
@@ -472,4 +890,4 @@ declare class ConsoleReporter implements Reporter {
472
890
  report(result: VerimuReport): string;
473
891
  }
474
892
 
475
- export { ApiKeyRequiredError, type CiProvider, ConsoleReporter, CveAggregator, type CveCheckResult, CveSourceError, CycloneDxGenerator, type Dependency, type Ecosystem, type GenerateSbomInput, type GenerateSbomResult, LockfileParseError, NoLockfileError, NpmScanner, OsvSource, type Sbom, type SbomDependency, type SbomFormat, type ScanResult, ScannerRegistry, type Severity, type VerimuConfig, VerimuError, type VerimuReport, type Vulnerability, type VulnerabilitySource, generateSbom, printReport, scan, shouldFailCi };
893
+ export { ApiKeyRequiredError, CargoScanner, type CiProvider, ConsoleReporter, CveAggregator, type CveCheckResult, CveSourceError, CycloneDxGenerator, type Dependency, type Ecosystem, type GenerateSbomInput, type GenerateSbomResult, GoScanner, LockfileParseError, MavenScanner, NoLockfileError, NpmScanner, NugetScanner, OsvSource, PipScanner, RubyScanner, type Sbom, type SbomDependency, type SbomFormat, type ScanResult, ScannerRegistry, type Severity, type UploadResult, VerimuApiClient, type VerimuConfig, VerimuError, type VerimuReport, type Vulnerability, type VulnerabilitySource, generateSbom, printReport, scan, shouldFailCi, uploadToVerimu };