Block deployments of actively exploited vulnerabilities
Your CI pipeline can move fast. But without a security gate, it can deploy software with known active exploits just as fast. attestd adds a single deterministic check before any build reaches production.
Most pipelines already run a scanner. The gap is not "no CVE data." It is a gate that can fail the job without a human reading the report.
- →CVSS-based gates produce noise. A score of 7.4 does not tell CI whether to exit 1 or continue.
- →Manifest scanners return issue lists. A merge bot or deploy job still needs a categorical fail condition.
- →Compromised packages often have no CVE at the moment CI pulls them. A CVE-only step returns clean.
- →Local artifact scanners need an image or SBOM first. A version pin in a lockfile is available earlier, before the build artifact exists.
Fail the job on categorical fields. Keep the exception path for humans, not the default path.
| Field | Meaning for this use case |
|---|---|
risk_state | Fail on critical by default. Optionally fail on high for production deploy jobs. |
actively_exploited | Strong fail condition even when risk_state is high rather than critical. Confirmed exploitation in the wild. |
supply_chain.compromised | Fail immediately. Confirmed malicious publish, independent of CVE coverage. |
fixed_version | Print in the job log so the fix PR has a concrete upgrade target. |
risk_factors | Human-readable reasons for the fail. Surface them with ::error:: or your CI annotation format. |
curl "https://api.attestd.io/v1/check?product=nginx&version=${VERSION}" \
-H "Authorization: Bearer ${ATTESTD_API_KEY}"- name: attestd risk check
env:
ATTESTD_API_KEY: ${{ secrets.ATTESTD_API_KEY }}
VERSION: ${{ env.NGINX_VERSION }}
run: |
RESULT=$(curl -sS "https://api.attestd.io/v1/check?product=nginx&version=$VERSION" \
-H "Authorization: Bearer $ATTESTD_API_KEY")
RISK=$(echo "$RESULT" | jq -r '.risk_state')
COMPROMISED=$(echo "$RESULT" | jq -r '.supply_chain.compromised // false')
FIXED=$(echo "$RESULT" | jq -r '.fixed_version // empty')
if [ "$COMPROMISED" = "true" ] || [ "$RISK" = "critical" ]; then
echo "::error::Deployment blocked (risk_state=$RISK compromised=$COMPROMISED)"
echo "$RESULT" | jq -r '.risk_factors // [] | join(", ")'
[ -n "$FIXED" ] && echo "Upgrade to: $FIXED"
exit 1
fi
echo "Risk check passed. Proceeding with deployment."- 01
Add a check step before deploy
Call /v1/check for each pinned runtime dependency you care about: base images, language runtimes, and high-risk packages. Fail on critical or supply_chain.compromised.
- 02
Wire the API key as a CI secret
Store ATTESTD_API_KEY in GitHub Actions, GitLab CI, or your runner secret store. Never commit it. Free tier is enough to validate the gate.
- 03
Annotate failures with the upgrade path
Log fixed_version and risk_factors in the job output so the next PR has a concrete version bump instead of a vague "security failed" message.
Automated deployments stop before reaching production.
When an actively exploited critical vulnerability or a confirmed supply chain compromise is detected, the CI step fails with a clear error and the fixed version. No human review needed for the gate itself. Only for the exception.
[email protected] and [email protected] affected an estimated 10% of cloud environments within two hours of publication. A CI gate that checked those versions at install time would have blocked the compromised release before any downstream build pulled it.
The same supply chain pattern shows up in ViteVenom and JoyFill: packages with no CVE that return supply_chain.compromised: true. A CVE-only scanner would have greenlit the install.
For CI-adjacent tool comparisons, see Attestd vs Grype (artifact scan vs name@version lookup) and Attestd vs Sonatype Guide (deterministic risk_state vs CVSS advisory output in a gate).