feat(db,api): resolve external entities - #2326
Conversation
…nfig access Add support for resolving external entities (users, groups, roles) by alias overlap against persisted rows, enabling cross-scraper membership tracking. Implement canonical ID resolution using UUID-shaped aliases or fresh UUIDs instead of deterministic hashing, allowing proper deduplication of entities from multiple sources. Add scraper_id field to config relationships and external_user_groups to track ownership. Implement ScraperID="all" support in config access lookups to resolve configs owned by other scrapers. Add CEL functions for querying external_user_groups and config_relationships. Improve alias normalization by dropping descriptor prefixes and applying case-insensitive matching. Add fallback lookup in TempCache.Find() for cross-scraper changes. Update IsTrace/IsDebug to respect parent context settings. BREAKING CHANGE: ExternalConfigAccess.ScraperID changed from *uuid.UUID to string to support "all" value; OwnerScraperID field added to track actual scraper ownership. Config relationship upsert conflict columns now include scraper_id. Refs: AWS Backup cross-scraper changes, external entity deduplication across sources
BenchstatBase: ✅ 4 improvement(s)
Full benchstat output |
WalkthroughChangesExternal entity cache updates
GCP IAM scraping
GitHub repository access and metadata
Sequence Diagram(s)sequenceDiagram
participant GCPScraper
participant CloudAssetInventory
participant CloudIdentity
GCPScraper->>CloudAssetInventory: Fetch IAM policy assets
CloudAssetInventory-->>GCPScraper: Return policy assets and group emails
GCPScraper->>CloudIdentity: Expand group memberships
CloudIdentity-->>GCPScraper: Return nested memberships
GCPScraper-->>GCPScraper: Append aggregated IAM and membership results
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 Checkov (3.3.8).github/workflows/gavel.ymlTraceback (most recent call last): config/schemas/config_github.schema.jsonTraceback (most recent call last): config/schemas/scrape_config.schema.jsonTraceback (most recent call last):
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@db/external_entities.go`:
- Around line 577-580: Update the cache rewrite in the Items iteration within
the relevant external-entity merge flow to preserve each cached entry’s existing
expiration by passing val.Expiration to c.Set, converting zero expiration to
cache.NoExpiration as required by the cache API. Add coverage for entries with
custom TTLs and non-expiring entries, while leaving the winner replacement
behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 164dd7e4-1bcf-4e5c-9efc-f5e01435282d
📒 Files selected for processing (1)
db/external_entities.go
| for key, val := range c.Items() { | ||
| if id, ok := val.Object.(uuid.UUID); ok { | ||
| if winner, merged := idMap[id]; merged { | ||
| c.Set(key, winner, cache.DefaultExpiration) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate file and relevant lines =="
if [ -f db/external_entities.go ]; then
sed -n '540,605p' db/external_entities.go | cat -n
else
fd -a 'external_entities.go' .
fi
echo "== imports/cache declarations =="
sed -n '1,80p' db/external_entities.go | cat -n
echo "== search Items/Set usage/idMap =="
rg -n "Items\\(|\\.Set\\(|idMap|DefaultExpiration|NoExpiration|Expiration" db/external_entities.go
echo "== list go-cache package references/locks =="
rg -n "github.com/patrickmn/go-cache|go-cache" go.mod go.sum 2>/dev/null || trueRepository: flanksource/config-db
Length of output: 6843
🌐 Web query:
patrickmn go-cache v2.1.0 Item.Expiration Items Set DefaultExpiration NoExpiration source
💡 Result:
In patrickmn/go-cache v2.1.0, the handling of item expiration is managed through specific constants and the Item struct definition [1]. Constants for Expiration: - DefaultExpiration (time.Duration(0)): When passed to the Set method, the cache uses the default expiration duration specified during the cache's initialization (via New or NewFrom) [1]. - NoExpiration (time.Duration(-1)): When passed to the Set method, the item is stored without an expiration time and will not be removed by the janitor/cleanup process [1]. Item Struct: The Item struct represents a cached object [1][2]. It contains: - Object (interface{}): The stored value. - Expiration (int64): The absolute expiration time in nanoseconds (UnixNano), or 0 if no expiration is set [1][2]. Methods: - Set(k string, x interface{}, d time.Duration): Adds an item to the cache. It translates DefaultExpiration and NoExpiration into the appropriate internal representation for the Item struct [1][2]. - SetDefault(k string, x interface{}): Introduced in v2.1.0, this is a convenience method that calls Set with the DefaultExpiration constant [3]. Example usage: // Create cache with 5-minute default expiration c:= cache.New(5time.Minute, 10time.Minute) // Set with default expiration c.Set("key1", "value1", cache.DefaultExpiration) // Set using SetDefault helper c.SetDefault("key2", "value2") // Set with no expiration c.Set("key3", "value3", cache.NoExpiration)
Citations:
- 1: https://github.com/patrickmn/go-cache/blob/v2.1.0/cache.go
- 2: https://github.com/patrickmn/go-cache/blob/46f407853014144407b6c2ec7ccc76bf67958d93/cache.go
- 3: https://github.com/patrickmn/go-cache/releases/tag/v2.1.0
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== cache cache initialization sites =="
rg -n "cache\\.New(From|External.*Cache|ExternalUserIDCache|Expiration|DefaultExpiration|NoExpiration)" . -g '*.go'
echo "== relevant cache init sections =="
sed -n '90,140p' db/external_entities.go | cat -n
sed -n '960,1015p' db/external_entities.go | cat -nRepository: flanksource/config-db
Length of output: 4294
🌐 Web query:
github.com/patrickmn/go-cache v2.1.0 cache.go func (c *Cache) Set expiry DefaultExpiration NoExpiration Items
💡 Result:
In the patrickmn/go-cache library (v2.1.0), the Set method is defined as: func (c *Cache) Set(k string, x interface{}, d time.Duration) [1][2][3]. The parameters and expiration behavior are as follows: - k: The key (string) under which to store the item [3]. - x: The value (interface{}) to store [3]. - d: The duration (time.Duration) for which the item should remain in the cache [3]. The expiration logic uses two specific constants for the duration (d) [3]: - cache.DefaultExpiration: Using this value (0) instructs the cache to use the default expiration duration specified when the cache instance was created via New or NewFrom [1][4][3]. - cache.NoExpiration: Using this value (-1) means the item will never expire and must be manually deleted using c.Delete(k) [1][4][3]. If a positive duration is provided, the item will expire after that specific duration [3]. The cache stores these items internally in a map, which is accessed safely via locking mechanisms within the Set method [3]. Additionally, the library provides a SetDefault(k, x) helper method, which is equivalent to calling Set(k, x, cache.DefaultExpiration) [5][3].
Citations:
- 1: https://github.com/pmylund/go-cache/blob/v2.1.0/cache.go
- 2: https://github.com/patrickmn/go-cache/blob/master/cache.go
- 3: https://github.com/patrickmn/go-cache/blob/46f407853014144407b6c2ec7ccc76bf67958d93/cache.go
- 4: https://pkg.go.dev/github.com/patrickmn/go-cache@v2.1.0+incompatible
- 5: https://github.com/patrickmn/go-cache/releases/tag/v2.1.0
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== all cache variable declarations and cache.New usage =="
rg -n "External.*Cache|cache\\.New|cache\\.DefaultExpiration|cache\\.NoExpiration|Set\\([^,]+, [^,]+, .*Expiration|Set\\([^,]+, [^,]+, .*time" db go.mod scrapers 2>/dev/null || true
echo "== cache declaration/init candidates =="
git ls-files '*.go' | xargs -r rg -n "cache\\.New|External.*Cache|idCache|Cache.*cache|DefaultExpiration|NoExpiration|Items\\(" {} >/tmp/cache_search.txt 2>/dev/null || true
cat /tmp/cache_search.txt | head -200Repository: flanksource/config-db
Length of output: 23330
🌐 Web query:
site:raw.githubusercontent.com/pmylund/go-cache/v2.1.0 cache.go Item.Expiration func (c *Cache) Items
💡 Result:
In go-cache v2.1.0, Item has Expiration *time.Time, and Cache.Items() returns map[string]*Item containing cached items that may already be expired; you’re supposed to check each item’s Expiration if that matters. (pkg.go.dev)
Citations:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
go mod download github.com/patrickmn/go-cache@v2.1.0+incompatible
GOCACHE="$(go env GOCACHE)"
CACHEMOD="$GOCACHE/modcache/github.com/patrickmn/go-cache@v2.1.0+incompatible/cache.go"
echo "== cache.go path =="
printf '%s\n' "$CACHEMOD"
if [ -f "$CACHEMOD" ]; then
sed -n '1,180p' "$CACHEMOD" | cat -n
else
echo "cache.go file not found; downloading package from raw source as fallback"
curl -fsSL https://raw.githubusercontent.com/patrickmn/go-cache/v2.1.0/cache.go > /tmp/patrickmn-cache-go.go
sed -n '1,140p' /tmp/patrickmn-cache-go.go | cat -n
fiRepository: flanksource/config-db
Length of output: 4746
🏁 Script executed:
#!/bin/bash
set -euo pipe fail
sed -n '140,240p' /tmp/patrickmn-cache-go.go | cat -nRepository: flanksource/config-db
Length of output: 204
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '140,240p' /tmp/patrickmn-cache-go.go | cat -nRepository: flanksource/config-db
Length of output: 3807
Preserve the cached entry’s existing expiration.
Items() returns the current Item.Expiration, but c.Set(result, winner, cache.DefaultExpiration) rewrites affected entries to the cache-wide default, which can shorten custom TTLs or cause NoExpiration entries to expire. Reuse val.Expiration when rewriting, using cache.NoExpiration for zero expiration, and add coverage for custom and non-expiring entries.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@db/external_entities.go` around lines 577 - 580, Update the cache rewrite in
the Items iteration within the relevant external-entity merge flow to preserve
each cached entry’s existing expiration by passing val.Expiration to c.Set,
converting zero expiration to cache.NoExpiration as required by the cache API.
Add coverage for entries with custom TTLs and non-expiring entries, while
leaving the winner replacement behavior unchanged.
Make IAM bindings queryable as deduplicated role resources and resolve group grants to transitive users and service accounts, so RBAC access reflects effective principals rather than only direct bindings. Enable group expansion by default with an explicit opt-out and publish the related schema and example settings; expose the Helm toggle for GitHub repository metadata. BREAKING CHANGE: GCP credentials now need the Cloud Identity groups.readonly scope for default group expansion; configurations without it must exclude IAMGroupMembers.
Expose repository topics as namespaced tags and preserve configured scraper plugins so fork, license, and archive metadata reaches extracted properties. Strip license bodies during sanitization to avoid retaining unnecessary payload data.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scrapers/gcp/gcp.go`:
- Around line 53-55: Update NewGCPContext’s TokenSource scope construction so
cloud-identity.groups.readonly is requested only when IAMGroupMembers scraping
is enabled; asset-only or explicitly excluded group-membership scrapes must
continue using only the required cloud-platform scope.
In `@scrapers/github/scraper.go`:
- Line 330: Update buildRepositoryResult to preserve the configured tags and
labels initialized by NewScrapeResult instead of replacing them with only
owner/repo metadata. Seed or merge the existing Tags and Labels maps before
adding repository and topic values, ensuring all configured entries remain
intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 48b7d6b2-aa21-4590-bdc1-38511bf6ca2d
📒 Files selected for processing (16)
api/v1/gcp.goapi/v1/gcp_test.gochart/values.schema.jsonchart/values.yamlconfig/schemas/config_gcp.schema.jsonconfig/schemas/scrape_config.schema.jsonfixtures/gcp.yamlscrapers/gcp/gcp.goscrapers/gcp/iam.goscrapers/gcp/iam_group_members.goscrapers/gcp/iam_roles.goscrapers/gcp/iam_test.goscrapers/github/repository_metadata_test.goscrapers/github/scraper.goscrapers/github/scraper_test.goscrapers/github/workflows.go
| tokenSource, err := gcpConfig.GCPConnection.TokenSource(ctx.Context, | ||
| "https://www.googleapis.com/auth/cloud-platform", | ||
| "https://www.googleapis.com/auth/cloud-identity.groups.readonly") |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
Google service account TokenSource requesting cloud-identity.groups.readonly scope not authorized error
💡 Result:
The error "Not authorized" or "Access denied" when using a service account with the cloud-identity.groups.readonly scope typically occurs because the Cloud Identity Groups API requires specific authentication setups that differ from standard Google Cloud IAM permissions [1][2][3]. To resolve this, follow these steps: 1. Configure Domain-Wide Delegation (DWD): The Cloud Identity API often requires the service account to act on behalf of a Google Workspace administrator [1][2]. * Ensure the service account has Domain-Wide Delegation enabled in the Google Cloud Console [1][3]. * In the Google Workspace Admin console, authorize the service account's Client ID for the required scopes (e.g., https://www.googleapis.com/auth/cloud-identity.groups.readonly) [1][4]. * When initializing your client, use the credentials to impersonate a domain administrator by specifying the admin's email address using a method like with_subject [1][2][4]. 2. Verify API Enablement: Ensure the Cloud Identity API is explicitly enabled in the Google Cloud project where the service account resides [5]. Even with correct scopes, the API call will fail if the underlying service is not enabled [5]. 3. Check IAM Permissions (Beyond Scopes): Scopes are OAuth-level authorizations [6]. In addition to scopes, the service account must have the appropriate IAM roles granted at the organization or group level within your Google Workspace or Cloud Identity account [1][7][8]. Simply having the scope in your code is insufficient if the service account principal is not granted the required IAM permissions (such as Group Admin roles) to manage or view groups [1][3]. 4. Use the Correct API: Ensure you are targeting the Cloud Identity API (cloudidentity.googleapis.com) rather than the older Admin SDK Directory API, as the two have different authorization requirements and behaviors [3]. If you continue to encounter issues, use the Google Cloud Policy Troubleshooter to determine if an IAM policy or organization constraint is blocking the service account's access [7][8][9].
Citations:
- 1: https://docs.cloud.google.com/identity/docs/how-to/setup
- 2: https://discuss.google.dev/t/using-cloud-identity-api-with-a-service-account/100373/1
- 3: https://stackoverflow.com/questions/30763840/how-can-i-access-group-members-with-a-service-account
- 4: https://stackoverflow.com/questions/76418865/httperror-403-when-requesting-admin-googleapis-python-issue-with-service-accou
- 5: https://github.com/steipete/gogcli/blob/main/internal/cmd/groups.go
- 6: https://stackoverflow.com/questions/59632474/access-denied-provided-scopes-are-not-authorized-error-when-trying-to-make
- 7: https://docs.cloud.google.com/iam/docs/resolve-permission-errors
- 8: https://cloud.google.com/policy-intelligence/docs/troubleshoot-access
- 9: https://oneuptime.com/blog/post/2026-02-17-how-to-debug-service-account-permission-issues-in-google-cloud-iam/view
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate gcp.go and relevant config =="
fd -a 'gcp\.go$' . || true
rg -n "GCPConnection|TokenSource|IAMGroupMembers|cloud-identity.groups.readonly|GCPGroupMembership|DefaultIgnoreList|IAMPolicy" . --glob '!vendor/**' --glob '!node_modules/**' | head -200
echo
echo "== gcp.go outline =="
if [ -f scrapers/gcp/gcp.go ]; then
ast-grep outline scrapers/gcp/gcp.go --view expanded | head -200 || true
echo
echo "== gcp.go relevant sections =="
sed -n '1,120p' scrapers/gcp/gcp.go
echo "---"
sed -n '160,190p' scrapers/gcp/gcp.go
echo "---"
sed -n '340,410p' scrapers/gcp/gcp.go
fi
echo
echo "== config files referencing GCPConnection/GCPGroup =="
rg -n "type .*GCP|GCPConnection|GCPFallback|Include.*IAM|IAMGroupMembers|IAMPolicy" -g '*.go' .Repository: flanksource/config-db
Length of output: 17312
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== connection structs/types =="
rg -n "type GCPConnection|func .*TokenSource|func .*HydrateConnection|Credentials.*json|Default.*cred|New.*TokenSource|JWT|impersonate|ADC|DefaultCredentials" -g '*.go' api scrapers utils . --glob '!vendor/**' --glob '!node_modules/**' | head -250
echo
echo "== api/v1/gcp.go =="
sed -n '45,140p' api/v1/gcp.go
echo
echo "== likely connection files =="
for f in $(fd -i 'gcp.*\.go$|connection.*\.go$' . | head -50); do
echo "--- $f"
wc -l "$f"
rg -n "GCPConnection|credential|TokenSource|google.*Default|DefaultCredentials|jwt|imperson" "$f" || true
done
echo
echo "== go module versions for google auth =="
if [ -f go.mod ]; then
rg -n "cloud.google.com/go/|google.golang.org/api|oauth2" go.mod go.sum
fi
echo
echo "== static check for default creds fallback =="
rg -n "DefaultCredentials|google.DefaultClient|credentialstoken|OAuth2Client|oauth2\.DefaultClient|NewService" scrapers/api scrapers/gcp scrapers/logs api/v1 -g '*.go' || trueRepository: flanksource/config-db
Length of output: 190
🏁 Script executed:
#!/bin/bash
set -u;
echo "== all tracked Go files containing GCPConnection or TokenSource =="
rg -n "GCPConnection|TokenSource|OAuth2Client|credentials|DefaultCredentials|impersonate|jwt" -g '*.go' . --glob '!vendor/**' --glob '!node_modules/**' | head -300 || true
echo
echo "== file sizes for likely target files =="
for f in api/v1/gcp.go scrapers/gcp/gcp.go; do
if [ -f "$f" ]; then
wc -l "$f"
fi
done
echo
echo "== api/v1/gcp.go full relevant struct =="
if [ -f api/v1/gcp.go ]; then
sed -n '1,120p' api/v1/gcp.go
fi
echo
echo "== import section from scrapers/gcp/gcp.go =="
if [ -f scrapers/gcp/gcp.go ]; then
sed -n '1,35p' scrapers/gcp/gcp.go
fiRepository: flanksource/config-db
Length of output: 7367
🌐 Web query:
duty connection GCPConnection TokenSource source.go
💡 Result:
The term GCPConnection is primarily associated with the open-source platform Flanksource Duty, where it serves as a configuration object for integrating with Google Cloud Platform services [1][2][3]. Within the Flanksource Duty repository, GCP connections are used to manage authentication for tasks such as health checks, configuration scraping, and automation [1][3]. There is no standard Go library type named GCPConnection in the official Google Cloud Go SDK or the golang.org/x/oauth2 package [4]. In Go ecosystem projects, authentication is typically handled by the oauth2.TokenSource interface [5][6][7]. The confusion regarding GCPConnection and TokenSource often arises from how developers implement custom authentication wrappers or providers in Go [6][7]. For example: 1. Custom implementations in other projects often define structures to hold authentication data (e.g., service account details or external token URLs) and implement the oauth2.TokenSource interface to return valid access tokens [6][7]. 2. In the Flanksource Duty codebase, GCP connection logic (often found in files like connection/gcp.go) is used to resolve these credentials or connection references into usable authentication mechanisms for GCP clients [8][9][1]. If you are looking for code related to TokenSource and GCP, the authoritative source is the google/oauth2 package, which provides utilities like google.DefaultTokenSource to automatically retrieve credentials from the environment, service account files, or workload identity [4].
Citations:
- 1: https://flanksource.com/docs/reference/connections/gcp
- 2: https://flanksource.com/docs/guide/canary-checker/reference/gcs-database-backup
- 3: https://flanksource.com/docs/reference/connections/gcp.md
- 4: https://pkg.go.dev/golang.org/x/oauth2/google
- 5: https://github.com/google/go-cloud/blob/master/gcp/gcp.go
- 6: https://github.com/kubernetes-sigs/gcp-compute-persistent-disk-csi-driver/blob/v1.26.0/pkg/gce-cloud-provider/compute/token_source.go
- 7: https://github.com/google/go-containerregistry/blob/v0.21.5/pkg/v1/google/auth.go
- 8: flanksource/duty@v1.0.951...v1.0.954
- 9: flanksource/duty@v1.0.951...v1.0.952
🌐 Web query:
site:github.com/flanksource/duty GCPConnection TokenSource
💡 Result:
In the context of Google Cloud Platform (GCP) development with Go, TokenSource refers to the oauth2.TokenSource interface [1]. This interface is a fundamental component of the Google Cloud Go client libraries and the underlying golang.org/x/oauth2 package [1]. A TokenSource is responsible for providing OAuth2 tokens, which are required to authenticate requests to GCP services. It abstracts the complexity of token management—such as initial retrieval, storage, and, crucially, automatic refreshing when a token expires. Key aspects of TokenSource in GCP: - Automatic Refresh: When a client library makes a request, it calls the TokenSource to obtain a valid token. If the current token is expired, the TokenSource automatically handles the refresh flow using the configured credentials. - Standard Implementation: You typically do not need to implement this interface yourself. The Google Cloud auth library (cloud.google.com/go/auth) and the oauth2 package provide pre-built implementations, such as those that load credentials from environment variables (GOOGLE_APPLICATION_CREDENTIALS), service account files, or metadata servers when running on GCP infrastructure (Compute Engine, GKE, Cloud Run). - Integration: When configuring GCP clients (e.g., for Storage, Pub/Sub, or Logging), the client options typically accept credentials that provide a TokenSource, ensuring that the client remains authenticated throughout its lifecycle [2][3][4]. If you encountered "GCPConnection TokenSource" in specific software like the 'flanksource/duty' repository, it likely refers to a custom abstraction layer within that application designed to manage and template GCP authentication credentials (including OAuth2 configurations) for interacting with various GCP services [5][2][6].
Citations:
- 1: chore(deps): bump the go-modules group across 1 directory with 25 updates duty#2031
- 2: flanksource/duty@0e5eb76
- 3: flanksource/duty@a6db774
- 4: https://github.com/flanksource/duty/blob/main/go.mod
- 5: flanksource/duty@1a0eafc
- 6: flanksource/duty@2b3d1cf
🌐 Web query:
flanksource duty GCPConnection TokenSource implementation google oauth2
💡 Result:
In Flanksource Duty, GCPConnection is a configuration object used to authenticate and connect to Google Cloud Platform services [1][2]. It does not typically require a manual "TokenSource" implementation by the user; instead, it handles authentication automatically based on the provided configuration [3][4]. Authentication for GCPConnection is managed in one of three ways: 1. Workload Identity: If neither a specific connection nor explicit credentials are provided, Duty defaults to using the environment's GKE Workload Identity [3]. 2. Connection Object: Users can reference a pre-defined Connection CRD using the connection:// URL scheme [1][5]. This is the recommended method for reusability and security [3]. 3. Explicit Credentials: Users can provide credentials directly within the GCPConnection block using the EnvVar type, which supports static values, Kubernetes ConfigMaps, or Kubernetes Secrets (e.g., service account JSON files) [1][6][3]. Under the hood, Flanksource Duty utilizes standard Google Cloud Go client libraries, which automatically resolve credentials (including TokenSources) from the provided service account keys, application default credentials, or workload identity configurations. Developers extending Duty (e.g., in the duty repository) handle GCP connection logic using the provided connection utilities to abstract away the complexity of manual OAuth2 token management [7][8].
Citations:
- 1: https://flanksource.com/docs/reference/connections/gcp.md
- 2: https://flanksource.com/docs/guide/canary-checker/reference/exec
- 3: https://flanksource.com/docs/guide/canary-checker/reference/gcs-database-backup
- 4: https://flanksource.com/docs/guide/config-db/scrapers/gcp
- 5: https://flanksource.com/docs/reference/connections/
- 6: https://flanksource.com/docs/connections
- 7: flanksource/duty@1a0eafc
- 8: flanksource/duty@a6db774
🌐 Web query:
Google Cloud Identity API service account scopes not authorized domain wide delegation
💡 Result:
The error "not authorized" when using a service account with domain-wide delegation usually indicates a mismatch between the scopes authorized in the Google Admin Console and the scopes requested by your application, or a failure to properly impersonate a user [1][2]. To resolve this, ensure the following requirements are met: 1. Exact Scope Matching: The OAuth scopes specified in your application code must be present in the authorized scopes list for the service account in the Google Admin Console [2][3]. If your application requests a scope that is not explicitly listed in the Admin Console, or if there is a mismatch (even a typo), you will receive an unauthorized_client or access_denied error [1][2]. 2. User Impersonation (Subject): When using domain-wide delegation, the service account acts on behalf of a user [4]. You must specify the email address of the user you wish to impersonate (the 'subject') in your code [1][4]. If you omit this or specify a user without the necessary permissions (such as appropriate Admin API privileges for the requested scope), the API call will be rejected [4][5]. 3. Admin Console Authorization: Ensure the service account's unique Client ID has been authorized in the Google Workspace Admin Console under Security > Access and data control > API controls > Manage Domain Wide Delegation [6][7]. 4. Propagation Time: Changes made in the Google Admin Console regarding domain-wide delegation can take up to 24 hours to propagate, though they often take effect within a few minutes [1][6]. If you are encountering this error, verify that the scopes requested in your credentials initialization match the scopes added in the Admin Console [2][6], and confirm that you are providing a valid email address via the 'subject' parameter (e.g.,.withSubject("admin@example.com") in Java or credentials.with_subject('admin@example.com') in Python) [4][3].
Citations:
- 1: https://developers.google.com/identity/protocols/oauth2/service-account
- 2: https://cloud.google.com/identity/docs/how-to/setup-policies
- 3: https://docs.cloud.google.com/identity/docs/how-to/setup-policies
- 4: https://docs.cloud.google.com/identity/docs/how-to/setup
- 5: https://discuss.google.dev/t/google-directory-api-service-acount-with-domain-wide-delegation/164589
- 6: https://knowledge.workspace.google.com/admin/apps/control-api-access-with-domain-wide-delegation
- 7: https://support.google.com/a/answer/7040511?hl=en
🌐 Web query:
cloud.google.com/go/auth/oauth2adapt DefaultTokenSource requested scopes token minting unauthorized
💡 Result:
The error you are encountering—often manifested as an unauthorized access or failure to mint tokens with specific scopes—typically occurs because Application Default Credentials (ADC) have inherent limitations regarding scope modification, especially when using existing "authorized_user" credentials [1]. When you use DefaultTokenSource (or DefaultClient) to request specific scopes, the library attempts to find and use your environment's default credentials [2][3]. If those credentials are user-managed (e.g., created via gcloud auth login), they are static [1]. You cannot dynamically expand the permissions (scopes) of an existing user-authenticated token [1]. If the scopes requested in your code exceed the scopes granted during the original authentication process, the request will fail because the underlying token source cannot mint a new token with broader permissions than the initial authorization [1]. To resolve this, consider the following approaches: 1. Re-authenticate: If you are using local ADC, revoke your current credentials and authenticate again with the required scopes [1]. gcloud auth revoke gcloud auth application-default login --scopes=SCOPE1,SCOPE2 2. Service Accounts: If this is for a production or automated environment, use a Service Account key or Workload Identity instead of user credentials [4][5]. Service Accounts can typically be granted the necessary IAM roles directly, bypassing the need to negotiate scopes at the token minting level in the same way user-authorized credentials do. 3. Verify Credentials Type: Ensure that your environment is not inadvertently picking up restrictive credentials. You can debug which credentials are being loaded by checking the GOOGLE_APPLICATION_CREDENTIALS environment variable or by using the gcloud auth application-default print-access-token command to verify the scopes of your current ADC token. The cloud.google.com/go/auth/oauth2adapt package itself is designed to convert between new auth types and legacy oauth2.TokenSource types, but it does not bypass the underlying limitations of the authorization method being adapted [6][7]. If your application requires dynamic scope handling, you may need to move away from DefaultTokenSource and explicitly manage an OAuth2 flow that supports requesting the necessary scopes [1].
Citations:
- 1: google: when using DefaultClient, scopes are not used for "authorized_user" golang/oauth2#207
- 2: https://github.com/golang/oauth2/blob/696f7b31/google/default.go
- 3: https://pkg.go.dev/github.com/golang/oauth2/google
- 4: https://pkg.go.dev/cloud.google.com/go/auth/oauth2adapt@v0.2.8
- 5: https://pkg.go.dev/cloud.google.com/go/auth/oauth2adapt
- 6: https://cloud.google.com/go/docs/reference/cloud.google.com/go/auth/oauth2adapt/latest
- 7: https://docs.cloud.google.com/go/docs/reference/cloud.google.com/go/auth/oauth2adapt/latest
🌐 Web query:
oauth2/credentials JWT ServiceAccountFromFileTokenSource scopes claims does not require preexisting scope
💡 Result:
In the context of Google's OAuth2 libraries, the behavior regarding scopes and claims for Service Account credentials depends on whether you are performing a standard OAuth2 flow (exchanging a JWT for an access token) or using direct JWT access [1][2]. 1. Standard OAuth2 Flow: When using service account credentials to obtain an access token, scopes are typically required [3][4][1]. These libraries are designed to be immutable, meaning you define the scopes at instantiation (e.g., via from_service_account_file) or use helper methods like with_scopes to create a new, scoped instance if the underlying implementation supports it [3][4][5]. 2. Direct JWT Access: Some implementations allow creating a JWT directly to be used as an access token [1][2]. In this specific mode, the JWT is signed locally using the service account's private key, and it requires either a scope or an audience (target_audience) to be specified [1][2]. The libraries enforce that at least one of these (scope or audience) must be present because they are required to populate the 'scope' (or 'aud') claim in the JWT's claim set [1][2]. Regarding your query about "not requiring a preexisting scope," the libraries generally require you to explicitly provide the intended scopes or audience at the time of credential creation [3][4][1]. They do not implicitly derive scopes from the service account's JSON file or environment. If you need to change or add scopes to an existing credential object, the common pattern across Python and other libraries is to use methods like with_scopes to generate a new credentials object with the desired, updated scope configuration [3][4][5]. You cannot typically modify the scope of an existing, immutable credential object in-place [3][4]. If you are encountering issues where a scope appears "unnecessary," ensure you are not accidentally confusing direct JWT access (where an audience might be used instead of a scope) with standard OAuth2 flows (which almost universally require scopes) [1][2][6].
Citations:
- 1: https://go.googlesource.com/oauth2/+/22134a41033e44c2cd074106770ab5b7ca910d15/google/jwt.go
- 2: https://github.com/golang/oauth2/blob/master/google/jwt.go
- 3: https://googleapis.dev/python/google-auth/latest/reference/google.oauth2._service_account_async.html
- 4: https://google-auth.readthedocs.io/en/latest/reference/google.oauth2.service%5Faccount.html
- 5: https://github.com/googleapis/google-auth-library-python/blob/main/google/oauth2/service_account.py
- 6: https://cloud.google.com/php/docs/reference/auth/latest/Credentials.ServiceAccountCredentials
Avoid requesting cloud-identity.groups.readonly unconditionally.
NewGCPContext now adds this scope before any feature selection, including asset-only scrapes with exclude: [IAMGroupMembers]. Service-account JWT credentials may mint tokens fine, but ADC / Workload Identity paths can require the requested scopes up front and fail instead of skipping group membership scraping. Scope it behind the group-membership path or add documented/config requirements for existing scrapes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scrapers/gcp/gcp.go` around lines 53 - 55, Update NewGCPContext’s TokenSource
scope construction so cloud-identity.groups.readonly is requested only when
IAMGroupMembers scraping is enabled; asset-only or explicitly excluded
group-membership scrapes must continue using only the required cloud-platform
scope.
| } | ||
|
|
||
| result := v1.ScrapeResult{ | ||
| BaseScraper: baseScraper, |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '--- locate files ---\n'
git ls-files | rg '(^|/)scraper\.go$|(^|/)interface\.go$' | sed -n '1,120p'
printf '\n--- github scraper outline ---\n'
ast-grep outline scrapers/github/scraper.go 2>/dev/null | sed -n '1,220p' || true
printf '\n--- relevant github scraper lines ---\n'
sed -n '270,360p' scrapers/github/scraper.go
printf '\n--- NewScrapeResult definition/usages ---\n'
rg -n "NewScrapeResult|func NewScrapeResult|BaseScraper|Labels|Tags" api scrapers/github/scraper.go | sed -n '1,220p'Repository: flanksource/config-db
Length of output: 250
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- locate relevant files ---'
git ls-files | rg '(^|/)scraper\.go$|(^|/)interface\.go$' | sed -n '1,160p'
echo
echo '--- github scraper relevant lines ---'
sed -n '270,360p' scrapers/github/scraper.go
echo
echo '--- NewScrapeResult definition/usages ---'
rg -n "NewScrapeResult|func NewScrapeResult|BaseScraper|Labels|Tags" api scrapers/github/scraper.go | sed -n '1,220p'Repository: flanksource/config-db
Length of output: 14024
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- interface.go Build/Serialize logic ---'
sed -n '1920,1990p' api/v1/interface.go
echo
echo '--- BaseScraper type and ApplyPlugins ---'
sed -n '285,345p' api/v1/common.go
echo
echo '--- current ScrapeResult struct fields ---'
sed -n '1245,1275p' api/v1/interface.go
echo
echo '--- tests for github scraper repository tags/labels/base tags ---'
rg -n "buildRepositoryResult|NewScrapeResult|base.*Tags|BaseScraper.*Tags|Labels.*base|Labels.*)" scrapers tests api -g '*_test.go' | sed -n '1,220p'Repository: flanksource/config-db
Length of output: 6183
Preserve configured scraper tags and labels.
NewScrapeResult initializes Tags from base.Tags.AsMap() and Labels from base.Labels, but buildRepositoryResult overwrites both by initializing Tags to only owner/repo. Seed or merge those maps before adding repository/topic metadata.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scrapers/github/scraper.go` at line 330, Update buildRepositoryResult to
preserve the configured tags and labels initialized by NewScrapeResult instead
of replacing them with only owner/repo metadata. Seed or merge the existing Tags
and Labels maps before adding repository and topic values, ensuring all
configured entries remain intact.
Add opt-in repository RBAC collection, mapping GitHub collaborators and teams to external identities, roles, and config access records. Preserve governance and security metadata during repository sanitization and expose key repository properties. Add schema coverage, fixtures, focused tests, and advisory Gavel pull-request checks.
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
config/schemas/scrape_config.schema.json (1)
1677-1677: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick winKeep GCP include/exclude documentation consistent.
This description documents
exclude: [IAMGroupMembers], but theexcludefield still says it accepts only asset types. Update the exclusion documentation—and clarify that group expansion runs with IAM policy scraping—so generated documentation does not contradict the supported configuration.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@config/schemas/scrape_config.schema.json` at line 1677, Update the GCP schema’s include/exclude descriptions to consistently document IAMGroupMembers as a supported feature flag, not only an asset type. Clarify in the exclusion field that excluding IAMGroupMembers disables group expansion performed alongside IAM policy scraping, while preserving the existing asset-type exclusion guidance.
♻️ Duplicate comments (1)
scrapers/github/scraper.go (1)
348-361: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winConfigured
Tags/LabelsfromBaseScraperare still dropped.
BaseScraper: baseScraperonly wires the config-level struct into the embedded field; it does not seedresult.Tags/result.Labels(the per-item maps) the wayNewScrapeResultdoes elsewhere (base.Tags.AsMap()/base.Labels). HereTagsis hard-coded to justowner/repo, andLabelsis never set, so any tags/labels configured on thegithubscrape spec are silently lost for Repository items.♻️ Proposed fix
result := v1.ScrapeResult{ BaseScraper: baseScraper, Type: ConfigTypeRepository, ID: externalConfigID, Name: repoFullName, ConfigClass: "Repository", Config: sanitizeRepository(repo), - Tags: v1.JSONStringMap{ - "owner": repoConfig.Owner, - "repo": repoConfig.Repo, - }, + Tags: baseScraper.Tags.AsMap(), + Labels: baseScraper.Labels, CreatedAt: repo.CreatedAt.GetTime(), Properties: properties, } + if result.Tags == nil { + result.Tags = v1.JSONStringMap{} + } + result.Tags["owner"] = repoConfig.Owner + result.Tags["repo"] = repoConfig.Repo🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scrapers/github/scraper.go` around lines 348 - 361, Update the Repository result construction in the GitHub scraper to seed per-item Tags and Labels from baseScraper, preserving configured values, then merge or add the owner and repo tags without overwriting them. Use the same conversion behavior as NewScrapeResult, including base.Tags.AsMap() and base.Labels, and ensure Labels is populated on the result.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/gavel.yml:
- Around line 21-23: Add persist-credentials: false to the with configuration of
the actions/checkout step in the Gavel workflow, while preserving the existing
fetch-depth setting.
- Around line 42-45: Harden the Gavel workflow by pinning flanksource/gavel to a
specific full commit SHA and replacing version latest with an exact Gavel
release. Update the checkout step to set persist-credentials: false, while
preserving the existing test --lint invocation and workflow behavior.
- Around line 14-17: Remove the job-level pull-requests: write and issues: write
permissions from the workflow containing actions/checkout and
flanksource/gavel’s test/lint execution; retain only the minimum read access
needed, or split trusted comment updates into a separate post-validation job
with write permissions.
---
Outside diff comments:
In `@config/schemas/scrape_config.schema.json`:
- Line 1677: Update the GCP schema’s include/exclude descriptions to
consistently document IAMGroupMembers as a supported feature flag, not only an
asset type. Clarify in the exclusion field that excluding IAMGroupMembers
disables group expansion performed alongside IAM policy scraping, while
preserving the existing asset-type exclusion guidance.
---
Duplicate comments:
In `@scrapers/github/scraper.go`:
- Around line 348-361: Update the Repository result construction in the GitHub
scraper to seed per-item Tags and Labels from baseScraper, preserving configured
values, then merge or add the owner and repo tags without overwriting them. Use
the same conversion behavior as NewScrapeResult, including base.Tags.AsMap() and
base.Labels, and ensure Labels is populated on the result.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 12965fa6-0f77-40e1-8b27-321ca7aa0f26
📒 Files selected for processing (12)
.github/workflows/gavel.ymlapi/v1/github.goapi/v1/zz_generated.deepcopy.gochart/crds/configs.flanksource.com_scrapeconfigs.yamlconfig/schemas/config_github.schema.jsonconfig/schemas/scrape_config.schema.jsonfixtures/github.yamlscrapers/github/rbac.goscrapers/github/rbac_test.goscrapers/github/repository_metadata_test.goscrapers/github/scraper.goscrapers/github/workflows.go
🚧 Files skipped from review as they are similar to previous changes (1)
- scrapers/github/workflows.go
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| issues: write |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow file =="
cat -n .github/workflows/gavel.yml
echo
echo "== other workflow references to gavel =="
rg -n "flanksource/gavel|gavel" .github/workflows || true
echo
echo "== GITHUB_TOKEN permissions in workflow snippets =="
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/gavel.yml')
text=p.read_text()
for i,line in enumerate(text.splitlines(),1):
if 'pull-requests' in line or 'issues' in line or 'contents' in line:
print(f"{i}: {line}")
PYRepository: flanksource/config-db
Length of output: 2302
🌐 Web query:
flanksource gavel GitHub action does it read pull request files execute test lint
💡 Result:
The flanksource/gavel GitHub Action is designed to execute tests and linting within a CI environment [1][2]. It does not simply read pull request files in isolation; rather, it runs the gavel binary within your workflow, which performs testing and linting operations on your codebase [1][3]. Key points regarding its functionality: - Execution: The action typically runs with the argument test --lint [1][2]. This command invokes the gavel tool to run tests and linters configured in your repository [3]. - Workflow Integration: It is used within GitHub Actions workflows (e.g., in a CI job) to automate these checks [1][4]. - Artifacts and Reporting: The action is configured to produce and upload results as artifacts (e.g., JSON, HTML) and can optionally post a sticky comment on the pull request summarizing the findings [1][5][2]. - Configuration: You can customize the behavior, including specific flags for linting and testing, through the action's inputs in your workflow YAML file [1][6]. In summary, the action acts as an execution wrapper for the gavel tool, which conducts the actual testing and linting of your code during the CI process, rather than acting as a passive analyzer that only reads pull request files [1][3].
Citations:
- 1: https://github.com/flanksource/gavel
- 2: https://libraries.io/npm/@flanksource%2Fgavel
- 3: https://github.com/flanksource/gavel/blob/main/.agents/skills/README.md
- 4: https://github.com/flanksource/clicky/blob/main/.github/workflows/gavel.yml
- 5: flanksource/gavel@23462da
- 6: https://github.com/flanksource/duty/blob/05d62efdde5d33b9e696535db40a3d33020dd1cf/.github/workflows/gavel.yml
Do not run PR-checked out test/lint code with write permissions.
actions/checkout persists the GITHUB_TOKEN before flanksource/gavel runs gavel test -- lint, while gavel can run test/lint code from the PR and is granted pull-requests: write and issues: write. Remove these job-level permissions or split the workflow so a trusted comment-only job receives write access after the untrusted test/lint job has completed.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/gavel.yml around lines 14 - 17, Remove the job-level
pull-requests: write and issues: write permissions from the workflow containing
actions/checkout and flanksource/gavel’s test/lint execution; retain only the
minimum read access needed, or split trusted comment updates into a separate
post-validation job with write permissions.
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | ||
| with: | ||
| fetch-depth: 0 |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow files =="
git ls-files '.github/workflows/*' | sed -n '1,120p'
echo
echo "== gavel workflow =="
if [ -f .github/workflows/gavel.yml ]; then
nl -ba .github/workflows/gavel.yml | sed -n '1,220p'
else
echo "missing .github/workflows/gavel.yml"
fi
echo
echo "== workflow uploads/artifacts and checkout settings =="
python3 - <<'PY'
from pathlib import Path
p=Path('.github/workflows/gavel.yml')
lines=p.read_text().splitlines() if p.exists() else []
for i,l in enumerate(lines,1):
if any(s in l.lower() for s in (['upload-artifact','actions/upload-artifact','upload-artifact','persist-credentials','actions/checkout'])):
print(f"{i}: {l}")
PYRepository: flanksource/config-db
Length of output: 510
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== gavel workflow with line numbers =="
awk '{printf "%7d %s\n", NR, $0}' .github/workflows/gavel.yml | sed -n '1,240p'
echo
echo "== gavel workflow occurrences =="
grep -nEi 'upload-artifact|actions/upload-artifact|persist-credentials|actions/checkout|persist' .github/workflows/gavel.yml || trueRepository: flanksource/config-db
Length of output: 2054
Disable checkout credential persistence.
actions/checkout writes the GitHub token to .git/config by default. Since this workflow does not use authenticated Git operations after checkout, add persist-credentials: false to avoid leaking the token in any later artifact captured by Gavel.
🧰 Tools
🪛 zizmor (1.26.1)
[warning] 21-23: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false
(artipacked)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/gavel.yml around lines 21 - 23, Add persist-credentials:
false to the with configuration of the actions/checkout step in the Gavel
workflow, while preserving the existing fetch-depth setting.
Source: Linters/SAST tools
| - uses: flanksource/gavel@main | ||
| with: | ||
| args: test --lint | ||
| version: latest |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== workflow excerpt =="
if [ -f .github/workflows/gavel.yml ]; then
cat -n .github/workflows/gavel.yml
else
echo ".github/workflows/gavel.yml not found"
fi
echo
echo "== related workflow files with checkout persist-credentials =="
for f in .github/workflows/*; do
[ -f "$f" ] || continue
if rg -n "actions/checkout|persist-credentials|uses: flanksource/gavel|version:" "$f"; then
echo "--- $f"
fi
done
echo
echo "== security scan action usages with permissions context =="
rg -n "permissions:|GITHUB_TOKEN|post-to-nexus|sonar|sca|gavel|actions/checkout|persist-credentials" .github/workflows -S || trueRepository: flanksource/config-db
Length of output: 8647
🌐 Web query:
Flanksource Gavel GitHub action main version latest
💡 Result:
To use the Flanksource Gavel GitHub Action, you typically reference it in your workflow file as flanksource/gavel@main [1][2]. The Gavel GitHub Action supports a version input, which defaults to latest [1][2]. This input allows you to specify a release tag (e.g., v0.0.44), use latest to pull the most recent release, or set it to source to use a pre-installed binary [1][2]. Example usage: - uses: flanksource/gavel@main with: args: test --lint version: latest For production workflows, it is often recommended to pin the action to a specific commit or release version (e.g., flanksource/gavel@v0.0.44) to ensure stability [3]. As of July 2026, the project continues to be actively developed, with recent releases including v0.0.44 [4]. You can check the official repository at https://github.com/flanksource/gavel for the most current release tags and documentation [1][5].
Citations:
- 1: https://github.com/flanksource/gavel
- 2: https://libraries.io/npm/@flanksource%2Fgavel
- 3: https://github.com/flanksource/duty/blob/05d62efdde5d33b9e696535db40a3d33020dd1cf/.github/workflows/gavel.yml
- 4: https://github.com/flanksource/gavel/releases/tag/v0.0.44
- 5: https://github.com/flanksource/gavel/releases
Pin Gavel and avoid granting it repository write access.
.github/workflows/gavel.yml currently gives pull-requests: write and issues: write to a job that downloads flanksource/gavel@main with version: latest, making PR scanning non-reproducible and exposing write permissions to an unpinned action/downloaded binary. Pin the action to a full commit SHA, use an exact Gavel release, and add persist-credentials: false to the checkout step.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/gavel.yml around lines 42 - 45, Harden the Gavel workflow
by pinning flanksource/gavel to a specific full commit SHA and replacing version
latest with an exact Gavel release. Update the checkout step to set
persist-credentials: false, while preserving the existing test --lint invocation
and workflow behavior.
Gavel summary
Totals: 777 passed · 21 failed · 10 skipped · 5m53s Failing testsginkgo ExecutionBenchmarkLocationFilter BenchmarkRunTemplateBool/smallEnv BenchmarkBenchSaveResultsSeed/N=10000 BenchmarkBenchSaveResultsUpdateUnchanged golangci-lint execution failed: fork/exec /home/runner/work/config-db/config-db/.gavel/golangci-lint: exec format error tsc wrapper failed: exit status 1 |
What
ScraperID="all"config lookups and track scraper ownership.Notes
ExternalConfigAccess.ScraperIDnow uses a string and includes a breaking change.Summary by CodeRabbit
New Features
IAMGroupMembersinclude flag (enabled by default when no includes are specified);AuditLogsremains opt-in.plugins.githubto enable repository metadata properties and topic tags.permissions.enabledto collect collaborator/team effective repository access.Bug Fixes
Documentation & Config
Tests