[{"data":1,"prerenderedAt":705},["ShallowReactive",2],{"/en-us/blog/how-to-exploit-parser-differentials/":3,"navigation-en-us":34,"banner-en-us":451,"footer-en-us":466,"Joern Schneeweisz":677,"next-steps-en-us":690},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":24,"_id":27,"_type":28,"title":29,"_source":30,"_file":31,"_stem":32,"_extension":33},"/en-us/blog/how-to-exploit-parser-differentials","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 to exploit parser differentials","Your guide to abusing 'language barriers' between web components.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749668524/Blog/Hero%20Images/closeup-photo-of-black-and-blue-keyboard-1194713.jpg","https://about.gitlab.com/blog/how-to-exploit-parser-differentials","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to exploit parser differentials\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Joern Schneeweisz\"}],\n        \"datePublished\": \"2020-03-30\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":19,"body":20,"category":21,"tags":22},[18],"Joern Schneeweisz","2020-03-30","\n\nThe move to microservices-based architecture creates more attack surface for nefarious actors, so when our [security researchers](/handbook/security/#security-research) discovered a file upload vulnerability within GitLab, we patched it right up in our [GitLab 12.7.4 security release](/releases/2020/01/30/security-release-gitlab-12-7-4-released/). We dive deeper into the problems that lead to this vulnerability and use it to illustrate the underlying concept of parser differentials.\n\n## File Uploads in GitLab\n\nTo understand the file upload vulnerability we need to go a bit deeper into file uploads within GitLab, and have a look at the involved components.\n\n### GitLab Workhorse\n\nThe first relevant component is GitLab's very own reverse proxy called [`gitlab-workhorse`](https://gitlab.com/gitlab-org/gitlab-workhorse/).`gitlab-workhorse` fulfills a variety of tasks, but for this specific example we only care about certain kinds of file uploads.\n\nThe second component is [`gitlab-rails`](https://gitlab.com/gitlab-org/gitlab), the Ruby on Rails-based heart of GitLab. It's the main application part of GitLab and implements most of the business logic.\n\nThe following source code excerpts from `gitlab-workhorse` are based on the [`8.18.0`](https://gitlab.com/gitlab-org/gitlab-workhorse/-/tags/v8.18.0) release which was the most recent version at the time of identifying the vulnerability.\n\nConsider the following route, defined in [`internal/upstream/routes.go`](https://gitlab.com/gitlab-org/gitlab-workhorse/-/blob/9a9a83e7f92ceea5fb0e1542d604171c58615e28/internal/upstream/routes.go#L207-208), which handles file uploads for [Conan](https://conan.io/) packages:\n\n```go\n// Conan Artifact Repository\nroute(\"PUT\", apiPattern+`v4/packages/conan/`, filestore.BodyUploader(api, proxy, nil)),\n```\n\nThe route defined above will pass any `PUT` request to paths underneath `/api/v4/packages/conan/` to the [`BodyUploader`](https://gitlab.com/gitlab-org/gitlab-workhorse/-/blob/9a9a83e7f92ceea5fb0e1542d604171c58615e28/internal/filestore/body_uploader.go#L40-79). Within this `BodyUploader` now some magic happens. Well, actually, it's not magic, the `BodyUploader` receives the uploaded file and lets the `gitlab-rails` backend know where the file has been placed. This happens in [`internal/filestore/file_handler.go`](https://gitlab.com/gitlab-org/gitlab-workhorse/-/blob/9a9a83e7f92ceea5fb0e1542d604171c58615e28/internal/filestore/file_handler.go#L52-81).\n\nAlso worth mentioning: Any not-matched routes in `gitlab-workhorse` will be passed on to the backend without modification. That's especially important in our discussion for non-`PUT` routes under `/api/v4/packages/conan/`.\n\n```go\n// GitLabFinalizeFields returns a map with all the fields GitLab Rails needs in order to finalize the upload.\nfunc (fh *FileHandler) GitLabFinalizeFields(prefix string) map[string]string {\n\tdata := make(map[string]string)\n\tkey := func(field string) string {\n\t\tif prefix == \"\" {\n\t\t\treturn field\n\t\t}\n\n\t\treturn fmt.Sprintf(\"%s.%s\", prefix, field)\n\t}\n  \n\tif fh.Name != \"\" {\n\t\tdata[key(\"name\")] = fh.Name\n\t}\n\tif fh.LocalPath != \"\" {\n\t\tdata[key(\"path\")] = fh.LocalPath\n\t}\n\tif fh.RemoteURL != \"\" {\n\t\tdata[key(\"remote_url\")] = fh.RemoteURL\n\t}\n\tif fh.RemoteID != \"\" {\n\t\tdata[key(\"remote_id\")] = fh.RemoteID\n\t}\n\tdata[key(\"size\")] = strconv.FormatInt(fh.Size, 10)\n\tfor hashName, hash := range fh.hashes {\n\t\tdata[key(hashName)] = hash\n\t}\n  \n\treturn data\n}\n```\n\nSo `gitlab-workhorse` will replace the uploaded file name by the path to where it has stored the file on disk, such that the `gitlab-rails` backend knows where to pick it up.\n\nObserve the following original request, as received by `gitlab-workhorse`:\n\n```\nPUT /api/v4/packages/conan/v1/files/Hello/0.1/root+xxxxx/beta/0/export/conanfile.py HTTP/1.1\nHost: localhost\nUser-Agent: Conan/1.22.0 (Python 3.8.1) python-requests/2.22.0\nAccept-Encoding: gzip, deflate\nAccept: */*\nConnection: close\nX-Checksum-Sha1: 93ebaf6e85e8edde99c1ed46eaa1b5e1e5f4ac78\nContent-Length: 1765\nAuthorization: Bearer [.. shortened ..]\n\nfrom conans import ConanFile, CMake, tools\n\n\nclass HelloConan(ConanFile):\n    name = \"Hello\"\n[.. shortened ..]\n```\n\nThis is what this request will look like to `gitlab-rails` after `gitlab-workhorse` has processed it (excerpted from `api_json.log`):\n\n```json\n{\n  \"time\": \"2020-02-20T14:49:44.738Z\",\n  \"severity\": \"INFO\",\n  \"duration\": 201.93,\n  \"db\": 67.34,\n  \"view\": 134.59,\n  \"status\": 200,\n  \"method\": \"PUT\",\n  \"path\": \"/api/v4/packages/conan/v1/files/Hello/0.1/root+xxxxx/beta/0/export/conanfile.py\",\n  \"params\": [\n    {\n      \"key\": \"file.md5\",\n      \"value\": \"719f0319f1fd5f6fcbc2433cc0008817\"\n    },\n    {\n      \"key\": \"file.path\",\n      \"value\": \"/var/opt/gitlab/gitlab-rails/shared/packages/tmp/uploads/582573467\"\n    },\n    {\n      \"key\": \"file.sha1\",\n      \"value\": \"93ebaf6e85e8edde99c1ed46eaa1b5e1e5f4ac78\"\n    },\n    {\n      \"key\": \"file.sha256\",\n      \"value\": \"f7059b223cd4d32002e5e34ab1ae5b4ea12f3bd0326589b00d5e910ce02c1f3a\"\n    },\n    {\n      \"key\": \"file.sha512\",\n      \"value\": \"efbe75ea58bd817d42fd9ca5ac556abd6fbe3236f66dfad81d508b5860252d32d1b1868ee03c7f4c6174a0ba6cc920a574b5865ca509f36c451113c9108f9a36\"\n    },\n    {\n      \"key\": \"file.size\",\n      \"value\": \"1765\"\n    }\n  ],\n  \"host\": \"localhost\",\n  \"remote_ip\": \"172.17.0.1, 127.0.0.1\",\n  \"ua\": \"Conan/1.22.0 (Python 3.8.1) python-requests/2.22.0\",\n  \"route\": \"/api/:version/packages/conan/v1/files/:package_name/:package_version/:package_username/:package_channel/:recipe_revision/export/:file_name\",\n  \"user_id\": 1,\n  \"username\": \"root\",\n  \"queue_duration\": 16.59,\n  \"correlation_id\": \"aSEqrgEfvX9\"\n}\n```\n\nIn particular, the `params` entry `file.path` is of interest, as it denotes the file system path where `gitlab-workhorse` has placed the uploaded file.\n\n### `gitlab-rails`\nThis `gitlab-workhorse`-modified request, as `gitlab-rails` will see it, is handled in [`lib/uploaded_file.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/v12.7.4-ee/lib/uploaded_file.rb#L45-66) within the `from_params` method:\n\n```ruby\n01  def self.from_params(params, field, upload_paths)\n02    path = params[\"#{field}.path\"]\n03    remote_id = params[\"#{field}.remote_id\"]\n04    return if path.blank? && remote_id.blank?\n05\n06    file_path = nil\n07    if path\n08      file_path = File.realpath(path)\n09\n10      paths = Array(upload_paths) \u003C\u003C Dir.tmpdir\n11      unless self.allowed_path?(file_path, paths.compact)\n12        raise InvalidPathError, \"insecure path used '#{file_path}'\"\n13      end\n14    end\n15\n16    UploadedFile.new(file_path,\n17      filename: params[\"#{field}.name\"],\n18      content_type: params[\"#{field}.type\"] || 'application/octet-stream',\n19      sha256: params[\"#{field}.sha256\"],\n20      remote_id: remote_id,\n21      size: params[\"#{field}.size\"])\n22  end\n```\nWe can see here the handling of the uploaded file reference. The part in line `10-13` in the snippet above implements a whitelist of a specific set of paths from where a `gitlab-workhorse` uploaded file will be accepted.`Dir.tmpdir` which resolves to the path `/tmp` is added to the whitelist as well. In the subsequent lines a new `UploadedFile` is constructed from the `file.path` and other parameters `gitlab-workhorse` has set.\n\n## `gitlab-workhorse` bypass\n\nSo we've seen the inner workings of both `gitlab-workhorse` and `gitlab-rails` when it comes to file uploads for Conan packages. In recap it would go as follows:\n\n```mermaid\nsequenceDiagram\n    participant User\n    participant workhorse\n    participant Rails\n    User->>workhorse: PUT request to conan registry\n    workhorse->>workhorse: Place uploaded file on disk and re-write PUT request\n    workhorse->>Rails: Pass on modified PUT request\n    Rails->>Rails: Pick up file from disk and store in UploadedFile\n```\n\nFrom an attacker perspective it would be nice to meddle with the modified `PUT` request, especially control over the `file.path` parameter would allow us to grab arbitrary files from `/tmp` and the defined `upload_paths`. But as `gitlab-workhorse` sits right in front of `gitlab-rails` we can't just pass those parameters or otherwise interact directly with `gitlab-rails` without going via `gitlab-workhorse`.\n\nWe can indeed achieve this by leveraging the fact that `gitlab-workhorse` parses the HTTP requests in a different way than `gitlab-rails` does. In particular, we can use [`Rack::MethodOverride`](https://www.rubydoc.info/gems/rack/Rack/MethodOverride) in `gitlab-rails` which is a default middleware in Ruby on Rails applications. The `Rack::MethodOverride` middleware allows us to send a `POST` request and let `gitlab-rails` know **\"well, actually this is a `PUT` request! ¯\\\\\\_(ツ)\\_/¯ \"**. With this little trick we can sneak past the `gitlab-workhorse` route which would intercept the `PUT` request, as `gitlab-workhorse` is not aware of the overridden `POST` method. So by specifying either a `_method=PUT` parameter or a `X-HTTP-METHOD-OVERRIDE: PUT` HTTP header we can indeed directly point `gitlab-rails` to files on disk. The method override is used a lot in Ruby on Rails applications to allow simple `\u003Cform>` based `POST` requests to use other [`REST`](https://de.wikipedia.org/wiki/Representational_State_Transfer)-based methods like `PUT` and `DELETE` by overriding the `\u003Cform>`s `POST` request with the `_method` parameter.\n\nSo a `POST` request to the right Conan endpoint with a `file.path` and `file.size` parameter will do the trick.\nA full request using this bypass would look like this:\n\n```\nPOST /api/v4/packages/conan/v1/files/Hello/0.1/lol+wat/beta/0/export/conanmanifest.txt?file.size=4&file.path=/tmp/test1234 HTTP/1.1\nHost: localhost\nUser-Agent: Conan/1.21.0 (Python 3.8.1) python-requests/2.22.0\nAccept-Encoding: gzip, deflate\nAccept: */*\nConnection: close\nX-HTTP-Method-Override: PUT\nX-Checksum-Deploy: true\nX-Checksum-Sha1: ee96149f7b93af931d4548e9562484bdb6ac8fda\nContent-Length: 4\nAuthorization: Bearer [.. shortened ..]\n\nasdf\n```\n\nThis would, instead of uploading a file, let us get a hold of the file `/tmp/test1234` from the GitLab server's file system. In recap, the flow to exploit this issue looks as follows:\n\n```mermaid\nsequenceDiagram\n    participant User\n    participant workhorse\n    participant Rails\n    User->>workhorse: POST request to conan registry\n    workhorse->>workhorse: Route does not match anything\n    workhorse->>Rails: Pass on unmodified POST request\n    Rails->>Rails: Interpret as PUT and pick up file from disk\n```\n\nWe fixed this issue within `gitlab-workhorse` by [signing Requests which pass `gitlab-workhorse`](https://gitlab.com/gitlab-org/gitlab-workhorse/-/commit/3a34323b104be89e92db49828268f0bfd831e75a), the signature then is verified on [the `gitlab-rails` side](https://gitlab.com/gitlab-org/gitlab/-/commit/043c664908e474f34e62e88365be0fc945f1d0b3)\n\n## How parser differentials can introduce vulnerabilities\n\nLet's take a huge step back and see from an high-level perspective what just happened. We've had `gitlab-workhorse` and `gitlab-rails` both looking at a `POST` request. But `gitlab-rails` ultimately saw a `PUT` request due to the overridden HTTP method.\n\nWhat occurred here is a case of a **parser differential**, as `gitlab-workhorse` and `gitlab-rails` parsed the incoming HTTP request differently. The term parser differential originates from the [Language-theoretic Security approach](http://langsec.org). It denotes the fact that two (or more) different parsers \"understand\" the very same message in a different way. Or, as described in the [LangSec handout](http://langsec.org/bof-handout.pdf) as follows:\n\n> Different interpretation of messages or data streams by components breaks any assumptions that components adhere to a shared specification and so introduces inconsistent state and unanticipated computation.\n\nIndeed such issues and the consequential _unanticipated computation_ get more and more common when we look at modern web environments. The days of web applications being a stand-alone bunch of scripts invoked on a web server are long gone. The rise of microservices leads to complex environments and the very same message (or HTTP request) might be interpreted by several different services in several different ways. Just as shown in the above example this sometimes comes along with security implications.\n\nFrom the point of view of a pragmatic bug hunter, the idea of parser differentials is very interesting as those issue can yield unique security bugs. Consider, for instance, this [RCE in couchdb](https://justi.cz/security/2017/11/14/couchdb-rce-npm.html). Also the [HTTP desync attack technique](https://portswigger.net/research/http-desync-attacks-request-smuggling-reborn), which has gotten a lot attention in the bug bounty community, is a matter of parser differentials.\n\nFor the developer perspective we need to be aware of other components and their parsing behavior in order to avoid security issues which arise from interpreting the same message differently.\n\nCover Photo by [Marta Branco](https://www.pexels.com/@martabranco?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels) on [Pexels](https://www.pexels.com/photo/closeup-photo-of-black-and-blue-keyboard-1194713/?utm_content=attributionCopyText&utm_medium=referral&utm_source=pexels)\n{: .note}\n","security",[21,23],"security research",{"slug":25,"featured":6,"template":26},"how-to-exploit-parser-differentials","BlogPost","content:en-us:blog:how-to-exploit-parser-differentials.yml","yaml","How To Exploit Parser Differentials","content","en-us/blog/how-to-exploit-parser-differentials.yml","en-us/blog/how-to-exploit-parser-differentials","yml",{"_path":35,"_dir":36,"_draft":6,"_partial":6,"_locale":7,"data":37,"_id":447,"_type":28,"title":448,"_source":30,"_file":449,"_stem":450,"_extension":33},"/shared/en-us/main-navigation","en-us",{"logo":38,"freeTrial":43,"sales":48,"login":53,"items":58,"search":388,"minimal":419,"duo":438},{"config":39},{"href":40,"dataGaName":41,"dataGaLocation":42},"/","gitlab logo","header",{"text":44,"config":45},"Get free trial",{"href":46,"dataGaName":47,"dataGaLocation":42},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":49,"config":50},"Talk to sales",{"href":51,"dataGaName":52,"dataGaLocation":42},"/sales/","sales",{"text":54,"config":55},"Sign in",{"href":56,"dataGaName":57,"dataGaLocation":42},"https://gitlab.com/users/sign_in/","sign in",[59,103,199,204,309,369],{"text":60,"config":61,"cards":63,"footer":86},"Platform",{"dataNavLevelOne":62},"platform",[64,70,78],{"title":60,"description":65,"link":66},"The most comprehensive AI-powered DevSecOps Platform",{"text":67,"config":68},"Explore our Platform",{"href":69,"dataGaName":62,"dataGaLocation":42},"/platform/",{"title":71,"description":72,"link":73},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":74,"config":75},"Meet GitLab Duo",{"href":76,"dataGaName":77,"dataGaLocation":42},"/gitlab-duo/","gitlab duo ai",{"title":79,"description":80,"link":81},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":82,"config":83},"Learn more",{"href":84,"dataGaName":85,"dataGaLocation":42},"/why-gitlab/","why gitlab",{"title":87,"items":88},"Get started with",[89,94,99],{"text":90,"config":91},"Platform Engineering",{"href":92,"dataGaName":93,"dataGaLocation":42},"/solutions/platform-engineering/","platform engineering",{"text":95,"config":96},"Developer Experience",{"href":97,"dataGaName":98,"dataGaLocation":42},"/developer-experience/","Developer experience",{"text":100,"config":101},"MLOps",{"href":102,"dataGaName":100,"dataGaLocation":42},"/topics/devops/the-role-of-ai-in-devops/",{"text":104,"left":105,"config":106,"link":108,"lists":112,"footer":181},"Product",true,{"dataNavLevelOne":107},"solutions",{"text":109,"config":110},"View all Solutions",{"href":111,"dataGaName":107,"dataGaLocation":42},"/solutions/",[113,138,160],{"title":114,"description":115,"link":116,"items":121},"Automation","CI/CD and automation to accelerate deployment",{"config":117},{"icon":118,"href":119,"dataGaName":120,"dataGaLocation":42},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[122,126,130,134],{"text":123,"config":124},"CI/CD",{"href":125,"dataGaLocation":42,"dataGaName":123},"/solutions/continuous-integration/",{"text":127,"config":128},"AI-Assisted Development",{"href":76,"dataGaLocation":42,"dataGaName":129},"AI assisted development",{"text":131,"config":132},"Source Code Management",{"href":133,"dataGaLocation":42,"dataGaName":131},"/solutions/source-code-management/",{"text":135,"config":136},"Automated Software Delivery",{"href":119,"dataGaLocation":42,"dataGaName":137},"Automated software delivery",{"title":139,"description":140,"link":141,"items":146},"Security","Deliver code faster without compromising security",{"config":142},{"href":143,"dataGaName":144,"dataGaLocation":42,"icon":145},"/solutions/security-compliance/","security and compliance","ShieldCheckLight",[147,150,155],{"text":148,"config":149},"Security & Compliance",{"href":143,"dataGaLocation":42,"dataGaName":148},{"text":151,"config":152},"Software Supply Chain Security",{"href":153,"dataGaLocation":42,"dataGaName":154},"/solutions/supply-chain/","Software supply chain security",{"text":156,"config":157},"Compliance & Governance",{"href":158,"dataGaLocation":42,"dataGaName":159},"/solutions/continuous-software-compliance/","Compliance and governance",{"title":161,"link":162,"items":167},"Measurement",{"config":163},{"icon":164,"href":165,"dataGaName":166,"dataGaLocation":42},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[168,172,176],{"text":169,"config":170},"Visibility & Measurement",{"href":165,"dataGaLocation":42,"dataGaName":171},"Visibility and Measurement",{"text":173,"config":174},"Value Stream Management",{"href":175,"dataGaLocation":42,"dataGaName":173},"/solutions/value-stream-management/",{"text":177,"config":178},"Analytics & Insights",{"href":179,"dataGaLocation":42,"dataGaName":180},"/solutions/analytics-and-insights/","Analytics and insights",{"title":182,"items":183},"GitLab for",[184,189,194],{"text":185,"config":186},"Enterprise",{"href":187,"dataGaLocation":42,"dataGaName":188},"/enterprise/","enterprise",{"text":190,"config":191},"Small Business",{"href":192,"dataGaLocation":42,"dataGaName":193},"/small-business/","small business",{"text":195,"config":196},"Public Sector",{"href":197,"dataGaLocation":42,"dataGaName":198},"/solutions/public-sector/","public sector",{"text":200,"config":201},"Pricing",{"href":202,"dataGaName":203,"dataGaLocation":42,"dataNavLevelOne":203},"/pricing/","pricing",{"text":205,"config":206,"link":208,"lists":212,"feature":296},"Resources",{"dataNavLevelOne":207},"resources",{"text":209,"config":210},"View all resources",{"href":211,"dataGaName":207,"dataGaLocation":42},"/resources/",[213,246,268],{"title":214,"items":215},"Getting started",[216,221,226,231,236,241],{"text":217,"config":218},"Install",{"href":219,"dataGaName":220,"dataGaLocation":42},"/install/","install",{"text":222,"config":223},"Quick start guides",{"href":224,"dataGaName":225,"dataGaLocation":42},"/get-started/","quick setup checklists",{"text":227,"config":228},"Learn",{"href":229,"dataGaLocation":42,"dataGaName":230},"https://university.gitlab.com/","learn",{"text":232,"config":233},"Product documentation",{"href":234,"dataGaName":235,"dataGaLocation":42},"https://docs.gitlab.com/","product documentation",{"text":237,"config":238},"Best practice videos",{"href":239,"dataGaName":240,"dataGaLocation":42},"/getting-started-videos/","best practice videos",{"text":242,"config":243},"Integrations",{"href":244,"dataGaName":245,"dataGaLocation":42},"/integrations/","integrations",{"title":247,"items":248},"Discover",[249,254,258,263],{"text":250,"config":251},"Customer success stories",{"href":252,"dataGaName":253,"dataGaLocation":42},"/customers/","customer success stories",{"text":255,"config":256},"Blog",{"href":257,"dataGaName":5,"dataGaLocation":42},"/blog/",{"text":259,"config":260},"Remote",{"href":261,"dataGaName":262,"dataGaLocation":42},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":264,"config":265},"TeamOps",{"href":266,"dataGaName":267,"dataGaLocation":42},"/teamops/","teamops",{"title":269,"items":270},"Connect",[271,276,281,286,291],{"text":272,"config":273},"GitLab Services",{"href":274,"dataGaName":275,"dataGaLocation":42},"/services/","services",{"text":277,"config":278},"Community",{"href":279,"dataGaName":280,"dataGaLocation":42},"/community/","community",{"text":282,"config":283},"Forum",{"href":284,"dataGaName":285,"dataGaLocation":42},"https://forum.gitlab.com/","forum",{"text":287,"config":288},"Events",{"href":289,"dataGaName":290,"dataGaLocation":42},"/events/","events",{"text":292,"config":293},"Partners",{"href":294,"dataGaName":295,"dataGaLocation":42},"/partners/","partners",{"backgroundColor":297,"textColor":298,"text":299,"image":300,"link":304},"#2f2a6b","#fff","Insights for the future of software development",{"altText":301,"config":302},"the source promo card",{"src":303},"/images/navigation/the-source-promo-card.svg",{"text":305,"config":306},"Read the latest",{"href":307,"dataGaName":308,"dataGaLocation":42},"/the-source/","the source",{"text":310,"config":311,"lists":313},"Company",{"dataNavLevelOne":312},"company",[314],{"items":315},[316,321,327,329,334,339,344,349,354,359,364],{"text":317,"config":318},"About",{"href":319,"dataGaName":320,"dataGaLocation":42},"/company/","about",{"text":322,"config":323,"footerGa":326},"Jobs",{"href":324,"dataGaName":325,"dataGaLocation":42},"/jobs/","jobs",{"dataGaName":325},{"text":287,"config":328},{"href":289,"dataGaName":290,"dataGaLocation":42},{"text":330,"config":331},"Leadership",{"href":332,"dataGaName":333,"dataGaLocation":42},"/company/team/e-group/","leadership",{"text":335,"config":336},"Team",{"href":337,"dataGaName":338,"dataGaLocation":42},"/company/team/","team",{"text":340,"config":341},"Handbook",{"href":342,"dataGaName":343,"dataGaLocation":42},"https://handbook.gitlab.com/","handbook",{"text":345,"config":346},"Investor relations",{"href":347,"dataGaName":348,"dataGaLocation":42},"https://ir.gitlab.com/","investor relations",{"text":350,"config":351},"Trust Center",{"href":352,"dataGaName":353,"dataGaLocation":42},"/security/","trust center",{"text":355,"config":356},"AI Transparency Center",{"href":357,"dataGaName":358,"dataGaLocation":42},"/ai-transparency-center/","ai transparency center",{"text":360,"config":361},"Newsletter",{"href":362,"dataGaName":363,"dataGaLocation":42},"/company/contact/","newsletter",{"text":365,"config":366},"Press",{"href":367,"dataGaName":368,"dataGaLocation":42},"/press/","press",{"text":370,"config":371,"lists":372},"Contact us",{"dataNavLevelOne":312},[373],{"items":374},[375,378,383],{"text":49,"config":376},{"href":51,"dataGaName":377,"dataGaLocation":42},"talk to sales",{"text":379,"config":380},"Get help",{"href":381,"dataGaName":382,"dataGaLocation":42},"/support/","get help",{"text":384,"config":385},"Customer portal",{"href":386,"dataGaName":387,"dataGaLocation":42},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":389,"login":390,"suggestions":397},"Close",{"text":391,"link":392},"To search repositories and projects, login to",{"text":393,"config":394},"gitlab.com",{"href":56,"dataGaName":395,"dataGaLocation":396},"search login","search",{"text":398,"default":399},"Suggestions",[400,402,406,408,412,416],{"text":71,"config":401},{"href":76,"dataGaName":71,"dataGaLocation":396},{"text":403,"config":404},"Code Suggestions (AI)",{"href":405,"dataGaName":403,"dataGaLocation":396},"/solutions/code-suggestions/",{"text":123,"config":407},{"href":125,"dataGaName":123,"dataGaLocation":396},{"text":409,"config":410},"GitLab on AWS",{"href":411,"dataGaName":409,"dataGaLocation":396},"/partners/technology-partners/aws/",{"text":413,"config":414},"GitLab on Google Cloud",{"href":415,"dataGaName":413,"dataGaLocation":396},"/partners/technology-partners/google-cloud-platform/",{"text":417,"config":418},"Why GitLab?",{"href":84,"dataGaName":417,"dataGaLocation":396},{"freeTrial":420,"mobileIcon":425,"desktopIcon":430,"secondaryButton":433},{"text":421,"config":422},"Start free trial",{"href":423,"dataGaName":47,"dataGaLocation":424},"https://gitlab.com/-/trials/new/","nav",{"altText":426,"config":427},"Gitlab Icon",{"src":428,"dataGaName":429,"dataGaLocation":424},"/images/brand/gitlab-logo-tanuki.svg","gitlab icon",{"altText":426,"config":431},{"src":432,"dataGaName":429,"dataGaLocation":424},"/images/brand/gitlab-logo-type.svg",{"text":434,"config":435},"Get Started",{"href":436,"dataGaName":437,"dataGaLocation":424},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":439,"mobileIcon":443,"desktopIcon":445},{"text":440,"config":441},"Learn more about GitLab Duo",{"href":76,"dataGaName":442,"dataGaLocation":424},"gitlab duo",{"altText":426,"config":444},{"src":428,"dataGaName":429,"dataGaLocation":424},{"altText":426,"config":446},{"src":432,"dataGaName":429,"dataGaLocation":424},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":452,"_dir":36,"_draft":6,"_partial":6,"_locale":7,"title":453,"button":454,"image":458,"config":461,"_id":463,"_type":28,"_source":30,"_file":464,"_stem":465,"_extension":33},"/shared/en-us/banner","is now in public beta!",{"text":82,"config":455},{"href":456,"dataGaName":457,"dataGaLocation":42},"/gitlab-duo/agent-platform/","duo banner",{"config":459},{"src":460},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":462},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":467,"_dir":36,"_draft":6,"_partial":6,"_locale":7,"data":468,"_id":673,"_type":28,"title":674,"_source":30,"_file":675,"_stem":676,"_extension":33},"/shared/en-us/main-footer",{"text":469,"source":470,"edit":476,"contribute":481,"config":486,"items":491,"minimal":665},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":471,"config":472},"View page source",{"href":473,"dataGaName":474,"dataGaLocation":475},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":477,"config":478},"Edit this page",{"href":479,"dataGaName":480,"dataGaLocation":475},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":482,"config":483},"Please contribute",{"href":484,"dataGaName":485,"dataGaLocation":475},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":487,"facebook":488,"youtube":489,"linkedin":490},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[492,515,572,601,635],{"title":60,"links":493,"subMenu":498},[494],{"text":495,"config":496},"DevSecOps platform",{"href":69,"dataGaName":497,"dataGaLocation":475},"devsecops platform",[499],{"title":200,"links":500},[501,505,510],{"text":502,"config":503},"View plans",{"href":202,"dataGaName":504,"dataGaLocation":475},"view plans",{"text":506,"config":507},"Why Premium?",{"href":508,"dataGaName":509,"dataGaLocation":475},"/pricing/premium/","why premium",{"text":511,"config":512},"Why Ultimate?",{"href":513,"dataGaName":514,"dataGaLocation":475},"/pricing/ultimate/","why ultimate",{"title":516,"links":517},"Solutions",[518,523,526,528,533,538,542,545,549,554,556,559,562,567],{"text":519,"config":520},"Digital transformation",{"href":521,"dataGaName":522,"dataGaLocation":475},"/topics/digital-transformation/","digital transformation",{"text":148,"config":524},{"href":143,"dataGaName":525,"dataGaLocation":475},"security & compliance",{"text":137,"config":527},{"href":119,"dataGaName":120,"dataGaLocation":475},{"text":529,"config":530},"Agile development",{"href":531,"dataGaName":532,"dataGaLocation":475},"/solutions/agile-delivery/","agile delivery",{"text":534,"config":535},"Cloud transformation",{"href":536,"dataGaName":537,"dataGaLocation":475},"/topics/cloud-native/","cloud transformation",{"text":539,"config":540},"SCM",{"href":133,"dataGaName":541,"dataGaLocation":475},"source code management",{"text":123,"config":543},{"href":125,"dataGaName":544,"dataGaLocation":475},"continuous integration & delivery",{"text":546,"config":547},"Value stream management",{"href":175,"dataGaName":548,"dataGaLocation":475},"value stream management",{"text":550,"config":551},"GitOps",{"href":552,"dataGaName":553,"dataGaLocation":475},"/solutions/gitops/","gitops",{"text":185,"config":555},{"href":187,"dataGaName":188,"dataGaLocation":475},{"text":557,"config":558},"Small business",{"href":192,"dataGaName":193,"dataGaLocation":475},{"text":560,"config":561},"Public sector",{"href":197,"dataGaName":198,"dataGaLocation":475},{"text":563,"config":564},"Education",{"href":565,"dataGaName":566,"dataGaLocation":475},"/solutions/education/","education",{"text":568,"config":569},"Financial services",{"href":570,"dataGaName":571,"dataGaLocation":475},"/solutions/finance/","financial services",{"title":205,"links":573},[574,576,578,580,583,585,587,589,591,593,595,597,599],{"text":217,"config":575},{"href":219,"dataGaName":220,"dataGaLocation":475},{"text":222,"config":577},{"href":224,"dataGaName":225,"dataGaLocation":475},{"text":227,"config":579},{"href":229,"dataGaName":230,"dataGaLocation":475},{"text":232,"config":581},{"href":234,"dataGaName":582,"dataGaLocation":475},"docs",{"text":255,"config":584},{"href":257,"dataGaName":5,"dataGaLocation":475},{"text":250,"config":586},{"href":252,"dataGaName":253,"dataGaLocation":475},{"text":259,"config":588},{"href":261,"dataGaName":262,"dataGaLocation":475},{"text":272,"config":590},{"href":274,"dataGaName":275,"dataGaLocation":475},{"text":264,"config":592},{"href":266,"dataGaName":267,"dataGaLocation":475},{"text":277,"config":594},{"href":279,"dataGaName":280,"dataGaLocation":475},{"text":282,"config":596},{"href":284,"dataGaName":285,"dataGaLocation":475},{"text":287,"config":598},{"href":289,"dataGaName":290,"dataGaLocation":475},{"text":292,"config":600},{"href":294,"dataGaName":295,"dataGaLocation":475},{"title":310,"links":602},[603,605,607,609,611,613,615,619,624,626,628,630],{"text":317,"config":604},{"href":319,"dataGaName":312,"dataGaLocation":475},{"text":322,"config":606},{"href":324,"dataGaName":325,"dataGaLocation":475},{"text":330,"config":608},{"href":332,"dataGaName":333,"dataGaLocation":475},{"text":335,"config":610},{"href":337,"dataGaName":338,"dataGaLocation":475},{"text":340,"config":612},{"href":342,"dataGaName":343,"dataGaLocation":475},{"text":345,"config":614},{"href":347,"dataGaName":348,"dataGaLocation":475},{"text":616,"config":617},"Sustainability",{"href":618,"dataGaName":616,"dataGaLocation":475},"/sustainability/",{"text":620,"config":621},"Diversity, inclusion and belonging (DIB)",{"href":622,"dataGaName":623,"dataGaLocation":475},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":350,"config":625},{"href":352,"dataGaName":353,"dataGaLocation":475},{"text":360,"config":627},{"href":362,"dataGaName":363,"dataGaLocation":475},{"text":365,"config":629},{"href":367,"dataGaName":368,"dataGaLocation":475},{"text":631,"config":632},"Modern Slavery Transparency Statement",{"href":633,"dataGaName":634,"dataGaLocation":475},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":636,"links":637},"Contact Us",[638,641,643,645,650,655,660],{"text":639,"config":640},"Contact an expert",{"href":51,"dataGaName":52,"dataGaLocation":475},{"text":379,"config":642},{"href":381,"dataGaName":382,"dataGaLocation":475},{"text":384,"config":644},{"href":386,"dataGaName":387,"dataGaLocation":475},{"text":646,"config":647},"Status",{"href":648,"dataGaName":649,"dataGaLocation":475},"https://status.gitlab.com/","status",{"text":651,"config":652},"Terms of use",{"href":653,"dataGaName":654,"dataGaLocation":475},"/terms/","terms of use",{"text":656,"config":657},"Privacy statement",{"href":658,"dataGaName":659,"dataGaLocation":475},"/privacy/","privacy statement",{"text":661,"config":662},"Cookie preferences",{"dataGaName":663,"dataGaLocation":475,"id":664,"isOneTrustButton":105},"cookie preferences","ot-sdk-btn",{"items":666},[667,669,671],{"text":651,"config":668},{"href":653,"dataGaName":654,"dataGaLocation":475},{"text":656,"config":670},{"href":658,"dataGaName":659,"dataGaLocation":475},{"text":661,"config":672},{"dataGaName":663,"dataGaLocation":475,"id":664,"isOneTrustButton":105},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[678],{"_path":679,"_dir":680,"_draft":6,"_partial":6,"_locale":7,"content":681,"config":685,"_id":687,"_type":28,"title":18,"_source":30,"_file":688,"_stem":689,"_extension":33},"/en-us/blog/authors/joern-schneeweisz","authors",{"name":18,"config":682},{"headshot":683,"ctfId":684},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749679144/Blog/Author%20Headshots/joernchen-headshot.png","joernchen",{"template":686},"BlogAuthor","content:en-us:blog:authors:joern-schneeweisz.yml","en-us/blog/authors/joern-schneeweisz.yml","en-us/blog/authors/joern-schneeweisz",{"_path":691,"_dir":36,"_draft":6,"_partial":6,"_locale":7,"header":692,"eyebrow":693,"blurb":694,"button":695,"secondaryButton":699,"_id":701,"_type":28,"title":702,"_source":30,"_file":703,"_stem":704,"_extension":33},"/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":44,"config":696},{"href":697,"dataGaName":47,"dataGaLocation":698},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":49,"config":700},{"href":51,"dataGaName":52,"dataGaLocation":698},"content:shared:en-us:next-steps.yml","Next Steps","shared/en-us/next-steps.yml","shared/en-us/next-steps",1753981631654]