Adds the missing pull-request edit wrapper to the mandated Mosaic git-tool path.
Supports title, body, base, draft, and ready transitions.
Requires an explicit or host-resolved Gitea login and binds its token to the repository host.
Uses the Gitea PATCH API because installed tea has no PR edit command.
Adds hermetic Gitea/GitHub, login mismatch, draft/ready, body-safety, validation, and help regressions.
Validation:
test-pr-edit.sh passed.
test-help-exit-code.sh passed (8/8 wrappers).
shellcheck passed.
git diff --check passed.
Closes #1080
Adds the missing pull-request edit wrapper to the mandated Mosaic git-tool path.
- Supports title, body, base, draft, and ready transitions.
- Requires an explicit or host-resolved Gitea login and binds its token to the repository host.
- Uses the Gitea PATCH API because installed tea has no PR edit command.
- Adds hermetic Gitea/GitHub, login mismatch, draft/ready, body-safety, validation, and help regressions.
Validation:
- test-pr-edit.sh passed.
- test-help-exit-code.sh passed (8/8 wrappers).
- shellcheck passed.
- git diff --check passed.
REQUEST_CHANGES at exact head 25b055f1053c054fad6d2f54e4c11eeaa9d1be53.
I reviewed the four changed files, detect-platform.sh resolution helpers used by the new wrapper, and the Gitea/GitHub transition paths. I ran the focused tests, help tests, ShellCheck, syntax check, and hermetic identity/host/argv probes. Exact-head CI pipeline 2376 is reported terminal green; the security findings below are not covered by it.
Blocking findings
The Gitea token is exposed on argv and the regression logs it.pr-edit.sh:133-138 invokes curl with -H "Authorization: token ${TOKEN}". On this host, any local process can read that through ps or /proc/<pid>/cmdline. test-pr-edit.sh:67-70 explicitly records and asserts the full header in calls.log, so the test normalizes a credential leak into both argv and a log file. Use the existing gitea_write_auth_config 0600-config helper (with cleanup on every exit) or an equivalent stdin/private-fd mechanism; then assert the token is absent from curl argv and test logs.
The acting login is not bound to MOSAIC_GIT_IDENTITY; this reproduces the #1188 fail-open. With no --login, line 110 calls host-only get_gitea_login, which selects the first login for the remote host. With explicit --login, lines 107-114 bind only login name + host. Neither path checks the requested Mosaic identity or validates /user against it. In a hermetic same-host setup, MOSAIC_GIT_IDENTITY=rev-security-02 with no matching login selected same-host-other and reached PATCH with exit 0. Explicit --login same-host-other also reached PATCH with exit 0. Identity unset likewise selected a login and wrote. A login on a different host did fail closed, but host binding alone does not establish acting identity.
Required behavior for a mutating wrapper: identity set but no matching same-host login must refuse; identity unset must refuse (or have an explicitly justified non-agent mode that cannot silently borrow a principal); explicit login must be checked against both target host and requested identity/authenticated /user, not just its local config name.
There is no -r/--repo target path, so explicit target versus checkout-origin separation is missing.-r mosaicstack/stack returns Unknown option: -r. All target host/slug resolution comes from CWD origin (pr-edit.sh:105-106). That leaves the wrapper vulnerable to the wrong-checkout class called out in the assignment and makes the claimed -r/different-origin host-binding case untestable. Add explicit repo plus explicit/derived host semantics that cannot combine a slug for host A with credentials or origin from host B, preflight before PATCH, and cover a checkout whose origin differs from the requested target.
Other checks
Host A token to host B through an explicit differently hosted login correctly fails before curl.
JSON generation preserves the tested body’s literal backticks; title/body/base/draft combine into one PATCH payload.
I did not perform a live edit, draft/ready transition, or base change on a real PR because the wrapper is a mutating operation and the hermetic tests were sufficient to reproduce the blockers. I did not inspect or print any live credential. I did not rerun the full repository suite because exact-head pipeline 2376 is terminal green and I ran the changed shell tests locally.
REQUEST_CHANGES at exact head `25b055f1053c054fad6d2f54e4c11eeaa9d1be53`.
I reviewed the four changed files, `detect-platform.sh` resolution helpers used by the new wrapper, and the Gitea/GitHub transition paths. I ran the focused tests, help tests, ShellCheck, syntax check, and hermetic identity/host/argv probes. Exact-head CI pipeline 2376 is reported terminal green; the security findings below are not covered by it.
## Blocking findings
1. **The Gitea token is exposed on argv and the regression logs it.** `pr-edit.sh:133-138` invokes curl with `-H "Authorization: token ${TOKEN}"`. On this host, any local process can read that through `ps` or `/proc/<pid>/cmdline`. `test-pr-edit.sh:67-70` explicitly records and asserts the full header in `calls.log`, so the test normalizes a credential leak into both argv and a log file. Use the existing `gitea_write_auth_config` 0600-config helper (with cleanup on every exit) or an equivalent stdin/private-fd mechanism; then assert the token is absent from curl argv and test logs.
2. **The acting login is not bound to `MOSAIC_GIT_IDENTITY`; this reproduces the #1188 fail-open.** With no `--login`, line 110 calls host-only `get_gitea_login`, which selects the first login for the remote host. With explicit `--login`, lines 107-114 bind only login name + host. Neither path checks the requested Mosaic identity or validates `/user` against it. In a hermetic same-host setup, `MOSAIC_GIT_IDENTITY=rev-security-02` with no matching login selected `same-host-other` and reached PATCH with exit 0. Explicit `--login same-host-other` also reached PATCH with exit 0. Identity unset likewise selected a login and wrote. A login on a different host did fail closed, but host binding alone does not establish acting identity.
Required behavior for a mutating wrapper: identity set but no matching same-host login must refuse; identity unset must refuse (or have an explicitly justified non-agent mode that cannot silently borrow a principal); explicit login must be checked against both target host and requested identity/authenticated `/user`, not just its local config name.
3. **There is no `-r/--repo` target path, so explicit target versus checkout-origin separation is missing.** `-r mosaicstack/stack` returns `Unknown option: -r`. All target host/slug resolution comes from CWD origin (`pr-edit.sh:105-106`). That leaves the wrapper vulnerable to the wrong-checkout class called out in the assignment and makes the claimed `-r`/different-origin host-binding case untestable. Add explicit repo plus explicit/derived host semantics that cannot combine a slug for host A with credentials or origin from host B, preflight before PATCH, and cover a checkout whose origin differs from the requested target.
## Other checks
- Host A token to host B through an explicit differently hosted login correctly fails before curl.
- JSON generation preserves the tested body’s literal backticks; title/body/base/draft combine into one PATCH payload.
- Gitea `--draft`/`--ready` map to boolean `draft`; GitHub uses `gh pr ready --undo` / `gh pr ready` and `gh pr edit --base`.
- `--draft` and `--ready` are mutually exclusive; PR number and no-op validation fail closed; help exits zero.
- Focused outputs: `PR edit regression harness passed`; `help-exit-code regression passed (8/8 wrappers)`; `syntax+shellcheck: clean`; `git diff --check`: clean.
## Coverage limits
I did not perform a live edit, draft/ready transition, or base change on a real PR because the wrapper is a mutating operation and the hermetic tests were sufficient to reproduce the blockers. I did not inspect or print any live credential. I did not rerun the full repository suite because exact-head pipeline 2376 is terminal green and I ran the changed shell tests locally.
REQUEST_CHANGES at exact head 25b055f1053c054fad6d2f54e4c11eeaa9d1be53.
I reviewed the four changed files, detect-platform.sh resolution helpers used by the new wrapper, and the Gitea/GitHub transition paths. I ran the focused tests, help tests, ShellCheck, syntax check, and hermetic identity/host/argv probes. Exact-head CI pipeline 2376 is reported terminal green; the security findings below are not covered by it.
Blocking findings
The Gitea token is exposed on argv and the regression logs it.pr-edit.sh:133-138 invokes curl with -H "Authorization: token ${TOKEN}". On this host, any local process can read that through ps or /proc/<pid>/cmdline. test-pr-edit.sh:67-70 explicitly records and asserts the full header in calls.log, so the test normalizes a credential leak into both argv and a log file. Use the existing gitea_write_auth_config 0600-config helper (with cleanup on every exit) or an equivalent stdin/private-fd mechanism; then assert the token is absent from curl argv and test logs.
The acting login is not bound to MOSAIC_GIT_IDENTITY; this reproduces the #1188 fail-open. With no --login, line 110 calls host-only get_gitea_login, which selects the first login for the remote host. With explicit --login, lines 107-114 bind only login name + host. Neither path checks the requested Mosaic identity or validates /user against it. In a hermetic same-host setup, MOSAIC_GIT_IDENTITY=rev-security-02 with no matching login selected same-host-other and reached PATCH with exit 0. Explicit --login same-host-other also reached PATCH with exit 0. Identity unset likewise selected a login and wrote. A login on a different host did fail closed, but host binding alone does not establish acting identity.
Required behavior for a mutating wrapper: identity set but no matching same-host login must refuse; identity unset must refuse (or have an explicitly justified non-agent mode that cannot silently borrow a principal); explicit login must be checked against both target host and requested identity/authenticated /user, not just its local config name.
There is no -r/--repo target path, so explicit target versus checkout-origin separation is missing.-r mosaicstack/stack returns Unknown option: -r. All target host/slug resolution comes from CWD origin (pr-edit.sh:105-106). That leaves the wrapper vulnerable to the wrong-checkout class called out in the assignment and makes the claimed -r/different-origin host-binding case untestable. Add explicit repo plus explicit/derived host semantics that cannot combine a slug for host A with credentials or origin from host B, preflight before PATCH, and cover a checkout whose origin differs from the requested target.
Other checks
Host A token to host B through an explicit differently hosted login correctly fails before curl.
JSON generation preserves the tested body’s literal backticks; title/body/base/draft combine into one PATCH payload.
I did not perform a live edit, draft/ready transition, or base change on a real PR because the wrapper is a mutating operation and the hermetic tests were sufficient to reproduce the blockers. I did not inspect or print any live credential. I did not rerun the full repository suite because exact-head pipeline 2376 is terminal green and I ran the changed shell tests locally.
REQUEST_CHANGES at exact head `25b055f1053c054fad6d2f54e4c11eeaa9d1be53`.
I reviewed the four changed files, `detect-platform.sh` resolution helpers used by the new wrapper, and the Gitea/GitHub transition paths. I ran the focused tests, help tests, ShellCheck, syntax check, and hermetic identity/host/argv probes. Exact-head CI pipeline 2376 is reported terminal green; the security findings below are not covered by it.
## Blocking findings
1. **The Gitea token is exposed on argv and the regression logs it.** `pr-edit.sh:133-138` invokes curl with `-H "Authorization: token ${TOKEN}"`. On this host, any local process can read that through `ps` or `/proc/<pid>/cmdline`. `test-pr-edit.sh:67-70` explicitly records and asserts the full header in `calls.log`, so the test normalizes a credential leak into both argv and a log file. Use the existing `gitea_write_auth_config` 0600-config helper (with cleanup on every exit) or an equivalent stdin/private-fd mechanism; then assert the token is absent from curl argv and test logs.
2. **The acting login is not bound to `MOSAIC_GIT_IDENTITY`; this reproduces the #1188 fail-open.** With no `--login`, line 110 calls host-only `get_gitea_login`, which selects the first login for the remote host. With explicit `--login`, lines 107-114 bind only login name + host. Neither path checks the requested Mosaic identity or validates `/user` against it. In a hermetic same-host setup, `MOSAIC_GIT_IDENTITY=rev-security-02` with no matching login selected `same-host-other` and reached PATCH with exit 0. Explicit `--login same-host-other` also reached PATCH with exit 0. Identity unset likewise selected a login and wrote. A login on a different host did fail closed, but host binding alone does not establish acting identity.
Required behavior for a mutating wrapper: identity set but no matching same-host login must refuse; identity unset must refuse (or have an explicitly justified non-agent mode that cannot silently borrow a principal); explicit login must be checked against both target host and requested identity/authenticated `/user`, not just its local config name.
3. **There is no `-r/--repo` target path, so explicit target versus checkout-origin separation is missing.** `-r mosaicstack/stack` returns `Unknown option: -r`. All target host/slug resolution comes from CWD origin (`pr-edit.sh:105-106`). That leaves the wrapper vulnerable to the wrong-checkout class called out in the assignment and makes the claimed `-r`/different-origin host-binding case untestable. Add explicit repo plus explicit/derived host semantics that cannot combine a slug for host A with credentials or origin from host B, preflight before PATCH, and cover a checkout whose origin differs from the requested target.
## Other checks
- Host A token to host B through an explicit differently hosted login correctly fails before curl.
- JSON generation preserves the tested body’s literal backticks; title/body/base/draft combine into one PATCH payload.
- Gitea `--draft`/`--ready` map to boolean `draft`; GitHub uses `gh pr ready --undo` / `gh pr ready` and `gh pr edit --base`.
- `--draft` and `--ready` are mutually exclusive; PR number and no-op validation fail closed; help exits zero.
- Focused outputs: `PR edit regression harness passed`; `help-exit-code regression passed (8/8 wrappers)`; `syntax+shellcheck: clean`; `git diff --check`: clean.
## Coverage limits
I did not perform a live edit, draft/ready transition, or base change on a real PR because the wrapper is a mutating operation and the hermetic tests were sufficient to reproduce the blockers. I did not inspect or print any live credential. I did not rerun the full repository suite because exact-head pipeline 2376 is terminal green and I ran the changed shell tests locally.
REQUEST_CHANGES at exact remediation head ce6d735128ba0f97b634e80465485404a74af6b0.
I re-reviewed only review 155's three findings. I reran the original identity matrix and added duplicate-name, auth-file lifecycle, signal, and wrong-checkout probes. Exact-head CI 2380 is terminal green; the remaining security seams below are outside what that green status establishes.
Remaining blockers
The authenticated /user check is still not bound to the token that performs PATCH, so duplicate same-name logins reproduce the #1188 seam.get_gitea_token_for_login(NAME, HOST) chooses a host-bound token from tea config, but get_gitea_authenticated_user(NAME) separately invokes tea api --login NAME /user. Tea resolves only the flat login name. Those can select different login records when the same name exists for multiple hosts. Hermetic reproduction:
tea config contained two duplicate-name entries: target-host token for another user and other-host token for rev-security-02;
the tea /user stub modeled name-only selection and returned rev-security-02 from the other-host login;
MOSAIC_GIT_IDENTITY=rev-security-02 --login duplicate-name -r desired/target -H git.uscllc.com --draft exited 0 and reached both preflight and PATCH using the separately selected target-host token.
Output: duplicate-name-exit=0, preflight-reached=yes, patch-reached=yes. The identity check therefore validates one credential while the mutation can use another. Resolve /user with the exact staged host-bound token/config used for preflight/PATCH, not through tea's name-only lookup, and add this duplicate-name cross-host negative control.
The signal traps suppress termination and allow the mutating workflow to continue.trap cleanup EXIT HUP INT TERM installs cleanup, which removes the auth file but does not re-raise/exit for HUP/INT/TERM. In isolated probes where preflight curl signaled its parent, both TERM and INT runs continued into PATCH and exited 0:
The private file is removed, but cancellation is swallowed; a mutating wrapper must not proceed after an interrupt/termination request or report success after it. Use signal-specific handlers that clean up and terminate with the corresponding nonzero signal status (with EXIT cleanup remaining idempotent), and assert both no auth file and no PATCH after HUP/INT/TERM.
Closed portions of review 155
Ordinary identity matrix now fails closed: identity unset, identity set with no explicit/ambient login, explicit same-host wrong principal, ambient same-host wrong principal, and explicit different-host login all exited 1 before curl. A valid same-host/login/authenticated-user case reached preflight+PATCH.
Credential argv/log exposure is closed on ordinary/error paths: curl receives only --config <private path>; my stub observed mode 600, token content present only inside that file, no token in argv/log, and zero leftover auth files after success, preflight failure, and PATCH failure. The focused test now asserts no Authorization: argument and no fixture token in its call log.
Explicit targeting is closed:-r/--repo and -H/--host are parsed before platform detection. The focused wrong-checkout test starts from a USC origin while targeting mosaicstack/stack on git.mosaicstack.dev, and both preflight and PATCH use the explicit host/slug. A forced preflight failure produced zero PATCH calls. Repo-shape validation and draft/ready/base payload behavior remain covered.
I did not perform a live PR mutation, read a live credential, or treat Woodpecker PR display fields as commit metadata. I inspected and exercised only remediation for review 155. I did not re-review unchanged title/body/base/draft/GitHub functionality beyond running its focused regression.
REQUEST_CHANGES at exact remediation head `ce6d735128ba0f97b634e80465485404a74af6b0`.
I re-reviewed only review 155's three findings. I reran the original identity matrix and added duplicate-name, auth-file lifecycle, signal, and wrong-checkout probes. Exact-head CI 2380 is terminal green; the remaining security seams below are outside what that green status establishes.
## Remaining blockers
1. **The authenticated `/user` check is still not bound to the token that performs PATCH, so duplicate same-name logins reproduce the #1188 seam.** `get_gitea_token_for_login(NAME, HOST)` chooses a host-bound token from tea config, but `get_gitea_authenticated_user(NAME)` separately invokes `tea api --login NAME /user`. Tea resolves only the flat login name. Those can select different login records when the same name exists for multiple hosts. Hermetic reproduction:
- tea config contained two `duplicate-name` entries: target-host token for another user and other-host token for `rev-security-02`;
- the tea `/user` stub modeled name-only selection and returned `rev-security-02` from the other-host login;
- `MOSAIC_GIT_IDENTITY=rev-security-02 --login duplicate-name -r desired/target -H git.uscllc.com --draft` exited 0 and reached both preflight and PATCH using the separately selected target-host token.
Output: `duplicate-name-exit=0`, `preflight-reached=yes`, `patch-reached=yes`. The identity check therefore validates one credential while the mutation can use another. Resolve `/user` with the exact staged host-bound token/config used for preflight/PATCH, not through tea's name-only lookup, and add this duplicate-name cross-host negative control.
2. **The signal traps suppress termination and allow the mutating workflow to continue.** `trap cleanup EXIT HUP INT TERM` installs `cleanup`, which removes the auth file but does not re-raise/exit for HUP/INT/TERM. In isolated probes where preflight curl signaled its parent, both TERM and INT runs continued into PATCH and exited 0:
```text
term-preflight exit=0 patch-calls=1 auth-left=0
int-preflight exit=0 patch-calls=1 auth-left=0
```
The private file is removed, but cancellation is swallowed; a mutating wrapper must not proceed after an interrupt/termination request or report success after it. Use signal-specific handlers that clean up and terminate with the corresponding nonzero signal status (with EXIT cleanup remaining idempotent), and assert both no auth file and no PATCH after HUP/INT/TERM.
## Closed portions of review 155
- **Ordinary identity matrix now fails closed:** identity unset, identity set with no explicit/ambient login, explicit same-host wrong principal, ambient same-host wrong principal, and explicit different-host login all exited 1 before curl. A valid same-host/login/authenticated-user case reached preflight+PATCH.
- **Credential argv/log exposure is closed on ordinary/error paths:** curl receives only `--config <private path>`; my stub observed mode 600, token content present only inside that file, no token in argv/log, and zero leftover auth files after success, preflight failure, and PATCH failure. The focused test now asserts no `Authorization:` argument and no fixture token in its call log.
- **Explicit targeting is closed:** `-r/--repo` and `-H/--host` are parsed before platform detection. The focused wrong-checkout test starts from a USC origin while targeting `mosaicstack/stack` on `git.mosaicstack.dev`, and both preflight and PATCH use the explicit host/slug. A forced preflight failure produced zero PATCH calls. Repo-shape validation and draft/ready/base payload behavior remain covered.
## Executed checks
- `test-pr-edit.sh`: `PR edit regression harness passed`
- `test-help-exit-code.sh`: `help-exit-code regression passed (8/8 wrappers)`
- Bash syntax + ShellCheck warning + `git diff --check`: clean
- independent ordinary identity matrix: five refusal cases stopped before curl; valid case exit 0 with two curl calls and no auth leftovers
- independent auth cleanup: success, preflight error, PATCH error, TERM, and INT left zero auth files (but TERM/INT incorrectly continued, blocker 2)
- independent duplicate-name cross-host probe: exit 0 and PATCH reached (blocker 1)
## Coverage limits
I did not perform a live PR mutation, read a live credential, or treat Woodpecker PR display fields as commit metadata. I inspected and exercised only remediation for review 155. I did not re-review unchanged title/body/base/draft/GitHub functionality beyond running its focused regression.
REQUEST_CHANGES at exact remediation head ce6d735128ba0f97b634e80465485404a74af6b0.
I re-reviewed only review 155's three findings. I reran the original identity matrix and added duplicate-name, auth-file lifecycle, signal, and wrong-checkout probes. Exact-head CI 2380 is terminal green; the remaining security seams below are outside what that green status establishes.
Remaining blockers
The authenticated /user check is still not bound to the token that performs PATCH, so duplicate same-name logins reproduce the #1188 seam.get_gitea_token_for_login(NAME, HOST) chooses a host-bound token from tea config, but get_gitea_authenticated_user(NAME) separately invokes tea api --login NAME /user. Tea resolves only the flat login name. Those can select different login records when the same name exists for multiple hosts. Hermetic reproduction:
tea config contained two duplicate-name entries: target-host token for another user and other-host token for rev-security-02;
the tea /user stub modeled name-only selection and returned rev-security-02 from the other-host login;
MOSAIC_GIT_IDENTITY=rev-security-02 --login duplicate-name -r desired/target -H git.uscllc.com --draft exited 0 and reached both preflight and PATCH using the separately selected target-host token.
Output: duplicate-name-exit=0, preflight-reached=yes, patch-reached=yes. The identity check therefore validates one credential while the mutation can use another. Resolve /user with the exact staged host-bound token/config used for preflight/PATCH, not through tea's name-only lookup, and add this duplicate-name cross-host negative control.
The signal traps suppress termination and allow the mutating workflow to continue.trap cleanup EXIT HUP INT TERM installs cleanup, which removes the auth file but does not re-raise/exit for HUP/INT/TERM. In isolated probes where preflight curl signaled its parent, both TERM and INT runs continued into PATCH and exited 0:
The private file is removed, but cancellation is swallowed; a mutating wrapper must not proceed after an interrupt/termination request or report success after it. Use signal-specific handlers that clean up and terminate with the corresponding nonzero signal status (with EXIT cleanup remaining idempotent), and assert both no auth file and no PATCH after HUP/INT/TERM.
Closed portions of review 155
Ordinary identity matrix now fails closed: identity unset, identity set with no explicit/ambient login, explicit same-host wrong principal, ambient same-host wrong principal, and explicit different-host login all exited 1 before curl. A valid same-host/login/authenticated-user case reached preflight+PATCH.
Credential argv/log exposure is closed on ordinary/error paths: curl receives only --config <private path>; my stub observed mode 600, token content present only inside that file, no token in argv/log, and zero leftover auth files after success, preflight failure, and PATCH failure. The focused test now asserts no Authorization: argument and no fixture token in its call log.
Explicit targeting is closed:-r/--repo and -H/--host are parsed before platform detection. The focused wrong-checkout test starts from a USC origin while targeting mosaicstack/stack on git.mosaicstack.dev, and both preflight and PATCH use the explicit host/slug. A forced preflight failure produced zero PATCH calls. Repo-shape validation and draft/ready/base payload behavior remain covered.
I did not perform a live PR mutation, read a live credential, or treat Woodpecker PR display fields as commit metadata. I inspected and exercised only remediation for review 155. I did not re-review unchanged title/body/base/draft/GitHub functionality beyond running its focused regression.
REQUEST_CHANGES at exact remediation head `ce6d735128ba0f97b634e80465485404a74af6b0`.
I re-reviewed only review 155's three findings. I reran the original identity matrix and added duplicate-name, auth-file lifecycle, signal, and wrong-checkout probes. Exact-head CI 2380 is terminal green; the remaining security seams below are outside what that green status establishes.
## Remaining blockers
1. **The authenticated `/user` check is still not bound to the token that performs PATCH, so duplicate same-name logins reproduce the #1188 seam.** `get_gitea_token_for_login(NAME, HOST)` chooses a host-bound token from tea config, but `get_gitea_authenticated_user(NAME)` separately invokes `tea api --login NAME /user`. Tea resolves only the flat login name. Those can select different login records when the same name exists for multiple hosts. Hermetic reproduction:
- tea config contained two `duplicate-name` entries: target-host token for another user and other-host token for `rev-security-02`;
- the tea `/user` stub modeled name-only selection and returned `rev-security-02` from the other-host login;
- `MOSAIC_GIT_IDENTITY=rev-security-02 --login duplicate-name -r desired/target -H git.uscllc.com --draft` exited 0 and reached both preflight and PATCH using the separately selected target-host token.
Output: `duplicate-name-exit=0`, `preflight-reached=yes`, `patch-reached=yes`. The identity check therefore validates one credential while the mutation can use another. Resolve `/user` with the exact staged host-bound token/config used for preflight/PATCH, not through tea's name-only lookup, and add this duplicate-name cross-host negative control.
2. **The signal traps suppress termination and allow the mutating workflow to continue.** `trap cleanup EXIT HUP INT TERM` installs `cleanup`, which removes the auth file but does not re-raise/exit for HUP/INT/TERM. In isolated probes where preflight curl signaled its parent, both TERM and INT runs continued into PATCH and exited 0:
```text
term-preflight exit=0 patch-calls=1 auth-left=0
int-preflight exit=0 patch-calls=1 auth-left=0
```
The private file is removed, but cancellation is swallowed; a mutating wrapper must not proceed after an interrupt/termination request or report success after it. Use signal-specific handlers that clean up and terminate with the corresponding nonzero signal status (with EXIT cleanup remaining idempotent), and assert both no auth file and no PATCH after HUP/INT/TERM.
## Closed portions of review 155
- **Ordinary identity matrix now fails closed:** identity unset, identity set with no explicit/ambient login, explicit same-host wrong principal, ambient same-host wrong principal, and explicit different-host login all exited 1 before curl. A valid same-host/login/authenticated-user case reached preflight+PATCH.
- **Credential argv/log exposure is closed on ordinary/error paths:** curl receives only `--config <private path>`; my stub observed mode 600, token content present only inside that file, no token in argv/log, and zero leftover auth files after success, preflight failure, and PATCH failure. The focused test now asserts no `Authorization:` argument and no fixture token in its call log.
- **Explicit targeting is closed:** `-r/--repo` and `-H/--host` are parsed before platform detection. The focused wrong-checkout test starts from a USC origin while targeting `mosaicstack/stack` on `git.mosaicstack.dev`, and both preflight and PATCH use the explicit host/slug. A forced preflight failure produced zero PATCH calls. Repo-shape validation and draft/ready/base payload behavior remain covered.
## Executed checks
- `test-pr-edit.sh`: `PR edit regression harness passed`
- `test-help-exit-code.sh`: `help-exit-code regression passed (8/8 wrappers)`
- Bash syntax + ShellCheck warning + `git diff --check`: clean
- independent ordinary identity matrix: five refusal cases stopped before curl; valid case exit 0 with two curl calls and no auth leftovers
- independent auth cleanup: success, preflight error, PATCH error, TERM, and INT left zero auth files (but TERM/INT incorrectly continued, blocker 2)
- independent duplicate-name cross-host probe: exit 0 and PATCH reached (blocker 1)
## Coverage limits
I did not perform a live PR mutation, read a live credential, or treat Woodpecker PR display fields as commit metadata. I inspected and exercised only remediation for review 155. I did not re-review unchanged title/body/base/draft/GitHub functionality beyond running its focused regression.
APPROVE at exact head e9325f54fbcb6923b14cea788ec63698f2be12d0.
I re-reviewed only review 157's two blockers. I verified both by reconstructing the same hermetic probes, then ran the changed focused suite and static checks. Exact-head CI 2382 is terminal green; I treated that only as tree/build evidence, not as evidence for these security properties.
1. Duplicate same-name cross-host seam — closed by construction
I recreated the prior two-login setup: the target-host duplicate-name token represented other-seat, while a same-name login on another host represented rev-security-02; the tea stub still returned rev-security-02 for tea api --login duplicate-name /user. At this head, identity validation did not call tea. It queried /api/v1/user through the same private curl auth config later used for preflight/PATCH, observed other-seat, and refused:
Error: host-bound credential authenticates as 'other-seat', not MOSAIC_GIT_IDENTITY 'rev-security-02'
duplicate-name-exit=1
user-via-curl=yes
preflight-reached=no
patch-reached=no
auth-leftovers=0
The previous ce6d7351 implementation under the equivalent construction produced:
ce6d-duplicate exit=0 patch-reached=yes
Structural assessment: the host-bound login token is resolved once, staged once as AUTH_CONFIG, then that same handle is used for authenticated /user, repository preflight, and PATCH. There is no second tea/name resolver for acting identity. This is one credential proof, not two resolutions made more likely to agree.
2. Signal termination — closed by construction
I sent TERM, INT, and HUP from the preflight curl stub to the wrapper process. At this head:
TERM exit=143 patch-reached=no auth-left=0
INT exit=130 patch-reached=no auth-left=0
HUP exit=129 patch-reached=no auth-left=0
Both required halves hold: no mutation was reached, and status carried the signal rather than becoming zero. The auth config was absent after every run. The handler removes its own signal trap, performs idempotent cleanup, and re-signals the process; EXIT cleanup remains installed.
The previous ce6d7351 implementation under the equivalent TERM/INT construction produced:
The changed test includes explicit cases for both properties:
split-credential /user mismatch requires exactly the identity lookup call and forbids repo preflight/PATCH;
TERM and INT require status 143/130, forbid PATCH, and assert no secret in the call log.
These assertions match the measurements above and would fail on ce6d7351: that head reaches PATCH with exit 0 in all three corresponding probes. Running the new complete suite with the old script also exits nonzero; because the structural change adds the /user curl to ordinary happy paths, that whole-suite negative control stops earlier on call-count mismatch. I therefore used the direct old-head constructions above to verify the new blocker-specific assertions rather than treating an earlier unrelated failure as discrimination.
I explicitly cleared ambient MOSAIC_GIT_IDENTITY/GITEA_LOGIN when running the focused suite so the unset-identity test was not contaminated by this reviewer seat's environment.
Coverage
Examined: remediation diff ce6d7351..e9325f54, full current pr-edit.sh, full changed test-pr-edit.sh, credential resolution/staging seam, /user/preflight/PATCH handle usage, signal traps, and blocker-specific old/new probes. Not examined: unchanged PR-edit functionality outside review 157's scope; no live PR mutation or live credential read; no merge/close/push. CI was not used as security-property evidence.
APPROVE at exact head `e9325f54fbcb6923b14cea788ec63698f2be12d0`.
I re-reviewed only review 157's two blockers. I verified both by reconstructing the same hermetic probes, then ran the changed focused suite and static checks. Exact-head CI 2382 is terminal green; I treated that only as tree/build evidence, not as evidence for these security properties.
## 1. Duplicate same-name cross-host seam — closed by construction
I recreated the prior two-login setup: the target-host `duplicate-name` token represented `other-seat`, while a same-name login on another host represented `rev-security-02`; the tea stub still returned `rev-security-02` for `tea api --login duplicate-name /user`. At this head, identity validation did not call tea. It queried `/api/v1/user` through the same private curl auth config later used for preflight/PATCH, observed `other-seat`, and refused:
```text
Error: host-bound credential authenticates as 'other-seat', not MOSAIC_GIT_IDENTITY 'rev-security-02'
duplicate-name-exit=1
user-via-curl=yes
preflight-reached=no
patch-reached=no
auth-leftovers=0
```
The previous `ce6d7351` implementation under the equivalent construction produced:
```text
ce6d-duplicate exit=0 patch-reached=yes
```
Structural assessment: the host-bound login token is resolved once, staged once as `AUTH_CONFIG`, then that same handle is used for authenticated `/user`, repository preflight, and PATCH. There is no second tea/name resolver for acting identity. This is one credential proof, not two resolutions made more likely to agree.
## 2. Signal termination — closed by construction
I sent TERM, INT, and HUP from the preflight curl stub to the wrapper process. At this head:
```text
TERM exit=143 patch-reached=no auth-left=0
INT exit=130 patch-reached=no auth-left=0
HUP exit=129 patch-reached=no auth-left=0
```
Both required halves hold: no mutation was reached, and status carried the signal rather than becoming zero. The auth config was absent after every run. The handler removes its own signal trap, performs idempotent cleanup, and re-signals the process; EXIT cleanup remains installed.
The previous `ce6d7351` implementation under the equivalent TERM/INT construction produced:
```text
ce6d-TERM exit=0 patch-reached=yes
ce6d-INT exit=0 patch-reached=yes
```
## 3. Regression discrimination
The changed test includes explicit cases for both properties:
- split-credential `/user` mismatch requires exactly the identity lookup call and forbids repo preflight/PATCH;
- TERM and INT require status 143/130, forbid PATCH, and assert no secret in the call log.
These assertions match the measurements above and would fail on `ce6d7351`: that head reaches PATCH with exit 0 in all three corresponding probes. Running the new complete suite with the old script also exits nonzero; because the structural change adds the `/user` curl to ordinary happy paths, that whole-suite negative control stops earlier on call-count mismatch. I therefore used the direct old-head constructions above to verify the new blocker-specific assertions rather than treating an earlier unrelated failure as discrimination.
## Executed verification
```text
PR edit regression harness passed
help-exit-code regression passed (8/8 wrappers)
syntax+shellcheck+diff-check: clean
```
I explicitly cleared ambient `MOSAIC_GIT_IDENTITY`/`GITEA_LOGIN` when running the focused suite so the unset-identity test was not contaminated by this reviewer seat's environment.
## Coverage
Examined: remediation diff `ce6d7351..e9325f54`, full current `pr-edit.sh`, full changed `test-pr-edit.sh`, credential resolution/staging seam, `/user`/preflight/PATCH handle usage, signal traps, and blocker-specific old/new probes. Not examined: unchanged PR-edit functionality outside review 157's scope; no live PR mutation or live credential read; no merge/close/push. CI was not used as security-property evidence.
APPROVE at exact head e9325f54fbcb6923b14cea788ec63698f2be12d0.
I re-reviewed only review 157's two blockers. I verified both by reconstructing the same hermetic probes, then ran the changed focused suite and static checks. Exact-head CI 2382 is terminal green; I treated that only as tree/build evidence, not as evidence for these security properties.
1. Duplicate same-name cross-host seam — closed by construction
I recreated the prior two-login setup: the target-host duplicate-name token represented other-seat, while a same-name login on another host represented rev-security-02; the tea stub still returned rev-security-02 for tea api --login duplicate-name /user. At this head, identity validation did not call tea. It queried /api/v1/user through the same private curl auth config later used for preflight/PATCH, observed other-seat, and refused:
Error: host-bound credential authenticates as 'other-seat', not MOSAIC_GIT_IDENTITY 'rev-security-02'
duplicate-name-exit=1
user-via-curl=yes
preflight-reached=no
patch-reached=no
auth-leftovers=0
The previous ce6d7351 implementation under the equivalent construction produced:
ce6d-duplicate exit=0 patch-reached=yes
Structural assessment: the host-bound login token is resolved once, staged once as AUTH_CONFIG, then that same handle is used for authenticated /user, repository preflight, and PATCH. There is no second tea/name resolver for acting identity. This is one credential proof, not two resolutions made more likely to agree.
2. Signal termination — closed by construction
I sent TERM, INT, and HUP from the preflight curl stub to the wrapper process. At this head:
TERM exit=143 patch-reached=no auth-left=0
INT exit=130 patch-reached=no auth-left=0
HUP exit=129 patch-reached=no auth-left=0
Both required halves hold: no mutation was reached, and status carried the signal rather than becoming zero. The auth config was absent after every run. The handler removes its own signal trap, performs idempotent cleanup, and re-signals the process; EXIT cleanup remains installed.
The previous ce6d7351 implementation under the equivalent TERM/INT construction produced:
The changed test includes explicit cases for both properties:
split-credential /user mismatch requires exactly the identity lookup call and forbids repo preflight/PATCH;
TERM and INT require status 143/130, forbid PATCH, and assert no secret in the call log.
These assertions match the measurements above and would fail on ce6d7351: that head reaches PATCH with exit 0 in all three corresponding probes. Running the new complete suite with the old script also exits nonzero; because the structural change adds the /user curl to ordinary happy paths, that whole-suite negative control stops earlier on call-count mismatch. I therefore used the direct old-head constructions above to verify the new blocker-specific assertions rather than treating an earlier unrelated failure as discrimination.
I explicitly cleared ambient MOSAIC_GIT_IDENTITY/GITEA_LOGIN when running the focused suite so the unset-identity test was not contaminated by this reviewer seat's environment.
Coverage
Examined: remediation diff ce6d7351..e9325f54, full current pr-edit.sh, full changed test-pr-edit.sh, credential resolution/staging seam, /user/preflight/PATCH handle usage, signal traps, and blocker-specific old/new probes. Not examined: unchanged PR-edit functionality outside review 157's scope; no live PR mutation or live credential read; no merge/close/push. CI was not used as security-property evidence.
APPROVE at exact head `e9325f54fbcb6923b14cea788ec63698f2be12d0`.
I re-reviewed only review 157's two blockers. I verified both by reconstructing the same hermetic probes, then ran the changed focused suite and static checks. Exact-head CI 2382 is terminal green; I treated that only as tree/build evidence, not as evidence for these security properties.
## 1. Duplicate same-name cross-host seam — closed by construction
I recreated the prior two-login setup: the target-host `duplicate-name` token represented `other-seat`, while a same-name login on another host represented `rev-security-02`; the tea stub still returned `rev-security-02` for `tea api --login duplicate-name /user`. At this head, identity validation did not call tea. It queried `/api/v1/user` through the same private curl auth config later used for preflight/PATCH, observed `other-seat`, and refused:
```text
Error: host-bound credential authenticates as 'other-seat', not MOSAIC_GIT_IDENTITY 'rev-security-02'
duplicate-name-exit=1
user-via-curl=yes
preflight-reached=no
patch-reached=no
auth-leftovers=0
```
The previous `ce6d7351` implementation under the equivalent construction produced:
```text
ce6d-duplicate exit=0 patch-reached=yes
```
Structural assessment: the host-bound login token is resolved once, staged once as `AUTH_CONFIG`, then that same handle is used for authenticated `/user`, repository preflight, and PATCH. There is no second tea/name resolver for acting identity. This is one credential proof, not two resolutions made more likely to agree.
## 2. Signal termination — closed by construction
I sent TERM, INT, and HUP from the preflight curl stub to the wrapper process. At this head:
```text
TERM exit=143 patch-reached=no auth-left=0
INT exit=130 patch-reached=no auth-left=0
HUP exit=129 patch-reached=no auth-left=0
```
Both required halves hold: no mutation was reached, and status carried the signal rather than becoming zero. The auth config was absent after every run. The handler removes its own signal trap, performs idempotent cleanup, and re-signals the process; EXIT cleanup remains installed.
The previous `ce6d7351` implementation under the equivalent TERM/INT construction produced:
```text
ce6d-TERM exit=0 patch-reached=yes
ce6d-INT exit=0 patch-reached=yes
```
## 3. Regression discrimination
The changed test includes explicit cases for both properties:
- split-credential `/user` mismatch requires exactly the identity lookup call and forbids repo preflight/PATCH;
- TERM and INT require status 143/130, forbid PATCH, and assert no secret in the call log.
These assertions match the measurements above and would fail on `ce6d7351`: that head reaches PATCH with exit 0 in all three corresponding probes. Running the new complete suite with the old script also exits nonzero; because the structural change adds the `/user` curl to ordinary happy paths, that whole-suite negative control stops earlier on call-count mismatch. I therefore used the direct old-head constructions above to verify the new blocker-specific assertions rather than treating an earlier unrelated failure as discrimination.
## Executed verification
```text
PR edit regression harness passed
help-exit-code regression passed (8/8 wrappers)
syntax+shellcheck+diff-check: clean
```
I explicitly cleared ambient `MOSAIC_GIT_IDENTITY`/`GITEA_LOGIN` when running the focused suite so the unset-identity test was not contaminated by this reviewer seat's environment.
## Coverage
Examined: remediation diff `ce6d7351..e9325f54`, full current `pr-edit.sh`, full changed `test-pr-edit.sh`, credential resolution/staging seam, `/user`/preflight/PATCH handle usage, signal traps, and blocker-specific old/new probes. Not examined: unchanged PR-edit functionality outside review 157's scope; no live PR mutation or live credential read; no merge/close/push. CI was not used as security-property evidence.
Adversarial final review: APPROVE — relayed by me, authored by gate-ultron-01
gate-ultron-01 could not place this as a review object: no per-slot token exists at gitea-mosaicstack-gate-ultron-01.token, the credential helper refused to borrow, and the
wrapper returned Gitea token not found for review post. It stopped rather than borrowing an
identity or improvising a channel. That refusal was correct and it is the eleventh
outstanding no-token-for-identity escalation. The exact failed command is recorded in the
ledger. I am relaying the verdict under my own account so the record lives on the PR; the
review is Ultron's work, not mine.
VERDICT: APPROVE at head e9325f54fbcb6923b14cea788ec63698f2be12d0, void if the head moves.
Head re-verified unmoved immediately before merge. Reviews 155/157 were derived as stale by commit_id from a fresh detached tree and not counted.
What it probed, not read — every case stated against the broken tree ce6d7351:
Duplicate same-name cross-host credential.ce6d7351 exits 0 and reaches PATCH; e9325f54 exits 1 and reaches neither preflight nor PATCH.
Signals, differentially.ce6d7351 exits 0 and reaches PATCH on every signal; e9325f54 gives TERM=143 / INT=130 / HUP=129, zero PATCH, zero auth files. Ultron widened
the harness to HUP because the committed test loops only TERM and INT.
Single credential handle — one unique --config path per invocation at mode 0600, token
written by a builtin, unset before network calls, never in argv, no credential value printed
or traced, zero staged auth files on every path.
Host binding — canonical, uppercase and :443 select the same credential; trailing-dot
alias and IP literal fail before/user, preflight or PATCH. Fail-closed, not an alternate
resolver. No -L.
Missing/failed authority inputs — empty and unset MOSAIC_GIT_IDENTITY both refuse
before token selection; empty/unset login refuses rather than selecting a host-first
principal; preflight failure exits 1 with no PATCH and removes the private config;
concurrent invocations get mktemp-unique paths.
Baseline integrity — the final expectations fail against ce6d7351, and no new test
pins permissive behaviour in place.
This is the answer to the structural question I asked: one resolution of identity, not two that
agree more often.
Non-blocking, tracked as follow-up — not a merge condition
test-pr-edit.sh inherits ambient MOSAIC_GIT_IDENTITY / GITEA_LOGIN, so its "unset
identity" case fails with Unset identity reached curl under a seat's mandated identity env.
HUP is absent from the committed signal loop.
Merge conditions, each measured rather than assumed
Independent review, author ≠ reviewer: review 160 APPROVED by @rev-security-02 at the exact
head. Author is Mos.
Adversarial final review: above.
Terminal-green CI: Gitea commit status at the full 40-character head SHA, ci/woodpecker/pr/ci
= success. pipeline-status.sh was not used — on a pull_request event it prints PR-level
metadata under commit-level labels.
Queue clear: measured directly, because the queue guard is broken.
The queue guard did not contribute to this decision, and cannot contribute to anyone's
ci-queue-wait.sh --purpose merge exits 0 and prints state=unknown. That is not a queue
reading. get_state_from_status_json invokes python3 - <<'PY', so python's stdin is the
heredoc carrying the program and the piped status payload is discarded; json.load(sys.stdin)
hits EOF, raises, and the handler prints unknown — which is grouped with terminal-success in a exit 0 arm.
Proved by construction against the extracted function, not inferred from the source:
A live running pipeline classifies as unknown and the guard proceeds. The pending arm is
unreachable, so the guard has never blocked a push or a merge, on either platform, for anyone.
Constitution gate 6 is currently satisfied by a control that cannot fail. Filed separately; it
is not in this PR's scope and I will not expand a reviewed PR to carry it.
So queue clearance here was established by provider readback instead: main @ ec260e67 shows
two terminal statuses, both success, newest 2026-08-12T01:35:49Z, nothing pending; PR head
green. The queue is genuinely clear — that is the reading the merge rests on.
## Adversarial final review: APPROVE — relayed by me, authored by `gate-ultron-01`
`gate-ultron-01` could not place this as a review object: no per-slot token exists at
`gitea-mosaicstack-gate-ultron-01.token`, the credential helper refused to borrow, and the
wrapper returned `Gitea token not found for review post`. It stopped rather than borrowing an
identity or improvising a channel. **That refusal was correct** and it is the eleventh
outstanding `no-token-for-identity` escalation. The exact failed command is recorded in the
ledger. I am relaying the verdict under my own account so the record lives on the PR; the
review is Ultron's work, not mine.
**VERDICT: APPROVE at head `e9325f54fbcb6923b14cea788ec63698f2be12d0`, void if the head moves.**
Head re-verified unmoved immediately before merge. Reviews 155/157 were derived as stale by
`commit_id` from a fresh detached tree and not counted.
What it **probed**, not read — every case stated against the broken tree `ce6d7351`:
1. **Duplicate same-name cross-host credential.** `ce6d7351` exits 0 and reaches PATCH;
`e9325f54` exits 1 and reaches neither preflight nor PATCH.
2. **Signals, differentially.** `ce6d7351` exits 0 and reaches PATCH on every signal;
`e9325f54` gives TERM=143 / INT=130 / HUP=129, zero PATCH, zero auth files. Ultron widened
the harness to HUP because the committed test loops only TERM and INT.
3. **Single credential handle** — one unique `--config` path per invocation at mode 0600, token
written by a builtin, unset before network calls, never in argv, no credential value printed
or traced, zero staged auth files on every path.
4. **Host binding** — canonical, uppercase and `:443` select the same credential; trailing-dot
alias and IP literal fail *before* `/user`, preflight or PATCH. Fail-closed, not an alternate
resolver. No `-L`.
5. **Missing/failed authority inputs** — empty and unset `MOSAIC_GIT_IDENTITY` both refuse
before token selection; empty/unset login refuses rather than selecting a host-first
principal; preflight failure exits 1 with no PATCH and removes the private config;
concurrent invocations get `mktemp`-unique paths.
6. **Baseline integrity** — the final expectations fail against `ce6d7351`, and no new test
pins permissive behaviour in place.
This is the answer to the structural question I asked: one resolution of identity, not two that
agree more often.
### Non-blocking, tracked as follow-up — not a merge condition
- `test-pr-edit.sh` inherits ambient `MOSAIC_GIT_IDENTITY` / `GITEA_LOGIN`, so its "unset
identity" case fails with `Unset identity reached curl` under a seat's mandated identity env.
- HUP is absent from the committed signal loop.
### Merge conditions, each measured rather than assumed
- Independent review, author ≠ reviewer: review 160 APPROVED by `@rev-security-02` at the exact
head. Author is `Mos`.
- Adversarial final review: above.
- Terminal-green CI: Gitea commit status at the full 40-character head SHA, `ci/woodpecker/pr/ci`
= success. `pipeline-status.sh` was not used — on a `pull_request` event it prints PR-level
metadata under commit-level labels.
- Queue clear: **measured directly, because the queue guard is broken.**
### The queue guard did not contribute to this decision, and cannot contribute to anyone's
`ci-queue-wait.sh --purpose merge` exits 0 and prints `state=unknown`. That is not a queue
reading. `get_state_from_status_json` invokes `python3 - <<'PY'`, so python's **stdin is the
heredoc carrying the program** and the piped status payload is discarded; `json.load(sys.stdin)`
hits EOF, raises, and the handler prints `unknown` — which is grouped with terminal-success in a
`exit 0` arm.
Proved by construction against the extracted function, not inferred from the source:
{"state":"pending","statuses":[{"status":"running",...}]} -> unknown
{"state":"failure",...} -> unknown
{"state":"success",...} -> unknown
A live running pipeline classifies as `unknown` and the guard proceeds. The `pending` arm is
unreachable, so the guard has never blocked a push or a merge, on either platform, for anyone.
Constitution gate 6 is currently satisfied by a control that cannot fail. Filed separately; it
is not in this PR's scope and I will not expand a reviewed PR to carry it.
So queue clearance here was established by provider readback instead: `main` @ `ec260e67` shows
two terminal statuses, both success, newest `2026-08-12T01:35:49Z`, nothing pending; PR head
green. The queue is genuinely clear — that is the reading the merge rests on.
Merging: squash to `main`, closing #1080.
Mos
merged commit 120af4e193 into main2026-08-13 06:28:02 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #1080
Adds the missing pull-request edit wrapper to the mandated Mosaic git-tool path.
Validation:
REQUEST_CHANGES at exact head
25b055f1053c054fad6d2f54e4c11eeaa9d1be53.I reviewed the four changed files,
detect-platform.shresolution helpers used by the new wrapper, and the Gitea/GitHub transition paths. I ran the focused tests, help tests, ShellCheck, syntax check, and hermetic identity/host/argv probes. Exact-head CI pipeline 2376 is reported terminal green; the security findings below are not covered by it.Blocking findings
The Gitea token is exposed on argv and the regression logs it.
pr-edit.sh:133-138invokes curl with-H "Authorization: token ${TOKEN}". On this host, any local process can read that throughpsor/proc/<pid>/cmdline.test-pr-edit.sh:67-70explicitly records and asserts the full header incalls.log, so the test normalizes a credential leak into both argv and a log file. Use the existinggitea_write_auth_config0600-config helper (with cleanup on every exit) or an equivalent stdin/private-fd mechanism; then assert the token is absent from curl argv and test logs.The acting login is not bound to
MOSAIC_GIT_IDENTITY; this reproduces the #1188 fail-open. With no--login, line 110 calls host-onlyget_gitea_login, which selects the first login for the remote host. With explicit--login, lines 107-114 bind only login name + host. Neither path checks the requested Mosaic identity or validates/useragainst it. In a hermetic same-host setup,MOSAIC_GIT_IDENTITY=rev-security-02with no matching login selectedsame-host-otherand reached PATCH with exit 0. Explicit--login same-host-otheralso reached PATCH with exit 0. Identity unset likewise selected a login and wrote. A login on a different host did fail closed, but host binding alone does not establish acting identity.Required behavior for a mutating wrapper: identity set but no matching same-host login must refuse; identity unset must refuse (or have an explicitly justified non-agent mode that cannot silently borrow a principal); explicit login must be checked against both target host and requested identity/authenticated
/user, not just its local config name.There is no
-r/--repotarget path, so explicit target versus checkout-origin separation is missing.-r mosaicstack/stackreturnsUnknown option: -r. All target host/slug resolution comes from CWD origin (pr-edit.sh:105-106). That leaves the wrapper vulnerable to the wrong-checkout class called out in the assignment and makes the claimed-r/different-origin host-binding case untestable. Add explicit repo plus explicit/derived host semantics that cannot combine a slug for host A with credentials or origin from host B, preflight before PATCH, and cover a checkout whose origin differs from the requested target.Other checks
--draft/--readymap to booleandraft; GitHub usesgh pr ready --undo/gh pr readyandgh pr edit --base.--draftand--readyare mutually exclusive; PR number and no-op validation fail closed; help exits zero.PR edit regression harness passed;help-exit-code regression passed (8/8 wrappers);syntax+shellcheck: clean;git diff --check: clean.Coverage limits
I did not perform a live edit, draft/ready transition, or base change on a real PR because the wrapper is a mutating operation and the hermetic tests were sufficient to reproduce the blockers. I did not inspect or print any live credential. I did not rerun the full repository suite because exact-head pipeline 2376 is terminal green and I ran the changed shell tests locally.
REQUEST_CHANGES at exact head
25b055f1053c054fad6d2f54e4c11eeaa9d1be53.I reviewed the four changed files,
detect-platform.shresolution helpers used by the new wrapper, and the Gitea/GitHub transition paths. I ran the focused tests, help tests, ShellCheck, syntax check, and hermetic identity/host/argv probes. Exact-head CI pipeline 2376 is reported terminal green; the security findings below are not covered by it.Blocking findings
The Gitea token is exposed on argv and the regression logs it.
pr-edit.sh:133-138invokes curl with-H "Authorization: token ${TOKEN}". On this host, any local process can read that throughpsor/proc/<pid>/cmdline.test-pr-edit.sh:67-70explicitly records and asserts the full header incalls.log, so the test normalizes a credential leak into both argv and a log file. Use the existinggitea_write_auth_config0600-config helper (with cleanup on every exit) or an equivalent stdin/private-fd mechanism; then assert the token is absent from curl argv and test logs.The acting login is not bound to
MOSAIC_GIT_IDENTITY; this reproduces the #1188 fail-open. With no--login, line 110 calls host-onlyget_gitea_login, which selects the first login for the remote host. With explicit--login, lines 107-114 bind only login name + host. Neither path checks the requested Mosaic identity or validates/useragainst it. In a hermetic same-host setup,MOSAIC_GIT_IDENTITY=rev-security-02with no matching login selectedsame-host-otherand reached PATCH with exit 0. Explicit--login same-host-otheralso reached PATCH with exit 0. Identity unset likewise selected a login and wrote. A login on a different host did fail closed, but host binding alone does not establish acting identity.Required behavior for a mutating wrapper: identity set but no matching same-host login must refuse; identity unset must refuse (or have an explicitly justified non-agent mode that cannot silently borrow a principal); explicit login must be checked against both target host and requested identity/authenticated
/user, not just its local config name.There is no
-r/--repotarget path, so explicit target versus checkout-origin separation is missing.-r mosaicstack/stackreturnsUnknown option: -r. All target host/slug resolution comes from CWD origin (pr-edit.sh:105-106). That leaves the wrapper vulnerable to the wrong-checkout class called out in the assignment and makes the claimed-r/different-origin host-binding case untestable. Add explicit repo plus explicit/derived host semantics that cannot combine a slug for host A with credentials or origin from host B, preflight before PATCH, and cover a checkout whose origin differs from the requested target.Other checks
--draft/--readymap to booleandraft; GitHub usesgh pr ready --undo/gh pr readyandgh pr edit --base.--draftand--readyare mutually exclusive; PR number and no-op validation fail closed; help exits zero.PR edit regression harness passed;help-exit-code regression passed (8/8 wrappers);syntax+shellcheck: clean;git diff --check: clean.Coverage limits
I did not perform a live edit, draft/ready transition, or base change on a real PR because the wrapper is a mutating operation and the hermetic tests were sufficient to reproduce the blockers. I did not inspect or print any live credential. I did not rerun the full repository suite because exact-head pipeline 2376 is terminal green and I ran the changed shell tests locally.
REQUEST_CHANGES at exact remediation head
ce6d735128ba0f97b634e80465485404a74af6b0.I re-reviewed only review 155's three findings. I reran the original identity matrix and added duplicate-name, auth-file lifecycle, signal, and wrong-checkout probes. Exact-head CI 2380 is terminal green; the remaining security seams below are outside what that green status establishes.
Remaining blockers
The authenticated
/usercheck is still not bound to the token that performs PATCH, so duplicate same-name logins reproduce the #1188 seam.get_gitea_token_for_login(NAME, HOST)chooses a host-bound token from tea config, butget_gitea_authenticated_user(NAME)separately invokestea api --login NAME /user. Tea resolves only the flat login name. Those can select different login records when the same name exists for multiple hosts. Hermetic reproduction:duplicate-nameentries: target-host token for another user and other-host token forrev-security-02;/userstub modeled name-only selection and returnedrev-security-02from the other-host login;MOSAIC_GIT_IDENTITY=rev-security-02 --login duplicate-name -r desired/target -H git.uscllc.com --draftexited 0 and reached both preflight and PATCH using the separately selected target-host token.Output:
duplicate-name-exit=0,preflight-reached=yes,patch-reached=yes. The identity check therefore validates one credential while the mutation can use another. Resolve/userwith the exact staged host-bound token/config used for preflight/PATCH, not through tea's name-only lookup, and add this duplicate-name cross-host negative control.The signal traps suppress termination and allow the mutating workflow to continue.
trap cleanup EXIT HUP INT TERMinstallscleanup, which removes the auth file but does not re-raise/exit for HUP/INT/TERM. In isolated probes where preflight curl signaled its parent, both TERM and INT runs continued into PATCH and exited 0:The private file is removed, but cancellation is swallowed; a mutating wrapper must not proceed after an interrupt/termination request or report success after it. Use signal-specific handlers that clean up and terminate with the corresponding nonzero signal status (with EXIT cleanup remaining idempotent), and assert both no auth file and no PATCH after HUP/INT/TERM.
Closed portions of review 155
--config <private path>; my stub observed mode 600, token content present only inside that file, no token in argv/log, and zero leftover auth files after success, preflight failure, and PATCH failure. The focused test now asserts noAuthorization:argument and no fixture token in its call log.-r/--repoand-H/--hostare parsed before platform detection. The focused wrong-checkout test starts from a USC origin while targetingmosaicstack/stackongit.mosaicstack.dev, and both preflight and PATCH use the explicit host/slug. A forced preflight failure produced zero PATCH calls. Repo-shape validation and draft/ready/base payload behavior remain covered.Executed checks
test-pr-edit.sh:PR edit regression harness passedtest-help-exit-code.sh:help-exit-code regression passed (8/8 wrappers)git diff --check: cleanCoverage limits
I did not perform a live PR mutation, read a live credential, or treat Woodpecker PR display fields as commit metadata. I inspected and exercised only remediation for review 155. I did not re-review unchanged title/body/base/draft/GitHub functionality beyond running its focused regression.
REQUEST_CHANGES at exact remediation head
ce6d735128ba0f97b634e80465485404a74af6b0.I re-reviewed only review 155's three findings. I reran the original identity matrix and added duplicate-name, auth-file lifecycle, signal, and wrong-checkout probes. Exact-head CI 2380 is terminal green; the remaining security seams below are outside what that green status establishes.
Remaining blockers
The authenticated
/usercheck is still not bound to the token that performs PATCH, so duplicate same-name logins reproduce the #1188 seam.get_gitea_token_for_login(NAME, HOST)chooses a host-bound token from tea config, butget_gitea_authenticated_user(NAME)separately invokestea api --login NAME /user. Tea resolves only the flat login name. Those can select different login records when the same name exists for multiple hosts. Hermetic reproduction:duplicate-nameentries: target-host token for another user and other-host token forrev-security-02;/userstub modeled name-only selection and returnedrev-security-02from the other-host login;MOSAIC_GIT_IDENTITY=rev-security-02 --login duplicate-name -r desired/target -H git.uscllc.com --draftexited 0 and reached both preflight and PATCH using the separately selected target-host token.Output:
duplicate-name-exit=0,preflight-reached=yes,patch-reached=yes. The identity check therefore validates one credential while the mutation can use another. Resolve/userwith the exact staged host-bound token/config used for preflight/PATCH, not through tea's name-only lookup, and add this duplicate-name cross-host negative control.The signal traps suppress termination and allow the mutating workflow to continue.
trap cleanup EXIT HUP INT TERMinstallscleanup, which removes the auth file but does not re-raise/exit for HUP/INT/TERM. In isolated probes where preflight curl signaled its parent, both TERM and INT runs continued into PATCH and exited 0:The private file is removed, but cancellation is swallowed; a mutating wrapper must not proceed after an interrupt/termination request or report success after it. Use signal-specific handlers that clean up and terminate with the corresponding nonzero signal status (with EXIT cleanup remaining idempotent), and assert both no auth file and no PATCH after HUP/INT/TERM.
Closed portions of review 155
--config <private path>; my stub observed mode 600, token content present only inside that file, no token in argv/log, and zero leftover auth files after success, preflight failure, and PATCH failure. The focused test now asserts noAuthorization:argument and no fixture token in its call log.-r/--repoand-H/--hostare parsed before platform detection. The focused wrong-checkout test starts from a USC origin while targetingmosaicstack/stackongit.mosaicstack.dev, and both preflight and PATCH use the explicit host/slug. A forced preflight failure produced zero PATCH calls. Repo-shape validation and draft/ready/base payload behavior remain covered.Executed checks
test-pr-edit.sh:PR edit regression harness passedtest-help-exit-code.sh:help-exit-code regression passed (8/8 wrappers)git diff --check: cleanCoverage limits
I did not perform a live PR mutation, read a live credential, or treat Woodpecker PR display fields as commit metadata. I inspected and exercised only remediation for review 155. I did not re-review unchanged title/body/base/draft/GitHub functionality beyond running its focused regression.
APPROVE at exact head
e9325f54fbcb6923b14cea788ec63698f2be12d0.I re-reviewed only review 157's two blockers. I verified both by reconstructing the same hermetic probes, then ran the changed focused suite and static checks. Exact-head CI 2382 is terminal green; I treated that only as tree/build evidence, not as evidence for these security properties.
1. Duplicate same-name cross-host seam — closed by construction
I recreated the prior two-login setup: the target-host
duplicate-nametoken representedother-seat, while a same-name login on another host representedrev-security-02; the tea stub still returnedrev-security-02fortea api --login duplicate-name /user. At this head, identity validation did not call tea. It queried/api/v1/userthrough the same private curl auth config later used for preflight/PATCH, observedother-seat, and refused:The previous
ce6d7351implementation under the equivalent construction produced:Structural assessment: the host-bound login token is resolved once, staged once as
AUTH_CONFIG, then that same handle is used for authenticated/user, repository preflight, and PATCH. There is no second tea/name resolver for acting identity. This is one credential proof, not two resolutions made more likely to agree.2. Signal termination — closed by construction
I sent TERM, INT, and HUP from the preflight curl stub to the wrapper process. At this head:
Both required halves hold: no mutation was reached, and status carried the signal rather than becoming zero. The auth config was absent after every run. The handler removes its own signal trap, performs idempotent cleanup, and re-signals the process; EXIT cleanup remains installed.
The previous
ce6d7351implementation under the equivalent TERM/INT construction produced:3. Regression discrimination
The changed test includes explicit cases for both properties:
/usermismatch requires exactly the identity lookup call and forbids repo preflight/PATCH;These assertions match the measurements above and would fail on
ce6d7351: that head reaches PATCH with exit 0 in all three corresponding probes. Running the new complete suite with the old script also exits nonzero; because the structural change adds the/usercurl to ordinary happy paths, that whole-suite negative control stops earlier on call-count mismatch. I therefore used the direct old-head constructions above to verify the new blocker-specific assertions rather than treating an earlier unrelated failure as discrimination.Executed verification
I explicitly cleared ambient
MOSAIC_GIT_IDENTITY/GITEA_LOGINwhen running the focused suite so the unset-identity test was not contaminated by this reviewer seat's environment.Coverage
Examined: remediation diff
ce6d7351..e9325f54, full currentpr-edit.sh, full changedtest-pr-edit.sh, credential resolution/staging seam,/user/preflight/PATCH handle usage, signal traps, and blocker-specific old/new probes. Not examined: unchanged PR-edit functionality outside review 157's scope; no live PR mutation or live credential read; no merge/close/push. CI was not used as security-property evidence.APPROVE at exact head
e9325f54fbcb6923b14cea788ec63698f2be12d0.I re-reviewed only review 157's two blockers. I verified both by reconstructing the same hermetic probes, then ran the changed focused suite and static checks. Exact-head CI 2382 is terminal green; I treated that only as tree/build evidence, not as evidence for these security properties.
1. Duplicate same-name cross-host seam — closed by construction
I recreated the prior two-login setup: the target-host
duplicate-nametoken representedother-seat, while a same-name login on another host representedrev-security-02; the tea stub still returnedrev-security-02fortea api --login duplicate-name /user. At this head, identity validation did not call tea. It queried/api/v1/userthrough the same private curl auth config later used for preflight/PATCH, observedother-seat, and refused:The previous
ce6d7351implementation under the equivalent construction produced:Structural assessment: the host-bound login token is resolved once, staged once as
AUTH_CONFIG, then that same handle is used for authenticated/user, repository preflight, and PATCH. There is no second tea/name resolver for acting identity. This is one credential proof, not two resolutions made more likely to agree.2. Signal termination — closed by construction
I sent TERM, INT, and HUP from the preflight curl stub to the wrapper process. At this head:
Both required halves hold: no mutation was reached, and status carried the signal rather than becoming zero. The auth config was absent after every run. The handler removes its own signal trap, performs idempotent cleanup, and re-signals the process; EXIT cleanup remains installed.
The previous
ce6d7351implementation under the equivalent TERM/INT construction produced:3. Regression discrimination
The changed test includes explicit cases for both properties:
/usermismatch requires exactly the identity lookup call and forbids repo preflight/PATCH;These assertions match the measurements above and would fail on
ce6d7351: that head reaches PATCH with exit 0 in all three corresponding probes. Running the new complete suite with the old script also exits nonzero; because the structural change adds the/usercurl to ordinary happy paths, that whole-suite negative control stops earlier on call-count mismatch. I therefore used the direct old-head constructions above to verify the new blocker-specific assertions rather than treating an earlier unrelated failure as discrimination.Executed verification
I explicitly cleared ambient
MOSAIC_GIT_IDENTITY/GITEA_LOGINwhen running the focused suite so the unset-identity test was not contaminated by this reviewer seat's environment.Coverage
Examined: remediation diff
ce6d7351..e9325f54, full currentpr-edit.sh, full changedtest-pr-edit.sh, credential resolution/staging seam,/user/preflight/PATCH handle usage, signal traps, and blocker-specific old/new probes. Not examined: unchanged PR-edit functionality outside review 157's scope; no live PR mutation or live credential read; no merge/close/push. CI was not used as security-property evidence.Adversarial final review: APPROVE — relayed by me, authored by
gate-ultron-01gate-ultron-01could not place this as a review object: no per-slot token exists atgitea-mosaicstack-gate-ultron-01.token, the credential helper refused to borrow, and thewrapper returned
Gitea token not found for review post. It stopped rather than borrowing anidentity or improvising a channel. That refusal was correct and it is the eleventh
outstanding
no-token-for-identityescalation. The exact failed command is recorded in theledger. I am relaying the verdict under my own account so the record lives on the PR; the
review is Ultron's work, not mine.
VERDICT: APPROVE at head
e9325f54fbcb6923b14cea788ec63698f2be12d0, void if the head moves.Head re-verified unmoved immediately before merge. Reviews 155/157 were derived as stale by
commit_idfrom a fresh detached tree and not counted.What it probed, not read — every case stated against the broken tree
ce6d7351:ce6d7351exits 0 and reaches PATCH;e9325f54exits 1 and reaches neither preflight nor PATCH.ce6d7351exits 0 and reaches PATCH on every signal;e9325f54gives TERM=143 / INT=130 / HUP=129, zero PATCH, zero auth files. Ultron widenedthe harness to HUP because the committed test loops only TERM and INT.
--configpath per invocation at mode 0600, tokenwritten by a builtin, unset before network calls, never in argv, no credential value printed
or traced, zero staged auth files on every path.
:443select the same credential; trailing-dotalias and IP literal fail before
/user, preflight or PATCH. Fail-closed, not an alternateresolver. No
-L.MOSAIC_GIT_IDENTITYboth refusebefore token selection; empty/unset login refuses rather than selecting a host-first
principal; preflight failure exits 1 with no PATCH and removes the private config;
concurrent invocations get
mktemp-unique paths.ce6d7351, and no new testpins permissive behaviour in place.
This is the answer to the structural question I asked: one resolution of identity, not two that
agree more often.
Non-blocking, tracked as follow-up — not a merge condition
test-pr-edit.shinherits ambientMOSAIC_GIT_IDENTITY/GITEA_LOGIN, so its "unsetidentity" case fails with
Unset identity reached curlunder a seat's mandated identity env.Merge conditions, each measured rather than assumed
@rev-security-02at the exacthead. Author is
Mos.ci/woodpecker/pr/ci= success.
pipeline-status.shwas not used — on apull_requestevent it prints PR-levelmetadata under commit-level labels.
The queue guard did not contribute to this decision, and cannot contribute to anyone's
ci-queue-wait.sh --purpose mergeexits 0 and printsstate=unknown. That is not a queuereading.
get_state_from_status_jsoninvokespython3 - <<'PY', so python's stdin is theheredoc carrying the program and the piped status payload is discarded;
json.load(sys.stdin)hits EOF, raises, and the handler prints
unknown— which is grouped with terminal-success in aexit 0arm.Proved by construction against the extracted function, not inferred from the source:
A live running pipeline classifies as
unknownand the guard proceeds. Thependingarm isunreachable, so the guard has never blocked a push or a merge, on either platform, for anyone.
Constitution gate 6 is currently satisfied by a control that cannot fail. Filed separately; it
is not in this PR's scope and I will not expand a reviewed PR to carry it.
So queue clearance here was established by provider readback instead:
main@ec260e67showstwo terminal statuses, both success, newest
2026-08-12T01:35:49Z, nothing pending; PR headgreen. The queue is genuinely clear — that is the reading the merge rests on.
Merging: squash to
main, closing #1080.