aonohako is a Go service for compiling and executing judge submissions over
SSE. It is designed for online judge pipelines that want a small control plane
binary, configurable runtime images, and testable build metadata.
POST /compile,POST /execute,GET /capabilities,GET /livez, andGET /readyz- queue-controlled SSE responses with
progress,log,image,error, and finalresultevents - a
boxworkspace layout that keeps submitted files immutable while allowing new files to be created in the same working directory - symlink-safe output capture for file outputs and sidecar artifacts
- SPJ and interactive IO judging support for problems that need custom verdict logic or bidirectional contestant/interactor communication
communication-v1execution on explicitly enabled Cloud Run embedded helpers and dedicated self-hosted cgroup runners: one private manager process coordinates 2–64 isolated launches of one shared, read-only participant binary- an atomic Pipeline V1 execution contract with explicit resources, private artifacts, batch/interactive steps, and final-judge input
runtime-images.ymlas the source of truth for runtime image groups- Docker build tooling that can emit production multi-language images and single-language CI smoke images from the same YAML catalog
- GitHub Actions CI that runs Go tests, repository policy checks, sandbox
regressions, per-language smoke builds in parallel, and an explicit
plain+python+javamixin smoke job, while publishing one consolidated toolchain summary across production runtime profiles
The runtime catalog lives in runtime-images.yml.
Reusable shared_installs blocks hold toolchains needed by several languages;
each referenced block is expanded once per generated image while direct
language-specific commands keep their declared order.
- Production mode builds grouped images such as
type-ifor common C/C++, Python, PyPy, and Java judge workloads,type-afor lighter scripting and esolang runtimes,type-bfor JVM/Node/Web tooling,type-cfor native and systems toolchains,type-dfor Kotlin,type-efor .NET languages, and dedicated profiles for larger or isolated toolchains such as Julia, Swift, proof assistants, Dart, DuckDB/GDL/Octave, HDL simulators, CUDA Ocelot, Dafny, Isabelle, and Lean. The full production profile table is maintained in docs/architecture.md. - CI mode expands the same catalog into one image per language so that each
smoke job validates a single toolchain in isolation. A separate CI job builds
the production profiles in parallel, runs
scripts/report_toolchain_versions.shonce per profile, records both toolchain versions and language-specific compile options, and uploads the profile summary fragment plus SBOM/scan diagnostics. Docker archive export is currently skipped in CI to conserve runner storage; each profile records an archive diagnostic JSON instead. A final CI job downloads those artifacts, publishes one consolidated GitHub Actions summary, verifies the exact profile and language inventories from the production matrix, binds each summary/SBOM/scan report and its hashes to one immutable image ID, regenerates the aggregate summary for exact comparison, and uploads the exact sorted manifest of summaries, provenance, SBOM/scan evidence, and archives or skip diagnostics as a single bundle. Missing downloads, empty profile sets, failed probes, malformed evidence, and non-portable archive checksum paths fail the summary job. - The current catalog covers native binaries, Python plus bundled judge
libraries (
numpy,pandas,seaborn,matplotlib,Pillow,qiskit,torch,torchvision,jax[cpu], and related dependencies), optional custom Python packages supplied at image build time, PyPy, Java/Kotlin/JVM languages, Node/Deno/Bun/QuickJS/TypeScript/CoffeeScript/Elm/ReScript/PureScript, AssemblyScript/WASI, .NET languages and PowerShell, Ruby, PHP, Lua/LuaJIT, Perl, Elixir/Erlang/Gleam, Haskell, Idris2, Standard ML with MLton and SML/NJ, OCaml, SQLite/DuckDB, Go, Rust, Zig, Nim, Pascal, Delphi, Object Pascal, Ada, GNU assembly, NASM, Objective-C/C++, C3, Crystal, D, Hare, Vala, Mojo, MoonBit, Fennel, Factor, Chapel, ALGOL 68, Koka, Pony, Bash/POSIX/Zsh/Fish shell, Zerolang, Odin, V, FreeBASIC/QBasic, Julia, Swift, R, Racket/Chibi Scheme/Chez Scheme/GNU Guile/Chicken Scheme, Mercury, SWI Prolog/GNU Prolog, Lisp/PicoLisp/Smalltalk/GolfScript, APECode, Befunge, Brainfuck, Malbolge, LOLCODE, Whitespace, WASM, Coq/Rocq, Lean, Agda, TLA+, Why3, Isabelle, Aheui, Dart, GDL/Octave, HDL simulation, CUDA Ocelot, Carbon, VB6, Dafny, BQN/APL/J/UIUA/Janet, and UHMLANG. C/C++ and assembly submitters compile into binaries and should target theplainruntime image rather than dedicated native runtime images. Add new languages by extending the YAML file instead of editing shell loops or workflow matrices. - Zerolang is pinned to the official v0.3.4 Linux x64 release. Compilation
imports canonical
.0text into a graph and then builds a native executable with therelease-fastprofile. Its stable source projection does not yet expose a stdin contract, so runtime smoke coverage currently uses fixed output while still exercising import, validation, compilation, and execution. SMLremains the compatibility selection for Standard ML andMLTONis the explicit MLton selection. Both compile with the same MLton 20241230 toolchain and share one installation in the production runtime pack.SMLNJselects SML/NJ 110.99.9. Its three official bootstrap archives are checksum-pinned and installed with-nolibwhile network supplementation is disabled. Compile is source pass-through; execution calls the native runtime directly with an image-built trusted heap that hides prompts and successful top-level diagnostics while preserving user stdout and syntax-error failure.PROLOGremains the interpreted SWI-Prolog selection.GNU_PROLOGselects GNU Prolog 1.4.5 from the pinned Debiangprolog=1.4.5.0-3package and emits a native executable with a fixedgplc --no-top-level --no-debuggercommand. The image-owned trusted initialization wrapper invokesmainand maps its success or failure to exit status 0 or 1. Smoke coverage proves submission initialization is not executed during compile and that invalid syntax fails.- AssemblyScript is pinned to 0.28.20 with the official WASI shim 0.1.0 and checksum-verified compiler, shim, Binaryen, and Long npm archives. A trusted wrapper resolves the shim from its immutable installation directory, emits a validated Wasm command artifact, and executes it with Wasmtime's bounded memory/table/instance policy without preopening the submission directory.
- Factor is pinned to the official 0.101 image and isolated in its own runtime profile because the image includes a JIT and FFI surface. Compilation parses source without running top-level forms. Both phases disable Factor's signal helper thread while permitting the VM's internal thread-directed GC signals; process creation, network sockets, and the remaining default seccomp restrictions stay denied.
- Chez Scheme is pinned to Debian trixie's
chezscheme 10.0.0+dfsg-5and exposed through the distinctCHEZ_SCHEMEcontract. Compilation invokes an immutable reader helper that consumes every form through EOF without loading or evaluating submitted top-level code; execution uses Chez's script mode. Neither phase receives a process or socket sandbox exception. - GNU Guile is pinned to Debian trixie's
guile-3.0andguile-3.0-libs3.0.10+really3.0.10-4packages under theGUILEcontract. Its immutable reader helper validates every form through EOF without evaluating top-level code, while compile and runtime commands disable automatic compilation. Reader compilation receives no process or socket sandbox exception. - Chicken Scheme pins Debian trixie's
chicken-binandlibchicken-dev5.3.0-2packages underCHICKEN_SCHEME.csc -staticlinks the CHICKEN runtime and statically available extensions into a direct native artifact; system libraries such as libc and libm remain normally shared. Compilation does not run ordinary top-level behavior. The compiler retains subprocess support only because its driver invokes the translator and C linker; Unix sockets and all runtime process and socket operations remain denied. LUA54is an explicit profile alias for the existing Lua runtime, so it reuses one pinned Lua 5.4.7 installation. Execution uses-Eto ignore environment startup hooks.- LuaJIT uses Debian's pinned OpenResty 2.1 build. Compilation emits raw
bytecode only to
/dev/null, with process and socket syscalls denied, and runtime startup ignoresLUA_INIT. - Bun is pinned to the checksum-verified official 1.3.14 Linux x64 baseline
build. Both
JAVASCRIPT_BUNandTYPESCRIPT_BUNperform syntax-only, no-bundle transpilation with installation, environment files, macros, and ambient config disabled. Bun 1.3.14's no-bundle--outdirpath fails with an empty output filename, so each validation uses a source-independent--outfileunder the reserved workspace cache and publishes only the submitted files. Execution uses--smol, disables native addons, applies a strict unhandled-rejection policy, and clears Bun's ambient options and transpiler cache. JSC receives a finite 64 GiB virtual-address ceiling while cgroup/RSS accounting enforces physical memory; process creation and network sockets remain denied. JAVASCRIPT_QUICKJSuses the checksum-pinned official QuickJS 2026-06-04 source release.qjsc -cvalidates source into a discarded C-bytecode file without evaluating top-level code; execution applies QuickJS's own memory and stack limits while the sandbox keeps process and socket creation denied. The image build also disables QuickJS shared-library modules so submitted.sofiles cannot inject native extensions.- MoonBit is pinned to the official
moonc 0.10.9+6e6c44045Linux x64 toolchain and its matching core snapshot. Both archives are checksum-verified; submission builds synthesize a dependency-free module and use a single-job, frozen native release build so they never update or contact the package registry. - Fennel is pinned to the checksum-verified official 1.6.1 standalone Linux
x64 compiler. A trusted writer uses its embedded
compile-stringAPI with the compiler sandbox and no user rc file, keeps compiler diagnostics out of the artifact, validates both generated and guarded Lua withluac5.4, and atomically publishes the result. The generated guard removes process spawning, the debug library, preloaded modules, and native-module loaders before submission code runs in the shared Lua runtime pack. - Chapel is pinned to the official 2.9.0 Debian 13 package in its own runtime pack. Submissions compile for one local locale with the packaged qthreads tasking runtime, and execution always fixes both locale and worker-thread counts to one.
- ALGOL 68 uses Algol 68 Genie 3.13.3 from a SHA-512 content-addressed source
mirror and verifies the archive again with SHA-256. Its core build is
additionally patched for judge use: cwd and environment option readers,
process-related prelude names, monitor command execution, and underlying
system/fork/execvecalls are all disabled. Both syntax checking and execution force interpreted-O0mode and ignore source pragmats. - Koka is pinned to the checksum-verified official 3.2.3 Linux x64 bundle.
Submission compilation disables automatic dependency installation, fixes
GCC 16 to the portable x86-64 baseline, and normalizes a lone
Main.kkto canonicalmain.kk. Generated ELF files with RPATH/RUNPATH or path-bearing dynamic dependencies are rejected before publication. - Pony is pinned to the checksum-verified official 0.69.1 Ubuntu 24.04 x86-64
bundle and installed as a minimal compiler, runtime, and standard-library
payload. Compilation targets the generic CPU, execution fixes the scheduler
ceiling with
--ponymaxthreads=1, and physical memory remains cgroup/RSS bounded while Pony's reserved virtual arena is exempted fromRLIMIT_AS. - Shell submissions use Debian Bash 5.2.37-2+b9, dash 0.5.12-12, Zsh
5.9-8+b23, or Fish 4.0.2-1 as runtime variants of one shell language family.
Zsh and Fish keep native
.zshand.fishartifacts while sharing the same bounded runtime contract. Syntax checks and execution disable startup files, while the dedicated type-x pack permits bounded child processes for normal shell composition with a 64-task helper budget and an 80-task per-run cgroup ceiling, but keeps networking disabled. The final image exposes only an explicit utility allowlist and makes hidden compilers, package/network tools, and their loader inputs unreadable. - PowerShell is pinned to the checksum-verified official 7.6.5 Linux x64
bundle in the shared type-e pack. Syntax checking uses the parser API without
executing the submission, while execution disables profiles, telemetry, and
update checks. Its home/config/data roots point at root-owned
/var/empty, and its module path contains only immutable empty compatibility roots plus the bundled modules, never the writable workspace. Its CoreCLR startup receives a 512-file-descriptor ceiling and noRLIMIT_AS, plus the normal request-derived GC heap cap; process creation, networking, memfd/NUMA exceptions, shared dotnet state, and the dotnet file-size override stay off. Its pack replaces/etc/passwdwith a root-owned 0444 empty NSS compatibility stub required by pwsh, exposing no identity records;/etc/groupand other account metadata stay root-only. - Carbon is pinned to an official experimental nightly. The image precompiles
that toolchain's Core objects and native runtimes, then submission compilation
emits an object, links a native executable against those trusted inputs, and
runs it through the standard binary sandbox. With the pinned nightly,
Runshould returni32and explicitly return zero; a voidRuncan leave the last output byte count as the process exit status. - Compile and execute environments set
ONLINE_JUDGE=1. Languages with compiler-supported defines or build tags also receive anONLINE_JUDGEcompile flag where appropriate, such as C/C++/Objective-C, NASM, Rust, Go, Pascal, Nim, D, Dart, Verilog, Crystal, V, Odin, C3, Swift, .NET, Cython, Haxe, and FreeBASIC/QBasic. JVM-family runtime launchers also pass-DONLINE_JUDGE=1. - Debian-based production profiles track
debian:trixie-slim, which raises the default Python, PyPy, and GCC toolchain versions for both production and single-language CI runtime images. - Python judge libraries in the runtime catalog are pinned to exact versions so runtime rebuilds stay reproducible across CI and production.
Inspect the generated matrix:
go run ./cmd/runtime-matrix -mode production
go run ./cmd/runtime-matrix -mode ciDry-run image builds:
./scripts/build_runtime_images.sh -mode production -dry-run -tag-prefix ghcr.io/seo-rii/aonohako
./scripts/build_runtime_images.sh -mode ci -dry-run -tag-prefix aonohako-ciCustom Python packages can be copied into runtime images by passing a directory as a named build context:
./scripts/build_runtime_images.sh \
-mode production \
-only type-i \
-tag-prefix ghcr.io/seo-rii/aonohako \
-python-packages-context /path/to/python/packagesThe same path can be supplied with AONOHAKO_PYTHON_PACKAGES_CONTEXT.
When neither is supplied and the repository python/ directory exists, it is
used by default. Contents are copied to /usr/local/lib/aonohako/python, which
is exported as PYTHONPATH in runtime images. The fixed bundled
sitecustomize.py is copied after that context, so a custom package context
cannot replace the trusted startup hook. The hook stays inactive unless an
execution requests image sidecar output.
deployment-contract.json publishes the installed-library capability and its
supported interpreter targets. Downstream deployment automation should require
that contract before enabling PyPy installed-library requests, so an older
runner ref fails before an image or control-plane revision is deployed.
An embedded helper advertises python-library-pypy-installed-v1 from
GET /capabilities only when it globally accepts explicit installed mode and
the running image declares exact python and pypy language membership, both
library-isolation flags, and the reserved external-library group. A
problem-specific installed-mode mapping alone does not advertise a fleet-wide
capability, and remote control planes do not advertise a downstream worker's
capability. The live response attests runner policy and trusted build metadata;
CI and startup selftests verify the actual image permissions, while image build
smoke tests verify the package catalog separately.
For non-root local development, forward both /compile and /execute to a
hardened runner:
AONOHAKO_DEPLOYMENT_TARGET=dev \
AONOHAKO_EXECUTION_TRANSPORT=remote \
AONOHAKO_SANDBOX_BACKEND=none \
AONOHAKO_REMOTE_RUNNER_URL=https://runner.internal \
go run ./cmd/serverBare go run ./cmd/server uses the compatibility local-dev shape, which is
still an embedded helper sandbox and requires a root parent. If you want the
local root-backed helper sandbox, run it explicitly with a dedicated work root:
sudo env \
AONOHAKO_DEPLOYMENT_TARGET=selfhosted \
AONOHAKO_EXECUTION_TRANSPORT=embedded \
AONOHAKO_SANDBOX_BACKEND=helper \
AONOHAKO_API_BEARER_TOKEN=replace-me \
AONOHAKO_WORK_ROOT=/work \
AONOHAKO_MAX_ACTIVE_RUNS=1 \
go run ./cmd/serverRun the test suite:
go test ./...Validate the current deployment environment without starting the HTTP server:
aonohako-selftest deployment-contractThe deployment contract JSON includes the selected execution shape, whether the
named security contract is implemented, effective and missing local
capabilities, queue and stream limits, inbound/remote auth posture, cgroup
parent presence, whether AONOHAKO_REQUIRE_WORK_ROOT_TMPFS is active, and the
configured AONOHAKO_WORK_ROOT_MAX_BYTES and AONOHAKO_WORK_ROOT_MAX_FILES
values.
Repository deployment tooling can validate its configured ceilings against the
machine-readable deployment-contract.json
manifest before creating a revision.
Checked deployment environment examples live under
docs/examples/: cloudrun-runner.env,
cloudrun-control-plane.env, selfhosted-runner.env, and
dev-control-plane.env.
Repository policy check:
./scripts/check_repo_policy.shSelf-hosted runner hosts can also check future cgroup backend prerequisites:
aonohako-selftest cgroup-preflightPORTdefaults to8080AONOHAKO_DEPLOYMENT_TARGETselects where the server is meant to run:cloudrun,selfhosted, ordev(default)AONOHAKO_EXECUTION_TRANSPORTselects how/compileand/executeare handled:embedded(default) orremoteAONOHAKO_SANDBOX_BACKENDselects the local sandbox implementation:helperornone.containeris a reserved enum value for a future backend and is rejected by startup validation today.- These axes map to explicit security contracts in code:
embedded-helper-process-hardening,remote-control-plane, and reservedreserved-container-isolation. The helper contract is process hardening by default; self-hosted helpers can opt into per-run cgroup memory, pids, and one-vCPU CPU bandwidth limits.aonohako-selftest deployment-contractmoves those cgroup-backed capabilities from missing to effective whenAONOHAKO_CGROUP_PARENTis configured. Mount-namespace, per-run UID, masked/proc, and post-startexecve()isolation remain unavailable in the helper backend. AONOHAKO_EXECUTION_MODEremains as a compatibility shorthand:cloudrun→cloudrun + embedded + helperlocal-root→selfhosted + embedded + helperlocal-dev→dev + embedded + helper(compatibility only; it is not the non-root development path)AONOHAKO_MAX_ACTIVE_RUNSdefaults to1forembedded + helper, stays1forcloudrun, and otherwise defaults tomax(1, cpu-2). Theembedded + helperbackend rejects values other than1.AONOHAKO_MAX_PENDING_QUEUEdefaults to16. Set it explicitly to0only for development cases that intentionally need an unlimited queue.AONOHAKO_MAX_ACTIVE_STREAMSdefaults to64and caps simultaneous/compileand/executerequest streams before they can occupy more server resources. Set it explicitly to0only for development cases that intentionally need unlimited open streams.AONOHAKO_MAX_ACTIVE_UPLOADSdefaults to4outside development andAONOHAKO_MAX_PRINCIPAL_ACTIVE_UPLOADSdefaults to2. These slots are acquired before authentication reads a signed request body, JSON decoding, Base64 validation, or payload URL fetches. The slot is released only after the request enters the bounded run queue or terminates early. Both limits default to0in development; production targets reject0.AONOHAKO_MAX_RETAINED_REQUEST_BYTESdefaults to268435456(256 MiB) and must be positive. It caps shared request data across upload, queueing, and execution. Known-length bodies reserve their input bytes; unknown-length bodies reserve the 64 MiB input cap. After decoding, every request-owned string is charged, including URL-expanded Base64 resources and metadata. URL downloads reserve the maximum permitted replacement field before fetching, then shrink to the actual size. Insufficient capacity returns HTTP 429 withrequest_byte_budget_exceededbefore SSE starts. Reservations last until the handler finishes, including cancellation and cleanup. This is a retained-data budget, not an RSS limit: allow additional memory for decoder/download scratch, responses, and sandbox execution.AONOHAKO_PLATFORM_BODY_HASH_CONCURRENCYdefaults tomin(4, AONOHAKO_MAX_ACTIVE_STREAMS)when streams are bounded, otherwisemin(4, AONOHAKO_MAX_ACTIVE_RUNS)and accepts values from1through64. It separately caps concurrent pre-auth body hashing for signed platform-auth requests, so stream concurrency can be higher than the number of simultaneous 64 MiB body hash operations.AONOHAKO_MAX_PRINCIPAL_ACTIVE_STREAMSdefaults to0fordevand16forcloudrunorselfhosted. It caps simultaneous request streams per authenticated or platform principal;0disables the per-principal cap.AONOHAKO_MAX_PRINCIPAL_REQUESTS_PER_MINUTEdefaults to0fordevand60forcloudrunorselfhosted. It caps accepted/compileand/executerequests per principal per fixed one-minute window. Set it to0on any deployment target to disable the per-process request-rate cap. This is intended for trusted Cloud Run or self-hosted runners whose concurrency and fleet capacity are bounded at the deployment layer.AONOHAKO_HEARTBEAT_INTERVAL_SECdefaults to10AONOHAKO_BODY_READ_TIMEOUT_SECdefaults to30and bounds how long the HTTP server will spend reading one/compileor/executerequest body. This keeps authenticated slow uploads from holding handler goroutines indefinitely before SSE streaming begins.AONOHAKO_REMOTE_SSE_IDLE_TIMEOUT_SECdefaults to30and bounds how long a remote/compileor/executeSSE response may stay silent before the control plane cancels it.AONOHAKO_REMOTE_STRICT_PROTOCOLcontrols whether remote responses must carryX-Aonohako-Protocol-Version. It defaults totrueoutsidedevandfalseindev, so production remote fleets fail closed on unversioned runner responses while local compatibility testing can still accept them.AONOHAKO_ALLOW_REQUEST_NETWORKcontrols whether/executemay honor client-suppliedenable_network=true. It defaults totrueonly fordevandfalseforcloudrunorselfhosted; public runners should route network-enabled problems to an explicitly opted-in runner pool. Outsidedev, enabling it also requiresAONOHAKO_NETWORK_EGRESS_ISOLATED=true, which asserts that the selected embedded runner or downstream remote runner is already inside a deny-by-default network namespace/cgroup-BPF/nftables or equivalent egress boundary that blocks loopback, private, link-local, and metadata addresses. The assertion does not create that infrastructure.AONOHAKO_ALLOW_REQUEST_RUNTIME_PROFILEcontrols whether/compileand/executemay honor request-suppliedruntime_profile. It defaults totrueonly fordevandfalseforcloudrunorselfhosted; production control planes should map problems to policy-owned profiles and enable this only on the trusted runner/control-plane boundary that receives those sanitized requests.AONOHAKO_PROBLEM_RUNTIME_PROFILESmay define a JSON object mapping requestproblem_idvalues to namedAONOHAKO_RUNTIME_TUNING_PROFILES. The server applies the mapped profile before stream or queue acquisition, so public entry points can keep directruntime_profileselection disabled. Withremotetransport, deploy the same profile definitions andproblem_idmapping to the downstream runner whenever the control plane forwards policy-selectedruntime_profilevalues.AONOHAKO_DEFAULT_GO_MODULE_MODEselects the default for Go compilation:stdlib(default) orinstalled. Installed mode uses only the exact modules and checksums committed undergo-modules/; compilation remains offline.AONOHAKO_ALLOW_REQUEST_GO_INSTALLED_MODULEScontrols whether a request may elevate from thestdlibdefault withgo_module_mode=installed. It defaults totrueonly fordevandfalseforcloudrunorselfhosted.AONOHAKO_PROBLEM_GO_MODULE_MODESmay define a JSON object mappingproblem_idvalues tostdliborinstalled. A problem mapping wins over a direct request and conflicting values are rejected before stream admission. Remote control planes and runners must use the same policy or explicitly trust the forwarded, policy-selected mode.AONOHAKO_DEFAULT_RUST_CRATE_MODEselects the default for Rust compilation:stdlib(default) orinstalled. Installed mode uses only the exact crate graph and checksums committed underrust-crates/; compilation remains offline.AONOHAKO_ALLOW_REQUEST_RUST_INSTALLED_CRATEScontrols whether a request may elevate from thestdlibdefault withrust_crate_mode=installed. It defaults totrueonly fordevandfalseforcloudrunorselfhosted.AONOHAKO_PROBLEM_RUST_CRATE_MODESmay define a JSON object mappingproblem_idvalues tostdliborinstalled. A problem mapping wins over a direct request and conflicting values are rejected before stream admission. Remote control planes and runners must use the same policy or explicitly trust the forwarded, policy-selected mode.AONOHAKO_DEFAULT_PYTHON_LIBRARY_MODEselects the request-wide default for CPython and PyPy imports:stdlib(default) orinstalled.stdlibkeeps submitted sibling modules and the standard library available while hiding the selected interpreter's protected package roots and the shared trusted image package root. Both modes use isolated interpreter startup;installedexplicitly adds global system packages, the trusted image package directory, and the fixed image-owned startup hook.AONOHAKO_ALLOW_REQUEST_PYTHON_INSTALLED_LIBRARIEScontrols whether a request may elevate from thestdlibdefault withpython_library_mode=installed. It defaults totrueonly fordevandfalseforcloudrunorselfhosted. Outside a problem-owned mapping, a request may always choose the saferstdlibmode.AONOHAKO_PROBLEM_PYTHON_LIBRARY_MODESmay define a JSON object mapping requestproblem_idvalues tostdliborinstalled. A problem mapping wins over direct request policy and conflicting request values are rejected before stream or queue admission. Withremotetransport, deploy the same default and problem mappings to the downstream runner, or explicitly allow the trusted runner boundary to receive the control plane's selectedinstalledmode.AONOHAKO_TRUSTED_RUNNER_INGRESSasserts that a root-backed embedded helper runner is reachable only through trusted/private ingress, Cloud Run IAM, mTLS, a gateway, or an equivalent control-plane boundary. It defaults totruefordevand remote control planes, but non-devembedded + helperrunners must set it explicitly totrue.- Numeric environment variables are strict: malformed, negative, or zero values where a positive integer is required fail startup instead of falling back.
AONOHAKO_INBOUND_AUTHcontrols inbound/compileand/executeauthentication. It defaults tononefordevandbearerforcloudrunorselfhosted. Supported values arenonefordevonly,bearer, andplatform.AONOHAKO_API_BEARER_TOKENis required whenAONOHAKO_INBOUND_AUTH=bearer.AONOHAKO_INBOUND_AUTH=platformdocuments that Cloud Run IAM, an API gateway, mTLS, private ingress, or another platform layer authenticates inbound calls before they reach this process. The upstream layer must strip any client-supplied identity headers and rewriteX-Aonohako-Principal; forwarded identity headers such asX-Forwarded-Emailare ignored by the application. Do not expose platform mode directly to the public internet.AONOHAKO_PLATFORM_PRINCIPAL_HMAC_SECRETis required forAONOHAKO_INBOUND_AUTH=platformoutsidedev. It makes platform mode verifyX-Aonohako-Principal-Signature: v4=<hex-hmac-sha256>overmethod + "\n" + request_uri + "\n" + principal + "\n" + timestamp + "\n" + nonce + "\n" + sha256_hex(body)before accepting the request.request_uriincludes the path and query string. The timestamp comes fromX-Aonohako-Principal-Timestampin RFC3339 format and must be within five minutes of the server clock.X-Aonohako-Principal-Noncemust be a fresh, cryptographically random 128-bit value encoded as 32 lowercase hex characters. A bounded replay cache rejects reuse by the same principal until the signature validity window expires; it fails closed when capacity is exhausted. Legacy replayablev3=and bodylessv2=signatures are rejected. Concurrent pre-auth body hashing first requires global and claimed-principal upload admission, and is additionally capped byAONOHAKO_PLATFORM_BODY_HASH_CONCURRENCY, so invalid signatures cannot force unbounded parallel 64 MiB body buffers.AONOHAKO_TRUSTED_PLATFORM_HEADERSandAONOHAKO_PLATFORM_TRUSTED_PROXY_CIDRSremain available only as optional defense-in-depth assertions for deployments that want source-CIDR checks in addition to signed platform principals; unsigned platform headers are not accepted outsidedev.AONOHAKO_WORK_ROOTpoints compile/run directories at a dedicated work root and is required forcloudrun, and forselfhosted + embedded + helperAONOHAKO_REQUIRE_WORK_ROOT_TMPFSis a strict boolean and defaults tofalsefor development and remote control planes. Productionembedded + helperrunners require it to betrue; startup verifies through/proc/self/mountinfothatAONOHAKO_WORK_ROOTis the tmpfs mount point, rather than merely a directory somewhere on a shared tmpfs.AONOHAKO_WORK_ROOT_MAX_BYTES, when nonzero, verifies throughstatfsthat the required work-root filesystem is bounded to that many bytes or less. Production helper runners require a positive value no greater than 1 GiB.AONOHAKO_WORK_ROOT_MAX_FILES, when nonzero, verifies throughstatfsthat the required work-root filesystem exposes no more than that many inodes. Production helper runners require a positive value no greater than 1048576. The only exception is the dedicated Cloud Run communication runner, which permits up to 4194304 advertised inodes because its 32 GiB instance exposes about 4.1 million inodes even on the bounded 1 GiB volume. This does not raise the ordinary runner ceiling; communication still allows only one session and scans each of its 65 workspaces with the 8192-entry limit.AONOHAKO_CGROUP_PARENTis required forselfhosted + embedded + helperand rejected for other deployment shapes. Startup validates that the parent directory is under a cgroup v2 mount and exposescpu,memory, andpids; each compile/execute/SPJ run is placed in a per-run cgroup withmemory.max,pids.max,cpu.max=100000 100000, andmemory.oom.group=1.GET /livezreports only API-process liveness.GET /readyzadditionally rechecks mandatory work-root and delegated-cgroup invariants and returns503if they disappear.GET /healthzremains a compatibility alias for readiness. These endpoints do not require application authentication.AONOHAKO_REMOTE_RUNNER_URLpointsremotetransport at anotheraonohakorunner service and must be an absolutehttp(s)URL without embedded credentials, query strings, or fragments. Outsidedev, bearer and Cloud Run identity-token authentication require anhttpsURL.AONOHAKO_REMOTE_RUNNER_AUTHcan benone,bearer, orcloudrun-idtoken;noneis allowed only fordevAONOHAKO_REMOTE_RUNNER_TOKENprovides the bearer token whenAONOHAKO_REMOTE_RUNNER_AUTH=bearerAONOHAKO_REMOTE_RUNNER_AUDIENCEoverrides the ID-token audience forcloudrun-idtokenauth; it defaults toAONOHAKO_REMOTE_RUNNER_URL
Per-request execution limits are part of the /execute payload. The
generated public limit table is the canonical numeric
contract:
limits.time_mslimits.memory_mblimits.output_bytesDefaults to64 KiBwhen omitted and is capped internally at64 MiB. This remains the execution capture and output-limit verdict boundary.capture_limits.stdout_bytes/capture_limits.stderr_bytesOptionally clip/executeresult fields, step fields, andlogevents per stream without changing output comparison, OLE detection, or step handoffs. Each omitted member retains the default response cap of at most64 KiB; explicit0suppresses that returned stream, and the maximum override is8 MiB.emit_logsDefaults totruefor both/compileand/execute. Set it tofalseto suppress compiler or contestant stdout/stderrlogSSE events without changing judging or the final result payload.stdinandexpected_stdoutEach inline field is capped at64 MiBbefore a request enters the shared queue. Usestdin_urlandexpected_stdout_urlto have Aonohako download HTTP(S) payloads server-side instead of embedding them in the JSON body.data_urlsources[],binaries[],programs[].binaries[],spj.binary, andinteractor.binaries[]may use HTTP(S)data_urlinstead ofdata_b64. Server-side payload downloads reject URL credentials and any destination that resolves to loopback, private, link-local, multicast, unspecified, or otherwise reserved address space. The same policy is enforced for every redirect and at connection time. All execute binaries across top-level, program, SPJ, and interactor fields share one request-wide 48 MiB decoded budget across inline and URL-backed payloads. URLs are resolved only after request structure and collection counts pass validation.enable_networkCloud Run embedded-helper runners rejecttrue. Self-hosted embedded-helper runners honor it only whenAONOHAKO_ALLOW_REQUEST_NETWORK=trueandAONOHAKO_NETWORK_EGRESS_ISOLATED=true, and then allow outboundAF_INET/AF_INET6client sockets only; listener syscalls and hostAF_UNIXsockets stay blocked. Control-plane instances can forward networked workloads only to egress-isolated opted-in runners withremotetransport.runtime_profileRequests may select an operator-definedAONOHAKO_RUNTIME_TUNING_PROFILESentry only whenAONOHAKO_ALLOW_REQUEST_RUNTIME_PROFILE=true. Public entry points should keep this disabled and let a trusted control plane attach the profile after applying problem policy.
This repository does not ship cloud-vendor deployment credentials or gcloud
workflow dependencies. The CI policy script fails if common secret-like or
cloud CLI markers are checked in, and it requires Dockerfile base images to be
digest-pinned or routed through digest-pinned build arguments.
The local execution path now enforces these invariants:
- the process working directory is
box/ - each execution workspace root remains server-owned, is assigned to its
sandbox role's GID, and is mode
0710(group traverse only) - submitted files are materialized with immutable permissions (
0444or0555) - the
box/directory is writable so submissions can create new files beside their own sources or binaries - interactive contestants run as UID/GID
65532, while the trusted interactor runs as UID/GID65531behind a different group-traversal boundary; this prevents either peer from traversing or mutating the other's workspace while preserving server ownership of the workspace root, even though the helper backend still shares the host mount and/procnamespaces - captured outputs reject symlinks to avoid read-through escapes
The runtime sandbox uses helper-process hardening rather than mount-based
filesystem isolation. It applies setrlimit, PR_SET_NO_NEW_PRIVS, seccomp,
fd cleanup, immutable submitted files, a writable per-run workspace, and
process-group cleanup. Self-hosted helper deployments can additionally set
AONOHAKO_CGROUP_PARENT to place each compile/execute/SPJ sandbox process in a
per-run cgroup with kernel-enforced memory, pids, and one-vCPU CPU bandwidth
limits.
Verdicts are classified from wall time, target CPU time, procfs RSS samples,
cgroup memory.peak when available, workspace scans, process exit state, and
output/SPJ evaluation in that order. No-cgroup helper runs exclude
rusage.Maxrss because it also contains API-server fork and helper setup memory.
Final run responses include optional verdict_source diagnostics such as
cpu_time, memory_rss, workspace_bytes, file_output, or spj so
operators can see which measurement or judge step selected the status. See
docs/architecture.md for
the exact policy and the remaining environment-dependent boundaries.
Security posture depends on where it runs:
cloudrun + embedded + helperis the supported production security target. Startup fails closed unlessAONOHAKO_WORK_ROOTis configured, writable, not group/world writable, owned by the server UID, the process is running as root,AONOHAKO_TRUSTED_RUNNER_INGRESS=trueis asserted, and the helper queue is single-slot.cloudrun + remote + noneis the supported Cloud Run control-plane shape when/compileand/executeshould be forwarded to a separate hardened runner. It still requires a boundedAONOHAKO_WORK_ROOTbecause the Cloud Run deployment contract requires a dedicated, bounded workspace root; local untrusted compile and execute work is forwarded to the remote runner.selfhosted + embedded + helperapplies the same dedicated work-root contract for local root-backed containers and VMs, includingAONOHAKO_MAX_ACTIVE_RUNS=1because separate requests still reuse the same sandbox UID. Its work root must be a dedicated bounded tmpfs mount with byte and inode ceilings. Because only one request runs at a time, that kernel backing-store ceiling also covers unlinked-open files and write/unlink bursts that directory scanning cannot observe. A delegated cgroup v2 parent is also mandatory, so compiler children and process-spawning runtime wrappers stay inside aggregate CPU, memory, pids, and cleanup accounting. Interactive peers within one request use the distinct fixed role identities described above.dev + remote + noneis the non-root development path. The local server forwards/compileand/executeto a remote hardened runner instead of building or running untrusted inputs locally.dev + embedded + helperremains available through the compatibility mode, but/executestill requires root because the local helper sandbox is root-backed.- for higher-throughput self-hosted deployments, keep helper-backed runners at one active execution each and scale a remote runner pool horizontally instead of increasing helper slots inside one process. See docs/selfhosted.md.
For Cloud Run deployments, use this baseline:
AONOHAKO_DEPLOYMENT_TARGET=cloudrunAONOHAKO_EXECUTION_TRANSPORT=embeddedAONOHAKO_SANDBOX_BACKEND=helperAONOHAKO_API_BEARER_TOKENset to a strong secret, orAONOHAKO_INBOUND_AUTH=platformonly when an upstream layer enforces inbound authenticationAONOHAKO_PLATFORM_PRINCIPAL_HMAC_SECRETwhen using platform auth outsidedevAONOHAKO_TRUSTED_RUNNER_INGRESS=trueafter configuring private ingress, Cloud Run IAM, mTLS, or an equivalent trusted control-plane boundary- second-generation execution environment
- service concurrency
1 - a bounded in-memory volume mounted at a path such as
/work, withAONOHAKO_WORK_ROOT=/work; setAONOHAKO_REQUIRE_WORK_ROOT_TMPFS=truewhen startup should fail unless that path is actually backed bytmpfs, and setAONOHAKO_WORK_ROOT_MAX_BYTESandAONOHAKO_WORK_ROOT_MAX_FILESto the intended volume byte and inode budgets - container memory sized above the work-root byte budget plus runtime headroom, because Cloud Run/no-cgroup runners rely on the outer container limit as the final OOM boundary
- communication runner memory sized for every participant limit plus the 512 MiB manager and server/work-root headroom; without child cgroups an outer container OOM terminates the instance and cannot be attributed to one participant
AONOHAKO_COMMUNICATION_ENABLED=trueonly on a dedicated communication service; ordinary Cloud Run runners do not advertisecommunication-v1AONOHAKO_COMMUNICATION_MAX_PARTICIPANTS=64(or a smaller dedicated tier limit) to reject oversized communication requests before starting processesAONOHAKO_COMMUNICATION_MEMORY_BUDGET_MB=24576(for the planned 32 GiB service) to reject requests whose declared participant memory plus the 512 MiB manager allowance exceeds the reserved 24 GiB execution budgetAONOHAKO_COMMUNICATION_CPU_COUNT=8,GOMAXPROCS=8, andAONOHAKO_COMMUNICATION_WALL_BUDGET_MS=600000on the dedicated eight-vCPU service; startup rejects a CPU-count mismatch and requests whose contention-adjusted wall allowance cannot finish inside that session budget- a 1 GiB communication work root. Communication participants use a fixed 8 MiB per-process workspace cap, the manager uses 128 MiB, and admission reserves 20% of the work root before any process starts
- Direct VPC egress with
all-trafficrouting and firewall-denied outbound traffic except for explicitly allowed targets - a dedicated service account with no unnecessary IAM permissions and no baked secrets in the image
For a Cloud Run API/control-plane service that forwards /compile and
/execute, use
AONOHAKO_EXECUTION_TRANSPORT=remote,
AONOHAKO_SANDBOX_BACKEND=none, the same bounded AONOHAKO_WORK_ROOT, and a
private AONOHAKO_REMOTE_RUNNER_URL with AONOHAKO_REMOTE_RUNNER_AUTH=bearer
or AONOHAKO_REMOTE_RUNNER_AUTH=cloudrun-idtoken.
Cloud Run's own documentation states that volumes must be configured through
Cloud Run volume mounts and that arbitrary in-container mounting is not
supported, so aonohako does not depend on cgroup creation or mount-based
filesystem isolation when running there.
The default runtime memory profile is locked down for public judge runners. Operators can narrow selected numeric knobs without passing arbitrary runtime flags through requests:
AONOHAKO_JVM_HEAP_PERCENTcontrols the Java/Kotlin-JVM/Clojure/Groovy/Scala-Xmxshare of the request memory limit. Java-family launchers also set direct-memory and metaspace/class-space caps from the request memory limit. Allowed range:25..75, default50.AONOHAKO_JAVA_STACK_SIZE_KBcontrols the Java-only platform-thread stack used for recursive solutions. Allowed range:1024..65536, default16384. The stack remains subject to the request memory limit when pages are committed; Kotlin-JVM, Clojure, Groovy, and Scala keep their existing stack settings.AONOHAKO_GO_MEMORY_RESERVE_MBsubtracts reserved host/runtime memory from Go-based interpreterGOMEMLIMIT. Allowed range:0..256, default32.AONOHAKO_GO_GOGCcontrols Go GC aggressiveness for Go-based interpreters. Allowed range:10..200, default50.AONOHAKO_ERLANG_SCHEDULERScontrols BEAM scheduler count for Erlang/Elixir. Allowed range:1..4, default1.AONOHAKO_ERLANG_ASYNC_THREADScontrols BEAM async thread count for Erlang/Elixir. Allowed range:0..4, default1.AONOHAKO_DOTNET_GC_HEAP_PERCENTcontrols the .NET and PowerShell GC heap hard-limit share of request memory; the runner converts it toDOTNET_GCHeapHardLimit. Allowed range:25..80, default60.dotnet/Dafny use a high finite 2 TiBRLIMIT_FSIZEfloor for CoreCLR/F# compatibility, so their practical disk-burst guard is workspace scanning plus bounded work-root/container storage rather than a tight file-size rlimit.AONOHAKO_KOTLIN_NATIVE_COMPILER_HEAP_MBcontrols Kotlin/Native compiler JVM heap. Allowed range:256..1536, default1024.AONOHAKO_DENO_OLD_SPACE_PERCENTcontrols the Deno/V8 old-space share used for--v8-flags=--max-old-space-size=.... Allowed range:30..75, default60. The pinned Deno archive is checksum-verified, and the explicitJAVASCRIPT_DENOandTYPESCRIPT_DENOprofiles share the same offline, permission-denied runtime; the legacyDENOprofile remains a TypeScript alias.AONOHAKO_NODE_OLD_SPACE_PERCENTcontrols the Node/V8 old-space share of the request memory limit. Allowed range:30..75, default60.AONOHAKO_NODE_MAX_SEMI_SPACE_MBcaps Node/V8 semi-space. Allowed range:1..16, default8.AONOHAKO_NODE_STACK_SIZE_KBsets Node stack size. Allowed range:512..8192, default2048.AONOHAKO_WASMTIME_MEMORY_GUARD_BYTESsets the Wasmtime guard size. Allowed range:65536..16777216, default65536.AONOHAKO_WASMTIME_MAX_WASM_STACK_BYTESsets the Wasmtime wasm stack cap. Allowed range:262144..8388608, default1048576.AONOHAKO_RUNTIME_TUNING_PROFILESmay define named, policy-owned runtime profiles as a JSON object. Each profile inherits the global tuning values and may override the same bounded numeric keys with snake_case names, for example{"low-memory":{"jvm_heap_percent":35,"node_old_space_percent":45},"java-deep":{"java_stack_size_kb":32768}}./compileand/executemay select one withruntime_profileonly whenAONOHAKO_ALLOW_REQUEST_RUNTIME_PROFILE=true; policy-disabled, unknown, or syntactically invalid profile names are rejected.AONOHAKO_PROBLEM_RUNTIME_PROFILESmaps boundedproblem_idstrings to those named profiles, for example{"contest-1/a":"low-memory"}. A mappedproblem_idapplies the profile even when direct request profile selection is disabled; conflictingruntime_profilevalues are rejected. Remote runner pools should receive the same runtime profile config as the control plane when forwarded requests include policy-selected profiles.
Invalid values fail startup. These settings only tune memory-related runtime caps; they do not expose network, filesystem, process, or arbitrary flag controls to submissions.
For non-Cloud-Run control-plane deployments that should still execute safely, use this baseline:
AONOHAKO_DEPLOYMENT_TARGET=devAONOHAKO_EXECUTION_TRANSPORT=remoteAONOHAKO_SANDBOX_BACKEND=noneAONOHAKO_REMOTE_RUNNER_URL=https://<dedicated-runner>- optional
AONOHAKO_REMOTE_RUNNER_AUTH=bearerwithAONOHAKO_REMOTE_RUNNER_TOKEN=..., orAONOHAKO_REMOTE_RUNNER_AUTH=cloudrun-idtokenwhen calling another Cloud Run service
MIT. See LICENSE.