You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
An appsettings.json in the working directory widens the --config allow-list: its Repositories entries past the config file's count stay allowed #120
RunAsync in GitBranchStateCache.Tool/Program.cs (~L138–152) adds the --config file after WebApplication.CreateBuilder has already loaded appsettings.json and appsettings.{Environment}.json from the working directory. The comment there gives the intent: "so an explicitly named file beats an appsettings.json that happens to be in the working directory".
For scalar settings it does win. For Upstreams:{name}:Repositories it doesn't, because .NET configuration merges arrays by index. The later file overwrites indexes 0..n-1. Any higher index from appsettings.json survives and gets bound as well.
This is the same merge that #46 fixed between --allow and --config, where ReplaceAllowLists now replaces the list instead of merging it. The appsettings.json → --config path was left as it was.
Run ktsu.GitBranchStateCache.Tool --port 18099 --mirror-root <dir> --config prod.json, then POST /v1/github/studio/<repo>.git/state with no credential. 401 means the repository passed the allow-list and reached admission; 404 means the allow-list refused it.
repo
observed
expected
game
401
401
sandbox
404
404
secret
401
404
other
404
404
Binding the options directly gives Repositories = [studio/game.git, studio/secret.git].
Why it matters
The README says the allow-list is what stops arbitrary callers from making the service hold a permanent mirror clone. Here the operator named one repository in the file they passed explicitly. A leftover dev or base-image appsettings.json in the working directory quietly adds more, and nothing reports it.
Suggested fix / acceptance criteria
Treat each upstream's Repositories as one value that the highest-precedence source replaces whole. For example, after building configuration, take each upstream's list from the last provider that sets any Upstreams:{name}:Repositories:* key, as ReplaceAllowLists already does for --allow.
With the files above, only studio/game.git is allowed.
Add a test alongside AllowFlagTests that layers an appsettings.json-style source under a --config source with a shorter list.
Optional: log at startup which source supplied each upstream's allow-list.
Priority: High. The repository allow-list fails open. A stray appsettings.json in the working directory (a dev leftover, or one baked into a base image) silently adds repositories beyond the ones the operator named in --config. Nothing is logged when that happens. Callers can then make the service hold a mirror of a repository the operator never allowed. The README describes the allow-list as the control against exactly that. Compare With the documented exact allow-list (--allow github=studio/game.git), a client whose remote has no .git gets 404 no-such-repository, despite the #57 fix #96, which fails closed and was rated Medium.
Next step: after building the configuration, take each upstream's Repositories from the highest-precedence provider that sets any Upstreams:{name}:Repositories:* key. Add the layered-source test next to AllowFlagTests, using the sandbox/secret/game fixture from the repro. The optional startup log of which source supplied each list would make this kind of override visible in the future.
Activity
matt-edmondson commentedon Nov 5, 2026
Triage
--allowcombined with--configoverwrites the config file's first allow-list entries instead of appending, silently dropping repositories #46: it is the same array-merge-by-index behaviour, reached through a different configuration source.appsettings.jsonin the working directory (a dev leftover, or one baked into a base image) silently adds repositories beyond the ones the operator named in--config. Nothing is logged when that happens. Callers can then make the service hold a mirror of a repository the operator never allowed. The README describes the allow-list as the control against exactly that. Compare With the documented exact allow-list (--allow github=studio/game.git), a client whose remote has no.gitgets 404 no-such-repository, despite the #57 fix #96, which fails closed and was rated Medium.GitBranchStateCache.Tool/Program.cs(RunAsync, around L138–152), together withReplaceAllowLists. Owner: @matt-edmondson, or whoever landed the--allowcombined with--configoverwrites the config file's first allow-list entries instead of appending, silently dropping repositories #46 fix, since this extends that same replace-whole-list rule to theappsettings*.json→--configboundary.--allowcombined with--configoverwrites the config file's first allow-list entries instead of appending, silently dropping repositories #46 (closed) fixed the--allow↔--configcase only. I found no open PR in GitBranchStateCache that covers this; the open PRs are Refuse a timer-backed timing longer than about 49.7 days [patch] #122, which handles timer bounds for A FetchTimeout, DiffTimeout, ProbeTimeout or MaintenanceInterval over ~49.7 days passes validation, then throws ArgumentOutOfRangeException on every git call or at startup #121, and a dependency bump.Repositoriesfrom the highest-precedence provider that sets anyUpstreams:{name}:Repositories:*key. Add the layered-source test next toAllowFlagTests, using thesandbox/secret/gamefixture from the repro. The optional startup log of which source supplied each list would make this kind of override visible in the future.Generated by Claude Code