# Riposte SANDBOX image (RIP-410) - the Kali-based image DockerKaliSandbox launches
# per tool run. The riposte CLI never runs tools itself; it shells out to
# `docker run`/`docker exec` against THIS image (src/riposte/sandbox/docker_kali.py).
# Every tool binary and asset pack lives at the EXACT paths the wrappers read - the
# permissive set baked here, the copyleft set hydrated by pull-copyleft.sh (see below).
#
# This Dockerfile is the build-side of docker/sandbox/tools-manifest.toml (the single
# source of truth). The offline gate tests/sandbox/test_image_manifest.py cross-checks
# the two WITHOUT building. The actual build + registry push is a FOUNDER step (heavy,
# network, publish); pins live as ARGs for reproducible, no-runtime-update builds.
#
# Build (amd64 only - asset names are x86_64; build with --platform linux/amd64):
#   docker build --platform linux/amd64 -f docker/sandbox/Dockerfile -t riposte-sandbox:pinned .
# Use: the CLI defaults to riposte-sandbox:pinned (override with RIPOSTE_SANDBOX_IMAGE).
#
# SUPPLY CHAIN: every fetched binary is sha256-verified at build (sha256sum -c). The
# ProjectDiscovery tools are verified against their per-release checksums.txt; ffuf,
# gitleaks, and trivy against the upstream-published sha256 pinned below; kerbrute and
# yara-x publish no checksums, so a locally-computed sha256 must be pinned in the *_SHA256
# ARG before a real build (the build FAILS CLOSED if the pin is empty).
#
# COPYLEFT-CLEAN PUBLISHED IMAGE (RIP-410 supply-chain split): this Dockerfile bundles ONLY
# the permissively-licensed arsenal. The 11 copyleft / custom-license tools (nmap, hydra,
# medusa, sqlmap, whatweb, testssl.sh, suricata, searchsploit/exploitdb, hayabusa, chainsaw,
# volatility3) and their restricted rule packs (hayabusa-rules, chainsaw mapping, suricata
# rules) are NOT baked here. The operator obtains them at their own site by running
# docker/sandbox/pull-copyleft.sh ONCE (network-enabled, on top of this clean base) to build
# the full local riposte-sandbox:pinned - so the operator, not Riposte, redistributes each
# copyleft binary under its own license. That hydration is a one-time up-front step; the
# tool-run sandbox stays --network none / no-egress (docker_kali.py), so nothing is fetched
# per tool run. See docker/sandbox/pull-copyleft.sh and docs/THIRD_PARTY_LICENSES.md.

# --- stage 1: fetch pinned release binaries + rule/template data into /staging ---
FROM kalilinux/kali-rolling AS fetch

# ProjectDiscovery Go single-binaries (MIT). Asset filename drops the leading 'v'.
ARG NUCLEI_VERSION=3.9.0
ARG HTTPX_VERSION=1.9.0
ARG SUBFINDER_VERSION=2.14.0
ARG DNSX_VERSION=1.2.3
ARG NAABU_VERSION=2.3.3
ARG KATANA_VERSION=1.2.2
# Other GitHub-release binaries.
ARG FFUF_VERSION=2.1.0
ARG KERBRUTE_VERSION=1.0.3
ARG GITLEAKS_VERSION=8.30.1
ARG TRIVY_VERSION=0.71.1
ARG YARAX_VERSION=1.18.0

# Upstream-published sha256 of the downloaded archive/binary (see the manifest).
ARG FFUF_SHA256=fc2c82736c14dcbea4daf3d3cf3878c1c4773008ba45c2bc0fceba7d17b40bb5
ARG GITLEAKS_SHA256=551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb
ARG TRIVY_SHA256=3cbae37cd440cd8676e5ce9207fe460b5641c7579a17e9d00f8894928c41a88d
# These projects publish NO upstream checksums file. Pins = the GitHub release-asset digest
# cross-verified by independent recompute (yara-x, HIGH). kerbrute (no upstream checksum, 2019
# release) is anchored instead by the Go checksum transparency log (sum.golang.org, source
# module) + a multi-source IOC binary identification + a VirusTotal identity check (genuine
# ropnop/kerbrute, HackTool-class, no malware). Vetted + owner-approved 2026-06-29 (see
# riposte-eval-rounds/SHA-PIN-PROPOSAL.md). Residual accepted for --network-none lab use: no
# cryptographic source->binary link (no Go on the build host to do a go-install build-compare);
# close at production/publish time. The build below verifies each download against its pin and
# FAILS CLOSED on mismatch. Versions match the *_VERSION ARGs above (KERBRUTE 1.0.3 / YARAX
# 1.18.0; no silent bump). (hayabusa + chainsaw pins moved to docker/sandbox/pull-copyleft.sh,
# the operator hydration step, since those copyleft binaries are not baked into this image.)
ARG KERBRUTE_SHA256=710a9d2653c8bd3689e451778dab9daec0de4c4c75f900788ccf23ef254b122a
ARG YARAX_SHA256=d800afceea3120ca1842c64627a0acf59e71338c754897dbb0069823ac41c7e5

