Skip to content

sec(api): /api/info is prefix-matched as public, so authentication is skipped for /api/info/deployment #1797

Description

@cristim

Found during the adversarial review of PR #1796. Pre-existing, currently mitigated, and the only such violation across all 121 routes — filed separately so #1796 can land narrowly.

The defect

isPublicEndpoint (internal/api/middleware.go:22) matches /api/info by prefix. /api/info/deployment is registered AuthUser (internal/api/router.go:363).

Because validateSecurityContext returns early for anything isPublicEndpoint accepts, authentication is skipped entirely for /api/info/deployment — an endpoint that returns the AWS account ID and the Secrets Manager ARN.

The file's own comment states that exact matching exists to prevent precisely this. The prefix entry contradicts it.

Why it is not currently exploitable

Router.Route performs its own AuthUser check as defense in depth, so the request is still rejected. The endpoint is protected by the second of two layers, with the first silently disabled.

That is worth fixing on its own terms. A control that is correct only because a different control happens to catch it is one refactor away from being live, and nothing records the dependency. This repo has hit that shape repeatedly: #1753 (an authorization denial discarded, safe only because two independent facts happened to align) and #1757 itself (a comment asserting CSRF parity that did not exist, with the real enforcement absent).

Fix

Make /api/info an exact match rather than a prefix, consistent with the stated intent of the list. Then confirm nothing else legitimately depends on the prefix behaviour — if another /api/info/* route is genuinely public, it should be listed explicitly rather than inherited.

Verification

Both directions, because a fix that makes /api/info/deployment unreachable would pass a "no longer public" test while breaking a working endpoint:

  • unauthenticated /api/info/deployment -> refused by validateSecurityContext, not only by Router.Route. Assert on which layer refuses, or the test cannot tell the fix from the pre-existing mitigation.
  • unauthenticated /api/info -> still public, if that is intended.
  • authenticated /api/info/deployment -> still succeeds.

A useful mutation: disable Router.Route's AuthUser check and confirm the unauthenticated request is still refused. That is the assertion proving the first layer works, and today it would fail.

Scope note

The reviewer swept all 121 routes and found no other prefix/exact mismatch of this kind, so this is an instance rather than a class.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions