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