#!/usr/bin/env node
// Hook: pre-write-pathguard.mjs
// Event: PreToolUse (Write)
// Purpose: Block writes to sensitive paths (.env, .ssh/, .aws/, credentials, etc.)
//
// Protocol:
//   - Read JSON from stdin: { tool_name, tool_input }
//   - tool_input.file_path — destination path
//   - Block: stderr + exit 2
//   - Allow: exit 0

import { readFileSync } from 'node:fs';
import { basename, normalize, resolve } from 'node:path';
import { getPolicyValue } from '../../scanners/lib/policy-loader.mjs';

// ---------------------------------------------------------------------------
// Sensitive path patterns — 8 categories
// ---------------------------------------------------------------------------

/** Category 1: Environment files
 *  Matches `.env` and any multi-segment suffix after it, e.g.
 *  `.env.local`, `.env.production.local.backup`, `.env.stage-1.local`,
 *  `.env.CI.secret`. Does NOT match `.envrc` (direnv) — no dot separator.
 *  v7.1.0 B1 fix: previous regex `/[\\/]\.env\.[a-z]+$/` only matched a
 *  single lowercase segment after `.env`.
 */
const ENV_PATTERNS = [
  /[\\/]\.env(\.[A-Za-z0-9._-]+)*$/,
];

/** Category 2: SSH directory */
const SSH_PATTERNS = [
  /[\\/]\.ssh[\\/]/,
];

/** Category 3: AWS credentials */
const AWS_PATTERNS = [
  /[\\/]\.aws[\\/]/,
];

/** Category 4: GPG directory */
const GPG_PATTERNS = [
  /[\\/]\.gnupg[\\/]/,
];

/** Category 5: Credential files */
const CREDENTIAL_FILES = [
  '.npmrc',
  '.pypirc',
  '.netrc',
  '.docker/config.json',
  'credentials.json',
  'service-account.json',
  'keyfile.json',
];

/** Category 6: Hook scripts (prevent hook tampering) */
const HOOK_PATTERNS = [
  /[\\/]\.claude[\\/].*hooks.*\.json$/,
  /[\\/]hooks[\\/]scripts[\\/].*\.mjs$/,
];

/** Category 7: System directories */
const SYSTEM_PATTERNS = [
  /^\/etc[\\/]/,
  /^\/usr[\\/]/,
  /^\/var[\\/]/,
];

/** Category 8: Settings files */
const SETTINGS_FILES = [
  'settings.json',
  'settings.local.json',
];

/** Category 9: Policy-defined additional protected paths */
const POLICY_PATTERNS = getPolicyValue('pathguard', 'additional_protected', []).map(p => new RegExp(p));

// ---------------------------------------------------------------------------
// Path classification
// ---------------------------------------------------------------------------

/**
 * Check if a file path targets a sensitive location.
 * @param {string} filePath - The path to check
 * @returns {{ blocked: boolean, category: string, reason: string }}
 */
function classifyPath(filePath) {
  if (!filePath) return { blocked: false, category: '', reason: '' };

  const norm = normalize(resolve(filePath));
  const base = basename(norm);

  // Category 1: Environment files
  for (const pat of ENV_PATTERNS) {
    if (pat.test(norm)) {
      return { blocked: true, category: 'env', reason: `Environment file: ${base}` };
    }
  }

  // Category 2: SSH
  for (const pat of SSH_PATTERNS) {
    if (pat.test(norm)) {
      return { blocked: true, category: 'ssh', reason: `SSH directory: ${norm}` };
    }
  }

  // Category 3: AWS
  for (const pat of AWS_PATTERNS) {
    if (pat.test(norm)) {
      return { blocked: true, category: 'aws', reason: `AWS credentials directory: ${norm}` };
    }
  }

  // Category 4: GPG
  for (const pat of GPG_PATTERNS) {
    if (pat.test(norm)) {
      return { blocked: true, category: 'gnupg', reason: `GPG directory: ${norm}` };
    }
  }

  // Category 5: Credential files
  for (const name of CREDENTIAL_FILES) {
    if (norm.endsWith(name) || base === name) {
      return { blocked: true, category: 'credentials', reason: `Credential file: ${base}` };
    }
  }

  // Category 6: Hook scripts
  for (const pat of HOOK_PATTERNS) {
    if (pat.test(norm)) {
      return { blocked: true, category: 'hooks', reason: `Hook configuration: ${base}` };
    }
  }

  // Category 7: System directories
  for (const pat of SYSTEM_PATTERNS) {
    if (pat.test(norm)) {
      return { blocked: true, category: 'system', reason: `System directory: ${norm}` };
    }
  }

  // Category 8: Settings files
  for (const name of SETTINGS_FILES) {
    if (base === name) {
      // Only block settings.json in .claude/ directories
      if (/[\\/]\.claude[\\/]/.test(norm) || /[\\/]\.vscode[\\/]/.test(norm)) {
        return { blocked: true, category: 'settings', reason: `Settings file: ${norm}` };
      }
    }
  }

  // Category 9: Policy-defined additional protected paths
  for (const pat of POLICY_PATTERNS) {
    if (pat.test(norm)) {
      return { blocked: true, category: 'policy', reason: `Policy-protected path: ${norm}` };
    }
  }

  return { blocked: false, category: '', reason: '' };
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

let input;
try {
  const raw = readFileSync(0, 'utf-8');
  input = JSON.parse(raw);
} catch {
  process.exit(0);
}

const toolInput = input?.tool_input ?? {};
const filePath = toolInput.file_path ?? '';

if (!filePath) {
  process.exit(0);
}

const result = classifyPath(filePath);

if (result.blocked) {
  process.stderr.write(
    `\n[llm-security] PATH GUARD: Write blocked\n` +
    `  Category: ${result.category}\n` +
    `  Reason: ${result.reason}\n` +
    `  Path: ${filePath}\n\n` +
    `This path is protected. If this write is intentional, ` +
    `ask the user to perform it manually.\n`
  );
  process.exit(2);
}

process.exit(0);
