Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 57 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: Build (Windows)

on:
push:
branches: [ "master", "main" ]
branches: [ "master", "main", "sow/**" ]
pull_request:
branches: [ "master", "main" ]
branches: [ "master", "main", "sow/**" ]
workflow_dispatch:

jobs:
Expand Down Expand Up @@ -35,6 +35,36 @@ jobs:

- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Compute SAMVersion
id: ver
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
# Prefer head_ref when its a sow branch (release-promotion PRs from sow/* to main),
# else use base_ref on PR events / ref_name on push events.
$headRef = '${{ github.head_ref }}'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Sanitize github.head_ref in version step

The new Compute SAMVersion PowerShell block embeds ${{ github.head_ref }} directly inside a single-quoted string, so a PR branch name containing ' can terminate the quote and inject arbitrary PowerShell statements. Because this job runs on pull_request, an attacker-controlled fork branch name can execute commands before restore/build and tamper with outputs or CI behavior. Pass github.head_ref via env (or escape single quotes) so it is treated as data instead of script.

Useful? React with 👍 / 👎.

$baseRef = '${{ github.base_ref }}'
$refName = '${{ github.ref_name }}'
if ($headRef -match '^sow/\d{4}-Q\d$') {
$ref = $headRef
} elseif ('${{ github.event_name }}' -eq 'pull_request') {
$ref = $baseRef
} else {
$ref = $refName
}
# .NET AssemblyVersion components are UInt16 (max 65535). Cap to 60000 for headroom.
$run = ${{ github.run_number }} % 60000
if ($ref -match 'sow/(\d{4})-Q(\d)') {
$v = "$($Matches[1]).$($Matches[2]).$run.0"
$src = "branch '$ref'"
} else {
$now = (Get-Date).ToUniversalTime()
$quarter = [int][Math]::Ceiling($now.Month / 3.0)
$v = "$($now.Year).$quarter.$run.0"
$src = "date $($now.ToString('yyyy-MM-dd')) (ref '$ref' not sow/yyyy-Qx)"
}
Write-Host "SAMVersion = $v (from $src)"
"samversion=$v" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8

- name: Clone dependency repos (siblings)
shell: pwsh
Expand All @@ -55,10 +85,32 @@ jobs:
$deps = $buildOrder[0..($buildOrder.Count-2)]
foreach ($r in $deps) {
if (Test-Path $r) { continue }
Write-Host "Cloning https://github.com/$org/$r.git"
git clone --depth 1 "https://github.com/$org/$r.git" $r
$headRef = '${{ github.head_ref }}'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Sanitize head_ref before embedding in PowerShell

Embedding ${{ github.head_ref }} directly inside a single-quoted PowerShell string makes this run: block vulnerable to script injection from PR branch names that contain ' (valid in Git refs), because the rendered value can terminate the quote and append arbitrary commands. This workflow runs on pull_request, so an attacker-controlled fork branch name can execute commands on the runner and alter CI behavior/artifacts. Pass github.head_ref via env and treat it as data (or escape single quotes) before use.

Useful? React with 👍 / 👎.

$baseRef = '${{ github.base_ref }}'
$refName = '${{ github.ref_name }}'
$candidates = @()
if ($headRef) { $candidates += $headRef }
# Current sow branch: base_ref on PR events, ref_name on push events.
$sowRef = if ($baseRef -match '^sow/') { $baseRef } elseif ($refName -match '^sow/') { $refName } else { '' }
if ($sowRef -and $sowRef -ne $headRef) { $candidates += $sowRef }
$candidates += 'sow/2026-Q2'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Clone dependencies from current branch on push runs

In .github/workflows/build.yml, when the workflow is triggered by push or workflow_dispatch, github.head_ref is empty so the script always prioritizes sow/2026-Q2 for dependency repos before falling back to default. That means builds on master/main can silently compile against sow/2026-Q2 dependencies instead of the matching branch, producing false CI results (passing/failing for the wrong code combination). The previous behavior cloned the default branch directly, so this is a regression in branch selection logic for non-PR runs.

Useful? React with 👍 / 👎.

$cloned = $false
foreach ($cand in $candidates) {
$has = (git ls-remote --heads "https://github.com/$org/$r.git" $cand 2>$null | Out-String).Trim()
if ($has) {
Write-Host "Cloning $org/$r @ $cand"
git clone --depth 1 --branch $cand "https://github.com/$org/$r.git" $r
Comment on lines +99 to +102
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Use exact ref names when probing candidate branches

In .github/workflows/build.yml, the branch existence check uses git ls-remote --heads ... $cand, but ls-remote treats <patterns> as glob matches against the tail of refs, not exact names. That means a candidate like sow/2026-Q2 can return a hit for refs/heads/team/sow/2026-Q2, after which the next line runs git clone --branch $cand and fails because the exact branch does not exist. This can break CI for repos that use namespaced branch conventions; probe with refs/heads/$cand (or --exit-code with full ref) so only exact branches are accepted.

Useful? React with 👍 / 👎.

$cloned = $true
break
}
}
if (-not $cloned) {
Write-Host "Cloning $org/$r @ default branch"
git clone --depth 1 "https://github.com/$org/$r.git" $r
}
}


# Ensure ReferencePath exists even before SAM_Windows builds
New-Item -ItemType Directory -Force -Path "SAM_Windows\build" | Out-Null

Expand All @@ -77,6 +129,7 @@ jobs:
'/v:m'
'/p:Configuration=Release'
'/p:UseSharedCompilation=false'
'/p:SAMVersion=${{ steps.ver.outputs.samversion }}'
'/p:RunPostBuildEvent=OnOutputUpdated'
"/p:ReferencePath=$windowsRef"
)
Expand Down
81 changes: 63 additions & 18 deletions .github/workflows/spdx-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,89 @@ name: SPDX + Copyright header check

on:
pull_request:
workflow_dispatch:

jobs:
spdx:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read

steps:
- uses: actions/checkout@v4
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Check header in changed .cs files
shell: bash
run: |
set -e
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
set -euo pipefail

if [ "${{ github.event_name }}" = "pull_request" ]; then
BASE="${{ github.event.pull_request.base.sha }}"
HEAD="${{ github.event.pull_request.head.sha }}"
else
HEAD="${{ github.sha }}"
BASE="$(git rev-parse "${HEAD}^" 2>/dev/null || true)"
fi

FILES=$(git diff --name-only "$BASE" "$HEAD" -- '*.cs' || true)
echo "Base SHA: ${BASE:-<none>}"
echo "Head SHA: $HEAD"
echo

if [ -z "$FILES" ]; then
if [ -z "${BASE:-}" ]; then
mapfile -t files < <(git ls-files '*.cs')
else
mapfile -t files < <(git diff --diff-filter=ACMR --name-only "$BASE" "$HEAD" -- '*.cs' || true)
fi

if [ "${#files[@]}" -eq 0 ]; then
echo "No C# files changed."
exit 0
fi

MISSING=""
for f in $FILES; do
HEADBLOCK=$(head -n 6 "$f")
echo "Changed C# files:"
printf ' - %s\n' "${files[@]}"
echo

missing=()

for f in "${files[@]}"; do
if [ ! -f "$f" ]; then
echo "Skipping missing file: $f"
continue
fi

headblock="$(head -n 20 "$f" | sed '1s/^\xEF\xBB\xBF//' | tr -d '\r')"

echo "Checking: $f"

if ! grep -q "SPDX-License-Identifier: LGPL-3.0-or-later" <<< "$headblock"; then
echo " Missing SPDX line"
missing+=("$f")
continue
fi

if ! grep -qE "Copyright \(c\) 2020[-–]2026 Michal Dengusiak & Jakub Ziolkowski and contributors" <<< "$headblock"; then
echo " Missing copyright line"
missing+=("$f")
continue
fi

echo "$HEADBLOCK" | grep -q "// SPDX-License-Identifier: LGPL-3.0-or-later" || MISSING="$MISSING $f"
echo "$HEADBLOCK" | grep -q "// Copyright (c) 2020–2026 Michal Dengusiak & Jakub Ziolkowski and contributors" || MISSING="$MISSING $f"
echo " OK"
done

if [ -n "$MISSING" ]; then
echo "❌ Missing required header in:"
for f in $MISSING; do echo " - $f"; done
echo ""
echo "Each changed .cs file must start with:"
echo
if [ "${#missing[@]}" -gt 0 ]; then
echo "Missing required header in:"
printf ' - %s\n' "${missing[@]}"
echo
echo "Each checked .cs file must contain within the first 20 lines:"
echo "// SPDX-License-Identifier: LGPL-3.0-or-later"
echo "// Copyright (c) 20202026 Michal Dengusiak & Jakub Ziolkowski and contributors"
echo "// Copyright (c) 2020-2026 Michal Dengusiak & Jakub Ziolkowski and contributors"
exit 1
fi

echo "SPDX + copyright headers OK."
echo "SPDX + copyright headers OK."
57 changes: 57 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<Project>

<PropertyGroup>
<!-- Local dev builds default to 1.0.0.0. CI passes /p:SAMVersion=YYYY.Q.<run_number>.0. -->
<SAMVersion Condition="'$(SAMVersion)' == ''">1.0.0.0</SAMVersion>
<AssemblyVersion>$(SAMVersion)</AssemblyVersion>
<FileVersion>$(SAMVersion)</FileVersion>
<!--
InformationalVersion: the content-identity stamp (SemVer +build metadata).
Composed from SAMVersion + SAMSourceRevision when CI sets the latter. If CI already
set InformationalVersion directly (e.g. SAM_Deploy installer.yml does this), respect
that and don't overwrite. Local dev builds leave both empty -> no SHA attribute emitted.
-->
<InformationalVersion Condition="'$(InformationalVersion)' == '' AND '$(SAMSourceRevision)' != ''">$(SAMVersion)+$(SAMSourceRevision)</InformationalVersion>
<!--
Suppress the SDK's SourceLink auto-append behaviour. By default, when SourceLink is
configured on a project (via NuGet or repo settings), the SDK appends the project's
OWN commit SHA to InformationalVersion (e.g. '2026.2.164.0+998af06.dd02a4c2...').
We set InformationalVersion explicitly to SAM_Deploy's SHA (which encodes all 22
submodule pointers), so the SDK's extra append is noise. False = keep our value clean.
-->
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<Deterministic>true</Deterministic>
</PropertyGroup>

<!--
Projects where SDK assembly-attribute generation is disabled (legacy AssemblyInfo.cs
with GenerateAssemblyInfo=false) OR projects where the SDK doesn't auto-generate
attributes at all (classic-style csprojs that don't set the property — '' evaluates
as not 'true') ignore the AssemblyVersion/FileVersion MSBuild properties above.
Inject the attributes via a generated source file so those assemblies also get
stamped by CI.
-->
<Target Name="_GenerateSAMVersionFile"
BeforeTargets="CoreCompile"
Condition="'$(GenerateAssemblyInfo)' != 'true'">
<ItemGroup>
<_SAMVersionLine Include="// &lt;auto-generated /&gt;" />
<_SAMVersionLine Include="[assembly: System.Reflection.AssemblyVersionAttribute(&quot;$(SAMVersion)&quot;)]" />
<_SAMVersionLine Include="[assembly: System.Reflection.AssemblyFileVersionAttribute(&quot;$(SAMVersion)&quot;)]" />
<!-- Only emit InformationalVersion attribute when CI provided a value (SDK projects
would otherwise get it via PropertyGroup -> SDK auto-gen). -->
<_SAMVersionLine Include="[assembly: System.Reflection.AssemblyInformationalVersionAttribute(&quot;$(InformationalVersion)&quot;)]" Condition="'$(InformationalVersion)' != ''" />
</ItemGroup>
<MakeDir Directories="$(IntermediateOutputPath)" />
<WriteLinesToFile
File="$(IntermediateOutputPath)SAMVersion.g.cs"
Lines="@(_SAMVersionLine)"
Overwrite="true"
WriteOnlyWhenDifferent="true" />
<ItemGroup>
<Compile Include="$(IntermediateOutputPath)SAMVersion.g.cs" KeepDuplicates="false" />
<FileWrites Include="$(IntermediateOutputPath)SAMVersion.g.cs" />
</ItemGroup>
</Target>

