Initial envsitter CLI and safe env matching
This commit is contained in:
40
src/test/dotenv-parse.test.ts
Normal file
40
src/test/dotenv-parse.test.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { parseDotenv } from '../dotenv/parse.js';
|
||||
|
||||
test('parseDotenv parses basic assignments and ignores comments', () => {
|
||||
const input = [
|
||||
'# comment',
|
||||
'FOO=bar',
|
||||
'export BAZ=qux',
|
||||
'EMPTY=',
|
||||
'TRAILING=ok # inline',
|
||||
''
|
||||
].join('\n');
|
||||
|
||||
const parsed = parseDotenv(input);
|
||||
assert.equal(parsed.errors.length, 0);
|
||||
assert.equal(parsed.values.get('FOO'), 'bar');
|
||||
assert.equal(parsed.values.get('BAZ'), 'qux');
|
||||
assert.equal(parsed.values.get('EMPTY'), '');
|
||||
assert.equal(parsed.values.get('TRAILING'), 'ok');
|
||||
});
|
||||
|
||||
test('parseDotenv supports quoted values', () => {
|
||||
const input = [
|
||||
"SINGLE='a b c'",
|
||||
'DOUBLE="a\\n\\t\\r\\\\b"'
|
||||
].join('\n');
|
||||
|
||||
const parsed = parseDotenv(input);
|
||||
assert.equal(parsed.errors.length, 0);
|
||||
assert.equal(parsed.values.get('SINGLE'), 'a b c');
|
||||
assert.equal(parsed.values.get('DOUBLE'), 'a\n\t\r\\b');
|
||||
});
|
||||
|
||||
test('parseDotenv reports invalid keys', () => {
|
||||
const input = 'NOT-OK=value\nOK=value2';
|
||||
const parsed = parseDotenv(input);
|
||||
assert.ok(parsed.errors.length >= 1);
|
||||
assert.equal(parsed.values.get('OK'), 'value2');
|
||||
});
|
||||
65
src/test/envsitter.test.ts
Normal file
65
src/test/envsitter.test.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtemp, writeFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { EnvSitter } from '../envsitter.js';
|
||||
|
||||
async function makeTempDotenv(contents: string): Promise<string> {
|
||||
const dir = await mkdtemp(join(tmpdir(), 'envsitter-'));
|
||||
const filePath = join(dir, '.env');
|
||||
await writeFile(filePath, contents, 'utf8');
|
||||
return filePath;
|
||||
}
|
||||
|
||||
test('EnvSitter lists keys and fingerprints without returning values', async () => {
|
||||
const filePath = await makeTempDotenv('A=1\nB=two\n');
|
||||
const es = EnvSitter.fromDotenvFile(filePath);
|
||||
|
||||
const keys = await es.listKeys();
|
||||
assert.deepEqual(keys, ['A', 'B']);
|
||||
|
||||
const fp = await es.fingerprintKey('B');
|
||||
assert.equal(fp.key, 'B');
|
||||
assert.equal(fp.algorithm, 'hmac-sha256');
|
||||
assert.equal(fp.length, 3);
|
||||
assert.ok(fp.fingerprint.length > 10);
|
||||
});
|
||||
|
||||
test('EnvSitter matches a candidate for a key (outside-in)', async () => {
|
||||
const filePath = await makeTempDotenv('OPENAI_API_KEY=sk-test-123\n');
|
||||
const es = EnvSitter.fromDotenvFile(filePath);
|
||||
|
||||
assert.equal(await es.matchCandidate('OPENAI_API_KEY', 'sk-test-123'), true);
|
||||
assert.equal(await es.matchCandidate('OPENAI_API_KEY', 'nope'), false);
|
||||
assert.equal(await es.matchCandidate('MISSING', 'sk-test-123'), false);
|
||||
});
|
||||
|
||||
test('EnvSitter bulk matching works across keys and by-key candidates', async () => {
|
||||
const filePath = await makeTempDotenv('K1=V1\nK2=V2\n');
|
||||
const es = EnvSitter.fromDotenvFile(filePath);
|
||||
|
||||
const bulk = await es.matchCandidateBulk(['K1', 'K2'], 'V2');
|
||||
assert.deepEqual(bulk, [
|
||||
{ key: 'K1', match: false },
|
||||
{ key: 'K2', match: true }
|
||||
]);
|
||||
|
||||
const byKey = await es.matchCandidatesByKey({ K1: 'V1', K2: 'nope' });
|
||||
assert.deepEqual(byKey, [
|
||||
{ key: 'K1', match: true },
|
||||
{ key: 'K2', match: false }
|
||||
]);
|
||||
});
|
||||
|
||||
test('EnvSitter scan detects JWT-like and URL values without exposing them', async () => {
|
||||
const jwt = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMifQ.sgn';
|
||||
const filePath = await makeTempDotenv(`JWT=${jwt}\nURL=https://example.com\nNOISE=hello\n`);
|
||||
const es = EnvSitter.fromDotenvFile(filePath);
|
||||
|
||||
const findings = await es.scan({ detect: ['jwt', 'url', 'base64'] });
|
||||
assert.deepEqual(findings, [
|
||||
{ key: 'JWT', detections: ['jwt'] },
|
||||
{ key: 'URL', detections: ['url'] }
|
||||
]);
|
||||
});
|
||||
33
src/test/pepper.test.ts
Normal file
33
src/test/pepper.test.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtemp, readFile } from 'node:fs/promises';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { resolvePepper } from '../pepper.js';
|
||||
|
||||
test('resolvePepper reads from env when set', async () => {
|
||||
const prev = process.env.ENVSITTER_PEPPER;
|
||||
process.env.ENVSITTER_PEPPER = 'unit-test-pepper';
|
||||
|
||||
try {
|
||||
const pepper = await resolvePepper({ createIfMissing: false });
|
||||
assert.equal(pepper.source, 'env');
|
||||
assert.equal(new TextDecoder().decode(pepper.pepperBytes), 'unit-test-pepper');
|
||||
} finally {
|
||||
if (prev === undefined) delete process.env.ENVSITTER_PEPPER;
|
||||
else process.env.ENVSITTER_PEPPER = prev;
|
||||
}
|
||||
});
|
||||
|
||||
test('resolvePepper creates a pepper file when missing', async () => {
|
||||
const dir = await mkdtemp(join(tmpdir(), 'envsitter-'));
|
||||
const pepperPath = join(dir, 'pepper');
|
||||
|
||||
const pepper = await resolvePepper({ pepperFilePath: pepperPath });
|
||||
assert.equal(pepper.source, 'file');
|
||||
assert.equal(pepper.pepperFilePath, pepperPath);
|
||||
assert.ok(pepper.pepperBytes.length >= 16);
|
||||
|
||||
const persisted = (await readFile(pepperPath, 'utf8')).trim();
|
||||
assert.ok(persisted.length > 0);
|
||||
});
|
||||
Reference in New Issue
Block a user