# syntax=docker/dockerfile:1 # # Multi-stage build: `docker compose up -d --build` (or `docker build .`) produces # the complete image from source. No prebuilt binary in the build context is needed, # so a fresh `git clone` builds — dist/ is git-ignored and is no longer required. # # frontend (web) -> Go binary with embedded UI (build) -> runtime image # # The Go binary is produced by build.sh with the same flags the release workflow's # binaries job uses, so the image binary and the published release binary come from # one source. # ---------------------------------------------------------------- frontend --- # Pinned to the Node 22 line the CI/release workflows use. On the build platform # only; the frontend static export is architecture-independent. FROM --platform=$BUILDPLATFORM node:22-bookworm-slim AS web WORKDIR /src/web COPY web/package.json web/package-lock.json ./ RUN npm ci --ignore-scripts COPY web/ ./ RUN npm run build:static # ------------------------------------------------------------- Go binary --- # Pinned to the Go 1.26 line (matches go.mod and the release binaries job). The # binary is CGO-free, so this stage runs on the build platform and cross-compiles # to $TARGETARCH without QEMU. FROM --platform=$BUILDPLATFORM golang:1.26-bookworm AS build WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . # Reuse the frontend built above; build.sh skips its own frontend step. COPY --from=web /src/web/out ./server/webui/dist ARG ARTEX_VERSION=dev ARG TARGETOS ARG TARGETARCH # Same flags as the release binaries job (ARTEX_COMPRESS=0, -trimpath, # -X main.version), driven through build.sh so there is one build path. RUN ARTEX_SKIP_FRONTEND=1 \ ARTEX_SKIP_NPM_CI=1 \ ARTEX_COMPRESS=0 \ ARTEX_OUTPUT=/out/artex \ ARTEX_BUILD_VERSION="${ARTEX_VERSION}" \ bash ./build.sh --target "${TARGETOS:-linux}/${TARGETARCH:-amd64}" # ------------------------------------------------------- third-party tools --- # Web-recon toolbox (drost-community take 1, #100): pinned release binaries, staged # on the build platform and copied into /usr/local/bin of the runtime image so hunt # workers can run them directly through their Bash tool. Versions pinned per tool; # upgrade deliberately. Each archive is verified against the checksums file of its # own GitHub release (exact asset name, sha256). That file comes from the same # release channel as the archive, so it catches corrupted or swapped downloads and # moved tags, not a compromised publisher. sqlmap has no release binaries: it is a # shallow clone of a pinned tag, checked against the expected commit. FROM --platform=$BUILDPLATFORM debian:bookworm-slim AS tools ARG TARGETARCH ARG NUCLEI_VERSION=v3.11.1 ARG HTTPX_VERSION=v1.12.0 ARG SUBFINDER_VERSION=v2.17.0 ARG KATANA_VERSION=v1.8.0 ARG FFUF_VERSION=v2.3.0 ARG DALFOX_VERSION=v3.2.4 ARG SQLMAP_VERSION=1.9.9 ARG SQLMAP_COMMIT=cceda39208b5aad478de2c3f731df4239d9881f6 RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates curl git unzip \ && rm -rf /var/lib/apt/lists/* RUN set -eux; \ case "${TARGETARCH:-amd64}" in \ amd64) PD_ARCH=amd64; FFUF_ARCH=amd64; DALFOX_ARCH=x86_64 ;; \ arm64) PD_ARCH=arm64; FFUF_ARCH=arm64; DALFOX_ARCH=aarch64 ;; \ *) echo "unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ esac; \ mkdir -p /out; \ fetch() { base="$1"; asset="$2"; sums="$3"; name="$4"; \ rm -rf /tmp/t; mkdir -p /tmp/t/x; \ curl -fsSL "$base/$asset" -o "/tmp/t/$asset"; \ curl -fsSL "$base/$sums" -o /tmp/t/sums.txt; \ want="$(awk -v a="$asset" '$2 == a || $2 == "*" a { print $1 }' /tmp/t/sums.txt)"; \ if [ -z "$want" ]; then echo "no checksum entry for $asset" >&2; exit 1; fi; \ got="$(sha256sum "/tmp/t/$asset" | cut -d' ' -f1)"; \ if [ "$want" != "$got" ]; then echo "checksum mismatch for $asset: want $want got $got" >&2; exit 1; fi; \ case "$asset" in *.zip) unzip -q "/tmp/t/$asset" -d /tmp/t/x ;; *) tar -xzf "/tmp/t/$asset" -C /tmp/t/x ;; esac; \ find /tmp/t/x -type f -name "$name" -exec install -m 0755 {} /out/ \; ; \ [ -x "/out/$name" ] || { echo "$name not found in $asset" >&2; exit 1; }; \ rm -rf /tmp/t; }; \ gh="https://github.com"; \ fetch "$gh/projectdiscovery/nuclei/releases/download/$NUCLEI_VERSION" \ "nuclei_${NUCLEI_VERSION#v}_linux_${PD_ARCH}.zip" "nuclei_${NUCLEI_VERSION#v}_checksums.txt" nuclei; \ fetch "$gh/projectdiscovery/httpx/releases/download/$HTTPX_VERSION" \ "httpx_${HTTPX_VERSION#v}_linux_${PD_ARCH}.zip" "httpx_${HTTPX_VERSION#v}_checksums.txt" httpx; \ fetch "$gh/projectdiscovery/subfinder/releases/download/$SUBFINDER_VERSION" \ "subfinder_${SUBFINDER_VERSION#v}_linux_${PD_ARCH}.zip" "subfinder_${SUBFINDER_VERSION#v}_checksums.txt" subfinder; \ fetch "$gh/projectdiscovery/katana/releases/download/$KATANA_VERSION" \ "katana_${KATANA_VERSION#v}_linux_${PD_ARCH}.zip" "katana-${KATANA_VERSION#v}-checksums.txt" katana; \ fetch "$gh/ffuf/ffuf/releases/download/$FFUF_VERSION" \ "ffuf_${FFUF_VERSION#v}_linux_${FFUF_ARCH}.tar.gz" "ffuf_${FFUF_VERSION#v}_checksums.txt" ffuf; \ fetch "$gh/hahwul/dalfox/releases/download/$DALFOX_VERSION" \ "dalfox-${DALFOX_VERSION}-linux-${DALFOX_ARCH}-musl.tar.gz" "dalfox-${DALFOX_VERSION}-linux-${DALFOX_ARCH}-musl.tar.gz.sha256" dalfox; \ git clone --quiet --depth 1 --branch "$SQLMAP_VERSION" https://github.com/sqlmapproject/sqlmap /opt/sqlmap; \ commit="$(git -C /opt/sqlmap rev-parse HEAD)"; \ if [ "$commit" != "$SQLMAP_COMMIT" ]; then echo "sqlmap commit mismatch: want $SQLMAP_COMMIT got $commit" >&2; exit 1; fi; \ rm -rf /opt/sqlmap/.git # ---------------------------------------------------------------- runtime --- FROM python:3.12-slim-bookworm ARG TARGETARCH # Common tools: ripgrep / curl / vim, plus a set of recon staples (add/remove as needed). RUN apt-get update && apt-get install -y --no-install-recommends \ ca-certificates ripgrep curl wget vim git jq unzip xz-utils \ dnsutils iputils-ping netcat-openbsd inetutils-telnet whois nmap \ && rm -rf /var/lib/apt/lists/* # Node 22 from the official nodejs.org tarball, verified against its SHASUMS256.txt: # bookworm's apt nodejs is 18, Playwright requires >=20, and 20 is end-of-life. # 22 matches the CI and release workflows. ARG NODE_VERSION=22.23.4 RUN set -eux; \ case "${TARGETARCH:-amd64}" in \ amd64) NODE_ARCH=x64 ;; \ arm64) NODE_ARCH=arm64 ;; \ *) echo "unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ esac; \ base="https://nodejs.org/dist/v${NODE_VERSION}"; \ asset="node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz"; \ curl -fsSL "$base/$asset" -o "/tmp/$asset"; \ curl -fsSL "$base/SHASUMS256.txt" -o /tmp/SHASUMS256.txt; \ want="$(awk -v a="$asset" '$2 == a { print $1 }' /tmp/SHASUMS256.txt)"; \ if [ -z "$want" ]; then echo "no checksum entry for $asset" >&2; exit 1; fi; \ got="$(sha256sum "/tmp/$asset" | cut -d' ' -f1)"; \ if [ "$want" != "$got" ]; then echo "checksum mismatch for $asset: want $want got $got" >&2; exit 1; fi; \ tar -xJf "/tmp/$asset" -C /usr/local --strip-components=1 --no-same-owner; \ rm -f "/tmp/$asset" /tmp/SHASUMS256.txt; \ node --version; npm --version # Pinned, checksum-verified recon binaries and the sqlmap checkout from the tools stage. COPY --from=tools /out/ /usr/local/bin/ COPY --from=tools /opt/sqlmap /opt/sqlmap RUN set -eux; \ ln -s /opt/sqlmap/sqlmap.py /usr/local/bin/sqlmap; \ nuclei -version >/dev/null; httpx -version; subfinder -version; katana -version; \ ffuf -V >/dev/null; dalfox --version >/dev/null; sqlmap --version >/dev/null # bbot recursive recon orchestrator (#117): blacklanternsecurity/bbot, AGPL-3.0 # (same license as ARTEX; standalone CLI dependency, not linked code). Pinned # version, own venv to isolate from the image python. Module dependencies are # preinstalled (--install-all-deps) so scans never need pip at runtime, which # would fail under a program's egress allowlist. ARG BBOT_VERSION=3.0.2 RUN set -eux; \ python -m venv /opt/bbot; \ /opt/bbot/bin/pip install --no-cache-dir "bbot==${BBOT_VERSION}"; \ ln -s /opt/bbot/bin/bbot /usr/local/bin/bbot; \ bbot -y --install-all-deps; \ bbot --version >/dev/null; \ bbot --dry-run -t example.com -m wafw00f,paramminer_getparams,lightfuzz,sslcert,gowitness >/dev/null 2>&1 || true; \ bbot --dry-run -t example.com -m wafw00f,paramminer_getparams,lightfuzz,sslcert,gowitness --no-color >/tmp/bbot-dry.log 2>&1 || true; \ ! grep -qE "Installing|ERRR|CRITICAL" /tmp/bbot-dry.log || { cat /tmp/bbot-dry.log; exit 1; } # Preinstall Playwright MCP and CLI (globally) so no npx network download is needed at runtime. # @playwright/mcp: browser MCP runs directly via `npx @playwright/mcp` (installed globally, no -y/@latest needed). # @playwright/cli: provides playwright-cli, verified with --help right after install. # Then install playwright (browser management) and preinstall chromium plus its system # dependencies via --with-deps, so MCP/CLI work on first start inside the container # without downloading browsers over the network. All three are pinned; upgrade deliberately. ARG PLAYWRIGHT_MCP_VERSION=0.0.83 ARG PLAYWRIGHT_CLI_VERSION=0.1.22 ARG PLAYWRIGHT_VERSION=1.64.1 RUN npm install -g "@playwright/mcp@${PLAYWRIGHT_MCP_VERSION}" "@playwright/cli@${PLAYWRIGHT_CLI_VERSION}" "playwright@${PLAYWRIGHT_VERSION}" \ && playwright-cli --help \ && playwright install --with-deps chromium \ && rm -rf /var/lib/apt/lists/* WORKDIR /app # Binary compiled from source in the build stage above. COPY --from=build /out/artex /app/artex # Daemon startup script: relaunches the process after a crash and forwards SIGTERM # to artex — docker stop only signals PID 1; without forwarding, artex never receives it, # cannot shut down gracefully, and is hard-killed by SIGKILL after 10 seconds. COPY start.sh /app/start.sh RUN chmod +x /app/artex /app/start.sh && mkdir -p /app/keys COPY skills/ /app/skills/ # persistence points: data/ (SQLite, task files) and keys/ (jwt.key, via ARTEX_JWT_KEY_DIR) VOLUME ["/app/data", "/app/keys"] EXPOSE 8787 8788 ENTRYPOINT ["/app/start.sh"] CMD ["-addr", ":8787", "-proxy", ":8788"]