Skip to content

fix(db): dedup duplicate API keys per provider on connection create (#3023) - #3100

Merged
diegosouzapw merged 1 commit into
release/v3.8.9from
fix/3023-dup-key
Jun 3, 2026
Merged

diegosouzapw merged 1 commit into
release/v3.8.9from
fix/3023-dup-key

Conversation

@diegosouzapw

Copy link
Copy Markdown
Owner

Closes #3023

Problem

Adding the same API key twice for one provider (e.g. with a different or blank name) created a second connection row.

Root cause

createProviderConnection only deduped apikey connections by (provider, auth_type, name). There was no comparison of the key value, so a duplicate key under a new name inserted a fresh row.

Fix

In the apikey branch, after the name-based upsert check, also look up existing apikey connections for the provider and compare the decrypted key (stored keys use non-deterministic AES-GCM, so ciphertext can't be compared — decrypt + compare trimmed plaintext). A match reuses/updates the existing connection.

Tests — tests/unit/provider-connection-apikey-dedup.test.ts

  • same key under a different name → 1 connection (dedups onto the existing id) (fails pre-fix)
  • same key with surrounding whitespace → still dedups (fails pre-fix)
  • genuinely different keys → 2 connections (passes both ways)

3 pass / 0 fail (RED→GREEN verified). ESLint clean (0 errors).

…3023)

createProviderConnection deduped apikey connections only by (provider, name). Adding the same key under a different/blank name created a duplicate row. It now also matches by the decrypted key value (AES-GCM ciphertext is non-deterministic, so we decrypt+compare plaintext, trimmed) and updates the existing connection instead. Tests cover same-key dedup, whitespace-variant dedup, and distinct-key separation.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@diegosouzapw
diegosouzapw merged commit 5f3b1e8 into release/v3.8.9 Jun 3, 2026
2 checks passed
@diegosouzapw
diegosouzapw deleted the fix/3023-dup-key branch June 3, 2026 10:52

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request implements API key deduplication in createProviderConnection to prevent duplicate connection rows for the same provider when the same API key is added under different names, and includes corresponding unit tests. The review feedback correctly points out a runtime error in the new test file where beforeEach and after hooks are incorrectly accessed as properties of the default test export from node:test instead of being imported as named exports.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@@ -0,0 +1,87 @@
import test from "node:test";

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.

high

In node:test, hooks like beforeEach and after are not properties of the default test export. They must be imported as named exports to avoid runtime TypeErrors.

import { test, beforeEach, after } from "node:test";

Comment on lines +20 to +25
test.beforeEach(resetStorage);

test.after(() => {
core.resetDbInstance();
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});

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.

high

Call the imported beforeEach and after hooks directly instead of accessing them as properties of test.

beforeEach(resetStorage);

after(() => {
  core.resetDbInstance();
  fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
});

@kilo-code-bot

kilo-code-bot Bot commented Jun 3, 2026 •

Copy link
Copy Markdown

Code Review Summary

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 1
WARNING 0
SUGGESTION 0
Issue Details (click to expand)

CRITICAL

File Line Issue
tests/unit/provider-connection-apikey-dedup.test.ts 9 Test sets API_KEY_SECRET but the dedup logic relies on decryptConnectionFields() from encryption.ts which checks STORAGE_ENCRYPTION_KEY (via isEncryptionEnabled()). While the test passes in passthrough mode (where keys are plaintext), it does not verify the encryption path where the fix is most critical — encrypted keys with non-deterministic AES-GCM ciphertext would not match without decrypting first. The test should set STORAGE_ENCRYPTION_KEY to enable encryption for proper verification.
Other Observations (not in diff)

Issues found in unchanged code that cannot receive inline comments:

File Line Issue
tests/unit/provider-connection-apikey-dedup.test.ts 9 The test sets API_KEY_SECRET without preserving/restoring the original value in test.after(). While the `

Positive observations

  • The implementation in src/lib/db/providers.ts (lines 208-224) correctly decrypts stored API keys before comparison, handling the non-deterministic AES-GCM ciphertext issue mentioned in the PR description.
  • The code structure properly falls back to name-based upsert first, then key-value comparison, maintaining backward compatibility.
  • Edge case handling for whitespace-only differences is well-designed.
Files Reviewed (3 files)
  • src/lib/db/providers.ts - Implementation (no issues in this file)
  • tests/unit/provider-connection-apikey-dedup.test.ts - 1 CRITICAL issue
  • CHANGELOG.md - Changelog entry (no issues)

Reviewed by laguna-m.1-20260312:free · 3,620,309 tokens

diegosouzapw added a commit that referenced this pull request Jun 3, 2026
#3023 dedup)

After #3100 (#3023) dedups provider connections by decrypted key value, the
seedConnection helper's shared 'sk-test' default collapsed multiple seeded
connections into one, breaking round-robin / least-used / fallback selection
tests (they saw 1 account instead of 2+). Default to a unique key per connection
(matching the existing unique-name default). Found via full test:unit — #3100 was
merged via gh, bypassing the pre-push test gate, so these never ran post-merge.
diegosouzapw added a commit that referenced this pull request Jun 4, 2026
…drift + #3100 dedup)

Full CI surfaced real failures that local subsets missed (gh-merged PRs bypass
the hooks that run these gates):
- typecheck:core (Lint job): 3 now-unused @ts-expect-error in mcp-server/server.ts
  (#3077 dynamic tool loops) → @ts-ignore (lenient, no TS2578).
- pack-artifact-policy.test.ts: build-reorg (#3124) renamed app/->dist/; the test
  still asserted app/ paths + REQUIRED order (it sorts alphabetically).
- electron-packaging.test.ts: extraResources from .next/electron-standalone ->
  .build/electron-standalone (#3124).
- glm-provider-model-import-route.test.ts: two GLM connections shared one apiKey,
  so #3100 (#3023) dedup collapsed them → only one discovery fetch. Distinct keys.

Remaining CI flakes (batch expiration, ModelSync self-fetch) pass in isolation —
concurrency/port flakiness under --test-concurrency=4, not real failures.
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…iegosouzapw#3023) (diegosouzapw#3100)

createProviderConnection deduped apikey connections only by (provider, name). Adding the same key under a different/blank name created a duplicate row. It now also matches by the decrypted key value (AES-GCM ciphertext is non-deterministic, so we decrypt+compare plaintext, trimmed) and updates the existing connection instead. Tests cover same-key dedup, whitespace-variant dedup, and distinct-key separation.
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
diegosouzapw#3023 dedup)

After diegosouzapw#3100 (diegosouzapw#3023) dedups provider connections by decrypted key value, the
seedConnection helper's shared 'sk-test' default collapsed multiple seeded
connections into one, breaking round-robin / least-used / fallback selection
tests (they saw 1 account instead of 2+). Default to a unique key per connection
(matching the existing unique-name default). Found via full test:unit — diegosouzapw#3100 was
merged via gh, bypassing the pre-push test gate, so these never ran post-merge.
HouMinXi pushed a commit to HouMinXi/OmniRoute that referenced this pull request Aug 2, 2026
…drift + diegosouzapw#3100 dedup)

Full CI surfaced real failures that local subsets missed (gh-merged PRs bypass
the hooks that run these gates):
- typecheck:core (Lint job): 3 now-unused @ts-expect-error in mcp-server/server.ts
  (diegosouzapw#3077 dynamic tool loops) → @ts-ignore (lenient, no TS2578).
- pack-artifact-policy.test.ts: build-reorg (diegosouzapw#3124) renamed app/->dist/; the test
  still asserted app/ paths + REQUIRED order (it sorts alphabetically).
- electron-packaging.test.ts: extraResources from .next/electron-standalone ->
  .build/electron-standalone (diegosouzapw#3124).
- glm-provider-model-import-route.test.ts: two GLM connections shared one apiKey,
  so diegosouzapw#3100 (diegosouzapw#3023) dedup collapsed them → only one discovery fetch. Distinct keys.

Remaining CI flakes (batch expiration, ModelSync self-fetch) pass in isolation —
concurrency/port flakiness under --test-concurrency=4, not real failures.
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
…iegosouzapw#3023) (diegosouzapw#3100)

createProviderConnection deduped apikey connections only by (provider, name). Adding the same key under a different/blank name created a duplicate row. It now also matches by the decrypted key value (AES-GCM ciphertext is non-deterministic, so we decrypt+compare plaintext, trimmed) and updates the existing connection instead. Tests cover same-key dedup, whitespace-variant dedup, and distinct-key separation.
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
diegosouzapw#3023 dedup)

After diegosouzapw#3100 (diegosouzapw#3023) dedups provider connections by decrypted key value, the
seedConnection helper's shared 'sk-test' default collapsed multiple seeded
connections into one, breaking round-robin / least-used / fallback selection
tests (they saw 1 account instead of 2+). Default to a unique key per connection
(matching the existing unique-name default). Found via full test:unit — diegosouzapw#3100 was
merged via gh, bypassing the pre-push test gate, so these never ran post-merge.
Poid-ZA pushed a commit to Poid-ZA/OmniRoute that referenced this pull request Aug 5, 2026
…drift + diegosouzapw#3100 dedup)

Full CI surfaced real failures that local subsets missed (gh-merged PRs bypass
the hooks that run these gates):
- typecheck:core (Lint job): 3 now-unused @ts-expect-error in mcp-server/server.ts
  (diegosouzapw#3077 dynamic tool loops) → @ts-ignore (lenient, no TS2578).
- pack-artifact-policy.test.ts: build-reorg (diegosouzapw#3124) renamed app/->dist/; the test
  still asserted app/ paths + REQUIRED order (it sorts alphabetically).
- electron-packaging.test.ts: extraResources from .next/electron-standalone ->
  .build/electron-standalone (diegosouzapw#3124).
- glm-provider-model-import-route.test.ts: two GLM connections shared one apiKey,
  so diegosouzapw#3100 (diegosouzapw#3023) dedup collapsed them → only one discovery fetch. Distinct keys.

Remaining CI flakes (batch expiration, ModelSync self-fetch) pass in isolation —
concurrency/port flakiness under --test-concurrency=4, not real failures.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…iegosouzapw#3023) (diegosouzapw#3100)

createProviderConnection deduped apikey connections only by (provider, name). Adding the same key under a different/blank name created a duplicate row. It now also matches by the decrypted key value (AES-GCM ciphertext is non-deterministic, so we decrypt+compare plaintext, trimmed) and updates the existing connection instead. Tests cover same-key dedup, whitespace-variant dedup, and distinct-key separation.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
diegosouzapw#3023 dedup)

After diegosouzapw#3100 (diegosouzapw#3023) dedups provider connections by decrypted key value, the
seedConnection helper's shared 'sk-test' default collapsed multiple seeded
connections into one, breaking round-robin / least-used / fallback selection
tests (they saw 1 account instead of 2+). Default to a unique key per connection
(matching the existing unique-name default). Found via full test:unit — diegosouzapw#3100 was
merged via gh, bypassing the pre-push test gate, so these never ran post-merge.
muhamadgalihsaputra pushed a commit to niyatna/NiyatnaRoute that referenced this pull request Sep 27, 2026
…drift + diegosouzapw#3100 dedup)

Full CI surfaced real failures that local subsets missed (gh-merged PRs bypass
the hooks that run these gates):
- typecheck:core (Lint job): 3 now-unused @ts-expect-error in mcp-server/server.ts
  (diegosouzapw#3077 dynamic tool loops) → @ts-ignore (lenient, no TS2578).
- pack-artifact-policy.test.ts: build-reorg (diegosouzapw#3124) renamed app/->dist/; the test
  still asserted app/ paths + REQUIRED order (it sorts alphabetically).
- electron-packaging.test.ts: extraResources from .next/electron-standalone ->
  .build/electron-standalone (diegosouzapw#3124).
- glm-provider-model-import-route.test.ts: two GLM connections shared one apiKey,
  so diegosouzapw#3100 (diegosouzapw#3023) dedup collapsed them → only one discovery fetch. Distinct keys.

Remaining CI flakes (batch expiration, ModelSync self-fetch) pass in isolation —
concurrency/port flakiness under --test-concurrency=4, not real failures.
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