Summary
The weekly periodic-security-scan workflow is designed to scan published images and open an issue when vulnerabilities are found. That alerting has never fired. There are two independent problems:
- A step-ordering bug aborts the job before the issue-filing logic runs, so nothing is ever reported.
- Even with that fixed, its thresholds do not match the publish gate:
build-containers blocks publishing at high, while this workflow only files an issue at critical. A high finding is therefore serious enough to block shipping but not serious enough to notify anyone.
Evidence
The last five scheduled runs all failed:
| Run |
Date |
| 30780553811 |
2026-08-03 |
| 30233416175 |
2026-07-27 |
| 29713593389 |
2026-07-20 |
| 29220593745 |
2026-07-13 |
| 28764834213 |
2026-07-06 |
And no issue has ever been created by it. There are currently zero issues in the repo carrying the security or grype labels that the workflow is configured to apply.
Problem 1: the job aborts before it can report
Run Grype vulnerability scan (SARIF) uses severity-cutoff: "low" and does not set fail-build. anchore/scan-action defaults fail-build to true, so with a low cutoff and no only-fixed the step fails on essentially any finding, which fails the job.
Upload SARIF to GitHub Security carries if: always() and therefore survives. The three steps that perform the alerting do not, so they are skipped. Step outcomes from scan-images (uvx/mcp-neo4j-cypher/spec.yaml) in the 2026-08-03 run:
success Log in to Container Registry
failure Run Grype vulnerability scan (SARIF)
success Upload SARIF to GitHub Security <- if: always()
skipped Run Grype vulnerability scan (JSON)
skipped Check for critical issues
skipped Create issue for critical findings
success Upload scan results
This also explains why GitHub code scanning has periodic-grype-* data while no issues exist: the SARIF upload works, and only the reporting path is dead.
Problem 2: thresholds do not match the publish gate
|
build-containers gate |
periodic-security-scan |
severity-cutoff |
high |
low |
only-fixed |
true |
unset, so false |
| alerting threshold |
blocks publish at high |
files an issue only when critical > 0 |
This is inconsistent in both directions:
- Scanning at
low without only-fixed surfaces findings that have no fix available, which nobody can action, and is what makes the step fail unconditionally.
- Alerting only on criticals means a high finding, which by policy is enough to block a publish, produces no notification at all.
high is computed into $GITHUB_OUTPUT at line 117 and then never used in the should_create_issue decision at line 122, which reads like the intent was there and got dropped.
Impact
Nothing has been reported since at least 2026-07-06. Fixing only problem 1 would surface the criticals:
Every high that currently blocks a build would still go unreported, including brightdata-mcp (3x @modelcontextprotocol/sdk), chroma-mcp (5x mcp), adb-mysql-mcp-server (cryptography), astra-db-mcp (3x undici), and onchain-mcp (2x SDK). See #830 for the full backlog.
Put differently: aligning the threshold is the difference between reporting 2 servers and reporting 10.
Suggested fix
Two things need aligning, not just one.
1. Let the job run to completion. Set fail-build: false on both scan steps so Check for critical issues decides the outcome rather than the scanner aborting the job. Alternatively add if: always() to the three downstream steps, but fail-build: false is cleaner given that step already computes the counts.
2. Align the thresholds with the publish gate. Match build-containers: severity-cutoff: high and only-fixed: true on both scan steps, and set should_create_issue=true when critical + high > 0.
That way "the weekly scan is red" and "this image could not be published today" mean the same thing, which is the useful invariant. It also makes a red run actionable, rather than the current state where it is always red because some unfixable low-severity CVE exists somewhere.
Summary
The weekly
periodic-security-scanworkflow is designed to scan published images and open an issue when vulnerabilities are found. That alerting has never fired. There are two independent problems:build-containersblocks publishing at high, while this workflow only files an issue at critical. A high finding is therefore serious enough to block shipping but not serious enough to notify anyone.Evidence
The last five scheduled runs all failed:
And no issue has ever been created by it. There are currently zero issues in the repo carrying the
securityorgrypelabels that the workflow is configured to apply.Problem 1: the job aborts before it can report
Run Grype vulnerability scan (SARIF)usesseverity-cutoff: "low"and does not setfail-build.anchore/scan-actiondefaultsfail-buildtotrue, so with alowcutoff and noonly-fixedthe step fails on essentially any finding, which fails the job.Upload SARIF to GitHub Securitycarriesif: always()and therefore survives. The three steps that perform the alerting do not, so they are skipped. Step outcomes fromscan-images (uvx/mcp-neo4j-cypher/spec.yaml)in the 2026-08-03 run:This also explains why GitHub code scanning has
periodic-grype-*data while no issues exist: the SARIF upload works, and only the reporting path is dead.Problem 2: thresholds do not match the publish gate
build-containersgateperiodic-security-scanseverity-cutoffhighlowonly-fixedtrueThis is inconsistent in both directions:
lowwithoutonly-fixedsurfaces findings that have no fix available, which nobody can action, and is what makes the step fail unconditionally.highis computed into$GITHUB_OUTPUTat line 117 and then never used in theshould_create_issuedecision at line 122, which reads like the intent was there and got dropped.Impact
Nothing has been reported since at least 2026-07-06. Fixing only problem 1 would surface the criticals:
fastmcpcritical (GHSA-vv7q-7jx5-f767) onmcp-neo4j-cypher,mcp-neo4j-aura-manager, andmcp-neo4j-memorynextcritical (GHSA-9qr9-h5gf-34mp) onmcp-server-neonEvery high that currently blocks a build would still go unreported, including
brightdata-mcp(3x@modelcontextprotocol/sdk),chroma-mcp(5xmcp),adb-mysql-mcp-server(cryptography),astra-db-mcp(3xundici), andonchain-mcp(2x SDK). See #830 for the full backlog.Put differently: aligning the threshold is the difference between reporting 2 servers and reporting 10.
Suggested fix
Two things need aligning, not just one.
1. Let the job run to completion. Set
fail-build: falseon both scan steps soCheck for critical issuesdecides the outcome rather than the scanner aborting the job. Alternatively addif: always()to the three downstream steps, butfail-build: falseis cleaner given that step already computes the counts.2. Align the thresholds with the publish gate. Match
build-containers:severity-cutoff: highandonly-fixed: trueon both scan steps, and setshould_create_issue=truewhencritical + high > 0.That way "the weekly scan is red" and "this image could not be published today" mean the same thing, which is the useful invariant. It also makes a red run actionable, rather than the current state where it is always red because some unfixable low-severity CVE exists somewhere.