LATENT: same-named gitea_get_commit_status_json with swapped tail params puts a token in a URL path if ever co-sourced #997

Open
opened 2026-07-31 09:28:16 +00:00 by mos-dt-0 · 0 comments
Collaborator

LATENT (caused nothing tonight): two same-named gitea_get_commit_status_json with swapped tail parameters, one of which puts a token in a URL path

Filing this explicitly as latent. I chased it as the cause of #995's unknown, and it is not the cause of anything — I killed it before filing that issue and said so there. It is recorded here because "not tonight's cause" is orthogonal to "worth a record", and because the way it fails if it ever becomes reachable is bad enough to be worth a tripwire.

The collision

Two files under ~/.config/mosaic/tools/git/ define a function with the same name and incompatible signatures:

File Line Signature
ci-queue-wait.sh 162 gitea_get_commit_status_json(host, repo, sha, token)
pr-ci-wait.sh 141 gitea_get_commit_status_json(host, repo, token, sha)

The last two positional parameters are transposed. Same name, same arity, same types (all strings), so nothing — not the shell, not a linter, not a test — can distinguish a correct call from an inverted one.

Why it is latent, stated as a measurement rather than an assumption

I checked reachability before believing it: zero cross-references between the two files, and neither sources the other. No third file sources both. Under the current call graph the collision cannot occur, so it explains none of tonight's behavior. That is exactly why this is not filed as a bug against either script's present behavior.

Why it is still worth a record

If any future caller sources both files — a shared helper, a new wrapper reusing both, a refactor that consolidates them into a _lib — the second definition silently wins for every subsequent call, and one of the two call sites is then passing sha where the callee reads token and vice versa. The failure is not a clean error:

  • The request goes out with the token interpolated into the URL path where the sha belongs.
  • A URL path is logged server-side, in access logs and often in proxy logs, in a way an Authorization header is not. This turns a transposition bug into a credential-disclosure bug.
  • The response is an HTTP error. curl -fsSL emits nothing on an HTTP error, so the caller sees empty output and, per #995, converts that into unknown and exits 0. The credential has already left the process by the time anything reports a problem.

That last chain is the reason this belongs next to #995 rather than in a style backlog: the collision would be silent at the point of damage and permissive at the point of reporting. A fail-closed check placed after an irreversible side effect only decides what lie to tell about it.

Suggested fix, cheap either way

Rename both to file-scoped names (_ciqw_gitea_commit_status / _prciw_gitea_commit_status), or consolidate to one definition in a shared _lib with a single parameter order and update both call sites. Either kills it permanently. A keyword/named-argument form would be better still, since it makes a transposition impossible rather than merely currently-absent.

Separately, and already noted in #995: both implementations pass the token via curl -H "Authorization: token ${token}" in argv, which is visible in ps to any process on the host. That is a real present-tense exposure, unlike the collision, and it is tracked there.

Provenance of this finding, because it is the useful part

I found this while holding a hypothesis it flattered, and it was elegant and explanatory enough that I had the whole write-up drafted as the root cause. The check that killed it was reachability — run before filing rather than after. That is the third instance tonight, across two seats, of the same tell: the finding flattered the hypothesis I already held. The cheap disconfirming probe, run while the finding still feels good, is what separated a filed falsehood from a filed latency.

Credit to pepper for asking that it be filed as latent rather than discarded.

— mos-dt (sb-it-1-dt). Signed in body; shared account on this host, so the signature is a labelled claim, never provenance.

## LATENT (caused nothing tonight): two same-named `gitea_get_commit_status_json` with swapped tail parameters, one of which puts a token in a URL path Filing this **explicitly as latent**. I chased it as the cause of #995's `unknown`, and it is not the cause of anything — I killed it before filing that issue and said so there. It is recorded here because "not tonight's cause" is orthogonal to "worth a record", and because the way it fails if it ever becomes reachable is bad enough to be worth a tripwire. ### The collision Two files under `~/.config/mosaic/tools/git/` define a function with the **same name** and **incompatible signatures**: | File | Line | Signature | |---|---|---| | `ci-queue-wait.sh` | 162 | `gitea_get_commit_status_json(host, repo, sha, token)` | | `pr-ci-wait.sh` | 141 | `gitea_get_commit_status_json(host, repo, token, sha)` | The last two positional parameters are **transposed**. Same name, same arity, same types (all strings), so nothing — not the shell, not a linter, not a test — can distinguish a correct call from an inverted one. ### Why it is latent, stated as a measurement rather than an assumption I checked reachability before believing it: **zero cross-references between the two files, and neither sources the other.** No third file sources both. Under the current call graph the collision cannot occur, so it explains none of tonight's behavior. That is exactly why this is not filed as a bug against either script's present behavior. ### Why it is still worth a record If any future caller sources both files — a shared helper, a new wrapper reusing both, a refactor that consolidates them into a `_lib` — the second definition silently wins for every subsequent call, and one of the two call sites is then passing `sha` where the callee reads `token` and vice versa. The failure is not a clean error: - The request goes out with the **token interpolated into the URL path** where the sha belongs. - A URL path is **logged server-side**, in access logs and often in proxy logs, in a way an `Authorization` header is not. This turns a transposition bug into a credential-disclosure bug. - The response is an HTTP error. `curl -fsSL` emits **nothing** on an HTTP error, so the caller sees empty output and, per #995, converts that into `unknown` and **exits 0**. The credential has already left the process by the time anything reports a problem. That last chain is the reason this belongs next to #995 rather than in a style backlog: the collision would be silent at the point of damage and permissive at the point of reporting. **A fail-closed check placed after an irreversible side effect only decides what lie to tell about it.** ### Suggested fix, cheap either way Rename both to file-scoped names (`_ciqw_gitea_commit_status` / `_prciw_gitea_commit_status`), or consolidate to one definition in a shared `_lib` with a **single** parameter order and update both call sites. Either kills it permanently. A keyword/named-argument form would be better still, since it makes a transposition impossible rather than merely currently-absent. Separately, and already noted in #995: both implementations pass the token via `curl -H "Authorization: token ${token}"` in **argv**, which is visible in `ps` to any process on the host. That is a real present-tense exposure, unlike the collision, and it is tracked there. ### Provenance of this finding, because it is the useful part I found this while holding a hypothesis it flattered, and it was elegant and explanatory enough that I had the whole write-up drafted as the root cause. The check that killed it was reachability — run **before** filing rather than after. That is the third instance tonight, across two seats, of the same tell: **the finding flattered the hypothesis I already held.** The cheap disconfirming probe, run while the finding still feels good, is what separated a filed falsehood from a filed latency. Credit to **pepper** for asking that it be filed as latent rather than discarded. — mos-dt (sb-it-1-dt). Signed in body; shared account on this host, so the signature is a labelled claim, never provenance.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mosaicstack/stack#997