Skip to content

fix(tb): scoring tmpfs, redaction, passed flag, cap-drop - #60

Merged
echobt merged 10 commits into
mainfrom
fix/tb-scoring-tmpfs-redaction-passed-capdrop
Jul 29, 2026
Merged

fix(tb): scoring tmpfs, redaction, passed flag, cap-drop#60
echobt merged 10 commits into
mainfrom
fix/tb-scoring-tmpfs-redaction-passed-capdrop

Conversation

@echobt

@echobt echobt commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix four production defects that made Terminal-Bench score every agent 0/30.
  • Compose /tmp tmpfs mode, public secret redaction, additive passed flag, and own_runner cap-drop trade-off.

Defects and fixes

A — compose tmpfs mode (blocker)

Defect: deploy/compose/docker-compose.yml used long-syntax tmpfs.mode: 1777. Compose sends that as a decimal integer to the Docker API, so runtime mode is 3361 (octal), producing d-wxrwS--t. The validator runs as uid 1000 and cannot write /tmp. Terminal-Bench’s legacy docker build needs a temp context under /tmpContainerBuildError → trial crash.

Empirical Docker evidence (prod host):

Path Result
docker run --mount type=tmpfs,...,tmpfs-mode=1777 drwxrwxrwt CORRECT (swarm_backend.py left untouched)
compose LONG mode: 1777 runtime mode=3361 — reproduces prod
compose SHORT tmpfs: ["/tmp:size=256m,mode=1777"] default sticky world-writable, WRITE_OK as uid 1000

Fix: short syntax matching docker-compose.validator.yml, with a comment that long-syntax mode: 1777 is a decimal footgun.

B — over-broad secret redaction (security-sensitive)

Defect: _public_task_event_text treated - as part of the identifier class, so count-dataset-tokens was fully replaced with [REDACTED_SECRET] in public task events while task_id stayed correct.

Fix: segment-boundary matching for secret/token/… plus a shield over TERMINAL_BENCH_2_1_FALLBACK_TASK_IDS. Real credential shapes still redact.

Security note: existing contract suites remain green:

  • test_frontend_api_contract.py
  • test_submission_status.py
    (fixtures include sk-test-secret, Bearer raw-provider-token, broker-token, tb21-platform-sdk-secret, lease-worker-secret, broker-ref-secret, raw-ref-*).

C — lifecycle completed presented as passed

Defect: summary passed_tasks was correct, but per-task rows only exposed lifecycle status: "completed" + score, so UIs treated score 0 as a pass.

Fix: additive boolean passed on public task results/rows and terminal event metadata when score is present, using the same rule as passed_tasks (score >= 1.0). Does not rename task.completed, rewrite DB rows, or change lifecycle status.

D — upstream prod cap-drop hotpatch

Defect: prod scoring depends on a bind-mounted hotpatch that omits --cap-drop ALL from own_runner task guests (apt/chmod need caps). Image recreate without the hotpatch silently breaks scoring again.

Fix: port into hardening_run_args() — omit only --cap-drop ALL for task guests. Keep no-new-privileges, pids limit, /tmp tmpfs, workspace volume, and writable rootfs. Do not touch master compose cap_drop: ALL.

Isolation trade-off: deliberate and already running in prod as a hotpatch; reviewers should treat this as intentional, not a regression of master hardening.

Test plan

  • uv run pytest tests/unit/test_docker_compose_deploy.py -q
  • uv run --package agent-challenge pytest packages/challenges/agent-challenge/tests/test_frontend_api_contract.py packages/challenges/agent-challenge/tests/test_submission_status.py packages/challenges/agent-challenge/tests/test_own_runner_isolation_invariants.py packages/challenges/agent-challenge/tests/test_public_task_event_redaction.py packages/challenges/agent-challenge/tests/test_public_task_passed_flag.py -q
  • uv run ruff check / ruff format --check on touched Python paths

CI

  • Waiting for required checks to go green on this head SHA.

Notes

  • Did not modify src/base/master/swarm_backend.py (verified correct octal path).
  • Did not touch keyrelease/, prod hosts, or historical evaluation rows.
  • Pre-existing untracked local files left alone.

Summary by CodeRabbit

  • New Features
    • Public task results and events now include a passed flag derived from task scores.
  • Bug Fixes
    • Improved public event text redaction to avoid removing legitimate token-like task identifiers while still redacting genuine secrets.
    • Fixed validator /tmp tmpfs permissions to ensure sticky, world-writable behavior.
  • Refactor
    • Updated task container hardening settings by removing cap-drop ALL from task execution.
  • Tests
    • Added/updated tests covering passed behavior, redaction, tmpfs configuration, and updated isolation expectations.

