Skip to content

fix(security): protect credentials in deployment scripts - #520

Merged
carlosmmatos-cs merged 5 commits into
mainfrom
fix/secure-credential-handling
Sep 8, 2026
Merged

fix(security): protect credentials in deployment scripts#520
carlosmmatos-cs merged 5 commits into
mainfrom
fix/secure-credential-handling

Conversation

@carlosmmatos-cs

@carlosmmatos-cs carlosmmatos-cs commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Credentials handed to cURL now travel on its configuration input rather than as command-line arguments, across the four bash scripts and the three PowerShell scripts, so they stay out of the process list and out of process-creation logs. Alongside that, requests and redirects are restricted to HTTPS, shell and PowerShell tracing is turned off before any credential is handled, secrets are dropped from the exported environment so child processes do not inherit them, and the downloaded installer is checked against a SHA-256 digest.

RHEL and CentOS 7 keep working, and they no longer need a special case. Every script uses one mechanism on every cURL: the credential goes on the configuration input as an Authorization header, which behaves the same on the cURL that EL7 ships and on current cURL. The version gate is gone entirely, so nothing is blocked on a cURL version any more. ALLOW_LEGACY_CURL and --allow-legacy-curl are still accepted, print a notice, and do nothing; the READMEs mark them deprecated. That is not a breaking change, since anyone on EL7 was already setting one of them to get past the previous gate.

A few smaller fixes came along with it. verify_sha256 falls back to openssl where sha256sum is unavailable, and stops with a clear message when neither is present. aws_ssm_parameter reports the parameter name and the AWS error message on failure, rather than the whole response body. The command -v curl guard runs at top level, after die is defined, so a host without cURL gets the intended message. And bash_migrate.yml was running shellcheck against cs-migrate.sh, which is not in the tree; it now points at falcon-linux-migrate.sh.

Tests are deliberately not part of this change. A proper suite will follow in its own PR.

… to fail

RHEL/CentOS 7 ships curl 7.29.0, which does not know oauth2-bearer. It does
not reject the unknown configuration key either: it ignores it, exits 0, and
sends the request with no credential at all. The previous commit therefore
broke EL7 silently rather than loudly.

Each script now detects the capability positively and falls back to a raw
Authorization header when it is missing. The credential still travels on
curl's configuration input in both modes, so it never reaches the command
line. The gate moves from 7.55 to 7.33, the version that added oauth2-bearer,
which also stops blocking curl 7.33 through 7.54 for no reason, and it is
restored in the uninstall and migrate scripts where it had been dropped.
Warning text now names what is actually unverified on very old curl, which is
redirect handling, and names the control that bounds it.

Two latent defects turned up while doing this. The command -v curl guard sat
inside a command substitution, where die only exits the subshell and leaves
the variable empty; in falcon-linux-uninstall.sh that guard also called die
39 lines before die was defined, so a host without curl got "die: command not
found" instead of the message. Both are fixed.

The credential handling suite could not fail. It called rg, which is not
installed on ubuntu-latest, so "if rg ...; then fail; fi" read exit 127 as no
match and three checks quietly did nothing. Replaced with grep behind a
helper that inspects the exit status, so a missing or broken tool fails
instead of passing. The mode loop now exercises both credential mechanisms
rather than looping over a variable nothing read.

The container matrix passed its setup command through a variable that the
container shell expanded after parsing, turning && and > into literal
arguments, which is why all five legs errored. A dependency script that
detects the package manager replaces it, and Oracle Linux 7 and CentOS 7 legs
are added, since the oldest curl exercised before this was 7.81.

Also: aws_ssm_parameter no longer prints the decrypted response body on
failure, since that body can carry the client secret; the live curl test runs
on python2 and cannot hang; and the PowerShell test reports every collected
failure instead of throwing on the first one.
The release job swept the whole tree for *.sh and *.ps1, so every test and CI
helper script added on this branch would have been uploaded as a release asset
and listed in checksum.txt. That is five extra files, including one named
install-test-deps.sh, which is exactly the sort of thing someone would
mistake for something they should run.

Restricting the search to bash, powershell and systemd, and skipping
bash/**/test/, brings the asset list back to the same 8 scripts and 3 zips
that a release produced before. Verified by running the whole step in a
container and diffing the result against the file list on main.
@carlosmmatos-cs
carlosmmatos-cs requested a review from a team as a code owner September 4, 2026 19:26
…y curl

Replaces the curl version gate with a single mechanism that works on every
supported curl: the bearer credential travels on curl's configuration input
as an Authorization header. Nothing is gated on a curl version now, and
ALLOW_LEGACY_CURL and --allow-legacy-curl are accepted, ignored, and
documented as deprecated.

verify_sha256 falls back to openssl where sha256sum is unavailable, and stops
with a clear message when neither is present. The aws_ssm_parameter failure
path reports the parameter name and the AWS error message rather than the
response body. The 'curl is missing' guard runs at top level, after die is
defined, so a host without curl gets the intended message.
Removes the CI helper scripts, the credential-handling workflow and the
container-pull unit test from this change, and reverts the workflow edits that
existed only to run them. The release asset list is unchanged either way: with
those scripts gone, the original find already yields the same eight shipped
scripts, so that edit is reverted too.

