Public/Test-CCSecurityConfig.ps1

function Test-CCSecurityConfig {
    <#
    .SYNOPSIS
        Validates GitHub security configuration (dependabot, secret scanning, alerts).

    .PARAMETER Repository
        GitHub repository in format "owner/repo". Auto-detected if not specified.

    .PARAMETER Token
        GitHub token. Falls back to GITHUB_TOKEN, GH_TOKEN, or gh CLI.

    .PARAMETER Standard
        Standard tier: core, active, minimal. Default: core.

    .PARAMETER Config
        Path to per-repo config file for overrides.

    .OUTPUTS
        [PSCustomObject[]] Array of check results.

    .EXAMPLE
        Test-CCSecurityConfig -Repository 'The-Code-Kitchen/LeadForge'
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject[]])]
    param(
        [Parameter()]
        [string]$Repository,

        [Parameter()]
        [string]$Token,

        [Parameter()]
        [ValidateSet('core', 'active', 'minimal')]
        [string]$Standard = 'core',

        [Parameter()]
        [string]$Config
    )

    $results = [System.Collections.ArrayList]::new()
    $token = Resolve-CCToken -Token $Token
    if (-not $token) { throw "No GitHub token available. Set GITHUB_TOKEN or run 'gh auth login'." }

    $repo = Resolve-CCRepository -Repository $Repository
    if (-not $repo) { throw "Cannot determine repository. Specify -Repository 'owner/repo'." }

    $stdConfig = (Get-CCStandardConfig -Standard $Standard -ConfigPath $Config).security

    # GH-SEC-001: Vulnerability alerts (Dependabot security updates)
    if ($stdConfig.vulnerability_alerts) {
        $alertsEnabled = $true
        try {
            Invoke-CCGitHubApi -Endpoint "repos/$repo/vulnerability-alerts" -Token $token
        }
        catch {
            if ($_.Exception.Response.StatusCode.value__ -eq 404) {
                $alertsEnabled = $false
            }
        }

        if ($alertsEnabled) {
            [void]$results.Add((New-CCCheckResult -CheckId 'GH-SEC-005' -Category 'Security' -Item 'Vulnerability Alerts' `
                        -Status 'Pass' -Severity 'Info' -Message 'Vulnerability alerts enabled'))
        }
        else {
            [void]$results.Add((New-CCCheckResult -CheckId 'GH-SEC-005' -Category 'Security' -Item 'Vulnerability Alerts' `
                        -Status 'Fail' -Severity 'Error' `
                        -Message 'Vulnerability alerts not enabled' `
                        -FixAvailable $true -FixId 'EnableVulnerabilityAlerts' -Current $false -Expected $true))
        }
    }

    # GH-SEC-001: Dependabot security updates
    if ($stdConfig.dependabot_security) {
        # Check if automated security fixes are enabled via the repo endpoint
        $repoInfo = Invoke-CCGitHubApi -Endpoint "repos/$repo" -Token $token
        # Note: This is inferred from the security_and_analysis field if available
        $secAnalysis = $repoInfo.security_and_analysis

        if ($secAnalysis -and $secAnalysis.dependabot_security_updates -and $secAnalysis.dependabot_security_updates.status -eq 'enabled') {
            [void]$results.Add((New-CCCheckResult -CheckId 'GH-SEC-001' -Category 'Security' -Item 'Dependabot Security' `
                        -Status 'Pass' -Severity 'Info' -Message 'Dependabot security updates enabled'))
        }
        else {
            [void]$results.Add((New-CCCheckResult -CheckId 'GH-SEC-001' -Category 'Security' -Item 'Dependabot Security' `
                        -Status 'Fail' -Severity 'Error' `
                        -Message 'Dependabot security updates not enabled' `
                        -FixAvailable $true -FixId 'EnableDependabotSecurity' -Current $false -Expected $true))
        }
    }

    # GH-SEC-002: Dependabot version updates (checks for dependabot.yml in repo)
    if ($stdConfig.dependabot_versions) {
        $dependabotFile = Invoke-CCGitHubApi -Endpoint "repos/$repo/contents/.github/dependabot.yml" -Token $token -AllowNotFound
        if (-not $dependabotFile) {
            $dependabotFile = Invoke-CCGitHubApi -Endpoint "repos/$repo/contents/.github/dependabot.yaml" -Token $token -AllowNotFound
        }

        if ($dependabotFile) {
            [void]$results.Add((New-CCCheckResult -CheckId 'GH-SEC-002' -Category 'Security' -Item 'Dependabot Versions' `
                        -Status 'Pass' -Severity 'Info' -Message 'Dependabot version updates configured (.github/dependabot.yml)'))
        }
        else {
            [void]$results.Add((New-CCCheckResult -CheckId 'GH-SEC-002' -Category 'Security' -Item 'Dependabot Versions' `
                        -Status 'Fail' -Severity 'Warning' `
                        -Message 'No .github/dependabot.yml — version updates not configured' `
                        -FixAvailable $true -FixId 'EnableDependabotVersions' -Current $false -Expected $true))
        }
    }

    # GH-SEC-003: Secret scanning
    if ($stdConfig.secret_scanning) {
        $secAnalysis = if (-not $secAnalysis) { (Invoke-CCGitHubApi -Endpoint "repos/$repo" -Token $token).security_and_analysis } else { $secAnalysis }

        if ($secAnalysis -and $secAnalysis.secret_scanning -and $secAnalysis.secret_scanning.status -eq 'enabled') {
            [void]$results.Add((New-CCCheckResult -CheckId 'GH-SEC-003' -Category 'Security' -Item 'Secret Scanning' `
                        -Status 'Pass' -Severity 'Info' -Message 'Secret scanning enabled'))
        }
        else {
            [void]$results.Add((New-CCCheckResult -CheckId 'GH-SEC-003' -Category 'Security' -Item 'Secret Scanning' `
                        -Status 'Fail' -Severity 'Error' `
                        -Message 'Secret scanning not enabled' `
                        -FixAvailable $true -FixId 'EnableSecretScanning' -Current $false -Expected $true))
        }
    }

    # GH-SEC-004: Push protection
    if ($stdConfig.push_protection) {
        $secAnalysis = if (-not $secAnalysis) { (Invoke-CCGitHubApi -Endpoint "repos/$repo" -Token $token).security_and_analysis } else { $secAnalysis }

        if ($secAnalysis -and $secAnalysis.secret_scanning_push_protection -and $secAnalysis.secret_scanning_push_protection.status -eq 'enabled') {
            [void]$results.Add((New-CCCheckResult -CheckId 'GH-SEC-004' -Category 'Security' -Item 'Push Protection' `
                        -Status 'Pass' -Severity 'Info' -Message 'Secret scanning push protection enabled'))
        }
        else {
            [void]$results.Add((New-CCCheckResult -CheckId 'GH-SEC-004' -Category 'Security' -Item 'Push Protection' `
                        -Status 'Fail' -Severity 'Warning' `
                        -Message 'Secret scanning push protection not enabled' `
                        -FixAvailable $true -FixId 'EnablePushProtection' -Current $false -Expected $true))
        }
    }

    return $results.ToArray()
}