Skip to content

docs: align Compose1440 published status and usage #44

docs: align Compose1440 published status and usage

docs: align Compose1440 published status and usage #44

name: Security release gate
on:
workflow_dispatch:
push:
branches:
- main
- 'verification/compose-cli-*'
permissions:
contents: read
concurrency:
group: compose-cli-security-${{ github.ref }}
cancel-in-progress: false
jobs:
build-test-scan:
runs-on: ubuntu-24.04
timeout-minutes: 75
env:
DAPPER_IMAGE: pasturestack/compose-cli-dapper:${{ github.sha }}
TRIVY_IMAGE: aquasec/trivy:0.74.0@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969
VERSION_OVERRIDE: 0.14.40
PLATFORM_COMPAT_JAR_URL: https://github.com/PastureStack/orchestration-engine/releases/download/v0.183.281/orchestration-engine-0.183.281.jar
PLATFORM_COMPAT_JAR_SHA256: da2a8a51562ed16e296f7e29e99482bb44042ff0834cca679bbe01d951ba1682
steps:
- name: Check out candidate
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Record candidate identity
shell: bash
run: |
set -euo pipefail
test -z "$(git status --porcelain)"
test "$(git rev-parse HEAD)" = "$GITHUB_SHA"
mkdir -p evidence
git rev-parse HEAD > evidence/source-revision.txt
sha256sum Dockerfile.dapper ubuntu-apt.lock ubuntu-security-packages.lock scripts/install-ubuntu-security-updates.sh .github/dapper-headers.json .github/dapper-unfixed.json go.mod go.sum vendor/modules.txt .python-version tests/integration/requirements.txt > evidence/source-locks.sha256
- name: Build, test, validate, and package twice
shell: bash
run: |
set -euo pipefail
docker build \
--build-arg DAPPER_HOST_ARCH=amd64 \
--tag "$DAPPER_IMAGE" \
--file Dockerfile.dapper \
.
source_path="$GITHUB_WORKSPACE"
run_ci() {
docker run --rm \
--volume "${source_path}:/go/src/github.com/PastureStack/compose-cli" \
--env "DAPPER_UID=$(id -u)" \
--env "DAPPER_GID=$(id -g)" \
--env "GOCACHE=/tmp/go-build-cache-${GITHUB_RUN_ID}" \
--env "XDG_CONFIG_HOME=/tmp/go-config-${GITHUB_RUN_ID}" \
--env "GIT_CONFIG_GLOBAL=/tmp/gitconfig-${GITHUB_RUN_ID}" \
--env "VERSION_OVERRIDE=$VERSION_OVERRIDE" \
--env "SKIP_INTEGRATION=true" \
--env "PLATFORM_COMPAT_JAR_URL=$PLATFORM_COMPAT_JAR_URL" \
--env "PLATFORM_COMPAT_JAR_SHA256=$PLATFORM_COMPAT_JAR_SHA256" \
"$DAPPER_IMAGE" ci
}
run_ci
artifact="dist/artifacts/compose-executor-0.14.40-linux-amd64.gz"
test -s "$artifact"
cp "$artifact" /tmp/compose-executor-first.gz
rm -rf bin build dist
run_ci
cmp /tmp/compose-executor-first.gz "$artifact"
./scripts/check-pasturestack-source
mkdir -p evidence/product
gzip -cd "$artifact" > evidence/product/compose-executor
chmod +x evidence/product/compose-executor
evidence/product/compose-executor --version | grep -F '0.14.40' >/dev/null
cp "$artifact" evidence/
(
cd evidence
sha256sum "$(basename "$artifact")" > compose-executor.gz.sha256
)
docker run --rm --entrypoint go \
--volume "$PWD:/work:ro" \
"$DAPPER_IMAGE" \
version -m /work/evidence/product/compose-executor \
> evidence/product-go-version.txt
grep -F 'go1.27.3' evidence/product-go-version.txt >/dev/null
- name: Record resolved build inputs
shell: bash
run: |
set -euo pipefail
docker inspect "$DAPPER_IMAGE" > evidence/dapper-image-inspect.json
docker run --rm --entrypoint sh "$DAPPER_IMAGE" -lc \
"if command -v docker >/dev/null 2>&1; then echo Unexpected Docker CLI >&2; exit 1; fi; printf 'package\\tversion\\n'; dpkg-query -W -f='\${binary:Package}\\t\${Version}\\n' | LC_ALL=C sort" \
> evidence/dapper-dpkg.tsv
cp go.mod go.sum vendor/modules.txt evidence/
- name: Scan source, product, and build image
shell: bash
run: |
set -euo pipefail
docker pull "$TRIVY_IMAGE"
trivy_cache="$RUNNER_TEMP/trivy-cache"
source_tree="$(mktemp -d)"
trap 'rm -rf "$source_tree"' EXIT
mkdir -p "$trivy_cache"
git archive --format=tar HEAD | tar -xf - -C "$source_tree"
docker run --rm \
-v "$source_tree:/scan:ro" \
-v "$PWD/evidence:/evidence" \
-v "$trivy_cache:/root/.cache/trivy" \
"$TRIVY_IMAGE" fs \
--scanners vuln,secret --format json \
--output /evidence/source-security-raw.json /scan
docker run --rm \
-v "$source_tree:/scan:ro" \
-v "$PWD/evidence:/evidence" \
-v "$trivy_cache:/root/.cache/trivy" \
"$TRIVY_IMAGE" fs \
--scanners vuln,secret \
--vex /scan/security/openvex.json --show-suppressed --format json \
--output /evidence/source-security.json /scan
docker run --rm \
-v "$PWD:/work:ro" \
-v "$PWD/evidence:/evidence" \
-v "$trivy_cache:/root/.cache/trivy" \
"$TRIVY_IMAGE" rootfs \
--scanners vuln,secret --format json \
--output /evidence/product-security-raw.json /evidence/product
docker run --rm \
-v "$PWD:/work:ro" \
-v "$PWD/evidence:/evidence" \
-v "$trivy_cache:/root/.cache/trivy" \
"$TRIVY_IMAGE" rootfs \
--scanners vuln,secret --vex /work/security/openvex.json --format json \
--output /evidence/product-security.json /evidence/product
docker run --rm \
-v /var/run/docker.sock:/var/run/docker.sock \
-v "$PWD:/work" -w /work \
-v "$trivy_cache:/root/.cache/trivy" \
"$TRIVY_IMAGE" image \
--scanners vuln,secret --list-all-pkgs --format json \
--output /work/evidence/dapper-image-raw.json "$DAPPER_IMAGE"
docker run --rm --entrypoint python3 "$DAPPER_IMAGE" \
/opt/python-security/scripts/check-pip-vendor-slice.py \
> evidence/pip-vendor-proof.json
python3 scripts/pip-security/test-pip-vex.py
python3 scripts/pip-security/filter-pip-vendor-findings.py \
--raw evidence/dapper-image-raw.json --proof evidence/pip-vendor-proof.json \
--output evidence/dapper-image-applicable.json --vex evidence/pip-vendor.openvex.json \
--subject "$(docker image inspect --format '{{.Id}}' "$DAPPER_IMAGE")"
bash .github/scripts/check-builder-header-boundary.sh "$DAPPER_IMAGE" > evidence/dapper-header-boundary.json
python3 .github/scripts/test_builder_vulnerability_policy.py
python3 .github/scripts/enforce-builder-vulnerabilities.py < evidence/dapper-image-applicable.json |
tee evidence/dapper-full-severity-policy.txt
jq -r '
.Results[]?.Vulnerabilities[]?
| select(.Severity == "CRITICAL" or .Severity == "HIGH")
| [.VulnerabilityID, .PkgIdentifier.PURL]
| @tsv
' evidence/dapper-image-applicable.json | LC_ALL=C sort -u \
> /tmp/dapper-critical-high.tsv
grep -F $'build\tCGO_ENABLED=0' evidence/product-go-version.txt >/dev/null
if grep -Eq '^linux-(image|modules)([-:]|[[:space:]])' evidence/dapper-dpkg.tsv; then
printf 'Unexpected Linux kernel runtime package in Dapper image\n' >&2
exit 1
fi
dapper_unreviewed="$(jq '[
.Results[]?.Vulnerabilities[]?
| select(.Severity == "CRITICAL" or .Severity == "HIGH")
| select(.PkgName != "linux-libc-dev" or ((.FixedVersion // "") != ""))
] | length' evidence/dapper-image-applicable.json)"
test "$dapper_unreviewed" -eq 0
jq -n \
--slurpfile report evidence/dapper-image-applicable.json \
--arg author 'PastureStack Security' \
--arg timestamp "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
--arg impact 'linux-libc-dev supplies user-space kernel API headers only in the disposable build and race-test image. The package is not copied into the CGO-disabled product binary, so the vulnerable kernel implementation is not shipped.' \
'{
"@context":"https://openvex.dev/ns/v0.2.0",
"@id":("https://github.com/PastureStack/compose-cli/security/dapper/" + $timestamp),
author:$author,
timestamp:$timestamp,
version:1,
statements:[
$report[0].Results[]?.Vulnerabilities[]?
| select((.Severity == "CRITICAL" or .Severity == "HIGH") and .PkgName == "linux-libc-dev" and ((.FixedVersion // "") == ""))
| {
vulnerability:{name:.VulnerabilityID},
products:[{"@id":.PkgIdentifier.PURL}],
status:"not_affected",
justification:"vulnerable_code_not_present",
impact_statement:$impact
}
]
}' > evidence/dapper.openvex.json
jq -r '
.statements[]
| .vulnerability.name as $id
| .products[]
| [$id, .["@id"]]
| @tsv
' security/openvex.json | LC_ALL=C sort -u > /tmp/source-vex.tsv
for raw_report in evidence/source-security-raw.json evidence/product-security-raw.json; do
jq -r '
.Results[]?.Vulnerabilities[]?
| [.VulnerabilityID, .PkgIdentifier.PURL]
| @tsv
' "$raw_report" | LC_ALL=C sort -u > /tmp/source-raw-findings.tsv
diff -u /tmp/source-vex.tsv /tmp/source-raw-findings.tsv
done
test "$(jq '.statements | length' evidence/dapper.openvex.json)" \
-eq "$(wc -l < /tmp/dapper-critical-high.tsv)"
docker run --rm \
-v "$source_tree:/scan:ro" \
-v "$PWD/evidence:/evidence" \
-v "$trivy_cache:/root/.cache/trivy" \
"$TRIVY_IMAGE" fs \
--format cyclonedx --output /evidence/source.cdx.json /scan
docker run --rm \
-v "$PWD/evidence:/evidence" \
-v "$trivy_cache:/root/.cache/trivy" \
"$TRIVY_IMAGE" rootfs \
--format cyclonedx --output /evidence/product.cdx.json /evidence/product
- name: Summarize and enforce candidate security
shell: bash
run: |
set -euo pipefail
count() {
jq "$2" "$1"
}
source_secrets=$(count evidence/source-security.json '[.Results[]?.Secrets[]?] | length')
source_applicable=$(count evidence/source-security.json '[.Results[]?.Vulnerabilities[]? | select(.Status != "not_affected")] | length')
source_critical=$(count evidence/source-security.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length')
source_high=$(count evidence/source-security.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length')
product_secrets=$(count evidence/product-security.json '[.Results[]?.Secrets[]?] | length')
product_critical=$(count evidence/product-security.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length')
product_high=$(count evidence/product-security.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length')
product_applicable=$(count evidence/product-security.json '[.Results[]?.Vulnerabilities[]? | select(.Status != "not_affected")] | length')
dapper_raw_critical=$(count evidence/dapper-image-applicable.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length')
dapper_raw_high=$(count evidence/dapper-image-applicable.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length')
dapper_reviewed=$(count evidence/dapper.openvex.json '.statements | length')
dapper_unreviewed=$(count evidence/dapper-image-applicable.json '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL" or .Severity == "HIGH") | select(.PkgName != "linux-libc-dev" or ((.FixedVersion // "") != ""))] | length')
dapper_secrets=$(count evidence/dapper-image-applicable.json '[.Results[]?.Secrets[]?] | length')
{
printf 'source_secrets=%s\n' "$source_secrets"
printf 'source_applicable=%s\n' "$source_applicable"
printf 'source_critical=%s\n' "$source_critical"
printf 'source_high=%s\n' "$source_high"
printf 'product_secrets=%s\n' "$product_secrets"
printf 'product_critical=%s\n' "$product_critical"
printf 'product_high=%s\n' "$product_high"
printf 'product_applicable=%s\n' "$product_applicable"
printf 'dapper_raw_critical=%s\n' "$dapper_raw_critical"
printf 'dapper_raw_high=%s\n' "$dapper_raw_high"
printf 'dapper_reviewed_unfixed_linux_libc_dev=%s\n' "$dapper_reviewed"
printf 'dapper_unreviewed_critical_high=%s\n' "$dapper_unreviewed"
printf 'dapper_secrets=%s\n' "$dapper_secrets"
} | tee evidence/security-summary.txt
test "$source_secrets" -eq 0
test "$source_applicable" -eq 0
test "$source_critical" -eq 0
test "$source_high" -eq 0
test "$product_secrets" -eq 0
test "$product_critical" -eq 0
test "$product_high" -eq 0
test "$product_applicable" -eq 0
test "$dapper_unreviewed" -eq 0
test "$dapper_reviewed" -eq "$((dapper_raw_critical + dapper_raw_high))"
test "$dapper_secrets" -eq 0
- name: Upload review evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: compose-cli-security-${{ github.sha }}
path: evidence/
if-no-files-found: error
retention-days: 30
- name: Clean runner resources
if: always()
shell: bash
run: |
set +e
docker rm -f $(docker ps -aq --filter ancestor="$DAPPER_IMAGE") 2>/dev/null
docker image rm -f "$DAPPER_IMAGE" "$TRIVY_IMAGE" 2>/dev/null
docker builder prune --all --force >/dev/null 2>&1
rm -rf bin build dist evidence/product