From f8bd20bb2087e0f0109871ad722dda2735944a1c Mon Sep 17 00:00:00 2001 From: SimpleRisk Updater Date: Fri, 18 Sep 2026 04:24:27 +0000 Subject: [PATCH 1/5] Testing image trigger: version 20260917-001 (code-development @ 6629a550) --- simplerisk-minimal/.testing-version | 2 +- simplerisk-minimal/Dockerfile | 4 ++-- simplerisk-minimal/stack.yml | 2 +- simplerisk/Dockerfile | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/simplerisk-minimal/.testing-version b/simplerisk-minimal/.testing-version index 66c8154..f0fa3e0 100644 --- a/simplerisk-minimal/.testing-version +++ b/simplerisk-minimal/.testing-version @@ -1 +1 @@ -20260909-001 +20260917-001 diff --git a/simplerisk-minimal/Dockerfile b/simplerisk-minimal/Dockerfile index 42b3e79..a1082fa 100644 --- a/simplerisk-minimal/Dockerfile +++ b/simplerisk-minimal/Dockerfile @@ -16,13 +16,13 @@ SHELL [ "/bin/ash", "-eo", "pipefail", "-c" ] # updates feed, then extract -- fail-closed unless PREGA_BUNDLE_FALLBACK allows the # pre-GA path. See common/download_and_verify_bundle.sh. COPY common/download_and_verify_bundle.sh /download_and_verify_bundle.sh -RUN PREGA_BUNDLE_FALLBACK="$PREGA_BUNDLE_FALLBACK" sh /download_and_verify_bundle.sh 20260909-001 +RUN PREGA_BUNDLE_FALLBACK="$PREGA_BUNDLE_FALLBACK" sh /download_and_verify_bundle.sh 20260917-001 FROM php:${php_version}-apache LABEL maintainer="SimpleRisk " -ENV version=20260909-001 +ENV version=20260917-001 WORKDIR /var/www diff --git a/simplerisk-minimal/stack.yml b/simplerisk-minimal/stack.yml index 4cd9ca1..478ce0c 100644 --- a/simplerisk-minimal/stack.yml +++ b/simplerisk-minimal/stack.yml @@ -8,7 +8,7 @@ services: - DB_SETUP=automatic - DB_SETUP_PASS=simplerisk_setup - SIMPLERISK_DB_HOSTNAME=mysql - image: simplerisk/simplerisk-minimal:20260909-001 + image: simplerisk/simplerisk-minimal:20260917-001 ports: - "80:80" - "443:443" diff --git a/simplerisk/Dockerfile b/simplerisk/Dockerfile index bad78a7..ca867ea 100644 --- a/simplerisk/Dockerfile +++ b/simplerisk/Dockerfile @@ -14,13 +14,13 @@ SHELL [ "/bin/ash", "-eo", "pipefail", "-c" ] # -fsSL on the SQL fetch too: without --fail, curl writes the 404 body into # /simplerisk.sql and the image ships an HTML error page as its schema. COPY common/download_and_verify_bundle.sh /download_and_verify_bundle.sh -RUN PREGA_BUNDLE_FALLBACK="$PREGA_BUNDLE_FALLBACK" sh /download_and_verify_bundle.sh 20260909-001 && \ - curl -fsSL "https://github.com/simplerisk/database/raw/master/simplerisk-$DB_LANG-20260909-001.sql" > /simplerisk.sql +RUN PREGA_BUNDLE_FALLBACK="$PREGA_BUNDLE_FALLBACK" sh /download_and_verify_bundle.sh 20260917-001 && \ + curl -fsSL "https://github.com/simplerisk/database/raw/master/simplerisk-$DB_LANG-20260917-001.sql" > /simplerisk.sql # Using Ubuntu image FROM ubuntu:${ubuntu_version_code} -ENV version=20260909-001 +ENV version=20260917-001 # Maintained by SimpleRisk LABEL maintainer="Simplerisk " From 31a4ac1f03f2ba1caf1967871d8807bf9937aaa3 Mon Sep 17 00:00:00 2001 From: Josh Sokol Date: Sun, 20 Sep 2026 13:37:07 -0500 Subject: [PATCH 2/5] Generate simplerisk-minimal TLS cert at runtime, drop baked-in CA (HackerOne #3764027) The Dockerfile generated a CA key/cert and Apache TLS key/cert at build time, baked both into the published image layer, and installed the CA into the container's system trust store. Anyone who pulled the image could extract the CA private key and forge certs for any hostname the container's own outbound HTTPS calls (e.g. the schema fetch in entrypoint.sh) would then trust. Removes the custom CA entirely -- unnecessary, since the non-minimal simplerisk image self-signs its Apache cert directly without one -- and moves self-signed cert generation from build time to container startup, so each deployment gets its own key instead of a key shared by every pull of the same tag. Co-Authored-By: Claude Sonnet 5 --- simplerisk-minimal/Dockerfile | 21 ++++++-------- simplerisk-minimal/common/entrypoint.sh | 29 +++++++++++++++++++ .../apache2/sites-enabled/default-ssl.conf | 1 - simplerisk-minimal/generate_dockerfile.sh | 21 ++++++-------- 4 files changed, 47 insertions(+), 25 deletions(-) diff --git a/simplerisk-minimal/Dockerfile b/simplerisk-minimal/Dockerfile index a1082fa..30f1e36 100644 --- a/simplerisk-minimal/Dockerfile +++ b/simplerisk-minimal/Dockerfile @@ -99,18 +99,15 @@ RUN echo 'upload_max_filesize = 5M' >> /usr/local/etc/php/conf.d/docker-php-uplo echo 'log_errors = On' >> /usr/local/etc/php/conf.d/docker-php-error_logging.ini && \ echo 'error_log = /dev/stderr' >> /usr/local/etc/php/conf.d/docker-php-error_logging.ini && \ echo 'display_errors = Off' >> /usr/local/etc/php/conf.d/docker-php-error_logging.ini && \ -# Create SSL Certificates for Apache SSL - mkdir -p /etc/apache2/ssl/ca /etc/apache2/ssl/simplerisk && \ -# Generate CA - openssl genrsa -out /etc/apache2/ssl/ca/ca.key 4096 && \ - openssl req -x509 -new -nodes -key /etc/apache2/ssl/ca/ca.key -sha256 -days 3650 -out /etc/apache2/ssl/ca/ca.crt -subj "/CN=SimpleRisk CA" && \ -# Generate certs - openssl genrsa -out /etc/apache2/ssl/simplerisk/simplerisk.key 2048 && \ - openssl req -new -key /etc/apache2/ssl/simplerisk/simplerisk.key -out /etc/apache2/ssl/simplerisk/simplerisk.csr -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,DNS:simplerisk,IP:127.0.0.1,IP:0.0.0.0" && \ - openssl x509 -req -days 365 -in /etc/apache2/ssl/simplerisk/simplerisk.csr -CA /etc/apache2/ssl/ca/ca.crt -CAkey /etc/apache2/ssl/ca/ca.key -CAcreateserial -out /etc/apache2/ssl/simplerisk/simplerisk.crt -copy_extensions copyall && \ - cp /etc/apache2/ssl/ca/ca.crt /usr/local/share/ca-certificates/simplerisk.crt && \ - chmod 644 /usr/local/share/ca-certificates/simplerisk.crt && \ - update-ca-certificates && \ +# SSL certificate directory for Apache. The actual key pair is generated at +# container startup by entrypoint.sh (see set_ssl_certificate), not here at +# build time: a key baked into this RUN would be identical in every pulled +# copy of the image and, combined with registering it as a trusted CA, would +# let anyone who pulls the image forge certs the container trusts (HackerOne +# #3764027). No custom CA is created or installed into the system trust +# store -- Apache's cert is self-signed directly, matching the simplerisk +# (non-minimal) image. + mkdir -p /etc/apache2/ssl/simplerisk && \ # Activate Apache modules a2enmod headers rewrite ssl && \ a2enconf security && \ diff --git a/simplerisk-minimal/common/entrypoint.sh b/simplerisk-minimal/common/entrypoint.sh index ee6c210..cf43d40 100644 --- a/simplerisk-minimal/common/entrypoint.sh +++ b/simplerisk-minimal/common/entrypoint.sh @@ -202,6 +202,33 @@ set_csrf_secret(){ [ -n "${SIMPLERISK_CSRF_SECRET:-}" ] && echo "" > "$CSRF_SECRET_PATH"; } +set_ssl_certificate(){ + # Generate Apache's self-signed TLS key pair at container startup rather + # than at image build time. A key baked into the image (the previous + # behavior) is identical in every copy of the image anyone pulls, and was + # additionally being registered as a trusted CA in the container's system + # trust store -- anyone who extracted that key could forge certs the + # container's own outbound curl calls would accept (HackerOne #3764027). + # Generating here means each deployment gets its own key, and idempotence + # (skip if already present) means an existing cert persisted in the + # /etc/apache2/ssl volume survives container restarts/recreation. + local ssl_dir='/etc/apache2/ssl/simplerisk' + local key="$ssl_dir/simplerisk.key" + local crt="$ssl_dir/simplerisk.crt" + + if [ -f "$key" ] && [ -f "$crt" ]; then + return 0 + fi + + print_log "ssl_setup:info" "No Apache TLS certificate found; generating a self-signed one for this container." + exec_cmd "mkdir -p $ssl_dir" "Failed to create SSL certificate directory. Exiting." + # basicConstraints=CA:FALSE is explicit because openssl's own default + # x509 extensions otherwise mark a self-signed leaf cert as CA:TRUE, + # which Apache logs a warning about on every start. + exec_cmd "openssl req -x509 -newkey rsa:2048 -nodes -keyout $key -out $crt -days 365 -subj '/CN=localhost' -addext 'subjectAltName=DNS:localhost,DNS:simplerisk,IP:127.0.0.1,IP:0.0.0.0' -addext 'basicConstraints=critical,CA:FALSE' -addext 'keyUsage=critical,digitalSignature,keyEncipherment' -addext 'extendedKeyUsage=serverAuth'" "Failed to generate self-signed TLS certificate. Exiting." + exec_cmd "chmod 600 $key" "Failed to set permissions on TLS private key. Exiting." +} + set_cron(){ # If SIMPLERISK_CRON_SETUP was passed and it is set to disabled if [[ -n "${SIMPLERISK_CRON_SETUP:-}" && "${SIMPLERISK_CRON_SETUP:-}" = disabled* ]]; then @@ -443,6 +470,8 @@ unset_variables() { } _main() { + set_ssl_certificate + # Detect whether the operator has opted into Docker-managed config # provisioning. If no DB env vars are set, leave config.php absent so # SimpleRisk's web installer runs on first request. diff --git a/simplerisk-minimal/common/etc/apache2/sites-enabled/default-ssl.conf b/simplerisk-minimal/common/etc/apache2/sites-enabled/default-ssl.conf index 0b55c84..45dbc28 100644 --- a/simplerisk-minimal/common/etc/apache2/sites-enabled/default-ssl.conf +++ b/simplerisk-minimal/common/etc/apache2/sites-enabled/default-ssl.conf @@ -11,7 +11,6 @@ SSLStrictSNIVHostCheck Off Options -Indexes SSLEngine on - SSLCACertificateFile /etc/apache2/ssl/ca/ca.crt SSLCertificateFile /etc/apache2/ssl/simplerisk/simplerisk.crt SSLCertificateKeyFile /etc/apache2/ssl/simplerisk/simplerisk.key SSLProtocol -all +TLSv1.2 +TLSv1.3 diff --git a/simplerisk-minimal/generate_dockerfile.sh b/simplerisk-minimal/generate_dockerfile.sh index 53ba282..634b366 100755 --- a/simplerisk-minimal/generate_dockerfile.sh +++ b/simplerisk-minimal/generate_dockerfile.sh @@ -144,18 +144,15 @@ RUN echo 'upload_max_filesize = 5M' >> /usr/local/etc/php/conf.d/docker-php-uplo echo 'log_errors = On' >> /usr/local/etc/php/conf.d/docker-php-error_logging.ini && \\ echo 'error_log = /dev/stderr' >> /usr/local/etc/php/conf.d/docker-php-error_logging.ini && \\ echo 'display_errors = Off' >> /usr/local/etc/php/conf.d/docker-php-error_logging.ini && \\ -# Create SSL Certificates for Apache SSL - mkdir -p /etc/apache2/ssl/ca /etc/apache2/ssl/simplerisk && \\ -# Generate CA - openssl genrsa -out /etc/apache2/ssl/ca/ca.key 4096 && \\ - openssl req -x509 -new -nodes -key /etc/apache2/ssl/ca/ca.key -sha256 -days 3650 -out /etc/apache2/ssl/ca/ca.crt -subj "/CN=SimpleRisk CA" && \\ -# Generate certs - openssl genrsa -out /etc/apache2/ssl/simplerisk/simplerisk.key 2048 && \\ - openssl req -new -key /etc/apache2/ssl/simplerisk/simplerisk.key -out /etc/apache2/ssl/simplerisk/simplerisk.csr -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost,DNS:simplerisk,IP:127.0.0.1,IP:0.0.0.0" && \\ - openssl x509 -req -days 365 -in /etc/apache2/ssl/simplerisk/simplerisk.csr -CA /etc/apache2/ssl/ca/ca.crt -CAkey /etc/apache2/ssl/ca/ca.key -CAcreateserial -out /etc/apache2/ssl/simplerisk/simplerisk.crt -copy_extensions copyall && \\ - cp /etc/apache2/ssl/ca/ca.crt /usr/local/share/ca-certificates/simplerisk.crt && \\ - chmod 644 /usr/local/share/ca-certificates/simplerisk.crt && \\ - update-ca-certificates && \\ +# SSL certificate directory for Apache. The actual key pair is generated at +# container startup by entrypoint.sh (see set_ssl_certificate), not here at +# build time: a key baked into this RUN would be identical in every pulled +# copy of the image and, combined with registering it as a trusted CA, would +# let anyone who pulls the image forge certs the container trusts (HackerOne +# #3764027). No custom CA is created or installed into the system trust +# store -- Apache's cert is self-signed directly, matching the simplerisk +# (non-minimal) image. + mkdir -p /etc/apache2/ssl/simplerisk && \\ # Activate Apache modules a2enmod headers rewrite ssl && \\ a2enconf security && \\ From d6860d5ef5cddb7f82f150290b099d1a2956fd00 Mon Sep 17 00:00:00 2001 From: Josh Sokol Date: Sun, 20 Sep 2026 13:37:12 -0500 Subject: [PATCH 3/5] Run simplerisk cron.php as www-data, not root /var/www/simplerisk (including cron/cron.php) is chowned to www-data, but the cron.d entry executed it as root every minute. Any web-tier compromise that gets code execution as www-data could overwrite cron.php and escalate to root on the next cron tick -- the same CWE-732 privilege-boundary crossing reported and fixed upstream in simplerisk/setup-scripts as HackerOne #3761952, just baked into this image's Dockerfile instead of the bare-metal installer script. cron.php only runs application-level jobs that already execute as www-data under Apache, so it doesn't need root. Co-Authored-By: Claude Sonnet 5 --- simplerisk/Dockerfile | 8 +++++++- simplerisk/generate_dockerfile.sh | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/simplerisk/Dockerfile b/simplerisk/Dockerfile index ca867ea..55a7f51 100644 --- a/simplerisk/Dockerfile +++ b/simplerisk/Dockerfile @@ -110,7 +110,13 @@ RUN chown -R www-data: /var/www/simplerisk RUN chown -R www-data: /var/log/simplerisk # Setting up cronjob -RUN echo "* * * * * root /usr/bin/php -f /var/www/simplerisk/cron/cron.php > /dev/null 2>&1" >> /etc/cron.d/simplerisk-cron && \ +# Runs as www-data, not root: /var/www/simplerisk (including cron.php) is +# chowned to www-data above, so a root cron entry over a www-data-writable +# file would let any web-tier compromise escalate to root on the next tick +# (the same class of bug as CWE-732 / HackerOne #3761952). cron.php only runs +# application-level jobs that already execute as www-data under Apache, so it +# doesn't need root. +RUN echo "* * * * * www-data /usr/bin/php -f /var/www/simplerisk/cron/cron.php > /dev/null 2>&1" >> /etc/cron.d/simplerisk-cron && \ chmod 0644 /etc/cron.d/simplerisk-cron RUN echo "0 0 * * * root /usr/sbin/logrotate /etc/logrotate.d/simplerisk.conf > /dev/null 2>&1" >> /etc/cron.d/logrotate-cron && \ chmod 0644 /etc/cron.d/logrotate-cron diff --git a/simplerisk/generate_dockerfile.sh b/simplerisk/generate_dockerfile.sh index 10e347a..4cadb84 100755 --- a/simplerisk/generate_dockerfile.sh +++ b/simplerisk/generate_dockerfile.sh @@ -159,7 +159,13 @@ RUN chown -R www-data: /var/www/simplerisk RUN chown -R www-data: /var/log/simplerisk # Setting up cronjob -RUN echo "* * * * * root /usr/bin/php -f /var/www/simplerisk/cron/cron.php > /dev/null 2>&1" >> /etc/cron.d/simplerisk-cron && \\ +# Runs as www-data, not root: /var/www/simplerisk (including cron.php) is +# chowned to www-data above, so a root cron entry over a www-data-writable +# file would let any web-tier compromise escalate to root on the next tick +# (the same class of bug as CWE-732 / HackerOne #3761952). cron.php only runs +# application-level jobs that already execute as www-data under Apache, so it +# doesn't need root. +RUN echo "* * * * * www-data /usr/bin/php -f /var/www/simplerisk/cron/cron.php > /dev/null 2>&1" >> /etc/cron.d/simplerisk-cron && \\ chmod 0644 /etc/cron.d/simplerisk-cron RUN echo "0 0 * * * root /usr/sbin/logrotate /etc/logrotate.d/simplerisk.conf > /dev/null 2>&1" >> /etc/cron.d/logrotate-cron && \\ chmod 0644 /etc/cron.d/logrotate-cron From 24528f84b3678376af23e8f9bb0b26671eb11548 Mon Sep 17 00:00:00 2001 From: Josh Sokol Date: Sun, 20 Sep 2026 13:51:45 -0500 Subject: [PATCH 4/5] grype: ignore newly published curl binary-classifier false positives CVE-2026-19931 and CVE-2026-18924 are the same pre-existing Grype false positive already documented above (binary classifier reads the PHP interpreter's embedded version string out of curl.so and reports it as curl's own version), just newly published CVE IDs against that same fictitious version. Verified via `grype -o json`: the only match is /usr/local/lib/php/extensions/.../curl.so; the real Debian curl package is a separate match Debian's tracker marks "wont-fix", which --only-fixed already excludes on its own. This was failing container-validation.yml on all three simplerisk-minimal PHP variants. Co-Authored-By: Claude Sonnet 5 --- .grype.yaml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.grype.yaml b/.grype.yaml index 23614b6..04b4903 100644 --- a/.grype.yaml +++ b/.grype.yaml @@ -22,3 +22,18 @@ ignore: package: name: curl type: binary + # Same binary-classifier false positive as above, newly published CVE IDs. + # Verified against simplerisk-minimal (php 8.3/8.4/8.5): the only "curl" + # match is /usr/local/lib/php/extensions/.../curl.so reporting the PHP + # version (e.g. 8.5.10) as its own. The real Debian curl package + # (8.14.1-2+deb13u5) is also affected but Debian's tracker marks both IDs + # "wont-fix", so --only-fixed already excludes that (legitimate) match on + # its own; only the misclassified binary match needs ignoring here. + - vulnerability: CVE-2026-19931 + package: + name: curl + type: binary + - vulnerability: CVE-2026-18924 + package: + name: curl + type: binary From d20e342f2bd27da0b3da80f5166b6c654710aedd Mon Sep 17 00:00:00 2001 From: Josh Sokol Date: Sun, 20 Sep 2026 14:13:23 -0500 Subject: [PATCH 5/5] docs: correct stale CI/CD and SSL claims, document grype curl false positive - SSL cert generation section still described the pre-fix simplerisk-minimal behavior (build-time CA). Updated to reflect the runtime self-signed cert. - GA promotion was documented as a manual dispatch; promote-latest.yml was changed to auto-fire on push to master, so merging testing -> master now ships to production immediately with no separate approval step. - Documented the testing branch's required-review ruleset (hit this session as a merge block) and the recurring Grype curl/PHP-version-string false positive in simplerisk-minimal (diagnosed this session). Co-Authored-By: Claude Sonnet 5 --- CLAUDE.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 6226b7d..df74b7e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -85,7 +85,7 @@ The entrypoint script handles: - Writing `config.php` by substituting env vars via `sed` - Automatic database provisioning (`DB_SETUP=automatic|automatic-only|manual|delete`) - Headless schema upgrade of an already-installed database (`DB_UPGRADE=automatic|automatic-only`) — runs SimpleRisk's core release-by-release upgrade (`run_database_upgrade_structured`) as the app DB user via `/db-upgrade.php`, emitting the structured per-release JSON to the log; `automatic-only` exits with the upgrade status (used by the EKS release upgrade Job) -- SSL certificate generation (minimal image generates a CA + signed cert; full-stack generates a self-signed cert) +- SSL certificate generation: both images use a self-signed Apache cert, no CA. Full-stack generates it at build time; minimal generates it at container startup (`entrypoint.sh`'s `set_ssl_certificate`, skipped if a cert already exists) so the private key isn't baked into the shared image layer. - Cron setup (`SIMPLERISK_CRON_SETUP` in minimal; always-on in full-stack) - Supervisor start (full-stack) or `apache2-foreground` (minimal) @@ -123,9 +123,12 @@ The entrypoint script handles: - **PRs** trigger `container-validation.yml`: builds all 5 variants (jammy, noble, php83, php84, php85), runs Dockle (Dockerfile linter) and Grype (CVE scanner, severity cutoff: critical, only-fixed), and runs `generator_checks` — the two `test_generate_dockerfile.sh` harnesses that pin the generators' version/source-mode behaviour. - **Release images are built once, then promoted — never rebuilt.** A push to `testing` runs `publish-testing.yml`, which builds both images from the current testing bundle and publishes immutable tags: `simplerisk-minimal` gets `-php83/-php84/-php85` (multi-arch `linux/amd64,linux/arm64`) and `simplerisk` gets `-jammy/-noble` (amd64). Each image's default variant also takes the bare `` and the floating `:testing`. -- **GA is a manual promote, not a build.** After the release merges to `master`, dispatch `promote-latest.yml`. It retags Docker Hub `:latest` to the existing RC digest (`buildx imagetools create`, multi-arch preserved), mirrors the same digests to GHCR cosign-signed, and writes SSM `/simplerisk/customers/image-tag/latest`. Nothing is rebuilt, so the bytes validated in testing are the bytes that ship. A currency guard refuses to promote a version whose digest is not the one `:testing` currently points at. +- **GA promotion fires automatically on merge to `master`, not on manual dispatch.** `promote-latest.yml` triggers on any push to `master` that touches `simplerisk-minimal/Dockerfile` (which carries `ENV version=`). It retags Docker Hub `:latest` to the existing RC digest (`buildx imagetools create`, multi-arch preserved), mirrors the same digests to GHCR cosign-signed, and writes SSM `/simplerisk/customers/image-tag/latest` — nothing is rebuilt. An idempotence guard skips the mutating steps if `:latest` already matches the target digest; `workflow_dispatch` remains available for a manual heal. `create_new_tag.yml` fires on the same push and tags the release. **There is no approval gate between the `testing`→`master` merge and production** — merging is the release. - The reusable workflow files (`*_rw.yml`) are called by the entry-point workflows. +- `testing` requires 1 approving PR review to merge (repo ruleset, not classic branch protection — check via `gh api repos/simplerisk/docker/rulesets`). Admins can bypass with `gh pr merge --admin`. ### Vulnerability ignore list `.grype.yaml` tracks CVEs intentionally ignored (e.g., unfixable at time of release). Update this file when suppressing a new finding, always with a comment explaining why. + +**Recurring false positive (simplerisk-minimal):** Grype's binary classifier misreads the PHP interpreter's version string embedded in `curl.so` as curl's own version, so new curl CVEs periodically get flagged against a nonexistent `curl `. Before assuming a curl CVE finding is real, check `grype -o json | jq` for a match with `type: binary` at path `.../php/extensions/.../curl.so` — if that's the only match (the real Debian `curl` package is a separate, usually-legitimate match), it's this false positive. Add the CVE ID to `.grype.yaml` scoped to `package: {name: curl, type: binary}`.