Files
Open_Ultra_SAST/Dockerfile
T
Marius CiepluchandClaude Opus 5 46232fe3e6 feat(ops): ship the tool and its engine in one image
A contributor should be able to run this with nothing installed but Docker:

  docker compose build
  TARGET=/path/to/repo docker compose run --rm ousast scan /target

One image rather than two services, and the reason is the seam. The model
layer reaches Joern by subprocess; putting both in the same image means Joern
is simply on PATH, so there is no container-to-container call, no docker socket
to mount, no host/container path translation, and no second code path in
cpg/backend.py to get wrong. Sibling services would need the tool to exec into
the engine's container, which means handing a linter the docker socket -- a
large amount of privilege for something that reads source code. The image is
big (a JRE plus ~2 GB of Joern); that is the honest cost of bundling an engine.

I had started building the two-service version with a docker-wrapper mode in
the backend and dropped it, tests included: once the engine is on PATH the
wrapper is complexity with no purpose.

Defaults chosen so an unfamiliar analyser is safe to try: the target mounts
READ-ONLY, /work is a tmpfs so CPG scratch never outlives the run or touches
the host, network_mode is `none` unless the LLM judge is wanted, and API keys
pass through the environment rather than into an image layer. mem_limit is set
because two Joern JVMs at once is what triggered an out-of-memory kill on a
7.7 GB machine during this feature's measurements.

pyinfra deploy kept for persistent host installs, with the same version pin and
the same checksum verification -- done as an explicit `sha512sum -c` step
because pyinfra's files.download offers sha384/sha256/sha1/md5 and Joern
publishes sha512. That mismatch was caught by validating against the installed
pyinfra rather than trusting the API from memory.

Both include php-cli: php2cpg drives PHP-Parser and shells out to a real
interpreter, and without it a PHP tree fails with an opaque "Process exited
with code 1" -- measured here, which is what makes a WordPress corpus feasible.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
2026-09-08 19:36:10 +02:00

51 lines
2.3 KiB
Docker

# OpenUltraSAST with its CPG engine, in one image.
#
# The model layer's arbiter is Joern, and `openultrasast.cpg.backend` reaches it by subprocess. Shipping both
# in one image is what makes that simple: Joern is on PATH inside, so there is no container-to-container call,
# no docker socket, no path translation between host and container, and no second code path in the backend to
# get wrong. The alternative -- tool and engine as sibling services -- needs the tool to exec into the other
# container, which means mounting the docker socket, and that is a large amount of privilege for a linter.
#
# The image is big (a JRE plus ~2 GB of Joern). That is the honest cost of bundling an engine, and it buys a
# contributor a working tool from `docker compose run` with nothing installed on their machine.
FROM eclipse-temurin:21-jre-noble
ARG JOERN_VERSION=v4.0.623
ARG ARCHIVE=joern-cli-linux-x86_64.zip
# php-cli is required by php2cpg, which drives PHP-Parser and shells out to a real interpreter. Without it a
# PHP tree fails with an opaque "Process exited with code 1" -- measured on this project, not assumed.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
python3 python3-venv python3-pip unzip curl ca-certificates git php-cli \
&& rm -rf /var/lib/apt/lists/*
RUN set -eux; \
cd /tmp; \
curl -fsSL -O "https://github.com/joernio/joern/releases/download/${JOERN_VERSION}/${ARCHIVE}"; \
curl -fsSL -O "https://github.com/joernio/joern/releases/download/${JOERN_VERSION}/${ARCHIVE}.sha512"; \
sha512sum -c "${ARCHIVE}.sha512"; \
unzip -q "${ARCHIVE}" -d /opt; \
rm -f "${ARCHIVE}" "${ARCHIVE}.sha512"; \
/opt/joern-cli/joern-parse --help > /dev/null
ENV PATH="/opt/joern-cli:${PATH}"
WORKDIR /app
COPY pyproject.toml README.md ./
COPY src ./src
COPY benchmarks ./benchmarks
# The core install is zero-dependency by design; `semantic` adds the tree-sitter grammars the candidate
# enumerator uses. The LLM endpoint stays optional and is configured by environment, never baked in.
RUN python3 -m venv /venv \
&& /venv/bin/pip install --no-cache-dir -e ".[semantic]"
ENV PATH="/venv/bin:${PATH}"
# Analysis never needs root.
RUN useradd --create-home --uid 1000 ousast && chown -R ousast /app
USER ousast
ENTRYPOINT ["python", "-m", "openultrasast.cli"]
CMD ["--help"]