Merge pull request #13 from mane/fix/api-key-env-handling
fix: avoid implicit API key imports
This commit is contained in:
3 files changed
+69
-33
No files matched your search
+26
-14
@@ -1,18 +1,19 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# T3MP3ST API Key Setup Script
|
# T3MP3ST API Key Setup Script
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
umask 077
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "╔═══════════════════════════════════════════════════════════╗"
|
echo "╔═══════════════════════════════════════════════════════════╗"
|
||||||
echo "║ T3MP3ST API KEY CONFIGURATION ║"
|
echo "║ T3MP3ST API KEY CONFIGURATION ║"
|
||||||
echo "╚═══════════════════════════════════════════════════════════╝"
|
echo "╚═══════════════════════════════════════════════════════════╝"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Check if .env exists
|
|
||||||
ENV_FILE="$(dirname "$0")/../.env"
|
ENV_FILE="$(dirname "$0")/../.env"
|
||||||
|
|
||||||
if [ -f "$ENV_FILE" ]; then
|
if [ -f "$ENV_FILE" ]; then
|
||||||
echo "[*] Existing .env file found"
|
echo "[*] Existing .env file found; it will be replaced after you enter a new key."
|
||||||
source "$ENV_FILE"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
@@ -22,29 +23,38 @@ echo " 2) Anthropic (Claude direct)"
|
|||||||
echo " 3) OpenAI (GPT models)"
|
echo " 3) OpenAI (GPT models)"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
read -p "Enter choice [1-3]: " choice
|
read -r -p "Enter choice [1-3]: " choice
|
||||||
|
|
||||||
case $choice in
|
case $choice in
|
||||||
1)
|
1)
|
||||||
echo ""
|
echo ""
|
||||||
echo "Get your OpenRouter API key at: https://openrouter.ai/keys"
|
echo "Get your OpenRouter API key at: https://openrouter.ai/keys"
|
||||||
read -p "Enter your OpenRouter API key: " api_key
|
read -rsp "Enter your OpenRouter API key: " api_key
|
||||||
echo "OPENROUTER_API_KEY=$api_key" > "$ENV_FILE"
|
echo ""
|
||||||
echo "LLM_PROVIDER=openrouter" >> "$ENV_FILE"
|
{
|
||||||
|
printf 'OPENROUTER_API_KEY=%s\n' "$api_key"
|
||||||
|
printf 'LLM_PROVIDER=openrouter\n'
|
||||||
|
} > "$ENV_FILE"
|
||||||
;;
|
;;
|
||||||
2)
|
2)
|
||||||
echo ""
|
echo ""
|
||||||
echo "Get your Anthropic API key at: https://console.anthropic.com/"
|
echo "Get your Anthropic API key at: https://console.anthropic.com/"
|
||||||
read -p "Enter your Anthropic API key: " api_key
|
read -rsp "Enter your Anthropic API key: " api_key
|
||||||
echo "ANTHROPIC_API_KEY=$api_key" > "$ENV_FILE"
|
echo ""
|
||||||
echo "LLM_PROVIDER=anthropic" >> "$ENV_FILE"
|
{
|
||||||
|
printf 'ANTHROPIC_API_KEY=%s\n' "$api_key"
|
||||||
|
printf 'LLM_PROVIDER=anthropic\n'
|
||||||
|
} > "$ENV_FILE"
|
||||||
;;
|
;;
|
||||||
3)
|
3)
|
||||||
echo ""
|
echo ""
|
||||||
echo "Get your OpenAI API key at: https://platform.openai.com/api-keys"
|
echo "Get your OpenAI API key at: https://platform.openai.com/api-keys"
|
||||||
read -p "Enter your OpenAI API key: " api_key
|
read -rsp "Enter your OpenAI API key: " api_key
|
||||||
echo "OPENAI_API_KEY=$api_key" > "$ENV_FILE"
|
echo ""
|
||||||
echo "LLM_PROVIDER=openai" >> "$ENV_FILE"
|
{
|
||||||
|
printf 'OPENAI_API_KEY=%s\n' "$api_key"
|
||||||
|
printf 'LLM_PROVIDER=openai\n'
|
||||||
|
} > "$ENV_FILE"
|
||||||
;;
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Invalid choice"
|
echo "Invalid choice"
|
||||||
@@ -52,6 +62,8 @@ case $choice in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
chmod 600 "$ENV_FILE"
|
||||||
|
|
||||||
# Add .env to gitignore if not already there
|
# Add .env to gitignore if not already there
|
||||||
GITIGNORE="$(dirname "$0")/../.gitignore"
|
GITIGNORE="$(dirname "$0")/../.gitignore"
|
||||||
if [ -f "$GITIGNORE" ]; then
|
if [ -f "$GITIGNORE" ]; then
|
||||||
@@ -69,7 +81,7 @@ echo "╔═══════════════════════
|
|||||||
echo "║ CONFIGURATION COMPLETE! ║"
|
echo "║ CONFIGURATION COMPLETE! ║"
|
||||||
echo "╚═══════════════════════════════════════════════════════════╝"
|
echo "╚═══════════════════════════════════════════════════════════╝"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Your API key has been saved to .env"
|
echo "Your API key has been saved to .env (mode 600)"
|
||||||
echo ""
|
echo ""
|
||||||
echo "Start the server with:"
|
echo "Start the server with:"
|
||||||
echo " npm run server"
|
echo " npm run server"
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { readFileSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
|
||||||
|
const configSource = readFileSync(join(process.cwd(), 'src/config/index.ts'), 'utf8');
|
||||||
|
const setupScript = readFileSync(join(process.cwd(), 'scripts/setup-api.sh'), 'utf8');
|
||||||
|
|
||||||
|
function configLoadEnvBlock(): string {
|
||||||
|
const start = configSource.indexOf('private loadEnvVariables(): void');
|
||||||
|
expect(start).toBeGreaterThanOrEqual(0);
|
||||||
|
const end = configSource.indexOf('\n /**\n * Get all settings', start);
|
||||||
|
expect(end).toBeGreaterThan(start);
|
||||||
|
return configSource.slice(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('API key environment handling hardening', () => {
|
||||||
|
it('ConfigManager does not implicitly read .env from the caller working directory', () => {
|
||||||
|
const block = configLoadEnvBlock();
|
||||||
|
|
||||||
|
expect(block).not.toContain("join(process.cwd(), '.env')");
|
||||||
|
expect(block).toContain("join(homedir(), '.t3mp3st', '.env')");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ConfigManager uses env keys in-memory and does not persist imported env keys', () => {
|
||||||
|
const block = configLoadEnvBlock();
|
||||||
|
|
||||||
|
expect(block).not.toMatch(/setApiKey\(/);
|
||||||
|
expect(block).not.toMatch(/config\.set\(['"]apiKeys['"]/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('setup-api.sh never sources .env and reads API keys silently into a 0600 file', () => {
|
||||||
|
expect(setupScript).not.toMatch(/\bsource\s+["']?\$ENV_FILE/);
|
||||||
|
expect(setupScript).toMatch(/umask\s+077/);
|
||||||
|
expect(setupScript).toMatch(/read\s+-rsp\s+"Enter your OpenRouter API key:/);
|
||||||
|
expect(setupScript).toMatch(/chmod\s+600\s+"\$ENV_FILE"/);
|
||||||
|
});
|
||||||
|
});
|
||||||
+6
-19
@@ -452,9 +452,10 @@ class ConfigManager {
|
|||||||
private loadEnvVariables(): void {
|
private loadEnvVariables(): void {
|
||||||
if (this.envLoaded) return;
|
if (this.envLoaded) return;
|
||||||
|
|
||||||
// Try to load from .env file in current directory or home
|
// Load only T3MP3ST-owned/home env files. Do NOT read process.cwd()/.env:
|
||||||
|
// operators often run T3MP3ST inside target repos, and importing that repo's
|
||||||
|
// secrets would contaminate this process with unrelated credentials.
|
||||||
const envPaths = [
|
const envPaths = [
|
||||||
join(process.cwd(), '.env'),
|
|
||||||
join(homedir(), '.t3mp3st', '.env'),
|
join(homedir(), '.t3mp3st', '.env'),
|
||||||
join(homedir(), '.env'),
|
join(homedir(), '.env'),
|
||||||
];
|
];
|
||||||
@@ -482,23 +483,9 @@ class ConfigManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check environment variables for API keys
|
// Environment variables (including values loaded from ~/.t3mp3st/.env above)
|
||||||
const envKeys = {
|
// are intentionally process-local. getApiKey() already gives env vars highest
|
||||||
openrouter: process.env.OPENROUTER_API_KEY,
|
// priority, so do not persist them into Conf's apiKeys store as a side effect.
|
||||||
venice: process.env.VENICE_API_KEY,
|
|
||||||
anthropic: process.env.ANTHROPIC_API_KEY,
|
|
||||||
openai: process.env.OPENAI_API_KEY,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Only set from env if not already set in config
|
|
||||||
const currentKeys = this.config.get('apiKeys');
|
|
||||||
|
|
||||||
for (const [provider, envKey] of Object.entries(envKeys)) {
|
|
||||||
if (envKey && !currentKeys[provider as keyof typeof currentKeys]) {
|
|
||||||
this.setApiKey(provider as 'openrouter' | 'venice' | 'anthropic' | 'openai', envKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.envLoaded = true;
|
this.envLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user