Skip to content

feat(scan): bounded-parallel subdirectory enumeration for SMB/network #50

feat(scan): bounded-parallel subdirectory enumeration for SMB/network

feat(scan): bounded-parallel subdirectory enumeration for SMB/network #50

Workflow file for this run

name: CI
on:
push:
branches:
- main
pull_request:
permissions:
contents: read
jobs:
build-and-smoke:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup .NET SDK
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Set SOURCE_DATE_EPOCH from git commit
shell: pwsh
run: |
$epoch = git log -1 --format=%ct
echo "SOURCE_DATE_EPOCH=$epoch" >> $env:GITHUB_ENV
- name: Build Release
run: dotnet build "RED/RED+.csproj" -c Release
- name: Engine unit tests
run: dotnet test "RED.Tests/RED.Tests.csproj" -c Release --logger "console;verbosity=normal"
# Fail the build on any known-vulnerable direct or transitive NuGet package.
# dotnet list always exits 0, so detect the advisory table and throw.
- name: Dependency vulnerability gate
shell: pwsh
run: |
$out = (dotnet list "!RED+.sln" package --vulnerable --include-transitive | Out-String)
Write-Host $out
if ($out -match 'has the following vulnerable packages') {
throw 'Vulnerable NuGet packages detected — see the list above.'
}
- name: Headless safety smoke
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$exe = Join-Path $PWD 'bin\Release\RED+.exe'
if (!(Test-Path $exe)) { throw "Missing RED+.exe at $exe" }
$root = Join-Path $env:RUNNER_TEMP ("redpp-ci-" + [guid]::NewGuid().ToString("N"))
$junctionTarget = $root + '-junction-target'
New-Item -ItemType Directory -Path $root | Out-Null
try {
$emptyDir = Join-Path $root 'empty-child'
$nonEmpty = Join-Path $root 'non-empty'
$junction = Join-Path $root 'junction'
$deny = Join-Path $root 'deny-access'
New-Item -ItemType Directory -Path $emptyDir, $nonEmpty, $junctionTarget, $deny | Out-Null
Set-Content -Path (Join-Path $nonEmpty 'keep.txt') -Value 'content' -NoNewline
New-Item -ItemType File -Path (Join-Path $root 'empty-file.txt') | Out-Null
New-Item -ItemType Junction -Path $junction -Target $junctionTarget | Out-Null
$currentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$denyAcl = $currentUser + ':(OI)(CI)(R)'
icacls "$deny" /deny "$denyAcl" | Out-Null
try {
& $exe -silent -path $root -emptyfiles -mode direct | Tee-Object -Variable output
if ($LASTEXITCODE -ne 0) { throw "RED++ exited with $LASTEXITCODE`n$output" }
}
finally {
icacls "$deny" /remove:d "$currentUser" | Out-Null
}
$joinedOutput = $output -join [Environment]::NewLine
if ($joinedOutput -notmatch 'Found 1 empty directories') { throw "Unexpected found-directory count:`n$joinedOutput" }
if (!(Test-Path $root)) { throw 'Root directory was deleted despite AutoProtectRoot' }
if (Test-Path $emptyDir) { throw 'Empty child directory was not deleted' }
if (!(Test-Path (Join-Path $nonEmpty 'keep.txt'))) { throw 'Non-empty directory payload was deleted' }
if (!(Test-Path $junction)) { throw 'Junction was deleted' }
if (!(Test-Path $junctionTarget)) { throw 'Junction target outside the scan root was deleted' }
if (!(Test-Path $deny)) { throw 'Deny-ACL directory was deleted' }
if (Test-Path (Join-Path $root 'empty-file.txt')) { throw 'Empty file was not deleted' }
}
finally {
Remove-Item -LiteralPath $root -Recurse -Force -ErrorAction SilentlyContinue
Remove-Item -LiteralPath $junctionTarget -Recurse -Force -ErrorAction SilentlyContinue
}