EDR keeps quarantining build tools or compiler output: safe exclusions
For developers whose endpoint protection keeps deleting build tools, wrappers, or freshly compiled binaries. This runbook shows how to prove the EDR is the actor, identify whether the hit is path-, hash-, signer-, or behavior-based, and write the narrowest exclusion that stops the breakage without creating a blind spot.
TL;DR — If your build suddenly starts failing with "file not found", "access denied", or unsigned-output errors even though the tool existed a second ago, assume the EDR quarantined either the build tool itself or the generated artifact. The safest fix is usually a narrowly scoped exclusion on a dedicated build/output path or trusted signer/certificate for your internal tools — not a blanket process or folder exclusion on your whole repo. Reading time: ~6 min
The scenario
It is 3:40 PM on a Tuesday. You pull the latest branch, kick off a local release build, and the compiler gets halfway through before the linker starts throwing "No such file or directory" for a binary that definitely existed moments earlier. CI is green, but every developer on managed laptops is now seeing the same local failure, and one person reports their EDR console says a "suspicious executable" was quarantined from target\release right after compilation. You need to get builds moving again without carving a giant hole in endpoint protection.
Symptoms
- Build succeeds on unmanaged machines or CI runners, fails only on EDR-managed endpoints.
- Freshly generated binaries disappear from the output directory during or immediately after build.
- Typical build errors:
clang: error: unable to execute command: program not executable
clang: error: linker command failed with exit code 1 (use -v to see invocation)
ld: cannot open output file bin/mytool.exe: Permission denied
collect2: error: ld returned 1 exit status
csc : error CS2012: Cannot open 'obj\Release\app.dll' for writing -- 'Access is denied.'
error: failed to run custom build command for `ring v0.17.x`
Caused by:
process didn't exit successfully: `...\build-script-build.exe` (exit code: 0xc0000142)
MSB3021: Unable to copy file "obj\Release\app.dll" to "bin\Release\app.dll". Access to the path is denied.
MSB3027: Could not copy "..." because the retry count was exceeded.
- Shell output shows files vanish between
ls/dircalls:
$ ls -l build/out
-rwxr-xr-x 1 you staff 183296 Mar 12 15:41 codegen
$ sleep 2
$ ls -l build/out
ls: build/out/codegen: No such file or directory
- EDR/AV event log mentions quarantine, remediation, blocked execution, or suspicious behavior for the tool path, temp extraction path, or generated executable.
- Re-running the build with output redirected to a different directory changes the failing filename or stage.
Likely causes
| Cause | How common | Quick check |
|---|---|---|
| Output directory is being monitored and fresh binaries/scripts are quarantined by path or behavior | Very common | mkdir -p /tmp/edr-test && cp ./your-built-binary /tmp/edr-test/ && sleep 5 && ls -l /tmp/edr-test |
| The build tool/wrapper itself was quarantined after an update because its hash changed | Very common | `sha256sum ./tools/codegen ./tools/compiler-wrapper 2>/dev/null |
| EDR is blocking child-process behavior from the build system (compiler spawning linker, codegen, shell, PowerShell, etc.) | Common | Re-run with verbose build logging: cargo build -vv or dotnet build -v:diag |
| Unsigned or newly signed internal tools are not covered by an existing signer/certificate allow rule | Common | signtool verify /pa .\tools\internal-tool.exe or codesign -dv --verbose=4 ./tools/internal-tool |
| Builds happen under user temp/cache paths that are treated as high-risk execution locations | Common | echo %TEMP% on Windows or echo "$TMPDIR" on macOS/Linux |
| Archive extraction or self-extracting toolchain bootstrap is being blocked before compile starts | Less common | Re-run the bootstrap step alone and inspect the extracted directory: `tar -tf toolchain.tar.gz |
Step-by-step diagnosis
- Confirm the file is being removed after creation, not never created.
# Linux/macOS
mkdir -p ./_edr_probe && cp ./path/to/tool-or-output ./_edr_probe/probe.bin && ls -l ./_edr_probe && sleep 5 && ls -l ./_edr_probe
# Windows PowerShell
New-Item -ItemType Directory -Force .\_edr_probe | Out-Null
Copy-Item .\path\to\tool-or-output.exe .\_edr_probe\probe.exe
Get-ChildItem .\_edr_probe
Start-Sleep -Seconds 5
Get-ChildItem .\_edr_probe
If the file exists and then disappears, this is your problem. Jump to Fixes → Output directory is being monitored.
- Check whether the tool binary itself is gone or replaced after a tool update.
sha256sum ./tools/* 2>/dev/null | sort
Get-ChildItem .\tools | Get-FileHash -Algorithm SHA256 | Sort-Object Path
If the expected wrapper/compiler/helper exe is missing, zero bytes, or quarantine events line up with a recent tool update, jump to Fixes → Build tool/wrapper quarantined after update.
- Re-run the build with maximum verbosity and note the exact child process that fails.
cargo build -vv
# or
dotnet build -v:diag
# or
cmake --build build --verbose
# or
ninja -v
This is your problem if logs show a helper process starts and immediately fails with access/exec errors, for example:
Running `...\build-script-build.exe`
The system cannot find the file specified.
or
execve(".../ld", ...): Permission denied
Jump to Fixes → Child-process behavior blocked.
- Check signer status for internal tools you distribute to developers.
signtool verify /pa .\tools\internal-tool.exe
Typical failure shape:
SignTool Error: No signature found.
On macOS:
codesign -dv --verbose=4 ./tools/internal-tool 2>&1 | sed -n '1,12p'
If the tool is unsigned, signed by a new cert, or signed by a different publisher than your existing allow rule, jump to Fixes → Signer/certificate rule missing or stale.
- Check whether your build is executing from temp, cache, or extracted-package paths.
echo "$TMPDIR"; pwd
Write-Host $env:TEMP; Get-Location
If the failing executable lives under %TEMP%, %LOCALAPPDATA%\Temp, browser download folders, or package-manager cache directories, jump to Fixes → Build under a dedicated trusted path.
- Inspect endpoint events on the machine before changing policy. Use your provider's dashboard or local endpoint event viewer and filter by the filename, SHA-256, and parent process around the failure time. If the event says quarantine/remediation on write or on execution, use that exact path/hash/signer in the relevant fix below. If there is no endpoint event at all, you may be chasing filesystem permissions instead of EDR.
View as text
[Build System] ---- 1. writes exe ----> [Output Dir]
|
| 2. spawns helper/linker
v
[Child Process] ---- 3. blocked/quarantined ----> [EDR]
^
| 4. file missing / access denied
|
[Developer]Fixes
Output directory is being monitored and fresh binaries/scripts are quarantined by path or behavior
Use a dedicated build/output root and exclude only that root from on-write or remediation scanning if your policy model supports path exclusions. Do not exclude the whole repository.
Examples:
# Linux/macOS: move build output under a dedicated path
mkdir -p "$HOME/.local/build-out/myapp"
export CARGO_TARGET_DIR="$HOME/.local/build-out/myapp/cargo"
export GOCACHE="$HOME/.local/build-out/myapp/go-cache"
# Windows: dedicated output root
New-Item -ItemType Directory -Force C:\BuildOutput\MyApp | Out-Null
setx CARGO_TARGET_DIR C:\BuildOutput\MyApp\cargo
setx NUGET_PACKAGES C:\BuildOutput\MyApp\nuget
Then add an exclusion in your EDR policy for that exact path, ideally limited to write/quarantine events for generated artifacts, not network/script behavior globally. In your provider's dashboard, this is usually under something like policy exclusions for file paths.
Verify it worked:
rm -f "$CARGO_TARGET_DIR"/release/yourbin && cargo build --release && ls -l "$CARGO_TARGET_DIR"/release/yourbin
The build tool/wrapper itself was quarantined after an update because its hash changed
Restore the quarantined file from the endpoint console if your process allows it, then replace broad hash-based exceptions with a signer- or path-based rule for your internal tools directory. Hash exclusions break every time you rebuild or upgrade.
# Recompute hashes after restoring/reinstalling the tool
Get-ChildItem .\tools | Get-FileHash -Algorithm SHA256
If you must use a hash temporarily, scope it to the exact file and set an expiry in your change ticket. Better long-term layout:
repo-root\tools\signed\internal-tool.exe
repo-root\tools\signed\compiler-wrapper.exe
Then exclude only repo-root\tools\signed\* or, better, trust the signer used for those binaries.
Verify it worked:
.\tools\compiler-wrapper.exe --version; if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
EDR is blocking child-process behavior from the build system
This usually shows up when make, ninja, msbuild, cargo, or a wrapper spawns helper shells, PowerShell, script interpreters, linkers, or code generators. The safe fix is a parent-process + child-process rule limited to your build toolchain paths, not a blanket allow for powershell.exe or cmd.exe everywhere.
Examples of narrow scope to request from security:
- Parent process:
C:\Program Files\Microsoft Visual Studio\...\MSBuild.exe - Allowed child paths:
C:\BuildOutput\MyApp\*,repo-root\tools\signed\* - Parent process:
/usr/bin/ninja - Allowed child paths:
/Users/you/.local/build-out/myapp/*
If your build currently shells out through generic scripts in temp, move them into a stable signed tools directory.
⚠️ Do not ask for a global allow on
cmd.exe,powershell.exe,/bin/sh, or your whole IDE process tree. That is the exact hole attackers want.
Verify it worked:
ninja -v 2>&1 | tee /tmp/build.log; test ${PIPESTATUS[0]} -eq 0
Unsigned or newly signed internal tools are not covered by an existing signer/certificate allow rule
Sign internal helper binaries and code generators with your org's code-signing certificate, then trust that signer in EDR policy instead of trusting mutable hashes.
Windows signing example:
signtool sign /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 /a .\tools\internal-tool.exe
signtool verify /pa .\tools\internal-tool.exe
macOS signing example:
codesign --force --timestamp --sign "Developer ID Application: Example Corp" ./tools/internal-tool
codesign -dv --verbose=4 ./tools/internal-tool 2>&1 | sed -n '1,12p'
If you rotated certs recently, update the EDR trust rule to include the new certificate chain or publisher.
Verify it worked:
signtool verify /pa .\tools\internal-tool.exe
Builds happen under user temp/cache paths that are treated as high-risk execution locations
Move build caches and extracted toolchains out of temp/download locations into a stable directory under your profile or a managed build root.
setx TMP C:\BuildTemp
setx TEMP C:\BuildTemp
New-Item -ItemType Directory -Force C:\BuildTemp, C:\BuildOutput\MyApp | Out-Null
mkdir -p "$HOME/.cache/build-temp" "$HOME/.local/build-out/myapp"
export TMPDIR="$HOME/.cache/build-temp"
For package managers, also pin cache dirs where supported.
export npm_config_cache="$HOME/.cache/npm"
export PIP_CACHE_DIR="$HOME/.cache/pip"
export CARGO_HOME="$HOME/.cargo"
Verify it worked:
printf '%s
' "$TMPDIR" && test -d "$TMPDIR"
Archive extraction or self-extracting toolchain bootstrap is being blocked before compile starts
Stop extracting toolchains into temp on every build. Pre-install the toolchain into a fixed path, or vendor it into a signed tools directory.
# Example: unpack once into a stable path
mkdir -p "$HOME/.local/toolchains/mytool-1.4.2"
tar -xzf ./toolchain.tar.gz -C "$HOME/.local/toolchains/mytool-1.4.2"
export PATH="$HOME/.local/toolchains/mytool-1.4.2/bin:$PATH"
If the bootstrapper is a self-extracting executable that keeps getting flagged, replace it with a plain archive plus checksum verification in your setup script.
Verify it worked:
mytool --version
Prevention
- Pin build outputs and caches to dedicated paths in repo bootstrap scripts so every developer lands in the same exclusion scope.
# bootstrap.sh
export CARGO_TARGET_DIR="$HOME/.local/build-out/myapp/cargo"
export TMPDIR="$HOME/.cache/build-temp"
mkdir -p "$CARGO_TARGET_DIR" "$TMPDIR"
- Sign internal tools and fail CI if a shipped helper binary is unsigned.
signtool verify /pa .\tools\*.exe; if ($LASTEXITCODE -ne 0) { throw "Unsigned tool in tools/" }
- Record SHA-256 and signer metadata for shipped build helpers in CI artifacts so support can match endpoint events quickly.
find ./tools -type f -maxdepth 1 -exec shasum -a 256 {} \; > tools.sha256
- Add a smoke test in CI and local bootstrap that executes each helper from the final installed path, not just from the repo checkout.
./tools/internal-tool --version && ./tools/codegen --help >/dev/null
- Keep a standard request template for security with exact scope: parent process, child path, signer, and expiry for temporary hash rules.
{"parent_process":"MSBuild.exe","child_path":"C:\\BuildOutput\\MyApp\\*","signer":"Example Corp","temporary_hash_expiry":"2026-11-15"}
- Alert on repeated quarantine events under build roots so you catch policy regressions before a release day.
# Example log filter concept; implement in your SIEM/EDR query language
path starts_with "C:\\BuildOutput\\MyApp\\" OR path starts_with "/Users/*/.local/build-out/myapp/"
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI