Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .github/build-matrix.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
"_comment": "Canonical registry of publishable Docker images. Single source of truth for the GitHub Actions workflows (detect-changes/select + _build-images.yml) and tests/test_docker_publish_matrix.py. 'service' is the Docker Hub image tag pushed as crhacky/blhackbox:<service>. 'paths' globs (an exact path, or a 'prefix/**' recursive prefix) decide which image a changed file rebuilds. 'scout' must stay a JSON boolean so the reusable workflow's `if: inputs.scout` evaluates correctly. NOTE: compose service 'hexstrike-bridge-mcp' uses image tag 'hexstrike-mcp'; 'hexstrike-ai' clones its upstream at build time and copies no repo files, so its only trigger is its Dockerfile.",
"components": [
{
"service": "kali-mcp",
"dockerfile": "docker/kali-mcp.Dockerfile",
"tag_prefix": "kali-mcp-",
"scout": false,
"paths": ["kali-mcp/**", "docker/kali-mcp.Dockerfile"]
},
{
"service": "wire-mcp",
"dockerfile": "docker/wire-mcp.Dockerfile",
"tag_prefix": "wire-mcp-",
"scout": false,
"paths": ["wire-mcp/**", "docker/wire-mcp.Dockerfile"]
},
{
"service": "screenshot-mcp",
"dockerfile": "docker/screenshot-mcp.Dockerfile",
"tag_prefix": "screenshot-mcp-",
"scout": false,
"paths": ["screenshot-mcp/**", "docker/screenshot-mcp.Dockerfile"]
},
{
"service": "hexstrike-ai",
"dockerfile": "docker/hexstrike-ai.Dockerfile",
"tag_prefix": "hexstrike-ai-",
"scout": false,
"paths": ["docker/hexstrike-ai.Dockerfile"]
},
{
"service": "hexstrike-mcp",
"dockerfile": "docker/hexstrike-mcp.Dockerfile",
"tag_prefix": "hexstrike-mcp-",
"scout": false,
"paths": ["hexstrike-mcp/**", "docker/hexstrike-mcp.Dockerfile"]
},
{
"service": "boaz-mcp",
"dockerfile": "docker/boaz-mcp.Dockerfile",
"tag_prefix": "boaz-mcp-",
"scout": false,
"paths": ["boaz-mcp/**", "docker/boaz-mcp.Dockerfile"]
},
{
"service": "claude-code",
"dockerfile": "docker/claude-code.Dockerfile",
"tag_prefix": "claude-code-",
"scout": false,
"paths": [
"docker/claude-code.Dockerfile",
"docker/claude-code-entrypoint.sh",
"CLAUDE.md",
".claude/skills/**"
]
}
]
}
113 changes: 113 additions & 0 deletions .github/workflows/_build-images.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: Reusable - Build and push one image

# Reusable workflow: builds a single Docker image and pushes it to Docker Hub.
# Called once per selected component (caller-side matrix) from:
# - ci.yml (per-component builds on push to main/master)
# - build-and-push.yml (release tags + manual dispatch)
# The caller passes `secrets: inherit` so DOCKERHUB_TOKEN resolves here.

on:
workflow_call:
inputs:
service:
description: "Image/service name — pushed as crhacky/blhackbox:<service>"
required: true
type: string
dockerfile:
description: "Path to the Dockerfile (build context is the repo root)"
required: true
type: string
tag_prefix:
description: "Metadata tag prefix, e.g. 'kali-mcp-'"
required: true
type: string
scout:
description: "Run the Docker Scout CVE scan + SARIF upload for this image"
required: false
type: boolean
default: false

env:
REGISTRY: docker.io
IMAGE_NAME: crhacky/blhackbox

jobs:
build:
runs-on: ubuntu-latest

permissions:
contents: read
packages: write
security-events: write

steps:
- name: Checkout repository
uses: actions/checkout@9f698171ed81b15d1823a05fc7211befd50c8ae0 # v6.0.3
with:
submodules: false

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

- name: Log in to Docker Hub
env:
DOCKERHUB_USERNAME: crhacky
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
max_attempts=5
attempt=1
backoff=3
while [ "$attempt" -le "$max_attempts" ]; do
echo "Docker login attempt ${attempt}/${max_attempts}..."
if echo "${DOCKERHUB_TOKEN}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin 2>&1; then
echo "Docker login succeeded on attempt ${attempt}"
exit 0
fi
if [ "$attempt" -lt "$max_attempts" ]; then
echo "Login failed, retrying in ${backoff}s..."
sleep "$backoff"
backoff=$((backoff * 2))
fi
attempt=$((attempt + 1))
done
echo "::error::Docker login failed after ${max_attempts} attempts"
exit 1

- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
prefix=${{ inputs.tag_prefix }},onlatest=true
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=${{ inputs.tag_prefix }}
type=raw,value=${{ inputs.tag_prefix }}latest,enable={{is_default_branch}}

- name: Build and push ${{ inputs.service }}
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
file: ${{ inputs.dockerfile }}
push: true
tags: crhacky/blhackbox:${{ inputs.service }}

- name: Docker Scout - Vulnerability Scan
uses: docker/scout-action@v1
if: ${{ always() && inputs.scout }}
with:
command: cves
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
sarif-file: sarif.output.json
summary: true
continue-on-error: true

- name: Upload SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: ${{ always() && inputs.scout }}
with:
sarif_file: sarif.output.json
continue-on-error: true
176 changes: 56 additions & 120 deletions .github/workflows/build-and-push.yml
Original file line number Diff line number Diff line change
@@ -1,139 +1,75 @@
name: Build and Push Docker Images

