22 KiB
CI/CD Gate Flow
Overview
The CI/CD Gate Flow describes how StellaOps integrates into continuous integration and deployment pipelines to provide automated security gates. The flow covers CLI-based scanning, policy evaluation, and pass/fail decisions that control pipeline progression.
Business Value: Shift-left security by catching vulnerabilities before deployment, with deterministic, reproducible verdicts that integrate into existing DevOps workflows.
Actors
| Actor | Type | Role |
|---|---|---|
| CI Pipeline | System | GitHub Actions, GitLab CI, Jenkins, etc. |
| StellaOps CLI | Tool | Executes scans from pipeline |
| Gateway | Service | API entry point |
| Scanner | Service | Performs image analysis |
| Policy Engine | Service | Evaluates security policies |
| Attestor | Service | Signs scan results |
Prerequisites
- StellaOps CLI installed in CI environment
- API credentials configured (token or OIDC)
- Policy set defined for the pipeline
- Container image built and available
Supported CI/CD Platforms
| Platform | Integration Method | Credentials |
|---|---|---|
| GitHub Actions | Action + CLI | OIDC or PAT |
| GitLab CI | Job template + CLI | CI_JOB_TOKEN or PAT |
| Azure DevOps | Task + CLI | Service connection |
| Jenkins | Plugin + CLI | Credentials binding |
| CircleCI | Orb + CLI | Context variables |
| Tekton | Task + CLI | Kubernetes secrets |
Flow Diagram
┌─────────────────────────────────────────────────────────────────────────────────┐
│ CI/CD Gate Flow │
└─────────────────────────────────────────────────────────────────────────────────┘
┌────────────┐ ┌───────────┐ ┌─────────┐ ┌─────────┐ ┌────────┐ ┌─────────┐
│ CI Pipeline│ │StellaOps │ │ Gateway │ │ Scanner │ │ Policy │ │ Attestor│
│ │ │ CLI │ │ │ │ │ │ │ │ │
└─────┬──────┘ └─────┬─────┘ └────┬────┘ └────┬────┘ └───┬────┘ └────┬────┘
│ │ │ │ │ │
│ docker build │ │ │ │ │
│───────┐ │ │ │ │ │
│ │ │ │ │ │ │
│<──────┘ │ │ │ │ │
│ │ │ │ │ │
│ stellaops │ │ │ │ │
│ scan │ │ │ │ │
│ --policy=prod │ │ │ │ │
│──────────────>│ │ │ │ │
│ │ │ │ │ │
│ │ POST /scans │ │ │ │
│ │────────────>│ │ │ │
│ │ │ │ │ │
│ │ │ Dispatch │ │ │
│ │ │───────────>│ │ │
│ │ │ │ │ │
│ │ │ │ Analyze │ │
│ │ │ │ image │ │
│ │ │ │───┐ │ │
│ │ │ │ │ │ │
│ │ │ │<──┘ │ │
│ │ │ │ │ │
│ │ │ │ Evaluate │ │
│ │ │ │──────────>│ │
│ │ │ │ │ │
│ │ │ │ │ Apply │
│ │ │ │ │ rules │
│ │ │ │ │───┐ │
│ │ │ │ │ │ │
│ │ │ │ │<──┘ │
│ │ │ │ │ │
│ │ │ │ Verdict │ │
│ │ │ │<──────────│ │
│ │ │ │ │ │
│ │ │ │ Sign │ │
│ │ │ │──────────────────────>│
│ │ │ │ │ │
│ │ │ │ DSSE │ │
│ │ │ │<──────────────────────│
│ │ │ │ │ │
│ │ │ Result │ │ │
│ │ │<───────────│ │ │
│ │ │ │ │ │
│ │ Verdict │ │ │ │
│ │<────────────│ │ │ │
│ │ │ │ │ │
│ Exit code │ │ │ │ │
│ (0=pass, │ │ │ │ │
│ 1=fail) │ │ │ │ │
│<──────────────│ │ │ │ │
│ │ │ │ │ │
│ [if pass] │ │ │ │ │
│ docker push │ │ │ │ │
│───────┐ │ │ │ │ │
│ │ │ │ │ │ │
│<──────┘ │ │ │ │ │
│ │ │ │ │ │
Step-by-Step
1. Pipeline Configuration
GitHub Actions Example
name: Build and Scan
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-scan:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # For OIDC
steps:
- uses: actions/checkout@v4
- name: Build Docker image
run: |
docker build -t myapp:${{ github.sha }} .
- name: Install StellaOps CLI
run: |
curl -sSL https://get.stellaops.io/cli | sh
echo "$HOME/.stellaops/bin" >> $GITHUB_PATH
- name: Authenticate with OIDC
run: |
stellaops auth login --oidc \
--issuer ${{ secrets.STELLAOPS_OIDC_ISSUER }} \
--client-id ${{ secrets.STELLAOPS_CLIENT_ID }}
- name: Scan image
id: scan
run: |
stellaops scan myapp:${{ github.sha }} \
--policy production \
--format sarif \
--output results.sarif \
--attestation \
--fail-on violation
- name: Upload SARIF to GitHub Security
if: always()
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: results.sarif
- name: Push to registry
if: steps.scan.outcome == 'success'
run: |
docker tag myapp:${{ github.sha }} ghcr.io/org/myapp:${{ github.sha }}
docker push ghcr.io/org/myapp:${{ github.sha }}
GitLab CI Example
stages:
- build
- scan
- deploy
variables:
STELLAOPS_API_URL: https://api.stellaops.local
build:
stage: build
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
scan:
stage: scan
image: stellaops/cli:latest
script:
- stellaops auth login --token $STELLAOPS_TOKEN
- stellaops scan $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
--policy production
--fail-on violation
artifacts:
reports:
sast: gl-sast-report.json
deploy:
stage: deploy
needs: [scan]
script:
- kubectl set image deployment/myapp app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
2. CLI Authentication
The CLI supports multiple authentication methods:
| Method | Command | Use Case |
|---|---|---|
| Token | stellaops auth login --token $TOKEN |
CI/CD with secrets |
| OIDC | stellaops auth login --oidc |
GitHub/GitLab OIDC |
| Interactive | stellaops auth login |
Local development |
| Keyless | stellaops auth login --keyless |
Sigstore OIDC |
3. Scan Execution
CLI submits scan request and waits for completion:
stellaops scan docker.io/myorg/myapp:v1.2.3 \
--policy production \
--format json \
--output scan-results.json \
--attestation \
--timeout 5m \
--fail-on violation
CLI Options
| Option | Description | Default |
|---|---|---|
--policy |
Policy set to evaluate against | default |
--format |
Output format (json, sarif, table) | table |
--output |
Write results to file | stdout |
--attestation |
Generate DSSE attestation | false |
--timeout |
Maximum wait time | 10m |
--fail-on |
Exit 1 on: violation, warning, any |
violation |
--quiet |
Suppress progress output | false |
3b. AI Code Guard (optional)
Run AI code guard checks on a change set and emit CI-friendly output:
stella guard run \
--policy .stellaops.yml \
--format sarif \
--out guard.sarif
Recommended exit behavior:
- pass: exit 0
- review: exit 0 (with warning in report)
- block: exit 1
4. Policy Evaluation
Policy engine evaluates findings against CI-specific rules:
# Policy Set: production
version: "stella-dsl@1"
name: production
rules:
- name: block-critical
condition: severity == 'critical' AND vex_status != 'not_affected'
action: FAIL
- name: block-high-unfixed
condition: severity == 'high' AND fixed_version == null
action: FAIL
- name: block-known-exploited
condition: kev == true
action: FAIL
- name: require-sbom
condition: sbom_complete == false
action: FAIL
message: "SBOM must cover all detected packages"
gates:
ci:
max_critical: 0
max_high_unfixed: 0
require_attestation: true
5. Verdict and Exit Code
CLI translates verdict to exit code:
| Verdict | Exit Code | Pipeline Result |
|---|---|---|
| PASS | 0 | Continue |
| WARN | 0 (or 1 if --fail-on warning) |
Continue with warning |
| FAIL | 1 | Block deployment |
| ERROR | 2 | Pipeline failure |
5a. DSSE Witness Verification (Required)
Sprint: SPRINT_20260112_004_DOC_cicd_gate_verification
Before deploying, pipelines must verify DSSE witness signatures and Rekor inclusion (or offline ledger). This ensures attestation integrity and provides tamper-evident audit trail.
Online Verification
# Verify DSSE signature and Rekor inclusion
stellaops proof verify \
--image ghcr.io/org/myapp:$COMMIT_SHA \
--attestation-type scan-result \
--check-rekor \
--fail-on-missing
# Exit codes:
# 0 - Verified successfully
# 1 - Verification failed
# 2 - Missing attestation or Rekor entry
Offline Verification (Air-Gapped Environments)
# Verify against local offline ledger
stellaops proof verify \
--image myapp:$COMMIT_SHA \
--attestation-type scan-result \
--offline \
--ledger-path /var/lib/stellaops/ledger \
--fail-on-missing
# Alternative: verify a bundled evidence pack
stellaops evidence-pack verify \
--bundle /path/to/evidence-pack.tar.gz \
--check-signatures \
--check-merkle
Cosign Equivalent Commands
For environments using cosign directly:
# Online: verify with Rekor
cosign verify-attestation \
--type https://stellaops.io/attestation/scan/v1 \
--rekor-url https://rekor.sigstore.dev \
ghcr.io/org/myapp:$COMMIT_SHA
# Offline: verify with bundled certificate
cosign verify-attestation \
--type https://stellaops.io/attestation/scan/v1 \
--certificate /path/to/cert.pem \
--certificate-chain /path/to/chain.pem \
--offline \
ghcr.io/org/myapp:$COMMIT_SHA
GitHub Actions Integration
- name: Verify attestation
run: |
stellaops proof verify \
--image ghcr.io/org/myapp:${{ github.sha }} \
--attestation-type scan-result \
--check-rekor \
--fail-on-missing
- name: Push to registry (only if verified)
if: success()
run: |
docker push ghcr.io/org/myapp:${{ github.sha }}
GitLab CI Integration
verify:
stage: verify
script:
- stellaops proof verify
--image $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
--attestation-type scan-result
--check-rekor
--fail-on-missing
rules:
- if: $CI_COMMIT_BRANCH == "main"
Related Documentation:
6. SARIF Integration
CLI outputs SARIF for IDE and GitHub integration:
{
"$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "StellaOps",
"version": "2.1.0",
"informationUri": "https://stellaops.io"
}
},
"results": [
{
"ruleId": "CVE-2024-1234",
"level": "error",
"message": {
"text": "Critical vulnerability in lodash@4.17.20"
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"uri": "package-lock.json"
}
}
}
]
}
]
}
]
}
7. Attestation Storage
If --attestation is specified, CLI stores attestation:
# View attestation
stellaops attestation show --scan $SCAN_ID
# Verify attestation
8. PR/MR Comment and Status Integration
StellaOps can post scan results as PR/MR comments and status checks for visibility directly in the SCM platform.
GitHub PR Integration
When scanning PRs, the system can:
- Post a summary comment with findings count and severity breakdown
- Create check runs with inline annotations
- Update commit status with pass/fail verdict
# GitHub Actions with PR comments
- name: Scan with PR feedback
run: |
stellaops scan myapp:${{ github.sha }} \
--policy production \
--pr-comment \
--check-run \
--github-token ${{ secrets.GITHUB_TOKEN }}
Example PR comment format:
## StellaOps Scan Results
**Verdict:** :warning: WARN
| Severity | Count |
|----------|-------|
| Critical | 0 |
| High | 2 |
| Medium | 5 |
| Low | 12 |
### Findings Requiring Attention
| CVE | Severity | Package | Status |
|-----|----------|---------|--------|
| CVE-2026-1234 | High | lodash@4.17.21 | Fix available: 4.17.22 |
| CVE-2026-5678 | High | express@4.18.0 | VEX: Not affected |
<details>
<summary>View full report</summary>
[Download SARIF](https://stellaops.example.com/scans/abc123/sarif)
[View in Console](https://stellaops.example.com/scans/abc123)
</details>
---
*Scan ID: abc123 | Policy: production | [Evidence](https://stellaops.example.com/evidence/abc123)*
GitLab MR Integration
For GitLab Merge Requests:
- Post MR notes with findings summary
- Update commit status on the pipeline
- Create discussion threads for critical findings
# GitLab CI with MR feedback
scan:
stage: test
script:
- stellaops scan $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA \
--policy production \
--mr-comment \
--commit-status \
--gitlab-token $CI_JOB_TOKEN
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
Comment Behavior Options
| Option | Description | Default |
|---|---|---|
--pr-comment / --mr-comment |
Post summary comment | false |
--check-run |
Create GitHub check run with annotations | false |
--commit-status |
Update commit status | false |
--update-existing |
Edit previous comment instead of new | true |
--collapse-details |
Use collapsible sections for long output | true |
--evidence-link |
Include link to evidence bundle | true |
Evidence Anchoring in Comments
Comments include evidence references for auditability:
- Scan ID: Unique identifier for the scan
- Policy Version: The policy version used for evaluation
- Attestation Digest: DSSE envelope digest for signed results
- Rekor Entry: Log index when transparency logging is enabled
Error Handling
| Scenario | Behavior |
|---|---|
| No SCM token | Skip comment, log warning |
| API rate limit | Retry with backoff, then skip |
| Comment too long | Truncate with link to full report |
| PR already merged | Skip comment |
Evidence-First Annotation Format
PR/MR comments use ASCII-only output for determinism and maximum compatibility:
## StellaOps Security Scan
**Verdict:** [BLOCKING] Policy violation detected
| Status | Finding | Package | Action |
| --- | --- | --- | --- |
| [+] New | CVE-2026-1234 | lodash@4.17.21 | Fix: 4.17.22 |
| [-] Fixed | CVE-2025-9999 | express@4.17.0 | Resolved |
| [^] Upgraded | CVE-2026-5678 | axios@1.0.0 | High -> Medium |
| [v] Downgraded | CVE-2026-4321 | react@18.0.0 | Medium -> Low |
### Evidence
| Field | Value |
| --- | --- |
| Attestation Digest | sha256:abc123... |
| Policy Verdict | FAIL |
| Verify Command | `stellaops verify --digest sha256:abc123...` |
---
*[OK] 12 findings unchanged | Policy: production v2.1.0*
ASCII Indicator Reference:
| Indicator | Meaning |
|---|---|
[OK] |
Pass / Success |
[BLOCKING] |
Fail / Hard gate triggered |
[WARNING] |
Soft gate / Advisory |
[+] |
New finding introduced |
[-] |
Finding fixed / removed |
[^] |
Severity upgraded |
[v] |
Severity downgraded |
Offline Mode
In air-gapped environments:
- Comments are queued locally
- Export comment payload for manual posting
- Generate markdown file for offline review stellaops attestation verify --image myapp:v1.2.3 --policy production
Attestation is stored as DSSE envelope:
```json
{
"payloadType": "application/vnd.in-toto+json",
"payload": "eyJfdHlwZSI6Imh0dHBzOi8vaW4tdG90by5pby9TdGF0ZW1lbnQvdjEi...",
"signatures": [
{
"keyid": "sha256:abc123...",
"sig": "MEQCI..."
}
]
}
Gate Behaviors
Soft Gate (Warning Only)
# .stellaops.yaml
gates:
ci:
mode: soft # Report but don't fail
notify:
- slack://security-channel
Hard Gate (Blocking)
gates:
ci:
mode: hard # Fail pipeline on violations
exceptions:
- CVE-2024-9999 # Known false positive
Progressive Gate
gates:
ci:
mode: progressive
thresholds:
- branch: main
max_critical: 0
max_high: 5
- branch: develop
max_critical: 2
max_high: 20
- branch: feature/*
mode: soft # Warn only on feature branches
Data Contracts
CLI Scan Output Schema
interface CliScanOutput {
scan_id: string;
image: string;
digest: string;
verdict: 'PASS' | 'WARN' | 'FAIL';
confidence: number;
summary: {
critical: number;
high: number;
medium: number;
low: number;
};
violations: Array<{
cve: string;
severity: string;
package: string;
rule: string;
message: string;
}>;
attestation?: {
digest: string;
rekor_log_index?: number;
};
timing: {
queued_ms: number;
scan_ms: number;
policy_ms: number;
total_ms: number;
};
}
Error Handling
| Error | Exit Code | Recovery |
|---|---|---|
| Auth failure | 2 | Check credentials |
| Image not found | 2 | Verify image reference |
| API timeout | 2 | Retry with --timeout |
| Policy not found | 2 | Check policy name |
| Network error | 2 | Check connectivity |
Observability
Metrics
| Metric | Type | Labels |
|---|---|---|
cli_scan_total |
Counter | verdict, ci_platform |
cli_scan_duration_seconds |
Histogram | ci_platform |
cli_gate_blocked_total |
Counter | policy, reason |
CI/CD Annotations
GitHub Actions annotations:
::error file=package-lock.json::CVE-2024-1234: Critical vulnerability in lodash@4.17.20
::warning file=Dockerfile::Using outdated base image
Related Flows
- Scan Submission Flow - Underlying scan mechanics
- Policy Evaluation Flow - Policy rule details
- Binary Delta Attestation Flow - Attestation details