fix(tools): run tools image as non-root operator (#114)

Stage ProjectDiscovery binaries before installing them into /usr/local/bin, run the tools image as the existing Kali operator identity, preserve runtime access to Foundry tools, and cover the non-root Dockerfile and smoke-test contracts.
This commit is contained in:
monkeygold authored and GitHub committed 2026-07-30 23:38:03 -04:00
1 parent 178a64e9c4
commit 571a7743ae
4 files changed
+77 -6

No files matched your search

+1
View File
@@ -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",
+1 -1
View File
@@ -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
+59
View File
@@ -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);
+16 -5
View File
@@ -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"]