Skip to content

Regenerating the interop wrapper now records itself in the manifest #377

Regenerating the interop wrapper now records itself in the manifest

Regenerating the interop wrapper now records itself in the manifest #377

Workflow file for this run

name: CodeQL
# Static analysis for both languages in this repository.
#
# C# (the Control Panel) runs on every push and pull request. It builds on a
# GitHub-hosted runner in a couple of minutes, so it is cheap enough to gate
# changes on.
#
# C++ (the mail server - every protocol parser, and the whole surface that faces
# the internet) runs weekly and on demand, and deliberately NOT on push or pull
# request. That trade-off costs something, so the reasoning is recorded here
# rather than left to be rediscovered:
#
# * The server cannot be built on a GitHub-hosted runner at all. It needs the
# v145 toolset from Visual Studio 2026 and the prebuilt native dependencies
# (boost_1_91_0, openssl-4.0.1, postgresql-18.3) that $(hMailServerLibs)
# points at. See .github/workflows/server-build.yml, which carries the same
# requirement and the same [self-hosted, windows] label. There is one such
# machine, and it is also the machine that runs the regression suite.
# * CodeQL builds its database by tracing the compiler, so it needs a FULL
# build every time. An incremental build that skips up-to-date files
# produces an empty database and the analysis fails outright with "No source
# code was seen during the build". That means 516 translation units from
# cold, on every run, with the extractor inside every cl.exe invocation.
#
# Putting that on every push would queue pull requests behind a multi-hour job
# on a single machine, and would collide with the regression suite that shares
# it. Weekly plus on-demand is the honest answer.
#
# The cost of that choice, stated plainly: an alert introduced by a pull request
# is not seen until the next weekly run on master, and is then attributed to
# master rather than to the change that introduced it. When a change touches the
# protocol parsers, run this workflow by hand against the branch
# (workflow_dispatch) before merging rather than waiting for the schedule.
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
schedule:
- cron: '32 4 * * 1' # weekly, Monday 04:32 UTC
workflow_dispatch:
inputs:
configuration:
description: 'Build configuration for the C++ server analysis'
type: choice
options: [Release, Debug]
default: Release
permissions:
contents: read
jobs:
csharp:
name: Analyze (csharp)
runs-on: windows-latest
permissions:
security-events: write
actions: read
contents: read
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up .NET 10
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
with:
dotnet-version: '10.0.x'
# security-and-quality adds the quality-tagged queries to the security
# suite; their findings surface as code-scanning alerts alongside the
# security ones. (Not, as this comment once claimed, on the Code quality
# page - that page was fed by a separate GitHub-managed buildless scan,
# which ignored this repository's config entirely and was switched off
# deliberately: it re-reported, without type information, the exact
# rules excluded below with measured reasoning.) The six rules that fire
# in bulk here on deliberate design rather than on defects are filtered
# in the config file, each with its measured count and the reasoning.
- name: Initialize CodeQL
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
languages: csharp
build-mode: manual
config-file: ./.github/codeql/codeql-config.yml
queries: security-and-quality
# Both solutions, deliberately: for a traced build, CodeQL analyses only
# what the compiler compiles, so a project left out of this step is a
# project that has never been scanned. That was DBUpdater, DBSetup,
# DBSetupQuick, DataDirectorySynchronizer, ImportTool and Shared until
# the managed quality scan's findings revealed the gap - the only scanner
# covering those tools was the one being switched off for ignoring the
# config. The test projects are not built here and stay unscanned:
# fixtures, not shipped code.
- name: Build Control Panel
run: dotnet build hmailserver/source/Tools/ControlPanel/ControlPanel.csproj -c Release
- name: Build the setup and migration tools
run: dotnet build "hmailserver/source/Tools/hMailServer Tools.sln" -c Release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
category: "/language:csharp"
upload: never
output: sarif-results
# paths-ignore in codeql-config.yml cannot drop these results: for a
# compiled language analysed with build-mode: manual, CodeQL extracts
# whatever the compiler compiles - including the WPF XAML compiler's
# *.g.cs under obj/ - and a config file's path filters only restrict
# extraction for interpreted languages or for build-mode: none. So the
# build output is dropped from the SARIF here, between analyse and upload.
- name: Drop build-output results from the SARIF
shell: python
run: |
import json
def in_build_output(result):
locations = result.get("locations") or []
if not locations:
return False
uri = locations[0].get("physicalLocation", {}).get("artifactLocation", {}).get("uri", "")
return any(part in ("obj", "bin") for part in uri.split("/"))
path = "sarif-results/csharp.sarif"
with open(path, encoding="utf-8") as f:
sarif = json.load(f)
for run in sarif["runs"]:
before = len(run["results"])
run["results"] = [r for r in run["results"] if not in_build_output(r)]
print(f"Dropped {before - len(run['results'])} build-output result(s); {len(run['results'])} remain.")
with open(path, "w", encoding="utf-8") as f:
json.dump(sarif, f)
- name: Upload results
uses: github/codeql-action/upload-sarif@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
sarif_file: sarif-results/csharp.sarif
category: "/language:csharp"
cpp:
name: Analyze (c-cpp)
# Schedule and manual dispatch only - see the header. A push or pull_request
# run of this job would sit on the one self-hosted machine for hours.
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: [self-hosted, windows]
# A cold traced build of 516 translation units plus query evaluation over
# 6.1 MB of C++. Bounded so a wedged run cannot hold the runner for a day.
timeout-minutes: 360
permissions:
security-events: write
actions: read
contents: read
env:
# inputs.configuration is empty on a scheduled run, and build.ps1 defaults
# to Debug when handed an empty string - which would quietly analyse the
# _DEBUG preprocessor path instead of the one that ships.
BUILD_CONFIGURATION: ${{ inputs.configuration || 'Release' }}
# MSBuild keeps its worker nodes alive between invocations by default. A
# node left over from an earlier job on this runner was started outside
# CodeQL's tracer, so anything it compiles is invisible to the extractor -
# which shows up as a partial database rather than as an error.
MSBUILDDISABLENODEREUSE: '1'
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# Fail here with a legible message rather than 500 lines into MSBuild.
# server-build.yml also warns about each individual dependency folder;
# this checks only the condition that makes the build impossible rather
# than merely likely to fail at link time.
- name: Verify native dependencies
shell: powershell
run: |
if (-not $env:hMailServerLibs) { throw "hMailServerLibs is not set on this runner. See .github/workflows/server-build.yml for what this runner needs." }
if (-not (Test-Path $env:hMailServerLibs)) { throw "hMailServerLibs path '$($env:hMailServerLibs)' does not exist." }
Write-Host "hMailServerLibs = $($env:hMailServerLibs)"
# The service on this runner runs from this checkout's build output, so
# the linker cannot replace hMailServer.exe while it is running. The
# project's own pre-build event does a NET STOP for exactly this reason,
# but build.ps1 suppresses pre- and post-build events, so it happens here
# instead - the same way server-build.yml handles it.
- name: Stop hMailServer service (free the executable)
shell: powershell
run: Stop-Service hMailServer -ErrorAction SilentlyContinue
# security-extended rather than security-and-quality, deliberately, and
# this is the one place the two languages are configured differently:
#
# * CodeQL ships no quality-tagged queries for C++ at all (measured
# in d1c35ec: 108 for C#, zero for C++), so security-and-quality
# would add nothing but the generic maintainability set.
# * What it would buy is roughly a hundred maintainability and
# reliability queries let loose on 6.1 MB of twenty-year-old ATL/COM
# C++, on a codebase that has never been scanned once. d1c35ec was
# written because 733 undifferentiated findings are "not a signal
# anyone can act on"; burying the first ever security scan of the
# protocol parsers under style alerts repeats that mistake.
#
# security-extended is strictly more security coverage with no quality
# queries, which is what this analysis is for. Once the security backlog
# is triaged, moving this to security-and-quality is a one-word change -
# but do it from a measured count, not on principle.
- name: Initialize CodeQL
uses: github/codeql-action/init@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
languages: c-cpp
build-mode: manual
config-file: ./.github/codeql/codeql-config.yml
queries: security-extended
# -Clean is not optional. This runner reuses its workspace between runs,
# so without it MSBuild skips every up-to-date translation unit, the
# extractor sees nothing, and the analyze step below fails with "No source
# code was seen during the build" - the classic CodeQL C++ failure on a
# self-hosted runner, and one that reads like a CodeQL bug rather than an
# incremental build.
- name: Build server under CodeQL tracing
shell: powershell
run: ./build/build.ps1 -Configuration $env:BUILD_CONFIGURATION -Clean
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@5595ccaf912efad79be6eef63a5619ff05969be3 # v4.37.6
with:
category: "/language:c-cpp"
- name: Restart hMailServer service
if: ${{ always() }}
shell: powershell
run: |
Start-Service hMailServer -ErrorAction SilentlyContinue
$service = Get-Service hMailServer -ErrorAction SilentlyContinue
if (-not $service -or $service.Status -ne 'Running') {
Write-Warning "hMailServer is not running. A build that failed after the clean step leaves the output directory empty, so the service binary may be missing - rerun server-build.yml to restore it."
}