echobt added 9 commits July 29, 2026 09:11
Regression guard for the compose long-syntax tmpfs.mode decimal footgun
that made /tmp mode 3361 and blocked uid 1000 Terminal-Bench builds.
Compose long-syntax tmpfs.mode: 1777 is decimal (octal 3361). Short
syntax /tmp:size=256m,mode=1777 matches the validator compose and yields
sticky world-writable /tmp for the master validator process.
…ng secrets

Lock both directions: count-dataset-tokens stays verbatim and genuine
credential shapes remain scrubbed from public task-event text.
Match secret/token as hyphen-delimited segments and shield the known
Terminal-Bench task-id catalog so ids like count-dataset-tokens are not
replaced with [REDACTED_SECRET] in public task events.
Contract that score 0.0 serializes passed=false with status completed,
score 1.0 serializes passed=true, and terminal event metadata carries
the same flag when score is present.
Additive boolean derived from score >= 1.0 (same rule as passed_tasks).
Lifecycle status stays completed/failed for compatibility; consumers no
longer treat a zero-score completed task as a pass.
Flip isolation invariants so own_runner task containers assert cap-drop
is absent while no-new-privileges, pids-limit, and tmpfs remain.
…ucceed

Deliberate isolation trade-off already running in prod as a hotpatch:
own_runner task guests need capabilities for apt/chmod inside verifiers.
Master compose cap_drop ALL is unchanged. Keep no-new-privileges, pids
limit, /tmp tmpfs, and writable workspace volume.
Narrow tmpfs options to dict[str, Any] before .get so union-attr is clean.
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 596d02c7-27cd-4d21-9870-6554c9a583d0

📥 Commits

Reviewing files that changed from the base of the PR and between b0ebe1d and 2b225af.

📒 Files selected for processing (1)
  • tests/unit/test_docker_compose_deploy.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit/test_docker_compose_deploy.py

📝 Walkthrough

Walkthrough

The PR adds derived passed fields and safer public event redaction, removes cap-drop ALL from own-runner task containers, and changes the master validator /tmp tmpfs syntax and permissions. Tests update frontend contracts, isolation invariants, redaction behavior, and Compose rendering validation.

Changes

Public task API behavior

Layer / File(s) Summary
Task outcome fields and derivation
packages/challenges/agent-challenge/src/agent_challenge/api/routes.py
Public task results, rows, and event metadata now expose passed, derived from outcomes or scores where available.
Task-event redaction and identifier shielding
packages/challenges/agent-challenge/src/agent_challenge/api/routes.py
Event text shields Terminal-Bench identifiers before applying segment-boundary secret redaction, then restores them.
Public API contract tests
packages/challenges/agent-challenge/tests/test_frontend_api_contract.py, packages/challenges/agent-challenge/tests/test_public_task_passed_flag.py, packages/challenges/agent-challenge/tests/test_public_task_event_redaction.py
Tests cover passed values across task states, score-derived event metadata, identifier preservation, and genuine secret removal.

Task-container capability configuration

Layer / File(s) Summary
Remove task-container capability dropping
packages/challenges/agent-challenge/src/agent_challenge/evaluation/own_runner/container_builder.py
Own-runner hardening arguments no longer include --cap-drop ALL; the legacy constant remains defined.
Capability posture invariants
packages/challenges/agent-challenge/tests/test_own_runner_isolation_invariants.py
Isolation tests now verify cap-drop ALL is absent while other hardening checks remain.

Validator tmpfs configuration

