Skip to content

enhancement: make process mitigations an install option (on by default) — they are inherited by the build tree #82

Description

@EliorMachlev

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions