Make the process mitigation policies an installable option — a checkbox on the Advanced Options page, enabled (blocking) by default — instead of the current unconditional, non-configurable behaviour.
Default stays hardened. This adds the escape hatch for the case where the mitigations break a build, which today requires editing appsettings.json by hand or not installing the product at all.
Why this is needed: the mitigations reach the build tree
ProcessMitigations documents itself as affecting only the service process:
https://github.com/EliorMachlev/JenkinsAsService/blob/main/src/JenkinsAsService/ProcessMitigations.cs#L8-L15
Mitigations affect only this process, never the spawned java.exe child, so builds are unaffected.
That is not correct. Process mitigation policies are inherited by child processes. Measured on Windows 11 26200, same parent and same child image, the only variable being whether SetProcessMitigationPolicy ran first:
[parent] image-load before = 0
[child BEFORE-set] image-load=0
[parent] image-load after = 7
[child AFTER-set] image-load=7
So the image-load policy — which does apply successfully today — propagates to java.exe and to every compiler, test runner and script the agent spawns beneath it.
The flag that matters is NoRemoteImages (1u << 0): it blocks loading DLLs from UNC paths and mapped network drives. A build step that runs tooling from a network share, or loads a native DLL from one, will fail under the service in a way it does not fail when run by hand — and the failure will surface as a confusing loader error inside the build, far from anything naming this product. PreferSystem32Images is inherited too and can change DLL resolution for build tools that ship their own copies of system-named libraries.
This has not been observed breaking a real build. The inheritance mechanism is confirmed; the impact on any specific pipeline is not. But the premise that made the "safe subset" safe was that it stopped at the service boundary, and it does not.
Proposed change
Config: add to HardeningSettings, alongside SanitizeEnvironment:
/// <summary>When true (default), applies process mitigation policies at startup.</summary>
public bool ProcessMitigations { get; set; } = true;
with a ConfigKeys.Hardening.ProcessMitigations constant. Program.cs:75 already runs after bootConfig is read, so the value is available without restructuring startup:
if (jenkinsSection.GetValue<bool?>(MitigationsKey) ?? true)
{
ProcessMitigations.Apply(msg => Log.Warning("{Warning}", msg));
}
Installer: a JENKINS_MITIGATIONS property defaulting to 1, a checkbox on AdvancedOptionsDlg, and plumbing through the --merge custom actions. Note the WriteAdvanced1/2/3 split exists to stay under the MSI 255-char custom action limit — adding a field may require rebalancing across the three, or a fourth.
Wording on the checkbox should say what turning it off costs and what leaving it on can break, e.g.:
☑ Apply process hardening (blocks DLL loading from network paths)
Recommended. Disable only if your builds load tools or libraries from UNC paths or mapped drives.
Docs: configuration.html is authoritative and needs the new key. Separately, docs/api-reference.html:300 repeats the false claim verbatim — "Affects future LoadLibrary calls in this process only, not the Java child" — and must be corrected regardless of whether this option lands. Security.md:101, README.md:277 and overview.html:77 say the policies are "applied to the service process", which is true as far as it goes but implies a containment that does not hold; they should say the child inherits them.
Worth deciding during implementation
- One switch or two?
image-load is the one with build-visible consequences; extension-point-disable is refused by the OS on most systems anyway (see #TBD — it logs a Win32 87 warning at every start). A single switch is simpler and probably right; splitting them lets an operator keep the harmless one.
- Better than a switch: launch the agent child with explicit mitigation attributes via
PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY that clear the inherited policy, keeping the service hardened while leaving builds untouched. That gets both properties instead of trading one for the other, and would make the option unnecessary. It is more work and more interop, and the interop cannot be exercised in CI — the same objection that sank the interactive-session launch. The config switch is the low-risk version and does not preclude this later.
Acceptance
- Fresh install with the box ticked (default) behaves exactly as today.
- Unticking it produces
Hardening:ProcessMitigations = false and no mitigation is applied at startup.
- Upgrade preserves an existing explicit value and adds the key at its default when absent (
ServiceSettingsNormalizer).
- The false "never the spawned child" claim is corrected in code comments,
architecture.html and Security.md.
- Test coverage for the on/off branch and for the config binding.
Make the process mitigation policies an installable option — a checkbox on the Advanced Options page, enabled (blocking) by default — instead of the current unconditional, non-configurable behaviour.
Default stays hardened. This adds the escape hatch for the case where the mitigations break a build, which today requires editing
appsettings.jsonby hand or not installing the product at all.Why this is needed: the mitigations reach the build tree
ProcessMitigationsdocuments itself as affecting only the service process:https://github.com/EliorMachlev/JenkinsAsService/blob/main/src/JenkinsAsService/ProcessMitigations.cs#L8-L15
That is not correct. Process mitigation policies are inherited by child processes. Measured on Windows 11 26200, same parent and same child image, the only variable being whether
SetProcessMitigationPolicyran first:So the
image-loadpolicy — which does apply successfully today — propagates tojava.exeand to every compiler, test runner and script the agent spawns beneath it.The flag that matters is
NoRemoteImages(1u << 0): it blocks loading DLLs from UNC paths and mapped network drives. A build step that runs tooling from a network share, or loads a native DLL from one, will fail under the service in a way it does not fail when run by hand — and the failure will surface as a confusing loader error inside the build, far from anything naming this product.PreferSystem32Imagesis inherited too and can change DLL resolution for build tools that ship their own copies of system-named libraries.This has not been observed breaking a real build. The inheritance mechanism is confirmed; the impact on any specific pipeline is not. But the premise that made the "safe subset" safe was that it stopped at the service boundary, and it does not.
Proposed change
Config: add to
HardeningSettings, alongsideSanitizeEnvironment:with a
ConfigKeys.Hardening.ProcessMitigationsconstant.Program.cs:75already runs afterbootConfigis read, so the value is available without restructuring startup:Installer: a
JENKINS_MITIGATIONSproperty defaulting to1, a checkbox onAdvancedOptionsDlg, and plumbing through the--mergecustom actions. Note theWriteAdvanced1/2/3split exists to stay under the MSI 255-char custom action limit — adding a field may require rebalancing across the three, or a fourth.Wording on the checkbox should say what turning it off costs and what leaving it on can break, e.g.:
Docs:
configuration.htmlis authoritative and needs the new key. Separately,docs/api-reference.html:300repeats the false claim verbatim — "Affects futureLoadLibrarycalls in this process only, not the Java child" — and must be corrected regardless of whether this option lands.Security.md:101,README.md:277andoverview.html:77say the policies are "applied to the service process", which is true as far as it goes but implies a containment that does not hold; they should say the child inherits them.Worth deciding during implementation
image-loadis the one with build-visible consequences;extension-point-disableis refused by the OS on most systems anyway (see #TBD — it logs a Win32 87 warning at every start). A single switch is simpler and probably right; splitting them lets an operator keep the harmless one.PROC_THREAD_ATTRIBUTE_MITIGATION_POLICYthat clear the inherited policy, keeping the service hardened while leaving builds untouched. That gets both properties instead of trading one for the other, and would make the option unnecessary. It is more work and more interop, and the interop cannot be exercised in CI — the same objection that sank the interactive-session launch. The config switch is the low-risk version and does not preclude this later.Acceptance
Hardening:ProcessMitigations = falseand no mitigation is applied at startup.ServiceSettingsNormalizer).architecture.htmlandSecurity.md.