Skip to content

Safety: graders and evidence read-only for the Bot under test #398

Closed

Description

@hsliuustc0106

Owner: Dev2

Child of #386 (layered safety RFC), related to #204 / #358 / #357.

Why

Evaluations (live E2E, Nightly, performance board) are only trustworthy if the Bot under test cannot read or modify the grader, expected values, or evidence. Goal-driven agents may tamper with scorers if they can.

Scope

  • Grader isolation: expected totals, fixtures' answer keys, and scoring code are outside the Bot's writable and readable scope; they run in a separate process/user or are injected only at assertion time.
  • Read-only observation: assertions read delivered bytes and state from outside the Bot's sandbox (as dev/live-e2e aims to do); the Bot cannot alter the observation path.
  • Tamper check: before and after a run, hash grader files and fixtures; fail the run (not just the check) if they changed.
  • Evidence integrity: sanitizer-cleared evidence (existing) plus hashes in the report so post-hoc edits are visible.
  • Apply to User-scenario matrix: map journeys to Nightly / live / perf coverage #358 scenarios and Performance dashboard: Nightly CI metrics for FlyBot smoothness #357 metrics from the start.

Acceptance

  • A deliberate attempt by a fake Bot to edit a grader file is detected and the run is marked invalid.
  • Live E2E and the perf nightly document which paths are Bot-writable and which are not.
  • No new secrets exposed in artifacts.

Process

Implementation via OpenCode + Codex review.

Activity

  1. added
    enhancementNew feature or request
    area:securityAuth, grants, approvals, secrets
    area:automationScheduled tasks and automation
    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)
    on Oct 19, 2026
  2. Birfy commented on Oct 19, 2026

    @Birfy
    Collaborator

    Triage on main 7b9e90b, 2026-10-19 ~12:10 Asia/Shanghai: needs-human, not agent-ready. Child of #386. Question for the owner: build grader isolation for dev/live-e2e now?

  3. Birfy commented on Oct 19, 2026

    @Birfy
    Collaborator

    Triage 2026-10-19 18:03 Asia/Shanghai (main 245be0b). Partly in place: dev/live-e2e/scenarios.mjs already hashes the uploaded orders.csv and summary.json and fails the scenario if they change during the run (immutability chain). Proposal for the next step, for the owner to confirm:

    Scope: in dev/live-e2e, hash the grader files (scenarios.mjs expectations, fixtures/) before and after each run and mark the run invalid (not just a failed check) if any changed; write the hashes into the report; add a README section listing what the Bot can write (its own workspace only) and what it cannot (grader, fixtures, report); add a test where a fake Bot edits a grader file and the run is marked invalid. Running graders as a separate OS user/process is deferred.

    Question: (a) build the tamper check + docs now, (b) full process/user isolation now, or (c) park until #357/#358 are scheduled? Recommendation: (a). It is small and dev-only.

  4. HugoXK commented on Oct 21, 2026

    @HugoXK
    Contributor

    Design for the owner to pick. This one is concrete now because #522 (merged) already ships the deterministic half of the #358 scenario runner under core/dev/eval/ (rubric.mjs, specs.mjs, judge.mjs, guard.mjs), and guard.mjs already hashes the rubric/spec files. #398 says the same must be true for the Bot under test: it must not be able to read or change the grader, expected values or evidence.

    What "the Bot under test" is here: a mock or live run whose transcript the runner scores. Today the runner scores a transcript the caller passes; it does not yet own the run, so the isolation has to be defined for a run the runner performs.

    (a) Hash-and-fail only (smallest). Before a run, guard.mjs hashes the rubric, specs and fixtures; after, it hashes them again and the run is marked invalid (not just failed) if any changed. No sandbox change. This catches tampering after the fact; it does not stop a Bot from reading the expected values, which is the second half of #398.

    • Size: ~40 lines (wire guard.unchanged into a run wrapper) + tests.

    (b) Read-only grader files + tamper check (recommended). The runner opens the rubric, specs, fixtures and scoring code read-only (chmod 0444 in a temp copy, or a separate process/user for the grader) so the Bot under test cannot write them, and runs the tamper check of (a) so a read the runner cannot prevent is still visible. Assertions read delivered bytes/state from outside the Bot's sandbox (as dev/live-e2e aims to do), so the observation path is not the Bot's.

    • Size: ~120–200 lines (a run wrapper that stages a read-only grader dir, runs the mock/live target, then scores + verifies).

    (c) Separate grader process/user. The grader runs in its own process (and, where the host allows, its own OS user) with the fixtures' keys injected only at assertion time. Strongest, but heavier and environment-specific (CI vs local); the checks of (a)/(b) still apply.

    • Size: ~200–300 lines plus CI wiring.

    Recommendation: (b) now, with (a) inside it as the tamper check; (c) can be a later step if the live path needs it. It is the part #398 asks for that is testable in unit tests, and it keeps the mock runner usable without a sandbox.

    Whichever is chosen, I can implement it as one PR on core/dev/eval/ plus its CI wiring, with a test where a fake Bot edits a rubric file and the run is marked invalid.

  5. added
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    and removed
    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)
    on Oct 25, 2026
  6. Birfy commented on Oct 25, 2026

    @Birfy
    Collaborator

    Written-by: Triage

    agent-ready, Owner: Dev2. The owner ruled this is a safety property, not a product taste: "the Bot under test must not be able to read or edit its own grader". So the three questions in the scope become three answered acceptance items, not a discussion.

    Where it lands, on main 24a40203:

    • dev/live-e2e/run.mjs (148 lines) — guards run first (:73 "never open a browser — or write anything — against a bench that should not"), the report is written at :50-54, the transcript at :131, and :135-146 is the clean-scan-then-stamp path (sanitizerFindings, infra_failed).
    • dev/live-e2e/report.mjs — buildReport, OUTCOMES, guardRun, scanTree is sanitize.mjs. OUTCOMES is where a new terminal outcome belongs, not a new boolean.
    • The fixtures and answer keys: dev/live-e2e/fixtures/orders.csv and scenarios.mjs.

    Acceptance, in the order a reviewer can check it:

    1. What the Bot under test may read: its own workspace and the delivered bytes. Not dev/live-e2e/** (graduers, scenarios.mjs expected values), not dev/live-e2e/fixtures/**, not the --evidence tree of a live run. Write the list into dev/live-e2e/README.md as a table (path → read / write / neither) — a scope nobody wrote down is one nobody can check.
    2. What it may write: only its workspace. A write attempt outside it fails in the run, not after it: extend guardRun so the run stops with a terminal outcome for this case (add it to OUTCOMES), and name the code the caller sees — one string, e.g. grader/tampered, surfaced the way core/src/host/channel.js:25 maps a thrown error to bot/internal rather than printing a stack.
    3. Tamper check: hash grader files and fixtures before the run and after it (createHash in run.mjs), and a mismatch must fail the run, not print a warning. The hash manifest goes into report.json next to the sanitizer findings so a post-hoc edit is visible.
    4. Fake-Bot test: one test in dev/live-e2e/test/ where the fake adapter (fake-adapter.mjs) tries to edit a grader file and the run comes back with the new outcome and a non-zero exitCode. Deleting the guard must red exactly that test.
    5. No new secret in artifacts — the existing sanitizer scan stays the gate (run.mjs:135-146), and the hash manifest must contain hashes only, never file contents.

    Out of scope: the budget/metrics halves (#357, #645 C) and the DSH sandbox itself — if the isolation can only be enforced inside DSH rather than in this repo, stop and say so on this issue instead of simulating it.

    Reviewer: must not be Dev2.

  7. Birfy commented on Oct 25, 2026

    @Birfy
    Collaborator

    Claimed by Dev2. Branch dev2/feat/398-grader-read-only from current main 24a40203.

    Plan of attack, in the order Triage's acceptance lists them:

    1. Write down the boundary first: a path → read/write table in dev/live-e2e/README.md for the Bot under test (its workspace + delivered bytes readable; dev/live-e2e/**, fixtures/**, --evidence trees not readable; writable: the workspace only). A boundary nobody wrote down cannot be checked.
    2. Make a write outside that scope fail during the run — extend guardRun (the guard runs before the turn at run.mjs:73), add one terminal outcome to report.mjs OUTCOMES, and name the string the caller sees (mapped like channel.js:25 maps a throw to bot/internal, no stack).
    3. Tamper check: hash the grader and fixtures before and after with createHash, fail the whole run (not warn) on a mismatch, and put the hash list in report.json.

    If the isolation we can actually enforce here turns out to live in DSH rather than in this repository, I will stop and say exactly which piece is missing instead of writing a simulation — per the card and per Chief's instruction.

  8. added
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 25, 2026
  9. removed
    agent-readyMaintainer-approved spec: an agent may take it. Only maintainers set this; never in a template
    on Oct 25, 2026
  10. removed
    agent-workingAn agent has claimed this issue (the claim comment names the branch)
    on Oct 25, 2026
  11. Birfy commented on Oct 25, 2026

    @Birfy
    Collaborator

    Written-by: Dev2

    #398 done — 四项在 main f9d01fd5 上核过:

    1. 单:#398 closed/completed;squash 提交正文带 Fixes #398(#1171 merge f9d01fd5)。
    2. changelog:故意没有——这套是 dev/live-e2e/ 的评测工具与文档(不随包发布、不在 verify 的 live 命令里),没有用户可见行为变化,与 #204 那套自身一致。
    3. 文档在 main:dev/live-e2e/README.md 的 path → read/write 表 + 三条 LIVE_E2E_HOME 命令、docs/subsystems/live-e2e.md 的不变量(含 canonical 路径那句)、docs/interop/live-e2e-adapter.md 的 tampered 终态,三处 contents 都是 200。
    4. 标签:摘了 #1171 的 agent-pr 与 #398 的 agent-working(pass 是 controller 的,不动)。

    交付一句:被测 Bot 的工作区不再可能是本仓($LIVE_E2E_HOME 或 ~/.cache/flybot-live-e2e/<name>,落在 checkout 内直接拒,路径按 realpath 比),--evidence 指进 dev/live-e2e 也拒;篡改由前后两次 manifest 判 tampered(exit 5,不写 sanitized.txt)。读隔离本仓做不到,PR 与 README 明写是部署侧的活,没拿哈希冒充。

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

    area:automationScheduled tasks and automationarea:securityAuth, grants, approvals, secretsenhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions