kind cluster + registry, Agent Substrate, ax control plane via ko, one files-only
smoke Task; helpers run from their own checkout because they resolve paths from
the git toplevel. Footprint and README follow once the cluster is up.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
source ops/shell/ousast.sh # bash / zsh
. ops\shell\ousast.ps1 # powershell
ousast scan .
ousast scan src/api
The container becomes an implementation detail. The engine is a JVM and ~2 GB
of Joern, and a contributor evaluating this tool should not have to install
either to try it -- what they get is a command.
Path rewriting is what makes it feel local: arguments under the working
directory are mapped to their mounted equivalents, so `src/api` means what the
user expects. Anything outside the mount is passed through unchanged WITH A
WARNING rather than silently rewritten into a path the container cannot see --
a wrong path that looks like it worked is worse than one that complains.
A native install wins when it is present. Someone who installed Joern
deliberately should not be quietly routed through a container, so the wrapper
uses a local joern-parse and venv when both exist; OUSAST_PREFER_NATIVE=0
forces the container. Verified end to end from an unrelated directory.
The image builds itself on first use and says why it is pausing. A first run
that prints a wall of build output and then works is worse than one that
explains itself.
Defaults stay conservative: the working directory mounts READ-ONLY, findings go
to ./.ousast as the only writable mount, and the network is off unless
`ousast-with-judge` asks for it.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01T7XD7x5KonMdmGRkp63EQG
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