diff --git a/.github/workflows/false-positive-ops.yml b/.github/workflows/false-positive-ops.yml
index ceb70fdfa3e..c3ca6838b65 100644
--- a/.github/workflows/false-positive-ops.yml
+++ b/.github/workflows/false-positive-ops.yml
@@ -171,12 +171,9 @@ jobs:
}
purl += '@.*$';
var cpe = process.env.CPE.trim().replaceAll(/^`|`$/g,'').split(':');
- var cpe22UriPrefix;
- if (cpe[1] == '2.3') {
- cpe22UriPrefix = 'cpe:/a:' + cpe[3] + ':' + cpe[4] + ':';
- } else {
- cpe22UriPrefix = 'cpe:/a:' + cpe[2] + ':' + cpe[3] + ':';
- }
+ var vendor = cpe[1] === '2.3' ? cpe[3] : cpe[2];
+ var product = cpe[1] === '2.3' ? cpe[4] : cpe[3];
+ var cpe22UriRegex = 'cpe:/a:' + vendor.replaceAll('.','\\.') + ':' + product.replaceAll('.','\\.') + '(:.*)?$';
await github.rest.issues.createComment({
issue_number: context.issue.number,
@@ -189,7 +186,7 @@ jobs:
' FP per issue #' + context.issue.number + '\n' +
' ]]>\n' +
' ' + purl + '\n' +
- ' ' + cpe22UriPrefix + '\n' +
+ ' ' + cpe22UriRegex + '\n' +
'\n```\n\n' +
'Link to test results: ' + context.serverUrl + '/' + context.repo.owner + '/' + context.repo.repo + '/actions/runs/' + context.runId
})
@@ -217,13 +214,9 @@ jobs:
}
purl += '@.*$';
var cpe = process.env.CPE.trim().replaceAll(/^`|`$/g,'').split(':');
- console.log(cpe);
- var cpe22UriPrefix;
- if (cpe[1] == '2.3') {
- cpe22UriPrefix = 'cpe:/a:' + cpe[3] + ':' + cpe[4] + ':';
- } else {
- cpe22UriPrefix = 'cpe:/a:' + cpe[2] + ':' + cpe[3] + ':';
- }
+ var vendor = cpe[1] === '2.3' ? cpe[3] : cpe[2];
+ var product = cpe[1] === '2.3' ? cpe[4] : cpe[3];
+ var cpe22UriRegex = 'cpe:/a:' + vendor.replaceAll('.','\\.') + ':' + product.replaceAll('.','\\.') + '(:.*)?$';
await github.rest.issues.createComment({
issue_number: context.issue.number,
@@ -236,7 +229,7 @@ jobs:
' FP per issue #' + context.issue.number + '\n' +
' ]]>\n' +
' ' + purl + '\n' +
- ' ' + cpe22UriPrefix + '\n' +
+ ' ' + cpe22UriRegex + '\n' +
'\n```\n\n' +
'Link to test results: ' + context.serverUrl + '/' + context.repo.owner + '/' + context.repo.repo + '/actions/runs/' + context.runId
})
@@ -264,12 +257,9 @@ jobs:
}
purl += '@.*$';
var cpe = process.env.CPE.trim().replaceAll(/^`|`$/g,'').split(':');
- var cpe22UriPrefix;
- if (cpe[1] == '2.3') {
- cpe22UriPrefix = 'cpe:/a:' + cpe[3] + ':' + cpe[4] + ':';
- } else {
- cpe22UriPrefix = 'cpe:/a:' + cpe[2] + ':' + cpe[3] + ':';
- }
+ var vendor = cpe[1] === '2.3' ? cpe[3] : cpe[2];
+ var product = cpe[1] === '2.3' ? cpe[4] : cpe[3];
+ var cpe22UriRegex = 'cpe:/a:' + vendor.replaceAll('.','\\.') + ':' + product.replaceAll('.','\\.') + '(:.*)?$';
await github.rest.issues.createComment({
issue_number: context.issue.number,
@@ -282,7 +272,7 @@ jobs:
' FP per issue #' + context.issue.number + '\n' +
' ]]>\n' +
' ' + purl + '\n' +
- ' ' + cpe22UriPrefix + '\n' +
+ ' ' + cpe22UriRegex + '\n' +
'\n```\n\n' +
'Link to test results: ' + context.serverUrl + '/' + context.repo.owner + '/' + context.repo.repo + '/actions/runs/' + context.runId
})
diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml
index 637dcd9957b..3d2ba14a1fd 100644
--- a/.github/workflows/lint-pr.yml
+++ b/.github/workflows/lint-pr.yml
@@ -2,6 +2,10 @@ name: "Lint PR"
on:
pull_request_target:
+ # BE CAREFUL - this event runs in the context of the default branch (`main`) workflow definition in the target
+ # repository (NOT the fork's context), so it has potentially sensitive access.
+ # It is critical that this only runs on very limited events and/or access to the repo
+ # Read https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_target
types:
- opened
- edited
diff --git a/.github/workflows/publish-suppressions.yml b/.github/workflows/publish-suppressions.yml
index 2d5d7d2963a..bc0acf4ddf5 100644
--- a/.github/workflows/publish-suppressions.yml
+++ b/.github/workflows/publish-suppressions.yml
@@ -2,13 +2,21 @@ name: Publish Suppressions
on:
workflow_dispatch:
- push:
- branches:
- - generatedSuppressions
+ pull_request_target:
+ # BE CAREFUL - this event runs in the context of the default branch (`main`) workflow definition in the target
+ # repository (NOT the fork's context), so it has potentially sensitive access.
+ # It is critical that this only runs on very limited events and/or access to the repo
+ # Read https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_target
+ types: [ closed ]
+ branches: [ generatedSuppressions ]
+ paths: [ generatedSuppressions.xml ]
+
permissions: {}
jobs:
update_suppression:
+ if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true
+
permissions:
contents: write # to push changes in repo (jamesives/github-pages-deploy-action)
diff --git a/core/src/main/java/org/owasp/dependencycheck/analyzer/OpenSSLAnalyzer.java b/core/src/main/java/org/owasp/dependencycheck/analyzer/OpenSSLAnalyzer.java
index 33f7ce947e0..47123e657ae 100644
--- a/core/src/main/java/org/owasp/dependencycheck/analyzer/OpenSSLAnalyzer.java
+++ b/core/src/main/java/org/owasp/dependencycheck/analyzer/OpenSSLAnalyzer.java
@@ -68,11 +68,20 @@ public class OpenSSLAnalyzer extends AbstractFileTypeAnalyzer {
*/
private static final FileFilter OPENSSLV_FILTER = FileFilterBuilder.newInstance().addFilenames(OPENSSLV_H).build();
/**
- * Open SSL Version number pattern.
+ * Open SSL Version number pattern (OpenSSL 1.x and earlier — literal hex constant).
*/
private static final Pattern VERSION_PATTERN = Pattern.compile(
"define\\s+OPENSSL_VERSION_NUMBER\\s+0x([0-9a-zA-Z]{8})L", Pattern.DOTALL
| Pattern.CASE_INSENSITIVE);
+ /**
+ * Open SSL version string pattern (OpenSSL 3.x — {@code OPENSSL_VERSION_STR "X.Y.Z"}).
+ * In 3.x, {@code OPENSSL_VERSION_NUMBER} is a macro expression that the
+ * legacy {@link #VERSION_PATTERN} cannot match, so this is the authoritative
+ * source of the version on 3.x headers.
+ */
+ private static final Pattern VERSION_STR_PATTERN = Pattern.compile(
+ "define\\s+OPENSSL_VERSION_STR\\s+\"([^\"]+)\"", Pattern.DOTALL
+ | Pattern.CASE_INSENSITIVE);
/**
* The offset of the major version number.
*/
@@ -191,28 +200,31 @@ protected void analyzeDependency(Dependency dependency, Engine engine)
throws AnalysisException {
final File file = dependency.getActualFile();
final String parentName = file.getParentFile().getName();
- boolean found = false;
final String contents = getFileContents(file);
+ String version = null;
if (!contents.isEmpty()) {
- final Matcher matcher = VERSION_PATTERN.matcher(contents);
- if (matcher.find()) {
- found = true;
- final String version = getOpenSSLVersion(Long.parseLong(matcher.group(1), HEXADECIMAL));
- dependency.addEvidence(EvidenceType.VERSION, OPENSSLV_H, "Version Constant",
- version, Confidence.HIGH);
- try {
- final PackageURL purl = PackageURLBuilder.aPackageURL().withType("generic")
- .withName("openssl").withVersion(version).build();
- dependency.addSoftwareIdentifier(new PurlIdentifier(purl, Confidence.HIGHEST));
- } catch (MalformedPackageURLException ex) {
- LOGGER.debug("Unable to build package url for openssl", ex);
- final GenericIdentifier id = new GenericIdentifier("generic:openssl@" + version, Confidence.HIGHEST);
- dependency.addSoftwareIdentifier(id);
+ final Matcher strMatcher = VERSION_STR_PATTERN.matcher(contents);
+ if (strMatcher.find()) {
+ version = strMatcher.group(1);
+ } else {
+ final Matcher matcher = VERSION_PATTERN.matcher(contents);
+ if (matcher.find()) {
+ version = getOpenSSLVersion(Long.parseLong(matcher.group(1), HEXADECIMAL));
}
-
}
}
- if (found) {
+ if (version != null) {
+ dependency.addEvidence(EvidenceType.VERSION, OPENSSLV_H, "Version Constant",
+ version, Confidence.HIGH);
+ try {
+ final PackageURL purl = PackageURLBuilder.aPackageURL().withType("generic")
+ .withName("openssl").withVersion(version).build();
+ dependency.addSoftwareIdentifier(new PurlIdentifier(purl, Confidence.HIGHEST));
+ } catch (MalformedPackageURLException ex) {
+ LOGGER.debug("Unable to build package url for openssl", ex);
+ final GenericIdentifier id = new GenericIdentifier("generic:openssl@" + version, Confidence.HIGHEST);
+ dependency.addSoftwareIdentifier(id);
+ }
dependency.setDisplayFileName(parentName + File.separatorChar + OPENSSLV_H);
dependency.addEvidence(EvidenceType.VENDOR, OPENSSLV_H, "Vendor", "OpenSSL", Confidence.HIGHEST);
dependency.addEvidence(EvidenceType.PRODUCT, OPENSSLV_H, "Product", "OpenSSL", Confidence.HIGHEST);
diff --git a/core/src/test/java/org/owasp/dependencycheck/analyzer/OpenSSLAnalyzerTest.java b/core/src/test/java/org/owasp/dependencycheck/analyzer/OpenSSLAnalyzerTest.java
index a9b14445a9a..e8a6d7781b3 100644
--- a/core/src/test/java/org/owasp/dependencycheck/analyzer/OpenSSLAnalyzerTest.java
+++ b/core/src/test/java/org/owasp/dependencycheck/analyzer/OpenSSLAnalyzerTest.java
@@ -115,4 +115,15 @@ void testOpenSSLVersionHeaderFile() throws AnalysisException {
assertThat(result.getEvidence(EvidenceType.VENDOR).toString(), containsString("OpenSSL"));
assertThat(result.getEvidence(EvidenceType.VERSION).toString(), containsString("1.0.2c"));
}
+
+ @Test
+ void testOpenSSL3xVersionHeaderFile() throws AnalysisException {
+ final Dependency result = new Dependency(BaseTest.getResourceAsFile(
+ this,
+ "openssl-3x/opensslv.h"));
+ analyzer.analyze(result, null);
+ assertThat(result.getEvidence(EvidenceType.PRODUCT).toString(), containsString("OpenSSL"));
+ assertThat(result.getEvidence(EvidenceType.VENDOR).toString(), containsString("OpenSSL"));
+ assertThat(result.getEvidence(EvidenceType.VERSION).toString(), containsString("3.5.6"));
+ }
}
diff --git a/core/src/test/resources/openssl-3x/opensslv.h b/core/src/test/resources/openssl-3x/opensslv.h
new file mode 100644
index 00000000000..299ba102298
--- /dev/null
+++ b/core/src/test/resources/openssl-3x/opensslv.h
@@ -0,0 +1,131 @@
+/*
+ * WARNING: do not edit!
+ * Generated by Makefile from include/openssl/opensslv.h.in
+ *
+ * Copyright 1999-2025 The OpenSSL Project Authors. All Rights Reserved.
+ *
+ * Licensed under the Apache License 2.0 (the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy
+ * in the file LICENSE in the source distribution or at
+ * https://www.openssl.org/source/license.html
+ */
+
+#ifndef OPENSSL_OPENSSLV_H
+#define OPENSSL_OPENSSLV_H
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * SECTION 1: VERSION DATA. These will change for each release
+ */
+
+/*
+ * Base version macros
+ *
+ * These macros express version number MAJOR.MINOR.PATCH exactly
+ */
+/* clang-format off */
+# define OPENSSL_VERSION_MAJOR 3
+/* clang-format on */
+/* clang-format off */
+# define OPENSSL_VERSION_MINOR 5
+/* clang-format on */
+/* clang-format off */
+# define OPENSSL_VERSION_PATCH 6
+/* clang-format on */
+
+/*
+ * Additional version information
+ *
+ * These are also part of the new version scheme, but aren't part
+ * of the version number itself.
+ */
+
+/* Could be: #define OPENSSL_VERSION_PRE_RELEASE "-alpha.1" */
+/* clang-format off */
+# define OPENSSL_VERSION_PRE_RELEASE ""
+/* clang-format on */
+/* Could be: #define OPENSSL_VERSION_BUILD_METADATA "+fips" */
+/* Could be: #define OPENSSL_VERSION_BUILD_METADATA "+vendor.1" */
+/* clang-format off */
+# define OPENSSL_VERSION_BUILD_METADATA ""
+/* clang-format on */
+
+/*
+ * Note: The OpenSSL Project will never define OPENSSL_VERSION_BUILD_METADATA
+ * to be anything but the empty string. Its use is entirely reserved for
+ * others
+ */
+
+/*
+ * Shared library version
+ *
+ * This is strictly to express ABI version, which may or may not
+ * be related to the API version expressed with the macros above.
+ * This is defined in free form.
+ */
+/* clang-format off */
+# define OPENSSL_SHLIB_VERSION 3
+/* clang-format on */
+
+/*
+ * SECTION 2: USEFUL MACROS
+ */
+
+/* For checking general API compatibility when preprocessing */
+#define OPENSSL_VERSION_PREREQ(maj, min) \
+ ((OPENSSL_VERSION_MAJOR << 16) + OPENSSL_VERSION_MINOR >= ((maj) << 16) + (min))
+
+/*
+ * Macros to get the version in easily digested string form, both the short
+ * "MAJOR.MINOR.PATCH" variant (where MAJOR, MINOR and PATCH are replaced
+ * with the values from the corresponding OPENSSL_VERSION_ macros) and the
+ * longer variant with OPENSSL_VERSION_PRE_RELEASE_STR and
+ * OPENSSL_VERSION_BUILD_METADATA_STR appended.
+ */
+/* clang-format off */
+# define OPENSSL_VERSION_STR "3.5.6"
+/* clang-format on */
+/* clang-format off */
+# define OPENSSL_FULL_VERSION_STR "3.5.6"
+/* clang-format on */
+
+/*
+ * SECTION 3: ADDITIONAL METADATA
+ *
+ * These strings are defined separately to allow them to be parsable.
+ */
+/* clang-format off */
+# define OPENSSL_RELEASE_DATE "7 Apr 2026"
+/* clang-format on */
+
+/*
+ * SECTION 4: BACKWARD COMPATIBILITY
+ */
+
+/* clang-format off */
+# define OPENSSL_VERSION_TEXT "OpenSSL 3.5.6 7 Apr 2026"
+/* clang-format on */
+
+/* clang-format off */
+/* Synthesize OPENSSL_VERSION_NUMBER with the layout 0xMNN00PP0L */
+# define OPENSSL_VERSION_NUMBER \
+ ( (OPENSSL_VERSION_MAJOR<<28) \
+ |(OPENSSL_VERSION_MINOR<<20) \
+ |(OPENSSL_VERSION_PATCH<<4) \
+ |0x0L )
+/* clang-format on */
+
+#ifdef __cplusplus
+}
+#endif
+
+#include
+#ifndef OPENSSL_NO_DEPRECATED_3_0
+#define HEADER_OPENSSLV_H
+#endif
+
+#endif /* OPENSSL_OPENSSLV_H */