Skip to content

fix: Optimized IP whitelist string conversion to avoid redundant processing on every verification#4063

Merged
chronark merged 16 commits intounkeyed:mainfrom
Akhileshait:fix-optimize-IPwhitelist-issue-#3544
Oct 9, 2025
Merged

fix: Optimized IP whitelist string conversion to avoid redundant processing on every verification#4063
chronark merged 16 commits intounkeyed:mainfrom
Akhileshait:fix-optimize-IPwhitelist-issue-#3544

Conversation

@Akhileshait
Copy link
Contributor

@Akhileshait Akhileshait commented Oct 5, 2025

What does this PR do?

Fixes #3544

This PR optimizes IP whitelist validation by moving the string parsing from the hot path (executed on every key verification) to the initialization phase (executed once when the key is loaded and cached).

Performance Impact:

  • Previously: IP whitelist string was split and trimmed on every verification request
  • Now: IP whitelist is parsed once during key loading and stored in the KeyVerifier struct
  • Result: Reduced CPU overhead for keys with IP whitelists, especially those verified frequently

Implementation:

  1. Added parsedIPWhitelist []string field to KeyVerifier struct
  2. Moved IP parsing logic to Get() function where keys are loaded
  3. Simplified withIPWhitelist() to use pre-parsed slice directly
  4. Removed redundant string operations and unused imports

Since keys are cached, this optimization ensures string processing happens only once per cache entry rather than on every verification request.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

Unit Tests

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

@vercel
Copy link

vercel bot commented Oct 5, 2025

@Akhileshait is attempting to deploy a commit to the Unkey Team on Vercel.

A member of the Team first needs to authorize it.

@changeset-bot
Copy link

changeset-bot bot commented Oct 5, 2025

⚠️ No Changeset found

Latest commit: 18b2e8b

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@CLAassistant
Copy link

CLAassistant commented Oct 5, 2025

CLA assistant check
All committers have signed the CLA.

@github-actions
Copy link
Contributor

github-actions bot commented Oct 5, 2025

Thank you for following the naming conventions for pull request titles! 🙏

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 5, 2025

📝 Walkthrough

Walkthrough

Preparse and cache key IP whitelists by introducing db.CachedKeyData (embedding FindKeyForVerificationRow plus ParsedIPWhitelist) and switch caches, loaders, verifier construction, and validation to use the cached pre-parsed map for O(1) IP checks instead of reparsing strings per verification.

Changes

Cohort / File(s) Summary
Cached key type
go/pkg/db/cached_key_data.go
Add CachedKeyData embedding FindKeyForVerificationRow and ParsedIPWhitelist map[string]struct{}.
Key load & cache population
go/internal/services/keys/get.go
SWR callback now returns db.CachedKeyData; parse IpWhitelist (split/trim/filter → map) during cache population and store in ParsedIPWhitelist.
Key verifier & validation
go/internal/services/keys/verifier.go, go/internal/services/keys/validation.go
KeyVerifier now holds parsedIPWhitelist map[string]struct{} (reordered); validation uses O(1) lookup against the pre-parsed map and skips parsing when empty.
Service & cache types (internal & gw)
go/internal/services/caches/caches.go, go/apps/gw/services/caches/caches.go, go/internal/services/keys/service.go
Change verification-key cache value type from db.FindKeyForVerificationRowdb.CachedKeyData; update field signatures, comments, and cache initialization generics.
API handlers (various routes)
go/apps/api/routes/v2_keys_add_permissions/handler.go, .../v2_keys_add_roles/handler.go, .../v2_keys_delete_key/handler.go, .../v2_keys_remove_permissions/handler.go, .../v2_keys_remove_roles/handler.go, .../v2_keys_set_permissions/handler.go, .../v2_keys_set_roles/handler.go, .../v2_keys_update_credits/handler.go, .../v2_keys_update_key/handler.go
Update Handler.KeyCache generic from cache.Cache[string, db.FindKeyForVerificationRow] to cache.Cache[string, db.CachedKeyData].

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant KeysService
    participant Cache
    participant DB
    participant KeyVerifier

    Client->>KeysService: request with API key
    KeysService->>Cache: lookup key by hash
    alt cache miss
        KeysService->>DB: load FindKeyForVerificationRow
        DB-->>KeysService: row
        KeysService->>KeysService: parse IpWhitelist -> map[string]struct{}
        KeysService->>Cache: store db.CachedKeyData{row, ParsedIPWhitelist}
        KeysService->>KeyVerifier: construct verifier(with parsedIPWhitelist)
    else cache hit
        Cache-->>KeysService: db.CachedKeyData
        KeysService->>KeyVerifier: construct verifier(use cached parsedIPWhitelist)
    end

    KeysService->>KeyVerifier: Verify(request)
    alt parsedIPWhitelist empty
        KeyVerifier->>KeysService: skip IP check
    else
        KeyVerifier->>KeyVerifier: O(1) lookup client IP in parsedIPWhitelist
        alt found
            KeyVerifier->>KeysService: allow
        else
            KeyVerifier->>KeysService: forbid
        end
    end

    KeysService->>Client: response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • feat: routes v2 ratelimit #2891 — Changes to cache/generic types and SWR callback return types; strongly related to the cache-type and SWR callback edits in this PR.

Suggested labels

Bug, 🕹️ oss.gg, :joystick: 300 points

Suggested reviewers

  • imeyer
  • chronark
  • perkinsjr

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly summarizes the core change by indicating that IP whitelist string conversion has been optimized to avoid redundant processing on each verification, which aligns with the main change implemented in this pull request.
Linked Issues Check ✅ Passed The implementation addresses the objectives of issue #3544 by moving the IP whitelist parsing to the key loading phase, storing the parsed whitelist in CachedKeyData, updating the validation logic to use the pre-parsed whitelist, and thus eliminating redundant per-verification string processing as requested.
Out of Scope Changes Check ✅ Passed All changes focus on the new CachedKeyData type, cache configurations, and verification flow required for pre-parsed IP whitelist processing, and no unrelated or out-of-scope code modifications are present.
Description Check ✅ Passed The pull request description follows the repository template by including sections for What does this PR do, Type of change, How should this be tested, and Checklist, references issue #3544, explains the change, motivation, and performance impact, making it sufficiently complete and structured.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6500c01 and 18b2e8b.

📒 Files selected for processing (1)
  • go/internal/services/keys/get.go (6 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
go/internal/services/keys/get.go (3)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/pkg/db/retry.go (1)
  • WithRetry (28-67)
go/pkg/db/key_find_for_verification.sql_generated.go (1)
  • FindKeyForVerificationRow (92-119)
🔇 Additional comments (4)
go/internal/services/keys/get.go (4)

7-7: LGTM! Import issue resolved.

The strings import has been correctly added to support the IP whitelist parsing logic.


71-96: Excellent optimization—parsing moved to cache population.

The IP whitelist parsing now occurs once during cache population rather than on every key verification, achieving the core PR objective. The implementation correctly:

  • Returns CachedKeyData with pre-parsed IPs
  • Uses map[string]struct{} for O(1) lookup performance
  • Filters empty strings after trimming (addresses previous review concern)
  • Initializes an empty map when no IPs exist, avoiding nil checks downstream

131-149: LGTM! Correctly adapted for new cache structure.

The KeyVerifier initialization has been properly updated to use key.FindKeyForVerificationRow to match the new CachedKeyData type.


204-223: LGTM! Pre-parsed whitelist correctly propagated to verifier.

The KeyVerifier initialization properly:

  • Assigns the database row from key.FindKeyForVerificationRow
  • Passes the pre-parsed IP whitelist map to the verifier via parsedIPWhitelist: key.ParsedIPWhitelist

This completes the optimization by making the cached parsed IPs available for O(1) lookup during verification.


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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
go/internal/services/keys/get.go (1)

176-180: Consider filtering empty strings from malformed input.

Malformed whitelist entries (e.g., trailing commas or consecutive commas like "1.2.3.4,,5.6.7.8") will create empty strings in the parsed slice. While harmless (empty strings won't match real IPs), filtering them out improves data quality.

Apply this diff to filter empty entries:

-	ips := strings.Split(key.IpWhitelist.String, ",")
-	parsedIPWhitelist = make([]string, len(ips))
-	for i, ip := range ips {
-		parsedIPWhitelist[i] = strings.TrimSpace(ip)
+	ips := strings.Split(key.IpWhitelist.String, ",")
+	parsedIPWhitelist = make([]string, 0, len(ips))
+	for _, ip := range ips {
+		trimmed := strings.TrimSpace(ip)
+		if trimmed != "" {
+			parsedIPWhitelist = append(parsedIPWhitelist, trimmed)
+		}
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 441fb9b and f0d1395.

📒 Files selected for processing (3)
  • go/internal/services/keys/get.go (2 hunks)
  • go/internal/services/keys/validation.go (2 hunks)
  • go/internal/services/keys/verifier.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
go/internal/services/keys/get.go (1)
go/internal/services/keys/status.go (1)
  • StatusValid (15-15)
🔇 Additional comments (4)
go/internal/services/keys/verifier.go (1)

42-43: LGTM! Field addition improves verification performance.

The new parsedIPWhitelist field enables pre-parsing of the IP whitelist during key loading, eliminating redundant string operations on every verification call.

go/internal/services/keys/validation.go (2)

60-62: LGTM! Correctly uses pre-parsed whitelist.

The empty check properly handles both nil and empty slices, maintaining the original behavior while eliminating runtime string parsing.


71-71: LGTM! Simplified membership check.

Using slices.Contains() on the pre-parsed list is cleaner and more efficient than the previous string split and iteration approach.

go/internal/services/keys/get.go (1)

197-202: LGTM! Initialization correctly includes pre-parsed whitelist.

The parsedIPWhitelist field is properly initialized alongside existing fields, enabling the optimized validation path.

@Akhileshait Akhileshait changed the title Issue #3544 fix: Optimized IP whitelist string conversion to avoid redundant processing on every verification fix: Optimized IP whitelist string conversion to avoid redundant processing on every verification Oct 5, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
go/internal/services/keys/get.go (1)

7-7: LGTM! Resolves the previous critical issue.

The strings import has been correctly added and is necessary for strings.Split (line 177) and strings.TrimSpace (line 180).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5067ff5 and d82ced0.

📒 Files selected for processing (1)
  • go/internal/services/keys/get.go (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
go/internal/services/keys/get.go (1)
go/internal/services/keys/status.go (1)
  • StatusValid (15-15)
🔇 Additional comments (1)
go/internal/services/keys/get.go (1)

198-203: LGTM! Achieves the optimization goal.

The parsedIPWhitelist field is correctly initialized with the pre-parsed IP slice, ensuring the whitelist is parsed once during key loading rather than on every verification. This eliminates redundant string operations in the hot path as intended.

@chronark
Copy link
Collaborator

chronark commented Oct 6, 2025

that looks fine to me
let's wait for #4069 to be merged and pull in the changes, to ensure this didn't break anything by accident.

@vercel
Copy link

vercel bot commented Oct 6, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
dashboard Ready Ready Preview Comment Oct 9, 2025 10:55am
engineering Ready Ready Preview Comment Oct 9, 2025 10:55am

@Akhileshait Akhileshait requested a review from Flo4604 October 7, 2025 09:09
@Flo4604
Copy link
Member

Flo4604 commented Oct 7, 2025

@Akhileshait can you please run make fmt the tests are failing due to an unused import.

@Akhileshait
Copy link
Contributor Author

@Flo4604 I have removed unused imports please run it now.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
go/internal/services/keys/validation.go (1)

69-71: Skip empty entries and normalize IPs in whitelist parsing

In go/internal/services/keys/get.go, after splitting and trimming each IP:

  • Add if trimmed == "" { continue } to skip empty entries
  • Use net.ParseIP(trimmed).String() for canonical IPv4/IPv6 formatting before storing in parsedIPWhitelist

This ensures no empty keys and consistent matching for IPv6 addresses.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e7e047 and 00a2702.

📒 Files selected for processing (1)
  • go/internal/services/keys/validation.go (2 hunks)
🔇 Additional comments (1)
go/internal/services/keys/validation.go (1)

58-60: LGTM! Efficient empty-whitelist check.

Using len(k.parsedIPWhitelist) == 0 correctly handles both nil and empty maps, maintaining the same "no whitelist configured" semantics while enabling the performance optimization.

@Flo4604
Copy link
Member

Flo4604 commented Oct 7, 2025

@Flo4604 I have removed unused imports please run it now.

The tests are still failing, you can run make build or docker compose -f ../deployment/docker-compose.yaml up apiv2 -d --build in the go directory to build and manually test your changes.

Otherwise make test will run all the tests

If you have any questions regarding the setup please do not hesitate to ask

Please also fix the merge conflicts that popped up.

@coderabbitai coderabbitai bot requested a review from chronark October 8, 2025 20:53
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
go/internal/services/keys/get.go (1)

131-140: Consider inlining the workspace-disabled case.

The early initialization at lines 131-135 creates a KeyVerifier that is discarded if the workspace is enabled (line 194 creates a new instance). While this pattern avoids duplication, it's slightly inefficient.

Consider inlining the initialization for the workspace-disabled case:

-	kv := &KeyVerifier{
-		Status:  StatusWorkspaceDisabled,
-		message: "workspace is disabled",
-		region:  s.region,
-	}
-
 	if !key.WorkspaceEnabled || (key.ForWorkspaceEnabled.Valid && !key.ForWorkspaceEnabled.Bool) {
+		kv := &KeyVerifier{
+			Status:  StatusWorkspaceDisabled,
+			message: "workspace is disabled",
+			region:  s.region,
+		}
 		// nolint:exhaustruct
 		return kv, kv.log, nil
 	}

This avoids allocating a KeyVerifier that will be discarded in the common case where the workspace is enabled.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00a2702 and 6500c01.

📒 Files selected for processing (10)
  • go/apps/api/routes/v2_keys_add_permissions/handler.go (1 hunks)
  • go/apps/api/routes/v2_keys_add_roles/handler.go (1 hunks)
  • go/apps/api/routes/v2_keys_delete_key/handler.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_permissions/handler.go (1 hunks)
  • go/apps/api/routes/v2_keys_remove_roles/handler.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_permissions/handler.go (1 hunks)
  • go/apps/api/routes/v2_keys_set_roles/handler.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_credits/handler.go (1 hunks)
  • go/apps/api/routes/v2_keys_update_key/handler.go (1 hunks)
  • go/internal/services/keys/get.go (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (10)
go/apps/api/routes/v2_keys_remove_roles/handler.go (1)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/apps/api/routes/v2_keys_delete_key/handler.go (1)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/apps/api/routes/v2_keys_set_permissions/handler.go (1)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/apps/api/routes/v2_keys_update_credits/handler.go (1)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/apps/api/routes/v2_keys_remove_permissions/handler.go (1)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/apps/api/routes/v2_keys_add_permissions/handler.go (1)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/apps/api/routes/v2_keys_add_roles/handler.go (1)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/apps/api/routes/v2_keys_update_key/handler.go (1)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/apps/api/routes/v2_keys_set_roles/handler.go (1)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/internal/services/keys/get.go (5)
go/pkg/db/cached_key_data.go (1)
  • CachedKeyData (5-8)
go/pkg/db/retry.go (1)
  • WithRetry (28-67)
go/pkg/db/key_find_for_verification.sql_generated.go (1)
  • FindKeyForVerificationRow (92-119)
go/internal/services/keys/verifier.go (1)
  • KeyVerifier (32-57)
go/internal/services/keys/status.go (2)
  • StatusWorkspaceDisabled (23-23)
  • StatusValid (15-15)
🔇 Additional comments (12)
go/apps/api/routes/v2_keys_remove_roles/handler.go (1)

32-32: LGTM! Type change aligns with IP whitelist optimization.

The KeyCache type now uses db.CachedKeyData, which embeds FindKeyForVerificationRow and adds ParsedIPWhitelist for pre-parsed IP addresses. This change enables the performance optimization described in the PR objectives without breaking existing Remove calls.

go/apps/api/routes/v2_keys_update_key/handler.go (1)

38-38: LGTM! Consistent type change.

The KeyCache field type change to db.CachedKeyData is consistent with the PR-wide refactor to support pre-parsed IP whitelists.

go/apps/api/routes/v2_keys_add_roles/handler.go (1)

32-32: LGTM! Type change supports performance optimization.

The KeyCache field now uses db.CachedKeyData, enabling pre-parsed IP whitelist lookups as intended by the PR.

go/apps/api/routes/v2_keys_delete_key/handler.go (1)

33-33: LGTM! Consistent cache type update.

The KeyCache type change to db.CachedKeyData maintains consistency across all handlers and supports the IP whitelist optimization.

go/apps/api/routes/v2_keys_set_roles/handler.go (1)

33-33: LGTM! Type change aligns with refactor.

The KeyCache field type update to db.CachedKeyData is consistent with the broader PR changes to optimize IP whitelist handling.

go/apps/api/routes/v2_keys_set_permissions/handler.go (1)

34-34: LGTM! Consistent type change.

The KeyCache field now uses db.CachedKeyData, maintaining consistency with other handlers and enabling pre-parsed IP whitelist functionality.

go/apps/api/routes/v2_keys_update_credits/handler.go (1)

34-34: LGTM! Type change supports optimization.

The KeyCache field type change to db.CachedKeyData is correct and consistent with the PR objectives.

go/apps/api/routes/v2_keys_remove_permissions/handler.go (1)

31-31: LGTM! Final handler type change.

The KeyCache field type update to db.CachedKeyData completes the consistent refactor across all API route handlers, supporting the IP whitelist optimization.

go/apps/api/routes/v2_keys_add_permissions/handler.go (1)

33-33: LGTM! Cache type updated to support pre-parsed IP whitelist.

The change from db.FindKeyForVerificationRow to db.CachedKeyData aligns with the broader refactoring to move IP whitelist parsing into the cache population phase.

go/internal/services/keys/get.go (3)

7-7: LGTM! Import required for IP parsing logic.

The strings package is correctly imported and used at lines 83 and 85 for splitting and trimming IP whitelist entries.


71-96: LGTM! IP parsing moved to cache population as intended.

This change achieves the core objective of parsing the IP whitelist once during cache population rather than on every verification request. Key improvements:

  • IP parsing (split, trim, filter) now happens inside the SWR callback
  • Empty strings are correctly filtered at line 86 via the trimmed != "" check
  • Result is stored as map[string]struct{} in CachedKeyData for O(1) lookup during verification
  • db.WithRetry wrapper provides resilience for transient database errors

Performance impact: Parsing overhead is eliminated from the hot path and amortized across cache lifetime.


194-214: LGTM! Uses pre-parsed IP whitelist from cache.

The initialization correctly:

  • Accesses the embedded FindKeyForVerificationRow via key.FindKeyForVerificationRow (line 195)
  • Uses the pre-parsed ParsedIPWhitelist map from cache (line 210)
  • Avoids redundant parsing on the verification hot path

@Akhileshait
Copy link
Contributor Author

Hi @Flo4604 I have resolved the bug which made the build fail. Actually Handler struct needed to be updated as I had updated CachedKeyData struct. I have tested this with make test. It ran successfully and similarly there is no issue with make build as well.
image

Copy link
Member

@Flo4604 Flo4604 left a comment

Choose a reason for hiding this comment

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

apps/api/integration/root_keys tests are failing looks like a bad merge

@Akhileshait
Copy link
Contributor Author

Hi @chronark Resolved merging bugs, please review those changes

@graphite-app
Copy link

graphite-app bot commented Oct 9, 2025

TV gif. Among an applauding crowd, Fred Savage as Kevin in The Wonder Years looks out at us, proud and raising up a thumbs up. (Added via Giphy)

@graphite-app
Copy link

graphite-app bot commented Oct 9, 2025

Graphite Automations

"Post a GIF when PR approved" took an action on this PR • (10/09/25)

1 gif was posted to this PR based on Andreas Thomas's automation.

@chronark chronark enabled auto-merge October 9, 2025 10:55
@chronark chronark added this pull request to the merge queue Oct 9, 2025
Merged via the queue into unkeyed:main with commit 617d755 Oct 9, 2025
13 checks passed
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.

Optimize IP whitelist string conversion to avoid redundant processing on every verification

4 participants