Skip to content

Commit 3ac84fd

Browse files
authored
fix(security): remove hardcoded API key references and standardize environment variables (#143)
1 parent 1027c00 commit 3ac84fd

8 files changed

Lines changed: 136 additions & 21 deletions

File tree

.env.example

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,30 @@
22
# Environment Variables for ad-blocking toolkit
33
# Copy this file to .env and fill in your values
44
# =============================================================================
5+
# SECURITY: Never commit .env files with real credentials!
6+
# This file is tracked in git as an example. Your .env file is gitignored.
7+
# =============================================================================
58

69
# Debug mode (set to any value to enable)
710
DEBUG=
811

9-
# AdGuard DNS API Key (for Console UI)
10-
ADGUARD_API_KEY=
12+
# =============================================================================
13+
# AdGuard DNS API Configuration
14+
# =============================================================================
15+
# Use ONE of the following formats (ADGUARD_AdGuard__ApiKey is recommended
16+
# for cross-platform compatibility across .NET, TypeScript, Rust, etc.):
17+
#
18+
# Recommended (cross-platform format - works with C#, TypeScript, Rust CLI, and others):
19+
ADGUARD_AdGuard__ApiKey=
20+
#
21+
# Alternative (TypeScript/JavaScript fallback for existing configs; new projects
22+
# should prefer ADGUARD_AdGuard__ApiKey, but the TypeScript client supports both):
23+
# ADGUARD_API_KEY=
24+
#
25+
# Legacy (Rust CLI only, deprecated):
26+
# ADGUARD_API_TOKEN=
1127

12-
# Linear API Key (for Linear import scripts)
28+
# =============================================================================
29+
# Linear Integration (for Linear import scripts in src/linear/)
30+
# =============================================================================
1331
LINEAR_API_KEY=
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
{
22
"AdGuard": {
3-
"ApiKey": "",
43
"BaseUrl": "https://api.adguard-dns.io/oapi/v1"
54
}
65
}

src/adguard-api-typescript/jest.config.js renamed to src/adguard-api-typescript/jest.config.cjs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
/** @type {import('jest').Config} */
22
module.exports = {
3-
preset: 'ts-jest',
3+
preset: 'ts-jest/presets/default-esm',
44
testEnvironment: 'node',
55
roots: ['<rootDir>/src', '<rootDir>/tests'],
66
testMatch: ['**/*.test.ts'],
7+
extensionsToTreatAsEsm: ['.ts'],
8+
transform: {
9+
'^.+\\.tsx?$': [
10+
'ts-jest',
11+
{
12+
useESM: true,
13+
},
14+
],
15+
},
716
collectCoverageFrom: [
817
'src/**/*.ts',
918
'!src/**/*.d.ts',
@@ -20,6 +29,7 @@ module.exports = {
2029
},
2130
},
2231
moduleNameMapper: {
32+
'^(\\.{1,2}/.*)\\.js$': '$1',
2333
'^@/(.*)$': '<rootDir>/src/$1',
2434
},
2535
setupFilesAfterEnv: ['<rootDir>/tests/setup.ts'],

src/adguard-api-typescript/src/client.ts

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,33 @@ export class ApiClientFactory {
8181

8282
/**
8383
* Configure from environment variable
84-
* @param envVar - Environment variable name (default: ADGUARD_API_KEY)
84+
*
85+
* Supports multiple environment variable naming conventions:
86+
* - .NET-compatible format: ADGUARD_AdGuard__ApiKey (recommended, tried first)
87+
* - Legacy/alternative format: ADGUARD_API_KEY (fallback)
88+
*
89+
* @param envVar - Custom environment variable name (overrides default lookup)
8590
* @param logger - Optional logger
8691
*/
87-
configureFromEnv(envVar: string = 'ADGUARD_API_KEY', logger?: Logger): void {
88-
const apiKey = process.env[envVar];
89-
if (!apiKey) {
90-
throw new Error(`Environment variable ${envVar} is not set`);
92+
configureFromEnv(envVar?: string, logger?: Logger): void {
93+
let apiKey: string | undefined;
94+
95+
if (envVar) {
96+
// Use explicitly provided environment variable name
97+
apiKey = process.env[envVar];
98+
if (!apiKey) {
99+
throw new Error(`Environment variable ${envVar} is not set`);
100+
}
101+
} else {
102+
// Try .NET-compatible format first, then fallback to legacy format
103+
apiKey = process.env['ADGUARD_AdGuard__ApiKey'] ?? process.env['ADGUARD_API_KEY'];
104+
if (!apiKey) {
105+
throw new Error(
106+
'API key not configured. Set ADGUARD_AdGuard__ApiKey (recommended) or ADGUARD_API_KEY environment variable.'
107+
);
108+
}
91109
}
110+
92111
this.configure(apiKey, logger);
93112
}
94113

@@ -239,14 +258,33 @@ export class AdGuardDnsClient {
239258

240259
/**
241260
* Create client from environment variable
242-
* @param envVar - Environment variable name (default: ADGUARD_API_KEY)
261+
*
262+
* Supports multiple environment variable naming conventions:
263+
* - .NET-compatible format: ADGUARD_AdGuard__ApiKey (recommended, tried first)
264+
* - Legacy/alternative format: ADGUARD_API_KEY (fallback)
265+
*
266+
* @param envVar - Custom environment variable name (overrides default lookup)
243267
* @param logger - Optional logger
244268
*/
245-
static fromEnv(envVar: string = 'ADGUARD_API_KEY', logger?: Logger): AdGuardDnsClient {
246-
const apiKey = process.env[envVar];
247-
if (!apiKey) {
248-
throw new Error(`Environment variable ${envVar} is not set`);
269+
static fromEnv(envVar?: string, logger?: Logger): AdGuardDnsClient {
270+
let apiKey: string | undefined;
271+
272+
if (envVar) {
273+
// Use explicitly provided environment variable name
274+
apiKey = process.env[envVar];
275+
if (!apiKey) {
276+
throw new Error(`Environment variable ${envVar} is not set`);
277+
}
278+
} else {
279+
// Try .NET-compatible format first, then fallback to legacy format
280+
apiKey = process.env['ADGUARD_AdGuard__ApiKey'] ?? process.env['ADGUARD_API_KEY'];
281+
if (!apiKey) {
282+
throw new Error(
283+
'API key not configured. Set ADGUARD_AdGuard__ApiKey (recommended) or ADGUARD_API_KEY environment variable.'
284+
);
285+
}
249286
}
287+
250288
return AdGuardDnsClient.withApiKey(apiKey, logger);
251289
}
252290

src/adguard-api-typescript/tests/client.test.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,57 @@ describe('AdGuardDnsClient', () => {
2626
});
2727

2828
describe('fromEnv', () => {
29-
it('should create client from environment variable', () => {
29+
it('should create client from custom environment variable', () => {
3030
process.env.TEST_API_KEY = 'test-key';
3131
const client = AdGuardDnsClient.fromEnv('TEST_API_KEY');
3232
expect(client).toBeInstanceOf(AdGuardDnsClient);
3333
delete process.env.TEST_API_KEY;
3434
});
3535

36+
it('should prefer ADGUARD_AdGuard__ApiKey when no envVar specified', () => {
37+
const originalDotNet = process.env.ADGUARD_AdGuard__ApiKey;
38+
const originalLegacy = process.env.ADGUARD_API_KEY;
39+
40+
process.env.ADGUARD_AdGuard__ApiKey = 'dotnet-format-key';
41+
process.env.ADGUARD_API_KEY = 'legacy-format-key';
42+
43+
const client = AdGuardDnsClient.fromEnv();
44+
expect(client).toBeInstanceOf(AdGuardDnsClient);
45+
46+
// Restore original values
47+
if (originalDotNet !== undefined) {
48+
process.env.ADGUARD_AdGuard__ApiKey = originalDotNet;
49+
} else {
50+
delete process.env.ADGUARD_AdGuard__ApiKey;
51+
}
52+
if (originalLegacy !== undefined) {
53+
process.env.ADGUARD_API_KEY = originalLegacy;
54+
} else {
55+
delete process.env.ADGUARD_API_KEY;
56+
}
57+
});
58+
59+
it('should fallback to ADGUARD_API_KEY when ADGUARD_AdGuard__ApiKey not set', () => {
60+
const originalDotNet = process.env.ADGUARD_AdGuard__ApiKey;
61+
const originalLegacy = process.env.ADGUARD_API_KEY;
62+
63+
delete process.env.ADGUARD_AdGuard__ApiKey;
64+
process.env.ADGUARD_API_KEY = 'legacy-format-key';
65+
66+
const client = AdGuardDnsClient.fromEnv();
67+
expect(client).toBeInstanceOf(AdGuardDnsClient);
68+
69+
// Restore original values
70+
if (originalDotNet !== undefined) {
71+
process.env.ADGUARD_AdGuard__ApiKey = originalDotNet;
72+
}
73+
if (originalLegacy !== undefined) {
74+
process.env.ADGUARD_API_KEY = originalLegacy;
75+
} else {
76+
delete process.env.ADGUARD_API_KEY;
77+
}
78+
});
79+
3680
it('should throw when env var not set', () => {
3781
expect(() => AdGuardDnsClient.fromEnv('NONEXISTENT_VAR')).toThrow();
3882
});

src/adguard-api-typescript/tests/helpers/retry.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22
* Retry policy helper tests
33
*/
44

5-
/// <reference types="jest" />
6-
5+
import { jest } from '@jest/globals';
76
import { AxiosError } from 'axios';
87
import {
98
executeWithRetry,

src/adguard-api-typescript/tests/rules-compiler-integration.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Rules compiler integration tests
33
*/
44

5+
import { jest } from '@jest/globals';
56
import { RulesCompilerIntegration } from '../src/rules-compiler-integration';
67
import { UserRulesRepository } from '../src/repositories/user-rules';
78
import { DnsServerRepository } from '../src/repositories/dns-server';
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
/**
22
* Jest test setup file
3+
*
4+
* IMPORTANT: This file sets up test-only mock values.
5+
* These are NOT real credentials and are only used for unit testing.
36
*/
47

5-
/// <reference types="jest" />
8+
import { jest } from '@jest/globals';
69

710
// Increase timeout for integration tests
811
jest.setTimeout(30000);
912

10-
// Mock environment variables for tests
11-
process.env.ADGUARD_API_KEY = process.env.ADGUARD_API_KEY || 'test-api-key';
13+
// Set test-only mock API key for unit tests (NOT a real credential)
14+
// Real integration tests should use actual credentials from environment
15+
const TEST_MOCK_API_KEY = 'test-mock-api-key-for-unit-tests';
16+
process.env.ADGUARD_API_KEY = process.env.ADGUARD_API_KEY || TEST_MOCK_API_KEY;
17+
process.env.ADGUARD_AdGuard__ApiKey = process.env.ADGUARD_AdGuard__ApiKey || TEST_MOCK_API_KEY;

0 commit comments

Comments
 (0)