Public/Test-OrgDefaultPermissions.ps1

function Test-OrgDefaultPermissions {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'Public check name follows project check contract.')]
    [CmdletBinding()]
    [OutputType([PSCustomObject[]])]
    param(
        [Parameter(Mandatory)]
        [ValidatePattern('^[a-zA-Z0-9._-]+$')]
        [string]$Owner,

        [Parameter(Mandatory)]
        [string]$Token
    )

    $resource = "org/$Owner"
    $results = [System.Collections.Generic.List[PSCustomObject]]::new()

    $ownerContext = Get-FylgyrOwnerContext -Owner $Owner -Token $Token
    if ($ownerContext.Type -eq 'User') {
        $results.Add((Format-FylgyrResult `
            -CheckName 'OrgDefaultPermissions' `
            -Status 'Info' `
            -Severity 'Info' `
            -Resource $resource `
            -Detail "Owner '$Owner' is a personal account. Organization default repository permission does not apply." `
            -Remediation 'No action needed. Run this check against an organization owner.' `
            -Target $resource))
        return $results.ToArray()
    }

    try {
        $orgInfo = Invoke-GitHubApi -Endpoint "orgs/$Owner" -Token $Token
    }
    catch {
        $msg = $_.Exception.Message
        if ($msg -match '403') {
            $results.Add((Format-FylgyrResult `
                -CheckName 'OrgDefaultPermissions' `
                -Status 'Error' `
                -Severity 'High' `
                -Resource $resource `
                -Detail 'Insufficient permissions to read organization default repository permission.' `
                -Remediation 'Use a fine-grained token with organization Administration:read, or a classic token with read:org/admin:org scope.' `
                -Target $resource))
            return $results.ToArray()
        }

        $results.Add((Format-FylgyrResult `
            -CheckName 'OrgDefaultPermissions' `
            -Status 'Error' `
            -Severity 'High' `
            -Resource $resource `
            -Detail "Failed to read organization default repository permission: $($_.Exception.Message)" `
            -Remediation 'Verify the owner and token, then rerun.' `
            -Target $resource))
        return $results.ToArray()
    }

    $defaultPerm = 'unknown'
    if ($orgInfo -and $orgInfo.PSObject.Properties['default_repository_permission'] -and $orgInfo.default_repository_permission) {
        $defaultPerm = [string]$orgInfo.default_repository_permission
    }

    if ($defaultPerm -in @('read', 'none')) {
        $results.Add((Format-FylgyrResult `
            -CheckName 'OrgDefaultPermissions' `
            -Status 'Pass' `
            -Severity 'Info' `
            -Resource $resource `
            -Detail "Organization default repository permission is '$defaultPerm'." `
            -Remediation 'No action needed.' `
            -Target $resource))
    }
    else {
        $results.Add((Format-FylgyrResult `
            -CheckName 'OrgDefaultPermissions' `
            -Status 'Fail' `
            -Severity 'High' `
            -Resource $resource `
            -Detail "Organization default repository permission is '$defaultPerm'. Broad default write/admin access increases lateral movement risk if a single account is compromised, as seen in Gentoo-style org compromise patterns." `
            -Remediation "Set the organization default repository permission to 'read' or 'none' and grant elevated access only per repository by exception." `
            -AttackMapping @('gentoo-github-compromise') `
            -Target $resource))
    }

    $results.ToArray()
}