verimu 0.0.2 → 0.0.3

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 */
@@ -307,6 +309,342 @@ declare class NpmScanner implements DependencyScanner {
307
309
  private parseDependenciesV1;
308
310
  }
309
311
 
312
+ /**
313
+ * C# / NuGet dependency scanner.
314
+ *
315
+ * Parses `packages.lock.json` (NuGet lock file, generated by
316
+ * `dotnet restore --use-lock-file`) to extract the full resolved
317
+ * dependency tree. The lock file itself tracks Direct vs Transitive.
318
+ *
319
+ * Lock file format (NuGet v1):
320
+ * ```json
321
+ * {
322
+ * "version": 1,
323
+ * "dependencies": {
324
+ * "net8.0": {
325
+ * "PackageName": {
326
+ * "type": "Direct" | "Transitive",
327
+ * "resolved": "13.0.3",
328
+ * "contentHash": "..."
329
+ * }
330
+ * }
331
+ * }
332
+ * }
333
+ * ```
334
+ */
335
+ declare class NugetScanner implements DependencyScanner {
336
+ readonly ecosystem: Ecosystem;
337
+ readonly lockfileNames: string[];
338
+ detect(projectPath: string): Promise<string | null>;
339
+ scan(projectPath: string, lockfilePath: string): Promise<ScanResult>;
340
+ /**
341
+ * Parses packages.lock.json and extracts dependencies across all
342
+ * target frameworks. Deduplicates by package name (keeps highest version
343
+ * if the same package appears under multiple frameworks).
344
+ */
345
+ private parseLockfile;
346
+ /**
347
+ * Builds a purl for a NuGet package.
348
+ * NuGet purls are straightforward: pkg:nuget/Name@Version
349
+ */
350
+ private buildPurl;
351
+ }
352
+
353
+ /**
354
+ * Python / pip dependency scanner.
355
+ *
356
+ * Supports multiple Python dependency file formats (in priority order):
357
+ * 1. `requirements.txt` — flat list of pinned dependencies
358
+ * 2. `Pipfile.lock` — Pipenv lock file with exact versions
359
+ *
360
+ * For `requirements.txt`, all listed packages are treated as direct
361
+ * dependencies (the file doesn't distinguish direct vs transitive).
362
+ * For `Pipfile.lock`, `default` packages are direct and `develop`
363
+ * packages are dev dependencies.
364
+ *
365
+ * Limitation: `requirements.txt` doesn't capture transitive deps unless
366
+ * generated with `pip freeze`. If using `pip freeze` output, all deps
367
+ * are listed but we can't distinguish direct vs transitive.
368
+ */
369
+ declare class PipScanner implements DependencyScanner {
370
+ readonly ecosystem: Ecosystem;
371
+ readonly lockfileNames: string[];
372
+ detect(projectPath: string): Promise<string | null>;
373
+ scan(projectPath: string, lockfilePath: string): Promise<ScanResult>;
374
+ /**
375
+ * Parses `requirements.txt` format.
376
+ *
377
+ * Supports:
378
+ * - `package==1.2.3` (pinned)
379
+ * - `package>=1.2.0` (minimum — uses the specified version)
380
+ * - `package~=1.2.0` (compatible release)
381
+ * - Comments (`#`) and blank lines are skipped
382
+ * - `-r other-file.txt` (include directive) — skipped for now
383
+ * - `--index-url` and other pip flags — skipped
384
+ */
385
+ private parseRequirementsTxt;
386
+ /**
387
+ * Parses `Pipfile.lock` (JSON format from Pipenv).
388
+ *
389
+ * Structure:
390
+ * ```json
391
+ * {
392
+ * "_meta": { ... },
393
+ * "default": {
394
+ * "requests": { "version": "==2.31.0", ... }
395
+ * },
396
+ * "develop": {
397
+ * "pytest": { "version": "==7.4.0", ... }
398
+ * }
399
+ * }
400
+ * ```
401
+ */
402
+ private parsePipfileLock;
403
+ /**
404
+ * Extracts the version number from a pip version specifier.
405
+ * "1.2.3" → "1.2.3"
406
+ * "1.2.3,<2.0" → "1.2.3"
407
+ */
408
+ private extractVersion;
409
+ /**
410
+ * Normalizes a pip package name per PEP 503.
411
+ * Converts to lowercase and replaces any run of [-_.] with a single hyphen.
412
+ */
413
+ private normalizePipName;
414
+ /**
415
+ * Builds a purl for a PyPI package.
416
+ * Per purl spec, the type is "pypi" (not "pip").
417
+ */
418
+ private buildPurl;
419
+ }
420
+
421
+ /**
422
+ * Rust / Cargo dependency scanner.
423
+ *
424
+ * Parses `Cargo.lock` (TOML format) to extract the full resolved
425
+ * dependency tree. Reads `Cargo.toml` to determine which packages
426
+ * are direct dependencies vs transitive.
427
+ *
428
+ * Cargo.lock format (v3):
429
+ * ```toml
430
+ * [[package]]
431
+ * name = "serde"
432
+ * version = "1.0.195"
433
+ * source = "registry+https://github.com/rust-lang/crates.io-index"
434
+ * checksum = "abc123..."
435
+ * dependencies = [
436
+ * "serde_derive",
437
+ * ]
438
+ * ```
439
+ *
440
+ * Note: We use a simple TOML parser since Cargo.lock has a very
441
+ * regular structure (just [[package]] entries). No need for a full
442
+ * TOML library.
443
+ */
444
+ declare class CargoScanner implements DependencyScanner {
445
+ readonly ecosystem: Ecosystem;
446
+ readonly lockfileNames: string[];
447
+ detect(projectPath: string): Promise<string | null>;
448
+ scan(projectPath: string, lockfilePath: string): Promise<ScanResult>;
449
+ /**
450
+ * Parses Cargo.lock by splitting on [[package]] blocks.
451
+ * This is a lightweight parser that handles the regular structure
452
+ * of Cargo.lock without needing a full TOML parser.
453
+ */
454
+ private parseLockfile;
455
+ /**
456
+ * Extracts a string field value from a TOML block.
457
+ * Handles: `name = "value"` format.
458
+ */
459
+ private extractField;
460
+ /**
461
+ * Parses Cargo.toml to extract direct dependency names.
462
+ * Looks for [dependencies] and [dev-dependencies] sections.
463
+ */
464
+ private parseCargoToml;
465
+ /**
466
+ * Builds a purl for a Cargo (crates.io) package.
467
+ */
468
+ private buildPurl;
469
+ }
470
+
471
+ /**
472
+ * Java / Maven dependency scanner.
473
+ *
474
+ * Maven doesn't have a lockfile. This scanner uses two strategies:
475
+ *
476
+ * 1. **Primary (auto)**: If `mvn` is on `$PATH`, runs
477
+ * `mvn dependency:list -DoutputType=text` to get the resolved
478
+ * dependency tree including transitive dependencies.
479
+ *
480
+ * 2. **Fallback (pre-generated)**: Looks for a `dependency-tree.txt`
481
+ * file in the project root. Users can generate this with:
482
+ * ```
483
+ * mvn dependency:list -DoutputFile=dependency-tree.txt -DoutputType=text
484
+ * ```
485
+ *
486
+ * The scanner detects a Maven project by the presence of `pom.xml`.
487
+ *
488
+ * Maven dependency:list output format (one per line):
489
+ * ```
490
+ * com.google.guava:guava:jar:32.1.3-jre:compile
491
+ * org.slf4j:slf4j-api:jar:2.0.9:compile
492
+ * junit:junit:jar:4.13.2:test
493
+ * ```
494
+ * Fields: groupId:artifactId:type:version:scope
495
+ */
496
+ declare class MavenScanner implements DependencyScanner {
497
+ readonly ecosystem: Ecosystem;
498
+ readonly lockfileNames: string[];
499
+ /** Allow injection for testing */
500
+ private execSyncFn;
501
+ constructor(execSyncImpl?: typeof execSync);
502
+ detect(projectPath: string): Promise<string | null>;
503
+ scan(projectPath: string, _lockfilePath: string): Promise<ScanResult>;
504
+ /**
505
+ * Parses Maven `dependency:list` output.
506
+ *
507
+ * Each dependency line has the format:
508
+ * groupId:artifactId:type:version:scope
509
+ * groupId:artifactId:type:classifier:version:scope
510
+ *
511
+ * Lines are typically indented with leading whitespace.
512
+ */
513
+ private parseDependencyList;
514
+ /** Checks if `mvn` is available on PATH */
515
+ private isMavenAvailable;
516
+ /**
517
+ * Runs `mvn dependency:list` and returns the output.
518
+ */
519
+ private runMavenDependencyList;
520
+ /**
521
+ * Builds a purl for a Maven package.
522
+ * Format: pkg:maven/groupId/artifactId@version
523
+ */
524
+ private buildPurl;
525
+ private buildResult;
526
+ }
527
+
528
+ /**
529
+ * Go module dependency scanner.
530
+ *
531
+ * Parses `go.sum` to extract the full resolved dependency list, and
532
+ * cross-references `go.mod` to distinguish direct vs indirect (transitive)
533
+ * dependencies.
534
+ *
535
+ * go.sum format (one or two lines per module):
536
+ * ```
537
+ * github.com/gin-gonic/gin v1.9.1 h1:abc123...=
538
+ * github.com/gin-gonic/gin v1.9.1/go.mod h1:def456...=
539
+ * ```
540
+ *
541
+ * Lines ending in `/go.mod` are checksums of the module's go.mod file —
542
+ * we skip those and only keep the `h1:` lines (source archive checksums).
543
+ *
544
+ * go.mod `require` block format:
545
+ * ```
546
+ * require (
547
+ * github.com/gin-gonic/gin v1.9.1
548
+ * golang.org/x/text v0.14.0 // indirect
549
+ * )
550
+ * ```
551
+ *
552
+ * Dependencies marked `// indirect` are transitive.
553
+ */
554
+ declare class GoScanner implements DependencyScanner {
555
+ readonly ecosystem: Ecosystem;
556
+ readonly lockfileNames: string[];
557
+ detect(projectPath: string): Promise<string | null>;
558
+ scan(projectPath: string, lockfilePath: string): Promise<ScanResult>;
559
+ /**
560
+ * Parses go.sum and extracts unique module dependencies.
561
+ *
562
+ * Each module may appear twice in go.sum (once for the source archive,
563
+ * once for go.mod). We deduplicate by module path + version, keeping
564
+ * only the `h1:` entry (not the `/go.mod` entry).
565
+ */
566
+ private parseGoSum;
567
+ /**
568
+ * Parses go.mod to extract direct and indirect dependency names.
569
+ *
570
+ * Handles both single-line and block `require` directives:
571
+ * ```
572
+ * require github.com/pkg/errors v0.9.1
573
+ *
574
+ * require (
575
+ * github.com/gin-gonic/gin v1.9.1
576
+ * golang.org/x/text v0.14.0 // indirect
577
+ * )
578
+ * ```
579
+ */
580
+ private parseGoMod;
581
+ /**
582
+ * Builds a purl for a Go module.
583
+ *
584
+ * Per purl spec, the type is "golang" and the module path
585
+ * uses `/` separators (no encoding needed for path segments).
586
+ *
587
+ * Example: `pkg:golang/github.com/gin-gonic/gin@v1.9.1`
588
+ */
589
+ private buildPurl;
590
+ }
591
+
592
+ /**
593
+ * Ruby dependency scanner (Bundler).
594
+ *
595
+ * Parses `Gemfile.lock` to extract the full resolved dependency list,
596
+ * and cross-references the `DEPENDENCIES` section to distinguish
597
+ * direct vs transitive gems.
598
+ *
599
+ * Gemfile.lock format:
600
+ * ```
601
+ * GEM
602
+ * remote: https://rubygems.org/
603
+ * specs:
604
+ * actioncable (7.1.2)
605
+ * actionpack (= 7.1.2)
606
+ * activesupport (= 7.1.2)
607
+ * rack (3.0.8)
608
+ *
609
+ * PLATFORMS
610
+ * ruby
611
+ *
612
+ * DEPENDENCIES
613
+ * puma (>= 5.0)
614
+ * rails (~> 7.1.2)
615
+ *
616
+ * BUNDLED WITH
617
+ * 2.5.3
618
+ * ```
619
+ *
620
+ * The `GEM > specs:` section lists all resolved gems with exact versions.
621
+ * The `DEPENDENCIES` section lists direct gems (from the Gemfile).
622
+ */
623
+ declare class RubyScanner implements DependencyScanner {
624
+ readonly ecosystem: Ecosystem;
625
+ readonly lockfileNames: string[];
626
+ detect(projectPath: string): Promise<string | null>;
627
+ scan(projectPath: string, lockfilePath: string): Promise<ScanResult>;
628
+ /**
629
+ * Parses the GEM > specs section to extract all resolved gems.
630
+ *
631
+ * Gems at the top level of the specs section (indented 4 spaces) are
632
+ * resolved packages. Their sub-dependencies (indented 6+ spaces) are
633
+ * constraints, not separate entries — those sub-deps appear as their
634
+ * own top-level spec entries elsewhere.
635
+ *
636
+ * Format: ` gem-name (1.2.3)`
637
+ */
638
+ private parseSpecs;
639
+ /**
640
+ * Parses the DEPENDENCIES section to get direct dependency names.
641
+ *
642
+ * Format: ` gem-name (>= 1.0)` or ` gem-name`
643
+ * The version constraint is optional and we only need the name.
644
+ */
645
+ private parseDependencies;
646
+ }
647
+
310
648
  /**
311
649
  * Registry of all available dependency scanners.
312
650
  * Auto-detects the correct scanner for a given project.
@@ -472,4 +810,4 @@ declare class ConsoleReporter implements Reporter {
472
810
  report(result: VerimuReport): string;
473
811
  }
474
812
 
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 };
813
+ 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 VerimuConfig, VerimuError, type VerimuReport, type Vulnerability, type VulnerabilitySource, generateSbom, printReport, scan, shouldFailCi };