Also points bash_migrate.yml at falcon-linux-migrate.sh. Its current shellcheck
target, cs-migrate.sh, is not in the tree.
@carlosmmatos-cs
carlosmmatos-cs merged commit 205b919 into main Sep 8, 2026
10 checks passed
@carlosmmatos-cs
carlosmmatos-cs deleted the fix/secure-credential-handling branch September 8, 2026 13:58
carlosmmatos-cs added a commit that referenced this pull request Sep 8, 2026
…o-discovery (#521)

* fix(security): harden OAuth and download redirects against credential replay

Stop bash OAuth token POSTs and PowerShell Invoke-FalconAuth from replaying
client_secret on HTTPS redirect hop 2. Pin fetch_tags to HTTPS without -L.
Strip Authorization before following Falcon download CDN redirects.

Follow-on to #520. Live-validated on the fork (CAND-001/002/003/004).

* fix(security): drop download rewrite; keep oauth and fetch_tags redirect hardening

Co-authored-by: Carlos Matos <carlosmmatos@users.noreply.github.com>

* chore: strip verbose oauth and fetch_tags comments

Keep the redirect hardening; drop the multi-line explanatory blocks.

* fix(security): re-issue the OAuth token request instead of following the redirect

Dropping -L and setting -MaximumRedirection 0 stops a 307/308 from replaying the
client secret in the request body, but it also disabled region auto-discovery,
which is what that redirect is for.

Measured against the live API on curl 7.29.0 and 7.76.1: a wrong-region token
request answers 308 with x-cs-region and a Location, and following it with -L
does return a token, so the body is replayed.

bash now reads x-cs-region off the un-followed response and re-issues the
request against that region, resolved through cs_cloud(), which is a closed
allowlist that dies on anything it does not recognise. cs_cloud() takes the
region as an optional argument for that, defaulting to the current one, so every
existing call site is unchanged. The retry dumps headers to a separate file so
the existing region-hint block still reads the first response, and the payload
stays on stdin rather than in argv. cs_cloud() failing is checked rather than
assumed to end the script, because exiting a nested command substitution does
not stop the caller under bash.

Windows PowerShell 5.1 returns a 3xx rather than throwing it, so
ConvertFrom-Json received the redirect body as a Byte[] and the catch reported
an unhandled error. Invoke-FalconAuth now handles the redirect on the success
path as well as in the catch, and reads X-Cs-Region through a helper, because
5.1 gives a WebHeaderCollection with only a string indexer while PowerShell 7
gives HttpResponseHeaders, where that indexer returns empty.

* fix(container-pull): drop the dead redirect pin from fetch_tags

fetch_tags lost -L, so --proto-redir has nothing to act on. Leaving it there
reads as though redirects were considered and handled on that call, which is
misleading in a change about redirects.

Measured with a pass-through curl wrapper over the real --list-tags path: the
registry token request and the tags list both answer 200 in a single hop, as do
the ccid and image-registry-credentials calls, so nothing on that path wants a
redirect. That was against registry.crowdstrike.com; the gov registries were not
reachable from the test environment.

The convention is now uniform across all four scripts: --proto-redir appears on
exactly the curl invocations that pass -L.

* docs(powershell): correct the header-collection note in Get-FalconRegionHeader

The note claimed the success path yields "a Dictionary, so ContainsKey". That was
an assumption. Measured on Windows PowerShell 5.1.26100.9168: Invoke-WebRequest
returns a body-less 3xx as Microsoft.PowerShell.Commands.WebResponseObject, whose
Content is a Byte[] — which is precisely why the old code failed inside
ConvertFrom-Json before it could reach the region logic.

The note now distinguishes what was measured on each platform and path from what
rests on documentation, and no longer names a type nothing verified.

Comment only; no behaviour change. The functional test still passes on
PowerShell 7.6.5 and on 5.1.

* chore: trim verbose comments in oauth redirect hardening

---------

Co-authored-by: Carlos Matos <carlosmmatos@users.noreply.github.com>
Co-authored-by: Carlos Matos <carlos.matos@crowdstrike.com>
carlosmmatos-cs added a commit that referenced this pull request Sep 10, 2026
Bumps the version string from 1.13.0 to 1.14.0 across all scripts and READMEs
ahead of the v1.14.0 release. This is a minor bump because of the new opt-in
FALCON_DEBUG mode added to the bash and PowerShell scripts (#522), which ships
alongside four fixes: the credential protections in the deployment scripts
(#520), the OAuth and fetch_tags redirect hardening that keeps region
auto-discovery (#521), the curl_command fix that stops the bearer token
crossing a redirect (#525), and the handle_curl_error path under sh (#526).

Updates the VERSION and $ScriptVersion constants in the bash and PowerShell
scripts, the Version usage lines in the READMEs, the pinned
raw.githubusercontent.com URLs, and the FALCON_DEBUG sample output.
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