Skip to content

feat: add redacted FALCON_DEBUG mode for bash and PowerShell - #522

Open
carlosmmatos wants to merge 1 commit into
CrowdStrike:mainfrom
carlosmmatos:fix/post-520-safe-debug
Open

feat: add redacted FALCON_DEBUG mode for bash and PowerShell#522
carlosmmatos wants to merge 1 commit into
CrowdStrike:mainfrom
carlosmmatos:fix/post-520-safe-debug

Conversation

@carlosmmatos

@carlosmmatos carlosmmatos commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Support currently has to reach for bash -x or Set-PSDebug -Trace to diagnose a failing install, and both print credentials. This adds an opt-in debug mode that prints useful progress markers without ever printing a value that is not known to be safe.

  • bash: FALCON_DEBUG=1 or --debug
  • PowerShell: -FalconDebug or $env:FALCON_DEBUG

Markers cover the script start, the OAuth request and response, the region auto-discovery retry, curl/HTTP status and exit codes, and the SSM parameter lookups.

Redaction is fail-closed. Only keys on a fixed allow-list keep their value; everything else becomes [DROPPED], and a bare token with no key is dropped entirely:

step=response http_status=308 curl_exit=0 cloud=us-1  ->  unchanged
FALCON_CLIENT_SECRET=hunter2                          ->  FALCON_CLIENT_SECRET=[DROPPED]
cs_falcon_oauth_token=hunter2                         ->  cs_falcon_oauth_token=[DROPPED]
ProvToken=hunter2                                     ->  ProvToken=[DROPPED]
some_var_added_next_year=hunter2                      ->  some_var_added_next_year=[DROPPED]
hunter2                                               ->  (dropped)

A deny-list was the first approach, and it had to anticipate every secret-bearing variable name. It missed several that these scripts actually use, and would have failed open for the next one somebody added.

In bash, curl_command reports status with --dump-header rather than adding its own -o. curl binds output files to URLs positionally, so an injected -o steals the body from a caller that supplies its own — which is exactly what download_installer does.

Neither language re-enables tracing. The set +x guard and Set-PSDebug -Off both stay, and no curl or Invoke-WebRequest gains a verbose flag. The PowerShell catch blocks now record a status code instead of serializing the whole exception object into the on-disk log.

READMEs document the new mode and stop recommending bash -x and Set-PSDebug -Trace for support.

Verification

  • shfmt (-i 4 -ci, -ln bash and -ln posix) and shellcheck (bash and dash) clean on all four bash scripts — 16/16 silent.
  • All three PowerShell scripts parse with no errors.
  • Canary test: every credential env var set to a sentinel, debug on, stdout+stderr captured — zero hits in all four bash scripts.
  • Filter unit table, both languages: allow-listed keys survive intact; ProvToken, NewFalconClientSecret, cs_falcon_oauth_token, ART_PASSWORD, an unknown future key and a bare value all drop.
  • Download path with a caller-supplied -o: the installer file is written, stdout carries no body, and the status marker still reports. Byte-identical capture with debug on and off.
  • falcon_debug_http_status fed a multi-hop dump containing Authorization: Bearer … and Set-Cookie emits 401 and nothing else.
  • Helpers verified safe under set -e (container-pull), and Write-Host verified not to corrupt $BaseUrl, $Headers = Invoke-FalconAuth.
  • --help and -h work in any argument position on all four scripts; an unknown argument behaves as it does on main.

Not covered locally: a full end-to-end sensor install in a Linux container with live credentials. The -o mechanism was verified directly against the real curl_command, but the complete install path still wants a run on a real host.

@carlosmmatos
carlosmmatos requested a review from a team as a code owner September 8, 2026 14:01
@cursor
cursor Bot force-pushed the fix/post-520-safe-debug branch 2 times, most recently from f06f83d to 023cc3c Compare September 8, 2026 14:36

@carlosmmatos-cs carlosmmatos-cs left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the debug feature closely, and I like the shape of it — the allowlist discipline at the call sites is genuinely good and the credential protections from #520 are untouched. Two things block merging, though, and the first one is the important one.

1. The branch is stacked on the wrong commit and will revert #521

#521 is already merged (dad81bf, squash). This branch is stacked on 56a3cef, which is an intermediate commit of #521, not its final tip (d2e05e7). #521 gained three more commits after 56a3cef:

  • 0354785 re-issue the OAuth token request instead of following the redirect
  • 6b54ff1 drop the dead redirect pin from fetch_tags
  • 6a2ae59 correct the header-collection note in Get-FalconRegionHeader

None of those are in this branch, so merging as-is reverts them. Diffing this branch against origin/main shows the regressions in both languages:

bashmain has an oauth_token_request() helper plus a region-retry block that re-issues the token request against the x-cs-region hint. This branch removes both and goes straight to die when the token comes back empty:

# main (bash/install/falcon-linux-install.sh)
hinted=$(grep -i ^x-cs-region: "${response_headers}" | ...)
if [ -n "$hinted" ] && [ "$hinted" != "$cs_falcon_cloud" ]; then
    retry_host=$(cs_cloud "$hinted")
    ...
        token_result=$(echo "$auth_payload" | oauth_token_request "$retry_host" "$retry_headers")

# this branch
if [ -z "$token" ]; then
    die "Unable to obtain CrowdStrike Falcon OAuth Token. ..."
fi

PowerShellmain has Get-FalconRegionHeader(), added precisely because the response-header collection type differs by platform. This branch drops it and returns to the old single-form access:

# this branch, falcon_windows_install.ps1:285
if ($response.Headers.Contains('X-Cs-Region')) {
    $region = $response.Headers.GetValues('X-Cs-Region')[0]

So both halves of "keep region auto-discovery" — the thing #521's title promises — get undone. A customer who leaves FALCON_CLOUD unset, or sets the wrong region, goes from a working auto-discovery retry to a hard failure.

For the record, a rebase is not mechanical here. git rebase origin/main conflicts in all 7 script files on the very first commit, and cherry-picking only the debug commit (023cc3c) onto main also conflicts in all 7. Please rebase onto origin/main, drop 4fd02b1 / 3990cf5 / 56a3cef entirely, and keep only the debug commit. After that the diff should be debug-only, as the PR description intends.

2. FALCON_DEBUG=1 breaks the sensor download and dumps the package to stdout

The debug branch in curl_command inserts its own -o "$body_file" before "$@":

http_code=$(printf '%s\n' "$auth_config" |
    curl -s -x "$proxy" -L --proto '=https' --proto-redir '=https' -K- \
        -o "$body_file" -w '%{http_code}' "$@") || curl_rc=$?
...
cat "$body_file"

But download_installer supplies its own -o:

bash/install/falcon-linux-install.sh:468
curl_command "https://$(cs_cloud)/sensors/entities/download-installer/v3?id=$sha" -o "${installer}"

curl binds output files to URLs positionally, so with one URL and two -o the first one wins. I confirmed the precedence directly:

$ curl -s -o /tmp/o1 -w '%{http_code}' https://example.com -o /tmp/o2
http_code=200
o1 size: 559
o2: never created

And reproduced the end state with the actual function body:

### calling curl_command with caller-supplied -o (as download_installer does)
--- installer file created? ---
NO -- installer MISSING (install would fail)
--- stdout captured ---
stdout bytes: 559
<!doctype html><html lang="en">...

So with debug on, the sensor package lands in the mktemp file instead of ${installer}, ${installer} is never created, and cat "$body_file" writes the whole package to stdout. The installer is tens of megabytes, so the practical result is a broken install and a terminal full of binary — under exactly the flag a support engineer is asked to turn on.

Same code path in bash/migrate/falcon-linux-migrate.sh:280 with the caller at :760. falcon-container-sensor-pull.sh:325 carries the same branch but no caller passes -o, so it is unaffected today; it would be worth making all three consistent anyway.

Simplest fix is to keep the caller's own output handling and get the status a different way — for example -w '%{http_code}' written to a separate file with --stderr, or --write-out to a fd, rather than injecting -o. Whatever the approach, please add a debug-on regression check for download_installer; the current test plan exercises the auth paths but not the download path, which is why this got through.

3. The scrub is a fail-open denylist that misses this repo's own variable names

Not a live leak — I enumerated every falcon_debug / Write-FalconDebug call site across all 7 scripts and none of them interpolate a secret. They pass step names, cloud, region hint, HTTP status, curl exit, and SSM parameter name. That part is well done, and the catch blocks deliberately take only [int]$_.Exception.Response.StatusCode instead of Exception.Message, which is the right call.

The concern is that the scrub is presented as the safety net, and it only fires when a recognised label immediately precedes the value:

client_secret=SUPERSECRETVALUE123          -> client_secret=[REDACTED]
FALCON_CLIENT_SECRET=SUPERSECRETVALUE123   -> FALCON_CLIENT_SECRET=SUPERSECRETVALUE123
cs_falcon_oauth_token=SUPERSECRETVALUE123  -> cs_falcon_oauth_token=SUPERSECRETVALUE123
ART_PASSWORD=SUPERSECRETVALUE123           -> ART_PASSWORD=SUPERSECRETVALUE123
X-aws-ec2-metadata-token: SUPERSECRET...   -> X-aws-ec2-metadata-token: SUPERSECRET...
SUPERSECRETVALUE123                        -> SUPERSECRETVALUE123

[Cc]lient_[Ss]ecret cannot match CLIENT_SECRET, so the all-caps env var name slips through even though OLD_/NEW_FALCON_CLIENT_SECRET got explicit all-caps patterns. So today's safety rests on call-site discipline, not on the scrub, and the next person to add a falcon_debug line will not get caught by it. Adding the actual variable names in use — FALCON_CLIENT_SECRET, cs_falcon_oauth_token, ART_PASSWORD, X-aws-ec2-metadata-token, aws_secret_access_key — would make the net match the claim.

4. Small things

  • The READMEs say Accepted values are ['1', 'true'], but bash accepts 1|true|TRUE|yes|YES|on|ON and PowerShell matches ^(1|true|yes|on)$ case-insensitively. Docs and code should agree.
  • Authorization: Bearer $token scrubs to Authorization:[REDACTED] [REDACTED] because two patterns both fire. Harmless, just untidy output.
  • mktemp failure is unchecked; if it fails, body_file is empty and curl gets -o "".
  • The PowerShell README says tracing "is forced off at startup (Set-PSDebug -Off) so credentials stay out of logs". Set-PSDebug -Off is pre-existing and does work, but it runs inside begin{} — after parameter binding. Someone who runs Set-PSDebug -Trace 2 before invoking the script still gets the binding of -FalconClientSecret traced. Worth softening the wording so it does not over-promise.

What checks out

  • shfmt (-i 4 -ci, both -ln bash and -ln posix) and shellcheck (bash and dash) are clean on all 4 bash scripts — 16/16 checks, no output.
  • The set +x guard from #520 is intact at the top of all 4 scripts, before any credential handling.
  • Nothing re-enables tracing: no set -x, no Set-PSDebug -Trace, no $VerbosePreference/$DebugPreference assignment, no Start-Transcript, and no -Verbose/-v added to any curl or Invoke-WebRequest call.
  • No debug code touches the set -- "$@" shift no-op from #520, and no secret is added to curl argv — the token still goes through -K- on stdin.
  • Write-FalconDebug uses Write-Host, so it stays out of the pipeline and cannot corrupt a captured return value. It also does not route through Write-FalconLog, so debug lines do not persist to the temp log file. Both are the right choices.
  • --debug parsing is consistent with each script's existing style, and --help still works.

Happy to re-review once it is rebased on origin/main and the -o collision is sorted.

Support currently has to reach for `bash -x` or `Set-PSDebug -Trace` to
diagnose a failing install, and both print credentials. This adds an opt-in
debug mode that prints useful progress markers without ever printing a value
that is not known to be safe.

bash gets `FALCON_DEBUG=1` or `--debug`; PowerShell gets `-FalconDebug` or
`$env:FALCON_DEBUG`. Markers cover the script start, the OAuth request and
response, the region auto-discovery retry, curl/HTTP status and exit codes,
and the SSM parameter lookups.

Redaction is fail-closed. Only keys on a fixed allow-list keep their value;
everything else becomes `[DROPPED]`, and a bare token with no key is dropped
entirely. A deny-list would have to anticipate every secret-bearing variable
name, and would silently fail open for the next one somebody adds.

In bash, `curl_command` reports status with `--dump-header`, not by adding
its own `-o`. curl binds output files to URLs positionally, so an injected
`-o` steals the body from a caller that supplies its own — which is exactly
what `download_installer` does.

Neither language re-enables tracing. The `set +x` guard and `Set-PSDebug
-Off` both stay, and no curl or Invoke-WebRequest gains a verbose flag. The
PowerShell catch blocks now record a status code instead of serializing the
whole exception object into the on-disk log.

READMEs document the new mode and stop recommending `bash -x` and
`Set-PSDebug -Trace` for support.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants