Skip to content

feat(db,api): resolve external entities - #2326

Open
moshloop wants to merge 4 commits into
mainfrom
feat/external-entity-resolution
Open

feat(db,api): resolve external entities#2326
moshloop wants to merge 4 commits into
mainfrom
feat/external-entity-resolution

Conversation

@moshloop

@moshloop moshloop commented Jul 23, 2026

Copy link
Copy Markdown
Member

What

  • Resolve and deduplicate external users, groups, and roles across scrapers.
  • Support ScraperID="all" config lookups and track scraper ownership.
  • Add CEL queries for external user groups and config relationships.

Notes

  • Alias matching is normalized and case-insensitive.
  • ExternalConfigAccess.ScraperID now uses a string and includes a breaking change.

Summary by CodeRabbit

  • New Features

    • GCP scraping now supports an IAMGroupMembers include flag (enabled by default when no includes are specified); AuditLogs remains opt-in.
    • GitHub scraping adds plugins.github to enable repository metadata properties and topic tags.
    • GitHub scraper config supports permissions.enabled to collect collaborator/team effective repository access.
  • Bug Fixes

    • Improved cache consistency after merged entities so updated identities are reflected reliably.
  • Documentation & Config

    • Updated GCP/GitHub config schemas and examples for include/exclude and new permissions behavior.
  • Tests

    • Expanded GCP IAM and GitHub repository/rbac coverage.

…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
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown

Benchstat

Base: 9d12f2d8b2d4fa8752d21f4eab463d7e675df3b2
Head: 1e3e17d6ffa188729a3db693dd6cadcaf5955507

✅ 4 improvement(s)
Benchmark Base Head Change p-value
LocationFilter/largeEnv-4 49.12µ 44.59µ -9.21% 0.002
RunTemplateBool/smallEnv-4 10.34µ 10.10µ -2.28% 0.002
BenchSaveResultsUpdateUnchanged/N=1000-4 2.929 2.896 -1.11% 0.041
BenchSaveResultsUpdateChanged/N=1000-4 6.837 6.761 -1.10% 0.041
Full benchstat output
goos: linux
goarch: amd64
pkg: github.com/flanksource/config-db/bench
cpu: AMD EPYC 9V74 80-Core Processor                
                                         │ bench-base.txt │           bench-head.txt           │
                                         │     sec/op     │    sec/op     vs base              │
LocationFilter/smallEnv-4                    18.15µ ±  2%   20.16µ ± 13%       ~ (p=0.394 n=6)
LocationFilter/largeEnv-4                    49.12µ ±  8%   44.59µ ±  1%  -9.21% (p=0.002 n=6)
RunTemplateBool/smallEnv-4                   10.34µ ±  3%   10.10µ ±  1%  -2.28% (p=0.002 n=6)
RunTemplateBool/largeEnv-4                   19.25µ ±  1%   19.15µ ±  2%       ~ (p=0.394 n=6)
BenchSaveResultsSeed/N=1000-4                 3.559 ± 12%    3.561 ± 11%       ~ (p=0.818 n=6)
BenchSaveResultsUpdateUnchanged/N=1000-4      2.929 ±  2%    2.896 ±  1%  -1.11% (p=0.041 n=6)
BenchSaveResultsUpdateChanged/N=1000-4        6.837 ±  2%    6.761 ±  2%  -1.10% (p=0.041 n=6)
geomean                                      3.855m         3.832m        -0.59%

                                         │ bench-base.txt │            bench-head.txt            │
                                         │      B/op      │     B/op      vs base                │
LocationFilter/smallEnv-4                    14.97Ki ± 0%   14.97Ki ± 0%       ~ (p=1.000 n=6) ¹
LocationFilter/largeEnv-4                    20.38Ki ± 0%   20.38Ki ± 0%  +0.00% (p=0.002 n=6)
RunTemplateBool/smallEnv-4                   8.359Ki ± 0%   8.359Ki ± 0%       ~ (p=1.000 n=6) ¹
RunTemplateBool/largeEnv-4                   10.16Ki ± 0%   10.16Ki ± 0%       ~ (p=0.061 n=6)
BenchSaveResultsSeed/N=1000-4                1.277Gi ± 0%   1.277Gi ± 0%       ~ (p=0.818 n=6)
BenchSaveResultsUpdateUnchanged/N=1000-4     32.21Mi ± 0%   32.20Mi ± 0%       ~ (p=0.485 n=6)
BenchSaveResultsUpdateChanged/N=1000-4       796.3Mi ± 1%   796.8Mi ± 1%       ~ (p=0.937 n=6)
geomean                                      990.2Ki        990.2Ki       +0.01%
¹ all samples are equal

                                         │ bench-base.txt │            bench-head.txt            │
                                         │   allocs/op    │  allocs/op    vs base                │
LocationFilter/smallEnv-4                     278.0 ±  0%    278.0 ±  0%       ~ (p=1.000 n=6) ¹
LocationFilter/largeEnv-4                     522.0 ±  0%    522.0 ±  0%       ~ (p=1.000 n=6) ¹
RunTemplateBool/smallEnv-4                    216.0 ±  0%    216.0 ±  0%       ~ (p=1.000 n=6) ¹
RunTemplateBool/largeEnv-4                    297.0 ±  0%    297.0 ±  0%       ~ (p=1.000 n=6) ¹
BenchSaveResultsSeed/N=1000-4                442.6k ±  0%   442.6k ±  0%       ~ (p=1.000 n=6)
BenchSaveResultsUpdateUnchanged/N=1000-4     396.9k ±  0%   396.9k ±  0%       ~ (p=0.900 n=6)
BenchSaveResultsUpdateChanged/N=1000-4       984.1k ± 13%   984.1k ± 13%       ~ (p=0.937 n=6)
geomean                                      7.703k         7.703k        -0.00%
¹ all samples are equal

                                         │ bench-base.txt │           bench-head.txt           │
                                         │      MB/s      │    MB/s     vs base                │
BenchSaveResultsSeed/N=1000-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
BenchSaveResultsUpdateUnchanged/N=1000-4     0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
BenchSaveResultsUpdateChanged/N=1000-4       0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                 ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

External entity cache updates

Layer / File(s) Summary
Cache merge rewrite
db/external_entities.go
Adds cache support and updates cached UUID values using loser-to-winner mappings.

GCP IAM scraping

Layer / File(s) Summary
Include flags and authorization
api/v1/gcp.go, api/v1/gcp_test.go, config/schemas/*, fixtures/gcp.yaml, scrapers/gcp/gcp.go
Adds default-enabled group membership expansion, documentation, tests, and the required Cloud Identity scope.
IAM policy aggregation and role enrichment
scrapers/gcp/iam.go, scrapers/gcp/iam_roles.go, scrapers/gcp/gcp.go
Aggregates IAM principals and access edges, creates role configs, lists policy assets, suppresses duplicate role assets, and enriches role metadata.
Cloud Identity group expansion
scrapers/gcp/iam_group_members.go, scrapers/gcp/gcp.go
Traverses nested groups, handles cycles and lookup failures, and appends deduplicated membership results.
IAM validation
scrapers/gcp/iam_test.go
Tests principal parsing, role classification, group traversal, error handling, and access aggregation.

GitHub repository access and metadata

Layer / File(s) Summary
Repository permissions contract
api/v1/github.go, api/v1/zz_generated.deepcopy.go, config/schemas/*, chart/crds/*, chart/values.*, fixtures/github.yaml
Adds the optional GitHub permissions configuration and propagates it through schemas, CRDs, fixtures, and chart values.
Repository RBAC collection
scrapers/github/rbac.go, scrapers/github/scraper.go
Fetches collaborators and teams, resolves effective roles, deduplicates access records, and attaches repository access results.
Repository metadata and sanitization
scrapers/github/scraper.go, scrapers/github/workflows.go, scrapers/github/repository_metadata_test.go, scrapers/github/scraper_test.go
Preserves scraper context, adds topic and badge properties, sanitizes license fields, and validates repository metadata.
GitHub RBAC validation
scrapers/github/rbac_test.go
Tests API pagination, access mapping, role aliases, and invalid identity or role handling.
Gavel workflow
.github/workflows/gavel.yml
Adds pull-request test and lint execution with Go caching and result artifacts.

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
Loading

Possibly related PRs

Suggested reviewers: adityathebe

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main change: resolving external entities across the db/api areas.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/external-entity-resolution
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/external-entity-resolution

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.yml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

config/schemas/config_github.schema.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

config/schemas/scrape_config.schema.json

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'

  • 2 others

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9d12f2d and 0131036.

📒 Files selected for processing (1)
  • db/external_entities.go

Comment thread db/external_entities.go
Comment on lines +577 to +580
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)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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 || true

Repository: 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:


🏁 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 -n

Repository: 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:


🏁 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 -200

Repository: 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
fi

Repository: 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 -n

Repository: flanksource/config-db

Length of output: 204


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '140,240p' /tmp/patrickmn-cache-go.go | cat -n

Repository: 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.

moshloop added 2 commits July 23, 2026 14:20
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0131036 and f40710b.

📒 Files selected for processing (16)
  • api/v1/gcp.go
  • api/v1/gcp_test.go
  • chart/values.schema.json
  • chart/values.yaml
  • config/schemas/config_gcp.schema.json
  • config/schemas/scrape_config.schema.json
  • fixtures/gcp.yaml
  • scrapers/gcp/gcp.go
  • scrapers/gcp/iam.go
  • scrapers/gcp/iam_group_members.go
  • scrapers/gcp/iam_roles.go
  • scrapers/gcp/iam_test.go
  • scrapers/github/repository_metadata_test.go
  • scrapers/github/scraper.go
  • scrapers/github/scraper_test.go
  • scrapers/github/workflows.go

Comment thread scrapers/gcp/gcp.go
Comment on lines +53 to +55
tokenSource, err := gcpConfig.GCPConnection.TokenSource(ctx.Context,
"https://www.googleapis.com/auth/cloud-platform",
"https://www.googleapis.com/auth/cloud-identity.groups.readonly")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 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:


🏁 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' || true

Repository: 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
fi

Repository: 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:


🌐 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:


🌐 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:


🌐 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:


🌐 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:


🌐 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:


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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Keep GCP include/exclude documentation consistent.

This description documents exclude: [IAMGroupMembers], but the exclude field 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 win

Configured Tags/Labels from BaseScraper are still dropped.

BaseScraper: baseScraper only wires the config-level struct into the embedded field; it does not seed result.Tags/result.Labels (the per-item maps) the way NewScrapeResult does elsewhere (base.Tags.AsMap() / base.Labels). Here Tags is hard-coded to just owner/repo, and Labels is never set, so any tags/labels configured on the github scrape 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

📥 Commits

Reviewing files that changed from the base of the PR and between f40710b and 1e3e17d.

📒 Files selected for processing (12)
  • .github/workflows/gavel.yml
  • api/v1/github.go
  • api/v1/zz_generated.deepcopy.go
  • chart/crds/configs.flanksource.com_scrapeconfigs.yaml
  • config/schemas/config_github.schema.json
  • config/schemas/scrape_config.schema.json
  • fixtures/github.yaml
  • scrapers/github/rbac.go
  • scrapers/github/rbac_test.go
  • scrapers/github/repository_metadata_test.go
  • scrapers/github/scraper.go
  • scrapers/github/workflows.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • scrapers/github/workflows.go

Comment on lines +14 to +17
permissions:
contents: read
pull-requests: write
issues: write

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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}")
PY

Repository: 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:


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.

Comment on lines +21 to +23
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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}")
PY

Repository: 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 || true

Repository: 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

Comment on lines +42 to +45
- uses: flanksource/gavel@main
with:
args: test --lint
version: latest

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 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 || true

Repository: 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:


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.

@github-actions

Copy link
Copy Markdown

Gavel summary

Source Pass Fail Skip Duration
(unknown) 0 1 0 -
e2e 2 2 1 1m7s
exec 8 1 3 2m36s
github.com/flanksource/config-db/bench 0 14 0 -
lint: golangci-lint 0 1 0 676.65µs
lint: tsc 0 1 0 168ms
scrapers 1 1 0 4.2s
api 6 0 0 3ms
aws 98 0 0 13ms
azure 43 0 0 7ms
changes 30 0 0 3.9s
db 39 0 0 7.0s
devops 208 0 0 11ms
extract 45 0 0 210ms
file 9 0 0 378.7µs
gcp 1 0 0 1ms
github 40 0 3 15ms
github.com/flanksource/config-db/api/v1 5 0 0 -
github.com/flanksource/config-db/cmd 4 0 0 6.0s
github.com/flanksource/config-db/cmd/scrapeui 2 0 0 -
github.com/flanksource/config-db/db 22 0 0 -
github.com/flanksource/config-db/scrapers/aws 5 0 0 -
github.com/flanksource/config-db/scrapers/gcp 6 0 0 -
github.com/flanksource/config-db/scrapers/github 6 0 0 -
github.com/flanksource/config-db/scrapers/postgres 2 0 0 -
github.com/flanksource/config-db/scrapers/processors 8 0 0 -
http 28 0 0 39ms
jobs 5 0 0 1m44s
kubernetes 34 0 0 1ms
lint: betterleaks 0 0 1 -
terraform 1 0 0 952.265µs
tests 2 0 2 4.8s
utils 12 0 0 481.065µs
v1 105 0 0 14ms

Totals: 777 passed · 21 failed · 10 skipped · 5m53s

Failing tests

ginkgo Execution

Skipping ./scrapers/playwright (no test files)

Ginkgo ran 1 suite in 1.71853194s
Test Suite Passed```

#### github.com/flanksource/config-db/bench — BenchmarkLocationFilter

BenchmarkLocationFilter


#### github.com/flanksource/config-db/bench — BenchmarkRunTemplateBool/smallEnv

BenchmarkRunTemplateBool/smallEnv
BenchmarkRunTemplateBool/smallEnv-4 42344 34707 ns/op 8560 B/op 216 allocs/op


#### github.com/flanksource/config-db/bench — BenchmarkBenchSaveResultsSeed/N=10000

BenchmarkBenchSaveResultsSeed/N=10000
BenchmarkBenchSaveResultsSeed/N=10000-4 1 101172496318 ns/op 0.00 MB/s 13696278176 B/op 4418491 allocs/op


#### github.com/flanksource/config-db/bench — BenchmarkBenchSaveResultsUpdateUnchanged

BenchmarkBenchSaveResultsUpdateUnchanged


_... and 14 more failing tests — see the full gavel-results artifact._

### Failing linters

#### golangci-lint — error

golangci-lint execution failed: fork/exec /home/runner/work/config-db/config-db/.gavel/golangci-lint: exec format error
Output:


#### tsc — error

tsc wrapper failed: exit status 1
Stderr:
gavel-tsc: cannot load 'typescript' from /home/runner/work/config-db/config-db/cmd/scrapeui/frontend/node_modules. Install it with your package manager (e.g. npm i -D typescript).


[View full results](https://github.com/flanksource/config-db/actions/runs/30010881553/artifacts/8565264353)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant