fix: load API keys from .env at runtime — never embed credentials in cron payloads or source code

This commit is contained in:
2026-03-06 02:04:55 +00:00
parent 0d3ef7c285
commit 261f5800ad
5 changed files with 50 additions and 5 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
node_modules/
.env
# Runtime data — never commit
data/jobs_queue.json

View File

@@ -1,4 +1,6 @@
#!/usr/bin/env node
import { loadEnv } from './lib/env.mjs';
loadEnv(); // load .env before anything else
/**
* job_applier.mjs — claw-apply Job Applier
* Reads jobs queue and applies using the appropriate handler per apply_type

View File

@@ -4,6 +4,10 @@
* Searches LinkedIn + Wellfound and populates the jobs queue
* Run via cron or manually: node job_searcher.mjs
*/
import { loadEnv } from './lib/env.mjs';
loadEnv(); // load .env before anything else
/**
*/
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';

27
lib/env.mjs Normal file
View File

@@ -0,0 +1,27 @@
/**
* env.mjs — Load .env file if present
* Reads key=value pairs from .env and sets process.env for any missing vars.
* Never overwrites vars already set in the environment.
*/
import { existsSync, readFileSync } from 'fs';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
const __dir = dirname(fileURLToPath(import.meta.url));
const envPath = resolve(__dir, '../.env');
export function loadEnv() {
if (!existsSync(envPath)) return;
const lines = readFileSync(envPath, 'utf8').split('\n');
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const eq = trimmed.indexOf('=');
if (eq === -1) continue;
const key = trimmed.slice(0, eq).trim();
const val = trimmed.slice(eq + 1).trim().replace(/^["']|["']$/g, '');
if (key && !(key in process.env)) {
process.env[key] = val;
}
}
}

View File

@@ -4,10 +4,12 @@
* Verifies config, tests logins, registers cron jobs
* Run once after configuring: node setup.mjs
*/
import { existsSync, mkdirSync } from 'fs';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
import { loadConfig } from './lib/queue.mjs';
import { loadEnv } from './lib/env.mjs';
loadEnv();
const __dir = dirname(fileURLToPath(import.meta.url));
@@ -46,6 +48,16 @@ async function main() {
mkdirSync(resolve(__dir, 'data'), { recursive: true });
console.log('\n✅ Data directory ready');
// Write .env file with API keys (gitignored — never embedded in cron payloads)
const envPath = resolve(__dir, '.env');
const envLines = [];
if (process.env.KERNEL_API_KEY) envLines.push(`KERNEL_API_KEY=${process.env.KERNEL_API_KEY}`);
if (process.env.ANTHROPIC_API_KEY) envLines.push(`ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}`);
if (envLines.length) {
writeFileSync(envPath, envLines.join('\n') + '\n', { mode: 0o600 });
console.log('✅ API keys written to .env (mode 600, gitignored)');
}
// Test Telegram
if (settings.notifications?.bot_token && settings.notifications?.telegram_user_id) {
const { sendTelegram } = await import('./lib/notify.mjs');
@@ -88,9 +100,8 @@ async function main() {
const telegramUserId = settings.notifications?.telegram_user_id;
const searchSchedule = settings.schedules?.search || '0 * * * *';
const applySchedule = settings.schedules?.apply || '0 */6 * * *';
const kernelApiKey = process.env.KERNEL_API_KEY;
const anthropicApiKey = process.env.ANTHROPIC_API_KEY || '';
// API keys are loaded from .env at runtime — never embedded in cron payloads
if (gatewayToken && telegramUserId) {
console.log('\nRegistering cron jobs...');
const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${gatewayToken}` };
@@ -100,7 +111,7 @@ async function main() {
schedule: { kind: 'cron', expr: searchSchedule, tz: settings.timezone || 'America/Los_Angeles' },
payload: {
kind: 'agentTurn',
message: `Run the claw-apply job searcher: \`cd ${__dir} && KERNEL_API_KEY=${kernelApiKey}${anthropicApiKey ? ` ANTHROPIC_API_KEY=${anthropicApiKey}` : ''} node job_searcher.mjs 2>&1\`. Report how many new jobs were found.`,
message: `Run the claw-apply job searcher: \`cd ${__dir} && node job_searcher.mjs 2>&1\`. Report how many new jobs were found.`,
timeoutSeconds: 0,
},
delivery: { mode: 'announce', to: telegramUserId },
@@ -113,7 +124,7 @@ async function main() {
schedule: { kind: 'cron', expr: applySchedule, tz: settings.timezone || 'America/Los_Angeles' },
payload: {
kind: 'agentTurn',
message: `Run the claw-apply job applier: \`cd ${__dir} && KERNEL_API_KEY=${kernelApiKey}${anthropicApiKey ? ` ANTHROPIC_API_KEY=${anthropicApiKey}` : ''} node job_applier.mjs 2>&1\`. Report results.`,
message: `Run the claw-apply job applier: \`cd ${__dir} && node job_applier.mjs 2>&1\`. Report results.`,
timeoutSeconds: 0,
},
delivery: { mode: 'announce', to: telegramUserId },