███████╗███████╗███╗ ██╗████████╗██████╗ ██╗ ██╗ ██╔════╝██╔════╝████╗ ██║╚══██╔══╝██╔══██╗╚██╗ ██╔╝ ███████╗█████╗ ██╔██╗ ██║ ██║ ██████╔╝ ╚████╔╝ ╚════██║██╔══╝ ██║╚██╗██║ ██║ ██╔══██╗ ╚██╔╝ ███████║███████╗██║ ╚████║ ██║ ██║ ██║ ██║ ╚══════╝╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝
Container Security Auditing for CI/CD Pipelines
Installation • Quick Start • Documentation • CI/CD Integration
Sentry is a security auditing tool that scans your container images for vulnerabilities and misconfigurations. It integrates with your CI/CD pipeline to automatically check containers before deployment.
Key Features:
- Multi-Scanner Support — Trivy, Grype, Snyk, Wiz, or Black Duck
- Security Checks — Non-root user, secret detection, healthcheck validation
- Reports — Markdown summaries, JSON for automation, 0-100 security scores
- CI/CD Gates — Pass/fail exit codes to block vulnerable deployments
flowchart LR
subgraph Scanners
S1[Trivy]
S2[Grype]
S3[Snyk]
S4[Wiz]
S5[Black Duck]
end
subgraph Checks["Security Checks"]
C1[Non-Root User]
C2[Secret Detection]
C3[Health Check]
end
subgraph Output["Output Formats"]
O1[Markdown Report]
O2[JSON]
O3[Exit Code]
O4[Score]
end
subgraph Config["Configuration"]
CF1[Severity Threshold]
CF2[Ignore CVEs]
CF3[Disable Checks]
end
flowchart LR
subgraph Input
A[Container Image]
end
subgraph Sentry["Sentry Module"]
B[Security Checks]
C[Vulnerability Scanner]
D[Score Calculator]
end
subgraph Scanners["Scanner Options"]
E[Trivy]
F[Grype]
G[Snyk]
H[Wiz]
I[Black Duck]
end
subgraph Output
J[Report]
K[JSON]
L[Exit Code]
end
A --> B
A --> C
C --> E & F & G & H & I
B --> D
C --> D
D --> J & K & L
Dagger is a programmable CI/CD engine that runs your pipelines in containers. Instead of writing YAML, you write code in Go, Python, or TypeScript.
Why Dagger?
- Portable — Same pipeline runs locally and in any CI (GitHub Actions, GitLab, Jenkins)
- Fast — Intelligent caching speeds up builds
- Reproducible — Containers ensure consistent environments
Sentry is a Dagger Module — a reusable component you can call from any Dagger pipeline.
flowchart TB
subgraph traditional["Traditional CI/CD"]
direction TB
T1[Write YAML config] --> T2[Debug syntax errors]
T2 --> T3[Push and wait for CI]
T3 --> T4[Fix CI-only failures]
T4 -.->|repeat| T1
end
subgraph dagger["With Dagger"]
direction TB
D1[Write code with IDE] --> D2[Test locally first]
D2 --> D3[Push with confidence]
D3 --> D4[Same result in CI]
end
- Docker — Install Docker
- Dagger CLI — Install with one command:
# macOS / Linux
curl -fsSL https://dl.dagger.io/dagger/install.sh | sh
# Windows (PowerShell)
Invoke-WebRequest -Uri https://dl.dagger.io/dagger/install.ps1 -OutFile install.ps1; .\install.ps1Verify installation:
dagger versionSentry runs directly from the Daggerverse — no cloning or installing needed.
stateDiagram-v2
[*] --> Scanning: dagger call scan
Scanning --> SecurityChecks: Container loaded
SecurityChecks --> VulnScan: Checks complete
VulnScan --> Scoring: Scan complete
Scoring --> Passed: score >= threshold
Scoring --> Failed: score < threshold
Passed --> [*]: Exit code 0
Failed --> [*]: Exit code 1
Scan any container image with a single command:
dagger call -m github.com/sylvester-francis/Sentry \
scan --container=nginx:latest \
reportOutput:
# Security Audit Report
## Executive Summary
┌─────────────────────────────────────────────────────────────┐
│ STATUS: PASSED │
├─────────────────────────────────────────────────────────────┤
│ Score: 85/100 │
│ Checks: 2 passed, 1 warning │
│ Vulnerabilities: 12 total (0 critical, 2 high) │
└─────────────────────────────────────────────────────────────┘
Get a pass/fail result for CI pipelines:
dagger call -m github.com/sylvester-francis/Sentry \
scan --container=myapp:latest \
exit-code
# Returns 0 (pass) or 1 (fail)Get JSON output for automation:
dagger call -m github.com/sylvester-francis/Sentry \
scan --container=myapp:latest \
jsonGet just the security score:
dagger call -m github.com/sylvester-francis/Sentry \
scan --container=myapp:latest \
score
# Returns: 85Sentry supports multiple vulnerability scanners. Default is Trivy.
| Scanner | Command | Auth Required |
|---|---|---|
| Trivy (default) | with-trivy |
No |
| Grype | with-grype |
No |
| Snyk | with-snyk --token=env:SNYK_TOKEN |
Yes |
| Wiz | with-wiz --client-id=... --client-secret=... |
Yes |
| Black Duck | with-black-duck --url=... --token=... |
Yes |
Example: Use Grype instead of Trivy
dagger call -m github.com/sylvester-francis/Sentry \
scan --container=myapp:latest \
with-grype \
reportExample: Use Snyk with authentication
export SNYK_TOKEN=your-token-here
dagger call -m github.com/sylvester-francis/Sentry \
scan --container=myapp:latest \
with-snyk --token=env:SNYK_TOKEN \
reportControl when the audit fails based on vulnerability severity:
# Fail only on CRITICAL vulnerabilities (lenient)
dagger call -m github.com/sylvester-francis/Sentry \
scan --container=myapp:latest \
fail-on --severity=CRITICAL \
exit-code
# Fail on HIGH or above (default)
dagger call -m github.com/sylvester-francis/Sentry \
scan --container=myapp:latest \
fail-on --severity=HIGH \
exit-code
# Fail on MEDIUM or above (strict)
dagger call -m github.com/sylvester-francis/Sentry \
scan --container=myapp:latest \
fail-on --severity=MEDIUM \
exit-codeSuppress specific CVEs (for accepted risks or false positives):
dagger call -m github.com/sylvester-francis/Sentry \
scan --container=myapp:latest \
ignore-cves --cve-ids=CVE-2024-1234,CVE-2024-5678 \
report# Skip non-root check (for containers that must run as root)
dagger call -m github.com/sylvester-francis/Sentry \
scan --container=myapp:latest \
with-non-root-check --enable=false \
report
# Skip secret detection
dagger call -m github.com/sylvester-francis/Sentry \
scan --container=myapp:latest \
with-secret-check --enable=false \
report
# Run security checks only (no vulnerability scanning)
dagger call -m github.com/sylvester-francis/Sentry \
scan --container=myapp:latest \
without-scanner \
reportSentry performs these automated security checks:
| Check | Severity | Description |
|---|---|---|
| Non-Root User | HIGH | Verifies container doesn't run as root (UID 0) |
| Secret Detection | HIGH | Scans environment variables for exposed credentials |
| Health Check | INFO | Verifies curl or wget is available for health probes |
flowchart TD
A[Container] --> B{Non-Root Check}
B -->|UID > 0| C[PASS]
B -->|UID = 0| D[FAIL - HIGH]
A --> E{Secret Detection}
E -->|No secrets found| F[PASS]
E -->|Secrets detected| G[FAIL - HIGH]
A --> H{Health Check}
H -->|curl/wget available| I[PASS]
H -->|Not available| J[WARN - INFO]
C & D & F & G & I & J --> K[Calculate Score]
K --> L{Score >= Threshold?}
L -->|Yes| M[PASSED]
L -->|No| N[FAILED]
Sentry detects these credential patterns in environment variables:
- AWS Access Keys (
AKIA...) - GitHub Tokens (
ghp_,gho_,ghs_,ghr_) - JWT Tokens (
eyJ...) - Private Keys (
-----BEGIN...) - Database URLs with credentials
- Slack Tokens, API Keys
sequenceDiagram
participant Dev as Developer
participant CI as CI/CD Pipeline
participant Sentry as Sentry Module
participant Registry as Container Registry
Dev->>CI: Push Code
CI->>CI: Build Container
CI->>Sentry: Scan Container
Sentry->>Sentry: Run Security Checks
Sentry->>Sentry: Run Vulnerability Scan
Sentry->>CI: Return Exit Code
alt Passed (exit code 0)
CI->>Registry: Push Container
CI->>Dev: Deploy Success
else Failed (exit code 1)
CI->>Dev: Block Deployment
end
name: Security Audit
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Dagger
run: curl -fsSL https://dl.dagger.io/dagger/install.sh | sh
- name: Run Security Audit
run: |
./bin/dagger call -m github.com/sylvester-francis/Sentry \
scan --container=myapp:${{ github.sha }} \
exit-codesecurity_audit:
stage: security
image: docker:latest
services:
- docker:dind
before_script:
- curl -fsSL https://dl.dagger.io/dagger/install.sh | sh
script:
- ./bin/dagger call -m github.com/sylvester-francis/Sentry \
scan --container=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA \
exit-codeCall Sentry from your own Dagger pipeline code:
package main
import (
"context"
"dagger/mymodule/internal/dagger"
)
type MyModule struct{}
func (m *MyModule) Build(ctx context.Context) (*dagger.Container, error) {
container := dag.Container().
From("golang:1.21").
// ... build steps ...
// Run security audit before deploying
result, err := dag.Sentry().
Scan(container).
FailOn("HIGH").
Report(ctx)
if err != nil {
return nil, err
}
return container, nil
}Human-readable report with executive summary, vulnerability breakdown, and check results.
Machine-readable format for automation and integration:
{
"timestamp": "2026-01-13T12:00:00Z",
"imageRef": "myapp:latest",
"scannerUsed": "trivy",
"passed": true,
"score": 85,
"checks": [...],
"vulnerabilities": [...],
"vulnSummary": {
"critical": 0,
"high": 2,
"medium": 5,
"low": 5,
"total": 12
}
}0-100 score based on findings. Deductions:
- CRITICAL vulnerability: -10 points
- HIGH vulnerability: -5 points
- MEDIUM vulnerability: -2 points
- Failed security check: -15 to -25 points
quadrantChart
title Security Score Interpretation
x-axis Low Risk --> High Risk
y-axis Low Confidence --> High Confidence
quadrant-1 Review Findings
quadrant-2 Safe to Deploy
quadrant-3 Needs Investigation
quadrant-4 Block Deployment
Score 90-100: [0.15, 0.85]
Score 70-89: [0.35, 0.70]
Score 50-69: [0.60, 0.45]
Score 0-49: [0.85, 0.25]
| Command | Description |
|---|---|
scan --container=<image> |
Start audit for a container |
scan-image --image-ref=<ref> |
Start audit from image reference string |
with-trivy / with-grype / with-snyk |
Select vulnerability scanner |
fail-on --severity=<level> |
Set failure threshold (CRITICAL/HIGH/MEDIUM/LOW) |
ignore-cves --cve-ids=<list> |
Suppress specific CVE IDs |
with-secret-check --enable=<bool> |
Enable/disable secret detection |
with-non-root-check --enable=<bool> |
Enable/disable non-root check |
report |
Generate Markdown report |
json |
Generate JSON report |
score |
Get numeric security score (0-100) |
summary |
Get one-line status summary |
passed |
Get boolean pass/fail |
exit-code |
Get CI exit code (0=pass, 1=fail) |
| Issue | Solution |
|---|---|
| Scanner returns empty results | Normal for minimal images (e.g., scratch, distroless) |
| "Container running as root" | Add USER nobody to your Dockerfile |
| "No curl/wget found" | Add health check tools or disable check with --with-health-check=false |
| 401 Unauthorized | Refresh your scanner authentication token |
dagger call -m github.com/sylvester-francis/Sentry testgit clone https://github.com/sylvester-francis/Sentry.git
cd Sentry
dagger develop
dagger functions
dagger call scan --container=alpine:latest report- Fork the repository
- Create a feature branch
- Make your changes
- Run tests:
dagger call test - Open a Pull Request
Built with Dagger