Skip to content

gate-25: the auth lookback is a fixed 20 lines, so it reads the previous method's attribute — and prose #363

Description

@rubenvdlinde

check_contract_coverage.py::_method_is_public_endpoint decides whether a controller method is network-facing by regex-searching a fixed 20-line window above the declaration:

def _method_is_public_endpoint(lines, decl_idx):
    start = max(0, decl_idx - 20)
    head = "\n".join(lines[start : decl_idx + 1])
    return bool(_PUBLIC_AUTH_RE.search(head))

The window is not bounded by the previous declaration and does not distinguish an attribute from prose. Two consequences, both live on decidesk development:

1. Neighbour leak — the window reaches into the previous method

SettingsController::setPublicationConfig (declaration L203) is #[AuthorizedAdminSetting(AdminSettings::class)] — admin only. The #[NoAdminRequired] at L183 belongs to getPublicationConfig, the method above it, and L183 is exactly the first line of the 20-line window. Gate-25 reports the admin-only method as a new public endpoint.

2. Prose leak — a sentence about the attribute is read as the attribute

SettingsController::update is admin-only, and its docblock explains precisely that:

* below; it is NOT the #[NoAdminRequired] posture of the read routes.

That sentence, whose whole content is that the method is not NoAdminRequired, makes the gate classify it as public. Same family as .github#358 (gate-19 and gate-26 parse prose) — now in gate-25.

⚠️ The remedy this shape invites is the harmful one. The obvious way to silence #2 is to delete the accurate sentence — degrading documentation to dodge a regex, which is the failure mode x-external-register and x-relation-schema-field were added to gate-54 to avoid.

Direction and blast radius

Ran gate-25's own helper over all 178 public controller methods in decidesk:

  • 4 methods classified public with no public marker anywhere in their own docblock or attributes (3 constructors — harmless, they are not routes — plus setPublicationConfig, which is).
  • 0 false negatives. A PHP attribute must sit adjacent to its declaration, so the window never falls short; it only over-reaches.

So this over-reports rather than opening a green hole, which is the safe direction — but it means "new public endpoint" is not reliable evidence about a method's auth posture, and an author trusting it could change an endpoint's posture to match the gate.

Suggested fix

Bound the lookback at the previous function declaration rather than at a fixed line count, and mask docblock/comment text before matching (the same _strip_ts_comments / _code_mask idea check_e2e_coverage.py already has, applied to PHP). An attribute is adjacent to its declaration by language rule, so the window can be much tighter than 20 lines.

Found while closing decidesk's four gate-25 findings; two of the four were getPublicationConfig (genuinely public) and setPublicationConfig (this false positive). Both are now covered by a real Newman contract collection regardless, so the finding was still worth acting on.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions