@@ -15,11 +15,12 @@ import (
1515// RepoAccessCache caches repository metadata related to lockdown checks so that
1616// multiple tools can reuse the same access information safely across goroutines.
1717type RepoAccessCache struct {
18- client * githubv4.Client
19- mu sync.Mutex
20- cache * cache2go.CacheTable
21- ttl time.Duration
22- logger * slog.Logger
18+ client * githubv4.Client
19+ mu sync.Mutex
20+ cache * cache2go.CacheTable
21+ ttl time.Duration
22+ logger * slog.Logger
23+ trustedBotLogins map [string ]struct {}
2324}
2425
2526type repoAccessCacheEntry struct {
@@ -85,6 +86,9 @@ func GetInstance(client *githubv4.Client, opts ...RepoAccessOption) *RepoAccessC
8586 client : client ,
8687 cache : cache2go .Cache (defaultRepoAccessCacheKey ),
8788 ttl : defaultRepoAccessTTL ,
89+ trustedBotLogins : map [string ]struct {}{
90+ "copilot" : {},
91+ },
8892 }
8993 for _ , opt := range opts {
9094 if opt != nil {
@@ -109,13 +113,22 @@ type CacheStats struct {
109113 Evictions int64
110114}
111115
116+ // IsSafeContent determines if the specified user can safely access the requested repository content.
117+ // Safe access applies when any of the following is true:
118+ // - the content was created by a trusted bot;
119+ // - the author currently has push access to the repository;
120+ // - the repository is private;
121+ // - the content was created by the viewer.
112122func (c * RepoAccessCache ) IsSafeContent (ctx context.Context , username , owner , repo string ) (bool , error ) {
113123 repoInfo , err := c .getRepoAccessInfo (ctx , username , owner , repo )
114124 if err != nil {
115- c .logDebug ("error checking repo access info for content filtering" , "owner" , owner , "repo" , repo , "user" , username , "error" , err )
116125 return false , err
117126 }
118- if repoInfo .IsPrivate || repoInfo .ViewerLogin == username {
127+
128+ c .logDebug (ctx , fmt .Sprintf ("evaluated repo access for user %s to %s/%s for content filtering, result: hasPushAccess=%t, isPrivate=%t" ,
129+ username , owner , repo , repoInfo .HasPushAccess , repoInfo .IsPrivate ))
130+
131+ if c .isTrustedBot (username ) || repoInfo .IsPrivate || repoInfo .ViewerLogin == strings .ToLower (username ) {
119132 return true , nil
120133 }
121134 return repoInfo .HasPushAccess , nil
@@ -136,30 +149,34 @@ func (c *RepoAccessCache) getRepoAccessInfo(ctx context.Context, username, owner
136149 if err == nil {
137150 entry := cacheItem .Data ().(* repoAccessCacheEntry )
138151 if cachedHasPush , known := entry .knownUsers [userKey ]; known {
139- c .logDebug ("repo access cache hit" , "owner" , owner , "repo" , repo , "user" , username )
152+ c .logDebug (ctx , fmt . Sprintf ( "repo access cache hit for user %s to %s/%s" , username , owner , repo ) )
140153 return RepoAccessInfo {
141154 IsPrivate : entry .isPrivate ,
142155 HasPushAccess : cachedHasPush ,
143156 ViewerLogin : entry .viewerLogin ,
144157 }, nil
145158 }
146- c .logDebug ("known users cache miss" , "owner" , owner , "repo" , repo , "user" , username )
159+
160+ c .logDebug (ctx , "known users cache miss, fetching from graphql API" )
161+
147162 info , queryErr := c .queryRepoAccessInfo (ctx , username , owner , repo )
148163 if queryErr != nil {
149164 return RepoAccessInfo {}, queryErr
150165 }
166+
151167 entry .knownUsers [userKey ] = info .HasPushAccess
152168 entry .viewerLogin = info .ViewerLogin
153169 entry .isPrivate = info .IsPrivate
154170 c .cache .Add (key , c .ttl , entry )
171+
155172 return RepoAccessInfo {
156173 IsPrivate : entry .isPrivate ,
157174 HasPushAccess : entry .knownUsers [userKey ],
158175 ViewerLogin : entry .viewerLogin ,
159176 }, nil
160177 }
161178
162- c .logDebug ("repo access cache miss" , "owner" , owner , "repo" , repo , "user" , username )
179+ c .logDebug (ctx , fmt . Sprintf ( "repo access cache miss for user %s to %s/%s" , username , owner , repo ) )
163180
164181 info , queryErr := c .queryRepoAccessInfo (ctx , username , owner , repo )
165182 if queryErr != nil {
@@ -223,19 +240,35 @@ func (c *RepoAccessCache) queryRepoAccessInfo(ctx context.Context, username, own
223240 }
224241 }
225242
243+ c .logDebug (ctx , fmt .Sprintf ("queried repo access info for user %s to %s/%s: isPrivate=%t, hasPushAccess=%t, viewerLogin=%s" ,
244+ username , owner , repo , bool (query .Repository .IsPrivate ), hasPush , query .Viewer .Login ))
245+
226246 return RepoAccessInfo {
227247 IsPrivate : bool (query .Repository .IsPrivate ),
228248 HasPushAccess : hasPush ,
229249 ViewerLogin : string (query .Viewer .Login ),
230250 }, nil
231251}
232252
233- func cacheKey (owner , repo string ) string {
234- return fmt .Sprintf ("%s/%s" , strings .ToLower (owner ), strings .ToLower (repo ))
253+ func (c * RepoAccessCache ) log (ctx context.Context , level slog.Level , msg string , attrs ... slog.Attr ) {
254+ if c == nil || c .logger == nil {
255+ return
256+ }
257+ if ! c .logger .Enabled (ctx , level ) {
258+ return
259+ }
260+ c .logger .LogAttrs (ctx , level , msg , attrs ... )
235261}
236262
237- func (c * RepoAccessCache ) logDebug (msg string , args ... any ) {
238- if c != nil && c .logger != nil {
239- c .logger .Debug (msg , args ... )
240- }
263+ func (c * RepoAccessCache ) logDebug (ctx context.Context , msg string , attrs ... slog.Attr ) {
264+ c .log (ctx , slog .LevelDebug , msg , attrs ... )
265+ }
266+
267+ func (c * RepoAccessCache ) isTrustedBot (username string ) bool {
268+ _ , ok := c .trustedBotLogins [strings .ToLower (username )]
269+ return ok
270+ }
271+
272+ func cacheKey (owner , repo string ) string {
273+ return fmt .Sprintf ("%s/%s" , strings .ToLower (owner ), strings .ToLower (repo ))
241274}
0 commit comments