[{"data":1,"prerenderedAt":705},["ShallowReactive",2],{"/en-us/blog/how-we-prevented-security-fixes-leaking-into-our-public-repositories/":3,"navigation-en-us":35,"banner-en-us":452,"footer-en-us":467,"Robert Speicher":678,"next-steps-en-us":690},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":25,"_id":28,"_type":29,"title":30,"_source":31,"_file":32,"_stem":33,"_extension":34},"/en-us/blog/how-we-prevented-security-fixes-leaking-into-our-public-repositories","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"How we prevented security fixes leaking into our public repositories","Working in the open makes it difficult to work on security vulnerabilities before they're disclosed, especially when that openness discloses them early!","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667227/Blog/Hero%20Images/security-leaks-unlocked.jpg","https://about.gitlab.com/blog/how-we-prevented-security-fixes-leaking-into-our-public-repositories","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How we prevented security fixes leaking into our public repositories\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Robert Speicher\"}],\n        \"datePublished\": \"2021-01-04\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":19,"body":20,"category":21,"tags":22},[18],"Robert Speicher","2021-01-04","One of GitLab's core values is \"[public by default][],\" which means we develop in\nthe open whenever possible. One notable exception to this is security fixes,\nbecause developing security fixes in public discloses vulnerabilities before a\nfix is available, exposing ourselves and our users to attacks.\n\nIn order to work on these security issues in private, public GitLab projects\nhave a security mirror that's accessible only to GitLab engineers. A design flaw in GitLab's mirroring feature would cause commits from the\nSecurity repository to be exposed in the public repository before they were\nintended for release.\n\nIn this post we'll describe what the problem was and how we finally resolved it.\n\n[public by default]: https://handbook.gitlab.com/handbook/values/#public-by-default\n\n## Mirroring setup\n\nTo ensure that developers working on a security fix are working against the\nlatest code for a project, we utilize GitLab's [push mirror](https://docs.gitlab.com/ee/user/project/repository/mirror/index.html) feature to mirror\nthe public (\"Canonical\") repository to its private Security fork.\n\nOn every commit to the Canonical repository, the Security repository receives\nthe same commit. All of the mirroring is performed by the [Gitaly][gitaly]\nserver, which handles all of the Git calls made by GitLab.\n\nIn order to know which Git objects in the source are missing on the destination,\nGitLab would [fetch the remote][] and then tell Gitaly to perform the push that\nwould bring the two in sync, which is where the trouble starts.\n\nBy performing a fetch, _every Git object in the Security repository was now\nknown and stored on-disk by the Canonical repository_. If someone knew the SHA\nof a commit in the _private_ repository that contained a security fix, they\ncould view it in the _public_ repository and discover the vulnerability we were\nfixing before it had been publicly disclosed.\n\n[push mirror]: https://docs.gitlab.com/ee/user/project/repository/repository_mirroring.html\n[gitaly]: https://gitlab.com/gitlab-org/gitaly\n[fetch the remote]: https://gitlab.com/gitlab-org/gitlab/blob/f5bfe5603137b8f9cf60a2db759db3dbe5c60727/app/services/projects/update_remote_mirror_service.rb#L30\n\n## No guessing necessary\n\nThankfully, even a truncated Git commit SHA is difficult to guess, so at first\nglance this might not look like a high-severity issue.\n\nHowever, the [GitLab help page](https://gitlab.com/help) shows exactly which\ncommit is currently running, and we always deploy security fixes to GitLab.com\nfor verification and to protect our users against the latest threats. Here's\nwhat that might look like:\n\n> ### GitLab Enterprise Edition 13.7.0-pre [690e4bbfe94][]\n\nWhen a security release was in progress, any logged-in user could click on the\nrunning commit SHA and view the entire [source code](/solutions/source-code-management/) tree at that point, security\nfixes included!\n\n[690e4bbfe94]: https://gitlab.com/gitlab-org/gitlab/-/commits/690e4bbfe94\n\n## Experimenting with a fix\n\nThe mirroring setup was a crucial part of our development and release process,\nand the existing fetch-based behavior was itself a crucial piece of what made\nthe mirroring functionality work. During our initial investigation, there was no\nobvious fix. One proposed workaround was to simply remove the SHA from the Help\npage, but that would only hide the problem and \"security through obscurity\"\nisn't really security at all.\n\nAnother workaround, which we [ended up implementing][mirror pause], was to\npause the mirroring as soon as a security fix was merged, and re-enable it\nonce the security release was published. This prevented the leak because the\nfetch was no longer happening, but it would \"stop the world\" while we worked\non a security release. The Security mirror quickly fell behind public\ndevelopment, which created a risk of new features causing merge conflicts\nwith the security fixes, or vice versa.\n\nStaff engineer [Jacob Vosmaer][], who began the Gitaly project within GitLab,\n[pointed out][] that, strangely, we only used this fetch-based behavior for\nbranches; tags used Git's low-level [`ls-remote` command][ls-remote].\n\nWhereas Git's `fetch` command creates a local copy of every object from the\nremote repository, the `ls-remote` command only prints the remote's available\nreferences to the terminal. If we used `ls-remote` for branches like we did for tags, the commits from\nthe mirror would no longer be persisted on-disk, and thus wouldn't be\navailable in the public repository.\n\nBecause push mirroring is such a critical part of our own workflow as well as\nour users', we didn't want to just make the change and hope for the best. We\n[set up an experiment][], where the old functionality stayed exactly as it was,\nbut when a [feature flag][] was enabled, we'd also gather the same commit\ninformation using `ls-remote`, and compare the new results to the original,\nlogging any differences.\n\nThe experiment ran on GitLab.com for about a month without major discrepancies.\nIt looked like we had a solution!\n\n[mirror pause]: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/626\n[Jacob Vosmaer]: /company/team/#jacobvosmaer-gitlab\n[pointed out]: https://gitlab.com/gitlab-org/gitlab/-/issues/38386#note_312363006\n[ls-remote]: https://git-scm.com/docs/git-ls-remote.html\n[set up an experiment]: https://gitlab.com/gitlab-org/gitaly/-/issues/2670\n[feature flag]: https://docs.gitlab.com/ee/operations/feature_flags.html\n\n## Iterating on the experiment\n\nConsidering the experiment a success, but still being wary of breaking a key\npiece of functionality, we proceeded with caution. Rather than replacing the old\nbehavior outright with the new, we [split the two paths based on a feature\nflag][split].\n\nWhen the flag was disabled the old, tried-and-true behavior would be used. With\nthe flag enabled, we'd use the new. We shipped this change and left the flag\nenabled, watching for errors.\n\nAfter two weeks without any reported mirroring errors, and with the security\nleak no longer occurring, we were satisfied we had found our fix.\n\nFirst we shipped a self-managed release [with the feature flag enabled by\ndefault][flag enabled], to ensure that if something unexpectedly broke for those\ninstallations it would be easy to revert to the previous behavior. Finally, after no errors reported from self-managed users, we [removed the\nfeature flag along with the old behavior][flag removal], and closed out the\nconfidential issue.\n\n[split]: https://gitlab.com/gitlab-org/gitaly/-/merge_requests/2183\n[flag enabled]: https://gitlab.com/gitlab-org/gitaly/-/merge_requests/2330\n[flag removal]: https://gitlab.com/gitlab-org/gitaly/-/merge_requests/2417\n\n## An annoying bug emerges\n\nShortly after making the new behavior the default, we started getting\n[complaints from team members][complaints]. They'd receive an automated email\ntelling them that a push mirror was broken, only to go check on the mirror and\nbe told everything was fine.\n\nThis went on for about two months due to the transient nature of the errors.\nEvery time we'd get an email and check to see if it was accurate, the mirroring\nreported everything was fine.\n\nAs we began to implement [a new piece of tooling][new tooling] that depended on\naccurate status reporting from push mirroring, the problem became bigger than a\nfew annoying, seemingly inaccurate emails; it was causing our tooling to behave\nerratically as well.\n\nBecause we had absolutely no idea what was happening or why, our first step was\nto [add logging][] when Gitaly was encountering an error that would mark the\nmirror as failed. The logging [revealed a weird anomaly][anomaly] where it\nappeared that the Security repository – the one _receiving_ updates – appeared\nto be _ahead_ of its source:\n\n```\nI, [2020-09-21T10:10:31] Divergent ref due to ancestry -- remote:f73bb2388a6, local:59812e04368\nI, [2020-09-21T10:26:39] Divergent ref due to ancestry -- remote:8ddcb3333da, local:f73bb2388a6\n```\n\nIn this pair, the first message is saying that the remote – the Security\nrepository – was showing its latest commit as `f73bb2388a6`, and that it wasn't\nan ancestor of the local `59812e04368` commit, causing the error message. On the\nnext run, we see that the local repository has \"caught up\" to the Security\nremote from the prior run.\n\nIt turned out that due to the number of branches and tags in this repository,\nthe `ls-remote` command was taking so long to complete that by the time the data\nwas returned, the local repository was updated by a new push.\n\nBecause we gathered the remote refs after the local ones, a network delay\ncreated a window for new local commits to be written and invalidate our list\nof local refs. Luckily there was a nice [boring solution][]: all we had to do\nwas [swap the order][] in which we gather references.\n\n[complaints]: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/914\n[new tooling]: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1111\n[add logging]: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/914#note_413855603\n[anomaly]: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/914#note_416246505\n[boring solution]: https://handbook.gitlab.com/handbook/values/#boring-solutions\n[swap the order]: https://gitlab.com/gitlab-org/gitaly/-/merge_requests/2606\n\n## Wrapping up\n\nAs soon as we swapped the order for gathering references, the transient errors\nwent away and we finally got to close this long-standing issue. We were pleased\nwith how we were able to modify such a critical piece of functionality safely\nand without any negative user impact.\n\n## Related issues\n\n- [Security commits available on GitLab.com](https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/426)\n- [Do not expose GitLab version on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/38386)\n- [Populate remote branches in-memory via `ls-remote` rather than using `fetch`](https://gitlab.com/gitlab-org/gitaly/-/issues/2670)\n- [Transient push mirror divergence errors](https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/914)\n\nPhoto by [iMattSmart](https://unsplash.com/@imattsmart?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/broken-lock?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)\n{: .note}\n","engineering",[23,24],"inside GitLab","security",{"slug":26,"featured":6,"template":27},"how-we-prevented-security-fixes-leaking-into-our-public-repositories","BlogPost","content:en-us:blog:how-we-prevented-security-fixes-leaking-into-our-public-repositories.yml","yaml","How We Prevented Security Fixes Leaking Into Our Public Repositories","content","en-us/blog/how-we-prevented-security-fixes-leaking-into-our-public-repositories.yml","en-us/blog/how-we-prevented-security-fixes-leaking-into-our-public-repositories","yml",{"_path":36,"_dir":37,"_draft":6,"_partial":6,"_locale":7,"data":38,"_id":448,"_type":29,"title":449,"_source":31,"_file":450,"_stem":451,"_extension":34},"/shared/en-us/main-navigation","en-us",{"logo":39,"freeTrial":44,"sales":49,"login":54,"items":59,"search":389,"minimal":420,"duo":439},{"config":40},{"href":41,"dataGaName":42,"dataGaLocation":43},"/","gitlab logo","header",{"text":45,"config":46},"Get free trial",{"href":47,"dataGaName":48,"dataGaLocation":43},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":50,"config":51},"Talk to sales",{"href":52,"dataGaName":53,"dataGaLocation":43},"/sales/","sales",{"text":55,"config":56},"Sign in",{"href":57,"dataGaName":58,"dataGaLocation":43},"https://gitlab.com/users/sign_in/","sign in",[60,104,200,205,310,370],{"text":61,"config":62,"cards":64,"footer":87},"Platform",{"dataNavLevelOne":63},"platform",[65,71,79],{"title":61,"description":66,"link":67},"The most comprehensive AI-powered DevSecOps Platform",{"text":68,"config":69},"Explore our Platform",{"href":70,"dataGaName":63,"dataGaLocation":43},"/platform/",{"title":72,"description":73,"link":74},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":75,"config":76},"Meet GitLab Duo",{"href":77,"dataGaName":78,"dataGaLocation":43},"/gitlab-duo/","gitlab duo ai",{"title":80,"description":81,"link":82},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":83,"config":84},"Learn more",{"href":85,"dataGaName":86,"dataGaLocation":43},"/why-gitlab/","why gitlab",{"title":88,"items":89},"Get started with",[90,95,100],{"text":91,"config":92},"Platform Engineering",{"href":93,"dataGaName":94,"dataGaLocation":43},"/solutions/platform-engineering/","platform engineering",{"text":96,"config":97},"Developer Experience",{"href":98,"dataGaName":99,"dataGaLocation":43},"/developer-experience/","Developer experience",{"text":101,"config":102},"MLOps",{"href":103,"dataGaName":101,"dataGaLocation":43},"/topics/devops/the-role-of-ai-in-devops/",{"text":105,"left":106,"config":107,"link":109,"lists":113,"footer":182},"Product",true,{"dataNavLevelOne":108},"solutions",{"text":110,"config":111},"View all Solutions",{"href":112,"dataGaName":108,"dataGaLocation":43},"/solutions/",[114,139,161],{"title":115,"description":116,"link":117,"items":122},"Automation","CI/CD and automation to accelerate deployment",{"config":118},{"icon":119,"href":120,"dataGaName":121,"dataGaLocation":43},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[123,127,131,135],{"text":124,"config":125},"CI/CD",{"href":126,"dataGaLocation":43,"dataGaName":124},"/solutions/continuous-integration/",{"text":128,"config":129},"AI-Assisted Development",{"href":77,"dataGaLocation":43,"dataGaName":130},"AI assisted development",{"text":132,"config":133},"Source Code Management",{"href":134,"dataGaLocation":43,"dataGaName":132},"/solutions/source-code-management/",{"text":136,"config":137},"Automated Software Delivery",{"href":120,"dataGaLocation":43,"dataGaName":138},"Automated software delivery",{"title":140,"description":141,"link":142,"items":147},"Security","Deliver code faster without compromising security",{"config":143},{"href":144,"dataGaName":145,"dataGaLocation":43,"icon":146},"/solutions/security-compliance/","security and compliance","ShieldCheckLight",[148,151,156],{"text":149,"config":150},"Security & Compliance",{"href":144,"dataGaLocation":43,"dataGaName":149},{"text":152,"config":153},"Software Supply Chain Security",{"href":154,"dataGaLocation":43,"dataGaName":155},"/solutions/supply-chain/","Software supply chain security",{"text":157,"config":158},"Compliance & Governance",{"href":159,"dataGaLocation":43,"dataGaName":160},"/solutions/continuous-software-compliance/","Compliance and governance",{"title":162,"link":163,"items":168},"Measurement",{"config":164},{"icon":165,"href":166,"dataGaName":167,"dataGaLocation":43},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[169,173,177],{"text":170,"config":171},"Visibility & Measurement",{"href":166,"dataGaLocation":43,"dataGaName":172},"Visibility and Measurement",{"text":174,"config":175},"Value Stream Management",{"href":176,"dataGaLocation":43,"dataGaName":174},"/solutions/value-stream-management/",{"text":178,"config":179},"Analytics & Insights",{"href":180,"dataGaLocation":43,"dataGaName":181},"/solutions/analytics-and-insights/","Analytics and insights",{"title":183,"items":184},"GitLab for",[185,190,195],{"text":186,"config":187},"Enterprise",{"href":188,"dataGaLocation":43,"dataGaName":189},"/enterprise/","enterprise",{"text":191,"config":192},"Small Business",{"href":193,"dataGaLocation":43,"dataGaName":194},"/small-business/","small business",{"text":196,"config":197},"Public Sector",{"href":198,"dataGaLocation":43,"dataGaName":199},"/solutions/public-sector/","public sector",{"text":201,"config":202},"Pricing",{"href":203,"dataGaName":204,"dataGaLocation":43,"dataNavLevelOne":204},"/pricing/","pricing",{"text":206,"config":207,"link":209,"lists":213,"feature":297},"Resources",{"dataNavLevelOne":208},"resources",{"text":210,"config":211},"View all resources",{"href":212,"dataGaName":208,"dataGaLocation":43},"/resources/",[214,247,269],{"title":215,"items":216},"Getting started",[217,222,227,232,237,242],{"text":218,"config":219},"Install",{"href":220,"dataGaName":221,"dataGaLocation":43},"/install/","install",{"text":223,"config":224},"Quick start guides",{"href":225,"dataGaName":226,"dataGaLocation":43},"/get-started/","quick setup checklists",{"text":228,"config":229},"Learn",{"href":230,"dataGaLocation":43,"dataGaName":231},"https://university.gitlab.com/","learn",{"text":233,"config":234},"Product documentation",{"href":235,"dataGaName":236,"dataGaLocation":43},"https://docs.gitlab.com/","product documentation",{"text":238,"config":239},"Best practice videos",{"href":240,"dataGaName":241,"dataGaLocation":43},"/getting-started-videos/","best practice videos",{"text":243,"config":244},"Integrations",{"href":245,"dataGaName":246,"dataGaLocation":43},"/integrations/","integrations",{"title":248,"items":249},"Discover",[250,255,259,264],{"text":251,"config":252},"Customer success stories",{"href":253,"dataGaName":254,"dataGaLocation":43},"/customers/","customer success stories",{"text":256,"config":257},"Blog",{"href":258,"dataGaName":5,"dataGaLocation":43},"/blog/",{"text":260,"config":261},"Remote",{"href":262,"dataGaName":263,"dataGaLocation":43},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":265,"config":266},"TeamOps",{"href":267,"dataGaName":268,"dataGaLocation":43},"/teamops/","teamops",{"title":270,"items":271},"Connect",[272,277,282,287,292],{"text":273,"config":274},"GitLab Services",{"href":275,"dataGaName":276,"dataGaLocation":43},"/services/","services",{"text":278,"config":279},"Community",{"href":280,"dataGaName":281,"dataGaLocation":43},"/community/","community",{"text":283,"config":284},"Forum",{"href":285,"dataGaName":286,"dataGaLocation":43},"https://forum.gitlab.com/","forum",{"text":288,"config":289},"Events",{"href":290,"dataGaName":291,"dataGaLocation":43},"/events/","events",{"text":293,"config":294},"Partners",{"href":295,"dataGaName":296,"dataGaLocation":43},"/partners/","partners",{"backgroundColor":298,"textColor":299,"text":300,"image":301,"link":305},"#2f2a6b","#fff","Insights for the future of software development",{"altText":302,"config":303},"the source promo card",{"src":304},"/images/navigation/the-source-promo-card.svg",{"text":306,"config":307},"Read the latest",{"href":308,"dataGaName":309,"dataGaLocation":43},"/the-source/","the source",{"text":311,"config":312,"lists":314},"Company",{"dataNavLevelOne":313},"company",[315],{"items":316},[317,322,328,330,335,340,345,350,355,360,365],{"text":318,"config":319},"About",{"href":320,"dataGaName":321,"dataGaLocation":43},"/company/","about",{"text":323,"config":324,"footerGa":327},"Jobs",{"href":325,"dataGaName":326,"dataGaLocation":43},"/jobs/","jobs",{"dataGaName":326},{"text":288,"config":329},{"href":290,"dataGaName":291,"dataGaLocation":43},{"text":331,"config":332},"Leadership",{"href":333,"dataGaName":334,"dataGaLocation":43},"/company/team/e-group/","leadership",{"text":336,"config":337},"Team",{"href":338,"dataGaName":339,"dataGaLocation":43},"/company/team/","team",{"text":341,"config":342},"Handbook",{"href":343,"dataGaName":344,"dataGaLocation":43},"https://handbook.gitlab.com/","handbook",{"text":346,"config":347},"Investor relations",{"href":348,"dataGaName":349,"dataGaLocation":43},"https://ir.gitlab.com/","investor relations",{"text":351,"config":352},"Trust Center",{"href":353,"dataGaName":354,"dataGaLocation":43},"/security/","trust center",{"text":356,"config":357},"AI Transparency Center",{"href":358,"dataGaName":359,"dataGaLocation":43},"/ai-transparency-center/","ai transparency center",{"text":361,"config":362},"Newsletter",{"href":363,"dataGaName":364,"dataGaLocation":43},"/company/contact/","newsletter",{"text":366,"config":367},"Press",{"href":368,"dataGaName":369,"dataGaLocation":43},"/press/","press",{"text":371,"config":372,"lists":373},"Contact us",{"dataNavLevelOne":313},[374],{"items":375},[376,379,384],{"text":50,"config":377},{"href":52,"dataGaName":378,"dataGaLocation":43},"talk to sales",{"text":380,"config":381},"Get help",{"href":382,"dataGaName":383,"dataGaLocation":43},"/support/","get help",{"text":385,"config":386},"Customer portal",{"href":387,"dataGaName":388,"dataGaLocation":43},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":390,"login":391,"suggestions":398},"Close",{"text":392,"link":393},"To search repositories and projects, login to",{"text":394,"config":395},"gitlab.com",{"href":57,"dataGaName":396,"dataGaLocation":397},"search login","search",{"text":399,"default":400},"Suggestions",[401,403,407,409,413,417],{"text":72,"config":402},{"href":77,"dataGaName":72,"dataGaLocation":397},{"text":404,"config":405},"Code Suggestions (AI)",{"href":406,"dataGaName":404,"dataGaLocation":397},"/solutions/code-suggestions/",{"text":124,"config":408},{"href":126,"dataGaName":124,"dataGaLocation":397},{"text":410,"config":411},"GitLab on AWS",{"href":412,"dataGaName":410,"dataGaLocation":397},"/partners/technology-partners/aws/",{"text":414,"config":415},"GitLab on Google Cloud",{"href":416,"dataGaName":414,"dataGaLocation":397},"/partners/technology-partners/google-cloud-platform/",{"text":418,"config":419},"Why GitLab?",{"href":85,"dataGaName":418,"dataGaLocation":397},{"freeTrial":421,"mobileIcon":426,"desktopIcon":431,"secondaryButton":434},{"text":422,"config":423},"Start free trial",{"href":424,"dataGaName":48,"dataGaLocation":425},"https://gitlab.com/-/trials/new/","nav",{"altText":427,"config":428},"Gitlab Icon",{"src":429,"dataGaName":430,"dataGaLocation":425},"/images/brand/gitlab-logo-tanuki.svg","gitlab icon",{"altText":427,"config":432},{"src":433,"dataGaName":430,"dataGaLocation":425},"/images/brand/gitlab-logo-type.svg",{"text":435,"config":436},"Get Started",{"href":437,"dataGaName":438,"dataGaLocation":425},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":440,"mobileIcon":444,"desktopIcon":446},{"text":441,"config":442},"Learn more about GitLab Duo",{"href":77,"dataGaName":443,"dataGaLocation":425},"gitlab duo",{"altText":427,"config":445},{"src":429,"dataGaName":430,"dataGaLocation":425},{"altText":427,"config":447},{"src":433,"dataGaName":430,"dataGaLocation":425},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":453,"_dir":37,"_draft":6,"_partial":6,"_locale":7,"title":454,"button":455,"image":459,"config":462,"_id":464,"_type":29,"_source":31,"_file":465,"_stem":466,"_extension":34},"/shared/en-us/banner","is now in public beta!",{"text":83,"config":456},{"href":457,"dataGaName":458,"dataGaLocation":43},"/gitlab-duo/agent-platform/","duo banner",{"config":460},{"src":461},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":463},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":468,"_dir":37,"_draft":6,"_partial":6,"_locale":7,"data":469,"_id":674,"_type":29,"title":675,"_source":31,"_file":676,"_stem":677,"_extension":34},"/shared/en-us/main-footer",{"text":470,"source":471,"edit":477,"contribute":482,"config":487,"items":492,"minimal":666},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":472,"config":473},"View page source",{"href":474,"dataGaName":475,"dataGaLocation":476},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":478,"config":479},"Edit this page",{"href":480,"dataGaName":481,"dataGaLocation":476},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":483,"config":484},"Please contribute",{"href":485,"dataGaName":486,"dataGaLocation":476},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":488,"facebook":489,"youtube":490,"linkedin":491},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[493,516,573,602,636],{"title":61,"links":494,"subMenu":499},[495],{"text":496,"config":497},"DevSecOps platform",{"href":70,"dataGaName":498,"dataGaLocation":476},"devsecops platform",[500],{"title":201,"links":501},[502,506,511],{"text":503,"config":504},"View plans",{"href":203,"dataGaName":505,"dataGaLocation":476},"view plans",{"text":507,"config":508},"Why Premium?",{"href":509,"dataGaName":510,"dataGaLocation":476},"/pricing/premium/","why premium",{"text":512,"config":513},"Why Ultimate?",{"href":514,"dataGaName":515,"dataGaLocation":476},"/pricing/ultimate/","why ultimate",{"title":517,"links":518},"Solutions",[519,524,527,529,534,539,543,546,550,555,557,560,563,568],{"text":520,"config":521},"Digital transformation",{"href":522,"dataGaName":523,"dataGaLocation":476},"/topics/digital-transformation/","digital transformation",{"text":149,"config":525},{"href":144,"dataGaName":526,"dataGaLocation":476},"security & compliance",{"text":138,"config":528},{"href":120,"dataGaName":121,"dataGaLocation":476},{"text":530,"config":531},"Agile development",{"href":532,"dataGaName":533,"dataGaLocation":476},"/solutions/agile-delivery/","agile delivery",{"text":535,"config":536},"Cloud transformation",{"href":537,"dataGaName":538,"dataGaLocation":476},"/topics/cloud-native/","cloud transformation",{"text":540,"config":541},"SCM",{"href":134,"dataGaName":542,"dataGaLocation":476},"source code management",{"text":124,"config":544},{"href":126,"dataGaName":545,"dataGaLocation":476},"continuous integration & delivery",{"text":547,"config":548},"Value stream management",{"href":176,"dataGaName":549,"dataGaLocation":476},"value stream management",{"text":551,"config":552},"GitOps",{"href":553,"dataGaName":554,"dataGaLocation":476},"/solutions/gitops/","gitops",{"text":186,"config":556},{"href":188,"dataGaName":189,"dataGaLocation":476},{"text":558,"config":559},"Small business",{"href":193,"dataGaName":194,"dataGaLocation":476},{"text":561,"config":562},"Public sector",{"href":198,"dataGaName":199,"dataGaLocation":476},{"text":564,"config":565},"Education",{"href":566,"dataGaName":567,"dataGaLocation":476},"/solutions/education/","education",{"text":569,"config":570},"Financial services",{"href":571,"dataGaName":572,"dataGaLocation":476},"/solutions/finance/","financial services",{"title":206,"links":574},[575,577,579,581,584,586,588,590,592,594,596,598,600],{"text":218,"config":576},{"href":220,"dataGaName":221,"dataGaLocation":476},{"text":223,"config":578},{"href":225,"dataGaName":226,"dataGaLocation":476},{"text":228,"config":580},{"href":230,"dataGaName":231,"dataGaLocation":476},{"text":233,"config":582},{"href":235,"dataGaName":583,"dataGaLocation":476},"docs",{"text":256,"config":585},{"href":258,"dataGaName":5,"dataGaLocation":476},{"text":251,"config":587},{"href":253,"dataGaName":254,"dataGaLocation":476},{"text":260,"config":589},{"href":262,"dataGaName":263,"dataGaLocation":476},{"text":273,"config":591},{"href":275,"dataGaName":276,"dataGaLocation":476},{"text":265,"config":593},{"href":267,"dataGaName":268,"dataGaLocation":476},{"text":278,"config":595},{"href":280,"dataGaName":281,"dataGaLocation":476},{"text":283,"config":597},{"href":285,"dataGaName":286,"dataGaLocation":476},{"text":288,"config":599},{"href":290,"dataGaName":291,"dataGaLocation":476},{"text":293,"config":601},{"href":295,"dataGaName":296,"dataGaLocation":476},{"title":311,"links":603},[604,606,608,610,612,614,616,620,625,627,629,631],{"text":318,"config":605},{"href":320,"dataGaName":313,"dataGaLocation":476},{"text":323,"config":607},{"href":325,"dataGaName":326,"dataGaLocation":476},{"text":331,"config":609},{"href":333,"dataGaName":334,"dataGaLocation":476},{"text":336,"config":611},{"href":338,"dataGaName":339,"dataGaLocation":476},{"text":341,"config":613},{"href":343,"dataGaName":344,"dataGaLocation":476},{"text":346,"config":615},{"href":348,"dataGaName":349,"dataGaLocation":476},{"text":617,"config":618},"Sustainability",{"href":619,"dataGaName":617,"dataGaLocation":476},"/sustainability/",{"text":621,"config":622},"Diversity, inclusion and belonging (DIB)",{"href":623,"dataGaName":624,"dataGaLocation":476},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":351,"config":626},{"href":353,"dataGaName":354,"dataGaLocation":476},{"text":361,"config":628},{"href":363,"dataGaName":364,"dataGaLocation":476},{"text":366,"config":630},{"href":368,"dataGaName":369,"dataGaLocation":476},{"text":632,"config":633},"Modern Slavery Transparency Statement",{"href":634,"dataGaName":635,"dataGaLocation":476},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":637,"links":638},"Contact Us",[639,642,644,646,651,656,661],{"text":640,"config":641},"Contact an expert",{"href":52,"dataGaName":53,"dataGaLocation":476},{"text":380,"config":643},{"href":382,"dataGaName":383,"dataGaLocation":476},{"text":385,"config":645},{"href":387,"dataGaName":388,"dataGaLocation":476},{"text":647,"config":648},"Status",{"href":649,"dataGaName":650,"dataGaLocation":476},"https://status.gitlab.com/","status",{"text":652,"config":653},"Terms of use",{"href":654,"dataGaName":655,"dataGaLocation":476},"/terms/","terms of use",{"text":657,"config":658},"Privacy statement",{"href":659,"dataGaName":660,"dataGaLocation":476},"/privacy/","privacy statement",{"text":662,"config":663},"Cookie preferences",{"dataGaName":664,"dataGaLocation":476,"id":665,"isOneTrustButton":106},"cookie preferences","ot-sdk-btn",{"items":667},[668,670,672],{"text":652,"config":669},{"href":654,"dataGaName":655,"dataGaLocation":476},{"text":657,"config":671},{"href":659,"dataGaName":660,"dataGaLocation":476},{"text":662,"config":673},{"dataGaName":664,"dataGaLocation":476,"id":665,"isOneTrustButton":106},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[679],{"_path":680,"_dir":681,"_draft":6,"_partial":6,"_locale":7,"content":682,"config":685,"_id":687,"_type":29,"title":18,"_source":31,"_file":688,"_stem":689,"_extension":34},"/en-us/blog/authors/robert-speicher","authors",{"name":18,"config":683},{"headshot":7,"ctfId":684},"rspeicher",{"template":686},"BlogAuthor","content:en-us:blog:authors:robert-speicher.yml","en-us/blog/authors/robert-speicher.yml","en-us/blog/authors/robert-speicher",{"_path":691,"_dir":37,"_draft":6,"_partial":6,"_locale":7,"header":692,"eyebrow":693,"blurb":694,"button":695,"secondaryButton":699,"_id":701,"_type":29,"title":702,"_source":31,"_file":703,"_stem":704,"_extension":34},"/shared/en-us/next-steps","Start shipping better software faster","50%+ of the Fortune 100 trust GitLab","See what your team can do with the intelligent\n\n\nDevSecOps platform.\n",{"text":45,"config":696},{"href":697,"dataGaName":48,"dataGaLocation":698},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":50,"config":700},{"href":52,"dataGaName":53,"dataGaLocation":698},"content:shared:en-us:next-steps.yml","Next Steps","shared/en-us/next-steps.yml","shared/en-us/next-steps",1754424499393]