Affected Platforms: Amazon Web Services (AWS)
Impacted Users: Any organization
Impact: Stolen cloud credentials result in significant financial losses and data breaches of sensitive information.
Severity Level: High
Organizations with modern CI/CD pipelines face threats from the Shai Hulud supply chain campaign, a software worm attributed to TeamPCP that has been targeting npm and PyPI packages since late 2025. Named after the giant sandworms in Dune, Shai Hulud injects malicious packages that execute during installs or CI jobs, harvesting build credentials to move into cloud infrastructure.
Organizations running modern CI/CD are learning a critical lesson from the Shai Hulud supply chain campaign: a poisoned build dependency doesn't stop at the pipeline—it becomes a bridge into the production cloud.
In May 2026, FortiCNAPP helped customers identify AWS estates affected by the worm. By mid-May, investigators found evidence of ongoing access to a Jenkins runner that matched Shai Hulud’s credential-harvesting pattern. They showed that an operator had exploited the Jenkins role over the internet, escalated privileges to full cloud admin, changed database network controls, and extracted data from Amazon Redshift. They also staged activities with object storage and email.
For leaders, the key takeaway is operational: pipeline identity equals production identity. Practitioners have a rich evidence trail if they know where to look. This blog details CloudTrail and host signals, showing how FortiCNAPP detected an intrusion with the alert “Potentially Compromised Keys,” helping analysts reconstruct host and cloud timelines.
There was a sharp increase in detected instances of the Shai Hulud worm during the “Mini Shai Hulud” wave in April–May 2026. Previously observed incidents described a repeatable model:
CI/CD runners become a vector for quickly expanding the cloud attack surface. Jenkins, GitHub Actions, and self-hosted agents routinely hold instance profiles, long-lived secrets, and network access to data tiers—exactly what Shai Hulud was built to collect.
| Phase | Approximate Date | What Happened |
| Supply chain exposure | Early May 2026 | Environment affected by aggressive Shai Hulud activity |
| Suspected CI persistence | Through mid-May 2026 | Possible residual access on Jenkins / build infrastructure |
| Cloud intrusion & exfiltration | Mid-May | External use of Jenkins instance role; IAM escalation; RDS/Redshift targeting and data exfiltration |
Table 1. Linking Shai Hulud to the Jenkins runner
Although there's no definitive proof that the worm infection caused the cloud compromise, the suspected link is strongly supported.
Host-level forensics and analysis to trace the complete attack path, from a specific malicious package artifact to the mid-May session, remain an ongoing area of investigation.
Figure 1, below, provides an overview of the attack and identity compromise that enable lateral movement within cloud infrastructure:
The following stages describe the cloud-based activities that took place after credential theft and were identified by FortiCNAPP Alerting:
| MITRE Tactic | Representative Techniques | What We Observed |
| Initial Access | T1195.002 (Supply chain compromise) | Prior Shai Hulud exposure; suspected Jenkins persistence |
| Valid Accounts | T1078.004 (Cloud accounts) | Jenkins instance role used from external IPs |
| Privilege Escalation | T1098 (Account manipulation) | IAM user cloudops-monitor with admin policy |
| Discovery | T1580, T1526 (Cloud infrastructure / IAM) | S3, VPC, RDS, Redshift, Secrets enumeration |
| Persistence | T1578 (Modify cloud compute infra) | Security groups, EC2, cluster SG attachment |
| Credential Access | T1552.005 (Cloud instance metadata), T1555 | IMDS curl on runner; Secrets Manager; GetClusterCredentials |
| Collection | T1530 (Data from cloud storage) | Redshift Data API ExecuteStatement (high volume) |
| Exfiltration | T1537 (Transfer to cloud account) | S3 policy staging; AssumeRole sessions named exfil* |
| Impact | T1496 (Resource hijacking) | SES identity verification staging |
Table 2. Mapping MITRE tactics to observed malware behavior
The earliest high-confidence cloud signal was the use of the Jenkins instance profile outside AWS, detected by FortiCNAPP as external instance-credential abuse.
On the Jenkins host, investigators observed access to the instance metadata service (IMDS) consistent with credential theft:
curl -s --connect-timeout 3 hxxp://169[.]254[.]169[.]254/latest/meta-data/iam/security-credentials/
curl -s hxxp://169[.]254[.]169[.]254/latest/meta-data/iam/security-credentials/jenkins
While that explained how the role credentials could be removed from the instance, the more challenging question is who managed them afterward?
A key piece of corroboration is network telemetry from the build environment, showing an unusual outbound connection to 89[.]22[.]231[.]63 on port 8080 during the investigation period. This same IP address was later observed as the source of the cloudops-monitor identity and of subsequent privileged escalation activities. The reuse of this IP tightly links host activity on the runner to operator infrastructure in the cloud logs, and the correlation detected by FortiCNAPP proved to be a much stronger indicator than any single detection signal.
| IP | Role in incident |
| 185[.]204[.]1[.]225 | First observed abuse of Jenkins role (recon, IAM escalation) |
| 89[.]22[.]231[.]63 | Primary operator IP after escalation; host outbound :8080 |
After obtaining temporary instance credentials from the compromised EC2, initial cloud authentication was detected through GetCallerIdentity and ListBuckets using the Jenkins assumed-role session. FortiCNAPP flagged the credentials being used from a non-instance IP, which should trigger immediate containment measures like disabling or restricting the instance profile and isolating the runner.
User-agent strings in these calls form two groups (see Indicators): one linked to a Fedora/Qubes AWS CLI build from the first IP, and another associated with an Ubuntu 26 AWS CLI build that accounts for most of the later activity. This pattern suggests either multiple operators or a change in the toolchain during the intrusion.
While still authenticated as Jenkins, the actor:
FortiCNAPP mapped CreateUser, AttachUserPolicy, and CreateAccessKey to Privilege Escalation (TA0004) with T1078 and T1098.
After 06:35 UTC, the bulk of activity runs as cloudops-monitor from 89[.]22[.]231[.]63.
FortiCNAPP impossible travel and sensitive infrastructure discovery observations fired on these APIs.
The actor executed hundreds of paginated ListSecrets calls, followed by GetSecretValue requests for secret names clearly linked to regional Redshift warehouses and reporting pipelines. These names included patterns like datawarehouse_redshift_* and truedata_reporting_redshift_*.
FortiCNAPP raised “Suspicious Enumeration of Cloud Secrets” and Credential Access (TA0006) on cloudops-monitor.
Secrets Manager provides the quickest access to connection strings for administrators with IAM rights but restricted network access, matching the description of this phase.
After network and secret access, the threat actor:
CloudTrail redacts SQL in request parameters, so individual queries are not visible during log-only review. The sustained interactive volume supports assessment of data collection and exfiltration rather than a one-off health check—consistent with leadership’s reporting of a confirmed warehouse breach.
FortiCNAPP Collection (TA0009) and Impact (TA0040) observations align with this phase.
Inline S3 policies are linked to an existing analytics role. The threat actor employed PutRolePolicy to attach specifically scoped S3 data-lake permissions to a role used for analytics workloads.
| Policy name | Permissions | Intent (Assessed) |
| exfil-s3-write | PutObject, GetObject, ListBucket on a production data bucket | Stage writes |
| exfil-s3-full | Above plus DeleteObject | Full object lifecycle control |
Policy names like exfil-s3-* are rare gifts to threat hunters—and warnings to teams that rely on naming conventions without content inspection.
AssumeRole with session name exfil
A separate emergency IAM user (not the attacker-created monitor account) eventually converts AssumeRole into a highly privileged break-glass role using roleSessionName: exfil after multiple denials. Following this, ListRoles and SendCommand activities were performed under sessions named exfil10 and exfil12.
SSM SendCommand
During those sessions, the actor ran SendCommand with the AWS-RunShellScript document on a managed EC2 instance (parameters are redacted in the logs). Successful invocations show in-VPC execution when external CLI access is limited, which is typical for tasks such as tooling, archiving, or transferring data to egress points.
Late in the window, cloudops-monitor and associated principals executed GetSendQuota, GetIdentityVerificationAttributes, VerifyEmailIdentity, and ListIdentities. Multiple corporate mailbox addresses were submitted for verification, but they are omitted here per publication policy.
Some SES calls originated from an in-account EC2 instance rather than from the external operator IP—suggesting a shift to execution within the victim VPC while control-plane abuse continued.
SES preparation often precedes phishing, extortion, or damage to domain reputation, so it's important to monitor even if data exfiltration occurred elsewhere.
FortiCNAPP consumed CloudTrail and identity context, producing 1,095 observations within the incident window. The customer-facing incident Potentially Compromised Keys provided the narrative practitioners need:
| Observation Category | Count (approx.) | Why it Mattered |
| Impossible travel | 202 | Operator/API geography inconsistent with baseline |
| MITRE Discovery | 170 | S3, RDS, Redshift, IAM recon |
| Sensitive infrastructure discovery | 128 | Cluster and security-group mutations |
| New API for principal | 120 | First-time APIs on compromised identities |
| Persistence | 62 | SG and cluster modifications |
| Privilege escalation | 52 | User creation, inline policies |
| Outside connections | 49 | External IPs on cloud identities |
| Credential access | 41 | Secrets Manager, GetClusterCredentials |
| External instance credential use | 20 | Jenkins role from non-AWS IP |
Table 3. Potentially compromised activities detected
Representative observation text seen in the export:
The platform’s value lies in correlating events under a single alert title: Jenkins role → cloudops-monitor → datastore APIs → exfil session naming, eliminating the need for analysts to manually connect over 1,300 CloudTrail events spanning six hours. Notably, identifying the use of external instance credentials is crucial for linking host-based and cloud-based threats. Mapping these threats from the host to the cloud is a complex, manual process that FortiCNAPP automates effectively.
The malware described in this report are detected and blocked by FortiGuard Antivirus as:
JS/Agent.B067!tr
JS/Agent.3A86!tr
The FortiGuard AntiVirus service engine is integrated into FortiGate, FortiMail, FortiClient, and FortiEDR. Customers running these products with up-to-date signatures are protected against the malware components described in this report.
The FortiGuard Web Filtering Service blocks the C2 server.
Organizations seeking to strengthen foundational security awareness may also consider completing Fortinet Certified Fundamentals (FCF) training in Cybersecurity. This module is designed to help end users learn to identify and protect themselves from phishing attacks.
The FortiGuard IP Reputation and Anti-Botnet Security Service proactively blocks infrastructure associated with this campaign by correlating malicious IP intelligence collected from Fortinet’s global sensor network, CERT collaborations, MITRE, trusted industry partners, and other intelligence sources.
If you believe this or any other cybersecurity threat has impacted your organization, contact our Global FortiGuard Incident Response Team for assistance.
Validate these in your environment before blocking them. Note that user agents evolve.
| IP | Context |
| 185[.]204[.]1[.]225 | Initial external abuse of Jenkins instance role |
| 89[.]22[.]231[.]63 | Primary post-escalation operator IP; host outbound connection on port 8080 |
| Type | Value |
| IAM user | cloudops-monitor |
| Session name | Context |
| exfil | Assumed break-glass / super-admin role |
| exfil10, exfil12 | Sessions used for SSM SendCommand and follow-on activity |
| Policy name | Context |
| exfil-s3-write | S3 write/list to production data bucket |
| exfil-s3-write | Adds s3:DeleteObject on same bucket |
Ubuntu operator toolchain (dominant, IP 89[.]22[.]231[.]63):
aws-cli/2.31.35 md/awscrt#1.0.0.dev0 ua/2.1 os/linux#7.0.0-15-generic md/arch#x86_64 lang/python#3.14.4 md/pyimpl#CPython ... md/distrib#ubuntu.26
Boto3/1.40.72 md/Botocore#1.40.72 ... os/linux#7.0.0-15-generic ... lang/python#3.14.4
Fedora/Qubes toolchain (early Jenkins-role abuse, IP 185[.]204[.]1[.]225):
aws-cli/2.34.29 md/awscrt#0.31.2 ua/2.1 os/linux#6.18.15-1.qubes.fc41.x86_64 md/arch#x86_64 lang/python#3.14.3 md/pyimpl#CPython ... md/distrib#fedora.42