In early August, GitGuardian researchers reported that a recent Shai-Hulud infostealer worm variant expanded its credential collection logic from 189 paths to 469 locations. The target set spans developer workstations, CI/CD tooling, cloud configuration files, and AI tool configs. That matters because modern intrusions increasingly start with a stolen token, not an exploited server.
This is a defense post, so the operating assumption is simple: if a developer endpoint, build agent, runner, or automation account was exposed to a Shai-Hulud-like execution chain, treat locally stored secrets as compromised until proven otherwise. The cited reporting does not provide a CVE, CVSS score, or CISA KEV entry. Do not wait for one. The risk is credential theft at scale, followed by package publishing abuse, cloud API access, source repo access, and persistence through automation identities.
Technical Analysis
What changed
The reported jump from 189 to 469 credential locations is operationally significant. It suggests the operators are prioritizing breadth over elegance: enumerate every place engineers and pipelines tend to leave usable authentication material, then exfiltrate before traditional AV or EDR teams catch up.
The affected environment is not a single product or version. It is the developer-to-cloud control plane:
- Developer workstations and laptops with shell profiles, editor state, package manager configs, browser-adjacent tokens, SSH material, and local .env files.
- CI/CD systems and runners where pipeline variables, artifact registry credentials, deploy keys, service account files, and temporary cloud credentials are accessible to jobs.
- Cloud tooling configs for AWS, Azure, Google Cloud, Kubernetes, Terraform-adjacent workflows, container registries, and CLI profiles.
- AI tool configs, which increasingly store API keys for model providers, coding assistants, agents, MCP servers, and SaaS integrations.
No CVE identifier appears in the source summary, and none should be invented. The defensive issue is technique coverage: unauthorized discovery, collection, and exfiltration of credentials from predictable locations.
Defender view of the attack chain
A typical chain looks like this:
- Execution reaches a developer context through a malicious package, trojanized dependency, poisoned build step, compromised extension, or local payload.
- The malware enumerates high-value files and environment variables rather than relying on one hardcoded path.
- It reads plaintext or weakly protected secrets: cloud credentials, kubeconfigs, npm tokens, git credentials, Docker client config, SSH private keys, .env files, and CI variables exposed to the process.
- It stages data in temp or user-writable paths, compresses or encodes it, then sends it outbound over common web ports to blend into normal dev traffic.
- Stolen tokens are replayed from attacker infrastructure against source control, package registries, cloud APIs, artifact stores, and AI provider consoles.
The important point for detection teams: the loudest signal is often not malware execution. It is an unusual process reading many credential-like files in a short window, or a non-interactive workload suddenly touching secrets outside its job scope.
Exploitation status
The news item describes an observed variant evolution reported by GitGuardian in early August, not a theoretical proof of concept. Treat it as actively relevant. There is no confirmed CISA KEV listing or CVE in the cited material. That does not reduce urgency; credential theft tooling often succeeds without needing a memory corruption bug or a patchable vulnerability.
Detection & Response
Hunting posture
Prioritize hunts on endpoints and CI runners with these properties:
- Short-burst access to multiple secret-bearing paths by shells, scripting runtimes, package managers, or unknown binaries.
- Processes launched by npm, yarn, pnpm, node, python, bash, sh, zsh, pwsh, or build agents that read .env, SSH keys, cloud credentials, kubeconfig, Docker config, or AI provider keys.
- Outbound HTTPS from build runners or developer machines to rare domains soon after credential-path access.
- Authentication events from new geographies, autonomous systems, or user agents immediately after a suspected exposure window.
Tune aggressively for developer environments. A rule that fires every time an engineer opens .env in an editor will be disabled by Friday. Focus on multi-path access, non-interactive processes, suspicious parent-child chains, and correlation with egress.
---
title: Multi-Path Credential File Discovery by Script or Shell Process
id: 9d6f2d42-6f3e-4b68-a41c-8c7aaab12e91
status: experimental
description: Detects shell or script processes accessing multiple common credential locations in one command line, consistent with infostealer-style enumeration across developer, CI/CD, cloud, and AI tooling paths.
references:
- https://attack.mitre.org/techniques/T1552/
- https://attack.mitre.org/techniques/T1552/001/
author: Security Arsenal
date: 2026/09/02
tags:
- attack.credential_access
- attack.t1552
- attack.t1552.001
logsource:
category: process_creation
product: windows
detection:
selection_image:
Image|endswith:
- '\cmd.exe'
- '\powershell.exe'
- '\pwsh.exe'
- '\node.exe'
- '\python.exe'
- '\bash.exe'
- '\sh.exe'
selection_secret_paths:
CommandLine|contains:
- '.env'
- '.aws/credentials'
- '.azure'
- 'credentials.db'
- '.kube/config'
- '.docker/config.json'
- '.npmrc'
- '.pypirc'
- 'id_rsa'
- 'id_ed25519'
- 'git-credentials'
- 'terraform.tfvars'
- 'config.json'
selection_multi:
CommandLine|contains:
- 'cat '
- 'type '
- 'findstr '
- 'Get-Content'
- 'select-string'
- 'copy '
- 'xcopy '
condition: selection_image and selection_secret_paths and selection_multi
falsepositives:
- Legitimate developer setup scripts and troubleshooting
- CI jobs that intentionally read a single sanctioned secret path
level: high
---
title: Package Manager Lifecycle Script Accessing Secret and Cloud Config Paths
id: 2d8b6a1f-5f0b-4c88-9e77-0f90d5a3d210
status: experimental
description: Detects npm, yarn, pnpm, pip, or similar lifecycle activity spawning shells or script runtimes that reference credential stores, cloud configs, Kubernetes config, SSH keys, package registry tokens, or AI API key files.
references:
- https://attack.mitre.org/techniques/T1195/
- https://attack.mitre.org/techniques/T1195/002/
- https://attack.mitre.org/techniques/T1552/
author: Security Arsenal
date: 2026/09/02
tags:
- attack.initial_access
- attack.credential_access
- attack.t1195.002
- attack.t1552
logsource:
category: process_creation
product: windows
detection:
selection_parent:
ParentImage|endswith:
- '\npm.exe'
- '\yarn.exe'
- '\pnpm.exe'
- '\pip.exe'
- '\python.exe'
- '\node.exe'
- '\bash.exe'
- '\sh.exe'
selection_child:
Image|endswith:
- '\cmd.exe'
- '\powershell.exe'
- '\pwsh.exe'
- '\node.exe'
- '\python.exe'
- '\curl.exe'
- '\wget.exe'
- '\tar.exe'
- '\7z.exe'
- '\rar.exe'
selection_targets:
CommandLine|contains:
- '.env'
- '.aws/'
- '.azure/'
- '.config/gcloud'
- '.kube/config'
- '.docker/config.json'
- '.npmrc'
- '.pypirc'
- 'id_rsa'
- 'id_ed25519'
- 'OPENAI_API_KEY'
- 'ANTHROPIC_API_KEY'
- 'GITHUB_TOKEN'
- 'CI_JOB_TOKEN'
condition: selection_parent and selection_child and selection_targets
falsepositives:
- Build systems that legitimately inject scoped credentials
- Developer onboarding scripts
level: critical
// Hunt processes touching multiple credential artifacts in a short window
let secretTokens = dynamic(['.env','.aws/credentials','.azure','.config/gcloud','.kube/config','.docker/config.json','.npmrc','.pypirc','id_rsa','id_ed25519','git-credentials','terraform.tfvars','OPENAI_API_KEY','ANTHROPIC_API_KEY','GITHUB_TOKEN','CI_JOB_TOKEN']);
DeviceProcessEvents
| where TimeGenerated > ago(7d)
| where ProcessCommandLine has_any (secretTokens)
| extend SecretHits = array_length(set_union(extract_all(@'\.env|\.aws/credentials|\.azure|\.config/gcloud|\.kube/config|\.docker/config\.json|\.npmrc|\.pypirc|id_rsa|id_ed25519|git-credentials|terraform\.tfvars|OPENAI_API_KEY|ANTHROPIC_API_KEY|GITHUB_TOKEN|CI_JOB_TOKEN', ProcessCommandLine), dynamic([])))
| extend IsScriptOrShell = FileName in~ ('cmd.exe','powershell.exe','pwsh.exe','node.exe','python.exe','bash','sh','zsh','npm','yarn','pnpm','pip','curl','wget')
| where IsScriptOrShell or ProcessCommandLine has_any ('cat ','type ','findstr ','Get-Content','Select-String','curl ','wget ')
| summarize FirstSeen=min(TimeGenerated), LastSeen=max(TimeGenerated), Hits=count(), DistinctSecrets=dcount(SecretHits), Commands=make_set(ProcessCommandLine, 8) by DeviceName, AccountName, InitiatingProcessFileName, FileName, ProcessId, InitiatingProcessId
| where Hits >= 2 or DistinctSecrets >= 2 or Commands has_any ('.aws/credentials','.kube/config','id_rsa','GITHUB_TOKEN')
| sort by LastSeen desc
-- Velociraptor hunt: processes referencing credential stores and recently touched secret files
LET proc_hits = SELECT Pid, Name, CommandLine, Exe, Username, CreateTime
FROM pslist()
WHERE CommandLine =~ '(?i)(\.env|aws/credentials|\.azure|gcloud|kube/config|docker/config\.json|\.npmrc|\.pypirc|id_rsa|id_ed25519|git-credentials|OPENAI_API_KEY|ANTHROPIC_API_KEY|GITHUB_TOKEN|CI_JOB_TOKEN)'
LET secret_files = SELECT FullPath, Size, Mtime, Atime
FROM glob(globs=['/home/*/.aws/credentials','/home/*/.azure/**','/home/*/.config/gcloud/**','/home/*/.kube/config','/home/*/.docker/config.json','/home/*/.npmrc','/home/*/.pypirc','/home/*/.ssh/id_*','/root/.aws/credentials','/root/.kube/config','/tmp/**/.env'], accessor='file')
WHERE Mtime > now() - 604800
SELECT * FROM proc_hits
UNION ALL
SELECT NULL AS Pid, 'file_artifact' AS Name, FullPath AS CommandLine, '' AS Exe, '' AS Username, Mtime AS CreateTime FROM secret_files
#!/usr/bin/env bash
# Audit Linux or macOS developer hosts and CI runners for exposed secret files.
# It reports metadata only. It does not print secret values.
set -euo pipefail
OUT="${1:-secret-exposure-audit-$(date +%Y%m%dT%H%M%SZ).csv}"
ROOTS="${HOME} /home /root /opt/actions-runner /tmp /var/lib/github-runner /builds"
echo 'path,mode,owner,group,size_bytes,mtime,issue' > "$OUT"
is_bad_mode() {
local mode="$1"
# Flag world-readable or group-writable private keys and credential stores.
[[ "$mode" =~ .[2367]$ ]] && return 0
[[ "$mode" =~ ..[2367]$ ]] && return 0
return 1
}
scan_path() {
local p="$1" issue=""
[[ -e "$p" ]] || return 0
mode=$(stat -c '%a' "$p" 2>/dev/null || stat -f '%Lp' "$p")
owner=$(stat -c '%U' "$p" 2>/dev/null || stat -f '%Su' "$p")
group=$(stat -c '%G' "$p" 2>/dev/null || stat -f '%Sg' "$p")
size=$(stat -c '%s' "$p" 2>/dev/null || stat -f '%z' "$p")
mtime=$(stat -c '%y' "$p" 2>/dev/null || stat -f '%Sm' "$p")
case "$p" in
*id_rsa|*id_ed25519|*.pem|*credentials|*config.json|*.env*|*.npmrc|*.pypirc|*kube/config*|*git-credentials*|*terraform.tfvars*)
if is_bad_mode "$mode"; then issue='weak_permissions'; else issue='present_review_required'; fi
;;
*) issue='present_review_required' ;;
esac
printf '%s,%s,%s,%s,%s,%s,%s
' "$p" "$mode" "$owner" "$group" "$size" "$mtime" "$issue" >> "$OUT"
}
for root in $ROOTS; do
[[ -d "$root" ]] || continue
while IFS= read -r -d '' f; do scan_path "$f"; done < <(find "$root" \( -name '.env' -o -name '.env.*' -o -name 'credentials' -o -name 'config.json' -o -name '.npmrc' -o -name '.pypirc' -o -name 'id_rsa' -o -name 'id_ed25519' -o -name 'git-credentials' -o -name 'terraform.tfvars' -o -path '*/.aws/*' -o -path '*/.azure/*' -o -path '*/.config/gcloud/*' -o -path '*/.kube/config' -o -path '*/.docker/config.json' -o -path '*/.ssh/*' \) -type f -print0 2>/dev/null)
done
echo "Wrote metadata report: $OUT"
echo 'Immediate actions: chmod 600 private keys and cloud credential files, remove secrets from repos, rotate tokens with unknown exposure, and move CI to OIDC short-lived credentials where possible.'
Response Playbook
Treat suspected contact as a secret exposure event, not only a malware event.
- Isolate the affected endpoint, runner, or job workspace. Preserve process trees, command lines, package metadata, DNS and proxy logs, cloud audit logs, source control audit logs, package registry events, and AI provider usage logs.
- Build an exposure inventory. Enumerate every credential reachable by the user, service principal, runner identity, node process, container, and pipeline variable scope. Do not limit scope to files with obvious names.
- Rotate in the right order. Revoke active sessions first, then rotate tokens and keys, then invalidate caches and downstream dependents. For cloud, revoke sessions and rotate access keys, refresh OIDC trust assumptions, and review role assumption chains. For source control and registries, rotate PATs, deploy keys, npm tokens, webhook secrets, and signing-adjacent credentials.
- Search for replay. Look for new IPs, impossible travel, unusual user agents, API calls outside working hours, package publish events, dependency confusion signals, repository permission changes, new OAuth grants, and unexpected AI API spend.
- Clean persistence. Remove malicious packages, lifecycle scripts, launch agents, scheduled tasks, shell profile modifications, rogue SSH authorized keys, newly created runner registrations, and untrusted browser or IDE extensions.
- Reimage high-confidence compromised runners and developer systems. Credential stealers are good at leaving behind second-order access paths; do not rely on deleting one binary.
- Notify owners of leaked secrets and downstream consumers. If packages or artifacts were published during the exposure window, assume supply-chain impact and begin dependent-notification workflows.
Remediation and Hardening
There is no vendor patch or fixed version to cite from the source item, and no CVE should be fabricated. The remediation is architectural and operational.
- Eliminate long-lived secrets from developer and CI contexts. Prefer OIDC-based short-lived cloud credentials, workload identity federation, ephemeral runners, and just-in-time repository access.
- Move secrets out of files and environment sprawl into a managed vault with explicit identity binding, audit, rotation, and tight read scope.
- Constrain package execution. Disable install scripts where feasible, require lockfile integrity, pin registries, use private mirrors, verify provenance or signatures where supported, and block unexpected lifecycle scripts in CI.
- Enforce egress policy on runners and endpoints. Deny direct internet except approved registries, artifact stores, source control, cloud APIs, and model providers. Alert on new domains following secret-path access.
- Reduce token blast radius. Use fine-grained tokens, repository-scoped deploy keys, short TTLs, separate read and publish identities, and no production cloud rights from routine build jobs.
- Protect AI credentials like production cloud keys. Inventory assistant, agent, MCP, and model API keys. Set spend limits, scope project access, log usage, and revoke embedded keys in local configs.
- Continuously scan for leaked secrets before and after commit. Pair pre-commit controls with server-side scanning, historical repo scanning, CI variable audits, and canary credentials for high-fidelity tripwires.
- Monitor identity replay. Correlate secret access telemetry with cloud audit trails, Git provider logs, registry publish events, SSO anomalies, and AI provider billing or API anomalies.
- Exercise the playbook. Run a tabletop where a runner token and a developer kubeconfig are exposed simultaneously. Time-to-revoke is the metric that matters.
Executive Takeaways
The 469-location figure is a warning about attacker economics: credential harvesting from predictable engineering paths is cheap, fast, and often more reliable than exploiting a patched vulnerability. Security leaders should fund secret elimination, ephemeral CI identity, package execution controls, egress restrictions, and rapid revocation capability. Measure coverage by how many credentials are not stored locally, how quickly active sessions can be killed, and whether detection connects file access to identity replay.
Related Resources
Security Arsenal Red Team Services AlertMonitor Platform Book a SOC Assessment pen-testing Intel Hub
Is your security operations ready?
Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.