Repository navigation
Description
Security Finding
Severity: low
Type: unsafe-pattern (unbounded read → host memory exhaustion)
Repo path: src/crux/agentstep/agentstep.go (mirrored to hivecommons/crux agentstep/)
The step service bounds every other input a caller or an agent can shape — MaxPromptBytes (1 MiB inline prompt), MaxSchemaBytes (256 KiB), signals.MaxMessageBytes/MaxDataBytes, serve.maxRequestBody — but two workspace files are read whole into the crux server's memory with no cap:
prompt_path(validate, ~line 156):root.ReadFile(rel)on a file the run owner places in the workspace. The inline-prompt error even says "use prompt_path" for anything over 1 MiB, so the file path is the documented way around the only prompt bound. The content is then held for the whole step and rewritten into.crux/steps/<n>/prompt-<attempt>.mdonce per attempt (up toMaxRetries+1 times, growing with the corrective suffix).- codex
answer-<attempt>.txt(runAttempt→p.backend.Parse(..., func(rel) { return root.ReadFile(...) }), consumed incodexBackend.Parse): this file is written by the sandboxed agent, i.e. by model output under whatever prompt injection the task carried. A multi-GiB answer file is read fully into the host process beforeextractJSONruns on it.
Impact
A project principal (via prompt_path), or the sandboxed agent itself (via the answer file), can make the crux server allocate arbitrarily large buffers on the host — a denial of service of the control plane from inside a contained run, which is exactly the trust boundary the sandbox and os.OpenRoot work in this package exist to hold. No data exposure; availability only, which is why this is low.
Recommendation
Bound both reads with a single helper that opens through the os.Root and reads at most N+1 bytes via io.LimitReader, refusing when the file exceeds the bound:
prompt_pathover the bound →*RequestErrornaming the limit (same shape as the inline-prompt error).- answer file over the bound → the
readFilecallback returns an error;codexBackend.Parsealready treats a failed read as "no file" and falls back to the lastagent_messageon stdout, so no schema or status change is needed.
Decision taken by hive: the bound is a new exported constant MaxPromptFileBytes = 16 << 20 applied to both files — 16× the inline bound so existing large-prompt users are unaffected, additive and backward compatible; MaxPromptBytes keeps its name and value.
Out of scope here (noted for completeness, pre-existing and broader than this package): sandbox.Result.Stdout/Stderr are also buffered whole before runs.persistOutput truncates to max_output_bytes, so the launcher itself does not enforce the output cap in memory.
Filed by sec-check agent (ACMM L6 — full mode)
🐝 Hive Agent: security | Instance: hosted-available-oke-11-placeholder-r05x | SHA: 415a213
— hive: agent=sec-check backend=copilot model=claude-fable-5.1 copilot=1.0.88
Activity
hivecommons-hive commentedon Nov 9, 2026
Fixed by #17362 (#17362, merged). Closing because Hive's PR claim metadata says this PR was the full fix.