1+ using System . Collections . Generic ;
12using Fallout . Common . CI . GitHubActions ;
3+ using Fallout . Common . CI . GitHubActions . Configuration ;
24using Fallout . Components ;
35
4- // Two generated build workflows. Both run Test+Pack; both are GENERATED from the
5- // attributes below — edit here and regenerate (`./build.sh`), never hand-edit the
6- // `.yml`.
6+ // Three generated workflows. build.yml and build-cross-platform.yml both run Test+Pack; all
7+ // three are GENERATED from the attributes below — edit here and regenerate (`./build.sh`),
8+ // never hand-edit the `.yml`.
79//
810// build.yml — the Linux PR gate, and the ONLY required status
911// check (job `ubuntu-latest`; branch protection keys on
1517// merge SHA, keeping HEAD attached so
1618// GitHubTasksTest.GitHubRepositoryFromLocalDirectoryTest
1719// (which reads .git/HEAD via GitRepository.FromLocalDirectory)
18- // resolves a non-null branch.
20+ // resolves a non-null branch. Also runs PackageGuard — its
21+ // policy-violation check gates every PR, though the target skips
22+ // SBOM/risk-report generation here (build/Build.PackageGuard.cs,
23+ // IsOnLongLivedBranch — this checkout is never on one of the four).
24+ // EnableGitHubToken avoids anonymous GitHub API rate-limiting on
25+ // PackageGuard's license lookups, now that it runs on every PR.
1926//
2027// build-cross-platform.yml — macOS + Windows in ONE workflow (one job per image).
2128// Cross-platform full Test+Pack is gated to RELEASE
4451 // long-lived and protected; all require the ubuntu-latest check.
4552 OnPullRequestBranches = new [ ] { DevelopBranch , MainBranch , ReleaseBranchPattern , SupportBranchPattern } ,
4653 OnPullRequestExcludePaths = new [ ] { "docs/**" , ".assets/**" , "**/*.md" } ,
47- InvokedTargets = new [ ] { nameof ( VerifyGeneratedTools ) , nameof ( ITest . Test ) , nameof ( IPack . Pack ) } ,
54+ InvokedTargets = new [ ] { nameof ( VerifyGeneratedTools ) , nameof ( ITest . Test ) , nameof ( IPack . Pack ) , nameof ( PackageGuard ) } ,
55+ EnableGitHubToken = true ,
4856 PublishArtifacts = false ) ]
4957[ GitHubActions (
5058 "build-cross-platform" ,
6169 OnPullRequestExcludePaths = new [ ] { "docs/**" , ".assets/**" , "**/*.md" } ,
6270 InvokedTargets = new [ ] { nameof ( ITest . Test ) , nameof ( IPack . Pack ) } ,
6371 PublishArtifacts = false ) ]
64- partial class Build
72+ // security-scan.yml — continuous SBOM + risk-report generation
73+ // (build/Build.PackageGuard.cs). PackageGuard's policy-violation
74+ // check already runs on every PR via build.yml above; this workflow
75+ // is for the SBOM/SARIF/HTML side, which build.yml's target
76+ // deliberately skips (its checkout is never one of the four
77+ // long-lived branches, so IsOnLongLivedBranch is false there).
78+ // Push-only, and only to develop/main/release/*/support/* — a push
79+ // is when there's actually a new commit on one of those branches to
80+ // report on. EnableGitHubToken feeds GITHUB_TOKEN to PackageGuard
81+ // (avoids GitHub API rate-limiting on license lookups) and to the
82+ // upload-sarif step's security-events:write use.
83+ [ GitHubActions (
84+ "security-scan" ,
85+ GitHubActionsImage . UbuntuLatest ,
86+ FetchDepth = 0 ,
87+ ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}" ,
88+ ConcurrencyCancelInProgress = true ,
89+ OnPushBranches = new [ ] { DevelopBranch , MainBranch , ReleaseBranchPattern , SupportBranchPattern } ,
90+ OnPushExcludePaths = new [ ] { "docs/**" , ".assets/**" , "**/*.md" } ,
91+ InvokedTargets = new [ ] { nameof ( PackageGuard ) } ,
92+ EnableGitHubToken = true ,
93+ // Specifying any `permissions:` block switches the job from GitHub's default read-all to
94+ // explicit-only — contents:read has to be listed too, or upload-sarif (and checkout) lose
95+ // it. See GitHub's own upload-sarif docs for this exact pairing.
96+ ReadPermissions = new [ ] { GitHubActionsPermissions . Contents } ,
97+ WritePermissions = new [ ] { GitHubActionsPermissions . SecurityEvents } ,
98+ PublishArtifacts = false ) ]
99+ partial class Build : IConfigureGitHubActions
65100{
66101 // The release workflow is intentionally hand-written at
67102 // .github/workflows/publish-packages-release.yml — that lets us name the GitHub
@@ -71,4 +106,24 @@ partial class Build
71106 // workflow's `name:` — it gates ICreateGitHubRelease.CreateGitHubRelease
72107 // (Build.cs) to the release workflow only.
73108 const string ReleaseWorkflow = "publish-packages-release" ;
109+
110+ // Injects the SARIF upload after security-scan's "dotnet fallout PackageGuard" run step —
111+ // GitHubActionsStepPosition.PostRun is exactly "after the run block, before the built-in
112+ // artifact upload". Scoped to this one generated job by WorkflowName; other jobs get no
113+ // insertions.
114+ void IConfigureGitHubActions . ConfigureSteps ( GitHubActionsStepPipeline pipeline )
115+ {
116+ if ( pipeline . WorkflowName == "security-scan" )
117+ {
118+ pipeline . Insert ( GitHubActionsStepPosition . PostRun , new GitHubActionsCustomStep
119+ {
120+ Name = "Upload risk-report SARIF to GitHub code scanning" ,
121+ Uses = "github/codeql-action/upload-sarif@v3" ,
122+ With = new Dictionary < string , string >
123+ {
124+ [ "sarif_file" ] = "output/packageguard/risk-report.sarif" ,
125+ } ,
126+ } ) ;
127+ }
128+ }
74129}
0 commit comments