Compare commits

..

2 Commits

Author SHA1 Message Date
diegosouzapw
0ea73bd527 chore(release): bump version to 3.0.0-rc.13 2026-03-23 15:39:11 -03:00
diegosouzapw
b2f0820560 fix(#549): resolve real API key from keyId in codex/droid/kilo settings
CLI settings routes (codex-settings, droid-settings, kilo-settings) were
writing the masked API key string directly to config files when the
dashboard sent a keyId. Now resolves the real key from the database via
getApiKeyById() before writing, matching the pattern already implemented
in claude-settings, openclaw-settings, and cline-settings.

Closes #549
2026-03-23 15:31:34 -03:00
7 changed files with 68 additions and 12 deletions

View File

@@ -6,16 +6,24 @@
---
## [3.0.0-rc.13] — 2026-03-23
### 🔧 Bug Fixes
- **config:** resolve real API key from `keyId` in CLI settings routes (`codex-settings`, `droid-settings`, `kilo-settings`) to prevent writing masked strings (#549)
---
## [3.0.0-rc.12] — 2026-03-23
### 🔀 Community PRs Merged
| PR | Author | Summary |
| -------- | -------- | --------------------------------------------------------------------------------- |
| **#546** | @k0valik | fix(cli): `--version` returning `unknown` on Windows — use `JSON.parse(readFileSync)` instead of ESM import |
| **#555** | @k0valik | fix(sse): centralized `resolveDataDir()` for path resolution in credentials, autoCombo, responses logger, and request logger |
| PR | Author | Summary |
| -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **#546** | @k0valik | fix(cli): `--version` returning `unknown` on Windows — use `JSON.parse(readFileSync)` instead of ESM import |
| **#555** | @k0valik | fix(sse): centralized `resolveDataDir()` for path resolution in credentials, autoCombo, responses logger, and request logger |
| **#544** | @k0valik | fix(cli): secure CLI tool detection via known installation paths (8 tools) with symlink validation, file-type checks, size bounds, minimal env in healthcheck |
| **#542** | @rdself | fix(ui): improve light mode contrast — add missing CSS theme variables (`bg-primary`, `bg-subtle`, `text-primary`) and fix dark-only colors in log detail |
| **#542** | @rdself | fix(ui): improve light mode contrast — add missing CSS theme variables (`bg-primary`, `bg-subtle`, `text-primary`) and fix dark-only colors in log detail |
### 🔧 Bug Fixes
@@ -27,6 +35,7 @@
- Test suite: **905 tests, 0 failures**
---
## [3.0.0-rc.10] — 2026-03-23
### 🔧 Bug Fixes

View File

@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: OmniRoute API
version: 3.0.0-rc.12
version: 3.0.0-rc.13
description: |
OmniRoute is a local-first AI API proxy router. It provides an OpenAI-compatible
endpoint that routes requests to multiple AI providers with load balancing,

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "omniroute",
"version": "3.0.0-rc.12",
"version": "3.0.0-rc.13",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "omniroute",
"version": "3.0.0-rc.12",
"version": "3.0.0-rc.13",
"hasInstallScript": true,
"license": "MIT",
"workspaces": [

View File

@@ -1,6 +1,6 @@
{
"name": "omniroute",
"version": "3.0.0-rc.12",
"version": "3.0.0-rc.13",
"description": "Smart AI Router with auto fallback — route to FREE & cheap models, zero downtime. Works with Cursor, Cline, Claude Desktop, Codex, and any OpenAI-compatible tool.",
"type": "module",
"bin": {

View File

@@ -12,6 +12,7 @@ import { createMultiBackup } from "@/shared/services/backupService";
import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState";
import { cliModelConfigSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { getApiKeyById } from "@/lib/localDb";
const getCodexConfigPath = () => getCliConfigPaths("codex").config;
const getCodexAuthPath = () => getCliConfigPaths("codex").auth;
@@ -166,7 +167,8 @@ export async function POST(request: Request) {
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const { baseUrl, apiKey, model } = validation.data;
const { baseUrl, model } = validation.data;
let { apiKey } = validation.data;
if (!apiKey) {
return NextResponse.json(
{ error: "baseUrl, apiKey and model are required" },
@@ -174,6 +176,21 @@ export async function POST(request: Request) {
);
}
// (#549) Resolve real key from DB if keyId was provided.
// The dashboard sends masked key strings — resolving by ID guarantees
// we always write the full key value to the config file.
const keyId = typeof rawBody?.keyId === "string" ? rawBody.keyId.trim() : null;
if (keyId) {
try {
const keyRecord = await getApiKeyById(keyId);
if (keyRecord?.key) {
apiKey = keyRecord.key as string;
}
} catch {
// Non-critical: fall back to whatever value was in apiKey
}
}
const codexDir = getCodexDir();
const configPath = getCodexConfigPath();
const authPath = getCodexAuthPath();

View File

@@ -12,6 +12,7 @@ import { createBackup } from "@/shared/services/backupService";
import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState";
import { cliModelConfigSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { getApiKeyById } from "@/lib/localDb";
const getDroidSettingsPath = () => getCliPrimaryConfigPath("droid");
const getDroidDir = () => path.dirname(getDroidSettingsPath());
@@ -101,7 +102,21 @@ export async function POST(request: Request) {
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const { baseUrl, apiKey, model } = validation.data;
const { baseUrl, model } = validation.data;
let { apiKey } = validation.data;
// (#549) Resolve real key from DB if keyId was provided.
const keyId = typeof rawBody?.keyId === "string" ? rawBody.keyId.trim() : null;
if (keyId) {
try {
const keyRecord = await getApiKeyById(keyId);
if (keyRecord?.key) {
apiKey = keyRecord.key as string;
}
} catch {
// Non-critical: fall back to whatever value was in apiKey
}
}
const droidDir = getDroidDir();
const settingsPath = getDroidSettingsPath();

View File

@@ -9,6 +9,7 @@ import { createBackup } from "@/shared/services/backupService";
import { saveCliToolLastConfigured, deleteCliToolLastConfigured } from "@/lib/db/cliToolState";
import { cliModelConfigSchema } from "@/shared/validation/schemas";
import { isValidationFailure, validateBody } from "@/shared/validation/helpers";
import { getApiKeyById } from "@/lib/localDb";
const KILO_DATA_DIR = path.join(os.homedir(), ".local", "share", "kilo");
const AUTH_PATH = path.join(KILO_DATA_DIR, "auth.json");
@@ -133,7 +134,21 @@ export async function POST(request) {
if (isValidationFailure(validation)) {
return NextResponse.json({ error: validation.error }, { status: 400 });
}
const { baseUrl, apiKey, model } = validation.data;
const { baseUrl, model } = validation.data;
let { apiKey } = validation.data;
// (#549) Resolve real key from DB if keyId was provided.
const keyId = typeof rawBody?.keyId === "string" ? rawBody.keyId.trim() : null;
if (keyId) {
try {
const keyRecord = await getApiKeyById(keyId);
if (keyRecord?.key) {
apiKey = keyRecord.key as string;
}
} catch {
// Non-critical: fall back to whatever value was in apiKey
}
}
// Ensure directories exist
await fs.mkdir(KILO_DATA_DIR, { recursive: true });