# Pinned rule/template refs (NO moving branches: tags or full commit SHAs only).
ARG NUCLEI_TEMPLATES_REF=v10.4.4
# Neo23x0/signature-base tag v2.0 (DRL-1.1). Pinned by commit SHA for reproducibility.
ARG YARA_RULES_SHA=6fb4bd73b7eea485a717d19df25eb76587d02573

RUN apt-get update && apt-get install -y --no-install-recommends \
        curl ca-certificates git unzip gzip tar && rm -rf /var/lib/apt/lists/*

WORKDIR /staging
RUN set -eux; \
    dl() { curl -fsSL "$1" -o "$2"; }; \
    # verify <file> <sha256>: fail the build unless the file matches (empty sha FAILS CLOSED).
    verify() { test -n "$2" || { echo "FATAL: no pinned sha256 for $1"; exit 1; }; echo "$2  $1" | sha256sum -c -; }; \
    mkdir -p bin; \
    # ProjectDiscovery: <tool>_<ver-no-v>_linux_amd64.zip, verified against the per-release
    # checksums.txt the project publishes (no hardcoded hashes to drift).
    for t in "nuclei:${NUCLEI_VERSION}" "httpx:${HTTPX_VERSION}" "subfinder:${SUBFINDER_VERSION}" "dnsx:${DNSX_VERSION}" "naabu:${NAABU_VERSION}" "katana:${KATANA_VERSION}"; do \
      name="${t%%:*}"; ver="${t##*:}"; base="https://github.com/projectdiscovery/${name}/releases/download/v${ver}"; \
      fn="${name}_${ver}_linux_amd64.zip"; \
      dl "${base}/${fn}" "/tmp/${fn}"; \
      # ProjectDiscovery's checksums filename is inconsistent across tools/versions:
      # most publish <tool>_<ver>_checksums.txt, but some (e.g. naabu) publish a
      # per-platform <tool>-linux-checksums.txt. Try both; the file lists every asset.
      dl "${base}/${name}_${ver}_checksums.txt" "/tmp/${name}_checksums.txt" \
        || dl "${base}/${name}-linux-checksums.txt" "/tmp/${name}_checksums.txt"; \
      ( cd /tmp && grep " ${fn}\$" "${name}_checksums.txt" | sha256sum -c - ); \
      unzip -o "/tmp/${fn}" "${name}" -d bin/; \
    done; \
    # ffuf: ffuf_<ver>_linux_amd64.tar.gz, binary 'ffuf'.
    dl "https://github.com/ffuf/ffuf/releases/download/v${FFUF_VERSION}/ffuf_${FFUF_VERSION}_linux_amd64.tar.gz" /tmp/ffuf.tgz; \
    verify /tmp/ffuf.tgz "${FFUF_SHA256}"; \
    tar -xzf /tmp/ffuf.tgz -C bin ffuf; \
    # kerbrute: single raw binary asset kerbrute_linux_amd64 (no version in name).
    dl "https://github.com/ropnop/kerbrute/releases/download/v${KERBRUTE_VERSION}/kerbrute_linux_amd64" bin/kerbrute; \
    verify bin/kerbrute "${KERBRUTE_SHA256}"; \
    # gitleaks: gitleaks_<ver>_linux_x64.tar.gz (x64, not amd64).
    dl "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" /tmp/gl.tgz; \
    verify /tmp/gl.tgz "${GITLEAKS_SHA256}"; \
    tar -xzf /tmp/gl.tgz -C bin gitleaks; \
    # trivy: trivy_<ver>_Linux-64bit.tar.gz.
    dl "https://github.com/aquasecurity/trivy/releases/download/v${TRIVY_VERSION}/trivy_${TRIVY_VERSION}_Linux-64bit.tar.gz" /tmp/trivy.tgz; \
    verify /tmp/trivy.tgz "${TRIVY_SHA256}"; \
    tar -xzf /tmp/trivy.tgz -C bin trivy; \
    # yara-x: asset keeps the 'v'; the binary INSIDE the tarball is 'yr' -> rename to yara-x.
    dl "https://github.com/VirusTotal/yara-x/releases/download/v${YARAX_VERSION}/yara-x-v${YARAX_VERSION}-x86_64-unknown-linux-gnu.tar.gz" /tmp/yarax.tgz; \
    verify /tmp/yarax.tgz "${YARAX_SHA256}"; \
    tar -xzf /tmp/yarax.tgz -C /tmp yr; \
    cp /tmp/yr bin/yara-x; \
    # NOTE: hayabusa (AGPL-3.0) + chainsaw (GPL-3.0) are copyleft and are NOT fetched here.
    # They are obtained by the operator hydration step docker/sandbox/pull-copyleft.sh so this
    # published image stays copyleft-clean. Their sha256 pins live in that script, fail-closed.
    chmod +x bin/*

# Pinned rule/template/mapping data (tags or commit SHAs; never a moving branch).
RUN set -eux; \
    git clone --depth 1 --branch "${NUCLEI_TEMPLATES_REF}" \
        https://github.com/projectdiscovery/nuclei-templates.git /staging/nuclei-templates; \
    # YARA: Neo23x0/signature-base (DRL-1.1), pinned to a commit SHA. Only its ./yara
    # subtree is the rule set; the rest (iocs, threatintel, README, .git) is not laid
    # down into the wrapper's rule path. Clone then checkout the exact SHA.
    git clone https://github.com/Neo23x0/signature-base.git /staging/signature-base; \
    git -C /staging/signature-base checkout "${YARA_RULES_SHA}"; \
    mkdir -p /staging/yara-rules; \
    cp -a /staging/signature-base/yara/. /staging/yara-rules/
# NOTE: the copyleft/restricted rule packs (hayabusa-rules DRL-1.1, chainsaw Sigma mapping
# GPL-3.0, suricata ET Open rules) are NOT fetched here. They travel with their copyleft
# tools and are laid down at the same /opt paths by docker/sandbox/pull-copyleft.sh.

# --- stage 2: the runtime sandbox image ---
FROM kalilinux/kali-rolling

# Distro-packaged tools (Kali rolling) - PERMISSIVE set only: zeek, hashcat, curl,
# seclists/wordlists, plus pipx and python for prowler. The copyleft distro tools
# (nmap, hydra, medusa, sqlmap, whatweb, testssl.sh, suricata, exploitdb/searchsploit)
# are deliberately NOT installed here; the operator adds them via pull-copyleft.sh so the
# published image is copyleft-clean.
RUN apt-get update && apt-get install -y --no-install-recommends \
        hashcat seclists wordlists curl ca-certificates pipx python3 \
    && rm -rf /var/lib/apt/lists/*
# zeek is currently UNINSTALLABLE on kali-rolling: its packaged build pins
# libc6 (< 2.38) but rolling ships 2.42, an unsatisfiable conflict. Install it in
# its OWN best-effort step (kept out of the line above so its upstream breakage
# does not abort the whole transaction), and log a LOUD warning if it is absent so
# the gap is visible, never silent (the zeek wrapper reports unavailability at
# runtime). Revisit when kali republishes zeek against current libc6, or install
# from zeek's own apt repo (download.opensuse.org/repositories/security:zeek).
RUN apt-get update; \
    if apt-get install -y --no-install-recommends zeek; then \
        echo "ZEEK_INSTALLED=yes"; \
    else \
        echo "WARNING: zeek is unavailable on kali-rolling (libc6 conflict); image built WITHOUT zeek"; \
    fi; \
    rm -rf /var/lib/apt/lists/*

# osquery (deb from pkg.osquery.io; provides 'osqueryi') + prowler (pipx into /opt so the
# console script lands on a baked, read-only-friendly PATH). volatility3 (VSL-1.0, copyleft/
# restricted) is NOT installed here; the operator adds it via pull-copyleft.sh.
ARG OSQUERY_VERSION=5.22.1
ARG PROWLER_VERSION=5.30.1
RUN set -eux; \
    curl -fsSL "https://pkg.osquery.io/deb/osquery_${OSQUERY_VERSION}-1.linux_amd64.deb" -o /tmp/osq.deb; \
    apt-get update && apt-get install -y --no-install-recommends /tmp/osq.deb && rm -rf /var/lib/apt/lists/* /tmp/osq.deb; \
    # prowler via pipx is BEST-EFFORT: kali-rolling's base python drifts ahead of what it pins
    # (prowler 5.x requires python <3.13 but rolling ships >=3.13), so a hard install aborts the
    # whole image on an upstream version conflict. Try it, log a LOUD warning if unavailable
    # (never silent), and keep building; the wrapper reports tool unavailability at runtime, and
    # a future build on a realigned base installs it again.
    ( PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install "prowler==${PROWLER_VERSION}" \
        || echo "WARNING: prowler unavailable on this base (python version drift); image built WITHOUT it" )

# Place the fetched PERMISSIVE release binaries on PATH (nuclei, httpx, subfinder, dnsx,
# naabu, katana, ffuf, kerbrute, gitleaks, trivy, yara-x). The copyleft fetched binaries
# (hayabusa, chainsaw) are added by pull-copyleft.sh, not copied here.
COPY --from=fetch /staging/bin/ /usr/local/bin/

# Lay down the PERMISSIVE rule/template data at the EXACT paths the wrappers read.
# /opt/yara-rules is the signature-base ./yara subtree only (clean rule set, no repo cruft).
# The restricted packs (hayabusa-rules -> /opt/hayabusa/rules, chainsaw mapping ->
# /opt/chainsaw/mappings, suricata rules -> /opt/suricata/rules) are laid down by
# pull-copyleft.sh at those same paths; only the ENV defaults below are baked here.
COPY --from=fetch /staging/nuclei-templates/ /opt/nuclei-templates/
COPY --from=fetch /staging/yara-rules/ /opt/yara-rules/

# Wordlists: map the wrapper paths onto the distro lists (seclists/rockyou). The seclists
# package periodically REORGANIZES its directory layout (a baked absolute path drifts and
# breaks the build), so resolve each list BY FILENAME under /usr/share/seclists and warn
# loudly if a name is gone (never a silent empty wordlist). rockyou is a stable distro path.
# ffuf's common.txt lands at the Debian-natural /usr/share/wordlists/common.txt so the
# wrapper default (_DEFAULT_WORDLIST), the manifest assets.ffuf_wordlist.path, and this
# baked path all agree (a fresh checkout with no override finds it); the cred/AD lists stay
# under /opt/wordlists.
RUN set -eux; \
    mkdir -p /opt/wordlists /usr/share/wordlists; \
    gzip -dc /usr/share/wordlists/rockyou.txt.gz > /opt/wordlists/rockyou.txt; \
    pick() { f="$(find /usr/share/seclists -type f -name "$1" 2>/dev/null | head -1)"; \
        if [ -n "$f" ]; then cp "$f" "$2"; else echo "WARNING: seclists '$1' not found; $2 absent"; fi; }; \
    pick common.txt /usr/share/wordlists/common.txt; \
    pick top-usernames-shortlist.txt /opt/wordlists/users.txt; \
    pick 10-million-password-list-top-1000.txt /opt/wordlists/passwords.txt; \
    pick names.txt /opt/wordlists/ad-users.txt; \
    pick subdomains-top1million-5000.txt /opt/wordlists/dns-subdomains.txt; \
    # trivy DB is network-fetched at build; BEST-EFFORT (a transient fetch failure must not
    # abort the whole image), with a loud warning if the cache is absent. The wrapper reports
    # unavailability at runtime; a future build re-fetches it. (The suricata ET Open ruleset is
    # a copyleft-set data pack fetched by pull-copyleft.sh, not here.)
    ( trivy --cache-dir /opt/trivy-cache image --download-db-only \
        || echo "WARNING: trivy DB download failed; baked cache absent" )

# Red-captured-artifact mount points (bind-mounted read-only by the CLI at run time);
# created empty so the paths resolve.
RUN mkdir -p /work/artifacts /work/repos /work/pcaps /work/evtx /work/images

# COPYLEFT HYDRATION SCRIPT. This published image is copyleft-clean: it ships ONLY the
# permissive arsenal above. The 11 copyleft / custom-license tools (nmap, hydra, medusa,
# sqlmap, whatweb, testssl.sh, suricata, searchsploit/exploitdb, hayabusa, chainsaw,
# volatility3) and their restricted rule packs are obtained by the OPERATOR at their own
# site by running this script ONCE, network-enabled, on top of this base, to assemble the
# full riposte-sandbox:pinned. It is a one-time up-front hydration, NOT a per-tool-run
# fetch: the tool-run sandbox is launched --network none / no-egress (docker_kali.py), so
# nothing is pulled at tool-run time. hayabusa + chainsaw are sha256-verified fail-closed in
# the script, exactly as the permissive fetched binaries are verified above. The ENV defaults
# below (RIPOSTE_HAYABUSA_RULES / RIPOSTE_CHAINSAW_RULES / RIPOSTE_CHAINSAW_MAPPING /
# RIPOSTE_SURICATA_RULES) stay baked here so the wrappers resolve; the script populates them.
COPY --chmod=0755 docker/sandbox/pull-copyleft.sh /opt/riposte/pull-copyleft.sh

# Bake the env defaults so a tool's emitted path always resolves inside the sandbox even
# if the CLI process did not export them. (RIPOSTE_VOL3_SYMBOLS is intentionally NOT set:
# the optional Windows symbol pack is not baked; the wrapper defaults it to unset.)
ENV RIPOSTE_NUCLEI_TEMPLATES=/opt/nuclei-templates \
    RIPOSTE_FFUF_WORDLIST=/usr/share/wordlists/common.txt \
    RIPOSTE_FEROX_WORDLIST=/usr/share/wordlists/common.txt \
    RIPOSTE_HYDRA_USERLIST=/opt/wordlists/users.txt \
    RIPOSTE_HYDRA_PASSLIST=/opt/wordlists/passwords.txt \
    RIPOSTE_MEDUSA_USERLIST=/opt/wordlists/users.txt \
    RIPOSTE_MEDUSA_PASSLIST=/opt/wordlists/passwords.txt \
    RIPOSTE_KERBRUTE_USERLIST=/opt/wordlists/ad-users.txt \
    RIPOSTE_DNSRECON_WORDLIST=/opt/wordlists/dns-subdomains.txt \
    RIPOSTE_HASHCAT_WORDLIST=/opt/wordlists/rockyou.txt \
    RIPOSTE_JOHN_WORDLIST=/opt/wordlists/rockyou.txt \
    RIPOSTE_YARAX_RULES=/opt/yara-rules \
    RIPOSTE_SURICATA_RULES=/opt/suricata/rules/suricata.rules \
    RIPOSTE_HAYABUSA_RULES=/opt/hayabusa/rules \
    RIPOSTE_CHAINSAW_RULES=/opt/hayabusa/rules \
    RIPOSTE_CHAINSAW_MAPPING=/opt/chainsaw/mappings/sigma-event-logs-all.yml
# Runtime scratch/cache: the rootfs is read-only with only /tmp (tmpfs) + /work writable,
# so point tool HOME/caches there. TRIVY_CACHE_DIR is the baked DB; HOME/XDG_CACHE_HOME
# give prowler/volatility3 a writable cache and zeek (which logs to cwd) a writable WORKDIR.
ENV TRIVY_CACHE_DIR=/opt/trivy-cache \
    HOME=/tmp \
    XDG_CACHE_HOME=/tmp/.cache

# The non-root uid:gid the sandbox execs tools as (matches docker_kali.py _SANDBOX_USER
# 1000:1000). No runtime package/rule updates and no egress are performed by the image.
RUN useradd -m -u 1000 sandbox
# Non-root by default even outside the riposte CLI (defense in depth; the CLI also passes
# --user 1000:1000 on docker run/exec). cwd is the writable tmpfs for tools that log to cwd.
USER 1000:1000
WORKDIR /tmp
CMD ["sleep", "infinity"]
