/**
 * Regression test for audit_1776853149979: terminal_create previously merged
 * caller-supplied env straight into execSync's environment for every command
 * in the session. Setting LD_PRELOAD or NODE_OPTIONS via that path was
 * functionally RCE on the host. validateEnv enforces a denylist + POSIX
 * shape — these tests pin the contract so the denylist can't silently
 * regress.
 */

import { describe, it, expect } from 'vitest';
import { validateEnv } from '../src/mcp-tools/validate-input.js';

describe('validateEnv (audit_1776853149979)', () => {
  describe('happy path', () => {
    it('accepts undefined / null as empty env', () => {
      expect(validateEnv(undefined)).toEqual({ valid: true, sanitized: {} });
      expect(validateEnv(null)).toEqual({ valid: true, sanitized: {} });
    });

    it('accepts an empty object', () => {
      expect(validateEnv({})).toEqual({ valid: true, sanitized: {} });
    });

    it('passes through a valid env unchanged', () => {
      const r = validateEnv({ FOO: 'bar', MY_VAR: '1', _UNDERSCORE: 'ok' });
      expect(r.valid).toBe(true);
      expect(r.sanitized).toEqual({ FOO: 'bar', MY_VAR: '1', _UNDERSCORE: 'ok' });
    });
  });

  describe('loader-hijack denylist', () => {
    // Names that let an attacker hijack a child process before any user code
    // runs. Each MUST be rejected — adding an exemption is a real CVE.
    const denylisted = [
      'LD_PRELOAD',
      'LD_LIBRARY_PATH',
      'LD_AUDIT',
      'DYLD_INSERT_LIBRARIES',
      'DYLD_LIBRARY_PATH',
      'DYLD_FALLBACK_LIBRARY_PATH',
      'DYLD_FORCE_FLAT_NAMESPACE',
      'NODE_OPTIONS',
      'NODE_PATH',
    ];

    for (const name of denylisted) {
      it(`rejects ${name}`, () => {
        const r = validateEnv({ [name]: '/tmp/whatever' });
        expect(r.valid).toBe(false);
        expect(r.error).toContain(name);
        expect(r.error).toContain('denylisted');
        expect(r.sanitized).toEqual({});
      });
    }
  });

  describe('shape validation', () => {
    it('rejects a name containing =', () => {
      const r = validateEnv({ 'A=B': 'x' });
      expect(r.valid).toBe(false);
      expect(r.error).toContain('not a valid POSIX env name');
    });

    it('rejects a digit-first name', () => {
      const r = validateEnv({ '1FOO': 'x' });
      expect(r.valid).toBe(false);
      expect(r.error).toContain('not a valid POSIX env name');
    });

    it('rejects a name with whitespace', () => {
      const r = validateEnv({ 'BAD NAME': 'x' });
      expect(r.valid).toBe(false);
      expect(r.error).toContain('not a valid POSIX env name');
    });

    it('rejects an array (not an object)', () => {
      const r = validateEnv(['FOO', 'BAR'] as unknown);
      expect(r.valid).toBe(false);
      expect(r.error).toContain('object of string→string');
    });

    it('rejects a non-string value', () => {
      const r = validateEnv({ FOO: 42 });
      expect(r.valid).toBe(false);
      expect(r.error).toContain('FOO');
      expect(r.error).toContain('must be a string');
    });

    it('rejects a value with a null byte', () => {
      const value = ['a', String.fromCharCode(0), 'b'].join('');
      const r = validateEnv({ FOO: value });
      expect(r.valid).toBe(false);
      expect(r.error).toContain('null byte');
    });

    it('rejects a value over 32K', () => {
      const r = validateEnv({ FOO: 'x'.repeat(40_000) });
      expect(r.valid).toBe(false);
      expect(r.error).toContain('exceeds 32768');
    });
  });
});
