Skip to content

Commit 24cfe3e

Browse files
authored
Support for vdb custom data (#472)
* Support for vdb custom data Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com> * Bug fix Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com> --------- Signed-off-by: Prabhu Subramanian <prabhu@appthreat.com>
1 parent 42c1f6a commit 24cfe3e

File tree

12 files changed

+184
-31
lines changed

12 files changed

+184
-31
lines changed

.github/workflows/pythonapp.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,40 @@ jobs:
5555
uv run depscan --purl "pkg:npm/@biomejs/biome@1.8.1"
5656
env:
5757
PYTHONIOENCODING: utf-8
58+
- name: Custom data tests
59+
run: |
60+
mkdir -p custom_vulns
61+
cat > custom_vulns/private.yaml <<EOF
62+
dataType: CVE_RECORD
63+
dataVersion: "5.2"
64+
cveMetadata:
65+
cveId: PRIVATE-DEPSCAN-TEST
66+
assignerOrgId: 00000000-0000-4000-8000-000000000000
67+
state: PUBLISHED
68+
datePublished: "2024-01-01T00:00:00Z"
69+
dateUpdated: "2024-01-01T00:00:00Z"
70+
containers:
71+
cna:
72+
providerMetadata:
73+
orgId: 00000000-0000-4000-8000-000000000000
74+
descriptions:
75+
- lang: en
76+
value: "Depscan Test Vulnerability"
77+
affected:
78+
- vendor: internal
79+
product: custom-test
80+
packageName: custom-test
81+
packageURL: pkg:pypi/custom-test
82+
versions:
83+
- version: "1.0.0"
84+
status: affected
85+
versionType: semver
86+
EOF
87+
88+
uv run depscan --purl "pkg:pypi/custom-test@1.0.0" --custom-data custom_vulns
89+
if: ${{ matrix.os == 'ubuntu-latest' }}
90+
env:
91+
PYTHONIOENCODING: utf-8
5892

5993
devenv:
6094
env:

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ options:
163163
--bom BOM Examine using the given Software Bill-of-Materials (SBOM) file in CycloneDX format. Use cdxgen command to produce one.
164164
--bom-dir BOM_DIR Examine all the Bill-of-Materials (BOM) files in the given directory.
165165
--purl SEARCH_PURL Scan a single package url.
166+
--custom-data CUSTOM_DATA
167+
Path to directory containing custom vulnerability data (JSON/YAML/TOML) to override/augment results.
166168
--report-template REPORT_TEMPLATE
167169
Jinja template file used for rendering a custom report
168170
--report-name REPORT_NAME

depscan/cli.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from custom_json_diff.lib.utils import json_load
2626
from rich.panel import Panel
2727
from rich.terminal_theme import DEFAULT_TERMINAL_THEME, MONOKAI
28-
from vdb.lib import config
28+
from vdb.lib import config, search
2929
from vdb.lib import db6 as db_lib
3030
from vdb.lib.utils import parse_purl
3131

@@ -288,6 +288,7 @@ def run_depscan(args):
288288
debug=args.enable_debug or os.environ.get("SCAN_DEBUG_MODE") == "debug",
289289
create_bom=create_bom,
290290
max_content_length=os.getenv("DEPSCAN_SERVER_MAX_CONTENT_LENGTH"),
291+
custom_data_directory=args.custom_data,
291292
)
292293
return simple.run_server(server_options)
293294
else:
@@ -382,6 +383,9 @@ def run_depscan(args):
382383
)
383384
sys.exit(0)
384385
pkg_list, project_types_list = set_project_types(args, src_dir)
386+
if args.custom_data:
387+
LOG.info(f"Loading custom vulnerability data from {args.custom_data}")
388+
search.load_custom_data(args.custom_data)
385389
if args.search_purl:
386390
# Automatically enable risk audit for single purl searches
387391
perform_risk_audit = True

depscan/cli_options.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,11 @@ def build_parser():
171171
dest="search_purl",
172172
help="Scan a single package url.",
173173
)
174+
parser.add_argument(
175+
"--custom-data",
176+
dest="custom_data",
177+
help="Path to directory containing custom vulnerability data (JSON/YAML/TOML) to override/augment results.",
178+
)
174179
parser.add_argument(
175180
"--report-template",
176181
dest="report_template",

devenv.lock

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
"devenv": {
44
"locked": {
55
"dir": "src/modules",
6-
"lastModified": 1763386227,
6+
"lastModified": 1766921574,
77
"owner": "cachix",
88
"repo": "devenv",
9-
"rev": "9f855598530d6ee075cc126e4f13812fd008209a",
9+
"rev": "02c9dcf3e050400d8101057f9f00ec458af7c959",
1010
"type": "github"
1111
},
1212
"original": {
@@ -19,10 +19,10 @@
1919
"flake-compat": {
2020
"flake": false,
2121
"locked": {
22-
"lastModified": 1761588595,
22+
"lastModified": 1766929376,
2323
"owner": "edolstra",
2424
"repo": "flake-compat",
25-
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
25+
"rev": "236f248441a986331cda53b039e7f9fd96e03635",
2626
"type": "github"
2727
},
2828
"original": {
@@ -34,10 +34,10 @@
3434
"flake-compat_2": {
3535
"flake": false,
3636
"locked": {
37-
"lastModified": 1761588595,
37+
"lastModified": 1766929376,
3838
"owner": "edolstra",
3939
"repo": "flake-compat",
40-
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
40+
"rev": "236f248441a986331cda53b039e7f9fd96e03635",
4141
"type": "github"
4242
},
4343
"original": {
@@ -49,10 +49,10 @@
4949
"flake-compat_3": {
5050
"flake": false,
5151
"locked": {
52-
"lastModified": 1761588595,
52+
"lastModified": 1766929376,
5353
"owner": "edolstra",
5454
"repo": "flake-compat",
55-
"rev": "f387cd2afec9419c8ee37694406ca490c3f34ee5",
55+
"rev": "236f248441a986331cda53b039e7f9fd96e03635",
5656
"type": "github"
5757
},
5858
"original": {
@@ -87,10 +87,10 @@
8787
]
8888
},
8989
"locked": {
90-
"lastModified": 1763319842,
90+
"lastModified": 1765911976,
9191
"owner": "cachix",
9292
"repo": "git-hooks.nix",
93-
"rev": "7275fa67fbbb75891c16d9dee7d88e58aea2d761",
93+
"rev": "b68b780b69702a090c8bb1b973bab13756cc7a27",
9494
"type": "github"
9595
},
9696
"original": {
@@ -121,10 +121,10 @@
121121
},
122122
"nixpkgs": {
123123
"locked": {
124-
"lastModified": 1761313199,
124+
"lastModified": 1764580874,
125125
"owner": "cachix",
126126
"repo": "devenv-nixpkgs",
127-
"rev": "d1c30452ebecfc55185ae6d1c983c09da0c274ff",
127+
"rev": "dcf61356c3ab25f1362b4a4428a6d871e84f1d1d",
128128
"type": "github"
129129
},
130130
"original": {
@@ -142,10 +142,10 @@
142142
]
143143
},
144144
"locked": {
145-
"lastModified": 1761999567,
145+
"lastModified": 1765052656,
146146
"owner": "cachix",
147147
"repo": "nixpkgs-python",
148-
"rev": "100c43175443402084f557154b06f2745995e742",
148+
"rev": "04b27dbad2e004cb237db202f21154eea3c4f89f",
149149
"type": "github"
150150
},
151151
"original": {
@@ -163,10 +163,10 @@
163163
]
164164
},
165165
"locked": {
166-
"lastModified": 1759902829,
166+
"lastModified": 1766728475,
167167
"owner": "bobvanderlinden",
168168
"repo": "nixpkgs-ruby",
169-
"rev": "5fba6c022a63f1e76dee4da71edddad8959f088a",
169+
"rev": "f167828eab19c3a7e3faa066a140d92e307f7b16",
170170
"type": "github"
171171
},
172172
"original": {

devenv.nix

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ in
3737
};
3838
java = lib.mkIf (lib.elem config.profile [ "android" "flutter" "reactNative" ] == false) {
3939
enable = true;
40-
jdk.package = pkgs.jdk23_headless;
40+
jdk.package = pkgs.jdk25_headless;
4141
};
4242
ruby = {
4343
enable = lib.mkIf (config.profile == "ruby") true;
44-
version = "3.4.4";
44+
version = "4.0.1";
4545
};
4646
dotnet = {
4747
enable = lib.mkIf (config.profile == "dotnet") true;

documentation/docs/adv-usage.mdx

Lines changed: 98 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ sidebar_position: 8
88

99
To download security advisories from GitHub, a personal access token with minimal permissions is necessary.
1010

11-
- Fine-grained token: Grant no permissions and select the following for repository access: `Public Repositories (read-only)`
12-
- Token (classic): Grant no permissions
11+
- Fine-grained token: Grant no permissions and select the following for repository access: `Public Repositories (read-only)`
12+
- Token (classic): Grant no permissions
1313

1414
```bash
1515
export GITHUB_TOKEN="<PAT token>"
@@ -19,9 +19,8 @@ export GITHUB_TOKEN="<PAT token>"
1919

2020
Depscan comes with a suggest mode enabled by default to simplify the triaging experience. The fix version for each vulnerability is retrieved from the sources. Sometimes, there might be known vulnerabilities in the fix version reported. Eg: in the below screenshot the fix versions suggested for jackson-databind might contain known vulnerabilities.
2121

22-
[//]: # (![Normal mode]&#40;docs/depscan-normal.png&#41;)
23-
24-
[//]: # (![Suggest mode]&#40;docs/depscan-suggest.png&#41;)
22+
[//]: # "![Normal mode](docs/depscan-normal.png)"
23+
[//]: # "![Suggest mode](docs/depscan-suggest.png)"
2524

2625
Notice, how the new suggested version is `2.9.10.5` which is an optimal fix version. Please note that the optimal fix version may not be the appropriate version for your application based on compatibility.
2726

@@ -91,7 +90,7 @@ export FETCH_LICENSE=true
9190

9291
The license data is sourced from choosealicense.com and is quite limited. If the license of a given package cannot be reliably matched against this list it will get silently ignored to reduce any noise. This behavior could change in the future once the detection logic gets improved.
9392

94-
[//]: # (![License scan]&#40;docs/license-scan.png&#41;)
93+
[//]: # "![License scan](docs/license-scan.png)"
9594

9695
## Kubernetes and Cloud apps
9796

@@ -129,15 +128,103 @@ Severity counts:
129128

130129
The objects available are taken from the CycloneDX \*.vdr.json BOM file generated, just have a look at the file for its full structure:
131130

132-
- `metadata`
133-
- `vulnerabilities`
134-
- `components`
135-
- `dependencies`
136-
- `services`
131+
- `metadata`
132+
- `vulnerabilities`
133+
- `components`
134+
- `dependencies`
135+
- `services`
137136

138137
`summary` is a dictionary type with vulnerability severity quantities as shown in the example above.
139138
`pkg_vulnerabilities` - Same as `vulnerabilities` from the VDR
140139
`pkg_group_rows` - List of vulnerability id and the dependency tree prioritized by depscan.
141140

142141
Furthermore, insights are imaginable to be made available to the template, please reach out or contribute on demand.
143142
We appreciate it if you like to contribute your report templates as examples, please add/find them [here](https://github.com/owasp-dep-scan/dep-scan/blob/master/contrib/report-templates).
143+
144+
## Custom Vulnerability Data
145+
146+
VDB 6 supports loading custom vulnerability data from a local directory at runtime. This allows you to:
147+
148+
1. **Add Private Vulnerabilities:** Include internal CVEs that are not public.
149+
2. **Override False Positives:** Correct data returned by the official database by marking specific versions as `unaffected`.
150+
151+
Custom data must follow the **CVE 5.2 JSON Schema**. Supported file extensions are `.json`, `.yaml`, `.yml`, and `.toml`.
152+
153+
To use custom data, pass the directory path to the `--custom-data` argument.
154+
155+
```shell
156+
depscan --purl pkg:npm/my-lib@1.0.0 --custom-data /path/to/custom/vulns
157+
```
158+
159+
Custom data is also supported in server mode.
160+
161+
```shell
162+
depscan --server --custom-data /path/to/custom/vulns
163+
```
164+
165+
### Example 1: Adding a Private Vulnerability
166+
167+
Create a file `private-vuln.yaml`. Since you are defining a new vulnerability record, you use the `cna` container.
168+
169+
```yaml
170+
dataType: CVE_RECORD
171+
dataVersion: "5.2"
172+
cveMetadata:
173+
cveId: PRIVATE-2025-001
174+
assignerOrgId: 00000000-0000-4000-8000-000000000000
175+
state: PUBLISHED
176+
datePublished: "2025-01-01T00:00:00Z"
177+
dateUpdated: "2025-01-01T00:00:00Z"
178+
containers:
179+
cna:
180+
providerMetadata:
181+
orgId: 00000000-0000-4000-8000-000000000000
182+
descriptions:
183+
- lang: en
184+
value: "Private vulnerability in internal library"
185+
affected:
186+
- vendor: internal
187+
product: my-lib
188+
packageName: my-lib
189+
packageURL: pkg:npm/my-lib
190+
versions:
191+
- version: "1.0.0"
192+
status: affected
193+
versionType: semver
194+
lessThan: "2.0.0"
195+
```
196+
197+
### Example 2: Overriding a False Positive
198+
199+
If the official database reports `CVE-2023-9999` for `pkg:pypi/requests` but you have determined it is a false positive for your specific version, you can override it using an **ADP (Authorized Data Publisher)** container. This is the recommended way to append or dispute existing vulnerability data.
200+
201+
**Logic:** If a CVE ID and Package URL combination exists in your custom data, VDB will **ignore** the entry from the official database and use yours instead.
202+
203+
Create `override.yaml`:
204+
205+
```yaml
206+
dataType: CVE_RECORD
207+
dataVersion: "5.2"
208+
cveMetadata:
209+
cveId: CVE-2023-9999
210+
assignerOrgId: 00000000-0000-4000-8000-000000000000
211+
state: PUBLISHED
212+
containers:
213+
# Use 'adp' to append/modify existing vulnerability data
214+
adp:
215+
- providerMetadata:
216+
orgId: 00000000-0000-4000-8000-000000000000
217+
shortName: "MySecTeam"
218+
descriptions:
219+
- lang: en
220+
value: "Override to mark specific version as unaffected"
221+
affected:
222+
- product: requests
223+
packageName: requests
224+
packageURL: pkg:pypi/requests
225+
versions:
226+
# Explicitly mark your version as unaffected
227+
- version: "2.31.0"
228+
status: unaffected
229+
versionType: semver
230+
```

documentation/docs/cli-usage.mdx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ options:
7474
--bom BOM Examine using the given Software Bill-of-Materials (SBOM) file in CycloneDX format. Use cdxgen command to produce one.
7575
--bom-dir BOM_DIR Examine all the Bill-of-Materials (BOM) files in the given directory.
7676
--purl SEARCH_PURL Scan a single package url.
77+
--custom-data CUSTOM_DATA
78+
Path to directory containing custom vulnerability data (JSON/YAML/TOML) to override/augment results.
7779
--report-template REPORT_TEMPLATE
7880
Jinja template file used for rendering a custom report
7981
--report-name REPORT_NAME

packages/analysis-lib/src/analysis_lib/output.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,8 @@ def summary_stats(results):
790790
ratings = i.get("ratings")
791791
if ratings:
792792
sev = ratings[0].get("severity", "").upper()
793+
if not sev:
794+
sev = "UNSPECIFIED"
793795
summary[sev] += 1
794796

795797
return summary
@@ -808,6 +810,7 @@ def pkg_risks_table(
808810
:param project_type: Project type
809811
:param scoped_pkgs: A dict of lists of required/optional/excluded packages.
810812
:param risk_results: A dict of the risk metrics and scope for each package.
813+
:param pkg_max_risk_score: Max risk score
811814
:param risk_report_file: Path to the JSON file for the risk audit findings.
812815
"""
813816
if not risk_results:

packages/analysis-lib/src/analysis_lib/utils.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,15 @@ def cve_to_vdr(cve: CVE, vid: str):
680680
except AttributeError:
681681
description, detail = "", ""
682682
if not source:
683-
source = {"name": cve.root.cveMetadata.assignerShortName.root.capitalize()}
683+
assigner_short_name = cve.root.cveMetadata.assignerShortName
684+
assigner_name = ""
685+
if assigner_short_name:
686+
if hasattr(assigner_short_name, "root"):
687+
assigner_name = str(assigner_short_name.root)
688+
else:
689+
assigner_name = str(assigner_short_name)
690+
691+
source = {"name": assigner_name.capitalize()}
684692
if source.get("name") == "Github_m":
685693
source = {
686694
"name": "GitHub Advisory Database",

0 commit comments

Comments
 (0)