# Release + manual publishing. Per-component builds on push to main/master are
# handled by ci.yml (after tests pass). This workflow rebuilds the FULL image set
# for a clean release, or a single image on demand.
on:
# Auto-trigger: fires after CI workflow completes on main/master
workflow_run:
workflows: ["CI"]
types: [completed]
branches: [main, master]

# Auto-trigger: version tags bypass CI wait (tag push implies tested code)
# Version tags publish the complete, consistent image set.
push:
tags: ["v*"]

# Manual trigger
# Manual trigger: build everything, or a single chosen image.
workflow_dispatch:

env:
REGISTRY: docker.io
IMAGE_NAME: crhacky/blhackbox
inputs:
service:
description: "Image to build"
type: choice
default: all
options:
- all
- kali-mcp
- wire-mcp
- screenshot-mcp
- hexstrike-ai
- hexstrike-mcp
- boaz-mcp
- claude-code

jobs:
build-and-push:
# Resolve the build matrix from the canonical registry: every image for a tag /
# "all" dispatch, or just the one requested via workflow_dispatch.
select:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.select.outputs.matrix }}
any_changed: ${{ steps.select.outputs.any_changed }}

if: >
github.event_name == 'workflow_dispatch' ||
github.event_name == 'push' ||
(github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')
steps:
- name: Checkout repository
uses: actions/checkout@9f698171ed81b15d1823a05fc7211befd50c8ae0 # v6.0.3

- name: Select images to build
id: select
env:
SERVICE: ${{ github.event.inputs.service }}
run: |
set -euo pipefail
if [ -n "${SERVICE:-}" ] && [ "${SERVICE}" != "all" ]; then
out="$(python3 scripts/select_build_matrix.py --service "${SERVICE}")"
else
out="$(python3 scripts/select_build_matrix.py --all)"
fi
echo "Selection: ${out}" >&2
{
echo "matrix=$(echo "${out}" | jq -c '.matrix')"
echo "any_changed=$(echo "${out}" | jq -r '.any_changed')"
} >> "$GITHUB_OUTPUT"

call-build:
needs: select
if: needs.select.outputs.any_changed == 'true'
permissions:
contents: read
packages: write
security-events: write

strategy:
fail-fast: false
matrix:
include:
- service: kali-mcp
dockerfile: docker/kali-mcp.Dockerfile
tag_prefix: "kali-mcp-"
scout: false
- service: wire-mcp
dockerfile: docker/wire-mcp.Dockerfile
tag_prefix: "wire-mcp-"
scout: false
- service: screenshot-mcp
dockerfile: docker/screenshot-mcp.Dockerfile
tag_prefix: "screenshot-mcp-"
scout: false
- service: claude-code
dockerfile: docker/claude-code.Dockerfile
tag_prefix: "claude-code-"
scout: false
- service: hexstrike-ai
dockerfile: docker/hexstrike-ai.Dockerfile
tag_prefix: "hexstrike-ai-"
scout: false
- service: hexstrike-mcp
dockerfile: docker/hexstrike-mcp.Dockerfile
tag_prefix: "hexstrike-mcp-"
scout: false
- service: boaz-mcp
dockerfile: docker/boaz-mcp.Dockerfile
tag_prefix: "boaz-mcp-"
scout: false

steps:
- name: Checkout repository
uses: actions/checkout@9f698171ed81b15d1823a05fc7211befd50c8ae0 # v6.0.3
with:
submodules: false
ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || '' }}

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0

- name: Log in to Docker Hub
env:
DOCKERHUB_USERNAME: crhacky
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
run: |
max_attempts=5
attempt=1
backoff=3
while [ "$attempt" -le "$max_attempts" ]; do
echo "Docker login attempt ${attempt}/${max_attempts}..."
if echo "${DOCKERHUB_TOKEN}" | docker login -u "${DOCKERHUB_USERNAME}" --password-stdin 2>&1; then
echo "Docker login succeeded on attempt ${attempt}"
exit 0
fi
if [ "$attempt" -lt "$max_attempts" ]; then
echo "Login failed, retrying in ${backoff}s..."
sleep "$backoff"
backoff=$((backoff * 2))
fi
attempt=$((attempt + 1))
done
echo "::error::Docker login failed after ${max_attempts} attempts"
exit 1

- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
prefix=${{ matrix.tag_prefix }},onlatest=true
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix=${{ matrix.tag_prefix }}
type=raw,value=${{ matrix.tag_prefix }}latest,enable={{is_default_branch}}

- name: Build and push ${{ matrix.service }}
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
file: ${{ matrix.dockerfile }}
push: true
tags: crhacky/blhackbox:${{ matrix.service }}

- name: Docker Scout - Vulnerability Scan
uses: docker/scout-action@v1
if: ${{ always() && matrix.scout }}
with:
command: cves
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }}
sarif-file: sarif.output.json
summary: true
continue-on-error: true

- name: Upload SARIF to GitHub Security
uses: github/codeql-action/upload-sarif@v4
if: ${{ always() && matrix.scout }}
with:
sarif_file: sarif.output.json
continue-on-error: true
include: ${{ fromJSON(needs.select.outputs.matrix) }}
uses: ./.github/workflows/_build-images.yml
with:
service: ${{ matrix.service }}
dockerfile: ${{ matrix.dockerfile }}
tag_prefix: ${{ matrix.tag_prefix }}
scout: ${{ matrix.scout }}
secrets: inherit
Loading