# secret-leak-scanner.ps1 -- PreToolUse hook
# Scans file write content for API keys, tokens, passwords, .env patterns.
# Prevents accidental secret commits.
# Exit code 2 = block. Exit code 0 = allow.
#
# Part of @connexum/ai-governance

$ErrorActionPreference = 'Stop'

$rawInput = [Console]::In.ReadToEnd()

if ([string]::IsNullOrWhiteSpace($rawInput)) { exit 0 }

try { $input_obj = $rawInput | ConvertFrom-Json } catch { exit 0 }

$toolName = if ($input_obj.tool_name) { $input_obj.tool_name } else { '' }
$toolInput = $input_obj.tool_input

# Only gate write tools and git commands
switch ($toolName) {
    { $_ -in 'Write', 'Edit', 'NotebookEdit' } { break }
    'Bash' {
        $command = if ($toolInput.command) { $toolInput.command } else { '' }
        if (-not $command) { exit 0 }
        # Block git add of .env files
        if ($command -match 'git\s+add\s+.*\.env\b') {
            [Console]::Error.WriteLine("BLOCKED: Attempted to stage .env file for commit. Secrets must not be committed.")
            exit 2
        }
        # Block git add of common credential files
        if ($command -match 'git\s+add\s+.*(credentials\.json|service-account\.json|\.pem$|\.key$|id_rsa|id_ed25519)') {
            [Console]::Error.WriteLine("BLOCKED: Attempted to stage sensitive credential file for commit.")
            exit 2
        }
        exit 0
    }
    default { exit 0 }
}

# Get content being written
$content = if ($toolInput.content) { $toolInput.content }
            elseif ($toolInput.new_string) { $toolInput.new_string }
            else { '' }

$filePath = if ($toolInput.file_path) { $toolInput.file_path }
             elseif ($toolInput.path) { $toolInput.path }
             else { '' }

if (-not $content) { exit 0 }

$fileName = if ($filePath) { [System.IO.Path]::GetFileName($filePath) } else { '' }

# Secret patterns (high confidence only)
$secretPatterns = @(
    @{ Pattern = 'sk-[a-zA-Z0-9]{20,}'; Description = 'Anthropic/OpenAI API key' }
    @{ Pattern = 'AKIA[0-9A-Z]{16}'; Description = 'AWS Access Key ID' }
    @{ Pattern = 'ghp_[a-zA-Z0-9]{36}'; Description = 'GitHub Personal Access Token' }
    @{ Pattern = 'gho_[a-zA-Z0-9]{36}'; Description = 'GitHub OAuth Token' }
    @{ Pattern = 'glpat-[a-zA-Z0-9_-]{20,}'; Description = 'GitLab Personal Access Token' }
    @{ Pattern = 'xoxb-[0-9]{10,}-[a-zA-Z0-9]{20,}'; Description = 'Slack Bot Token' }
    @{ Pattern = 'xoxp-[0-9]{10,}-[a-zA-Z0-9]{20,}'; Description = 'Slack User Token' }
    @{ Pattern = 'SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}'; Description = 'SendGrid API Key' }
    @{ Pattern = 'sq0atp-[a-zA-Z0-9_-]{22}'; Description = 'Square Access Token' }
    @{ Pattern = 'sk_live_[a-zA-Z0-9]{24,}'; Description = 'Stripe Live Secret Key' }
    @{ Pattern = 'rk_live_[a-zA-Z0-9]{24,}'; Description = 'Stripe Live Restricted Key' }
    @{ Pattern = 'pk_live_[a-zA-Z0-9]{24,}'; Description = 'Stripe Live Publishable Key' }
    @{ Pattern = '-----BEGIN (RSA |EC |DSA )?PRIVATE KEY-----'; Description = 'Private Key' }
    @{ Pattern = 'DATABASE_URL\s*=\s*postgres(ql)?://[^"''\s]+@[^"''\s]+'; Description = 'Database connection string with credentials' }
    @{ Pattern = 'RESEND_API_KEY\s*=\s*re_[a-zA-Z0-9]{20,}'; Description = 'Resend API Key' }
)

# PHI/PII patterns (HIPAA, GDPR, PCI DSS)
$phiPiiPatterns = @(
    @{ Pattern = '[0-9]{3}-[0-9]{2}-[0-9]{4}'; Description = 'SSN (Social Security Number)' }
    @{ Pattern = '\b[0-9]{4}[\s-]?[0-9]{4}[\s-]?[0-9]{4}[\s-]?[0-9]{4}\b'; Description = 'Possible credit card number (PCI DSS)' }
    @{ Pattern = 'MRN[:\s]*[0-9]{6,}'; Description = 'Medical Record Number (HIPAA PHI)' }
    @{ Pattern = 'DOB[:\s]*[0-9]{1,2}[/-][0-9]{1,2}[/-][0-9]{2,4}'; Description = 'Date of Birth (PHI/PII)' }
    @{ Pattern = 'patient[_\s]?(name|id|ssn|dob)[:\s]*[A-Za-z0-9]'; Description = 'Patient identifier (HIPAA PHI)' }
    @{ Pattern = 'health[_\s]?plan[_\s]?(id|number)[:\s]*[A-Za-z0-9]'; Description = 'Health Plan ID (HIPAA PHI)' }
)

foreach ($entry in $secretPatterns) {
    if ($content -match $entry.Pattern) {
        [Console]::Error.WriteLine("BLOCKED: Secret detected in file write -- $($entry.Description)")
        [Console]::Error.WriteLine("File: $filePath")
        [Console]::Error.WriteLine("Remove the secret and use environment variables instead.")
        exit 2
    }
}

# Scan for PHI/PII in non-test, non-hook files
$skipPhi = $fileName -match '\.(test|spec)\.' -or $fileName -match '\.(sh|ps1|md)$' -or
           $fileName -eq 'data-classifier.ts' -or $fileName -eq 'encryption-layer.ts'

if (-not $skipPhi) {
    foreach ($entry in $phiPiiPatterns) {
        if ($content -match $entry.Pattern) {
            [Console]::Error.WriteLine("WARNING: Possible sensitive data detected -- $($entry.Description)")
            [Console]::Error.WriteLine("File: $filePath")
            [Console]::Error.WriteLine("Ensure this data is redacted or encrypted before storage.")
        }
    }
}

exit 0
