Skip to content

merge-guard: extend API-merge mint to curl/wget with a sound argv-parser extractor (supersedes accepted-residual) #1098

Description

@michael-wojcik

RESOLUTION (2026-07-05): WON'T-FIX — a sound curl/wget extractor is structurally impossible

An 8-round dual/triple-independent adversarial investigation (Design D+) concluded that a by-construction-sound curl/wget merge-endpoint extractor is not achievable — for the same reason the denylist failed below: curl/wget's value-flag vocabulary is unbounded. The "sound argv parse" reframing (see Fix direction) does not escape the wall; it relocates it from the flag-denylist to the URL-gather, in both directions:

  • Launder wall (gather): binding "the request-URL positional / the value of the URL flag" requires distinguishing a non-request URL in a flag value (-A '<url>') from a request URL in a flag value (--url <url>). A positionals-only gather skips both (misses --url → launder); gathering flag values counts -A (→ over-block). Distinguishing them requires enumerating every value-flag's semantics = the unbounded vocabulary. Confirmed launder on curl 8.7.1: curl -X PUT .../pulls/6/merge --url .../pulls/9/merge binds PR6 while curl merges 6 AND 9 (baseline mints nothing here, so a cure would introduce this).
  • Over-block wall (mirror): any read-method allow-list is incomplete — -X HEAD / -X OPTIONS are real reads outside it, so a today-correct delete main --next -X HEAD dev compound gets DECLINED (cardinal over-block).

Both directions hit uncompletable enumerations — the structural impossibility this issue's own Fix direction tried to avoid, re-confirmed. Per the SACROSANCT rule "cure only if provably sound, else keep baseline," the gated-but-unmintable baseline is retained (safe: no launder, no over-block; curl/wget merges still route through the user's approval click — today's behavior, no regression). What did prove sound (and would seed any future attempt): target-locality, the sole-mutating-destructive-token bind, char-decode-stability + path-transform-boundedness (by-construction), and the fail-safe method axis. Full 8-round record in the comment below. #1109 (git/refs first-match launder) remains a separately-tracked pre-existing residual.


Class

Feature gap / deferred-cure. PR #1095 landed the API-based PR-merge mint (#1096) gh-api-only after review found the curl/wget endpoint extraction structurally unsound. This tracks extending the cure to curl/wget with a sound extractor. Supersedes the earlier accepted-residual (option-1) approach — that approach is abandoned; do not file it separately.

Background

The first #1096 implementation recognized gh api, curl, and wget merge spellings, extracting the endpoint PR by skipping a denylist of value-taking flags. An adversarial audit proved this denylist is structurally unsound for curl/wget: their flag vocabulary is unbounded and grows per release, so any un-enumerated value-flag can carry a decoy pull-request URL that binds the wrong PR into the approval token — a target-confusion laundering class (52+ leaking flags confirmed, including the most common). PR #1095 removed the curl/wget mint arm; curl/wget merge reverts to its pre-existing gated-but-unmintable state (still read-gated). gh-api remains covered (finite flags + first-positional-endpoint = provably sound).

Desired outcome

A faithful curl/wget API-based PR-merge command mints an approval token that authorizes only that exact merge — closing the pre-existing gated-but-unmintable over-block for those clients without reopening the target-confusion laundering surface.

Fix direction

Do not extend the value-flag denylist (it cannot be a security boundary over an unbounded flag space). Bind the endpoint from a real argv parse (the request-URL positional / the value of the URL flag), not a substring first-match. The extractor must:

  • bind only the pull-request merge endpoint that is the actual request URL, never a decoy in a flag value or another leg;
  • never fail-to-bind a faithful merge (that would be a cardinal-sin over-block);
  • carry a bidirectional certification (no new over-block; no laundering) parametrized over a broad flag corpus, not a fixed sample.

Related

PR #1095; the gh-api-only narrowing and its adversarial audit; the shared per-leg endpoint helper (the sound gh-api reference implementation).

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions