diff --git a/package.json b/package.json index fe32458..f882dc8 100755 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "test:cybench-ci": "node scripts/test-cybench-ci.mjs", "tools:check": "bash scripts/check-tools-image.sh", "tools:build": "bash tools/build.sh", + "test:tools-dockerfile": "node scripts/test-tools-dockerfile.mjs", "install:tools": "bash scripts/install-tools.sh", "docs:sync": "node scripts/pagenary-docsite.mjs", "docs:build": "npm run docs:sync && pagenary build t3mp3st-docs", diff --git a/scripts/check-tools-image.sh b/scripts/check-tools-image.sh index 4569c6f..a0e70f4 100755 --- a/scripts/check-tools-image.sh +++ b/scripts/check-tools-image.sh @@ -17,7 +17,7 @@ if ! command -v docker >/dev/null 2>&1; then fi echo "Checking tools image: $IMAGE" -docker run --rm --platform linux/amd64 "$IMAGE" bash -lc ' +docker run --rm --platform linux/amd64 "$IMAGE" bash -c ' set -e fail=0 for c in radare2 gdb objdump upx; do diff --git a/scripts/test-tools-dockerfile.mjs b/scripts/test-tools-dockerfile.mjs new file mode 100644 index 0000000..d2e84a8 --- /dev/null +++ b/scripts/test-tools-dockerfile.mjs @@ -0,0 +1,59 @@ +#!/usr/bin/env node +/** + * Regression guard for the tools image's non-root runtime contract. + * + * Python packages install console scripts in /usr/local/bin (including httpx). + * Go tools must therefore be built in a staging directory before installation, + * otherwise `go install` refuses to overwrite the existing Python script. + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const here = path.dirname(fileURLToPath(import.meta.url)); +const dockerfile = fs.readFileSync(path.join(here, '..', 'tools', 'Dockerfile'), 'utf8'); +const smokeScript = fs.readFileSync(path.join(here, 'check-tools-image.sh'), 'utf8'); + +let passed = 0; +let failed = 0; + +function check(label, condition) { + if (condition) { + passed += 1; + console.log(` ok ${label}`); + } else { + failed += 1; + console.error(` FAIL ${label}`); + } +} + +check( + 'Go binaries are not built directly into /usr/local/bin', + !/export\s+GOBIN=\/usr\/local\/bin/.test(dockerfile), +); +check( + 'Go binaries use a staging directory', + /export\s+GOBIN=\/tmp\/projectdiscovery-bin/.test(dockerfile), +); +check( + 'staged Go binaries are installed into /usr/local/bin', + /install\s+-m\s+0755\s+"\$GOBIN"\/\*\s+\/usr\/local\/bin\//.test(dockerfile), +); +check( + 'Go staging directory is removed', + /rm\s+-rf\s+"\$GOBIN"/.test(dockerfile), +); +check( + 'operator user reuses the existing Kali operator group', + /useradd\s+--gid\s+operator\s+--create-home\s+--shell\s+\/bin\/bash\s+operator/.test(dockerfile), +); +check('image runtime user is operator', /^USER operator$/m.test(dockerfile)); +check('image runtime workdir is /work', /^WORKDIR \/work$/m.test(dockerfile)); +check( + 'tools smoke avoids a login shell whose logout hook can mask success', + /docker run .* "\$IMAGE" bash -c '/.test(smokeScript) && + !/docker run .* "\$IMAGE" bash -lc '/.test(smokeScript), +); + +console.log(`\n${failed === 0 ? 'PASS' : 'FAIL'}: ${passed} passed, ${failed} failed`); +process.exit(failed === 0 ? 0 : 1); diff --git a/tools/Dockerfile b/tools/Dockerfile index 4c0cf6f..e521dae 100644 --- a/tools/Dockerfile +++ b/tools/Dockerfile @@ -14,8 +14,7 @@ FROM kalilinux/kali-rolling ENV DEBIAN_FRONTEND=noninteractive \ - PIP_BREAK_SYSTEM_PACKAGES=1 \ - PATH="/root/go/bin:/root/.foundry/bin:${PATH}" + PIP_BREAK_SYSTEM_PACKAGES=1 # ── apt: reverse/crypto/pwn + web/recon + osint, plus build deps for the pip layer ── RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -50,19 +49,25 @@ RUN pip3 install --no-cache-dir pipx \ && (pipx install mythril || echo "WARN: mythril pipx install failed") # ── go: the "+" — ProjectDiscovery suite + dalfox (not in stock Kali apt) ── -RUN go install github.com/projectdiscovery/httpx/cmd/httpx@latest && \ +# Stage first: the Python dependency layer already creates /usr/local/bin/httpx, +# and `go install` refuses to overwrite a non-Go executable at GOBIN. `install` +# deliberately replaces that console script with ProjectDiscovery httpx. +RUN export GOBIN=/tmp/projectdiscovery-bin && \ + go install github.com/projectdiscovery/httpx/cmd/httpx@latest && \ go install github.com/projectdiscovery/katana/cmd/katana@latest && \ go install github.com/projectdiscovery/naabu/v2/cmd/naabu@latest && \ go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest && \ go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest && \ go install github.com/hahwul/dalfox/v2@latest && \ - rm -rf /root/go/pkg /root/.cache/go-build + install -m 0755 "$GOBIN"/* /usr/local/bin/ && \ + rm -rf "$GOBIN" /root/go/pkg /root/.cache/go-build # ── npm + foundry: smart-contract "+" (solhint, forge/cast) ── RUN apt-get update && apt-get install -y --no-install-recommends nodejs npm && \ npm install -g solhint && \ rm -rf /var/lib/apt/lists/* /root/.npm -RUN curl -L https://foundry.paradigm.xyz | bash && /root/.foundry/bin/foundryup || \ +RUN curl -L https://foundry.paradigm.xyz | bash && /root/.foundry/bin/foundryup && \ + cp /root/.foundry/bin/forge /root/.foundry/bin/cast /root/.foundry/bin/anvil /usr/local/bin/ 2>/dev/null || \ echo "foundry install skipped (offline build) — re-run foundryup later" # nuclei templates (best-effort; the scan still runs without a fresh pull) @@ -79,5 +84,11 @@ RUN curl -fsSL https://github.com/conda-forge/miniforge/releases/latest/download && /opt/conda/bin/conda clean -afy \ || echo "WARN: sage (conda-forge) install failed/skipped — crypto falls back to z3/sympy/fpylll" +# Kali already defines the operator group; attach the runtime user to it. +RUN useradd --gid operator --create-home --shell /bin/bash operator \ + && mkdir -p /work \ + && chown operator:operator /work + +USER operator WORKDIR /work CMD ["bash"]