</Project>
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Grasshopper.Kernel;
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright (c) 2020–2026 Michal Dengusiak & Jakub Ziolkowski and contributors

using Grasshopper.Kernel;
using System;
using System.Drawing;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/*
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright (c) 2020-2026 Michal Dengusiak & Jakub Ziolkowski and contributors
/*
* This file is part of the Sustaiable Analytical Model (SAM)
* Copyright (c) 2020, the respective contributors. All rights reserved.
*
Expand Down Expand Up @@ -53,6 +55,3 @@
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0.*")]
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>true</UseWindowsForms>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
<Deterministic>false</Deterministic>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
Expand Down Expand Up @@ -54,9 +53,7 @@
<PackageReference Include="NetTopologySuite">
<Version>2.5.0</Version>
</PackageReference>
<PackageReference Include="Newtonsoft.Json">
<Version>13.0.3</Version>
</PackageReference>
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
<PropertyGroup />
<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Grasshopper.Kernel;
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright (c) 2020–2026 Michal Dengusiak & Jakub Ziolkowski and contributors

using Grasshopper.Kernel;
using System;
using System.Drawing;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/*
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright (c) 2020-2026 Michal Dengusiak & Jakub Ziolkowski and contributors
/*
* This file is part of the Sustaiable Analytical Model (SAM)
* Copyright (c) 2020, the respective contributors. All rights reserved.
*
Expand Down Expand Up @@ -53,6 +55,3 @@
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("1.0.*")]
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
<UseWindowsForms>true</UseWindowsForms>
<ImportWindowsDesktopTargets>true</ImportWindowsDesktopTargets>
<StartProgram>C:\Program Files\Rhino 8\System\Rhino.exe</StartProgram>
<Deterministic>false</Deterministic>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
<CopyLocalLockFileAssemblies>True</CopyLocalLockFileAssemblies>
Expand All @@ -30,7 +29,7 @@
<ItemGroup>
<PackageReference Include="Grasshopper" Version="8.21.25188.17001" ExcludeAssets="runtime" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
</ItemGroup>
<PropertyGroup />
Expand Down
5 changes: 4 additions & 1 deletion SAM_TMP/SAM.Analytical.TMP/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/*
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright (c) 2020–2026 Michal Dengusiak & Jakub Ziolkowski and contributors

/*
* This file is part of the Sustaiable Analytical Model (SAM)
* Copyright (c) 2020, the respective contributors. All rights reserved.
*
Expand Down
7 changes: 2 additions & 5 deletions SAM_TMP/SAM.Analytical.TMP/SAM.Analytical.TMP.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<OutputType>Library</OutputType>
<Deterministic>false</Deterministic>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<AssemblyTitle>SAM.Analytical</AssemblyTitle>
<Product>SAM.Analytical</Product>
<Copyright>Copyright © 2020</Copyright>
<AssemblyVersion>1.0.%2a</AssemblyVersion>
<FileVersion>1.0.%2a</FileVersion>
<Copyright>Copyright (c) 2020-2026 Michal Dengusiak &amp; Jakub Ziolkowski and contributors</Copyright>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
</PropertyGroup>
Expand Down Expand Up @@ -42,7 +39,7 @@
<Version>2.5.0</Version>
</PackageReference>
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Text.Json" Version="8.0.5" />
</ItemGroup>
<ItemGroup>
<Folder Include="Classes\" />
Expand Down
5 changes: 4 additions & 1 deletion SAM_TMP/SAM.Core.TMP/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
/*
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright (c) 2020–2026 Michal Dengusiak & Jakub Ziolkowski and contributors

/*
* This file is part of the Sustaiable Analytical Model (SAM)
* Copyright (c) 2020, the respective contributors. All rights reserved.
*
Expand Down
Loading