Files
sencho/backend/src/__tests__/unset-env-classification.test.ts
T
Anso 122c1b8073 fix: stop Doctor exposing hash fragments as unset variables (#1558)
Classify Compose stderr unset-variable warnings into intentional references vs literal-dollar fragments from secret values. Adds env-literal-dollar preflight rule and safe remediation text. Fixes #1550.
2026-07-05 03:50:40 -04:00

86 lines
3.3 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import { classifyUnsetEnvVars } from '../helpers/unsetEnvClassification';
import type { StackEnvSources } from '../helpers/envFileResolution';
function sources(over: Partial<StackEnvSources> = {}): StackEnvSources {
return {
stackDir: '/stack',
baseDir: '/compose',
composeFiles: [],
envFiles: [],
inlineEnvKeysByService: {},
interpolationRefs: [],
authoredComposeText: '',
...over,
};
}
describe('classifyUnsetEnvVars', () => {
it('keeps intentional ${VAR} refs as unset variables', () => {
const src = sources({
authoredComposeText: 'services:\n web:\n image: nginx\n environment:\n - DB_HOST=${DB_HOST}\n',
interpolationRefs: [{ name: 'DB_HOST', required: false, hasDefault: false, alternate: false }],
});
const result = classifyUnsetEnvVars(['DB_HOST', 'E6SDEbshpc'], src);
expect(result.intentional).toEqual(['DB_HOST']);
expect(result.literalDollar).toHaveLength(1);
expect(result.literalDollar[0].likelySecret).toBe(false);
});
it('classifies bcrypt hash fragments as literal-dollar warnings without exposing fragments', () => {
const compose = [
'services:',
' demo:',
' image: alpine:3',
' environment:',
' - EXAMPLE_AUTH_HASH=$2b$10$E6SDEbshpc$vCSrREDACTED',
].join('\n');
const src = sources({
authoredComposeText: compose,
inlineEnvKeysByService: { demo: ['EXAMPLE_AUTH_HASH'] },
});
const result = classifyUnsetEnvVars(['E6SDEbshpc', 'vCSr'], src);
expect(result.intentional).toEqual([]);
expect(result.literalDollar).toHaveLength(1);
expect(result.literalDollar[0]).toMatchObject({
envKey: 'EXAMPLE_AUTH_HASH',
likelySecret: true,
service: 'demo',
});
expect(JSON.stringify(result)).not.toContain('E6SDEbshpc');
expect(JSON.stringify(result)).not.toContain('vCSr');
});
it('classifies map-form bcrypt hash fragments as literal-dollar warnings', () => {
const compose = [
'services:',
' demo:',
' image: alpine:3',
' environment:',
' EXAMPLE_AUTH_HASH: $2b$10$E6SDEbshpc$vCSrREDACTED',
].join('\n');
const src = sources({
authoredComposeText: compose,
inlineEnvKeysByService: { demo: ['EXAMPLE_AUTH_HASH'] },
});
const result = classifyUnsetEnvVars(['E6SDEbshpc', 'vCSr'], src);
expect(result.intentional).toEqual([]);
expect(result.literalDollar[0]?.envKey).toBe('EXAMPLE_AUTH_HASH');
});
it('treats bare $VAR references as intentional', () => {
const compose = 'services:\n web:\n image: nginx\n environment:\n - TOKEN=$TOKEN\n';
const src = sources({ authoredComposeText: compose });
const result = classifyUnsetEnvVars(['TOKEN'], src);
expect(result.intentional).toEqual(['TOKEN']);
expect(result.literalDollar).toEqual([]);
});
it('attributes env-file-only spurious fragments to a lone likely-secret key', () => {
const src = sources({ authoredComposeText: 'services:\n web:\n image: nginx\n env_file:\n - ./secrets.env\n' });
const result = classifyUnsetEnvVars(['E6SDEbshpc'], src, ['EXAMPLE_AUTH_HASH']);
expect(result.intentional).toEqual([]);
expect(result.literalDollar).toEqual([{ envKey: 'EXAMPLE_AUTH_HASH', likelySecret: true }]);
});
});