uv-sbom 2.2.0

SBOM generation tool for uv projects - Generate CycloneDX SBOMs from uv.lock files
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
# uv-sbom

[![GitHub release](https://img.shields.io/github/release/Taketo-Yoda/uv-sbom.svg)](https://github.com/Taketo-Yoda/uv-sbom/releases) [![PyPI - Version](https://img.shields.io/pypi/v/uv-sbom-bin?logo=python&logoColor=white&label=PyPI)](https://pypi.org/project/uv-sbom-bin/) [![Crates.io Version](https://img.shields.io/crates/v/uv-sbom?logo=rust&logoColor=white)](https://crates.io/crates/uv-sbom)
[![shield_license]][license_file] [![CI](https://github.com/Taketo-Yoda/uv-sbom/actions/workflows/ci.yml/badge.svg)](https://github.com/Taketo-Yoda/uv-sbom/actions/workflows/ci.yml)
[![Dependabot Updates](https://github.com/Taketo-Yoda/uv-sbom/actions/workflows/dependabot/dependabot-updates/badge.svg)](https://github.com/Taketo-Yoda/uv-sbom/actions/workflows/dependabot/dependabot-updates) [![CodeQL](https://github.com/Taketo-Yoda/uv-sbom/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/Taketo-Yoda/uv-sbom/actions/workflows/github-code-scanning/codeql)

[English]README.md | [日本語]README-JP.md

----

Generate SBOMs (Software Bill of Materials) for Python projects managed by [uv](https://github.com/astral-sh/uv).

## Features

- 📦 Parses `uv.lock` files to extract dependency information
- 🔍 Automatically fetches license information from PyPI with retry logic
- 🛡️ Checks for known vulnerabilities using OSV API (Markdown format only)
- 📋 License compliance policy check with configurable allow/deny lists and wildcard support
- 🔎 **Vulnerability Resolution Guide** - Identifies which direct dependency introduces each vulnerable transitive package
- 📊 Outputs in multiple formats:
  - **CycloneDX 1.6** JSON format (standard SBOM format)
  - **Markdown** format with direct and transitive dependencies clearly separated
- 🚀 Fast and standalone - written in Rust
- 💾 Output to stdout or file
- 🛡️ Robust error handling with helpful error messages and suggestions
- 📈 Progress tracking during license information retrieval

## Scope and Key Differences from CycloneDX

### SBOM Scope

This tool generates SBOMs based on **uv.lock** file contents, which includes:
- Direct runtime dependencies
- Transitive runtime dependencies
- Development dependencies (if locked in uv.lock)

**What's NOT included:**
- Build system dependencies (e.g., hatchling, setuptools)
- Publishing tools (e.g., twine, build)
- Dependencies only present in the virtual environment but not locked in uv.lock

### Comparison with CycloneDX Official Tools

As of v7.2.1, the official cyclonedx-python library does not yet provide direct support for uv. When generating SBOMs for Python projects:

| Aspect | uv-sbom (this tool) | CycloneDX Official Tools |
|--------|---------------------|--------------------------|
| **Data Source** | `uv.lock` file | `.venv` virtual environment |
| **Scope** | Production runtime dependencies only | Entire supply chain including build/dev tools |
| **Package Count** | Typically fewer (e.g., 16 packages) | Typically more (e.g., 38+ packages) |
| **Use Case** | Production security scanning | Comprehensive supply chain audit |
| **Accuracy** | Reflects locked dependencies | Reflects installed packages |

### Which Tool Should You Use?

- **For production security scanning**: Use `uv-sbom` to focus on dependencies that will be deployed to production
- **For comprehensive supply chain audit**: Use CycloneDX official tools to include all development and build-time dependencies
- **For regulatory compliance**: Check your specific requirements - some regulations may require the comprehensive approach

The focused approach of `uv-sbom` reduces noise in security vulnerability scanning by excluding build-time dependencies that don't ship with the final application.

## Installation

### Cargo (Recommended for Rust users)

![Crates.io Total Downloads](https://img.shields.io/crates/d/uv-sbom)

Install from [crates.io](https://crates.io/crates/uv-sbom):

```bash
cargo install uv-sbom
```

### uv tool (Python users)

![PyPI - Downloads](https://img.shields.io/pypi/dm/uv-sbom-bin?logo=PyPI&logoColor=white)

Install the Python wrapper package:

```bash
uv tool install uv-sbom-bin
```

Or via pip:

```bash
pip install uv-sbom-bin
```

After installation, use the `uv-sbom` command:

```bash
uv-sbom --version
```

**Note**: The package name is `uv-sbom-bin`, but the installed command is `uv-sbom`.

### Pre-built Binaries

![GitHub Downloads (all assets, all releases)](https://img.shields.io/github/downloads/Taketo-Yoda/uv-sbom/total?logo=GitHub)


Download pre-built binaries from [GitHub Releases](https://github.com/Taketo-Yoda/uv-sbom/releases):

**macOS (Apple Silicon)**:
```bash
curl -LO https://github.com/Taketo-Yoda/uv-sbom/releases/latest/download/uv-sbom-aarch64-apple-darwin.tar.gz
tar xzf uv-sbom-aarch64-apple-darwin.tar.gz
sudo mv uv-sbom /usr/local/bin/
```

**macOS (Intel)**:
```bash
curl -LO https://github.com/Taketo-Yoda/uv-sbom/releases/latest/download/uv-sbom-x86_64-apple-darwin.tar.gz
tar xzf uv-sbom-x86_64-apple-darwin.tar.gz
sudo mv uv-sbom /usr/local/bin/
```

**Linux (x86_64)**:
```bash
curl -LO https://github.com/Taketo-Yoda/uv-sbom/releases/latest/download/uv-sbom-x86_64-unknown-linux-gnu.tar.gz
tar xzf uv-sbom-x86_64-unknown-linux-gnu.tar.gz
sudo mv uv-sbom /usr/local/bin/
```

**Windows**:
Download `uv-sbom-x86_64-pc-windows-msvc.zip` from the [releases page](https://github.com/Taketo-Yoda/uv-sbom/releases) and extract to your desired location.

### From Source

```bash
# Clone the repository
git clone https://github.com/Taketo-Yoda/uv-sbom.git
cd uv-sbom

# Build and install
cargo build --release
cargo install --path .
```

### Verify Installation

```bash
uv-sbom --version
```

## Usage

### Basic usage

Generate a CycloneDX JSON SBOM for the current directory:

```bash
uv-sbom
```

### Output formats

Generate a Markdown table with direct and transitive dependencies:

```bash
uv-sbom --format markdown
```

Generate a CycloneDX JSON (default):

```bash
uv-sbom --format json
```

### Output language

Use the `--lang` option to switch the output language for human-readable formats (Markdown). The default is English (`en`).

```bash
# Generate a Japanese Markdown SBOM report
uv-sbom --format markdown --lang ja

# Generate an English Markdown SBOM report (default)
uv-sbom --format markdown --lang en
```

**Supported values:** `en` (English, default), `ja` (Japanese)

**Note:** The `--lang` option affects section headers, table column names, and status labels in Markdown output. Package names, CVE IDs, and SPDX license identifiers always remain in their original form regardless of `--lang`.

### Specify project path

Analyze a project in a different directory:

```bash
uv-sbom --path /path/to/project
```

### Save to file

Output to a file instead of stdout:

```bash
uv-sbom --format json --output sbom.json
uv-sbom --format markdown --output SBOM.md
```

### Combined options

```bash
uv-sbom --path /path/to/project --format markdown --output SBOM.md
```

### Excluding packages

You can exclude specific packages from the SBOM using the `--exclude` or `-e` option:

```bash
# Exclude a single package
uv-sbom -e "pytest"

# Exclude multiple packages
uv-sbom -e "pytest" -e "mypy" -e "black"

# Use wildcards to exclude patterns
uv-sbom -e "debug-*"        # Exclude all packages starting with "debug-"
uv-sbom -e "*-dev"          # Exclude all packages ending with "-dev"
uv-sbom -e "*-test-*"       # Exclude all packages containing "-test-"

# Combine with other options
uv-sbom --format json --output sbom.json -e "pytest" -e "*-dev"
```

**Pattern Syntax:**
- Use `*` as a wildcard to match zero or more characters
- Patterns are case-sensitive
- Maximum 64 patterns per invocation

**Preventing Information Leakage:**
Use the `--exclude` option to skip specific internal or proprietary libraries. This prevents their names from being sent to external registries (like PyPI) during metadata retrieval, ensuring your internal project structure remains private.

### Configuration file

You can use a configuration file (`uv-sbom.config.yml`) to set default options instead of passing them on the command line every time.

#### Generating a config template

Use the `--init` option to generate a template configuration file with all available fields as commented examples:

```bash
# Generate template in the current directory
uv-sbom --init

# Generate template in a specific directory
uv-sbom --init --path ./my-project
```

This creates a `uv-sbom.config.yml` file with inline documentation for every option. If the file already exists, the command exits with an error to prevent accidental overwriting.

**Auto-discovery**: Place a `uv-sbom.config.yml` file in your project directory (where `uv.lock` is located). The tool automatically detects and loads it.

**Explicit path**: Use `--config` / `-c` to specify a config file at a custom location.

```bash
# Auto-discovered config file (place in project directory)
uv-sbom

# Explicit config file path
uv-sbom --config ./custom-config.yml
```

**Example configuration file** (`uv-sbom.config.yml`):

```yaml
# Output format: json or markdown
format: markdown

# Packages to exclude from SBOM (supports wildcards)
exclude_packages:
  - "pytest"
  - "mypy"
  - "*-dev"

# Enable CVE vulnerability checking
check_cve: true

# Severity threshold for vulnerability check (low/medium/high/critical)
severity_threshold: high

# CVSS threshold for vulnerability check (0.0-10.0)
# cvss_threshold: 7.0

# CVEs to ignore (with optional reason)
ignore_cves:
  - id: CVE-2024-1234
    reason: "False positive for our use case"
  - id: CVE-2024-5678
    reason: "Mitigated by network configuration"

# License compliance policy
license_policy:
  allow: ["MIT", "Apache-2.0", "BSD-*", "ISC", "PSF-2.0"]
  deny: ["GPL-3.0-only", "GPL-3.0-or-later", "AGPL-*"]
  unknown: "warn"  # "warn" | "deny" | "allow"
```

#### Config File Schema Reference

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `format` | string | No | Output format (`json` / `markdown`) |
| `exclude_packages` | string[] | No | Package exclusion patterns (supports wildcards) |
| `check_cve` | bool | No | Override CVE checking behavior. Defaults to true when unset |
| `severity_threshold` | string | No | Severity threshold (`low` / `medium` / `high` / `critical`) |
| `cvss_threshold` | number | No | CVSS threshold (0.0 - 10.0) |
| `ignore_cves` | object[] | No | List of CVEs to ignore |
| `ignore_cves[].id` | string | Yes | CVE ID (e.g., `CVE-2024-1234`) |
| `ignore_cves[].reason` | string | No | Reason for ignoring |
| `license_policy` | object | No | License compliance policy configuration |
| `license_policy.allow` | string[] | No | Allowed license patterns (supports wildcards) |
| `license_policy.deny` | string[] | No | Denied license patterns (supports wildcards) |
| `license_policy.unknown` | string | No | Unknown license handling (`warn` / `deny` / `allow`) |

#### Priority and Merge Rules

- **CLI arguments override config file values** for scalar fields (`format`, `severity_threshold`, `cvss_threshold`)
- **`check_cve`** defaults to true when unset. Set to false in config to disable. Use --no-check-cve CLI flag to opt out
- **`exclude_packages`** are **merged** from both CLI and config file, then deduplicated
- **`ignore_cves`** are **merged** from both CLI (`--ignore-cve`) and config file, deduplicated by ID (CLI entry takes precedence for duplicates)
- **`check_license`** is enabled if set via CLI flag OR config file (logical OR, same as `check_cve`)
- **`--license-allow`** and **`--license-deny`** CLI options **override** config file `license_policy.allow` / `license_policy.deny` entirely (not merged)

### Ignoring specific CVEs

You can ignore specific CVEs from the command line using `--ignore-cve` / `-i`:

```bash
# Ignore specific CVEs from CLI
uv-sbom --ignore-cve CVE-2024-1234 --ignore-cve CVE-2024-5678

# Short form
uv-sbom -i CVE-2024-1234 -i CVE-2024-5678

# Combine config file and CLI ignores (both sources are merged)
uv-sbom --config ./config.yml -i CVE-2024-9999
```

### Checking for vulnerabilities

CVE vulnerability checking is **enabled by default** using the [OSV (Open Source Vulnerability) database](https://osv.dev). No flag is required:

```bash
# Check for vulnerabilities in Markdown output (CVE check runs automatically)
uv-sbom --format markdown

# Save vulnerability report to file
uv-sbom --format markdown --output SBOM.md

# Combine with exclude patterns
uv-sbom --format markdown -e "pytest" -e "*-dev"

# Opt out of CVE checking
uv-sbom --format markdown --no-check-cve
```

### Disabling CVE Checking

CVE vulnerability checking is enabled by default. To opt out, use the `--no-check-cve` flag:

```bash
# Generate SBOM without CVE checking
uv-sbom --format markdown --no-check-cve

# Disable via config file
# check_cve: false  (in uv-sbom.config.yml)
```

> **Note:** `--no-check-cve` conflicts with `--severity-threshold`, `--cvss-threshold`, and `--suggest-fix`.

### License Compliance Check

Use the `--check-license` option to check packages against a configurable license policy:

```bash
# Enable license compliance check (using config file policy)
uv-sbom --check-license --format markdown

# With inline policy (overrides config file)
uv-sbom --check-license --license-allow "MIT,Apache-2.0,BSD-*" --license-deny "GPL-3.0,AGPL-*"

# Combined with vulnerability check
uv-sbom --check-license --severity-threshold high
```

**How it works:**
- **Deny takes precedence over allow**: If a license matches both lists, it is denied
- **Wildcard patterns**: Use `BSD-*`, `AGPL-*` etc. for pattern matching (case-insensitive)
- **Unknown license handling**: Configure how licenses not in either list are treated:
  - `warn` (default): Report as warning but don't fail
  - `deny`: Treat unknown licenses as violations
  - `allow`: Silently allow unknown licenses
- **Exit code**: Returns exit code 1 when policy violations are detected

### Vulnerability Threshold Options

You can control which vulnerabilities trigger a non-zero exit code using threshold options:

```bash
# Check for any vulnerabilities (exits with 1 if found)
uv-sbom --format markdown

# Check for High or Critical severity only
uv-sbom --format markdown --severity-threshold high

# Check for Critical severity only
uv-sbom --format markdown --severity-threshold critical

# Check for CVSS >= 7.0 only
uv-sbom --format markdown --cvss-threshold 7.0

# Check for CVSS >= 9.0 (Critical) only
uv-sbom --format markdown --cvss-threshold 9.0
```

**Threshold Options:**
- `--severity-threshold <LEVEL>`: Filter by severity level (low, medium, high, critical)
- `--cvss-threshold <SCORE>`: Filter by CVSS score (0.0-10.0)

**Notes:**
- Only one threshold option can be used at a time
- Cannot be used with `--no-check-cve`
- Vulnerabilities below the threshold are still shown in the report but don't trigger exit code 1
- When using `--cvss-threshold`, vulnerabilities without CVSS scores (N/A) are excluded from threshold evaluation

### PyPI Link Verification

Use the `--verify-links` option to validate that packages exist on PyPI before generating hyperlinks. Packages that don't exist on PyPI will be rendered as plain text:

```bash
# Generate Markdown with verified PyPI links
uv-sbom --format markdown --verify-links

# Combine with other options
uv-sbom --format markdown --verify-links --output SBOM.md
```

**Behavior:**
- Without `--verify-links`: All package names get PyPI hyperlinks (default, fast)
- With `--verify-links`: Only verified packages get hyperlinks; unverified packages render as plain text
- Network errors gracefully fall back to plain text (no crash)
- Requests are executed in parallel (max 10 concurrent) for performance

### CI Integration

Use vulnerability thresholds for CI/CD pipeline integration:

```yaml
# GitHub Actions example
- name: Generate SBOM
  run: uv-sbom --format markdown --output sbom.md

- name: Security Check (High and Critical only)
  run: uv-sbom --format markdown --severity-threshold high

- name: Security Check (CVSS >= 7.0)
  run: uv-sbom --format markdown --cvss-threshold 7.0
```

```yaml
# GitHub Actions - License Compliance Check
- name: License Compliance Check
  run: uv-sbom --check-license --format markdown

- name: Combined Security and License Check
  run: uv-sbom --check-license --severity-threshold high
```

```yaml
# GitLab CI example
security_scan:
  script:
    - uv-sbom --format markdown --severity-threshold high
  allow_failure: false
```

**Important Notes:**
- Vulnerability checking is **only available for Markdown format**
- Requires internet connection to query OSV API
- Not available in `--dry-run` mode (skips network operations)
- Use `--exclude` to prevent internal packages from being sent to OSV API

**Example Output:**

When vulnerabilities are found, a section like this is added to the Markdown output:

```markdown
## Vulnerability Report

**⚠️ Security Issues Detected**

The following packages have known security vulnerabilities:

| Package | Current Version | Fixed Version | CVSS | Severity | CVE ID |
|---------|----------------|---------------|------|----------|--------|
| urllib3 | 2.0.0 | 2.0.7 | 9.8 | 🔴 CRITICAL | CVE-2023-45803 |
| requests | 2.28.0 | 2.31.0 | 7.5 | 🟠 HIGH | CVE-2023-32681 |

---

*Vulnerability data provided by [OSV](https://osv.dev) under CC-BY 4.0*
```

> **Note:** Vulnerability IDs (CVE, GHSA, PYSEC, RUSTSEC, etc.) in the vulnerability report are always rendered as hyperlinks, regardless of `--verify-links`. These IDs are sourced from the OSV database and link to authoritative vulnerability databases (NVD, GitHub Advisories, OSV.dev), so link verification is unnecessary.

### Vulnerability Resolution Guide

When vulnerabilities are detected in transitive dependencies, uv-sbom automatically generates a **Vulnerability Resolution Guide**. This section shows which direct dependency introduces each vulnerable transitive package, so you know exactly what to upgrade.

#### Markdown output example

```markdown
## Vulnerability Resolution Guide

The following transitive dependencies have known vulnerabilities. The table shows which direct dependency introduces each vulnerable package.

| Vulnerable Package | Current | Fixed Version | Severity | Introduced By (Direct Dep) | Vulnerability ID |
|--------------------|---------|---------------|----------|---------------------------|-----------------|
| urllib3 | 1.26.15 | >= 2.0.7 | 🟠 HIGH | requests (2.31.0) | [CVE-2024-XXXXX]https://nvd.nist.gov/vuln/detail/CVE-2024-XXXXX |
| certifi | 2023.7.22 | >= 2024.2.2 | 🟠 HIGH | requests (2.31.0), httpx (0.25.0) | [CVE-2024-YYYYY]https://nvd.nist.gov/vuln/detail/CVE-2024-YYYYY |
```

#### CycloneDX JSON output

In CycloneDX format, the introducing dependency is included as a `properties` entry:

```json
{
  "vulnerabilities": [
    {
      "id": "CVE-2024-XXXXX",
      "properties": [
        {
          "name": "uv-sbom:introduced-by",
          "value": "requests@2.31.0"
        }
      ]
    }
  ]
}
```

> **Note:** The resolution guide only appears for **transitive** dependency vulnerabilities. Direct dependency vulnerabilities are shown in the standard vulnerability table, as users can upgrade them directly.

### Upgrade Advisor (`--suggest-fix`)

Use `--suggest-fix` to automatically suggest which direct dependency version to upgrade to resolve each transitive vulnerability.

**Requires**:
- `uv` CLI installed
- `pyproject.toml` in the project directory

**Example**:
```bash
uv-sbom generate --suggest-fix
```

**Output**: Adds a "Recommended Action" column to the Vulnerability Resolution Guide showing:
- `⬆️ Upgrade requests → 2.32.3 (resolves urllib3 to 2.2.1)` — when an upgrade fixes the vulnerability
- `⚠️ Cannot resolve: latest httpx still pins idna < 3.7` — when no upgrade helps
- `❓ Could not analyze: <error>` — when simulation failed

**Try it with the included example**:

```bash
# This example has transitive CVEs designed to show both Upgradable and Unresolvable outcomes
uv-sbom -p examples/suggest-fix-project --suggest-fix -f markdown
```

See [`examples/suggest-fix-project/README.md`](examples/suggest-fix-project/README.md) for a full walkthrough.

**License Compliance Check output example:**

```markdown
## License Compliance Check

Policy: 3 allowed patterns, 1 denied pattern | Unknown: warn

### Violations (2 found)

| Package | Version | License | Reason |
|---------|---------|---------|--------|
| some-gpl-lib | 1.0.0 | GPL-3.0 | Denied by policy |
| mystery-pkg | 2.1.0 | Unknown | Not in allow list |
```

When no vulnerabilities are found:

```markdown
## Vulnerability Report

**✅ No Known Vulnerabilities**

No security vulnerabilities were found in the scanned packages.

---

*Vulnerability data provided by [OSV](https://osv.dev) under CC-BY 4.0*
```

### uv Workspace Support

[uv workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces/) let you manage multiple Python
packages in a single repository with a shared `uv.lock` file. `uv-sbom --workspace` generates a separate
SBOM for each member package, reflecting only the dependencies reachable from that member.

**When to use:**
- Your repository contains multiple Python packages (e.g., `api/`, `worker/`)
- You need per-member SBOMs for independent security scanning or compliance reporting

**Usage:**

```bash
# Generate one SBOM per workspace member (CycloneDX JSON, default)
uv-sbom --workspace --path /path/to/workspace
```

**Expected output:**

```
Workspace mode: 2 members found

  Processing: api
  ...
  Processing: worker
  ...

📦 Workspace SBOM Summary
────────────────────────────────────────────────────────────
Member               Output File
────────────────────────────────────────────────────────────
api                  /path/to/workspace/packages/api/sbom.json
worker               /path/to/workspace/packages/worker/sbom.json
────────────────────────────────────────────────────────────
```

Each member gets its own `sbom.json` containing only the packages reachable from that member.
Transitive dependencies are included, but packages belonging to other members are excluded.

**With other options:**

```bash
# Markdown output for all members
uv-sbom --workspace --path examples/workspace --format markdown

# With license compliance check
uv-sbom --workspace --path examples/workspace --check-license
```

> **Note:** `--workspace` and `--output` are mutually exclusive. In workspace mode, each member's SBOM is
> automatically written to `sbom.json` (or `sbom.md` for Markdown) inside the member's own directory.

See [`examples/workspace/`](examples/workspace/) for a runnable demo with two member packages (`api` and `worker`).

### Validating configuration with dry-run

Use the `--dry-run` option to validate your configuration before the tool communicates with external registries:

```bash
# Verify exclude patterns work correctly
uv-sbom --dry-run -e "internal-*" -e "proprietary-pkg"

# Test configuration with all options
uv-sbom --dry-run --path /path/to/project --format json -e "*-dev"
```

**Why use --dry-run:**
- **Verify exclude patterns**: Ensure your `--exclude` patterns correctly match the packages you want to skip
- **Prevent information leakage**: Confirm that sensitive internal packages are excluded BEFORE the tool communicates with PyPI registry
- **Fast validation**: All input validation happens without network overhead
- **Early error detection**: Catch configuration issues (missing uv.lock, invalid patterns, etc.) immediately

**What happens in dry-run mode:**
- ✅ Reads and parses `uv.lock` file
- ✅ Validates all command-line arguments
- ✅ Checks exclude patterns and warns about unmatched patterns
- ✅ Outputs success message if no issues found
- ❌ Skips license fetching from PyPI (no network communication)
- ❌ Skips SBOM output generation

## Security

### Exclude Pattern Input Validation

The `-e`/`--exclude` option implements the following security measures to protect against malicious input:

#### Character Restrictions

Only the following characters are allowed in patterns:
- **Alphanumeric characters**: a-z, A-Z, 0-9, Unicode letters/numbers
- **Hyphens** (`-`), **underscores** (`_`), **dots** (`.`): Common in package names
- **Square brackets** (`[`, `]`): For package extras (e.g., `requests[security]`)
- **Asterisks** (`*`): For wildcard matching

Control characters, shell metacharacters, and path separators are blocked to prevent:
- Terminal escape sequence injection
- Log injection attacks
- Command injection (defense in depth)

#### Pattern Limits

- **Maximum patterns**: 64 patterns can be specified per invocation
- **Maximum length**: 255 characters per pattern
- **Minimum content**: Patterns must contain at least one non-wildcard character

These limits prevent denial-of-service attacks via:
- Excessive memory consumption
- CPU exhaustion from complex pattern matching

#### Examples

**Valid patterns**:
```bash
uv-sbom -e 'pytest'           # Exact match
uv-sbom -e 'test-*'           # Prefix wildcard
uv-sbom -e '*-dev'            # Suffix wildcard
uv-sbom -e 'package[extra]'   # Package with extras
```

**Invalid patterns** (rejected with error):
```bash
uv-sbom -e ''                 # Empty pattern
uv-sbom -e '***'              # Only wildcards
uv-sbom -e 'pkg;rm -rf /'     # Contains shell metacharacter
uv-sbom -e "$(cat /etc/passwd)" # Shell command substitution blocked
```

For more detailed security information, including threat model and attack vectors, see [SECURITY.md](SECURITY.md).

## Command-line options

```
Options:
  -f, --format <FORMAT>              Output format: json or markdown [default: json]
  -p, --path <PATH>                  Path to the project directory [default: current directory]
  -o, --output <OUTPUT>              Output file path (if not specified, outputs to stdout)
  -e, --exclude <PATTERN>            Exclude packages matching patterns (supports wildcards: *)
  -c, --config <PATH>               Path to config file (auto-discovers uv-sbom.config.yml if not specified)
  -i, --ignore-cve <CVE_ID>         CVE IDs to ignore (can be specified multiple times)
      --lang <LANG>                  Output language for human-readable formats: en or ja [default: en]
      --init                         Generate a uv-sbom.config.yml template file
      --dry-run                      Validate configuration without network communication or output generation
      --verify-links                 Verify PyPI links exist before generating hyperlinks (Markdown format only)
      --check-cve                    [DEPRECATED] CVE checking is now enabled by default. This flag has no effect. Use --no-check-cve to opt out
      --no-check-cve                 Disable CVE vulnerability checking (enabled by default)
      --severity-threshold <LEVEL>   Severity threshold for vulnerability check (low/medium/high/critical)
                                     Cannot be used with --no-check-cve
      --cvss-threshold <SCORE>       CVSS threshold for vulnerability check (0.0-10.0)
                                     Cannot be used with --no-check-cve
      --suggest-fix                  Suggest direct dependency upgrade versions to resolve transitive vulnerabilities
                                     Requires uv CLI installed and pyproject.toml in project directory
      --workspace                    Generate one SBOM per workspace member
                                     Cannot be used with --output
      --check-license                Check license compliance against policy
      --license-allow <LIST>         Comma-separated list of allowed license patterns (overrides config)
      --license-deny <LIST>          Comma-separated list of denied license patterns (overrides config)
  -h, --help                         Print help
  -V, --version                      Print version
```

## Exit Codes

uv-sbom returns the following exit codes:

| Exit Code | Description | Examples |
|-----------|-------------|----------|
| 0 | Success | SBOM generated successfully, no vulnerabilities above threshold, `--help` or `--version` displayed |
| 1 | Vulnerabilities or license violations detected | Vulnerabilities above threshold detected, license policy violations found |
| 2 | Invalid command-line arguments | Unknown option, invalid argument type |
| 3 | Application error | Missing uv.lock file, invalid project path, invalid exclude pattern, network error, file write error |

### Exit Codes with Vulnerability and License Checking

When using CVE or license checking, the exit code behavior changes based on threshold settings:

| Scenario | Exit Code |
|----------|-----------|
| No vulnerabilities found | 0 |
| Vulnerabilities found (no threshold specified) | 1 |
| Vulnerabilities found, all below threshold | 0 |
| Vulnerabilities found, some above threshold | 1 |
| License policy violations detected | 1 |
| Combined: either check fails | 1 |
| Combined: both checks pass | 0 |

**Examples:**
```bash
# Returns 0 if no High/Critical vulnerabilities, even if Low/Medium exist
uv-sbom --format markdown --severity-threshold high

# Returns 0 if no vulnerabilities have CVSS >= 7.0
uv-sbom --format markdown --cvss-threshold 7.0
```

### Common Error Scenarios

**Exit code 3 - Application errors:**
```bash
# Missing uv.lock file
$ uv-sbom --path /path/without/uv-lock
❌ An error occurred:
uv.lock file not found: /path/without/uv-lock/uv.lock
# Exit code: 3

# Invalid exclude pattern (empty)
$ uv-sbom -e ""
❌ An error occurred:
Exclusion pattern cannot be empty
# Exit code: 3

# Invalid exclude pattern (invalid characters)
$ uv-sbom -e "pkg;name"
❌ An error occurred:
Exclusion pattern contains invalid character ';' in pattern 'pkg;name'
# Exit code: 3

# Nonexistent project path
$ uv-sbom --path /nonexistent
❌ An error occurred:
Invalid project path: /nonexistent
# Exit code: 3
```

**Exit code 2 - CLI argument errors:**
```bash
# Unknown option
$ uv-sbom --unknown-option
error: unexpected argument '--unknown-option' found
# Exit code: 2

# Invalid format value
$ uv-sbom --format invalid
error: invalid value 'invalid' for '--format <FORMAT>'
# Exit code: 2
```

### Usage in Scripts

```bash
#!/bin/bash

uv-sbom --format json --output sbom.json

case $? in
  0)
    echo "SBOM generated successfully"
    ;;
  1)
    echo "Vulnerabilities detected above threshold"
    exit 1
    ;;
  2)
    echo "Invalid command-line arguments"
    exit 2
    ;;
  3)
    echo "Application error occurred"
    exit 3
    ;;
esac
```

## Output Examples

### Markdown format

> **Note**: The Markdown format sample is based on the SBOM format from [ja-complete v0.1.0]https://github.com/Taketo-Yoda/ja-complete/tree/v0.1.0.

```markdown
## Summary

| Item | Count | Status |
|------|-------|--------|
| Direct dependencies | 2 ||
| Transitive dependencies | 3 ||
| ...vulnerability rows... | | |
| License violations | 0 ||

**Overall: No issues found** ✅

# Software Bill of Materials (SBOM)

## Component Inventory

A comprehensive list of all software components and libraries included in this project.

| Package | Version | License | Description |
|---------|---------|---------|-------------|
| janome | 0.5.0 | AL2 | Japanese morphological analysis engine. |
| pydantic | 2.12.5 | MIT | Data validation using Python type hints |
| ...additional packages... |

## Direct Dependencies

Primary packages explicitly defined in the project configuration(e.g., pyproject.toml).

| Package | Version | License | Description |
|---------|---------|---------|-------------|
| janome | 0.5.0 | AL2 | Japanese morphological analysis engine. |
| pydantic | 2.12.5 | MIT | Data validation using Python type hints |

## Transitive Dependencies

Secondary dependencies introduced by the primary packages.

### Dependencies for pydantic

| Package | Version | License | Description |
|---------|---------|---------|-------------|
| annotated-types | 0.7.0 | MIT License | Reusable constraint types to use with typing.Annotated |
| pydantic-core | 2.41.5 | MIT | Core functionality for Pydantic validation and serialization |
```

### CycloneDX JSON format

```json
{
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "version": 1,
  "serialNumber": "urn:uuid:...",
  "metadata": {
    "timestamp": "2024-01-01T00:00:00Z",
    "tools": [
      {
        "name": "uv-sbom",
        "version": "0.1.0"
      }
    ]
  },
  "components": [
    {
      "type": "library",
      "name": "requests",
      "version": "2.31.0",
      "description": "HTTP library for Python",
      "licenses": [
        {
          "license": {
            "name": "Apache 2.0"
          }
        }
      ],
      "purl": "pkg:pypi/requests@2.31.0"
    }
  ]
}
```

## Requirements

- A Python project managed by `uv` with a `uv.lock` file
- Internet connection for fetching license information from PyPI

## Network Requirements

### External Domains Accessed

`uv-sbom` makes HTTP requests to the following external services during SBOM generation:

#### Required for all operations:

1. **PyPI (Python Package Index)**
   - Domain: `https://pypi.org`
   - Purpose: Fetch license information for Python packages
   - When: Every SBOM generation (unless using `--dry-run`)
   - Rate limit: No official limit, but tool implements retry logic
   - Endpoint: `/pypi/{package_name}/json`

#### Optional (only when using `--no-check-cve` disables CVE, or `--verify-links`):

2. **PyPI Link Verification**
   - Domain: `https://pypi.org`
   - Purpose: Verify package existence on PyPI via HTTP HEAD requests
   - When: Only when `--verify-links` flag is used
   - Rate limit: Max 10 concurrent requests
   - Endpoint: `/project/{package_name}/`

3. **OSV (Open Source Vulnerability Database)**
   - Domain: `https://api.osv.dev`
   - Purpose: Fetch vulnerability information for security scanning
   - When: By default (unless `--no-check-cve` is used)
   - Rate limit: Tool implements 10 requests/second limit
   - Endpoints:
     - `/v1/querybatch` - Batch query for vulnerability IDs
     - `/v1/vulns/{vuln_id}` - Detailed vulnerability information

### Firewall Configuration

If you are behind a corporate firewall or proxy, ensure the following domains are on the allowlist:

```
# Required
pypi.org

# Optional (for --verify-links only; OSV is accessed by default unless --no-check-cve)
pypi.org       # Also used for --verify-links
api.osv.dev    # Disabled only with --no-check-cve
```

### Proxy Configuration

The tool respects standard HTTP/HTTPS proxy environment variables:

```bash
export HTTP_PROXY=http://proxy.company.com:8080
export HTTPS_PROXY=http://proxy.company.com:8080
export NO_PROXY=localhost,127.0.0.1

uv-sbom --format json
```

### Offline Mode

To validate configuration without making network requests, use `--dry-run`:

```bash
uv-sbom --dry-run
```

This mode:
- Validates `uv.lock` file
- Validates command-line arguments
- Checks exclude patterns
- Skips license fetching (no PyPI access)
- Skips vulnerability checking (no OSV access)
- Skips SBOM output generation

## Error Handling

uv-sbom provides detailed error messages with helpful suggestions:

- **Missing uv.lock file**: Clear message with suggestions on how to fix
- **Invalid project path**: Validates directory existence before processing
- **License fetch failures**: Retries failed requests (up to 3 attempts) and continues processing
- **File write errors**: Checks directory existence and permissions
- **Progress tracking**: Shows real-time progress during license information retrieval

Example error message:
```
❌ An error occurred:

uv.lock file not found: /path/to/project/uv.lock

💡 Hint: uv.lock file does not exist in project directory "/path/to/project".
   Please run in the root directory of a uv project, or specify the correct path with the --path option.
```

## Troubleshooting

### uv.lock file not found
Ensure you're running the command in a directory containing a `uv.lock` file, or use the `--path` option to specify the correct project directory.

### License information fetch failures
Some packages may fail to retrieve license information from PyPI. The tool will:
1. Automatically retry up to 3 times
2. Continue processing other packages
3. Display warnings for failed packages
4. Include packages in the output without license information if fetching fails

### Network issues
If you're behind a proxy or firewall, ensure that you can access `https://pypi.org`. The tool uses a 10-second timeout for API requests.

## For Developers

### Architecture

uv-sbom is built with **Hexagonal Architecture** (Ports and Adapters) + **Domain-Driven Design (DDD)** for maintainability and testability.

See [ARCHITECTURE.md](ARCHITECTURE.md) for a full breakdown of layers, ports, adapters, and architectural decision records (ADRs).

### Test Coverage

The test suite covers Unit, Integration, and End-to-End scenarios.

See [DEVELOPMENT.md](DEVELOPMENT.md) for how to run tests and contribute.

### Development Setup

After cloning the repository, activate the git hooks:

```bash
make setup
```

This enables `pre-commit` (auto-format) and `pre-push` (fmt check, clippy, tests) hooks from `.githooks/`, ensuring code quality checks run automatically for all contributors.

### Reference

- [DEVELOPMENT.md]DEVELOPMENT.md — Development guide
- [ARCHITECTURE.md]ARCHITECTURE.md — Hexagonal Architecture + DDD implementation details
- [CHANGELOG.md]CHANGELOG.md — Change history
- [.claude/project-context.md].claude/project-context.md — Complete project context for Claude Code
- [.claude/instructions.md].claude/instructions.md — Coding guidelines and instructions for Claude Code

## Attribution

### Vulnerability Data

By default, this tool retrieves vulnerability data from [OSV (Open Source Vulnerability)](https://osv.dev), which is provided under the [Creative Commons Attribution 4.0 International License (CC-BY 4.0)](https://creativecommons.org/licenses/by/4.0/). Use `--no-check-cve` to opt out of vulnerability checking.

**Required Attribution:**
- Vulnerability data provided by OSV
- Available at: https://osv.dev
- License: CC-BY 4.0

The OSV database is a collaborative effort to provide comprehensive, accurate, and accessible vulnerability information for open source software.

## License

MIT License - see [LICENSE](LICENSE) file for details.

[shield_license]: https://img.shields.io/badge/license-MIT-blue.svg
[license_file]: LICENSE