Codex gates at explicit high effort + resolved-config echo (#110) - #115
Conversation
codex-review.sh and manager-review.sh silently inherited whatever effort the operator's personal Codex CLI/config defaulted to (often low) — an unintended weakening of the merge-gating review and manager-debate gates. Both scripts now source config/models.conf (resolved from their own control-plane location) and, when reviewing/debating a target repo that has committed its own .fabrica/models.conf, source that AFTER the defaults from the exact fetched/anchored worktree the review runs against. -c model_reasoning_effort is always passed explicitly; -m is passed only when a model is resolved, with the existing CLI -m flag kept in precedence over FABRICA_CODEX_MODEL. The resolved model + effort are echoed into the posted PR comment / issue comment header (`reviewer: <model> @ <effort>`) so every review/debate documents what gated it. A missing/unsourceable config fails loudly, pointing at scripts/doctor.sh, instead of silently reviewing at unknown effort. Also fixes a portability gotcha found while testing this: `if ! . file; then` does not reliably catch a sourcing failure under `set -e` on older bash (e.g. macOS's shipped bash 3.2), which aborts the whole script immediately instead of running the intended error handler. Both sourcing sites now toggle errexit off around the `.` call and check `$?` explicitly, which behaves consistently across bash versions. scripts/test/north-star-gate.test.sh's make_cp_clone helper now also copies config/models.conf into its throwaway control-plane clones, since manager-review.sh requires it to exist relative to its own location. Sync: goes live as soon as the live checkout pulls main (scripts are invoked by absolute path). This raises the review/debate gate from implicitly-inherited low effort to explicit high — a rail-strengthening change approved in the model-tiering design session. Closes #110 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex reviewer (cross-vendor, read-only)Reviewed-head: 294e0b9 Posted verbatim by No actionable findings could be identified because the repository diff could not be inspected: the execution environment failed to start its command host. |
Codex reviewer (cross-vendor, read-only)Reviewed-head: 294e0b9 Posted verbatim by No actionable findings could be identified because the repository inspection tool failed to start, so the requested diff could not be examined. |
…onf (#115) An adversarial review of PR #115 found that codex-review.sh sourced a target repo's .fabrica/models.conf directly from the PR head's own detached worktree into the main (non-sandboxed) harness shell. That let a malicious PR (1) run arbitrary shell with the operator's gh/codex credentials via `source`, or (2) downgrade its own review by committing FABRICA_REVIEW_EFFORT=low, with no allowlist/floor to stop it. Fixes: - Trust anchor: codex-review.sh now reads the override from the gh-bound DEFAULT branch, fetched fresh into a private per-run ref (mirroring manager-review.sh's existing anchor) -- never the untrusted PR head. - Parse, not source: both scripts read the override via a new strict, non-evaluating parser (mc_parse_target_override in the new scripts/lib/models-conf.sh) instead of `source`/`.`. Only FABRICA_<allowedkey>=<value> lines with a tight charset are recognized; everything else is silently ignored, never executed. - Gate keys are not target-overridable: the parser recognizes FABRICA_REVIEW_EFFORT/FABRICA_DEBATE_EFFORT but never applies them from a target override, printing a warning that is folded into the posted PR/issue comment header. Only the producer/model keys (CODER/HANDS/CODEX model) can be overridden. Tests: new scripts/test/models-conf-parser.test.sh (44 assertions, isolated parser unit tests covering the malicious-file, gate-key-rejection, and valid-override scenarios, plus static source assertions on both scripts), a new end-to-end case in scripts/test/north-star-gate.test.sh exercising the real manager-review.sh against a malicious target override, and CI/manifest updates to run and track the new files. All existing tests remain green (64 + 197 + 44 = 305 assertions). Manually verified end-to-end against the real codex-review.sh with a hermetic bare-repo/gh/codex harness: a malicious PR-head payload never executes and never affects the resolved model/effort, which come from the trusted default-branch override instead. Docs: reviewer/codex-review.md, reviewer/manager-review.md, README.md, and templates/repo-setup.md updated to describe the trust anchor, parse-not-source, and gate-key-immutability design, and to correct stale "gates don't read this yet" wording. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deploying fabrica with
|
| Latest commit: |
c35bf1d
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://28fc6ef7.fabrica-6yx.pages.dev |
| Branch Preview URL: | https://coder-110-codex-gates.fabrica-6yx.pages.dev |
Revision: fixed P1 from the adversarial reviewThe P1. The fix — two parts, applied to both scripts:
Tests added:
Docs updated: Confirmation both P1 scenarios are now dead:
|
…afely
manager-review.sh's per-target .fabrica/models.conf override (P1 fix on this
same PR) was still read via `mc_parse_target_override < "$worktree/.fabrica/
models.conf"` — a `<`-redirect from the checked-out worktree path, which
follows symlinks. A target committing that file as a symlink to an arbitrary
operator-local regular file would pass `[ -f ]` and leak the pointed-to
file's FABRICA_CODEX_MODEL value into the public issue comment header.
Switch to `git show "${head_commit}:.fabrica/models.conf"` piped through a
here-string into mc_parse_target_override, mirroring codex-review.sh's
already symlink-safe read of the same file (a symlinked blob resolves to the
link-target-path string, which fails the parser's charset check and is
ignored) — same anchor commit, no new fetch.
Add scripts/test/north-star-gate.test.sh (22): a target commits
.fabrica/models.conf as a symlink to a sentinel file carrying a valid
FABRICA_CODEX_MODEL= line, and the gate's output must never contain the
sentinel's value. Add matching static source assertions to
scripts/test/models-conf-parser.test.sh (h), and fix a stale comment that
described manager-review.sh's now-removed file-redirect convention.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Revision: P2 fix — symlink-safe read of the target
|
What
Both
scripts/codex-review.shandscripts/manager-review.shnow:config/models.confresolved relative to the script's own location (this clone's control-plane root, following symlinks — never a hardcoded personal path)..fabrica/models.conf, source it AFTER the shipped defaults — read from the same fetched/anchored worktree the review runs against (the temp detached worktree checked out at the exact PR head / anchored default-branch commit), never the operator's possibly-stale or dirty cwd checkout.-c model_reasoning_effort="$FABRICA_REVIEW_EFFORT"(codex-review.sh) /"$FABRICA_DEBATE_EFFORT"(manager-review.sh).-m <model>is passed only when a model is actually resolved — the existing-mCLI flag keeps precedence overFABRICA_CODEX_MODEL.reviewer: <model> @ <effort>(e.g.reviewer: operator-default @ highwhen nothing was pinned), so every review/debate documents on the record what gated it.config/models.conf(pointing atscripts/doctor.shcheck (k)) rather than silently reviewing/debating at an unknown effort. Same for an unsourceable per-target override.reviewer/codex-review.mdandreviewer/manager-review.mdare updated with a new "Model policy (#110)" section documenting the precedence and header echo.Why
scripts/codex-review.shandscripts/manager-review.shpreviously rancodex execwith no explicit reasoning-effort override, so they silently inherited whatever the operator's personal Codex CLI/config (~/.codex/config.toml) defaulted to — oftenlow. That's an unintended weakening of the merge-gating review and the manager-debate gate, both of which are supposed to always run at maximum capability (spend-by-leverage — seeconfig/models.conf, from #109/PR #113). This raises both gates to an explicit, resolved effort (highby default) and makes the resolution auditable in the PR/issue history.A portability fix found along the way
While testing the new sourcing logic I found that
if ! . "$file"; then …does not reliably catch a sourcing failure underset -eon older bash (e.g. macOS's shipped/bin/bash3.2.57) — the shell aborts the whole script immediately with the sourced command's own exit code, bypassing the intendedthenbranch entirely. Both scripts now toggleset +earound the.call and check$?explicitly, which behaves consistently across bash versions. Verified this concretely with isolated repros before landing the fix (see commit message).Test plan
shellcheck -x -S styleclean against the CI-pinned 0.9.0 release (downloaded the static binary; local install is 0.11.0 and was not used for the check).bash scripts/test/north-star-resolver.test.sh— 64/64 pass.bash scripts/test/north-star-gate.test.sh— 190/190 pass (this suite exercisesmanager-review.shend-to-end;make_cp_clonewas updated to also stageconfig/models.confinto its throwaway control-plane clones, since the script now requires it relative to its own location).gh/codexonPATH, real git, throwaway bare remote simulating a PR) covering both scripts: default run (reviewer: operator-default @ high), CLI-mprecedence, per-target.fabrica/models.confoverride applying both model and effort, CLI-mstill beating an override's model, a dirty uncommitted local override being correctly ignored (worktree/committed state wins), and the missing/unsourceable-config failure paths.Closes #110
Sync: goes live as soon as the live checkout pulls main (scripts are invoked by absolute path). This raises the review/debate gate from implicitly-inherited low effort to explicit high — a rail-strengthening change approved in the model-tiering design session.