[{"data":1,"prerenderedAt":1126},["ShallowReactive",2],{"/blog/building-gitlab-with-gitlab-api-fuzzing-workflow":3,"navigation-en-us":328,"banner-en-us":756,"footer-en-us":766,"blog-post-authors-en-us-Mike Eddington|Eugene Lim":1011,"blog-related-posts-en-us-building-gitlab-with-gitlab-api-fuzzing-workflow":1037,"blog-promotions-en-us":1063,"next-steps-en-us":1116},{"id":4,"title":5,"authors":6,"body":9,"category":305,"date":306,"description":307,"extension":308,"externalUrl":309,"faq":309,"featured":310,"heroImage":311,"meta":312,"navigation":313,"path":314,"seo":315,"slug":319,"stem":320,"tags":321,"template":326,"updatedDate":309,"__hash__":327},"blogPosts/en-us/blog/building-gitlab-with-gitlab-api-fuzzing-workflow.md","Building GitLab with GitLab: Web API Fuzz Testing",[7,8],"Mike Eddington","Eugene Lim",{"type":10,"value":11,"toc":297},"minimark",[12,23,28,43,47,50,66,93,104,107,113,128,134,137,143,169,172,178,181,187,190,194,209,223,232,235,244,247,251,254,262,265,269,272,281,284],[13,14,15,16,22],"p",{},"At GitLab, we try to ",[17,18,21],"a",{"href":19,"rel":20},"https://handbook.gitlab.com/handbook/product/product-processes/#dogfood-everything",[],"dogfood everything"," to help us better understand the product, pain points, and configuration issues. We use what we learn to build a more efficient, feature-rich platform and user experience. In this first installment of our “Building GitLab with GitLab” series, we will focus on security testing. We constantly strive to improve our security testing coverage and integrate it into our DevSecOps lifecycle. These considerations formed the motivation for the API fuzzing dogfooding project at GitLab. By sharing our lessons from building this workflow, we hope other teams can also learn how to integrate GitLab’s Web API Fuzz Testing and solve some common challenges.",[24,25,27],"h2",{"id":26},"what-is-web-api-fuzz-testing","What is Web API Fuzz Testing?",[13,29,30,31,36,37,42],{},"Web API Fuzz Testing involves generating and sending various unexpected input parameters to a web API in an attempt to trigger unexpected behavior and errors in the API backend. By analyzing these errors, you can discover bugs and potential security issues missed by other scanners that focus on specific vulnerabilities. GitLab's Web API Fuzz Testing complements and should be run in addition to GitLab Secure’s other security scanners such as static application security testing (",[17,32,35],{"href":33,"rel":34},"https://docs.gitlab.com/user/application_security/sast/",[],"SAST",") and dynamic application security testing (",[17,38,41],{"href":39,"rel":40},"https://docs.gitlab.com/user/application_security/dast/",[],"DAST",") APIs.",[24,44,46],{"id":45},"auto-generating-an-openapi-specification","Auto-generating an OpenAPI specification",[13,48,49],{},"To run the Web API Fuzzing Analyzer, you need one of the following:",[51,52,53,57,60,63],"ul",{},[54,55,56],"li",{},"OpenAPI Specification - Version 2 or 3",[54,58,59],{},"GraphQL Schema",[54,61,62],{},"HTTP Archive (HAR)",[54,64,65],{},"Postman Collection - Version 2.0 or 2.1",[13,67,68,69,74,75,80,81,86,87,92],{},"At the start of the API fuzzing project, the API Vision working group was also working on an issue to automatically document ",[17,70,73],{"href":71,"rel":72},"https://gitlab.com/groups/gitlab-org/-/epics/8636",[],"GitLab’s REST API endpoints in an OpenAPI specification",", so we worked with our colleague Andy Soiron on implementing it. Because GitLab uses the ",[17,76,79],{"href":77,"rel":78},"https://github.com/ruby-grape/grape",[],"grape"," API framework, Andy had already identified and ",[17,82,85],{"href":83,"rel":84},"https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95877",[],"tested"," the ",[17,88,91],{"href":89,"rel":90},"https://github.com/ruby-grape/grape-swagger",[],"grape-swagger"," gem that auto-generates an OpenAPI v2 specification based on existing grape annotations. For example, the following API endpoint code:",[94,95,101],"pre",{"className":96,"code":98,"language":99,"meta":100},[97],"language-text","\n     Class.new(Grape::API) do\n       format :json\n       desc 'This gets something.'\n       get '/something' do\n         { bla: 'something' }\n       end\n       add_swagger_documentation\n     end\n\n","text","",[102,103,98],"code",{"__ignoreMap":100},[13,105,106],{},"Will be parsed by grape-swagger into:",[94,108,111],{"className":109,"code":110,"language":99,"meta":100},[97],"{\n  // rest of OpenAPI v2 specification\n  …\n  \"paths\": {\n    \"/something\": {\n      \"get\": {\n        \"description\": \"This gets something.\",\n        \"produces\": [\n          \"application/json\"\n        ],\n        \"operationId\": \"getSomething\",\n        \"responses\": {\n          \"200\": {\n            \"description\": \"This gets something.\"\n          }\n        }\n      }\n    }\n  }\n}\n",[102,112,110],{"__ignoreMap":100},[13,114,115,116,121,122,127],{},"However, with almost 2,000 API operations with different requirements and formats, a lot of additional work needed to be done to resolve edge cases that did not meet the requirements of grape-swagger or the OpenAPI format. For example, one simple case was API endpoints that accept file parameters, such as the ",[17,117,120],{"href":118,"rel":119},"https://docs.gitlab.com/api/issues/#upload-metric-image",[],"upload metric image endpoint",". GitLab uses the ",[17,123,126],{"href":124,"rel":125},"https://gitlab.com/gitlab-org/gitlab/tree/master/workhorse",[],"Workhorse"," smart reverse proxy to handle \"large\" HTTP requests such as file uploads. As such, file parameters must be of the type WorkhorseFile:",[94,129,132],{"className":130,"code":131,"language":99,"meta":100},[97],"namespace ':id/issues/:issue_iid/metric_images' do\n            …\n            desc 'Upload a metric image for an issue' do\n              success Entities::IssuableMetricImage\n            end\n            params do\n              requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The image file to be uploaded'\n              optional :url, type: String, desc: 'The url to view more metric info'\n              optional :url_text, type: String, desc: 'A description of the image or URL'\n            end\n            post do\n              require_gitlab_workhorse!\n\n",[102,133,131],{"__ignoreMap":100},[13,135,136],{},"Because grape-swagger does not recognize what OpenAPI type WorkhorseFile corresponds to, it excludes the parameter from its output. We fixed this by adding a grape-swagger-specific documentation to override the type during generation:",[94,138,141],{"className":139,"code":140,"language":99,"meta":100},[97],"\n             requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The image file to be uploaded', documentation: { type: 'file' }\n\n",[102,142,140],{"__ignoreMap":100},[13,144,145,146,149,150,153,154,157,158,161,162,157,165,168],{},"However, not all edge cases could be resolved with a simple match-and-replace in the grape annotations. For example, Ruby on Rails supports wildcard segment parameters. A route like ",[102,147,148],{},"get 'books/*section/:title'"," would match",[102,151,152],{},"books/some/section/last-words-a-memoir",". In addition, the URI would be parsed such that the ",[102,155,156],{},"section"," path parameter would have the value ",[102,159,160],{},"some/section"," and the ",[102,163,164],{},"title",[102,166,167],{},"last-words-a-memoir",".",[13,170,171],{},"Currently, grape-swagger does not recognize these wildcard segments as path parameters. For example, the route would generate:",[94,173,176],{"className":174,"code":175,"language":99,"meta":100},[97],"\"paths\": {\n  \"/api/v2/books/*section/{title}\": {\n    \"get\": {\n    ...\n      \"parameters\": [\n         {\n           \"in\": \"query\", \"name\": \"*section\"\n           ...\n  }\n}\n",[102,177,175],{"__ignoreMap":100},[13,179,180],{},"Instead of the expected:",[94,182,185],{"className":183,"code":184,"language":99,"meta":100},[97],"\"paths\": {\n  \"/api/v2/books/{section}/{title}\": {\n    \"get\": {\n    ...\n      \"parameters\": [\n         {\n           \"in\": \"path\", \"name\": \"section\"\n           ...\n  }\n}\n",[102,186,184],{"__ignoreMap":100},[13,188,189],{},"As such, we also needed to make several patches to grape-swagger, which we forked while waiting for the changes to be accepted upstream. Nevertheless, with lots of careful checking and cooperation across teams, we managed to get the OpenAPI specification generated for most of the endpoints.",[24,191,193],{"id":192},"performance-tuning","Performance tuning",[13,195,196,197,202,203,208],{},"With the OpenAPI specification, we could now begin with the API fuzzing. GitLab already uses the ",[17,198,201],{"href":199,"rel":200},"https://docs.gitlab.com/ci/review_apps/",[],"Review Apps"," feature to generate testing environments for some feature changes, providing a readily available fuzzing target. However, given the large number of endpoints, it would be impossible to expect a standard shared runner to complete fuzzing in a single job. The Web API Fuzz Testing documentation includes a ",[17,204,207],{"href":205,"rel":206},"https://docs.gitlab.com/user/application_security/api_fuzzing/#performance-tuning-and-testing-speed",[],"performance tuning section"," that recommends the following:",[51,210,211,214,217,220],{},[54,212,213],{},"using a multi-CPU Runner",[54,215,216],{},"excluding slow operations",[54,218,219],{},"splitting a test into multiple jobs",[54,221,222],{},"excluding operations in feature branches, but not default branch",[13,224,225,226,231],{},"The first recommendation was easy to implement with a dedicated fuzzing runner. We recommend doing this for large scheduled fuzzing workflows, especially if you select the Long-100 fuzzing profile. We also began excluding slow operations by checking the job logs for the time taken to complete each operation. Along the way, we identified other endpoints that needed to be excluded, such as the ",[17,227,230],{"href":228,"rel":229},"https://docs.gitlab.com/api/personal_access_tokens/#revoke-a-personal-access-token",[],"revoke token endpoint"," that prematurely ended the fuzzing session.",[13,233,234],{},"Splitting the test into multiple jobs took the most effort due to the requirements of the OpenAPI format. Each OpenAPI document includes a required set of objects and fields, so it is not simply a matter of splitting after a fixed number of lines. Additionally, each operation relies on entities defined in the definitions object, so we needed to ensure that when splitting the OpenAPI specification, the entities required by the endpoints were included. We also wrote a quick script to fill the example parameter data with actual data from the testing environment, such as project IDs.",[13,236,237,238,243],{},"While it was possible to run these scripts locally, then push the split jobs and OpenAPI specifications to the repository, this created a large number of changes every time we updated the original OpenAPI specification. Instead, we adapted the workflow to use dynamically generated child pipelines that would split the OpenAPI document in a CI job, then generate a child pipeline with jobs for each split document. This made iterating a lot easier and more agile. We have uploaded ",[17,239,242],{"href":240,"rel":241},"https://gitlab.com/eugene_lim/api-fuzzing-dogfooding",[],"the scripts and pipeline configuration"," for reference.",[13,245,246],{},"By tweaking the number of parallel jobs and fuzzing profile, we were eventually able to achieve a reasonably comprehensive fuzzing session in an acceptable time frame. When tuning your own fuzzing workflow, balancing these trade-offs is essential.",[24,248,250],{"id":249},"triaging-the-api-fuzzing-findings","Triaging the API fuzzing findings",[13,252,253],{},"With the fuzzing done, we were now confronted with hundreds of findings. Unlike DAST analyzers that try to detect specific vulnerabilities, Web API Fuzz Testing looks for unexpected behavior and errors that may not necessarily be vulnerabilities. This is why fuzzing faults discovered by the API Fuzzing Analyzer show up as vulnerabilities with a severity of “Unknown.” This requires more involved triaging.",[13,255,256,257,168],{},"Fortunately, the Web API fuzzer also outputs Postman collections as artifacts in the Vulnerability Report page. These collections allow you to quickly repeat requests that triggered a fault during fuzzing. For this stage of the fuzzing workflow, we recommend that you set up a local instance of the application so that you can easily check logs and debug specific faults. In this case, we ran the ",[17,258,261],{"href":259,"rel":260},"https://gitlab.com/gitlab-org/gitlab-development-kit",[],"GitLab Development Kit",[13,263,264],{},"Many of the faults occurred due to a lack of error handling for unexpected inputs. We created issues from the Vulnerability Report page, and if we found that a particular fault had the same root cause as a previously triaged fault, we linked the vulnerability to the original issue instead.",[24,266,268],{"id":267},"lessons-learned","Lessons learned",[13,270,271],{},"The API fuzzing dogfooding project turned out to be a fruitful exercise that benefited other workstreams at GitLab, such as the API documentation project. In addition, tuning and triaging helped us identify key pain points in the process for improvement. Automated API documentation generation is difficult even with OpenAPI, particularly on a long-lived codebase. GitLab’s existing annotations and tests helped speed up documentation via a distributed, asynchronous workflow across multiple teams. In addition, many GitLab features such as Review Apps, Vulnerability Reports, and dynamically generated child pipelines helped us build a robust fuzzing workflow.",[13,273,274,275,280],{},"There are still many improvements that can be made to the workflow. Moving to OpenAPI v3 could improve endpoint coverage. The Secure team also wrote a ",[17,276,279],{"href":277,"rel":278},"https://gitlab.com/gitlab-org/security-products/har-recorder",[],"HAR Recorder"," tool that could help generate HAR files on the fly instead of relying on static documentation. For now, due to the high compute cost of fuzzing thousands of operations in GitLab’s API, the workflow is better suited to a scheduled pipeline instead of GitLab’s core pipeline.",[13,282,283],{},"For teams that have already implemented several layers of static and dynamic checks and want to take further steps to increase coverage, we recommend trying a Web API fuzzing exercise as a way to validate assumptions and discover “unknown unknowns” in your code.",[13,285,286,287,292,293,296],{},"We encourage you to get familiar with API fuzzing and let us know how it works for you. If you face any issues or have any feedback, please file an issue at the ",[17,288,291],{"href":289,"rel":290},"https://gitlab.com/gitlab-org/gitlab/-/issues/",[],"issue tracker on GitLab.com",". Use the ",[102,294,295],{},"~\"Category:API Security\""," label when opening a new issue regarding API fuzzing to ensure it is quickly reviewed by the appropriate team members.",{"title":100,"searchDepth":298,"depth":298,"links":299},2,[300,301,302,303,304],{"id":26,"depth":298,"text":27},{"id":45,"depth":298,"text":46},{"id":192,"depth":298,"text":193},{"id":249,"depth":298,"text":250},{"id":267,"depth":298,"text":268},"engineering","2023-05-09","Our new series shows how we dogfood new DevSecOps platform features to ready them for you. First up, security testing.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659740/Blog/Hero%20Images/building-gitlab-with-gitlab-no-type.png",{},true,"/en-us/blog/building-gitlab-with-gitlab-api-fuzzing-workflow",{"title":5,"description":307,"ogTitle":5,"ogDescription":307,"noIndex":310,"ogImage":311,"ogUrl":316,"ogSiteName":317,"ogType":318,"canonicalUrls":316},"https://about.gitlab.com/blog/building-gitlab-with-gitlab-api-fuzzing-workflow","https://about.gitlab.com","article","building-gitlab-with-gitlab-api-fuzzing-workflow","en-us/blog/building-gitlab-with-gitlab-api-fuzzing-workflow",[322,323,322,324,325],"inside GitLab","security","testing","tutorial","BlogPost","aYOrJLrOFdgsnPpZN2oAPIwkq6n57U-QDUgyTFwGnLE",{"logo":329,"freeTrial":334,"sales":339,"login":344,"items":349,"search":676,"minimal":707,"duo":726,"switchNav":735,"pricingDeployment":746},{"config":330},{"href":331,"dataGaName":332,"dataGaLocation":333},"/","gitlab logo","header",{"text":335,"config":336},"Get free trial",{"href":337,"dataGaName":338,"dataGaLocation":333},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":340,"config":341},"Request a demo",{"href":342,"dataGaName":343,"dataGaLocation":333},"/sales/?contact-topic=request-demo","sales",{"text":345,"config":346},"Sign in",{"href":347,"dataGaName":348,"dataGaLocation":333},"https://gitlab.com/users/sign_in/","sign in",[350,379,479,484,598,654],{"text":351,"config":352,"menu":354},"Platform",{"dataNavLevelOne":353},"platform",{"type":355,"columns":356},"cards",[357,363,371],{"title":351,"description":358,"link":359},"The intelligent orchestration platform for DevSecOps",{"text":360,"config":361},"Explore our Platform",{"href":362,"dataGaName":353,"dataGaLocation":333},"/platform/",{"title":364,"description":365,"link":366},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":367,"config":368},"Meet GitLab Duo",{"href":369,"dataGaName":370,"dataGaLocation":333},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":372,"description":373,"link":374},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":375,"config":376},"Learn more",{"href":377,"dataGaName":378,"dataGaLocation":333},"/why-gitlab/","why gitlab",{"text":380,"left":313,"config":381,"menu":383},"Product",{"dataNavLevelOne":382},"solutions",{"type":384,"link":385,"columns":389,"feature":458},"lists",{"text":386,"config":387},"View all Solutions",{"href":388,"dataGaName":382,"dataGaLocation":333},"/solutions/",[390,414,437],{"title":391,"description":392,"link":393,"items":398},"Automation","CI/CD and automation to accelerate deployment",{"config":394},{"icon":395,"href":396,"dataGaName":397,"dataGaLocation":333},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[399,403,406,410],{"text":400,"config":401},"CI/CD",{"href":402,"dataGaLocation":333,"dataGaName":400},"/solutions/continuous-integration/",{"text":364,"config":404},{"href":369,"dataGaLocation":333,"dataGaName":405},"gitlab duo agent platform - product menu",{"text":407,"config":408},"Source Code Management",{"href":409,"dataGaLocation":333,"dataGaName":407},"/solutions/source-code-management/",{"text":411,"config":412},"Automated Software Delivery",{"href":396,"dataGaLocation":333,"dataGaName":413},"Automated software delivery",{"title":415,"description":416,"link":417,"items":422},"Security","Deliver code faster without compromising security",{"config":418},{"href":419,"dataGaName":420,"dataGaLocation":333,"icon":421},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[423,427,432],{"text":424,"config":425},"Application Security Testing",{"href":419,"dataGaName":426,"dataGaLocation":333},"Application security testing",{"text":428,"config":429},"Software Supply Chain Security",{"href":430,"dataGaLocation":333,"dataGaName":431},"/solutions/supply-chain/","Software supply chain security",{"text":433,"config":434},"Software Compliance",{"href":435,"dataGaName":436,"dataGaLocation":333},"/solutions/software-compliance/","software compliance",{"title":438,"link":439,"items":444},"Measurement",{"config":440},{"icon":441,"href":442,"dataGaName":443,"dataGaLocation":333},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[445,449,453],{"text":446,"config":447},"Visibility & Measurement",{"href":442,"dataGaLocation":333,"dataGaName":448},"Visibility and Measurement",{"text":450,"config":451},"Value Stream Management",{"href":452,"dataGaLocation":333,"dataGaName":450},"/solutions/value-stream-management/",{"text":454,"config":455},"Analytics & Insights",{"href":456,"dataGaLocation":333,"dataGaName":457},"/solutions/analytics-and-insights/","Analytics and insights",{"title":459,"type":384,"items":460},"GitLab for",[461,467,473],{"text":462,"config":463},"Enterprise",{"icon":464,"href":465,"dataGaLocation":333,"dataGaName":466},"Building","/enterprise/","enterprise",{"text":468,"config":469},"Small Business",{"icon":470,"href":471,"dataGaLocation":333,"dataGaName":472},"Work","/small-business/","small business",{"text":474,"config":475},"Public Sector",{"icon":476,"href":477,"dataGaLocation":333,"dataGaName":478},"Organization","/solutions/public-sector/","public sector",{"text":480,"config":481},"Pricing",{"href":482,"dataGaName":483,"dataGaLocation":333,"dataNavLevelOne":483},"/pricing/","pricing",{"text":485,"config":486,"menu":488},"Resources",{"dataNavLevelOne":487},"resources",{"type":384,"link":489,"columns":493,"feature":587},{"text":490,"config":491},"View all resources",{"href":492,"dataGaName":487,"dataGaLocation":333},"/resources/",[494,527,554],{"title":495,"items":496},"Getting started",[497,502,507,512,517,522],{"text":498,"config":499},"Install",{"href":500,"dataGaName":501,"dataGaLocation":333},"/install/","install",{"text":503,"config":504},"Quick start guides",{"href":505,"dataGaName":506,"dataGaLocation":333},"/get-started/","quick setup checklists",{"text":508,"config":509},"Learn",{"href":510,"dataGaLocation":333,"dataGaName":511},"https://university.gitlab.com/","learn",{"text":513,"config":514},"Product documentation",{"href":515,"dataGaName":516,"dataGaLocation":333},"https://docs.gitlab.com/","product documentation",{"text":518,"config":519},"Best practice videos",{"href":520,"dataGaName":521,"dataGaLocation":333},"/getting-started-videos/","best practice videos",{"text":523,"config":524},"Integrations",{"href":525,"dataGaName":526,"dataGaLocation":333},"/integrations/","integrations",{"title":528,"items":529},"Discover",[530,535,540,545,549],{"text":531,"config":532},"Customer success stories",{"href":533,"dataGaName":534,"dataGaLocation":333},"/customers/","customer success stories",{"text":536,"config":537},"Blog",{"href":538,"dataGaName":539,"dataGaLocation":333},"/blog/","blog",{"text":541,"config":542},"Demo Hub",{"href":543,"dataGaName":544,"dataGaLocation":333},"/demo-hub/","demo hub",{"text":546,"config":547},"The Source",{"href":548,"dataGaName":539,"dataGaLocation":333},"/the-source/",{"text":550,"config":551},"Remote",{"href":552,"dataGaName":553,"dataGaLocation":333},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":555,"items":556},"Connect",[557,562,567,572,577,582],{"text":558,"config":559},"GitLab Services",{"href":560,"dataGaName":561,"dataGaLocation":333},"/services/","services",{"text":563,"config":564},"Contribute",{"href":565,"dataGaName":566,"dataGaLocation":333},"https://contributors.gitlab.com","contribute",{"text":568,"config":569},"Community",{"href":570,"dataGaName":571,"dataGaLocation":333},"/community/","community",{"text":573,"config":574},"Forum",{"href":575,"dataGaName":576,"dataGaLocation":333},"https://forum.gitlab.com/","forum",{"text":578,"config":579},"Events",{"href":580,"dataGaName":581,"dataGaLocation":333},"/events/","events",{"text":583,"config":584},"Partners",{"href":585,"dataGaName":586,"dataGaLocation":333},"/partners/","partners",{"config":588,"title":591,"text":592,"link":593},{"background":589,"textColor":590},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":594,"config":595},"Read the latest",{"href":596,"dataGaName":597,"dataGaLocation":333},"/whats-new/","whats new",{"text":599,"config":600,"menu":602},"Company",{"dataNavLevelOne":601},"company",{"type":384,"columns":603},[604],{"items":605},[606,611,617,619,624,629,634,639,644,649],{"text":607,"config":608},"About",{"href":609,"dataGaName":610,"dataGaLocation":333},"/company/","about",{"text":612,"config":613,"footerGa":616},"Jobs",{"href":614,"dataGaName":615,"dataGaLocation":333},"/jobs/","jobs",{"dataGaName":615},{"text":578,"config":618},{"href":580,"dataGaName":581,"dataGaLocation":333},{"text":620,"config":621},"Leadership",{"href":622,"dataGaName":623,"dataGaLocation":333},"/company/team/e-group/","leadership",{"text":625,"config":626},"Handbook",{"href":627,"dataGaName":628,"dataGaLocation":333},"https://handbook.gitlab.com/","handbook",{"text":630,"config":631},"Investor relations",{"href":632,"dataGaName":633,"dataGaLocation":333},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":635,"config":636},"Trust Center",{"href":637,"dataGaName":638,"dataGaLocation":333},"/security/","trust center",{"text":640,"config":641},"AI Transparency Center",{"href":642,"dataGaName":643,"dataGaLocation":333},"/ai-transparency-center/","ai transparency center",{"text":645,"config":646},"Newsletter",{"href":647,"dataGaName":648,"dataGaLocation":333},"/company/contact/#contact-forms","newsletter",{"text":650,"config":651},"Press",{"href":652,"dataGaName":653,"dataGaLocation":333},"/press/","press",{"text":655,"config":656,"menu":657},"Contact us",{"dataNavLevelOne":601},{"type":384,"columns":658},[659],{"items":660},[661,666,671],{"text":662,"config":663},"Talk to sales",{"href":664,"dataGaName":665,"dataGaLocation":333},"/sales/","talk to sales",{"text":667,"config":668},"Support portal",{"href":669,"dataGaName":670,"dataGaLocation":333},"https://support.gitlab.com/hc/en-us","support portal",{"text":672,"config":673},"Customer portal",{"href":674,"dataGaName":675,"dataGaLocation":333},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":677,"login":678,"suggestions":685},"Close",{"text":679,"link":680},"To search repositories and projects, login to",{"text":681,"config":682},"gitlab.com",{"href":347,"dataGaName":683,"dataGaLocation":684},"search login","search",{"text":686,"default":687},"Suggestions",[688,690,694,696,700,704],{"text":364,"config":689},{"href":369,"dataGaName":364,"dataGaLocation":684},{"text":691,"config":692},"Code Suggestions (AI)",{"href":693,"dataGaName":691,"dataGaLocation":684},"/solutions/code-suggestions/",{"text":400,"config":695},{"href":402,"dataGaName":400,"dataGaLocation":684},{"text":697,"config":698},"GitLab on AWS",{"href":699,"dataGaName":697,"dataGaLocation":684},"/partners/technology-partners/aws/",{"text":701,"config":702},"GitLab on Google Cloud",{"href":703,"dataGaName":701,"dataGaLocation":684},"/partners/technology-partners/google-cloud-platform/",{"text":705,"config":706},"Why GitLab?",{"href":377,"dataGaName":705,"dataGaLocation":684},{"freeTrial":708,"mobileIcon":713,"desktopIcon":718,"secondaryButton":721},{"text":709,"config":710},"Start free trial",{"href":711,"dataGaName":338,"dataGaLocation":712},"https://gitlab.com/-/trials/new/","nav",{"altText":714,"config":715},"Gitlab Icon",{"src":716,"dataGaName":717,"dataGaLocation":712},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":714,"config":719},{"src":720,"dataGaName":717,"dataGaLocation":712},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":722,"config":723},"Get Started",{"href":724,"dataGaName":725,"dataGaLocation":712},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":727,"mobileIcon":731,"desktopIcon":733},{"text":728,"config":729},"Learn more about GitLab Duo",{"href":369,"dataGaName":730,"dataGaLocation":712},"gitlab duo",{"altText":714,"config":732},{"src":716,"dataGaName":717,"dataGaLocation":712},{"altText":714,"config":734},{"src":720,"dataGaName":717,"dataGaLocation":712},{"button":736,"mobileIcon":741,"desktopIcon":743},{"text":737,"config":738},"/switch",{"href":739,"dataGaName":740,"dataGaLocation":712},"#contact","switch",{"altText":714,"config":742},{"src":716,"dataGaName":717,"dataGaLocation":712},{"altText":714,"config":744},{"src":745,"dataGaName":717,"dataGaLocation":712},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":747,"mobileIcon":752,"desktopIcon":754},{"text":748,"config":749},"Back to pricing",{"href":482,"dataGaName":750,"dataGaLocation":712,"icon":751},"back to pricing","GoBack",{"altText":714,"config":753},{"src":716,"dataGaName":717,"dataGaLocation":712},{"altText":714,"config":755},{"src":720,"dataGaName":717,"dataGaLocation":712},{"title":757,"titleMobile":758,"button":759,"config":764},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":375,"config":760},{"href":761,"dataGaName":762,"dataGaLocation":763},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":765,"disabled":310},"release",{"data":767},{"text":768,"source":769,"edit":775,"contribute":780,"config":785,"items":790,"minimal":1000},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":770,"config":771},"View page source",{"href":772,"dataGaName":773,"dataGaLocation":774},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":776,"config":777},"Edit this page",{"href":778,"dataGaName":779,"dataGaLocation":774},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":781,"config":782},"Please contribute",{"href":783,"dataGaName":784,"dataGaLocation":774},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":786,"facebook":787,"youtube":788,"linkedin":789},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[791,838,892,936,968],{"title":480,"links":792,"subMenu":807},[793,797,802],{"text":794,"config":795},"View plans",{"href":482,"dataGaName":796,"dataGaLocation":774},"view plans",{"text":798,"config":799},"Why Premium?",{"href":800,"dataGaName":801,"dataGaLocation":774},"/pricing/premium/","why premium",{"text":803,"config":804},"Why Ultimate?",{"href":805,"dataGaName":806,"dataGaLocation":774},"/pricing/ultimate/","why ultimate",[808],{"title":809,"links":810},"Contact Us",[811,814,816,818,823,828,833],{"text":812,"config":813},"Contact sales",{"href":664,"dataGaName":343,"dataGaLocation":774},{"text":667,"config":815},{"href":669,"dataGaName":670,"dataGaLocation":774},{"text":672,"config":817},{"href":674,"dataGaName":675,"dataGaLocation":774},{"text":819,"config":820},"Status",{"href":821,"dataGaName":822,"dataGaLocation":774},"https://status.gitlab.com/","status",{"text":824,"config":825},"Terms of use",{"href":826,"dataGaName":827,"dataGaLocation":774},"/terms/","terms of use",{"text":829,"config":830},"Privacy statement",{"href":831,"dataGaName":832,"dataGaLocation":774},"/privacy/","privacy statement",{"text":834,"config":835},"Cookie preferences",{"dataGaName":836,"dataGaLocation":774,"id":837,"isOneTrustButton":313},"cookie preferences","ot-sdk-btn",{"title":380,"links":839,"subMenu":848},[840,844],{"text":841,"config":842},"DevSecOps platform",{"href":362,"dataGaName":843,"dataGaLocation":774},"devsecops platform",{"text":845,"config":846},"AI-Assisted Development",{"href":369,"dataGaName":847,"dataGaLocation":774},"ai-assisted development",[849],{"title":850,"links":851},"Topics",[852,857,862,867,872,877,882,887],{"text":853,"config":854},"CICD",{"href":855,"dataGaName":856,"dataGaLocation":774},"/topics/ci-cd/","cicd",{"text":858,"config":859},"GitOps",{"href":860,"dataGaName":861,"dataGaLocation":774},"/topics/gitops/","gitops",{"text":863,"config":864},"DevOps",{"href":865,"dataGaName":866,"dataGaLocation":774},"/topics/devops/","devops",{"text":868,"config":869},"Version Control",{"href":870,"dataGaName":871,"dataGaLocation":774},"/topics/version-control/","version control",{"text":873,"config":874},"DevSecOps",{"href":875,"dataGaName":876,"dataGaLocation":774},"/topics/devsecops/","devsecops",{"text":878,"config":879},"Cloud Native",{"href":880,"dataGaName":881,"dataGaLocation":774},"/topics/cloud-native/","cloud native",{"text":883,"config":884},"AI for Coding",{"href":885,"dataGaName":886,"dataGaLocation":774},"/topics/devops/ai-for-coding/","ai for coding",{"text":888,"config":889},"Agentic AI",{"href":890,"dataGaName":891,"dataGaLocation":774},"/topics/agentic-ai/","agentic ai",{"title":893,"links":894},"Solutions",[895,897,899,904,908,911,915,918,920,923,926,931],{"text":424,"config":896},{"href":419,"dataGaName":424,"dataGaLocation":774},{"text":413,"config":898},{"href":396,"dataGaName":397,"dataGaLocation":774},{"text":900,"config":901},"Agile development",{"href":902,"dataGaName":903,"dataGaLocation":774},"/solutions/agile-delivery/","agile delivery",{"text":905,"config":906},"SCM",{"href":409,"dataGaName":907,"dataGaLocation":774},"source code management",{"text":853,"config":909},{"href":402,"dataGaName":910,"dataGaLocation":774},"continuous integration & delivery",{"text":912,"config":913},"Value stream management",{"href":452,"dataGaName":914,"dataGaLocation":774},"value stream management",{"text":858,"config":916},{"href":917,"dataGaName":861,"dataGaLocation":774},"/solutions/gitops/",{"text":462,"config":919},{"href":465,"dataGaName":466,"dataGaLocation":774},{"text":921,"config":922},"Small business",{"href":471,"dataGaName":472,"dataGaLocation":774},{"text":924,"config":925},"Public sector",{"href":477,"dataGaName":478,"dataGaLocation":774},{"text":927,"config":928},"Education",{"href":929,"dataGaName":930,"dataGaLocation":774},"/solutions/education/","education",{"text":932,"config":933},"Financial services",{"href":934,"dataGaName":935,"dataGaLocation":774},"/solutions/finance/","financial services",{"title":485,"links":937},[938,940,942,944,947,949,952,954,956,958,960,962,964,966],{"text":498,"config":939},{"href":500,"dataGaName":501,"dataGaLocation":774},{"text":503,"config":941},{"href":505,"dataGaName":506,"dataGaLocation":774},{"text":508,"config":943},{"href":510,"dataGaName":511,"dataGaLocation":774},{"text":513,"config":945},{"href":515,"dataGaName":946,"dataGaLocation":774},"docs",{"text":536,"config":948},{"href":538,"dataGaName":539,"dataGaLocation":774},{"text":950,"config":951},"What's new",{"href":596,"dataGaName":597,"dataGaLocation":774},{"text":531,"config":953},{"href":533,"dataGaName":534,"dataGaLocation":774},{"text":550,"config":955},{"href":552,"dataGaName":553,"dataGaLocation":774},{"text":558,"config":957},{"href":560,"dataGaName":561,"dataGaLocation":774},{"text":563,"config":959},{"href":565,"dataGaName":566,"dataGaLocation":774},{"text":568,"config":961},{"href":570,"dataGaName":571,"dataGaLocation":774},{"text":573,"config":963},{"href":575,"dataGaName":576,"dataGaLocation":774},{"text":578,"config":965},{"href":580,"dataGaName":581,"dataGaLocation":774},{"text":583,"config":967},{"href":585,"dataGaName":586,"dataGaLocation":774},{"title":599,"links":969},[970,972,974,976,978,980,984,989,991,993,995],{"text":607,"config":971},{"href":609,"dataGaName":601,"dataGaLocation":774},{"text":612,"config":973},{"href":614,"dataGaName":615,"dataGaLocation":774},{"text":620,"config":975},{"href":622,"dataGaName":623,"dataGaLocation":774},{"text":625,"config":977},{"href":627,"dataGaName":628,"dataGaLocation":774},{"text":630,"config":979},{"href":632,"dataGaName":633,"dataGaLocation":774},{"text":981,"config":982},"Sustainability",{"href":983,"dataGaName":981,"dataGaLocation":774},"/sustainability/",{"text":985,"config":986},"Diversity, inclusion and belonging (DIB)",{"href":987,"dataGaName":988,"dataGaLocation":774},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":635,"config":990},{"href":637,"dataGaName":638,"dataGaLocation":774},{"text":645,"config":992},{"href":647,"dataGaName":648,"dataGaLocation":774},{"text":650,"config":994},{"href":652,"dataGaName":653,"dataGaLocation":774},{"text":996,"config":997},"Modern Slavery Transparency Statement",{"href":998,"dataGaName":999,"dataGaLocation":774},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":1001},[1002,1005,1008],{"text":1003,"config":1004},"Terms",{"href":826,"dataGaName":827,"dataGaLocation":774},{"text":1006,"config":1007},"Cookies",{"dataGaName":836,"dataGaLocation":774,"id":837,"isOneTrustButton":313},{"text":1009,"config":1010},"Privacy",{"href":831,"dataGaName":832,"dataGaLocation":774},[1012,1026],{"id":1013,"title":7,"body":309,"config":1014,"content":1016,"description":309,"extension":1020,"meta":1021,"navigation":313,"path":1022,"seo":1023,"stem":1024,"__hash__":1025},"blogAuthors/en-us/blog/authors/mike-eddington.yml",{"template":1015},"BlogAuthor",{"name":7,"config":1017},{"headshot":1018,"ctfId":1019},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659488/Blog/Author%20Headshots/gitlab-logo-extra-whitespace.png","q5tK0TgB1ZovSwShKSvOJ","yml",{},"/en-us/blog/authors/mike-eddington",{},"en-us/blog/authors/mike-eddington","kPnswGufNenJZUy4z-2ItEzY6jUb3njGroAv2l2kSXE",{"id":1027,"title":8,"body":309,"config":1028,"content":1029,"description":309,"extension":1020,"meta":1032,"navigation":313,"path":1033,"seo":1034,"stem":1035,"__hash__":1036},"blogAuthors/en-us/blog/authors/eugene-lim.yml",{"template":1015},{"name":8,"config":1030},{"headshot":1018,"ctfId":1031},"6KHdIdghkUfSTzV2MzxNcj",{},"/en-us/blog/authors/eugene-lim",{},"en-us/blog/authors/eugene-lim","iOs1OH0m1hAeHzTvx2KdBIeysBDUlPVPYg6ZoLWsLQU",[1038,1047,1055],{"title":1039,"description":1040,"heroImage":1041,"category":305,"date":1042,"authors":1043,"slug":1046,"externalUrl":309},"Confidential AI for GitLab Self-Hosted","Give developers AI coding agents in GitLab Duo without source code leaving a hardware-encrypted boundary — no GPUs needed.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1773866173/vte9qh8rriznvyclhkes.png","2026-08-06",[1044,1045],"Mathias Ewald","Martin Paloncy, Edgeless Systems","confidential-ai-for-gitlab-self-hosted",{"title":1048,"description":1049,"heroImage":1050,"category":305,"date":1051,"authors":1052,"slug":1054,"externalUrl":309},"Green DevOps: Why carbon measurement belongs in your CI/CD pipeline","CI/CD pipelines have a hidden carbon cost. Here's why measuring it matters, and how you can get started with Eco CI and Carmen in GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1765809212/noh0mdfn9o94ry9ykura.png","2026-07-09",[1053],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":1056,"description":1057,"heroImage":1058,"category":305,"date":1059,"authors":1060,"slug":1062,"externalUrl":309},"How to build CI/CD observability at scale","This practical guide to GitLab pipeline analytics helps self-managed users gain operational insights using Prometheus and Grafana.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-04-28",[1061],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"promotions":1064},[1065,1079,1091,1102],{"id":1066,"categories":1067,"header":1069,"text":1070,"button":1071,"image":1076},"ai-modernization",[1068],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":1072,"config":1073},"Get your AI maturity score",{"href":1074,"dataGaName":1075,"dataGaLocation":539},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":1077},{"src":1078},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":1080,"categories":1081,"header":1083,"text":1070,"button":1084,"image":1088},"devops-modernization",[1082,876],"product","Are you just managing tools or shipping innovation?",{"text":1085,"config":1086},"Get your DevOps maturity score",{"href":1087,"dataGaName":1075,"dataGaLocation":539},"/assessments/devops-modernization-assessment/",{"config":1089},{"src":1090},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":1092,"categories":1093,"header":1094,"text":1070,"button":1095,"image":1099},"security-modernization",[323],"Are you trading speed for security?",{"text":1096,"config":1097},"Get your security maturity score",{"href":1098,"dataGaName":1075,"dataGaLocation":539},"/assessments/security-modernization-assessment/",{"config":1100},{"src":1101},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":1103,"paths":1104,"header":1107,"text":1108,"button":1109,"image":1114},"github-azure-migration",[1105,1106],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":1110,"config":1111},"See how GitLab compares to GitHub",{"href":1112,"dataGaName":1113,"dataGaLocation":539},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":1115},{"src":1090},{"header":1117,"blurb":1118,"button":1119,"secondaryButton":1124},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":1120,"config":1121},"Get your free trial",{"href":1122,"dataGaName":338,"dataGaLocation":1123},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":812,"config":1125},{"href":664,"dataGaName":343,"dataGaLocation":1123},1786803757684]