Skip to content

Azure DevOps repos with a space in the project name ("My Project/_git/game") pass startup validation, then every request 404s with nothing logged #62

Description

@matt-edmondson

What's wrong

MirrorStore.IsSafeSegment (GitBranchStateCache/Mirrors/MirrorStore.cs:148-163) only allows [A-Za-z0-9._-] in each path segment. Its remarks (lines ~137-141) justify this with "every repository path a forge actually issues is already within this set". That is not true for Azure DevOps, where project names, and repository names, routinely contain spaces (e.g. https://dev.azure.com/myorg/My Project/_git/game).

Nothing earlier rejects such a path:

  • RepositoryPattern and GitBranchStateCacheOptionsValidator accept "My Project/_git/game" as an allow-list entry. The README says startup validation "reports every problem at once".
  • UpstreamUrl.TryCombine would escape the path correctly to %20.

So the service starts cleanly with a configuration that can never serve that repository.

At request time, BranchStateHandler.Resolve (Endpoints/BranchStateHandler.cs:412-416) returns null without logging when mirrors.TryResolve fails. Unknown-upstream and not-allow-listed requests both log (lines ~400, ~406). The operator sees only 404s from clients and nothing explaining them.

The refusal itself is deliberate (see MirrorStoreTests.TryResolve_UnsafeRepositoryPath_IsRefused, "studio/re po"). The bug is that it is invisible: startup accepts the configuration and runtime rejects it silently.

Reproduction

  • Config: Upstreams:ado:BaseUrl=https://dev.azure.example/myorg, Upstreams:ado:Repositories:0="My Project/_git/game".
  • Request: POST /v1/ado/My%20Project/_git/game/state.
  • Result: 404 {"error":"no-such-repository",...}. The only git invocation was the startup git --version, and no log line mentioned the refusal. This was reproduced with the repo's own ServiceFixture and ScriptedGit.

Suggested fix

  • Minimum:
    • Have the options validator reject any allow-list pattern whose literal segments, or any upstream key, MirrorStore would refuse, with a message naming the entry.
    • Add an EndpointLog line on the TryResolve failure path.
  • Better, for Azure DevOps users: support such names by mapping a segment to a reversible, readable directory name (e.g. percent-encoding the characters outside the safe set), so My Project becomes My%20Project on disk and cannot collide with another repository.

Acceptance criteria

  • A configuration containing "My Project/_git/game" either serves that repository, or fails startup validation with a clear message.
  • A request refused by MirrorStore.TryResolve produces a log entry.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions