# ---------------------------------------------------------------------------
# Stage 1: gobuilder
# Compiles Go tools with exact version tags. Module and build caches are discarded
# after this stage, and only the resulting binaries are copied to the runtime.
# ---------------------------------------------------------------------------
FROM kalilinux/kali-rolling@sha256:c6d78f57ebfdd9fec428a8b04f74876e66fddd08dfd328c076224a552e7332a3 AS gobuilder

RUN apt-get update && \
    apt-get install -y --no-install-recommends kali-archive-keyring && \
    apt-get update && \
    apt-get install -y --no-install-recommends golang-go git ca-certificates

ENV GOBIN=/out/bin
RUN mkdir -p /out/bin && \
    go install -v github.com/projectdiscovery/httpx/cmd/httpx@v1.11.0 && \
    go install -v github.com/projectdiscovery/katana/cmd/katana@v1.7.0 && \
    go install -v github.com/projectdiscovery/cvemap/cmd/vulnx@v1.0.0 && \
    go install -v github.com/jaeles-project/gospider@v1.1.6 && \
    go install -v github.com/projectdiscovery/interactsh/cmd/interactsh-client@v1.3.1 && \
    go install -v golang.org/x/vuln/cmd/govulncheck@v1.7.0

# ---------------------------------------------------------------------------
# Stage 2: downloader
# Downloads direct binary release artifacts and verifies their cryptographic
# SHA-256 checksums before unpacking. No unverified remote installers are executed.
# ---------------------------------------------------------------------------
FROM kalilinux/kali-rolling@sha256:c6d78f57ebfdd9fec428a8b04f74876e66fddd08dfd328c076224a552e7332a3 AS downloader

RUN apt-get update && \
    apt-get install -y --no-install-recommends kali-archive-keyring && \
    apt-get update && \
    apt-get install -y --no-install-recommends curl ca-certificates tar

RUN set -eux; \
    mkdir -p /out/bin /out/pentester_bin /tmp/downloads; \
    ARCH="$(uname -m)"; \
    case "$ARCH" in \
        x86_64) \
            UV_URL="https://github.com/astral-sh/uv/releases/download/0.12.10/uv-x86_64-unknown-linux-gnu.tar.gz"; \
            UV_SHA="173d95a0c32d18c896c46ba6fafbf3cf9c14ab74b033f81b76c883ef492a976b"; \
            TRIVY_URL="https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-64bit.tar.gz"; \
            TRIVY_SHA="2ae6fe3ee734b7fdf11335663e18c75ea12dccc76062f09f164a3b0f8be4371a"; \
            TRUFFLEHOG_URL="https://github.com/trufflesecurity/trufflehog/releases/download/v3.95.9/trufflehog_3.95.9_linux_amd64.tar.gz"; \
            TRUFFLEHOG_SHA="f6d1106b85107d79527ed7a5b98b592beadd8b770dc3c9e8c1ad99e1b2cf127e"; \
            GITLEAKS_URL="https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz"; \
            GITLEAKS_SHA="551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb"; \
            CAIDO_URL="https://caido.download/releases/v0.56.0/caido-cli-v0.56.0-linux-x86_64.tar.gz"; \
            CAIDO_SHA="1bc13f1ad558cd333924890d9d09b73f1cdf6726b7855265cd4c4ddece0211d3"; \
            ;; \
        aarch64|arm64) \
            UV_URL="https://github.com/astral-sh/uv/releases/download/0.12.10/uv-aarch64-unknown-linux-gnu.tar.gz"; \
            UV_SHA="9ff6b9d4665edcdd3a88dcc73cd1eb641754deb927f14e8c62ebfde6bf4f5f5e"; \
            TRIVY_URL="https://github.com/aquasecurity/trivy/releases/download/v0.74.0/trivy_0.74.0_Linux-ARM64.tar.gz"; \
            TRIVY_SHA="b94ce1976bbf3c15b514b605ee88be7c6d94a29be2302847ff01cb794d47aad5"; \
            TRUFFLEHOG_URL="https://github.com/trufflesecurity/trufflehog/releases/download/v3.95.9/trufflehog_3.95.9_linux_arm64.tar.gz"; \
            TRUFFLEHOG_SHA="9d9c2ec4ea36a089a9c5aaafe1969d176013ddf9f44d68e8cd75291aed8c83ed"; \
            GITLEAKS_URL="https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_arm64.tar.gz"; \
            GITLEAKS_SHA="e4a487ee7ccd7d3a7f7ec08657610aa3606637dab924210b3aee62570fb4b080"; \
            CAIDO_URL="https://caido.download/releases/v0.56.0/caido-cli-v0.56.0-linux-aarch64.tar.gz"; \
            CAIDO_SHA="0b1548bdfb548b26f5647bf9dc088a29a0ebf304cae4492bfd8ec3c0f4fcf777"; \
            ;; \
        *) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;; \
    esac; \
    \
    curl -fsSL "${UV_URL}" -o /tmp/downloads/uv.tar.gz; \
    echo "${UV_SHA}  /tmp/downloads/uv.tar.gz" | sha256sum -c -; \
    tar -xzf /tmp/downloads/uv.tar.gz -C /tmp/downloads; \
    install -m 0755 /tmp/downloads/uv-*/uv /out/bin/uv; \
    install -m 0755 /tmp/downloads/uv-*/uvx /out/bin/uvx; \
    \
    curl -fsSL "${TRIVY_URL}" -o /tmp/downloads/trivy.tar.gz; \
    echo "${TRIVY_SHA}  /tmp/downloads/trivy.tar.gz" | sha256sum -c -; \
    tar -xzf /tmp/downloads/trivy.tar.gz -C /tmp/downloads; \
    install -m 0755 /tmp/downloads/trivy /out/bin/trivy; \
    \
    curl -fsSL "${TRUFFLEHOG_URL}" -o /tmp/downloads/trufflehog.tar.gz; \
    echo "${TRUFFLEHOG_SHA}  /tmp/downloads/trufflehog.tar.gz" | sha256sum -c -; \
    tar -xzf /tmp/downloads/trufflehog.tar.gz -C /tmp/downloads; \
    install -m 0755 /tmp/downloads/trufflehog /out/pentester_bin/trufflehog; \
    \
    curl -fsSL "${GITLEAKS_URL}" -o /tmp/downloads/gitleaks.tar.gz; \
    echo "${GITLEAKS_SHA}  /tmp/downloads/gitleaks.tar.gz" | sha256sum -c -; \
    tar -xzf /tmp/downloads/gitleaks.tar.gz -C /tmp/downloads; \
    install -m 0755 /tmp/downloads/gitleaks /out/bin/gitleaks; \
    \
    curl -fsSL "${CAIDO_URL}" -o /tmp/downloads/caido.tar.gz; \
    echo "${CAIDO_SHA}  /tmp/downloads/caido.tar.gz" | sha256sum -c -; \
    tar -xzf /tmp/downloads/caido.tar.gz -C /tmp/downloads; \
    install -m 0755 /tmp/downloads/caido-cli /out/bin/caido-cli; \
    \
    SEMGREP_RULES_URL="https://semgrep.dev/c/p/python"; \
    SEMGREP_RULES_SHA="31c1dfa46e8ddd97f9ac98c607ddd77b20a2c3356d7ec987359961d47ec27035"; \
    curl -fsSL "${SEMGREP_RULES_URL}" -o /tmp/downloads/p-python.yaml; \
    echo "${SEMGREP_RULES_SHA}  /tmp/downloads/p-python.yaml" | sha256sum -c -; \
    mkdir -p /out/semgrep-rules; \
    install -m 0644 /tmp/downloads/p-python.yaml /out/semgrep-rules/p-python.yaml; \
    rm -rf /tmp/downloads;

# ---------------------------------------------------------------------------
# Stage 3: Runtime stage
# Pinned Kali Linux baseline with deterministic packages and verified tooling.
# ---------------------------------------------------------------------------
FROM kalilinux/kali-rolling@sha256:c6d78f57ebfdd9fec428a8b04f74876e66fddd08dfd328c076224a552e7332a3

ARG BUILD_VERSION="0.1.0a4"
ARG BUILD_REVISION=""

LABEL description="AI Agent Penetration Testing Environment with Comprehensive Automated Tools" \
      org.opencontainers.image.title="Guile Sandbox" \
      org.opencontainers.image.version="${BUILD_VERSION}" \
      org.opencontainers.image.revision="${BUILD_REVISION}" \
      org.opencontainers.image.source="https://github.com/guile-security/guile"


# Note: apt-get upgrade is intentionally omitted to maintain deterministic build reproducibility.
RUN apt-get update && \
    apt-get install -y kali-archive-keyring sudo && \
    apt-get update

RUN useradd -m -s /bin/bash pentester && \
    usermod -aG sudo pentester && \
    echo "pentester ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
    touch /home/pentester/.hushlogin

RUN mkdir -p /home/pentester/tools /home/pentester/.local/bin /home/pentester/.npm-global /app/certs /workspace /opt/guile/semgrep-rules && \
    chown -R pentester:pentester /app/certs /home/pentester /workspace /opt/guile/semgrep-rules

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    wget curl git vim nano unzip tar \
    apt-transport-https ca-certificates gnupg lsb-release \
    software-properties-common \
    gcc libc6-dev \
    python3 python3-pip python3-venv python3-setuptools \
    net-tools dnsutils whois \
    file xxd \
    jq parallel ripgrep grep \
    less procps htop \
    iproute2 iputils-ping netcat-traditional \
    nmap ncat ndiff \
    sqlmap nuclei subfinder naabu ffuf \
    nodejs npm pipx \
    golang-go \
    libcap2-bin \
    gdb \
    libnss3-tools \
    chromium fonts-liberation \
    wapiti

RUN setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip $(which nmap)

USER pentester
RUN openssl ecparam -name prime256v1 -genkey -noout -out /app/certs/ca.key && \
    openssl req -x509 -new -key /app/certs/ca.key \
    -out /app/certs/ca.crt \
    -days 3650 \
    -subj "/C=US/ST=CA/O=Security Testing/CN=Testing Root CA" \
    -addext "basicConstraints=critical,CA:TRUE" \
    -addext "keyUsage=critical,digitalSignature,keyEncipherment,keyCertSign" && \
    openssl pkcs12 -export \
    -out /app/certs/ca.p12 \
    -inkey /app/certs/ca.key \
    -in /app/certs/ca.crt \
    -passout pass:"" \
    -name "Testing Root CA" && \
    chmod 644 /app/certs/ca.crt && \
    chmod 600 /app/certs/ca.key && \
    chmod 600 /app/certs/ca.p12

USER root
RUN cp /app/certs/ca.crt /usr/local/share/ca-certificates/ca.crt && \
    update-ca-certificates

# Copy direct downloaded and verified binaries from downloader stage
COPY --from=downloader /out/bin/ /usr/local/bin/
COPY --from=downloader --chown=pentester:pentester /out/pentester_bin/ /home/pentester/.local/bin/
COPY --from=downloader /out/semgrep-rules/ /opt/guile/semgrep-rules/


# Go tools are built in the gobuilder stage; copy the binaries
COPY --from=gobuilder --chown=pentester:pentester /out/bin/ /home/pentester/go/bin/

# Python virtual environment with cryptographic hash verification
COPY containers/tooling/python/requirements.lock /tmp/python-requirements.lock
RUN python3 -m venv /app/.venv && \
    /usr/local/bin/uv pip install --no-cache --python /app/.venv/bin/python --require-hashes -r /tmp/python-requirements.lock && \
    rm -f /tmp/python-requirements.lock && \
    chown -R pentester:pentester /app/.venv

# NPM tooling with locked package tree and integrity hashes
COPY --chown=pentester:pentester containers/tooling/npm/package.json /home/pentester/.npm-global/package.json
COPY --chown=pentester:pentester containers/tooling/npm/package-lock.json /home/pentester/.npm-global/package-lock.json

ENV NPM_CONFIG_PREFIX=/home/pentester/.npm-global
USER pentester
WORKDIR /home/pentester/.npm-global
RUN npm ci --omit=dev && \
    npm cache clean --force && \
    mkdir -p /home/pentester/.npm-global/bin && \
    for b in /home/pentester/.npm-global/node_modules/.bin/*; do \
        ln -sf "$b" "/home/pentester/.npm-global/bin/$(basename "$b")"; \
    done && \
    # ast-grep ships two identical binaries (`ast-grep` and `sg`); dedupe
    ln -sf ast-grep /home/pentester/.npm-global/node_modules/@ast-grep/cli/sg && \
    ln -sf ast-grep /home/pentester/.npm-global/bin/sg

# Setup agent-browser environment
ENV AGENT_BROWSER_EXECUTABLE_PATH=/usr/bin/chromium
ENV AGENT_BROWSER_USER_AGENT="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"
ENV AGENT_BROWSER_ARGS="--disable-blink-features=AutomationControlled,--no-first-run,--no-default-browser-check,--lang=en-US"
ENV AGENT_BROWSER_SCREENSHOT_DIR=/workspace/.agent-browser-screenshots
ENV AGENT_BROWSER_IDLE_TIMEOUT_MS=180000

USER root
RUN set -eu; \
    { \
        for var in AGENT_BROWSER_EXECUTABLE_PATH AGENT_BROWSER_USER_AGENT \
                   AGENT_BROWSER_ARGS AGENT_BROWSER_SCREENSHOT_DIR \
                   AGENT_BROWSER_IDLE_TIMEOUT_MS; do \
            eval "value=\${$var}"; \
            printf 'export %s="${%s:-%s}"\n' "$var" "$var" "$value"; \
        done; \
    } > /tmp/agent-browser.sh; \
    install -m 0644 /tmp/agent-browser.sh /etc/profile.d/agent-browser.sh; \
    rm /tmp/agent-browser.sh; \
    env -i bash -lc 'test "${AGENT_BROWSER_IDLE_TIMEOUT_MS}" = "180000"'

USER pentester
RUN /home/pentester/.npm-global/bin/agent-browser doctor --offline --quick

# Pinned tree-sitter grammars
RUN set -eux; \
    TS_PARSER_DIR="/home/pentester/.tree-sitter/parsers"; \
    mkdir -p "${TS_PARSER_DIR}"; \
    git clone https://github.com/tree-sitter/tree-sitter-java.git "${TS_PARSER_DIR}/tree-sitter-java" && (cd "${TS_PARSER_DIR}/tree-sitter-java" && git checkout e10607b45ff745f5f876bfa3e94fbcc6b44bdc11); \
    git clone https://github.com/tree-sitter/tree-sitter-javascript.git "${TS_PARSER_DIR}/tree-sitter-javascript" && (cd "${TS_PARSER_DIR}/tree-sitter-javascript" && git checkout 58404d8cf191d69f2674a8fd507bd5776f46cb11); \
    git clone https://github.com/tree-sitter/tree-sitter-python.git "${TS_PARSER_DIR}/tree-sitter-python" && (cd "${TS_PARSER_DIR}/tree-sitter-python" && git checkout 26855eabccb19c6abf499fbc5b8dc7cc9ab8bc64); \
    git clone https://github.com/tree-sitter/tree-sitter-go.git "${TS_PARSER_DIR}/tree-sitter-go" && (cd "${TS_PARSER_DIR}/tree-sitter-go" && git checkout 2346a3ab1bb3857b48b29d779a1ef9799a248cd7); \
    git clone https://github.com/tree-sitter/tree-sitter-bash.git "${TS_PARSER_DIR}/tree-sitter-bash" && (cd "${TS_PARSER_DIR}/tree-sitter-bash" && git checkout a06c2e4415e9bc0346c6b86d401879ffb44058f7); \
    git clone https://github.com/tree-sitter/tree-sitter-json.git "${TS_PARSER_DIR}/tree-sitter-json" && (cd "${TS_PARSER_DIR}/tree-sitter-json" && git checkout 254c42a6476413b776221e03982ac8ae159eeb72); \
    git clone https://github.com/tree-sitter-grammars/tree-sitter-yaml.git "${TS_PARSER_DIR}/tree-sitter-yaml" && (cd "${TS_PARSER_DIR}/tree-sitter-yaml" && git checkout a1c4812a73ec5e089de8e441fdea3a921e8d5079); \
    git clone https://github.com/tree-sitter/tree-sitter-typescript.git "${TS_PARSER_DIR}/tree-sitter-typescript" && (cd "${TS_PARSER_DIR}/tree-sitter-typescript" && git checkout 75b3874edb2dc714fb1fd77a32013d0f8699989f); \
    if [ -d "${TS_PARSER_DIR}/tree-sitter-typescript/typescript" ]; then \
        ln -sfn "${TS_PARSER_DIR}/tree-sitter-typescript/typescript" "${TS_PARSER_DIR}/tree-sitter-typescript-typescript"; \
    fi; \
    if [ -d "${TS_PARSER_DIR}/tree-sitter-typescript/tsx" ]; then \
        ln -sfn "${TS_PARSER_DIR}/tree-sitter-typescript/tsx" "${TS_PARSER_DIR}/tree-sitter-typescript-tsx"; \
    fi; \
    /home/pentester/.npm-global/bin/tree-sitter init-config >/dev/null 2>&1 || true; \
    TS_CONFIG="/home/pentester/.config/tree-sitter/config.json"; \
    mkdir -p "$(dirname "${TS_CONFIG}")"; \
    [ -f "${TS_CONFIG}" ] || printf '{}\n' > "${TS_CONFIG}"; \
    TMP_CFG="$(mktemp)"; \
    jq --arg p "${TS_PARSER_DIR}" '.["parser-directories"] = ((.["parser-directories"] // []) + [$p] | unique)' "${TS_CONFIG}" > "${TMP_CFG}"; \
    mv "${TMP_CFG}" "${TS_CONFIG}"

# Pinned Git tools
WORKDIR /home/pentester/tools
RUN git clone https://github.com/aravind0x7/JS-Snooper.git && \
    (cd JS-Snooper && git checkout 932704cbce1a04ebe89a558f395a6e474e01b15b) && \
    chmod +x JS-Snooper/js_snooper.sh && \
    git clone https://github.com/xchopath/jsniper.sh.git && \
    (cd jsniper.sh && git checkout 65d0b508dc88e6de3dfe6ac60e6d89eca22be686) && \
    chmod +x jsniper.sh/jsniper.sh && \
    git clone https://github.com/ticarpi/jwt_tool.git && \
    (cd jwt_tool && git checkout 3bc7407cf2222d6a821dcc19c776e5a1b1cb9a9b) && \
    chmod +x jwt_tool/jwt_tool.py

# Nuclei templates pinned to exact release commit
RUN git clone https://github.com/projectdiscovery/nuclei-templates.git /home/pentester/nuclei-templates && \
    cd /home/pentester/nuclei-templates && \
    git checkout f4251b06c100067f230c4f54a1679de59802c7a4

# Set up jwt_tool wrapper script
RUN printf '%s\n' \
        '#!/bin/bash' \
        'exec /app/.venv/bin/python /home/pentester/tools/jwt_tool/jwt_tool.py "$@"' \
        > /home/pentester/.local/bin/jwt_tool && \
    chmod +x /home/pentester/.local/bin/jwt_tool

USER root

RUN apt-get autoremove -y && \
    apt-get autoclean && \
    rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
    # Purge non-English locales (~160MB)
    find /usr/share/locale -mindepth 1 -maxdepth 1 -type d \
        ! -name 'en' ! -name 'en_US' ! -name 'C' -exec rm -rf {} + && \
    # Remove package documentation and man pages not needed at runtime (~95MB)
    rm -rf /usr/share/doc/* /usr/share/doc-base/* /usr/share/man/*

ENV PATH="/app/.venv/bin:/home/pentester/go/bin:/home/pentester/.local/bin:/home/pentester/.npm-global/bin:$PATH"
ENV VIRTUAL_ENV="/app/.venv"
ENV TRUFFLEHOG_NO_UPDATE=1
ENV REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt

WORKDIR /app
COPY --chown=pentester:pentester guile/tools/proxy/caido_api.py /opt/guile-python/caido_api.py
ENV PYTHONPATH=/opt/guile-python

# Ensure interactive/login shells inherit the exact PATH
RUN echo 'export PATH="/app/.venv/bin:/home/pentester/go/bin:/home/pentester/.local/bin:/home/pentester/.npm-global/bin:$PATH"' >> /home/pentester/.bashrc && \
    echo 'export PATH="/app/.venv/bin:/home/pentester/go/bin:/home/pentester/.local/bin:/home/pentester/.npm-global/bin:$PATH"' >> /home/pentester/.profile

COPY containers/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh

USER pentester
WORKDIR /workspace

ENTRYPOINT ["docker-entrypoint.sh"]
