Skip to content

gate-5 (route-auth) is satisfied by a COMMENT mentioning the attribute — a false-negative vector in a security gate #196

Description

@rubenvdlinde

What

gate-5 decides whether a routed method has an auth attribute by grepping a window of source lines:

head_block=$(sed -n "${start},${def_line}p" "$path")
if ! echo "$head_block" | grep -qE '#\[(PublicPage|NoAdminRequired|NoCSRFRequired|AuthorizedAdminSetting)\b|@(PublicPage|NoAdminRequired|NoCSRFRequired)\b'; then

That window is raw text. It does not distinguish an attribute from the same characters appearing inside a docblock comment.

So this passes the gate:

/**
 * ADMIN ONLY: `#[NoAdminRequired]` is deliberately NOT used here.
 */
public function analytics(string $productId): JSONResponse

— while the method has no attribute at all.

Why it matters

This is the expensive direction for a security gate: a pass leaves no log, so a method that mentions any of the four attribute names in prose is silently exempt. Anyone can turn the gate off for a method by writing about it. The gate's own header already records finding a false negative of a different shape (the 20-line lookback borrowing a neighbour's attribute) and calls that out as "the expensive direction, and invisible because a pass leaves no log" — this is the same class.

Where it shows

openconnector's ProductSubscriptionsController:

Two methods with identical auth posture, one flagged and one not, decided entirely by prose.

The awkward part

There is no good way for an author to positively declare "admin only" on a plain controller method: absence of #[NoAdminRequired] is the admin gate, and @NoAdminRequired in a docblock would make it non-admin. So documenting the posture is genuinely the only option available — and it happens to also satisfy the regex, which is why this has not been noticed.

Suggested direction

Strip comments before matching, and give the "deliberately admin-only" case an explicit, greppable marker that is not a real attribute name — e.g. an @auth admin-only <reason> tag in the same family as @spec exclude / @e2e exclude. Then:

  • an attribute is an attribute
  • a deliberate admin-only endpoint is declared, not inferred from a missing line
  • prose stops being load-bearing

I have deliberately not changed the gate here: doing so would flag subscribe() and every other method across the fleet currently passing on prose, and that sweep wants to land with the new marker rather than before it.

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