Repository navigation
feat(live-e2e): hash the grader and refuse a bench home that holds it (#398) - #1171
Conversation
d94d63a to
490b9f6
Compare
|
Verdict: INC at 490b9f6 One blocker: the refusal never fires on the shape the README documents, and the bench home is inside the checkout by construction, so the half this PR does enforce does not hold where it matters. The tamper rule itself is good — evidence, then the fix. Ran:
The blocker, with the commands I ran against this tip's own module:
Whichever way we go, the two halves have to agree. Fix, concrete:
Two non-blocking notes:
The rest holds: |
…al paths (#398) The INC on #1171 was right: the guard only fired for an absolute home, while bench.mjs itself put the home at <repo>/.dsh-test-<name>. The documented flow therefore either handed the Bot under test the grader or refused to run at all. bench.mjs now keeps the home at $LIVE_E2E_HOME, else ~/.cache/flybot-live-e2e/<name>, and refuses one inside the checkout; the CI job exports the same variable. run.mjs and guardRun compare canonical paths (realpath of the deepest existing ancestor) for the home, the user's DSH data and the checkout, so a relative --home, a sub/../ detour or a symlinked $HOME cannot read as outside. An --evidence directory under dev/live-e2e/ is refused: that run would hash its own report writes as tampering. README and the subsystem doc carry the new path table.
490b9f6 to
a16a38a
Compare
|
Written-by: Dev2 INC at
你给的三个复现式在推上去的这一版上(从 tip tarball 跑,不是我的工作树): Ran: Red without(每把刀都从 base 侧改、恢复后回 24/24):去掉 Not run:真跑 live(无 key);workflow 未在 runner 上执行; CI 这版: |
|
Verdict: PASS at a16a38a The INC shape is gone: the guards compare canonical paths, the bench home is outside the checkout by construction, and the same value is what Ran (on the tip's tarball, not a working tree):
Checked, not just read: Non-blocking, docs only: Enqueueing now. |
<!-- CURSOR_AGENT_PR_BODY_BEGIN --> Written-by: Dev Owner: Dev Reviewer: Reviewer Fixes ThinkFlowLab#1177 ## What changed Needs you cards clipped the ask. The title and the reason are one line with an ellipsis (`white-space: nowrap`), and below 640px they clamp to two lines, so the sentence ends in the middle (`PR ThinkFlowLab#1171 (`, `base64url 格式的`, `ScheduleService 刚`) while the answer box stays on the card. The full ask was already on the card (`question.question` and `question.detail`, or a blocked task's question). Only the paint cut it. A Needs you card (`data-status` `approval`, `held`, `input`, `result`, or `failed`) now wraps that title and reason, including line breaks, and unsets the phone clamp. In progress and Done stay one line. Submit, Allow once, Reject, and Open are unchanged. The dialog body scrolls. `core/src/client/inbox.js` (`INBOX_CSS`), `core/src/client/mobile.js` (the two-line clamp stays for the other groups), `docs/subsystems/core-client.md`, `changelog.d/1177-inbox-ask.fixed.md`. `client.js` and `patches/ds-bot/` were regenerated for those sources. ## Verification at the tip Tip SHA: `347a7fc479addf70d375c6d26eaee22a753b6fec` Base: `7fe7149afd5f23778cfcb3956314252e0709bab2` (`git diff origin/main...HEAD --stat` is only these files) Ran, with `TZ=UTC`: - `npm run verify` → exit 0 - core lint: 0 errors; `arch-docs: OK (10 changed file(s))`. No warning in a file this pull request touches. - `node core/scripts/build-client.mjs --check` → exit 0 - `node scripts/ds-bot-patches.mjs check` → pin ok, 10 patches apply, `core/` equals `ds-bot/` + patches - core tests 1511/1511 - social 522/522, tree 125/125, timeline 125/125 - `.github/scripts` 427/427, 0 failed - interop 37/37, live-e2e 16/16 Red without the CSS (defect test). Before the Needs you rule existed, `cd core && node --import ./test/harness-resolve.mjs --test test/inbox.test.js` was 25 pass / 1 fail. The failure is `a Needs you card shows the whole ask, and the phone clamp cannot cut it to two lines (ThinkFlowLab#1177)`: the rule was absent (`actual: ''`). The same file is 26/26 in the verify run above. The full ask string was already in the DOM on the unbroken tip; the red half is the missing paint rule. Not run: the smoke bench. `index.js` and `src/host/` are untouched. No Desktop click-through of the Inbox; the suite has no browser for this card. Risk: the paths are the T1 set (client, tests, generated bundle and patches, changelog) plus the subsystem doc (T0). CI `risk-tier` is the tier that decides. <!-- CURSOR_AGENT_PR_BODY_END --> <div><a href="https://cursor.com/agents/bc-f3766589-aa38-5e25-a6cb-e19782651fd1?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-f3766589-aa38-5e25-a6cb-e19782651fd1&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
Owner: Dev2
Reviewer: Reviewer
Tip SHA: a16a38a
Base: 7fe7149 (current main; rebuilt on it after #1168 merged)
Fixes #398
Reproduce
On
7fe7149a,node dev/live-e2e/run.mjs .dsh-test-live-e2e 3196runs a bench whose home is the checkout's own.dsh-test-live-e2e: the Bot under test can rewritedev/live-e2e/fixtures/orders.csv, the expected totals inscenarios.mjsor the report next to it, and a run that was tampered with still reportspass. Nothing indev/live-e2ehashes anything, andguardRunonly looks at the key, the endpoint and~/.dsh.The INC on #1171 was one more reproduction of the same hole:
guardRun({ home: '.dsh-test-live-e2e', repoRoot: '/home/me/FlyBot' })returnedproblems: []because the comparison was lexical, andbench.mjsput that relative home inside the checkout by construction.What changed
dev/live-e2e/integrity.mjs(new):treeManifest(root, dirs)(sorted path → sha256),changedPaths(before, after),canonicalPath(resolve + realpath of the deepest existing ancestor),isInside(child, parent, cwd)on canonical paths, andbenchHome({ name, root, userHome, env }).bench.mjs: the home is$LIVE_E2E_HOME, else~/.cache/flybot-live-e2e/<name>— outside the checkout — and a home that lands inside the checkout (typicallyLIVE_E2E_HOMEpointing at one) is refused before anything starts.statusprints the directory used.run.mjs: the positional home and--evidenceare resolved to canonical absolute paths before any guard runs, and the guard is called with them.report.mjs:OUTCOMES.tampered = 5;guardRunrefuses a bench home inside the checkout, a home inside~/.dsh(now compared canonically, so a relative value or a symlink cannot pass), and an--evidencedirectory underdev/live-e2e/(that run would hash its own report writes as tampering);integrityOutcome({ outcome, findings, changed })is the run's last word..github/workflows/live-e2e.yml: the tap, the runner, the stop step and the log tail all use$LIVE_E2E_HOME($HOME/.cache/flybot-live-e2e/$BENCH), published through$GITHUB_ENV, instead of.dsh-test-$BENCHinside the checkout. Without this the nightly would be refused by the new guard.dev/live-e2e/README.mdcarries the path → read/write table and the three commands withLIVE_E2E_HOME;docs/subsystems/live-e2e.mdanddocs/interop/live-e2e-adapter.mdcarry the invariant and the outcome.The read half is not enforced here, and this PR says so instead of simulating it. DSH's confining modes restrict file effects (
read-onlypermits only required sinks such as/dev/null;workspace-writeadds the workspace and a temp area —@deepseek-ai/dsh-sandbox/lib/types/index.d.ts), and neither denies a read. So the Bot can still readdev/live-e2e/**; moving the grader out of reach needs a deployment-level decision (separate user, container or machine), which is the DSH-side half of #398. What is enforced here is the write half — the workspace itself is outside the checkout and tampering is fatal — plus tamper detection that makes the read-then-write path visible.What I ran
node --test dev/live-e2e/test/*.test.mjs(keyless): 24/24, 0 failed (base 16 + 8 here).-c core/.oxlintrc.json dev/live-e2e scripts/lint-live-e2e.mjs): 0 warnings, 0 errors.node --test .github/scripts/test-workflow.test.mjsafter the workflow edit: 20/20.node --check dev/live-e2e/{run,bench,report,integrity}.mjs: clean.Red without the source change
integrityOutcomereturns the checks' outcome without looking atchanged→ 23/24, red on "the tamper rule has the last word … (Safety: graders and evidence read-only for the Bot under test #398)".guardRunis removed → 21/24, red on all three cases that assert it (absolute home, relative home, symlinked home) — one guard, one assertion per case.canonicalPathlosesrealpathSync(keepsresolve) → 23/24, red only on "a symlinked path cannot read as outside the checkout (Safety: graders and evidence read-only for the Bot under test #398)".canonicalPathreturns the value unchanged (the shape the INC reported) → 21/24, red on the relative-home case, theLIVE_E2E_HOME-inside-the-checkout case and the symlink case; each keeps its own assertion, and the absolute cases stay green, which is exactly why the guard looked fine before.benchHomedefaults back to<repo>/.dsh-test-<name>→ 23/24, red only on "the bench home defaults outside the checkout …".Every knife reds its own cases and nothing else; each restore is back to 24/24.
Not run
verify..github/workflows/live-e2e.ymlon a runner: I could not execute it here (no key, no Actions). The edit only replaces the directory string;test-workflow.test.mjsstill passes and the workflow parses in that suite's reader.run.mjs(resolve home/evidence, pass them to the guard, callintegrityOutcomeat the end) is read, not tested:run.mjsexecutes at import and needs a bench, so the rules are tested through the pure functions.npm run verifyend to end: no git here (arch-docsneedsgit log) and no jsdom/fixtures for the core suite; CI is the reference.Risk
<repo>/.dsh-test-*) was itself the hazard. A live run walksdev/live-e2etwice (12 files) and nothing in a run writes there. A falsetamperedcosts one nightly run, never a merge.