Scan in CI: a sandbox scorecard on every pull request¶
The ironctl scan audit also ships as a GitHub Action, so every
repository can render an IronClaw containment scorecard right in its pull
requests and gate merges on isolation posture. It is the same local, read-only,
credential-free grader; the action just installs ironctl, runs it against your
target, and posts the result as a sticky PR comment.
Add it to your workflow¶
name: Sandbox scorecard
on: [pull_request]
permissions:
contents: read
pull-requests: write # only needed for the sticky comment
jobs:
scan:
runs-on: ubuntu-latest
steps:
- name: Start the container you want to grade
run: docker run -d --name my-sandbox my-image:latest sleep 3600
- uses: IronSecCo/ironclaw@v1
with:
target: my-sandbox
min-score: 90 # omit / 0 = report-only, never blocks the check
IronSecCo/ironclaw@v1 is the Marketplace
listing (root action.yml). The subdir ref
IronSecCo/ironclaw/.github/actions/scan@v1 runs the exact same grader and stays
supported as a fallback — both point at one scan.sh, so they never diverge.
On the pull request you get a scorecard comment that updates in place on every push:
IronClaw containment scan:
my-sandboxscored 100/100 (grade A)¶
Dimension Verdict Score Non-root user (uid != 0) PASS
15/15 Dropped capabilities PASS
20/20 Seccomp profile PASS
15/15 Network isolation / egress PASS
15/15 Read-only root filesystem PASS
10/10 No docker.sock exposure PASS
15/15 No shared host namespaces PASS
10/10
Grade a compose service or a k8s manifest¶
The file modes need no Docker daemon — the action reads the file directly:
# a docker-compose service
- uses: IronSecCo/ironclaw@v1
with:
mode: compose
target: docker-compose.yml
service: app # required only if the file has >1 service
# a Kubernetes pod / workload manifest
- uses: IronSecCo/ironclaw@v1
with:
mode: k8s
target: deploy/pod.yaml
Inputs¶
| Input | Default | Description |
|---|---|---|
target |
(required) | Container name/id, or path to a compose/k8s file. |
mode |
container |
container, compose, or k8s. |
service |
"" |
Compose service name (when the file has more than one). |
min-score |
0 |
Fail the check below this score. 0 = report-only. |
policy-check |
false |
Policy-as-code gate (mode=k8s only): fail the check if the manifest violates the rules --emit-policy would generate. Needs no cluster or controller. Independent of min-score. |
comment |
true |
Post the scorecard as a sticky PR comment. |
badge |
false |
Write and upload the SVG badge as a build artifact. |
upload-sarif |
false |
Emit SARIF and upload it to GitHub code scanning (Security tab). Requires security-events: write. |
version |
latest |
ironctl release (latest or a tag like v0.1.252). |
github-token |
${{ github.token }} |
Token for the comment + release lookup. |
Outputs: score, grade, scorecard (path), badge-path, sarif-path, passed, and policy-passed.
Surface findings in the Security tab (SARIF)¶
Set upload-sarif: true and grant security-events: write, and the action
uploads a SARIF 2.1.0 log so every
failed isolation dimension shows up in your repo's Security > Code scanning
tab, deduped across runs, alongside CodeQL and any other scanner:
permissions:
contents: read
security-events: write # required to upload SARIF
pull-requests: write # only if you also keep the sticky comment
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: IronSecCo/ironclaw@v1
with:
mode: compose
target: docker-compose.yml
upload-sarif: true
upload-sarif defaults to false, so the action asks for no new permission
unless you opt in. Results are anchored at the scanned config file (with a line
region when derivable), and a clean 100/A target uploads zero findings. SARIF
emit is fail-open: a write failure never blocks the scan or its min-score gate.
The upload uses github/codeql-action/upload-sarif, pinned by commit SHA.
Report-only vs. gating¶
- Leave
min-scoreat0(the default) to run report-only: the scorecard is posted and the check always passes. Good for adopting the action without risk, or watching a score trend before you enforce it. - Set
min-score(for example90for an A) to gate: the check fails when the posture regresses below the bar. The comment is still posted first, so reviewers see exactly which dimension slipped.
How it works¶
The action downloads the released ironctl binary, runs ironctl scan against
your target, extracts the markdown scorecard, and posts it. The sticky comment
is keyed by mode + target, so re-runs update in place instead of spamming, and
multiple targets in one PR each keep their own comment. On non-PR events the
scorecard goes to the job summary instead.
For the file modes (compose / k8s) the comment also shows a delta versus
the base branch: the action fetches the base version of the scanned file via
the contents API, grades it, and adds a line such as
Δ vs base (main): +12 — base scored 88/100. Posture improved. so a reviewer
sees at a glance whether the change hardened or regressed the posture. A file
that is new on the PR is noted as such, and the whole delta step is fail-open —
if the base cannot be fetched or graded, the scorecard is still posted without a
delta line. container mode has no git base to compare against, so it shows no
delta.
Every comment also ends with a copy-paste README badge snippet and an "Add
this to your README" nudge, rendered offline by ironctl scan --badge-md. Paste
it once and your repo carries a live containment-grade badge that links back to
the scan receipt, turning a one-off PR check into a persistent, self-promoting
signal. The snippet lives inside the same sticky comment, so it is idempotent and
never double-posted.
Everything the scan command guarantees still holds: it is local and
read-only, it never talks to a control-plane, it needs no credentials, and it is
fail-closed. See .github/actions/scan
for the action source, and the scan reference for the dimension
weights and grade bands.