Skip to content

[sec-check] crux agentstep: prompt_path and codex answer file are read into host memory with no size bound #17361

Closed

Description

@hivecommons-hive

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:

  1. 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>.md once per attempt (up to MaxRetries+1 times, growing with the corrective suffix).
  2. codex answer-<attempt>.txt (runAttempt → p.backend.Parse(..., func(rel) { return root.ReadFile(...) }), consumed in codexBackend.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 before extractJSON runs 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_path over the bound → *RequestError naming the limit (same shape as the inline-prompt error).
  • answer file over the bound → the readFile callback returns an error; codexBackend.Parse already treats a failed read as "no file" and falls back to the last agent_message on 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

  1. added
    help wantedDenotes an issue that needs help from a contributor. Must meet "help wanted" guidelines.
    on Nov 9, 2026
  2. added
    hive/likely-doneHive verified that a merged PR references or claims this issue; pending confirmation
    and removed on Nov 9, 2026
  3. hivecommons-hive commented on Nov 9, 2026

    @hivecommons-hive
    ContributorAuthor

    Fixed by #17362 (#17362, merged). Closing because Hive's PR claim metadata says this PR was the full fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent/sec-checkCreated or modified by the sec-check agenthelp wantedDenotes an issue that needs help from a contributor. Must meet "help wanted" guidelines.hive/likely-doneHive verified that a merged PR references or claims this issue; pending confirmationsecurity

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions