Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
136 commits
Select commit Hold shift + click to select a range
6fbe8b5
fix: verify clean workspace installs
jusso-dev Jul 26, 2026
a2f9c5c
feat: polish the Muster workspace UI
jusso-dev Jul 26, 2026
b89979b
feat: apply Muster branding everywhere
jusso-dev Jul 26, 2026
e4570a4
feat: finish production task delegation
jusso-dev Jul 26, 2026
810be14
feat: govern durable agent execution
jusso-dev Jul 26, 2026
a55f7d3
fix: send messages on insecure homelab origins
jusso-dev Jul 26, 2026
a0659e1
feat: deliver durable enterprise room messaging
jusso-dev Jul 26, 2026
5a91f4e
test: proxy deployed browser checks through SSH
jusso-dev Jul 26, 2026
e584970
feat: complete enterprise room collaboration
jusso-dev Jul 26, 2026
22e8dc4
test: verify governed homelab evidence uploads
jusso-dev Jul 26, 2026
7a279c0
feat(rooms): complete enterprise governance parity (#23)
jusso-dev Jul 26, 2026
2535624
fix(rooms): support governed actions on LAN hosts (#23)
jusso-dev Jul 26, 2026
ae148b6
fix(rooms): resolve dynamic room identity before loading (#23)
jusso-dev Jul 26, 2026
25ea2d9
fix(rooms): parse archived query flags explicitly (#23)
jusso-dev Jul 26, 2026
03ef20f
feat(integrations): add governed REST connector framework (#13)
jusso-dev Jul 26, 2026
b3805b1
test(integrations): route connector smoke per environment (#13)
jusso-dev Jul 26, 2026
558f9a5
fix(integrations): support Node 24 pinned DNS lookups (#13)
jusso-dev Jul 26, 2026
260a028
test(e2e): assert stable room display headings (#13)
jusso-dev Jul 26, 2026
a87dafa
test(integrations): execute Defender connector smoke (#13)
jusso-dev Jul 26, 2026
f480e3a
feat(integrations): govern Tawny and Kelpie actions (#10)
jusso-dev Jul 26, 2026
eb3100e
test(integrations): isolate anonymous auth proof (#10)
jusso-dev Jul 26, 2026
ea71c6c
test(ci): scope clean-install message assertion (#10)
jusso-dev Jul 26, 2026
fe99021
feat(tasks): make creation retry-safe (#10)
jusso-dev Jul 26, 2026
e743073
test(homelab): prove critical recovery flows (#10)
jusso-dev Jul 26, 2026
6c7ddc8
test(homelab): await refresh recovery request (#10)
jusso-dev Jul 26, 2026
ce2699d
test(homelab): await retry reconciliation (#10)
jusso-dev Jul 26, 2026
9ce4016
test(homelab): target explicit send control (#10)
jusso-dev Jul 26, 2026
091dc9b
fix(authz): conceal room membership boundaries (#10)
jusso-dev Jul 26, 2026
47b4759
test(homelab): select visible mobile room link (#10)
jusso-dev Jul 26, 2026
be780e9
test(homelab): keep remote health format atomic (#10)
jusso-dev Jul 26, 2026
b89e12b
test(homelab): prove signed-out sessions fail closed (#10)
jusso-dev Jul 26, 2026
ee4d1bc
test(ci): align assertions with current shell (#10)
jusso-dev Jul 26, 2026
a74526a
test(ci): await clean-install message durability (#10)
jusso-dev Jul 26, 2026
cb8d279
feat(security): add bounded observation redaction
jusso-dev Jul 26, 2026
55369ec
feat(security): redact agent observation boundaries
jusso-dev Jul 26, 2026
cb5e5ea
feat(agents): model freshness-aware readiness evidence
jusso-dev Jul 26, 2026
d5a0d41
feat(agents): persist and enforce runtime readiness
jusso-dev Jul 26, 2026
c9721b7
feat(agents): show readiness before delegation
jusso-dev Jul 26, 2026
c5af1df
feat(rooms): show live multi-agent activity
jusso-dev Jul 26, 2026
9ad9388
feat(tasks): show completed agent handoffs
jusso-dev Jul 26, 2026
b961f70
feat(rooms): export review-ready threads
jusso-dev Jul 26, 2026
2df08b4
feat(search): add scoped Slack-style filters
jusso-dev Jul 26, 2026
b4fcab2
test(search): harden live filter browser flow
jusso-dev Jul 26, 2026
235ef98
feat(rooms): add governed visual reaction packs
jusso-dev Jul 26, 2026
e900cb6
ci: provision object storage for browser tests
jusso-dev Jul 26, 2026
24ed0d8
fix(web): include Sharp native runtime assets
jusso-dev Jul 26, 2026
6d82ec4
fix(rooms): revalidate governed reaction assets
jusso-dev Jul 26, 2026
3f55232
feat(agents): ship governed Jessie threat hunts
jusso-dev Jul 26, 2026
2661be7
ci: enforce Jessie hunt end-to-end proof
jusso-dev Jul 27, 2026
82d7b79
test(agents): prove Jessie on homelab integrations
jusso-dev Jul 27, 2026
e2ed4c6
fix(agents): preserve Jessie source capabilities in demo seed
jusso-dev Jul 27, 2026
c046b8c
fix(agents): normalize Codex output schemas
jusso-dev Jul 27, 2026
a3c7a88
fix(agents): bind Jessie enrichment to linked case
jusso-dev Jul 27, 2026
e89eaae
fix(agents): guarantee linked-case enrichment proposals
jusso-dev Jul 27, 2026
8ff92a2
test(agents): stabilize homelab connector proof
jusso-dev Jul 27, 2026
e0878cb
feat(agents): ship governed Alfie research
jusso-dev Jul 27, 2026
015c631
test(agents): prove Alfie governed research flow
jusso-dev Jul 27, 2026
179026f
fix(agents): harden Alfie research execution
jusso-dev Jul 27, 2026
be72290
feat(agents): add governed Parker reports
jusso-dev Jul 27, 2026
f6fbe09
feat(reports): add Parker review and version flow
jusso-dev Jul 27, 2026
ca6147b
fix(reports): harden Parker report scope
jusso-dev Jul 27, 2026
3e6acd9
fix(reports): scope Parker reports and retries
jusso-dev Jul 27, 2026
58f3f17
feat(reports): schedule governed Parker tasks
jusso-dev Jul 27, 2026
fb96551
feat(reports): add Parker schedule settings
jusso-dev Jul 27, 2026
8599401
refactor(reports): isolate Parker scheduler
jusso-dev Jul 27, 2026
9e9d2f7
test(reports): cover Parker scheduler durability
jusso-dev Jul 27, 2026
c858846
fix(reports): validate Parker schedules
jusso-dev Jul 27, 2026
f58bf3a
feat(reports): expand Parker schedule controls
jusso-dev Jul 27, 2026
fd27145
feat(reports): deliver approved SMTP reports
jusso-dev Jul 27, 2026
87d9e4d
feat(reports): execute Parker reports asynchronously
jusso-dev Jul 27, 2026
cd84364
fix(reports): harden Parker replay and delivery
jusso-dev Jul 27, 2026
e13bb3b
fix(reports): route email approvals to Parker delivery
jusso-dev Jul 27, 2026
a37ea88
chore(deps): roll up safe dependency updates (#35)
jusso-dev Jul 27, 2026
52c8a64
docs: align README with tested product reality (#38)
jusso-dev Jul 27, 2026
679bd82
Land governed agents, Slack, and homelab release (#42)
jusso-dev Jul 27, 2026
f5cc316
fix(slack): accept Socket Mode hello frames
jusso-dev Jul 27, 2026
ff45dfd
Merge pull request #43 from jusso-dev/fix/slack-socket-hello
jusso-dev Jul 27, 2026
90ce6a8
fix(slack): accept workspace OAuth responses
jusso-dev Jul 27, 2026
6690da4
Merge pull request #44 from jusso-dev/fix/slack-oauth-null-enterprise
jusso-dev Jul 27, 2026
146d19f
Preserve Slack harness context
jusso-dev Jul 27, 2026
f44fdc4
Merge pull request #51 from jusso-dev/agent/preserve-slack-harness-co…
jusso-dev Jul 27, 2026
0872ac3
Allow scoped HTTP homelab connectors
jusso-dev Jul 27, 2026
0f83986
Merge pull request #52 from jusso-dev/agent/allow-homelab-http-connec…
jusso-dev Jul 27, 2026
a80620b
Make agent heartbeats idempotent
jusso-dev Jul 27, 2026
496cc09
Merge pull request #53 from jusso-dev/agent/idempotent-agent-heartbeats
jusso-dev Jul 27, 2026
3ac2f2c
feat(mcp): remote Muster MCP endpoint for Hermes (#72)
jusso-dev Jul 28, 2026
4140106
fix(mcp): address review findings on #72 (auth, safety, correctness)
jusso-dev Jul 28, 2026
b1e62be
fix(seed): deconflict demo-mode seed from bootstrap fixtures
jusso-dev Jul 28, 2026
09ab17c
fix(mcp): address CodeRabbit follow-up nitpicks on integration tests
jusso-dev Jul 28, 2026
a36896f
ci(security): capture trivy sarif/sbom even when the scan fails
jusso-dev Jul 28, 2026
d16f816
fix(deps): bump MCP SDK to fix @hono/node-server path-traversal advisory
jusso-dev Jul 28, 2026
f776d6c
fix(ci): disable provenance/SBOM when loading PR images
jusso-dev Jul 28, 2026
820eef1
Merge pull request #82 from jusso-dev/claude/issue-72-hermes-mcp-2026…
jusso-dev Jul 28, 2026
33e57ef
fix: address CodeRabbit findings from PR #41
jusso-dev Jul 28, 2026
16d575b
Merge pull request #83 from jusso-dev/fix/coderabbit-pr41-followups
jusso-dev Jul 28, 2026
38c9408
feat(mcp): approval-gated Kelpie write proposals for Hermes (#84)
jusso-dev Jul 28, 2026
f2f85de
feat(mcp): governed operational knowledge for Hermes (#71) (#85)
jusso-dev Jul 28, 2026
71fb7de
docs(integrations): separate Kelpie mock vs live certification (#86)
jusso-dev Jul 28, 2026
0e624ae
feat(skills): versioned Hermes skill packs and policy bundle (#73) (#87)
jusso-dev Jul 28, 2026
740bab9
feat(mcp): invocation list and bounded audit export (#77) (#88)
jusso-dev Jul 28, 2026
d1aa339
feat(mcp): governed missions for Hermes cron and delegation (#76) (#89)
jusso-dev Jul 28, 2026
318294f
feat(ops): Hermes MCP runbook, admin installation API, Node 26 deferr…
jusso-dev Jul 28, 2026
645bc4a
chore(runtime): Node 26 runtime and type cutover (#34) (#91)
jusso-dev Jul 28, 2026
f9c2f3a
docs: reframe README as Hermes MCP control plane
jusso-dev Jul 28, 2026
70dc6d8
feat(ops): E2E homelab bootstrap for Kelpie, Slack, and Hermes
jusso-dev Jul 28, 2026
29bbcda
fix(slack): route Hey Jessie / talk to Alfie to the right agent
jusso-dev Jul 28, 2026
660afc4
docs(ops): document natural Slack agent addressing forms
jusso-dev Jul 28, 2026
761634f
fix(slack): post as Parker/Jessie/Alfie via chat:write.customize
jusso-dev Jul 28, 2026
146ef71
fix(slack): avoid shadowing identity when setting agent presentation
jusso-dev Jul 28, 2026
a9cb47a
feat(web): ops control-plane health dashboard
jusso-dev Jul 29, 2026
baaf3c2
feat(ops): strip chat UI, Slack pack intro on channel join
jusso-dev Jul 29, 2026
21e396f
docs: add Muster governed AI operations hero to README
jusso-dev Jul 29, 2026
1fc913c
docs(readme): document Parker, Jessie, and Alfie pack
jusso-dev Jul 29, 2026
f3d349c
Update README.md
jusso-dev Jul 29, 2026
2aaf24f
feat(integrations): add Brolga as a governed connector (#92)
jusso-dev Jul 29, 2026
ffff990
feat(web): Security Company OS foundation (#93)
jusso-dev Jul 29, 2026
ded9a5c
fix(web): real ops board, no fixture data, Guides (#94)
jusso-dev Jul 29, 2026
c398889
feat(agents): pack handoff v1 and real Teams/Capabilities screens
jusso-dev Jul 30, 2026
6de5d72
fix(database): grant agents.handoff to the bootstrap administrator
jusso-dev Jul 30, 2026
3a275ef
fix(agents): reject self-handoff and unknown-agent handoffs at the AP…
jusso-dev Jul 30, 2026
15d7a14
fix(web): make Settings navigate to real surfaces instead of dead but…
jusso-dev Jul 30, 2026
9531f9a
feat(web): create and dispatch agent work from the board, unify OS la…
jusso-dev Jul 30, 2026
cdc2060
fix(agent-gateway): settle plainly delegated tasks when their run ends
jusso-dev Jul 30, 2026
e7a0a78
feat(web): read agent run results from the Operations board (#96)
jusso-dev Jul 30, 2026
5d549dd
fix(web): remove dead controls that teach the app is broken (#95)
jusso-dev Jul 30, 2026
c89c584
fix(web): stop showing a fake team structure on Teams (#97)
jusso-dev Jul 30, 2026
937372a
chore(web): delete unreachable legacy room and search UI (#98)
jusso-dev Jul 30, 2026
763e9d4
fix: close capability drift, missions dead end, audit verifier usabil…
jusso-dev Jul 30, 2026
1caaea9
fix(web): trim phantom agent tabs and make run detail explain failure…
jusso-dev Jul 30, 2026
addb93d
fix(web): close expired approvals and retry stuck agent runs (#100)
jusso-dev Jul 30, 2026
bd65945
fix(web): raise the type scale so the portal is readable (#102)
jusso-dev Jul 30, 2026
2d38633
fix(web): lift the one sentence the type sweep missed (#103)
jusso-dev Jul 30, 2026
3b96c9a
feat(web): real Tools, Rooms, and Permissions panels on agent detail …
jusso-dev Jul 30, 2026
ecce1d7
fix(web): archive tasks from the board and release provably dead runs…
jusso-dev Jul 30, 2026
5a68338
Redesign the Command dashboard and application chrome (#106)
jusso-dev Jul 30, 2026
b4862a2
chore(deps-dev): bump the development group with 2 updates
dependabot[bot] Aug 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
36 changes: 35 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,43 @@
# VCS / IDE
.git
.next
.github
.claude
.hermes
.turbo
.next
.playwright
.vscode
.idea

# Dependencies & build outputs (rebuilt inside the image)
node_modules
**/node_modules
**/dist
**/.next
**/coverage
**/tsconfig.tsbuildinfo

# Tests & local tooling (not needed for production image)
tests
**/*.test.ts
**/*.spec.ts
test-results
playwright-report
screenshots
vitest.config.ts
playwright*.config.ts

# Docs / non-runtime content (keep skills + deploy runtime)
docs
*.md
!skills/**/SKILL.md

# Env & secrets
.env
.env.*
!.env.example

# Logs / temp
*.log
tmp
.DS_Store
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ MUSTER_ORGANISATION_SLUG=muster
MUSTER_DEFAULT_TIMEZONE=UTC
KELPIE_BASE_URL=http://localhost:4011
KELPIE_API_TOKEN=mock-kelpie-token
# Brolga threat-intelligence context engine. The origin only — the /api/v1
# prefix comes from the query template. Brolga refuses to serve a reachable
# address without a token, so BROLGA_API_TOKEN is required, not optional.
BROLGA_BASE_URL=
BROLGA_API_TOKEN=
TAWNY_BASE_URL=http://localhost:4012
TAWNY_API_TOKEN=mock-tawny-token
BOWER_BASE_URL=http://localhost:4013
Expand All @@ -30,3 +35,6 @@ SENTINEL_CLIENT_ID=
SENTINEL_CLIENT_SECRET=
SENTINEL_WORKSPACE_ID=
MUSTER_MOCK_INTEGRATIONS=true
MUSTER_AGENT_GATEWAY_TOKEN=replace-with-at-least-32-random-bytes
# Optional comma-separated HTTPS origins for additional Alfie research feeds.
MUSTER_RESEARCH_ALLOWED_FEED_ORIGINS=
261 changes: 203 additions & 58 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,30 @@ on:
tags: ["v*.*.*"]
pull_request:

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

permissions:
contents: read

env:
CI: "true"
TURBO_TELEMETRY_DISABLED: "1"
DATABASE_URL: postgresql://muster:muster@127.0.0.1:5432/muster
REDIS_URL: redis://127.0.0.1:6379
BETTER_AUTH_SECRET: muster-ci-only-secret-at-least-32-characters
BETTER_AUTH_URL: http://127.0.0.1:3000
# Unit tests stub object-storage env; no MinIO service required for quality.
OBJECT_STORAGE_ENDPOINT: http://127.0.0.1:9000
OBJECT_STORAGE_REGION: us-east-1
OBJECT_STORAGE_BUCKET: muster-evidence
OBJECT_STORAGE_ACCESS_KEY: muster
OBJECT_STORAGE_SECRET_KEY: local-minio-secret

jobs:
# Fast path: unit, lint, typecheck, build, migration drift, shell installers.
# Postgres only — no MinIO/Redis containers (unit tests mock or skip).
quality:
runs-on: ubuntu-24.04
services:
Expand All @@ -30,113 +43,245 @@ jobs:
options: >-
--health-cmd "pg_isready -U muster -d muster"
--health-interval 5s --health-timeout 3s --health-retries 20
redis:
image: redis:8.2.1-bookworm
ports: ["6379:6379"]
options: >-
--health-cmd "redis-cli ping"
--health-interval 5s --health-timeout 3s --health-retries 20
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: pnpm/action-setup@f40ffcd9367d9f12939873eb1018b921a783ffaa # v4
with:
version: 11.17.0
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "24"
node-version: "26"
cache: pnpm
- name: Turbo cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: .turbo
key: turbo-${{ runner.os }}-node26-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-node26-${{ hashFiles('pnpm-lock.yaml', 'turbo.json') }}-
turbo-${{ runner.os }}-node26-
- run: pnpm install --frozen-lockfile
- name: Build database dependencies
run: pnpm exec turbo build --filter=@muster/database
- run: pnpm db:migrate
- run: pnpm db:seed
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm test:unit
- run: pnpm contracts:generate
- name: Verify committed migrations
# Pure shell tests: no install side-effects beyond repo files.
- run: pnpm test:release-homelab
- run: pnpm test:release-image
- run: pnpm test:homelab-installer
- run: pnpm skills:validate
- run: pnpm kelpie:certify-mock
- name: Database migrate + bootstrap + clean verify
run: |
pnpm exec turbo build --filter=@muster/database
pnpm db:migrate
pnpm db:bootstrap
pnpm db:verify-clean
- name: Lint, typecheck, unit tests, build
run: |
# Single turbo invocation reuses package graph and local turbo cache.
pnpm exec turbo run lint typecheck test build --concurrency=100%
- name: Verify contracts + migrations stay committed
run: |
pnpm contracts:generate
pnpm db:generate
git diff --exit-code -- packages/database/migrations
- run: pnpm build
- run: pnpm exec playwright install --with-deps chromium
- run: pnpm exec playwright test tests/muster.spec.ts --project=chromium
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: playwright-report
path: playwright-report
if-no-files-found: ignore
retention-days: 14
git diff --exit-code -- packages/contracts packages/database/migrations

# Secrets/audit/CodeQL only — full image rebuild lives in `container` (once).
release-security:
permissions:
contents: read
security-events: write
uses: ./.github/workflows/security.yml

# Build once per workflow; do not wait for quality on PRs (wall-clock parallel).
# On main/tag, promote still waits for quality + security via the promote job.
container:
runs-on: ubuntu-24.04
outputs:
image_ref: ${{ steps.image.outputs.ref }}
image_digest: ${{ steps.build.outputs.digest }}
permissions:
contents: read
packages: write
attestations: write
id-token: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Normalize image reference
id: image
shell: bash
run: |
image_ref="${REGISTRY}/${GITHUB_REPOSITORY,,}"
if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then
build_ref="${image_ref}:staging-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
else
build_ref="${image_ref}:verify-${GITHUB_SHA}"
fi
{
printf 'ref=%s\n' "$image_ref"
printf 'build_ref=%s\n' "$build_ref"
} >> "$GITHUB_OUTPUT"
- name: Log in to GitHub Container Registry
if: github.event_name == 'push'
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Generate image tags and OCI labels
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=ref,event=tag
type=sha
labels: |
org.opencontainers.image.title=Muster
org.opencontainers.image.description=Shared workspace for human and agent-driven security operations
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
- uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
# load:true (PR path) cannot export manifest lists. Provenance/SBOM
# attestations produce multi-artifact images, so only enable them on
# push where we publish to the registry instead of loading locally.
- uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
id: build
with:
context: .
push: ${{ github.event_name == 'push' }}
load: ${{ github.event_name == 'pull_request' }}
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
provenance: mode=max
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Verify container starts
tags: ${{ steps.image.outputs.build_ref }}
labels: |
org.opencontainers.image.title=Muster
org.opencontainers.image.description=Shared workspace for human and agent-driven security operations
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
provenance: ${{ github.event_name == 'push' && 'mode=max' || false }}
sbom: ${{ github.event_name == 'push' }}
cache-from: type=gha,scope=muster-image
cache-to: type=gha,mode=max,scope=muster-image
- name: Scan built image with Trivy
run: |
docker run --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
aquasec/trivy:0.67.2 image \
--exit-code 1 --ignore-unfixed --severity HIGH,CRITICAL \
"${{ steps.image.outputs.build_ref }}"
- name: Generate release SBOM
if: github.event_name == 'push'
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
with:
image: ${{ steps.image.outputs.ref }}@${{ steps.build.outputs.digest }}
format: cyclonedx-json
output-file: muster-sbom.cdx.json
upload-artifact: false
- name: Generate pull request SBOM
if: github.event_name == 'pull_request'
run: docker image inspect "${REGISTRY}/${IMAGE_NAME}:sha-${GITHUB_SHA::7}"
- name: Attest published image
uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
with:
image: ${{ steps.image.outputs.build_ref }}
format: cyclonedx-json
output-file: muster-sbom.cdx.json
upload-artifact: false
- name: Verify staged OCI application platform
if: github.event_name == 'push'
run: |
docker buildx imagetools inspect \
"${{ steps.image.outputs.ref }}@${{ steps.build.outputs.digest }}" --raw |
./scripts/verify-image-platform.sh
- name: Record immutable image evidence and checksums
if: github.event_name == 'push'
uses: actions/attest@36051bcae73b7c2a8a6945a48cbf80953c6baa35 # v4
run: |
printf '%s\n' \
"${{ steps.image.outputs.ref }}@${{ steps.build.outputs.digest }}" > muster-image.txt
sha256sum muster-sbom.cdx.json muster-image.txt > SHA256SUMS
- name: Upload release evidence
if: github.event_name == 'push'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: muster-release-evidence
path: |
muster-sbom.cdx.json
muster-image.txt
SHA256SUMS
- name: Verify container image exists
if: github.event_name == 'pull_request'
run: docker image inspect "${{ steps.image.outputs.build_ref }}"
- name: Attest published image provenance
if: github.event_name == 'push'
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4
with:
subject-name: ${{ steps.image.outputs.ref }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
- name: Attest published image SBOM
if: github.event_name == 'push'
uses: actions/attest@f7c74d28b9d84cb8768d0b8ca14a4bac6ef463e6 # v4
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-name: ${{ steps.image.outputs.ref }}
subject-digest: ${{ steps.build.outputs.digest }}
sbom-path: muster-sbom.cdx.json
push-to-registry: true
- name: Verify anonymous public pull
- name: Verify anonymous staging pull
if: github.event_name == 'push'
shell: bash
run: |
public_image="${REGISTRY}/${IMAGE_NAME,,}:sha-${GITHUB_SHA::7}"
docker logout "$REGISTRY"
public_image="${{ steps.image.outputs.build_ref }}"
docker logout "$REGISTRY" || true
docker pull "$public_image"
docker image inspect "$public_image" \
--format '{{.Os}}/{{.Architecture}}' | grep -x 'linux/amd64'
docker buildx imagetools inspect "$public_image" --raw |
./scripts/verify-image-platform.sh

# Merge gate: quality + container + security must all pass before promote.
promote:
if: github.event_name == 'push'
needs: [quality, container, release-security]
runs-on: ubuntu-24.04
concurrency:
group: muster-release-tags-${{ github.repository }}
cancel-in-progress: false
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_REF: ${{ needs.container.outputs.image_ref }}
IMAGE_DIGEST: ${{ needs.container.outputs.image_digest }}
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
- uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Generate release tags
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5
with:
images: ${{ env.IMAGE_REF }}
tags: |
type=ref,event=tag
type=sha,format=long
- name: Log in to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Promote verified digest to release tags
env:
RELEASE_TAGS: ${{ steps.meta.outputs.tags }}
run: |
promotion_policy() {
case "${1##*:}" in
"sha-${GITHUB_SHA}" | v*) printf 'immutable\n' ;;
*)
printf 'Unexpected release tag: %s\n' "$1" >&2
return 1
;;
esac
}
while IFS= read -r tag; do
[[ -n "$tag" ]] || continue
policy="$(promotion_policy "$tag")"
./scripts/promote-image-tag.sh \
"$tag" "${IMAGE_REF}@${IMAGE_DIGEST}" "$policy" check
done <<< "$RELEASE_TAGS"
while IFS= read -r tag; do
[[ -n "$tag" ]] || continue
policy="$(promotion_policy "$tag")"
./scripts/promote-image-tag.sh \
"$tag" "${IMAGE_REF}@${IMAGE_DIGEST}" "$policy" apply
done <<< "$RELEASE_TAGS"
- name: Publication summary
if: github.event_name == 'push'
run: |
{
echo "### Published container"
echo "\`${REGISTRY}/${IMAGE_NAME}\`"
echo "\`${IMAGE_REF}@${IMAGE_DIGEST}\`"
echo "The workflow verified an anonymous pull after publication. GHCR visibility is configured once at the package level and retained by subsequent releases."
} >> "$GITHUB_STEP_SUMMARY"
Loading
Loading