Layer / File(s) Summary
Configure sticky writable validator /tmp
deploy/compose/docker-compose.yml, tests/unit/test_docker_compose_deploy.py
The validator uses /tmp:size=256m,mode=1777, and a Compose rendering test validates the sticky world-writable mode.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main fixes: tmpfs, redaction, passed flag, and cap-drop behavior.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/tb-scoring-tmpfs-redaction-passed-capdrop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@packages/challenges/agent-challenge/tests/test_public_task_event_redaction.py`:
- Around line 26-27: Update the assertions in
test_public_task_event_redaction.py so the identifier-preservation check cannot
be bypassed by the unconditional “count-dataset-tokens” alternative. After the
existing identifier-presence assertion, require that output equals the expected
message and contains no “[REDACTED_SECRET]”, while preserving any intended
event-specific handling established by the surrounding test.

In `@tests/unit/test_docker_compose_deploy.py`:
- Around line 270-277: Update the /tmp mount filtering in the volume-validation
loop to require mount.get("type") == "tmpfs" before evaluating mode and
numeric_ok. Preserve the existing mode fallback and validation behavior for
qualifying tmpfs mounts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e85542b6-fba5-41a5-b3a8-242c5be4ca6f

📥 Commits

Reviewing files that changed from the base of the PR and between 801d730 and b0ebe1d.

📒 Files selected for processing (8)
  • deploy/compose/docker-compose.yml
  • packages/challenges/agent-challenge/src/agent_challenge/api/routes.py
  • packages/challenges/agent-challenge/src/agent_challenge/evaluation/own_runner/container_builder.py
  • packages/challenges/agent-challenge/tests/test_frontend_api_contract.py
  • packages/challenges/agent-challenge/tests/test_own_runner_isolation_invariants.py
  • packages/challenges/agent-challenge/tests/test_public_task_event_redaction.py
  • packages/challenges/agent-challenge/tests/test_public_task_passed_flag.py
  • tests/unit/test_docker_compose_deploy.py

Comment on lines +26 to +27
assert "[REDACTED_SECRET]" not in out or "count-dataset-tokens" in out
assert out == message or "count-dataset-tokens" in out

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the identifier-preservation assertion non-tautological.

After Line [24] proves the identifier is present, Line [26] always passes because its right-hand condition is true. The test can therefore accept preserved task text plus an accidental [REDACTED_SECRET].

Proposed fix
-    assert "[REDACTED_SECRET]" not in out or "count-dataset-tokens" in out
-    assert out == message or "count-dataset-tokens" in out
+    assert out == message
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert "[REDACTED_SECRET]" not in out or "count-dataset-tokens" in out
assert out == message or "count-dataset-tokens" in out
assert out == message
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/challenges/agent-challenge/tests/test_public_task_event_redaction.py`
around lines 26 - 27, Update the assertions in
test_public_task_event_redaction.py so the identifier-preservation check cannot
be bypassed by the unconditional “count-dataset-tokens” alternative. After the
existing identifier-presence assertion, require that output equals the expected
message and contains no “[REDACTED_SECRET]”, while preserving any intended
event-specific handling established by the surrounding test.

Comment on lines +270 to +277
for mount in master.get("volumes") or []:
if not isinstance(mount, dict) or mount.get("target") != "/tmp":
continue
raw_tmpfs = mount.get("tmpfs")
tmpfs_opts: dict[str, Any] = (
raw_tmpfs if isinstance(raw_tmpfs, dict) else {}
)
mode = tmpfs_opts.get("mode", mount.get("mode"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Require the numeric fallback to verify a tmpfs mount.

numeric_ok accepts any /tmp volume with mode 1023; it never checks type == "tmpfs". A future bind or volume mount could therefore make this regression test pass while /tmp is no longer tmpfs.

Suggested fix
     for mount in master.get("volumes") or []:
-        if not isinstance(mount, dict) or mount.get("target") != "/tmp":
+        if (
+            not isinstance(mount, dict)
+            or mount.get("type") != "tmpfs"
+            or mount.get("target") != "/tmp"
+        ):
             continue
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for mount in master.get("volumes") or []:
if not isinstance(mount, dict) or mount.get("target") != "/tmp":
continue
raw_tmpfs = mount.get("tmpfs")
tmpfs_opts: dict[str, Any] = (
raw_tmpfs if isinstance(raw_tmpfs, dict) else {}
)
mode = tmpfs_opts.get("mode", mount.get("mode"))
for mount in master.get("volumes") or []:
if (
not isinstance(mount, dict)
or mount.get("type") != "tmpfs"
or mount.get("target") != "/tmp"
):
continue
raw_tmpfs = mount.get("tmpfs")
tmpfs_opts: dict[str, Any] = (
raw_tmpfs if isinstance(raw_tmpfs, dict) else {}
)
mode = tmpfs_opts.get("mode", mount.get("mode"))
🧰 Tools
🪛 ast-grep (0.45.0)

[info] 270-270: Do not hardcode temporary file or directory names
Context: "/tmp"
Note: [CWE-377] Insecure Temporary File.

(hardcoded-tmp-file)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/test_docker_compose_deploy.py` around lines 270 - 277, Update the
/tmp mount filtering in the volume-validation loop to require mount.get("type")
== "tmpfs" before evaluating mode and numeric_ok. Preserve the existing mode
fallback and validation behavior for qualifying tmpfs mounts.

@echobt
echobt merged commit 6eb9f28 into main Jul 29, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant