[{"data":1,"prerenderedAt":2058},["ShallowReactive",2],{"/en-us/blog/tags/testing/":3,"navigation-en-us":19,"banner-en-us":437,"footer-en-us":452,"testing-tag-page-en-us":663},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"content":8,"config":10,"_id":12,"_type":13,"title":14,"_source":15,"_file":16,"_stem":17,"_extension":18},"/en-us/blog/tags/testing","tags",false,"",{"tag":9,"tagSlug":9},"testing",{"template":11},"BlogTag","content:en-us:blog:tags:testing.yml","yaml","Testing","content","en-us/blog/tags/testing.yml","en-us/blog/tags/testing","yml",{"_path":20,"_dir":21,"_draft":6,"_partial":6,"_locale":7,"data":22,"_id":433,"_type":13,"title":434,"_source":15,"_file":435,"_stem":436,"_extension":18},"/shared/en-us/main-navigation","en-us",{"logo":23,"freeTrial":28,"sales":33,"login":38,"items":43,"search":374,"minimal":405,"duo":424},{"config":24},{"href":25,"dataGaName":26,"dataGaLocation":27},"/","gitlab logo","header",{"text":29,"config":30},"Get free trial",{"href":31,"dataGaName":32,"dataGaLocation":27},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":34,"config":35},"Talk to sales",{"href":36,"dataGaName":37,"dataGaLocation":27},"/sales/","sales",{"text":39,"config":40},"Sign in",{"href":41,"dataGaName":42,"dataGaLocation":27},"https://gitlab.com/users/sign_in/","sign in",[44,88,184,189,295,355],{"text":45,"config":46,"cards":48,"footer":71},"Platform",{"dataNavLevelOne":47},"platform",[49,55,63],{"title":45,"description":50,"link":51},"The most comprehensive AI-powered DevSecOps Platform",{"text":52,"config":53},"Explore our Platform",{"href":54,"dataGaName":47,"dataGaLocation":27},"/platform/",{"title":56,"description":57,"link":58},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":59,"config":60},"Meet GitLab Duo",{"href":61,"dataGaName":62,"dataGaLocation":27},"/gitlab-duo/","gitlab duo ai",{"title":64,"description":65,"link":66},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":67,"config":68},"Learn more",{"href":69,"dataGaName":70,"dataGaLocation":27},"/why-gitlab/","why gitlab",{"title":72,"items":73},"Get started with",[74,79,84],{"text":75,"config":76},"Platform Engineering",{"href":77,"dataGaName":78,"dataGaLocation":27},"/solutions/platform-engineering/","platform engineering",{"text":80,"config":81},"Developer Experience",{"href":82,"dataGaName":83,"dataGaLocation":27},"/developer-experience/","Developer experience",{"text":85,"config":86},"MLOps",{"href":87,"dataGaName":85,"dataGaLocation":27},"/topics/devops/the-role-of-ai-in-devops/",{"text":89,"left":90,"config":91,"link":93,"lists":97,"footer":166},"Product",true,{"dataNavLevelOne":92},"solutions",{"text":94,"config":95},"View all Solutions",{"href":96,"dataGaName":92,"dataGaLocation":27},"/solutions/",[98,123,145],{"title":99,"description":100,"link":101,"items":106},"Automation","CI/CD and automation to accelerate deployment",{"config":102},{"icon":103,"href":104,"dataGaName":105,"dataGaLocation":27},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[107,111,115,119],{"text":108,"config":109},"CI/CD",{"href":110,"dataGaLocation":27,"dataGaName":108},"/solutions/continuous-integration/",{"text":112,"config":113},"AI-Assisted Development",{"href":61,"dataGaLocation":27,"dataGaName":114},"AI assisted development",{"text":116,"config":117},"Source Code Management",{"href":118,"dataGaLocation":27,"dataGaName":116},"/solutions/source-code-management/",{"text":120,"config":121},"Automated Software Delivery",{"href":104,"dataGaLocation":27,"dataGaName":122},"Automated software delivery",{"title":124,"description":125,"link":126,"items":131},"Security","Deliver code faster without compromising security",{"config":127},{"href":128,"dataGaName":129,"dataGaLocation":27,"icon":130},"/solutions/security-compliance/","security and compliance","ShieldCheckLight",[132,135,140],{"text":133,"config":134},"Security & Compliance",{"href":128,"dataGaLocation":27,"dataGaName":133},{"text":136,"config":137},"Software Supply Chain Security",{"href":138,"dataGaLocation":27,"dataGaName":139},"/solutions/supply-chain/","Software supply chain security",{"text":141,"config":142},"Compliance & Governance",{"href":143,"dataGaLocation":27,"dataGaName":144},"/solutions/continuous-software-compliance/","Compliance and governance",{"title":146,"link":147,"items":152},"Measurement",{"config":148},{"icon":149,"href":150,"dataGaName":151,"dataGaLocation":27},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[153,157,161],{"text":154,"config":155},"Visibility & Measurement",{"href":150,"dataGaLocation":27,"dataGaName":156},"Visibility and Measurement",{"text":158,"config":159},"Value Stream Management",{"href":160,"dataGaLocation":27,"dataGaName":158},"/solutions/value-stream-management/",{"text":162,"config":163},"Analytics & Insights",{"href":164,"dataGaLocation":27,"dataGaName":165},"/solutions/analytics-and-insights/","Analytics and insights",{"title":167,"items":168},"GitLab for",[169,174,179],{"text":170,"config":171},"Enterprise",{"href":172,"dataGaLocation":27,"dataGaName":173},"/enterprise/","enterprise",{"text":175,"config":176},"Small Business",{"href":177,"dataGaLocation":27,"dataGaName":178},"/small-business/","small business",{"text":180,"config":181},"Public Sector",{"href":182,"dataGaLocation":27,"dataGaName":183},"/solutions/public-sector/","public sector",{"text":185,"config":186},"Pricing",{"href":187,"dataGaName":188,"dataGaLocation":27,"dataNavLevelOne":188},"/pricing/","pricing",{"text":190,"config":191,"link":193,"lists":197,"feature":282},"Resources",{"dataNavLevelOne":192},"resources",{"text":194,"config":195},"View all resources",{"href":196,"dataGaName":192,"dataGaLocation":27},"/resources/",[198,231,254],{"title":199,"items":200},"Getting started",[201,206,211,216,221,226],{"text":202,"config":203},"Install",{"href":204,"dataGaName":205,"dataGaLocation":27},"/install/","install",{"text":207,"config":208},"Quick start guides",{"href":209,"dataGaName":210,"dataGaLocation":27},"/get-started/","quick setup checklists",{"text":212,"config":213},"Learn",{"href":214,"dataGaLocation":27,"dataGaName":215},"https://university.gitlab.com/","learn",{"text":217,"config":218},"Product documentation",{"href":219,"dataGaName":220,"dataGaLocation":27},"https://docs.gitlab.com/","product documentation",{"text":222,"config":223},"Best practice videos",{"href":224,"dataGaName":225,"dataGaLocation":27},"/getting-started-videos/","best practice videos",{"text":227,"config":228},"Integrations",{"href":229,"dataGaName":230,"dataGaLocation":27},"/integrations/","integrations",{"title":232,"items":233},"Discover",[234,239,244,249],{"text":235,"config":236},"Customer success stories",{"href":237,"dataGaName":238,"dataGaLocation":27},"/customers/","customer success stories",{"text":240,"config":241},"Blog",{"href":242,"dataGaName":243,"dataGaLocation":27},"/blog/","blog",{"text":245,"config":246},"Remote",{"href":247,"dataGaName":248,"dataGaLocation":27},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":250,"config":251},"TeamOps",{"href":252,"dataGaName":253,"dataGaLocation":27},"/teamops/","teamops",{"title":255,"items":256},"Connect",[257,262,267,272,277],{"text":258,"config":259},"GitLab Services",{"href":260,"dataGaName":261,"dataGaLocation":27},"/services/","services",{"text":263,"config":264},"Community",{"href":265,"dataGaName":266,"dataGaLocation":27},"/community/","community",{"text":268,"config":269},"Forum",{"href":270,"dataGaName":271,"dataGaLocation":27},"https://forum.gitlab.com/","forum",{"text":273,"config":274},"Events",{"href":275,"dataGaName":276,"dataGaLocation":27},"/events/","events",{"text":278,"config":279},"Partners",{"href":280,"dataGaName":281,"dataGaLocation":27},"/partners/","partners",{"backgroundColor":283,"textColor":284,"text":285,"image":286,"link":290},"#2f2a6b","#fff","Insights for the future of software development",{"altText":287,"config":288},"the source promo card",{"src":289},"/images/navigation/the-source-promo-card.svg",{"text":291,"config":292},"Read the latest",{"href":293,"dataGaName":294,"dataGaLocation":27},"/the-source/","the source",{"text":296,"config":297,"lists":299},"Company",{"dataNavLevelOne":298},"company",[300],{"items":301},[302,307,313,315,320,325,330,335,340,345,350],{"text":303,"config":304},"About",{"href":305,"dataGaName":306,"dataGaLocation":27},"/company/","about",{"text":308,"config":309,"footerGa":312},"Jobs",{"href":310,"dataGaName":311,"dataGaLocation":27},"/jobs/","jobs",{"dataGaName":311},{"text":273,"config":314},{"href":275,"dataGaName":276,"dataGaLocation":27},{"text":316,"config":317},"Leadership",{"href":318,"dataGaName":319,"dataGaLocation":27},"/company/team/e-group/","leadership",{"text":321,"config":322},"Team",{"href":323,"dataGaName":324,"dataGaLocation":27},"/company/team/","team",{"text":326,"config":327},"Handbook",{"href":328,"dataGaName":329,"dataGaLocation":27},"https://handbook.gitlab.com/","handbook",{"text":331,"config":332},"Investor relations",{"href":333,"dataGaName":334,"dataGaLocation":27},"https://ir.gitlab.com/","investor relations",{"text":336,"config":337},"Trust Center",{"href":338,"dataGaName":339,"dataGaLocation":27},"/security/","trust center",{"text":341,"config":342},"AI Transparency Center",{"href":343,"dataGaName":344,"dataGaLocation":27},"/ai-transparency-center/","ai transparency center",{"text":346,"config":347},"Newsletter",{"href":348,"dataGaName":349,"dataGaLocation":27},"/company/contact/","newsletter",{"text":351,"config":352},"Press",{"href":353,"dataGaName":354,"dataGaLocation":27},"/press/","press",{"text":356,"config":357,"lists":358},"Contact us",{"dataNavLevelOne":298},[359],{"items":360},[361,364,369],{"text":34,"config":362},{"href":36,"dataGaName":363,"dataGaLocation":27},"talk to sales",{"text":365,"config":366},"Get help",{"href":367,"dataGaName":368,"dataGaLocation":27},"/support/","get help",{"text":370,"config":371},"Customer portal",{"href":372,"dataGaName":373,"dataGaLocation":27},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":375,"login":376,"suggestions":383},"Close",{"text":377,"link":378},"To search repositories and projects, login to",{"text":379,"config":380},"gitlab.com",{"href":41,"dataGaName":381,"dataGaLocation":382},"search login","search",{"text":384,"default":385},"Suggestions",[386,388,392,394,398,402],{"text":56,"config":387},{"href":61,"dataGaName":56,"dataGaLocation":382},{"text":389,"config":390},"Code Suggestions (AI)",{"href":391,"dataGaName":389,"dataGaLocation":382},"/solutions/code-suggestions/",{"text":108,"config":393},{"href":110,"dataGaName":108,"dataGaLocation":382},{"text":395,"config":396},"GitLab on AWS",{"href":397,"dataGaName":395,"dataGaLocation":382},"/partners/technology-partners/aws/",{"text":399,"config":400},"GitLab on Google Cloud",{"href":401,"dataGaName":399,"dataGaLocation":382},"/partners/technology-partners/google-cloud-platform/",{"text":403,"config":404},"Why GitLab?",{"href":69,"dataGaName":403,"dataGaLocation":382},{"freeTrial":406,"mobileIcon":411,"desktopIcon":416,"secondaryButton":419},{"text":407,"config":408},"Start free trial",{"href":409,"dataGaName":32,"dataGaLocation":410},"https://gitlab.com/-/trials/new/","nav",{"altText":412,"config":413},"Gitlab Icon",{"src":414,"dataGaName":415,"dataGaLocation":410},"/images/brand/gitlab-logo-tanuki.svg","gitlab icon",{"altText":412,"config":417},{"src":418,"dataGaName":415,"dataGaLocation":410},"/images/brand/gitlab-logo-type.svg",{"text":420,"config":421},"Get Started",{"href":422,"dataGaName":423,"dataGaLocation":410},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":425,"mobileIcon":429,"desktopIcon":431},{"text":426,"config":427},"Learn more about GitLab Duo",{"href":61,"dataGaName":428,"dataGaLocation":410},"gitlab duo",{"altText":412,"config":430},{"src":414,"dataGaName":415,"dataGaLocation":410},{"altText":412,"config":432},{"src":418,"dataGaName":415,"dataGaLocation":410},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":438,"_dir":21,"_draft":6,"_partial":6,"_locale":7,"title":439,"button":440,"image":444,"config":447,"_id":449,"_type":13,"_source":15,"_file":450,"_stem":451,"_extension":18},"/shared/en-us/banner","is now in public beta!",{"text":67,"config":441},{"href":442,"dataGaName":443,"dataGaLocation":27},"/gitlab-duo/agent-platform/","duo banner",{"config":445},{"src":446},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":448},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":453,"_dir":21,"_draft":6,"_partial":6,"_locale":7,"data":454,"_id":659,"_type":13,"title":660,"_source":15,"_file":661,"_stem":662,"_extension":18},"/shared/en-us/main-footer",{"text":455,"source":456,"edit":462,"contribute":467,"config":472,"items":477,"minimal":651},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":457,"config":458},"View page source",{"href":459,"dataGaName":460,"dataGaLocation":461},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":463,"config":464},"Edit this page",{"href":465,"dataGaName":466,"dataGaLocation":461},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":468,"config":469},"Please contribute",{"href":470,"dataGaName":471,"dataGaLocation":461},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":473,"facebook":474,"youtube":475,"linkedin":476},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[478,501,558,587,621],{"title":45,"links":479,"subMenu":484},[480],{"text":481,"config":482},"DevSecOps platform",{"href":54,"dataGaName":483,"dataGaLocation":461},"devsecops platform",[485],{"title":185,"links":486},[487,491,496],{"text":488,"config":489},"View plans",{"href":187,"dataGaName":490,"dataGaLocation":461},"view plans",{"text":492,"config":493},"Why Premium?",{"href":494,"dataGaName":495,"dataGaLocation":461},"/pricing/premium/","why premium",{"text":497,"config":498},"Why Ultimate?",{"href":499,"dataGaName":500,"dataGaLocation":461},"/pricing/ultimate/","why ultimate",{"title":502,"links":503},"Solutions",[504,509,512,514,519,524,528,531,535,540,542,545,548,553],{"text":505,"config":506},"Digital transformation",{"href":507,"dataGaName":508,"dataGaLocation":461},"/topics/digital-transformation/","digital transformation",{"text":133,"config":510},{"href":128,"dataGaName":511,"dataGaLocation":461},"security & compliance",{"text":122,"config":513},{"href":104,"dataGaName":105,"dataGaLocation":461},{"text":515,"config":516},"Agile development",{"href":517,"dataGaName":518,"dataGaLocation":461},"/solutions/agile-delivery/","agile delivery",{"text":520,"config":521},"Cloud transformation",{"href":522,"dataGaName":523,"dataGaLocation":461},"/topics/cloud-native/","cloud transformation",{"text":525,"config":526},"SCM",{"href":118,"dataGaName":527,"dataGaLocation":461},"source code management",{"text":108,"config":529},{"href":110,"dataGaName":530,"dataGaLocation":461},"continuous integration & delivery",{"text":532,"config":533},"Value stream management",{"href":160,"dataGaName":534,"dataGaLocation":461},"value stream management",{"text":536,"config":537},"GitOps",{"href":538,"dataGaName":539,"dataGaLocation":461},"/solutions/gitops/","gitops",{"text":170,"config":541},{"href":172,"dataGaName":173,"dataGaLocation":461},{"text":543,"config":544},"Small business",{"href":177,"dataGaName":178,"dataGaLocation":461},{"text":546,"config":547},"Public sector",{"href":182,"dataGaName":183,"dataGaLocation":461},{"text":549,"config":550},"Education",{"href":551,"dataGaName":552,"dataGaLocation":461},"/solutions/education/","education",{"text":554,"config":555},"Financial services",{"href":556,"dataGaName":557,"dataGaLocation":461},"/solutions/finance/","financial services",{"title":190,"links":559},[560,562,564,566,569,571,573,575,577,579,581,583,585],{"text":202,"config":561},{"href":204,"dataGaName":205,"dataGaLocation":461},{"text":207,"config":563},{"href":209,"dataGaName":210,"dataGaLocation":461},{"text":212,"config":565},{"href":214,"dataGaName":215,"dataGaLocation":461},{"text":217,"config":567},{"href":219,"dataGaName":568,"dataGaLocation":461},"docs",{"text":240,"config":570},{"href":242,"dataGaName":243,"dataGaLocation":461},{"text":235,"config":572},{"href":237,"dataGaName":238,"dataGaLocation":461},{"text":245,"config":574},{"href":247,"dataGaName":248,"dataGaLocation":461},{"text":258,"config":576},{"href":260,"dataGaName":261,"dataGaLocation":461},{"text":250,"config":578},{"href":252,"dataGaName":253,"dataGaLocation":461},{"text":263,"config":580},{"href":265,"dataGaName":266,"dataGaLocation":461},{"text":268,"config":582},{"href":270,"dataGaName":271,"dataGaLocation":461},{"text":273,"config":584},{"href":275,"dataGaName":276,"dataGaLocation":461},{"text":278,"config":586},{"href":280,"dataGaName":281,"dataGaLocation":461},{"title":296,"links":588},[589,591,593,595,597,599,601,605,610,612,614,616],{"text":303,"config":590},{"href":305,"dataGaName":298,"dataGaLocation":461},{"text":308,"config":592},{"href":310,"dataGaName":311,"dataGaLocation":461},{"text":316,"config":594},{"href":318,"dataGaName":319,"dataGaLocation":461},{"text":321,"config":596},{"href":323,"dataGaName":324,"dataGaLocation":461},{"text":326,"config":598},{"href":328,"dataGaName":329,"dataGaLocation":461},{"text":331,"config":600},{"href":333,"dataGaName":334,"dataGaLocation":461},{"text":602,"config":603},"Sustainability",{"href":604,"dataGaName":602,"dataGaLocation":461},"/sustainability/",{"text":606,"config":607},"Diversity, inclusion and belonging (DIB)",{"href":608,"dataGaName":609,"dataGaLocation":461},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":336,"config":611},{"href":338,"dataGaName":339,"dataGaLocation":461},{"text":346,"config":613},{"href":348,"dataGaName":349,"dataGaLocation":461},{"text":351,"config":615},{"href":353,"dataGaName":354,"dataGaLocation":461},{"text":617,"config":618},"Modern Slavery Transparency Statement",{"href":619,"dataGaName":620,"dataGaLocation":461},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":622,"links":623},"Contact Us",[624,627,629,631,636,641,646],{"text":625,"config":626},"Contact an expert",{"href":36,"dataGaName":37,"dataGaLocation":461},{"text":365,"config":628},{"href":367,"dataGaName":368,"dataGaLocation":461},{"text":370,"config":630},{"href":372,"dataGaName":373,"dataGaLocation":461},{"text":632,"config":633},"Status",{"href":634,"dataGaName":635,"dataGaLocation":461},"https://status.gitlab.com/","status",{"text":637,"config":638},"Terms of use",{"href":639,"dataGaName":640,"dataGaLocation":461},"/terms/","terms of use",{"text":642,"config":643},"Privacy statement",{"href":644,"dataGaName":645,"dataGaLocation":461},"/privacy/","privacy statement",{"text":647,"config":648},"Cookie preferences",{"dataGaName":649,"dataGaLocation":461,"id":650,"isOneTrustButton":90},"cookie preferences","ot-sdk-btn",{"items":652},[653,655,657],{"text":637,"config":654},{"href":639,"dataGaName":640,"dataGaLocation":461},{"text":642,"config":656},{"href":644,"dataGaName":645,"dataGaLocation":461},{"text":647,"config":658},{"dataGaName":649,"dataGaLocation":461,"id":650,"isOneTrustButton":90},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",{"allPosts":664,"featuredPost":2036,"totalPagesCount":2056,"initialPosts":2057},[665,691,716,739,759,782,806,825,844,866,888,909,930,952,974,994,1015,1035,1055,1076,1095,1116,1136,1155,1174,1193,1214,1234,1257,1277,1297,1317,1336,1355,1376,1396,1416,1436,1456,1475,1494,1514,1536,1557,1578,1597,1617,1638,1658,1678,1698,1718,1739,1758,1778,1800,1819,1840,1859,1878,1896,1917,1938,1958,1978,1998,2016],{"_path":666,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":667,"content":675,"config":684,"_id":687,"_type":13,"title":688,"_source":15,"_file":689,"_stem":690,"_extension":18},"/en-us/blog/ai-in-software-development",{"title":668,"description":669,"ogTitle":668,"ogDescription":669,"noIndex":6,"ogImage":670,"ogUrl":671,"ogSiteName":672,"ogType":673,"canonicalUrls":671,"schema":674},"How AI will change software development","AI has made self-driving cars possible, so what about self-writing code? We asked 14 DevOps practitioners, industry analysts and execs to share their take on how AI will impact software development.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681709/Blog/Hero%20Images/future-of-software-ai.png","https://about.gitlab.com/blog/ai-in-software-development","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How AI will change software development\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Valerie Silverthorne\"}],\n        \"datePublished\": \"2020-10-28\",\n      }",{"title":668,"description":669,"authors":676,"heroImage":670,"date":678,"body":679,"category":680,"tags":681},[677],"Valerie Silverthorne","2020-10-28","\n\n_This is the third in our four-part series on the future of software development. Part one examines [the changing developer role](/blog/software-developer-changing-role/), part two takes a deep dive into [emerging technologies with the potential to impact development](/blog/how-tomorrows-tech-affects-sw-dev/), and part four shares [how to future-proof your developer career](/blog/future-proof-your-developer-career/)._\n\nArtificial intelligence has often been dismissed as a promising technology breakthrough that somehow remains out of reach, particularly when it comes to software development. The [role of AI in software development](/topics/devops/the-role-of-ai-in-devops/) has been written about for years and not much substantive has come of it.\n\nBut the stars may be aligning now. Developers are intrigued, and we can see that by looking at the growing popularity of the Python programming language. Stack Overflow’s [annual survey](https://insights.stackoverflow.com/survey/2020) shows Python’s rise in \"popularity\" and \"interest\" based on the number of questions members asked about it. It’s certainly the go-to language for [ML-powered chat bots](https://medium.com/better-programming/software-developer-trends-of-2020-and-beyond-d1b955bc46b8).\n\nAnd in our [2020 Global DevSecOps Survey](/developer-survey/), close to one-quarter of developers surveyed said that an understanding of AI/ML will be the most important skill for their future careers. And roughly 16% of testers said their teams are using bots right now or have an AI/ML tool in place for testing.\n\nSo if Tesla can create a self-driving car, can self-writing code be that far off? The short answer is no, at least according to the more than a dozen [DevOps](/topics/devops/) practitioners, industry analysts, and GitLab executives we spoke with about the future of software development. Here’s what they're thinking.\n\n## A gradual process\n\nAt GitLab AI feels like it will happen but gradually. \"Every set of software in the future is going to be the combination of some procedural code and some (AI) models,\" says GitLab CEO [Sid Sijbrandij](/company/team/#sytses). \"The models will eat more and more of the code over time.\" But Sid sees AI’s role as \"less of a distinct activity and more of an integrated call out to a library or a call out to a model.\"\n\nTo put it another way, senior developer evangelist [Brendan O’Leary](/company/team/#brendan) thinks it would be strange if AI weren’t playing a much more significant (and helpful) role in code development ten years from now. \"But this isn’t going to replace humans – it’s going to make the human role more critical to understand what’s important,\" Brendan says. He likens it to a detail-oriented second set of eyes that can sort through all the data quickly to focus coders on areas that need it. \"Computer-aided detection is really valuable in mammography because it’s hard to look for 1 millimeter specs of white,\" Brendan explains. \"Computer-aided detection is valuable because it surfaces the 'second look' areas to focus on. That’s the model I think we can expect when it comes to AI and software development.\"\n\n[Carlos Eduardo Arango Gutierrez](https://www.linkedin.com/in/eduardo-arango/?originalSubdomain=co), a software engineer at Red Hat (and a [GitLab Hero](/community/heroes/)) sees a big benefit to a bot \"colleague\" that will not only ID problems but will suggest solutions. \"I'm waiting for a bot that says 'oh your code is wrong and this is how you fix it,'\" Carlos says. \"You're no longer stuck because the bot is going to run the test for you and fix it.\"\n\n## Meet the Turing Bots\n\nSo there’s clearly a backstop/code testing/QA role for AI in software development, but there is more to it than that, according to Forrester Research. In its September 2020 webinar, \"The Future of Software Development: How AI Will Automate More Than 70% of Software Development,\" [Diego Lo Giudce](https://www.linkedin.com/in/diego-lo-giudice-52232/detail/recent-activity/posts/) and [Mike Gualtieri](https://www.linkedin.com/in/mgualtieri/), both vice presidents and principal analysts, make the case that so called \"Turing Bots\" will be generating code from software artifacts in ten years, or less. The technologies driving the bots include autonomous testing, auto ML (for predicting), reinforcement learning, and machine coding, the webinar says.\n\nThat’s a bold prediction and a lot to unpack for today's DevOps teams. It will be a process and culture shift, certainly, but it will also require sweeping changes in the developer thought process. Forrester recommends developers start now to \"define more precise artifacts and patterns, including app requirements, UX design and solution architecture.\"\n\n## Now take a deep breath\n\nIt’s important to remember, though, that AI is only as good as the data fed to it by humans – it’s not a substitute *for* humans. [Jose Manrique Lopez de la Fuente](https://www.linkedin.com/in/jose-manrique-lopez-de-la-fuente-b869884/), CEO at Bitergia, and also a GitLab hero, puts it this way: \"I don’t believe that we won’t need developers any more,\" he says. \"Artificial intelligence is not intelligent.\"\n\n_Wondering if your skills will keep you relevant in a time of AI overlords? Don’t miss our look at skills critical to a DevOps team's future in the fourth part of our series on the future of software development._\n","insights",[682,683,9],"DevOps","careers",{"slug":685,"featured":6,"template":686},"ai-in-software-development","BlogPost","content:en-us:blog:ai-in-software-development.yml","Ai In Software Development","en-us/blog/ai-in-software-development.yml","en-us/blog/ai-in-software-development",{"_path":692,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":693,"content":699,"config":710,"_id":712,"_type":13,"title":713,"_source":15,"_file":714,"_stem":715,"_extension":18},"/en-us/blog/align-engineering-security-appsec-tests-in-ci",{"title":694,"description":695,"ogTitle":694,"ogDescription":695,"noIndex":6,"ogImage":696,"ogUrl":697,"ogSiteName":672,"ogType":673,"canonicalUrls":697,"schema":698},"How Developer-Centric AppSec Testing Transforms DevOps Teams","Find and fix security bugs faster by implementing developer-centric application security testing in the CI pipeline. And the bonus? Engineering and security will finally be better aligned.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681513/Blog/Hero%20Images/stackhawk.jpg","https://about.gitlab.com/blog/align-engineering-security-appsec-tests-in-ci","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How developer-centric AppSec testing can dramatically change your DevOps team\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Joni Klippert\"}],\n        \"datePublished\": \"2020-08-21\",\n      }",{"title":700,"description":695,"authors":701,"heroImage":696,"date":703,"body":704,"category":705,"tags":706},"How developer-centric AppSec testing can dramatically change your DevOps team",[702],"Joni Klippert","2020-08-21","\n\nSoftware development has accelerated dramatically over the past decade. As [DevOps](/topics/devops/) became pervasive, companies went from shipping software monthly to shipping software to production frequently throughout the day. This happened as engineering teams took ownership of the deployment, performance, and resilience of their software. \n\nAnd it has paid off. Companies that have adopted DevOps are deploying software significantly faster, ultimately driving business value as innovation is more rapidly delivered to customers.\n\nSecurity, however, did not keep up. Security teams typically fell into one of two positions - the blocker of frequent deployments or the team perpetually bringing up issues in last month’s work. The need for a shift in the security model is widely known. It was the subject of the [2019 Black Hat Conference keynote](https://www.blackhat.com/us-19/briefings/schedule/index.html#every-security-team-is-a-software-team-now-17280), stats from GitLab’s [2020 Global DevSecOps Survey](https://about.gitlab.com/resources/downloads/2020-devsecops-report.pdf) make this obvious, and we’ve [shared our opinions](https://www.stackhawk.com/blog/application-security-is-broken/) at StackHawk.\n\nI believe there is a solution (or at least a *huge* step in the right direction)... developer-centric [application security](/topics/devsecops/) tooling in the CI pipeline.\n\n## The CI pipeline aligns engineering and security\n\nWhile some in the industry have been debating the term DevSecOps, leading companies have started adopting developer-first security tooling that brings alignment through the CI pipeline. Instrumented correctly, it ensures that security bugs are caught before they hit production and that the fix cycle is drastically shortened.\n\nThe legacy model has security teams running application security tests against production environments. These sort of checks are great if they are your backstop. But if this is the primary way of assessing your application’s security posture, you need to catch up with modern engineering practices. \n\nModern teams are running checks on each microservice that makes up the customer facing application, catching bugs in pipeline, and equipping developers with the information to self serve fixes and triage issues. Fix times are significantly shorter, as developers are still in the context of the code they were working on. By testing microservices vs. the end state application, the underlying bugs are much easier to find and fix. And with developer-centric tooling, developers can fix bugs themselves instead of cycling through siloed internal processes. This structure better aligns each function with their best skill sets. Engineers know the application the best and are most equipped to fix, and security teams are able to focus on strategy instead of Jira ticket creation.\n\nThe key is to get the instrumentation right (read: don’t break the build for stupid stuff).\n\n## Application security tests in CI\n\nThat sounds great in theory, but what does it look like in practice? Getting started is actually more simple than it seems. We suggest adding three application security tests to start:\n\n## Software composition analysis (SCA)\n\nSCA identifies the open source dependencies in your code base and compares that against a database of known security vulnerabilities. Some tools automatically create pull requests to patch outdated libraries. Open source use is exponentially growing, especially with chained dependencies. SCA is incredibly important, but also can be noisy with non-exploitable findings.\n\nSome of the leading vendors in the space are [GitLab](/) and [Snyk](https://snyk.io/), with up and comers like [FOSSA](https://fossa.com/) also worth paying attention to.\n\n## Dynamic application security testing (DAST)\n\nDAST runs security tests against your running application, from localhost to CI to production. The beauty of DAST is that it most closely resembles what an attacker would see, by attacking your running application and reducing false positives. The two things to be sure of as you start testing with DAST is that your scanner is finding all of your paths and API endpoints and that it is able to scan as an authenticated user.\n\nGitLab provides DAST checks for Ultimate tier customers. If you want more robust scanning options and additional functionality to manage and fix bugs, [StackHawk](https://www.stackhawk.com) is the only place to turn (obviously I’m biased here). Other solutions include legacy vendors such as [Rapid7](https://www.rapid7.com/) or open source leader [ZAP](https://www.zaproxy.org/).\n\n## Secrets detection\n\nFinally, you’ll want to ensure that you have detection for leaked secrets in code. This tooling looks for credentials, keys, or other secrets that may have unintentionally been committed to the code base by developers. GitLab includes [secret detection](https://docs.gitlab.com/ee/user/application_security/secret_detection/) in their GitLab Ultimate security tooling.\n\n## Getting started\n\nOftentimes, the thought of adding application security tests to the development workflow feels insurmountable. With a long list of priorities, engineering leadership will sometimes put this off. The reality, however, is that it is not that hard.\n\nAt StackHawk, we see many customers completing their first successful scans within 15 minutes of sign up and instrumentation in CI is literally as easy as adding [a few lines of YAML](https://docs.stackhawk.com/continuous-integration/) to your build.\n\nHere is our recommended playbook of how to get started with AppSec in CI. While this is specific to StackHawk, the principles can be applied to other tools as well.\n\n### Step 1: local testing and config\nAfter signing up and grabbing your API key, start iterating on [configuration](https://docs.stackhawk.com/hawkscan/configuration/) while testing against your application on localhost. This allows you to quickly adjust config and get successful authenticated scans running.\n\n### Step 2: non-blocking CI instrumentation\nAfter you’ve ironed out the configuration locally, add the test to your CI pipeline. At this point, it is strongly recommended to instrument as a non-blocking test so that you can triage any existing findings and smooth out any kinks.\n\n#### Step 3: bug triage - fix critical issues in flight, backlog and discuss the rest\nAfter your first non-blocking CI run, start triaging any initial findings. Any bugs marked as High criticality should likely be fixed with some sense of urgency. Lows and Mediums should be triaged depending on your application and the bugs, either quickly addressed or added to a backlog for review. Existing findings should not be the blocker for you instrumenting checks to ensure that new bugs don’t get shipped to production.\n\n#### Step 4: switch to blocking tests\nAfter ironing out config locally and in CI, and then triaging initial findings, it is time to finalize the roll out. Switch the StackHawk test to blocking mode to ensure that new security bugs don’t hit production. You can set the scanner to break on High or Medium and High, which depends on your business and the nature of the application. With this in place, you can be confident that production-ready applications have been scanned for security.\n\n## Cultural shifts: it is more than CI\nThe CI pipeline is the natural hingepoint to start aligning engineering and security. A cultural shift, however, is absolutely needed. (If you're doubtful about this, here's a frank look at why [dev and sec don't get along](/blog/developer-security-divide/).) Modern engineering teams recognize that delivering a secure application is part of quality engineering. Engineers aren’t comfortable shipping applications with UI bugs, and they shouldn’t accept security holes either. \n\nSecurity, on the other hand, needs to shift from the blocker to speedy development and to the enabler of safety in an environment of high speed delivery. Modern security engineers are ensuring that their teams are working with safe-by-default frameworks, are equipped with developer-centric tooling, and that there are proper integration tests for business logic that can’t be tested by external tooling.\n\nWhile there is significant catch up needed, it is encouraging to see the leading software teams out there testing application security on every build.\n\n## Dig deeper\n\nTo learn more about adding AppSec tests to your CI build, join me at my [How Security Belongs in DevOps](https://sched.co/dUWD) talk at GitLab Commit on August 26th. You can also always sign up for a [free StackHawk trial or demo](https://www.stackhawk.com) or talk to your GitLab sales representative about the security features in GitLab Ultimate. And for the best of both worlds, check out more details on running [automated security testing with StackHawk in GitLab](https://docs.stackhawk.com/continuous-integration/gitlab.html).\n\n_Joni Klippert is founder & CEO of StackHawk, a software-as-a-service company built to help developers find and fix security vulnerabilities in their code. Joni has been building software for developers for more than 10 years, previously serving as VP Product, VictorOps from seed stage to acquisition by Splunk. Joni is a Colorado native and holds an MBA from the University of Colorado. She currently lives in Denver with her fiance Jason and Whippet \"Q\"._\n\nCover image by [Adi Goldstein](https://unsplash.com/@adigold1) on [Unsplash](https://unsplash.com)\n{: .note}\n\n\n\n","engineering",[108,707,682,708,9,709],"collaboration","security","workflow",{"slug":711,"featured":6,"template":686},"align-engineering-security-appsec-tests-in-ci","content:en-us:blog:align-engineering-security-appsec-tests-in-ci.yml","Align Engineering Security Appsec Tests In Ci","en-us/blog/align-engineering-security-appsec-tests-in-ci.yml","en-us/blog/align-engineering-security-appsec-tests-in-ci",{"_path":717,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":718,"content":724,"config":733,"_id":735,"_type":13,"title":736,"_source":15,"_file":737,"_stem":738,"_extension":18},"/en-us/blog/arctic-engine-fuzz-testing-blog",{"title":719,"description":720,"ogTitle":719,"ogDescription":720,"noIndex":6,"ogImage":721,"ogUrl":722,"ogSiteName":672,"ogType":673,"canonicalUrls":722,"schema":723},"How Arctic Engine uses GitLab's fuzz testing","Using GitLab's fuzz testing, we discovered and fixed various real defects that could crash our software. Now we can detect vulnerabilities before merging the code.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681504/Blog/Hero%20Images/arcticengine.png","https://about.gitlab.com/blog/arctic-engine-fuzz-testing-blog","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How Arctic Engine uses GitLab's fuzz testing\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Huldra\"}],\n        \"datePublished\": \"2020-08-19\",\n      }",{"title":719,"description":720,"authors":725,"heroImage":721,"date":727,"body":728,"category":729,"tags":730},[726],"Huldra","2020-08-19","\n\n{::options parse_block_html=\"true\" /}\n\n\n\n## About Arctic Engine\n\n[Arctic Engine](https://gitlab.com/huldra/arctic) is an open-source, free game\nengine released under the [MIT license](https://opensource.org/licenses/MIT).\nArctic Engine is implemented in C++ and focuses on simplicity. Being a C++\nprogrammer and making games should not be joyless, disillusioning, and\ndiscouraging. In the '80s and '90s, a programmer could make games alone, and\nit was fun. Arctic Engine aims at making game development in C++ fun again.\n\n## Testing can be fun\n\nTesting the game engine is very important since games are usually no more\nrobust and performant than the underlying middleware or game engine. Writing\ntests by hand is time-consuming and disillusioning, and it may drain the fun\nfrom the development process. So, to my shame, I avoided writing tests in every\nway I could. For instance, I used static analyzers to detect bugs. The problem\nwith static analyzers was the lack of motivation to fix potential issues. You\nmay be unsure whether a bug is really there, and it can sometimes be hard to\nfind a way to trigger it.\n\nThe other possibility was fuzz testing. I heard about fuzzing but didn't try it\nearlier because I thought it was hard to integrate with the project. I could\nnot be more wrong. It's amazing how little effort it takes to get fuzz testing\nup and running with GitLab.\n\n## Fuzz testing and what it exposed\n\nThanks to [Sam Kerr](https://gitlab.com/stkerr) for proving me wrong about\nfuzzing by [actually fuzzing](https://gitlab.com/huldra/arctic/-/commit/946382569d88c3af7f4a7ea075c3c3cb18d3b06b)\nthe sound loader code. Arctic Engine allows loading a sound from a WAV file in\nmemory. To fuzz the loader's code, you create a small CPP file with a single\nfunction like this:\n\n```cpp\nextern \"C\" int LLVMFuzzerTestOneInput(const uint8_t * data, size_t size) {\n    std::shared_ptr\u003Carctic::SoundInstance> result = arctic::LoadWav(data, size);\n    return 0;\n}\n```\n\nThen you add ``-fsanitize=fuzzer`` flag to the CMakeLists.txt file and a few\nlines to the `.gitlab-ci.yml` file, and the fuzzing begins! You may want to\ndrop in a few WAV files to the corpus folder to help the fuzzer and speed up\nthe process, but that's optional. Ok, it was a little harder than that with the\nArctic Engine because it would output a message and quit upon processing\nunsupported file formats. Still, handling file loading errors this way was a\nbad idea, and I finally had a reason to fix it.\n\nThe fuzzer started crashing Arctic Engine: first, it triggered a signed integer\noverflow, a division by zero, and a buffer overrun. And then, the wave loader\ngot out-of-memory while trying to resample a tiny WAV file with a sampling rate\nof 1 sample per second to 44100 samples per second. Wow.\n\nWhat I liked about fuzzing is that fuzzer actually crashes your program and\nprovides you the input so you can reproduce the crash. And once you've set up\nthe test harness, the entire testing process is fully automated, saving you\ntime and effort. It's like having a personal QA team, you commit your code, and\nin a few minutes, you already have it tests-covered.\n\nThen I fuzzed the CSV and the TGA file parsers and expected to find some bugs\nin the CSV and none in the TGA. What can I say? You may not find bugs where you\nexpect them to be and find bugs where you thought there were none. The TGA\nloader crashed immediately with a buffer overrun. It did not account for files\ncontaining only a valid header but no actual image data after it.\n\n## Plans\n\nI will add a simple HTTP web server and some multiplayer network interaction\ncode to the Arctic Engine. I was putting it off for quite a while now because I\nthought testing would be a pain. Now that I know how easy it is to apply\nGitLab's fuzz testing to any data processing code, I'm very optimistic and\nsomewhat challenged. Like \"Can I make it withstand the fuzzer from the first try?\".\nIt makes writing code fun for me once again.\n\n## Further reading\n\n- [GitLab's coverage-guided fuzz testing documentation](https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/#coverage-guided-fuzz-testing)\n- [GitLab's Fuzzing 101 playlist](https://www.youtube.com/playlist?list=PL05JrBw4t0KoYzW1CR-g1rMc9Xgmnhjfe)\n\n### About the guest author\n\nHuldra is a senior videogame programmer by day maintainer of the [Arctic Engine](https://gitlab.com/huldra/arctic) by night. She started it because she wanted a game engine that kept simple things simple and made complex things possible.\n","unfiltered",[108,731,708,9,732,9],"open source","user stories",{"slug":734,"featured":6,"template":686},"arctic-engine-fuzz-testing-blog","content:en-us:blog:arctic-engine-fuzz-testing-blog.yml","Arctic Engine Fuzz Testing Blog","en-us/blog/arctic-engine-fuzz-testing-blog.yml","en-us/blog/arctic-engine-fuzz-testing-blog",{"_path":740,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":741,"content":747,"config":753,"_id":755,"_type":13,"title":756,"_source":15,"_file":757,"_stem":758,"_extension":18},"/en-us/blog/auto-devops-explained",{"title":742,"description":743,"ogTitle":742,"ogDescription":743,"noIndex":6,"ogImage":744,"ogUrl":745,"ogSiteName":672,"ogType":673,"canonicalUrls":745,"schema":746},"Auto DevOps 101: How we’re making CI/CD easier","VP of product strategy Mark Pundsack shares everything you need to know about Auto DevOps.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666915/Blog/Hero%20Images/autodevops.jpg","https://about.gitlab.com/blog/auto-devops-explained","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Auto DevOps 101: How we’re making CI/CD easier\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Valerie Silverthorne\"}],\n        \"datePublished\": \"2019-10-07\",\n      }",{"title":742,"description":743,"authors":748,"heroImage":744,"date":749,"body":750,"category":680,"tags":751},[677],"2019-10-07","\nContinuous integration and continuous delivery (CI/CD) are the gold standards of software development but they can be challenging to achieve. GitLab’s [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/) feature is designed to make the CI/CD process much easier with baked-in best practices and automation that will move code seamlessly through the entire development lifecycle. [Mark Pundsack](/company/team/#markpundsack), VP of product strategy, demonstrated how straightforward – and customizable – Auto DevOps is during our company-wide meeting, [Contribute 2019](/blog/how-we-scaled-our-summits/). Here’s what you need to know to get started with [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/).\n\n## It’s a shift... left\n\n“Auto DevOps is a [CI/CD pipeline](/topics/ci-cd/) that we have defined for you,” Mark says. “It’s basically all these best practices that we want to encourage everybody to have, and we believe are a good baseline for software development.” The goal is to have everyone set up to do CI/CD, but not just the bare minimum CI/CD, he says. “Like most people when they create a project, they start with running tests. That's the natural thing for CI. And then maybe they'll even get into CD, but they're not going to do things like [code quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html) analysis and security analysis. And we really believe in the [shift left movement](/blog/secure-containers-devops/). If you look at everything as a pipeline, we want to take security and things like that which are stuck at the end and we want to move them as far left as possible. We believe you should be checking for security even on your first deploy. So we said, okay, let's put all that in there and make a script that says this is everything that you should be doing, so let's just do it for you.”\n\nThe roots of Auto DevOps can be found in previous versions of GitLab which offered Auto Deploy. “We evolved [Auto DevOps] as the company evolved to have more and more capabilities around the DevOps lifecycle,” Mark explains. Today, Auto DevOps tackles 12 software development steps automatically. Customers wanting more flexibility can choose the [Composable Auto DevOps](/releases/2019/04/22/gitlab-11-10-released/#composable-auto-devops) option, where the template can easily be modified to suit the requirements.\n\n## The Auto DevOps process\n\nAuto DevOps begins with language detection using [Heroku buildpacks](https://devcenter.heroku.com/articles/buildpacks). While not all languages are supported, a build is created and tested automatically for the supported languages, Mark explains. Auto DevOps uses the open source version of [Code Climate](https://codeclimate.com/oss) to do code quality analysis and the results are displayed in the merge request when a change is made. After that, it’s time for security testing; including dependency scanning, license management, and container scanning. “All those things kick off again right from your first deploy,” Mark says. “We’re really taking shifting left seriously there.”\n\nAt this point developers are able to auto review applications. And once that review app is available Auto DevOps will kick off [dynamic application security testing (DAST)](https://docs.gitlab.com/ee/user/application_security/dast/). “It tries to detect security vulnerabilities in your running application,” Mark says. Finally Auto DevOps will auto deploy to either staging or production depending on how its configured. “From the first push it just automatically does all this stuff all the way – from deployment to production – which is pretty great.”\n\nAn app in production will get automatic browser performance testing which both challenges the application and records the results. [Auto monitoring](https://docs.gitlab.com/ee/topics/autodevops/#auto-monitoring) is also running so users can easily track response times, error rates, and even things like CPU and memory utilization. “All of this happens without any configuration whatsoever and that's really, that's why we put ‘auto’ in front of all of these,” Mark says. “It's really almost all the capabilities of our [DevOps lifecycle](/stages-devops-lifecycle/) thrown in by default.”\n\nWatch Mark demonstrate exactly how Auto DevOps works in the video below:\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/pPRF1HEtQ3s\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\nCover image by [Joshua Sortino](https://unsplash.com/@sortino) on [Unsplash](https://unsplash.com)\n{: .note}\n",[108,682,9,752],"production",{"slug":754,"featured":6,"template":686},"auto-devops-explained","content:en-us:blog:auto-devops-explained.yml","Auto Devops Explained","en-us/blog/auto-devops-explained.yml","en-us/blog/auto-devops-explained",{"_path":760,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":761,"content":767,"config":776,"_id":778,"_type":13,"title":779,"_source":15,"_file":780,"_stem":781,"_extension":18},"/en-us/blog/automate-to-accelerate-webcast-recap",{"title":762,"description":763,"ogTitle":762,"ogDescription":763,"noIndex":6,"ogImage":764,"ogUrl":765,"ogSiteName":672,"ogType":673,"canonicalUrls":765,"schema":766},"Testing & release automation: Accelerate development","If you’re not using automated testing, your competitors almost certainly are – catch up on our recent webcast to get started.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749671288/Blog/Hero%20Images/gitlab-live-event.png","https://about.gitlab.com/blog/automate-to-accelerate-webcast-recap","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Automate to accelerate: What you need to know about test and release automation\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Rebecca Dodd\"}],\n        \"datePublished\": \"2017-12-08\",\n      }",{"title":768,"description":763,"authors":769,"heroImage":764,"date":771,"body":772,"category":680,"tags":773},"Automate to accelerate: What you need to know about test and release automation",[770],"Rebecca Dodd","2017-12-08","\n\nBuild better software, faster, with test and release automation. Check out our recent webcast to discover why it's critical to your software development process.\n\n\u003C!-- more -->\n\nIt's been six years since Marc Andreessen's landmark \"[software is eating the world](https://www.wsj.com/articles/SB10001424053111903480904576512250915629460)\" claim, and we know now that he was on the money. Whether or not you consider yourself to be in the business of software, you are. Virtually all products and services today contain digital elements, and some component of your user experience will absolutely be online.\n\nWe've moved beyond software for manufacturing, to where 61 percent of financial services jobs are expected to be replaced by software in the 2030s. Every sort of job has the potential to be consumed by software — robo-advisors, truck drivers, grocery stockers, cashiers, and the list goes on.\n\nConsider [this statement made earlier this year by the Nvidia CEO](https://www.technologyreview.com/s/607831/nvidia-ceo-software-is-eating-the-world-but-ai-is-going-to-eat-software/): “Software is eating the world, but AI is eating software” – this puts a new software development issue in play just to stay competitive. The power of AI, when harnessed correctly, will change the landscape entirely yet again. Your key to effective AI may just well be adaptive Continuous Integration functionality.\n\nTo keep up, your release cycle needs to be efficient – we’re talking about when and how you distribute updates to your product. Enter release management.\n\nIn this webcast GitLab Senior Solutions Architect [Joel Krooswyk](/company/team/#JoelKroos) talks about:\n\n- Release management and how it's changing\n- Why automation is critical to test and release processes\n- Challenges of adopting test and release automation and how to overcome them\n- Unified continuous integration and continuous delivery\n\nAnd he demonstrates how to get started with test automation in no time at all with [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/).\n\n## Watch the recording\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/dvayJWwzfPY\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\n## Grab the slides\n\n\u003Cfigure class=\"video_container\">\n\u003Ciframe src=\"https://docs.google.com/presentation/d/e/2PACX-1vTIAQe2m4mheFhuanNFJzqlY4TdVY3f2wR1wg7L1jVdYF5tL3D1ewo0a5DzUotdAZp5X16ypME200Ev/embed?start=false&loop=false&delayms=3000\" frameborder=\"0\" width=\"960\" height=\"569\" allowfullscreen=\"true\" mozallowfullscreen=\"true\" webkitallowfullscreen=\"true\">\u003C/iframe>\n\u003C/figure>\n",[682,9,774,775],"CI","webcast",{"slug":777,"featured":6,"template":686},"automate-to-accelerate-webcast-recap","content:en-us:blog:automate-to-accelerate-webcast-recap.yml","Automate To Accelerate Webcast Recap","en-us/blog/automate-to-accelerate-webcast-recap.yml","en-us/blog/automate-to-accelerate-webcast-recap",{"_path":783,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":784,"content":790,"config":800,"_id":802,"_type":13,"title":803,"_source":15,"_file":804,"_stem":805,"_extension":18},"/en-us/blog/automating-with-gitlab-duo-part-1-generating-tests",{"title":785,"description":786,"ogTitle":785,"ogDescription":786,"noIndex":6,"ogImage":787,"ogUrl":788,"ogSiteName":672,"ogType":673,"canonicalUrls":788,"schema":789},"Automating with GitLab Duo, Part 1: Generating tests","Learn how we used the AI-driven DevSecOps platform to generate automated tests and improve our development speed and quality.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097480/Blog/Hero%20Images/Blog/Hero%20Images/blog-image-template-1800x945%20%284%29_3LZkiDjHLjhqEkvOvBsVKp_1750097480784.png","https://about.gitlab.com/blog/automating-with-gitlab-duo-part-1-generating-tests","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Automating with GitLab Duo, Part 1: Generating tests\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Byron Boots\"}],\n        \"datePublished\": \"2024-12-02\",\n      }",{"title":785,"description":786,"authors":791,"heroImage":787,"date":793,"body":794,"category":795,"tags":796},[792],"Byron Boots","2024-12-02","Automated testing is time-consuming and can feel like it’s not moving a project forward. However, as many developers have likely experienced, automated testing provides an overall positive return on investment. In building a custom module (we'll call it gitlab-helper for this article), this was particularly true.\n\nOur initial development focused on migrating tried and used functionality from existing scripts to a new module whose sole purpose was to serve as a baseline for future functionality. Although existing scripts lacked automated testing, their consistent usage was strong anecdotal evidence the functionality worked as expected.\n\nOur objective was to deliver a more mature solution to this problem, so automated testing became a necessity. This introduced the challenge of building efficiently, while balancing the time to test and ensure a robust product; and with a total of three team members, this was no small bottleneck. Therefore, the team decided to take advantage of [GitLab Duo](https://about.gitlab.com/gitlab-duo/), our suite of AI capabilities, for test generation, improving speed and quality of the delivered product.\n\nIn this three-part series on automating with GitLab Duo, we will cover:\n\n1. How we used GitLab Duo to generate tests for our code  \n2. How we worked interactively with GitLab Duo for more complex situations  \n3. The results we were able to achieve (Spoiler: 1 developer + GitLab Duo = 84% coverage in 2 days)\n\n## Using GitLab Duo to generate tests for code\n\nWhile functionality is available across tools, this article will cover using GitLab Duo in VS Code, with the [GitLab Workflow extension for VS Code](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow) to generate tests. Links to other GitLab Duo options are available in the [references](#references) below.\n\n### Install and enable GitLab Duo\n\nAs a prerequisite to using GitLab Duo, we ensured we had a GitLab Duo-enabled account. If you don't have GitLab Duo, you can [sign up for a free 60-day trial](https://about.gitlab.com/solutions/gitlab-duo-pro/sales/?type=free-trial).\n\nTo use GitLab Duo Chat in VS Code, we followed the [instructions for installation](https://docs.gitlab.com/ee/user/gitlab_duo_chat/#use-gitlab-duo-chat-in-vs-code). Then, we were able to see the GitLab Duo Chat extension on the sidebar and open the Chat window.\n\n![Ask a question window](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097489/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750097488918.png)\n\n### Generate tests with Chat\n\ngitlab-helper is a custom module built for standardizing interaction with the GitLab API across the team's work and extends other library functionalities to simplify development and scripting work. Once a method or feature was migrated to gitlab-helper and appeared to be implemented appropriately, the process to generate tests for it was simple:\n- Select the method, class, or entire file in the IDE.\n- Right-click on the selected code.\n- Under **GitLab Duo Chat**, select **Generate tests**.\n\n![Sequence to generate tests, including drop-down for generate tests](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097489/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750097488919.png)\n\nWithin a few seconds, tests were generated and presented in the GitLab Duo Chat window. These tests can be reviewed and or added to the codebase, via copy/paste, into existing or new test files. As is the case with most natural language processing generations today, particularly around context, some of the initial tests created by GitLab Duo failed, thus requiring finetuning (for instance, when dealing with nested dependencies).\n\n> **Pro tip:** GitLab Duo does not auto-create files to add generated tests to. We found it was helpful to create new test files and add a `# Tests Generated by Duo` comment at the top of them and suffix them with `_duo.py` to indicate where the tests came from.\n\nGitLab Duo provided a great starting point for building out gitlab-helper’s automated testing and greatly improved test writing efficiency and code coverage, speeding up the development process substantially. Alongside GitLab Duo, numerous iterations of valuable tests were introduced into the gitlab-helper module with human oversight.\n\nRead the next installment in this series where we share [what we learned while using GitLab Duo for generating automated tests](https://about.gitlab.com/blog/automating-with-gitlab-duo-part-2-complex-testing/) and working interactively with AI for more complex situations.\n\n## References\n\nThere’s more than one way to use GitLab Duo to generate tests, check out the other options below:\n\n* The GitLab UI  \n* [The GitLab Web IDE (VS Code in the cloud)](https://docs.gitlab.com/ee/user/project/web_ide/index.html)  \n* VS Code, with the [GitLab Workflow extension for VS Code](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow)  \n* JetBrains IDEs, with the [GitLab Duo Plugin for JetBrains](https://plugins.jetbrains.com/plugin/22325-gitlab-duo)  \n* Visual Studio for Windows, with the [GitLab Extension for Visual Studio](https://marketplace.visualstudio.com/items?itemName=GitLab.GitLabExtensionForVisualStudio)\n","ai-ml",[797,798,9,481,799],"AI/ML","tutorial","features",{"slug":801,"featured":6,"template":686},"automating-with-gitlab-duo-part-1-generating-tests","content:en-us:blog:automating-with-gitlab-duo-part-1-generating-tests.yml","Automating With Gitlab Duo Part 1 Generating Tests","en-us/blog/automating-with-gitlab-duo-part-1-generating-tests.yml","en-us/blog/automating-with-gitlab-duo-part-1-generating-tests",{"_path":807,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":808,"content":814,"config":819,"_id":821,"_type":13,"title":822,"_source":15,"_file":823,"_stem":824,"_extension":18},"/en-us/blog/automating-with-gitlab-duo-part-2-complex-testing",{"title":809,"description":810,"ogTitle":809,"ogDescription":810,"noIndex":6,"ogImage":811,"ogUrl":812,"ogSiteName":672,"ogType":673,"canonicalUrls":812,"schema":813},"Automating with GitLab Duo, Part 2: Complex testing","Find out how the GitLab team addressed more complex testing situations using GitLab Duo's AI capabilities, including ensuring that code testing followed standards.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099243/Blog/Hero%20Images/Blog/Hero%20Images/blog-image-template-1800x945%20%284%29_3LZkiDjHLjhqEkvOvBsVKp_1750099243011.png","https://about.gitlab.com/blog/automating-with-gitlab-duo-part-2-complex-testing","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Automating with GitLab Duo, Part 2: Complex testing\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Byron Boots\"}],\n        \"datePublished\": \"2024-12-10\",\n      }",{"title":809,"description":810,"authors":815,"heroImage":811,"date":816,"body":817,"category":795,"tags":818},[792],"2024-12-10","The first part of our three-part series on [test generation with GitLab Duo](https://about.gitlab.com/blog/automating-with-gitlab-duo-part-1-generating-tests/) focused on how to automate code testing. Now, we will share the lessons we learned while using AI for test generation.\n\n## Situations we encountered and how we handled them\n\nOverall, we were pleased with the results using [GitLab Duo](https://about.gitlab.com/gitlab-duo/) to generate tests on our code. As is the case with any language generation, some cases required minor adjustments such as fixing import paths or editing contents in datasets. For the more complex cases, we had to remember that AI solutions often lack context. Here's how we handled the more complex testing situations with GitLab Duo.\n\n### Updating existing test cases\n\nAs is often the case when developing a software product, we encountered instances that required updates to existing tests. Rather than manually making adjustments to a full test suite for a common issue, we took full advantage of the GitLab Duo Chat window in VS Code. For example, to refactor tests, we used the Chat prompt “Please update the provided tests to use unittest rather than pytest” followed by pasting in the tests we wanted GitLab Duo to update.\n\n![Automated test generation](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099252/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750099252303.png)\n\n\u003Cbr>\u003C/br>\n\n![Chat prompt requesting use of unittest rather than pytest](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099252/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750099252304.png)\n\n**Note:** We copy-and-pasted GitLab Duo's recommendations into our code.\n\n### Creating tests for legacy code\n\nCreating tests for legacy code we knew worked was another challenging situation we encountered. In such circumstances, it was valuable to provide error snippets alongside failing tests and ask GitLab Duo to provide new tests. A full copy-and-paste from the terminal window of noted failures and errors to Chat, along with a request to “Please explain and fix this failing test” or similar prompts, yielded a summary of the issues the test was encountering as well as a new test addressing the problem. We did find this sometimes required multiple rounds of refactoring as new test failures were identified. However, the efficiency of GitLab Duo to provide various refactored solutions was fast and a net positive on team and developer efficiency.\n\n### Working with complex or abstracted code\n\nIn other instances, the modularization or complexity of our code led to variance in GitLab Duo’s results. For instance, when generating tests, GitLab Duo sometimes generated a series of passing and failing tests caused by differences in testing approach (e.g. usage of Mock and which objects were mocked). We provided GitLab Duo its own example of a passing test and asked it to modify individual tests one at a time to match the style of the passing tests to maintain consistency. We also would provide GitLab Duo a file of functioning tests for a similar object or task so it could mirror the structure.\n\n### Ensuring generated code follows our standards\n\nWhile developing a Python module, GitLab Duo generated many tests using Mock and often they required refactoring, particularly around naming standardization. In such cases, we could leverage GitLab Duo Chat to refactor tests with instructions as to which specific test components to update. Prompting GitLab Duo for these changes was immensely faster than refactoring tests individually, as we had previousy done.\n\n### Addressing uncovered test cases\n\nGitLab Duo generated tests for additional test cases the team had not previously considered, thus increasing coverage. Luckily, we could use GitLab Duo to quickly and efficiently address these edge cases and expand testing coverage, which is a key value-add for our team to build quickly and ensure a robust product.\n\n## What we learned\n\nHere are few key lessons that have been important to our success with GitLab Duo:\n\n* **Fast and efficient for rapid development and iteration -** GitLab Duo’s role in generating automated tests has been a key accelerator in development for our team and allowed us to work faster and with greater confidence in our changes.\n* **Important to use appropriate prompts -** When using GitLab Duo for our use case, we touched on a key topic for machine learning optimization: prompt engineering. Sometimes we needed to modify our question by just a few keywords to lead to the ideal generated answer. \n* **Need understanding of underlying frameworks and code -** When it comes to any AI-generated code that makes it into a product, even if only as testing, it’s critical that we understand how the code functions so we can adequately debug as well as request informed changes.\n* **Need understanding of desired end state and standards -** Similar to following coding standards for formatting and library usage while developing without AI, it’s important to maintain the vision of what the intended outcomes look like and what standards are being adhered to when using AI. GitLab Duo needs the context to understand code standards, so it’s critical for team members using GitLab Duo to provide adequate oversight of its outputs to ensure quality and other expectations are met.\n* **GitLab Duo is not a replacement for all tests -** While we use GitLab Duo significantly for generating automated tests, it does not replace our other tests and human oversight. Functional tests, integration tests, and more still serve a valuable place in the QA process and overall software development lifecycle.\n\nIn our next article in this series, we’ll cover [a test we ran to validate the impact of GitLab Duo on our team’s automated testing](https://about.gitlab.com/blog/automating-with-gitlab-duo-part-3-validating-testing/) and discuss the impressive results we have achieved thus far.",[797,9,481],{"slug":820,"featured":6,"template":686},"automating-with-gitlab-duo-part-2-complex-testing","content:en-us:blog:automating-with-gitlab-duo-part-2-complex-testing.yml","Automating With Gitlab Duo Part 2 Complex Testing","en-us/blog/automating-with-gitlab-duo-part-2-complex-testing.yml","en-us/blog/automating-with-gitlab-duo-part-2-complex-testing",{"_path":826,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":827,"content":833,"config":838,"_id":840,"_type":13,"title":841,"_source":15,"_file":842,"_stem":843,"_extension":18},"/en-us/blog/automating-with-gitlab-duo-part-3-validating-testing",{"title":828,"description":829,"ogTitle":828,"ogDescription":829,"noIndex":6,"ogImage":830,"ogUrl":831,"ogSiteName":672,"ogType":673,"canonicalUrls":831,"schema":832},"Automating with GitLab Duo, Part 3: Validating testing","Discover what test we ran to validate the impact of GitLab Duo on our team’s automated testing – and the results we achieved.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097447/Blog/Hero%20Images/Blog/Hero%20Images/blog-image-template-1800x945%20%284%29_3LZkiDjHLjhqEkvOvBsVKp_1750097447404.png","https://about.gitlab.com/blog/automating-with-gitlab-duo-part-3-validating-testing","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Automating with GitLab Duo, Part 3: Validating testing\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Byron Boots\"}],\n        \"datePublished\": \"2024-12-17\",\n      }",{"title":828,"description":829,"authors":834,"heroImage":830,"date":835,"body":836,"category":795,"tags":837},[792],"2024-12-17","In previous entries in this series, we covered [how we used GitLab Duo to generate tests for our code](https://about.gitlab.com/blog/automating-with-gitlab-duo-part-1-generating-tests/) as well as [what we learned while using GitLab Duo for generating automated tests](https://about.gitlab.com/blog/automating-with-gitlab-duo-part-2-complex-testing/). We also shared some of the ways we addressed making changes to GitLab Duo generated tests. This last article in the series will cover a test we ran to validate the impact of GitLab Duo on our team’s automated testing and discuss the impressive results we have achieved thus far.\n\n### Validation testing results\n\nTo validate that our usage of GitLab Duo to generate tests was adding value the way we expected, we challenged ourselves and GitLab Duo to replace and increase test coverage. The team removed all previously written tests to get our test coverage to 0% and then methodically went through the repository and created new test files to store GitLab Duo-generated tests.\n\nFrom this starting point, the team followed the steps outlined in [the first blog](https://about.gitlab.com/blog/automating-with-gitlab-duo-part-1-generating-tests/) to generate tests. Tests and test files were unmodified by humans to provide a stable control group and a `Tests Generated by Duo` comment at the top of them were suffixed by `duo.py` to indicate where the tests came from.\n\nAll iterations of the tests were only done through interactions with GitLab Duo through the `Generate Tests` and GitLab Duo Chat window as outlined in [the second blog in the series](https://about.gitlab.com/blog/automating-with-gitlab-duo-part-2-complex-testing/). As we shared, we requested GitLab Duo to make updates based on encountered errors, test failures, and example code snippets for GitLab Duo to use as added context. \n\nAt all times. when testing with GitLab Duo, we were running tests and coverage reports so we could see if our GitLab Duo-generated tests were increasing testing coverage and adding value as we expected. Taking advantage of [GitLab's test coverage visualization](https://docs.gitlab.com/ee/ci/testing/test_coverage_visualization/), we were able to continuously monitor the results of our work.\n\nUltimately, after using GitLab Duo to regenerate tests for code previously covered through our mostly manual testing, we were able to achieve test coverage of 84%. This was a great accomplishment for the team because:\n\n1. It was a significant improvement from prior coverage, which was at 74%.  \n2. It took approximately two days by one engineer to achieve 84%, compared to the approximately four weeks across multiple engineers that the 74% had taken.\n\nSince this experiment, the team has increased coverage even further to 89% with the help of GitLab Duo, while continuing to introduce new features.\n\n![image of achievements](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097456/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750097456771.png)\n\nUsing GitLab Duo allowed for increased testing efficiency and coverage, and also allowed developers with lower context around existing code to write valuable tests, quickly. This has resulted in increased confidence on the team to develop new features without worrying about introducing errors.\n\n> If you'd like to [try GitLab Duo](https://about.gitlab.com/solutions/gitlab-duo-pro/sales/), sign up for a free, 60-day trial today!\n",[797,9,481,799],{"slug":839,"featured":6,"template":686},"automating-with-gitlab-duo-part-3-validating-testing","content:en-us:blog:automating-with-gitlab-duo-part-3-validating-testing.yml","Automating With Gitlab Duo Part 3 Validating Testing","en-us/blog/automating-with-gitlab-duo-part-3-validating-testing.yml","en-us/blog/automating-with-gitlab-duo-part-3-validating-testing",{"_path":845,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":846,"content":852,"config":860,"_id":862,"_type":13,"title":863,"_source":15,"_file":864,"_stem":865,"_extension":18},"/en-us/blog/browser-based-dast-feature-announcement",{"title":847,"description":848,"ogTitle":847,"ogDescription":848,"noIndex":6,"ogImage":849,"ogUrl":850,"ogSiteName":672,"ogType":673,"canonicalUrls":850,"schema":851},"Introducing browser-based DAST and integrated passive checks","We're working hard on reducing noise. Here's what you need to know about the status of our browser-based DAST offering today.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749682466/Blog/Hero%20Images/vek-labs-e8ofKlNHdsg-unsplash.jpg","https://about.gitlab.com/blog/browser-based-dast-feature-announcement","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Introducing browser-based DAST and integrated passive checks\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Isaac Dawson\"}],\n        \"datePublished\": \"2022-10-19\",\n      }",{"title":847,"description":848,"authors":853,"heroImage":849,"date":855,"body":856,"category":857,"tags":858},[854],"Isaac Dawson","2022-10-19","\n\nThe [DAST](/direction/secure/dynamic-analysis/dast/) and [Vulnerability Research](/handbook/engineering/development/sec/secure/vulnerability-research/) teams at GitLab are excited to announce we have fully [integrated passive checks](/releases/2022/08/22/gitlab-15-3-released/#browser-based-dast-passive-check-milestone) into our new [browser-based DAST analyzer](https://docs.gitlab.com/ee/user/application_security/dast/browser_based.html). Passive checks work by monitoring the network traffic to target applications while the web site is automatically crawled. This allows us to identify weaknesses that may exist without sending potentially disruptive network requests. This continues our effort to fully switch over to our browser-based analyzer for DAST in the coming months. If you are interested in using our new DAST analyzer please see our [documentation on how to configure a browser-based DAST scan](https://docs.gitlab.com/ee/user/application_security/dast/browser_based.html).\n\n## History of DAST at GitLab\n\nDAST was [officially launched](/releases/2018/01/22/gitlab-10-4-released/) as a part of the GitLab 10.4 release in January 2018. By utilizing the powerful OWASP [Zed Attack Proxy](https://www.zaproxy.org/) we were able to give our customers the ability to dynamically scan\ntheir web applications. From that initial minimal viable product, we have grown to the point where we are now running over a million scans a month.\n\nScanning web applications in the CI/CD context comes with challenges. Unlike [SAST](/direction/secure/static-analysis/sast/), which is relatively fast, dynamically scanning an application can take a significant amount of time. DAST is resource intensive as it needs to process each request and response to the target application. To ensure smooth operations for the majority of our customers we have to run under the assumption that our memory and CPU footprints should be as small as possible. Finally, and most likely the biggest pain point, is the disconnect that often occurs when trying to visualize or understand how a web application should be analyzed when one can not physically see any of the interactions between the scanner and the target application.\n\nZAP was originally built as a desktop application, meaning auditors could use it to see the target application while conducting their testing. Only by utilizing ZAP's scripting API could we make DAST scans viable in a CI/CD context. This surfaced some challenges: what is easy to configure in the UI may or may not be straightforward to adjust from a configuration file or a command line option. \n\nWhen reviewing our support tickets however, a very clear picture began to surface. Users were having the most problem with configuring authentication for their applications. This makes sense, as it is where the user interacts with the scanner the most. Modern applications almost require a browser to authenticate, due to relying on browser features like local or session storage. These features are commonly used for handling JSON Web Token (JWT) based authentication and authorization. \n\nIt should be noted that ZAP does have a browser based crawler extension, called AJAX Spider. This extension uses [Crawljax](https://github.com/crawljax/crawljax). GitLab provides this feature by supplying the correct CI/CD variable, but it is no longer recommended. AJAX Spider is however, only an extension, and does not represent a tight integration between the browser and the DAST tool. \n\n## A path forward\n\nWe needed a system where we could have full control over a browser to allow us to instrument interactions between a website and the DAST tool. A DAST tool which does not tightly integrate with a browser will be limited in its ability to fully interact with today's demanding web applications. Just some of the challenges of this are:\n\n- Single Page Applications (SPAs)\n- Complex, multi-step sign in features\n- Complex front-end frameworks  \n- Utilization of built-in browser features (e.g. usage of localStorage or sessionStorage) \n\nA new DAST tool based completely off instrumenting a browser and written in Go would give GitLab a lot more control going forward. What started as a side project during nights and weekends began to show some promise. The Vulnerability Research and DAST teams worked together to assess the viability of building out this DAST analyzer. To allow us to take advantage of new features without having to implement the entire engine, we opted to continue to use ZAP as the proxy. This means our analyzer is forwarding all the browser traffic through ZAP, but processing logic of crawling and passive checks are now done in our engine. While we've been relatively quiet on our progress, we've been incrementally rolling out new features with almost every release. The end goal is to completely replace ZAP with our own engine.\n\n## Where we are today\n\nOf course the biggest announcement is that we have fully switched over to using our own vulnerability check system for passive checks. These checks replace ZAP's \"passive\" checks. The Vulnerability Research team invested a lot of time looking over how each ZAP plugin worked and determined whether it was worth implementing, or if it should be implemented differently. Alert fatigue is a real concern we share with our customers. By reducing the noise (false positives) in our DAST offering, we hope to reduce the chance of our customers ignoring real findings. As such, our goal is to significantly reduce the noise that is usually associated with DAST scan results, and this is achieved through three different methods:\n\n1. Not implementing certain checks\n2. Reducing false positives (incorrect findings) \n3. Aggregating true positives (real findings)\n\nPoint one is worth expanding upon. DAST vulnerabilities are unique in that in some cases the fix for a vulnerability is reliant on the browser or user-agent being modified, and not  the target application. Browsers increase their security directly or have it increased by deprecating features that were used in attacks. Browsers deciding to disable Flash is a good example of this – what was a vulnerability yesterday may no longer be a vulnerability today. \n\nZAP's check for [Charset mis-match](https://www.zaproxy.org/docs/alerts/90011/) is another case in point. After [researching](https://gitlab.com/gitlab-org/gitlab/-/issues/331218) what was possible in 2022, it turns out this is no longer an issue worth reporting. Other DAST tools report similar issues that are no longer realistically exploitable or worth reporting, so this is not just an issue with ZAP. \n\nReducing false positives is another major initiative, and one that led us to create a rather unique system for creating new vulnerability checks. Traditionally, DAST tools use a plugin architecture of hardcoded vulnerability checks. While quick to implement, they can have the downside of being difficult to understand or error prone. They are also harder to implement in a standardized way. At GitLab we opted to use a configuration-file-based check system, much like today's SAST tools. \n\nFinally, aggregating true positives allows us to merge similar issues into a single finding. These types of issues are usually fixed by a single configuration change, such as adding a security header.\n\nOur passive checks are almost entirely written in YAML, using a custom specification that allows us to define a check as text. Where this is not feasible, reusable components are written that can be referenced by various checks. Below is an example check that looks for the `X-Content-Type-Options` header in HTTP response headers and reports if it is missing the `nosniff` value.\n\n```yaml\n","product",[9,799,859],"code review",{"slug":861,"featured":6,"template":686},"browser-based-dast-feature-announcement","content:en-us:blog:browser-based-dast-feature-announcement.yml","Browser Based Dast Feature Announcement","en-us/blog/browser-based-dast-feature-announcement.yml","en-us/blog/browser-based-dast-feature-announcement",{"_path":867,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":868,"content":874,"config":882,"_id":884,"_type":13,"title":885,"_source":15,"_file":886,"_stem":887,"_extension":18},"/en-us/blog/building-gitlab-with-gitlab-api-fuzzing-workflow",{"title":869,"description":870,"ogTitle":869,"ogDescription":870,"noIndex":6,"ogImage":871,"ogUrl":872,"ogSiteName":672,"ogType":673,"canonicalUrls":872,"schema":873},"Building GitLab with GitLab: Web API Fuzz Testing","Our new series shows how we dogfood new DevSecOps platform features to ready them for you. First up, security testing.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659740/Blog/Hero%20Images/building-gitlab-with-gitlab-no-type.png","https://about.gitlab.com/blog/building-gitlab-with-gitlab-api-fuzzing-workflow","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Building GitLab with GitLab: Web API Fuzz Testing\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Mike Eddington\"},{\"@type\":\"Person\",\"name\":\"Eugene Lim\"}],\n        \"datePublished\": \"2023-05-09\",\n      }",{"title":869,"description":870,"authors":875,"heroImage":871,"date":878,"body":879,"category":705,"tags":880},[876,877],"Mike Eddington","Eugene Lim","2023-05-09","\n\nAt GitLab, we try to [dogfood everything](/handbook/product/product-processes/#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.\n\n## What is Web API Fuzz Testing?\n\nWeb 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 ([SAST](https://docs.gitlab.com/ee/user/application_security/sast/)) and dynamic application security testing ([DAST](https://docs.gitlab.com/ee/user/application_security/dast/)) APIs.\n\n## Auto-generating an OpenAPI specification\nTo run the Web API Fuzzing Analyzer, you need one of the following:\n* OpenAPI Specification - Version 2 or 3\n* GraphQL Schema\n* HTTP Archive (HAR)\n* Postman Collection - Version 2.0 or 2.1\n\nAt the start of the API fuzzing project, the [API Vision working group](/company/team/structure/working-groups/api-vision/) was also working on an issue to automatically document [GitLab’s REST API endpoints in an OpenAPI specification](https://gitlab.com/groups/gitlab-org/-/epics/8636), so we worked with our colleague Andy Soiron on implementing it. Because GitLab uses the [grape](https://github.com/ruby-grape/grape) API framework, Andy had already identified and [tested](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95877) the [grape-swagger](https://github.com/ruby-grape/grape-swagger) gem that auto-generates an OpenAPI v2 specification based on existing grape annotations. For example, the following API endpoint code:\n\n```\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``` \nWill be parsed by grape-swagger into:\n\n```\n{\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```\n\n\nHowever, 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 [upload metric image endpoint](https://docs.gitlab.com/ee/api/issues.html#upload-metric-image). GitLab uses the [Workhorse](https://gitlab.com/gitlab-org/gitlab/tree/master/workhorse) smart reverse proxy to handle \"large\" HTTP requests such as file uploads. As such, file parameters must be of the type WorkhorseFile:\n\n\n```\nnamespace ':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\nBecause 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:\n\n```\n             requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The image file to be uploaded', documentation: { type: 'file' }\n```\n\nHowever, 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 `get 'books/*section/:title'` would match`books/some/section/last-words-a-memoir`. In addition, the URI would be parsed such that the `section` path parameter would have the value `some/section` and the `title` path parameter would have the value `last-words-a-memoir`.\n\nCurrently, grape-swagger does not recognize these wildcard segments as path parameters. For example, the route would generate:\n\n```\n\"paths\": {\n  \"/api/v2/books/*section/{title}\": {\n    \"get\": {\n    ...\n      \"parameters\": [\n         {\n           \"in\": \"query\", \"name\": \"*section\"\n           ...\n  }\n}\n```\n\nInstead of the expected:\n\n```\n\"paths\": {\n  \"/api/v2/books/{section}/{title}\": {\n    \"get\": {\n    ...\n      \"parameters\": [\n         {\n           \"in\": \"path\", \"name\": \"section\"\n           ...\n  }\n}\n```\n\nAs 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.\n\n## Performance tuning\n\nWith the OpenAPI specification, we could now begin with the API fuzzing. GitLab already uses the [Review Apps](https://docs.gitlab.com/ee/ci/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 [performance tuning section](https://docs.gitlab.com/ee/user/application_security/api_fuzzing/#performance-tuning-and-testing-speed) that recommends the following:\n\n* using a multi-CPU Runner\n* excluding slow operations\n* splitting a test into multiple jobs\n* excluding operations in feature branches, but not default branch\n\nThe 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 [revoke token endpoint](https://docs.gitlab.com/ee/api/personal_access_tokens.html#revoke-a-personal-access-token) that prematurely ended the fuzzing session.\n\nSplitting 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.\n\nWhile 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 [the scripts and pipeline configuration](https://gitlab.com/eugene_lim/api-fuzzing-dogfooding) for reference.\n\nBy 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.\n\n## Triaging the API fuzzing findings\n\nWith 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.\n\nFortunately, 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 [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).\n\nMany 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.\n\n## Lessons learned\n\nThe 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.\n\nThere 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 [HAR Recorder](https://gitlab.com/gitlab-org/security-products/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.\n\nFor 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.\n\nWe 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 [issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/). Use the `~\"Category:API Security\"` label when opening a new issue regarding API fuzzing to ensure it is quickly reviewed by the appropriate team members.\n",[881,708,881,9,798],"inside GitLab",{"slug":883,"featured":6,"template":686},"building-gitlab-with-gitlab-api-fuzzing-workflow","content:en-us:blog:building-gitlab-with-gitlab-api-fuzzing-workflow.yml","Building Gitlab With Gitlab Api Fuzzing Workflow","en-us/blog/building-gitlab-with-gitlab-api-fuzzing-workflow.yml","en-us/blog/building-gitlab-with-gitlab-api-fuzzing-workflow",{"_path":889,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":890,"content":896,"config":903,"_id":905,"_type":13,"title":906,"_source":15,"_file":907,"_stem":908,"_extension":18},"/en-us/blog/career-spotlight-sre-vs-devops-engineer-vs-devops-platform-engineer",{"title":891,"description":892,"ogTitle":891,"ogDescription":892,"noIndex":6,"ogImage":893,"ogUrl":894,"ogSiteName":672,"ogType":673,"canonicalUrls":894,"schema":895},"DevOps careers: SRE, engineer, and platform engineer","Where does an SRE leave off and a DevOps engineer (or platform engineer) begin? Here's what you need to know.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666685/Blog/Hero%20Images/comparing-confusing-terms-in-github-bitbucket-and-gitlab-cover.jpg","https://about.gitlab.com/blog/career-spotlight-sre-vs-devops-engineer-vs-devops-platform-engineer","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"DevOps careers: SRE, engineer, and platform engineer\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Lauren Gibbons Paul\"}],\n        \"datePublished\": \"2022-04-25\",\n      }",{"title":891,"description":892,"authors":897,"heroImage":893,"date":899,"body":900,"category":901,"tags":902},[898],"Lauren Gibbons Paul","2022-04-25","Even if you’re totally happy in your current position, it pays to keep an eye on your DevOps career path and learn about emerging roles, especially given [the way the DevOps space evolves so rapidly](https://www.simplilearn.com/is-a-devops-career-right-for-you-article). \n\nFor example, you might be wondering about the role of site reliability engineer (SRE) as opposed to DevOps engineer (and the totally new position called DevOps platform engineer, more on that later). These are all engineering positions requiring tech expertise and coding chops, but they play distinct roles on the DevOps team. Here’s what you need to know:\n\n## SRE: A seasoned role\n\nAs the title suggests, at a high level, SREs focus primarily on reliability, solving operational, scale, and uptime problems. In 2003, Google originated the SRE role to safeguard the uptime of its site, but it has evolved considerably since the advent of cloud native applications and platforms. Today, SREs concentrate on [minimizing the frequency and impact of failures](https://thenewstack.io/the-evolution-of-the-site-reliability-engineer-sre/) that can impact the overall reliability of a cloud\napplication. \n\nAccording to Glassdoor, SREs typically require a Bachelor’s or graduate engineering or computer science degree. Salaries range widely, according to Glassdoor, hitting about $120,000 after 2 to 4 years of experience but can reach up to [$300,000 and higher](https://www.glassdoor.com/Salaries/us-site-reliability-engineer-salary-SRCH_IL.0,2_IN1_KO3,28.html) at the senior level.\n\nAt least one blogger feels [the SRE title](https://rootly.com/blog/should-you-be-an-sre-or-a-devops-engineer) carries more prestige and earning potential than DevOps engineers.\n\nTypical SRE responsibilities include everything from designing, developing, installing, and maintaining software solutions to working with engineering teams to refine deployment and release processes. Collaboration and communication are important job skills for the SRE role, as they need to work closely with multiple roles across the organization. At the time of this blog's publication, there were 4,000 SRE jobs on Glassdoor. Indeed had more than 5,000 SRE postings and ZipRecruiter showed [nearly 12,000 posts](https://www.ziprecruiter.com/candidate/search?radius=5000&amp;search=site+reliability+engineer&amp;location=Remote) for remote SRE jobs.\n\nPython, Go, and Java were the [most sought-after SRE skills](https://www.indeed.com/jobs=site%20Reliability%20Engineer&amp;l&amp;vjk=829f6081218e60bd) listed on Indeed.\n\nAccording to Indeed, SREs transition to \"DevOps engineer\" at a high rate.\n\n## DevOps engineers bridge the gap\n\nDevOps engineers, on the other hand, concentrate on removing obstacles to production and automation and [making development and IT work well together](https://harness.io/blog/sre-vs-devops/).\n\nLike SREs, DevOps engineers need to be good at working and communicating with others, eliminating barriers to increase speed and quality of code delivery. With typically less need to be on call, the DevOps engineer\nmay have a more favorable work-life balance than an SRE, who can have around-the-clock call.\n\nDevOps engineer work responsibilities include such things as analysis of technology utilized within the company and then developing steps and processes to improve and expand upon them. Project management is another key function, establishing milestones for departmental contributions and establishing processes to facilitate collaboration.\n\nThe educational requirements for the two roles are comparable, with a Bachelor’s degree in computer science or engineering or higher as the usual price of admission.\n\nAccording to Glassdoor, the salary range for DevOps engineers is slightly lower than that of SREs, from a low of about $63,000 up to a high of $234,000 for someone with [2 to 4 years of experience](https://www.glassdoor.com/Salaries/us-devops-engineer-salary-SRCH_IL.0,2_IN1_KO3,18.htm). \n\nDevOps engineer positions are easier to find than SREs. Glassdoor has more than 6,000 DevOps engineer job posts. Indeed has more than 17,000. And ZipRecruiter has [more than 81,000](https://www.ziprecruiter.com/candidate/search?radius=5000&amp;search=devops+engineer&amp;location=Remote) remote DevOps engineer listings.\n\n## New to the game\n\n[Cloud native](/topics/cloud-native) development and the desire to have a unified DevOps platform have brought a new role, the DevOps platform engineer, a position that [works in parallel with the site reliability engineering function](/topics/devops/what-is-a-devops-platform-engineer/).\n\nPlatform engineering teams apply development principles to accelerate software delivery, ensuring app dev teams are productive in all aspects of the lifecycle. Platform engineers focus on the entire software development lifecycle from source to production. From this introspective process, they build a workflow that enables application\ndevelopers to [rapidly code and ship software](https://www.getambassador.io/resources/rise-of-cloud-native-engineering-organizations/).\n\nYou can find a helpful description of the roles of SRE vs. DevOps engineer vs. platform engineer [here](https://iximiuz.com/en/posts/devops-sre-and-platform-engineering/).\n\nBut it’s hard to find much career data for this emerging role. Glassdoor, Indeed, and ZipRecruiter do not yet separate out this role from the category of “DevOps engineer,” and consolidated salary and career path data is not available at this time. It is reasonable to conclude this new role will have higher pay based on rarer skill sets and job experience. Suffice to say, this is a hot area and bears watching.\n\n## Benefits of a DevOps career\n\nThe DevOps industry (and technology as a whole) is constantly evolving. And that creates a lot of opportunities. There are lots of job opportunities cropping up based on how technology changes, and this also means that you can have many chances to learn a new skill and score a role where there is an employee shortage. \n\nThere is a high demand for fresh new talent who are also eager to keep learning and adapting to an ever-changing environment. And in this evolving world of DevOps, the more change that happens means there are endless learning opportunities that will help build you up professionally. This makes you a competitive hire in the future, as well as becoming part of a technological landscape that will always be needed. \n\n## Skills required for a DevOps career\n\nWhether you have goals to become an SRE, a full-fledged DevOps engineer, or start slow and figure out where you want to work in the DevOps space, there are both soft and technical skills that definitely are or may require for you to be successful in whichever role you pursue.\n\nSome soft skills include:\n\n1. **The ability to be flexible.** Projects can stop and start and change at any time for lots of reasons. Things break and get buggy on the regular.  Being able to go with that flow and maintain good levels of productivity and professionalism will take you far. \n2. **Good communication skills.** DevOps projects are rarely simple and not only require the ability to communicate your thoughts but the patience to listen to others. \n3. **Ability to work collaboratively.** There are multiple people involved with any given DevOps project. Be prepared to have discussions about various projects and be part of the development process as a team, not as an individual.\n\nSome of the more technical skills that can help your job pursuits include (but are by no means limited to):\n\n1. **CI/CD.** Aspiring engineers should look for ways to add CI/CD concepts to existing personal projects and code. Creating your own personal projects involving CI/CD is a good way to test your deployment skills while also creating a good proof of skills reference for job interviews. \n2. **Coding skills.** Familiarity with multiple languages, such as Rust, Java, JavaScript, Ruby, Python, PHP, Bash, and many more is important for a DevOps engineer. You need to be able to write and fix issues in multiple programming languages. \n3. **Cloud computing.** Lots of application infrastructures revolved around cloud technologies, so having a basic knowledge of cloud computing will give you a competitive edge. \n4. **Automation knowledge.** A lot of working in DevOps is being able to automate time-consuming processes that need to happen all at once. Diving into some automation knowledge will help you more easily integrate with a new DevOps role. \n\n## The future of DevOps\n\nAccording to a newer Forrester report, future success in DevOps will need people and their organizations to be open to a mindset and technology shift. New tools will come around, common practices may shift, and DevOps teams need to be able to adapt to changes while continuing to work together to deliver top-quality work. \n\nA few trends to keep an eye on as time progresses are serverless computerless architecture, [the rise of DevSecOps](/topics/devsecops/), and low-code/no-code development to deploy applications swiftly with higher agility.\n","devsecops",[682,683,9],{"slug":904,"featured":6,"template":686},"career-spotlight-sre-vs-devops-engineer-vs-devops-platform-engineer","content:en-us:blog:career-spotlight-sre-vs-devops-engineer-vs-devops-platform-engineer.yml","Career Spotlight Sre Vs Devops Engineer Vs Devops Platform Engineer","en-us/blog/career-spotlight-sre-vs-devops-engineer-vs-devops-platform-engineer.yml","en-us/blog/career-spotlight-sre-vs-devops-engineer-vs-devops-platform-engineer",{"_path":910,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":911,"content":917,"config":924,"_id":926,"_type":13,"title":927,"_source":15,"_file":928,"_stem":929,"_extension":18},"/en-us/blog/collaborating-on-a-cross-stage-feature",{"title":912,"description":913,"ogTitle":912,"ogDescription":913,"noIndex":6,"ogImage":914,"ogUrl":915,"ogSiteName":672,"ogType":673,"canonicalUrls":915,"schema":916},"How we tested a feature that affected (almost) all parts of GitLab","Crowd-sourcing testing across teams","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749677856/Blog/Hero%20Images/collaboration.png","https://about.gitlab.com/blog/collaborating-on-a-cross-stage-feature","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How we tested a feature that affected (almost) all parts of GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Aakriti Gupta\"}],\n        \"datePublished\": \"2021-03-17\",\n      }",{"title":912,"description":913,"authors":918,"heroImage":914,"date":920,"body":921,"category":729,"tags":922},[919],"Aakriti Gupta","2021-03-17","\n\n{::options parse_block_html=\"true\" /}\n\n\n\nIn 13.9 Team Geo [released Maintenance Mode](https://about.gitlab.com/releases/2021/02/22/gitlab-13-9-released/#maintenance-mode), which was a large, cross stage and cross team project, a few milestones in the making.\n\nThis feature allows system administrators to put GitLab in a read-only mode. All parts of the system are affected and testing such a wide scope was challenging.\n\n## Why was testing this feature hard?\n\nAs we started testing with the QA team, it was clear that no one individual or team could know enough about the entire product to design a comprehensive QA plan. The more we tested, the more features we found to test - it was soon becoming an impossibly long list of tests to write for our small team.\n\nWe needed to prioritize manually testing the most important features, and save working on automated tests for another iteration.\n\nBut, what were the most important things to test?\n\nThis is where we decided to crowd-source testing. [We rolled-out discussion issues](https://gitlab.com/dashboard/issues?scope=all&utf8=%E2%9C%93&state=closed&author_username=aakriti.gupta&search=crowd-sourced+maintenance+mode+testing) to each of the 13 stages and asked them to contribute the three most important features that they own, that we should prioritise testing.\n\nWe used these issues to share knowledge of maintenance mode, and responsibility of its development, testing and documentation.\n\nThe response was overwhelming!\n\nProduct managers and engineers from across the development department contributed to our list of tests and collaboratively reviewed and improved documentation. They proactively asked how their features would behave and in some cases, even started MRs to fix the documentation.\n\nThe conversations helped us hone our plan for future iterations of this feature.\n\n## What we learned\n1\\. **Test iteratively and collaboratively**\n\nGet QA and developer teams working together early, instead of after development is almost done, or worse - after release. GitLab's [Quad planning](https://about.gitlab.com/handbook/engineering/quality/quality-engineering/quad-planning/) process was introduced last year to foster better collaboration between Quality, Development, UX, and Product teams. As [Jennie from QA](https://gitlab.com/jennielouie) chalked out a plan for QA together with developers, she found a few edge cases that would have otherwise been discovered too late.\n\n2\\. **Don’t hesitate to ask other teams to contribute**\n\nWhen we rolled out a dozen plus issues to all development teams, we were not sure if we’d get even a few responses, but we were overwhelmed with the interest, response and active participation that came from all the teams.\n\n3\\. **Communicate well**\n\nGive people enough and succinct information. When requesting help from other teams, help them prioritize the request by explaining the why.\n\n4\\. **Documentation as a form of developer communication**\n\nAs we worked through large documentation MRs, I realized the documentation was not only important for system administrators, but for developers of GitLab as well. Developers wanted to know how maintenance mode affected their features.\n\n5\\. **Iterate**\n\nKeep the discussions short-lived and focused on the most important aspects. Do not draw out the conversations too long, and move pending conversations over to follow-up issues.\nAs we learned of new test cases, [Nick from QA](https://gitlab.com/nwestbury) and I created follow-up test issues to resolve together with DRIs.\n\n6\\. **The more, the merrier**\n\nWhile the discussions started only with Engineering Managers and Product Managers, they often invited engineers in their conversations and this brought more eyes to the project and helped us answer a lot of unknowns.\n",[707,881,923,9,709],"remote work",{"slug":925,"featured":6,"template":686},"collaborating-on-a-cross-stage-feature","content:en-us:blog:collaborating-on-a-cross-stage-feature.yml","Collaborating On A Cross Stage Feature","en-us/blog/collaborating-on-a-cross-stage-feature.yml","en-us/blog/collaborating-on-a-cross-stage-feature",{"_path":931,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":932,"content":938,"config":946,"_id":948,"_type":13,"title":949,"_source":15,"_file":950,"_stem":951,"_extension":18},"/en-us/blog/collaboration-in-product-planning",{"title":933,"description":934,"ogTitle":933,"ogDescription":934,"noIndex":6,"ogImage":935,"ogUrl":936,"ogSiteName":672,"ogType":673,"canonicalUrls":936,"schema":937},"Successful collaboration approaches in product planning","Collaboration can be hard, but we've found a few tips and tricks that help us succeed here at GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749664102/Blog/Hero%20Images/gitlab-values-cover.png","https://about.gitlab.com/blog/collaboration-in-product-planning","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Successful approaches for team collaboration between Design, Product, Engineering, and Quality\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Jason Yavorska\"}],\n        \"datePublished\": \"2020-06-03\",\n      }",{"title":939,"description":934,"authors":940,"heroImage":935,"date":942,"body":943,"category":729,"tags":944},"Successful approaches for team collaboration between Design, Product, Engineering, and Quality",[941],"Jason Yavorska","2020-06-03","\nWithin the CI/CD sub-department here at GitLab we've been focusing on improving collaboration between each of the internal teams that contribute to our success as we move through our [product process](/handbook/product-development-flow/): Design, Product, Writing, Quality, and Engineering. We've noticed a few key points that really seem to make a big difference: \n\n## Team collaboration: Understand the problem you're trying to solve early\n\nTry to recognize early when you don't understand the problem to solve, or if there's disagreement. And even when you think you have the solution, test it out a bit and explore some edge cases (or you'll have to loop back around again). As an important benefit, a good understanding of the problem to solve will help you come up with the [smallest but valuable iteration](https://handbook.gitlab.com/handbook/values/#iteration).\n\n### Don't make it one person's job to lead the way or get things started\n\nWe expect an active contribution from everyone on the team around defining the problem and coming up with the solution for it. Everyone has an [important role to play](/handbook/product/product-processes/#pm-em-ux-and-set-quad-dris) - Product Manager, Product Designer, Quality, and Engineering. \n\nWhile Product Managers should be able to clearly articulate why something is prioritized, they should not be seen as the only facilitator of product advancement. It's a team effort, and everyone at GitLab is expected to be a [manager of one](https://handbook.gitlab.com/handbook/values/#managers-of-one). \n\n### Take advantage of everyone's different expertise\n\nSometimes, a bit of research will really align to one area of expertise. Maybe there's a business opportunity for the Product Manager, a complex visualization of data for the designer, or a hard technical challenge for the engineer - each of these can cause that person to go off and lead the way for that aspect. But they should have the support of the rest of the group. In all other cases, understanding the problem and coming up with a solution (whether using the product validation flow or just informally in an issue) is a team effort, and everyone is equally important.\n\nFollowing these principles will help your team both build trust and learn to recognize when more analysis is needed instead of pushing ahead too early. It will also help you to achieve more than any one person can do on their own.\n",[945,857,9,708,707],"design",{"slug":947,"featured":6,"template":686},"collaboration-in-product-planning","content:en-us:blog:collaboration-in-product-planning.yml","Collaboration In Product Planning","en-us/blog/collaboration-in-product-planning.yml","en-us/blog/collaboration-in-product-planning",{"_path":953,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":954,"content":960,"config":968,"_id":970,"_type":13,"title":971,"_source":15,"_file":972,"_stem":973,"_extension":18},"/en-us/blog/container-network-security-is-important",{"title":955,"description":956,"ogTitle":955,"ogDescription":956,"noIndex":6,"ogImage":957,"ogUrl":958,"ogSiteName":672,"ogType":673,"canonicalUrls":958,"schema":959},"How to secure your Kubernetes pods using GitLab Container Network Security","We help you get started with securing your Kubernetes cluster using Cilium, a GitLab-managed application.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681687/Blog/Hero%20Images/diane-helentjaris-TYk0YQbog9g-unsplash.jpg","https://about.gitlab.com/blog/container-network-security-is-important","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to secure your Kubernetes pods using GitLab Container Network Security\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Fernando Diaz\"}],\n        \"datePublished\": \"2020-10-23\",\n      }",{"title":955,"description":956,"authors":961,"heroImage":957,"date":963,"body":964,"category":708,"tags":965},[962],"Fernando Diaz","2020-10-23","\n\n{::options parse_block_html=\"true\" /}\n\nKubernetes does not come secure out of the box. There is a lot of configuration needed\nto achieve a secure cluster. One important security configuration to consider is how pods\ncommunicate with each other. This is where Network Policies come into play, making sure that\nyour pods are not exchanging data with unknown or malicious sources, which can compromise\nyour cluster.\n\n[Network Policies](https://kubernetes.io/docs/concepts/services-networking/network-policies/) are rules on how pods can communicate\nwith other pods as well as endpoints. They are pretty much a firewall for your internal cluster network.\n\nGitLab provides Container Network Security using [Cilium](https://cilium.io/) as a [GitLab-managed application](https://docs.gitlab.com/ee/user/clusters/applications.html#install-cilium-using-gitlab-cicd).\nCilium is a CNI [network plugin](https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/) for Kubernetes that can be used to implement support for Network Policies.\n\nThe video below provides an introduction on how to easily implement Network Policies from GitLab,\nas well as a demo on testing Network Policies:\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube-nocookie.com/embed/45Q__T42ZMA\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\n## Network Policies in action\n\nThere are many different ways of configuring Network Policies within your Kubernetes cluster. You can configure\nthe `ingress from` as well as the `egress to` traffic. There are four kinds of selectors\nwhich can be used to configure traffic between pods:\n\n- podSelector: Selects provided pods in the same namespace\n- namespaceSelector: Selects all pods on given namespace\n- podSelector & namespaceSelector: Selects provided pods in given namespace\n- ipBlock: Blocks external [IP CIDR](https://en.wikipedia.org/wiki/Classless_Inter-Domain_Routing) ranges provided\n\nMore information on the behavior of \"to\" and \"from\" selectors can be found in the [Kubernetes documentation](https://kubernetes.io/docs/concepts/services-networking/network-policies/#behavior-of-to-and-from-selectors).\n\nBelow is an example of a Network Policy that only allows ingress traffic\nto pod with label `app: \"notes\"` from pods with label `access: \"true\"`.\n\n```yaml\napiVersion: networking.k8s.io/v1\nkind: NetworkPolicy\nmetadata:\n  name: access-notes\nspec:\n  podSelector:\n    matchLabels:\n      app: \"notes\"\n  ingress:\n  - from:\n    - podSelector:\n        matchLabels:\n          access: \"true\"\n```\n\n## Installing Cilium as a GitLab-managed application\n\nCilium is provided by GitLab as a managed application, meaning\nthat GitLab installs and upgrades Cilium for you. There is no need\nto worry about how to get Cilium up and running. Cilium as well as your Network\nPolicies can be configured as needed.\n\nIn order to install and configure Cilium as a GitLab managed application, you can follow the steps provided in\nthe [GitLab cluster applications documentation](https://docs.gitlab.com/ee/user/clusters/applications.html#install-cilium-using-gitlab-cicd).\nThis sample project [Simply Simple Notes](https://gitlab.com/gitlab-examples/security/simply-simple-notes), is configured to use Cilium. It will install Cilium on the Kubernetes cluster associated with the project.\n\n[This guide](https://kubernetes.io/docs/tasks/administer-cluster/declare-network-policy/) can be used to test your Network Policies once Cilium has been installed.\n\n## Threat monitoring dashboard\n\nBy default Cilium installs with Hubble, a monitoring daemon which collects packet flow metrics per namespace. These\nmetrics are sent to the GitLab [Threat Monitoring dashboard](https://docs.gitlab.com/ee/user/application_security/threat_monitoring/).\n\n![threat monitoring packet metrics](https://about.gitlab.com/images/blogimages/container-network-security/packet-metrics.png)\nPacket Metrics displayed in the Threat Management dashboard\n{: .note.text-center}\n\nThe packet flow metrics collected are:\n- The total number of inbound and outbound packets for the given time period\n- The proportion of packets dropped according to the configured policies\n- The average rate per-second of forwarded and dropped packets for the requested time interval\n\nWithin the Threat Monitoring dashboard, you can also view and configure the Network Policies in your project. This makes it easy to navigate\nyour container network configuration in one interface.\n\n![threat monitoring Network Policies](https://about.gitlab.com/images/blogimages/container-network-security/network-policy.png)\nNetwork Policies displayed in the Threat Management dashboard\n{: .note.text-center}\n\nNetwork Policies can also be created and edited through an intuitive UI. You can just select the network rules you wish to use and the YAML will be automatically created and applied to your cluster. This eliminates the need to edit the complicated YAML structure for Network Policies directly, and allows you to make sure the correct rules are being applied without confusion.\n\nNetwork Rules can be created using the following rule types:\n- Labels\n- Entities\n- IP/CIDR\n- DNS\n- Level 4\n\n![threat monitoring policy creation](https://about.gitlab.com/images/blogimages/container-network-security/policy-creation.png)\nPolicy being created in the Threat Management dashboard\n{: .note.text-center}\n\n## Learn more about GitLab Security\n\nI hope this blog can help get you started with Network Policies in Kubernetes. Check out some other\nways GitLab can help with security.\n\n- [How application security engineers can use GitLab to secure their projects](/blog/secure-stage-for-appsec/)\n- [How to capitalize on GitLab Security tools with external CI](https://docs.gitlab.com/ee/integration/jenkins.html)\n- [What you need to know about Kubernetes RBAC](/blog/understanding-kubernestes-rbac/)\n\nCover image by [Diane Helentjaris](https://unsplash.com/@dhelentjaris) on [Unsplash](https://unsplash.com/photos/TYk0YQbog9g)\n{: .note}\n",[708,683,966,967,9],"kubernetes","agile",{"slug":969,"featured":6,"template":686},"container-network-security-is-important","content:en-us:blog:container-network-security-is-important.yml","Container Network Security Is Important","en-us/blog/container-network-security-is-important.yml","en-us/blog/container-network-security-is-important",{"_path":975,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":976,"content":982,"config":988,"_id":990,"_type":13,"title":991,"_source":15,"_file":992,"_stem":993,"_extension":18},"/en-us/blog/cross-project-pipeline",{"title":977,"description":978,"ogTitle":977,"ogDescription":978,"noIndex":6,"ogImage":979,"ogUrl":980,"ogSiteName":672,"ogType":673,"canonicalUrls":980,"schema":981},"How to trigger multiple pipelines using GitLab CI/CD","Discover how to trigger and visualize pipelines when you set up GitLab CI/CD across multiple projects.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666903/Blog/Hero%20Images/pipeline.jpg","https://about.gitlab.com/blog/cross-project-pipeline","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to trigger multiple pipelines using GitLab CI/CD\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Itzik Gan Baruch\"}],\n        \"datePublished\": \"2019-07-24\",\n      }",{"title":977,"description":978,"authors":983,"heroImage":979,"date":985,"body":986,"category":705,"tags":987},[984],"Itzik Gan Baruch","2019-07-24","\n[Continuous integration (CI)](/solutions/continuous-integration/) is the practice of [automating code building and testing](/topics/ci-cd/) before it is\nmerged into the master or default branch. This allows developers to merge code early and frequently, while\nmitigating the risk of introducing new bugs into the master source code repository.\n\nWhile CI verifies that new code won't break when integrated with other code in the same repo, having\nall tests pass on that repo is only the first step. After running CI on the code, it is important to\ndeploy and run tests in a live environment. Moving from [CI to continuous delivery and deployment (CD)](/solutions/continuous-integration/)\nis [the next step of DevOps maturity](/topics/devops/). Deploying and then testing again allows code in one project\nto be tested together with other components and services which may be managed in other projects.\n\n## Why do I need to verify that my code works with other components?\n\nA good example could be a\nmicroservices architecture. Usually, different [microservices](/topics/microservices/) are managed in\ndifferent [projects](https://docs.gitlab.com/ee/user/project/) – each microservice has its own\nrepository and own pipeline. It's also very common for different teams to be\nresponsible for different microservices and their pipeline configurations. As a developer you will\nwant to confirm that your code changes don't break functionality of the dependent microservices.\nTherefore, you will want to execute tests on those microservices in addition to your project tests.\n\n## The cross-project pipeline\n\nWhen running your [project pipeline](/topics/ci-cd/cicd-pipeline/), you also want to trigger cross-project or multi-project pipelines,\nwhich will eventually deploy and test the latest version of all dependent microservices. To\nachieve this goal you need an easy, flexible and convenient way to trigger other\npipelines as part of your project CI. GitLab CI/CD offers an easy way to run a cross-project\npipeline by simply adding a pipeline trigger job in the CI configuration file.\n\n## GitLab CI/CD configuration file\n\nIn GitLab CI/CD, pipelines, and their component jobs and stages, are defined in\nthe [`.gitlab-ci.yml`](https://docs.gitlab.com/ee/ci/yaml/) file for each project. The\nfile is part of the project repository. It is fully versioned and developers can edit it with any\ncommon IDE of their choice. They do not have to ask the system admin or DevOps team to make\nchanges in the pipeline configuration as it is self-service. The `.gitlab-ci.yml` file defines the structure\nand order of the pipelines and determines what to execute\nusing [GitLab Runner](https://docs.gitlab.com/runner/) (the agent that runs the jobs), and what\ndecisions to make when specific conditions are encountered, like when a process succeeds or fails.\n\n## Add a cross-project pipeline triggering job\n\nSince GitLab 11.8, GitLab provides a new CI/CD configuration syntax for triggering cross-project\npipelines found in the [pipeline configuration file](https://docs.gitlab.com/ee/ci/yaml/).\nThe following code illustrates configuring a bridge job to trigger a downstream pipeline:\n\n```\n//job1 is a job in the upstream project\ndeploy:\n\tstage: Deploy\n\tscript: this is my script\n\n//job2 is a bridge job in the upstream project which triggers cross-project pipeline\nAndroid:\n\tstage: Trigger-cross-projects\n            trigger: mobile/android\n```\n\nIn the example above, as soon as the deploy job succeeds in the deploy stage, the Android\nbridge job is going to be started. The initial status of this job will be pending. GitLab will\ncreate a downstream pipeline in the mobile/android project and, as soon as the pipeline gets created,\nthe Android job will succeed. In this case mobile/android is a full path to that project.\n\nThe user who created the upstream pipeline needs to have access rights to the downstream\nproject (mobile/android in this case). If a downstream project cannot be found, or a user does not\nhave access rights to create a pipeline there, the Android job will be marked as failed.\n\n## Browse from upstream pipeline graphs to downstream\n\nGitLab CI/CD makes it possible to visualize the pipeline configuration. In the below illustration, the\nbuild, test, and deploy stages are parts of the upstream project. Once the deploy job succeeds, four\ncross-projects will be triggered in parallel and you will be able to browse to them by clicking on\none of the downstream jobs.\n\n![Build, test and deploy stages](https://about.gitlab.com/images/blogimages/Cross-proj-img1.png){: .shadow.medium.center}\n\nIn the below illustration the Service – Finance downstream pipeline is visible. We can now scroll\nleft to the upstream pipeline, scroll right back to the downstream pipeline or select another\ndownstream pipeline.\n\n![Service – Finance pipeline](https://about.gitlab.com/images/blogimages/Cross-proj-img2.png){: .shadow.medium.center}\n\n## Specifying a downstream pipeline branch\n\nIt is possible to specify a branch name that a downstream pipeline will use:\n\n```\ntrigger:\n     project: mobile/android\n     branch: stable-11-2\n```\n\nUse a project keyword to specify the full path to a downstream project. Use a branch keyword to\nspecify a branch name. GitLab will use a commit that is currently on the HEAD of the branch\nwhen creating a downstream pipeline.\n\n## Passing variables to a downstream pipeline\n\nSometimes you might want to pass variables to a downstream pipeline. You can do that using\nthe variables keyword, just like you would when defining a regular job.\n\n```\nAndroid:\n           variable:\n\t     ENVIRONMENT: ‘This is the variable value for the downstream pipeline’\n           stage: Trigger-cross-projects\n           trigger: mobile/android\n```\nThe ENVIRONMENT variable will be passed to every job defined in a downstream pipeline. It will be\navailable as an environment variable when GitLab Runner picks a job.\n\n## Cross-project pipeline summary\n\nThe `.gitlab-ci.yml` file defines the order of the CI/CD stages, which jobs to execute, and at which\nconditions to run or skip a job's execution. Adding a 'bridge job' with the `trigger` keyword to\nthis file can be used to trigger cross-project pipelines. We can pass parameters to jobs in\ndownstream pipelines, and even define a branch that a downstream pipeline will use.\n\nPipelines can be complex structures with many sequential and parallel jobs, and as we just\nlearned, sometimes they can trigger downstream pipelines. To make it easier to understand the\nflow of a pipeline, including its downstream pipelines, GitLab has pipeline graphs for viewing\npipelines and each pipeline's status.\n\n![Service – Finance pipeline](https://about.gitlab.com/images/blogimages/Cross-proj-img4.png){: .shadow.medium.center}\n\nHey community, what else would you like me to explain in a blog post? Let me know in the comments or tweet us [@gitlab](https://twitter.com/gitlab).\n\nCover image by [Tian Kuan](https://unsplash.com/@realaxer) on [Unsplash](https://unsplash.com)\n{: .note}\n",[108,682,799,9,709],{"slug":989,"featured":6,"template":686},"cross-project-pipeline","content:en-us:blog:cross-project-pipeline.yml","Cross Project Pipeline","en-us/blog/cross-project-pipeline.yml","en-us/blog/cross-project-pipeline",{"_path":995,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":996,"content":1002,"config":1009,"_id":1011,"_type":13,"title":1012,"_source":15,"_file":1013,"_stem":1014,"_extension":18},"/en-us/blog/dast-release-first-gitlab-active-check",{"title":997,"description":998,"ogTitle":997,"ogDescription":998,"noIndex":6,"ogImage":999,"ogUrl":1000,"ogSiteName":672,"ogType":673,"canonicalUrls":1000,"schema":1001},"Introducing GitLab browser-based active checks in DAST","As of GitLab 16.4, or DAST 4.0.9, browser-based DAST active scans will search for path traversal vulnerabilities using the GitLab check 22.1 instead of the ZAP alert 6.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749664638/Blog/Hero%20Images/applicationsecurity.png","https://about.gitlab.com/blog/dast-release-first-gitlab-active-check","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Introducing GitLab browser-based active checks in DAST\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Cameron Swords\"}],\n        \"datePublished\": \"2023-10-10\",\n      }",{"title":997,"description":998,"authors":1003,"heroImage":999,"date":1005,"body":1006,"category":708,"tags":1007},[1004],"Cameron Swords","2023-10-10","\nGitLab's [DAST](/direction/secure/dynamic-analysis/dast/) and [Vulnerability Research](/handbook/engineering/development/sec/secure/vulnerability-research/) teams released the first GitLab active check in browser-based dynamic application security testing. This continues our work to integrate passive checks into browser-based DAST. As of GitLab 16.4, or DAST 4.0.9, browser-based DAST active scans will search for path traversal vulnerabilities using the GitLab check [22.1](https://docs.gitlab.com/ee/user/application_security/dast/checks/22.1.html) instead of the ZAP alert [6](https://www.zaproxy.org/docs/alerts/6/).\n\nReplacing ZAP alerts with GitLab active checks enables developers and security teams to detect vulnerabilities in modern-day web applications more effectively. Going forward, we anticipate replacing more ZAP alerts with GitLab active checks. If you are interested in using the browser-based DAST analyzer, please see: [How to configure a browser-based DAST scan documentation](https://docs.gitlab.com/ee/user/application_security/dast/browser_based.html).\n\nBelow is an explanation of how active checks work, different types of attacks, and worked examples of browser-based attacks.\n\n## How to use GitLab active checks\nCustomers who run active scans (full scans) will automatically run GitLab active checks as they are tested and released by the DAST team. Each corresponding ZAP alert will be turned off at this time.\n\nCustomers can opt out of these changes, disabling the GitLab active checks and re-enabling the ZAP alerts by adding the CI/CD variable `DAST_FF_BROWSER_BASED_ACTIVE_ATTACK: \"false\"`.\n\n## What is an active check?\nAn active check defines a series of attacks that, when run against the target web application, identify susceptibility to specific kinds of weakness ([CWE](https://cwe.mitre.org/)). Active checks are run during the [active scan](https://docs.gitlab.com/ee/user/application_security/dast/browser_based.html#active-scans) (full scan) phase of a DAST scan.\n\n## What does an active check attack do?\n[In-scope](https://docs.gitlab.com/ee/user/application_security/dast/browser_based.html#managing-scope) HTTP requests recorded during the crawl phase of the DAST scan are searched for injection locations, places in the request where an attack payload can be injected. Example injection locations include cookie values, request paths, query parameters, headers, JSON string values, XML, and inputs submitted with a form.\n\nEach attack defines payloads, which are text or binary content to inject into an HTTP request. Payloads can have prefixes (e.g. `c:\\`) or affixes (e.g. `.exe`). Payloads can be an extension of the value originally submitted with the HTTP request.\n\nEvery active check attack will be run against every crawled HTTP request's injection locations. Each injection location may have multiple attack payloads injected into it by each attack. Each unique payload injected into an injection location becomes a new HTTP request to send to the target web application. HTTP responses to these requests are used to determine if the attack succeeded.\n\n## Types of attacks\nDifferent types of attacks are necessary to detect different kinds of weaknesses.\n\n### Match response attacks\nMatch response attacks send an attack payload with the HTTP request and search the HTTP response body for unintentionally exposed content. For example, a path traversal attack that uses a payload of `/etc/passwd` might look for evidence of that file in the HTTP response body.\n\nMost attacks are match response attacks.\n\n### Timing attacks\nTiming attacks are useful for blind injection payloads where the success of the attack is determined by how long the target web application took to return the HTTP response. For example, a SQL injection attack might use a payload containing `sleep(15)` to ask the database to pause for 15 seconds and determine attack success if the target web application took longer than 15 seconds to return the HTTP response.\n\nNaive timing attacks are prone to false positives due to unpredictable timing delays introduced by factors such as variable internet speeds and cached content. To mitigate this, each DAST timing attack uses multiple payloads with individual success conditions, and each timing attack must succeed three times in a row to register as a weakness. Timing attacks run one at a time to prevent one attack from skewing the results of other attacks.\n\n### Callback attacks\nCallback attacks are useful to determine if the target web application unintentionally allows data to be exposed to an external entity. For example, a URL in a website query parameter could be injected with the callback server `https://site.com/login?redirect-to=https://callback-server.dast/123456789`. DAST determines if the target web application unintentionally made an HTTP request to an untrusted source by asking the callback server if it received a request with ID `123456789`.\n\nThe initial priority for DAST browser-based attacks is on match response and timing attacks. For callback attacks, see [Breach and Attack Simulation](https://docs.gitlab.com/ee/user/application_security/).\n\n## How are attacks defined?\nThe [Vulnerability Research team](/handbook/engineering/development/sec/secure/vulnerability-research/) writes active checks in YAML to minimize the time required to update or add new checks. A simplified example of the 22.1 path traversal attack looks as follows:\n\n```yaml\nactive_check:\n  attacks:\n    - id: 2\n      type: \"match_response\"\n      description: \"Inject /etc/passwd, report as vulnerable if the response body matches /etc/passwd file contents.\"\n      target_tech: [\"os:unix\"]\n      injection_locations_policy:\n        default:\n          locations:\n            - \"cookie_value\"\n            - \"request_parameter_value\"\n            - \"request_body_parameter_value\"\n            - \"json_value\"\n            - \"xml_value\"\n            - \"multipart_form_data_filename\"\n            - \"multipart_form_data_value\"\n      match_response_attack:\n        payloads: [\"/etc/passwd\"]\n        injections:\n          - template: \"{payload}\"\n          - template: \"{prefix}{payload}{suffix}\"\n            affixes:           \n              - prefix: \"/../../../../../../../../../../../..\"\n                suffix: \"\"\n        matchers:\n          - description: \"Check the HTTP response body to see if it contains the /etc/passwd file contents\"\n            severity: \"High\"\n            match:\n              location: \"response_body\"\n              expression: \"root:.:0:0:\"\n```\n\n## Worked example\nDuring the DAST crawl phase, DAST submits a form with an input field named `file_name` (headers simplified for brevity).\n\n```\nPOST /read-file HTTP/1.1\nAccept: text/html\nContent-Length: 20\nContent-Type: application/x-www-form-urlencoded\nHost: site.com\n\nfile_name=browserker\n```\n\nDuring the active scan phase, DAST creates attacks from crawled HTTP requests. From the above request, injection locations are found for each of the four header values, the request path `/read-file` and the form input value `browserker`. For a path traversal attack with payload `/etc/passwd`, six attack HTTP requests will be made to the target web application, each with the payload injected into the according injection location.\n\nThe attack on the form input value injection location HTTP would be:\n\n```\nPOST /read-file HTTP/1.1\nAccept: text/html\nContent-Length: 20\nContent-Type: application/x-www-form-urlencoded\nHost: site.com\n\nfile_name=/etc/passwd\n```\n\nAssuming the target web application is vulnerable to a path traversal in the form input, it might read the contents of `/etc/passwd` and return it in the HTTP response, such as:\n\n```\nHTTP/1.1 200 OK\nCache-Control: no-store, no-cache, must-revalidate, proxy-revalidate\nContent-Length: 229\nContent-Type: text/html; charset=utf-8\nDate: Mon, 25 Sep 2023 14:55:20 GMT\n\n\u003Chtml>\n\u003Cbody>\n  \u003Cdiv id=\"content\">\n    root:x:0:0:root:/root:/bin/bash\n    daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\n    bin:x:2:2:bin:/bin:/usr/sbin/nologin\n    sys:x:3:3:sys:/dev:/usr/sbin/nologin\n  \u003C/div>\n\u003C/body>\n\u003C/html>\n```\n\nThe DAST path traversal attack regular expression `root:.:0:0:` matches against the HTTP response body, so the attack is successful and a new finding is created.\n\n[Try GitLab's browser-based DAST scanning](https://docs.gitlab.com/ee/user/application_security/dast/browser_based.html).\n",[1008,857,9,799,708],"DevSecOps",{"slug":1010,"featured":6,"template":686},"dast-release-first-gitlab-active-check","content:en-us:blog:dast-release-first-gitlab-active-check.yml","Dast Release First Gitlab Active Check","en-us/blog/dast-release-first-gitlab-active-check.yml","en-us/blog/dast-release-first-gitlab-active-check",{"_path":1016,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1017,"content":1023,"config":1029,"_id":1031,"_type":13,"title":1032,"_source":15,"_file":1033,"_stem":1034,"_extension":18},"/en-us/blog/detect-application-vulnerabilities-with-gitlabs-browser-based-dast",{"title":1018,"description":1019,"ogTitle":1018,"ogDescription":1019,"noIndex":6,"ogImage":1020,"ogUrl":1021,"ogSiteName":672,"ogType":673,"canonicalUrls":1021,"schema":1022},"Detect application vulnerabilities with GitLab’s browser-based DAST","Learn why you should include dynamic application security testing as part of a defense-in-depth strategy for software development, and how to migrate from proxy-based DAST.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749664923/Blog/Hero%20Images/security-checklist.png","https://about.gitlab.com/blog/detect-application-vulnerabilities-with-gitlabs-browser-based-dast","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Detect application vulnerabilities with GitLab’s browser-based DAST\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sara Meadzinger\"}],\n        \"datePublished\": \"2024-05-13\",\n      }",{"title":1018,"description":1019,"authors":1024,"heroImage":1020,"date":1026,"body":1027,"category":708,"tags":1028},[1025],"Sara Meadzinger","2024-05-13","Proxy-based dynamic application security testing was removed in GitLab 17.0 (May 16, 2024) and replaced with GitLab's proprietary [DAST](https://docs.gitlab.com/ee/user/application_security/dast/browser/) tool (formerly called “browser-based DAST”). DAST runs automated penetration tests to find vulnerabilities in your web applications as they are running. DAST automates a hacker’s approach, simulates real-world attacks, and can identify critical threats such as cross-site scripting, a SQL injection, and cross-site request forgery. DAST is completely language-agnostic and examines your application from the outside in. \n\nWe recommend adding [DAST](https://docs.gitlab.com/ee/user/application_security/dast/browser/) to your software development security alongside other foundational [application security](https://docs.gitlab.com/ee/user/application_security/) testing such as secret detection, dependency scanning, static application security testing (SAST), container scanning, and API security testing. Including DAST in this defense-in-depth approach ensures that your team can identify and mitigate the runtime vulnerabilities and misconfigurations DAST detects that other security tools cannot detect. With a running application in a test environment, DAST scans can be automated in a CI/CD pipeline, automated on a schedule, or run independently by using [on-demand scans](https://docs.gitlab.com/ee/user/application_security/dast/on-demand_scan.html). Read on to learn how to migrate to GitLab DAST, or how to get started if you aren’t already using this security feature.\n\n## Our decision to remove proxy-based DAST\n\nRemoval of support for proxy-based DAST in 17.0 was [announced in 16.6](https://docs.gitlab.com/ee/update/deprecations.html?removal_milestone=17.0#proxy-based-dast-deprecated). Proxy-based DAST was introduced in GitLab 10.4 (January 2018), and its foundation was the open source Zed Attack Proxy (ZAP) project. At the time, this provided great dynamic analysis within GitLab’s DevSecOps platform. Over time, however, the architecture of apps evolved and web applications grew in complexity. [Legacy DAST crawlers](https://en.wikipedia.org/wiki/Single-page_application#Security_scanning), including proxy-based DAST, couldn’t provide sufficient coverage of modern applications so we began developing our modern, proprietary DAST solution. GitLab’s new DAST offering uses a browser to fully navigate the website like a real user while uncovering vulnerabilities. GitLab DAST runs on either arm64 or amd64 architectures, and a FIPS-compliant version is available as well. Our [Vulnerability Research](https://handbook.gitlab.com/handbook/engineering/development/sec/secure/vulnerability-research/) team wrote GitLab’s DAST detections and regularly updates our vulnerability definitions to ensure \n\n## DAST migration information\n\nIf you are using proxy-based DAST to run a scan in a CI/CD pipeline, we recommend migrating to [DAST](https://docs.gitlab.com/ee/user/application_security/dast/browser/) to continue dynamically scanning your web applications for vulnerabilities.  Follow the configuration recommendations outlined in the [DAST Migration Guide](https://docs.gitlab.com/ee/user/application_security/dast/proxy_based_to_browser_based_migration_guide.html) to ensure GitLab DAST scans can continue successfully.\n\nIf you would like to continue using proxy-based DAST, you can do so until GitLab 18.0 (May 2025). Bugs and vulnerabilities in this legacy analyzer will not be fixed. To continue using the non-supported proxy-based DAST, set the CI/CD variable \u003CDAST_VERSION:4> .\n\nIf you are using on-demand DAST scans, they automatically began using the newer DAST analyzer (effective from the third GitLab 17.0 breaking change window, 2024-05-06 09:00 UTC to 2024-05-08 22:00 UTC), without any required action on your part.\n\n## Migrate to GitLab DAST\n\nIf you aren’t using DAST yet, you can either run automatic DAST scans that are initiated by a merge request, or you can run manual on-demand DAST scans. \n\nTo enable automated scans, follow these simple [getting started](https://docs.gitlab.com/ee/user/application_security/dast/#getting-started) steps:\n1. Edit your `.gitlab.ci.yml` file to include the DAST template \u003CDAST.gitlab-ci.yml>\n1. Add a DAST stage to the CI/CD pipeline definition. This should be added after the deploy step.\n1. Define the URL to be scanned by DAST by using one of these methods:\nSet the \u003CDAST_TARGET_URL> variable. If set, this value takes precedence\nAdd the URL in a \u003Cenvironment_url.txt> file at your project’s root\n1. [Configure authentication](https://docs.gitlab.com/ee/user/application_security/dast/browser/configuration/authentication.html) to ensure DAST can crawl as much of your application as possible.\n\nA basic configuration for an application with a single-step login form might look like this:\n\n```\ninclude:\n  - template: DAST.gitlab-ci.yml\n\ndast:\n  variables:\n    DAST_TARGET_URL: \"https://example.com\"\n    DAST_AUTH_URL: \"https://example.com/login\"\n    DAST_AUTH_USERNAME_FIELD: \"css:[name=username]\"\n    DAST_AUTH_PASSWORD_FIELD: \"css:[name=password]\"\n    DAST_AUTH_SUBMIT_FIELD: \"css:button[type=submit]\"\n```\n\nTo run DAST scans manually outside of the DevOps lifecycle, follow these steps to [create an on-demand scan](https://docs.gitlab.com/ee/user/application_security/dast/on-demand_scan.html#create-an-on-demand-scan).\n\n## How DAST works\n\nOnce a DAST scan has been triggered, the following steps occur:\n1. Authenticate - If you have configured authentication, the analyzer will authenticate to allow maximum crawling coverage. If authentication fails, the scan halts.\n1. Discovery - The crawler discovers the surface area of the target application by performing user actions like following links, clicking buttons, and filling out forms.\n1. Passive checks - Identifies vulnerabilities in HTTP messages and pages discovered while crawling. These are enabled by default.\n1. Active checks - Identifies vulnerabilities by injecting payloads into HTTP requests recorded during the crawl phase. DAST:\n- analyzes HTTP requests for injection locations (including but not limited to query values, header values, cookie values, form posts, JSON string values, and XML entities)\n- injects attack payloads into those locations and analyzes the HTTP responses to determine attack success and report vulnerabilities\n\nActive checks are disabled by default due to the nature of their probing attacks. Scan results should be reviewed via the [Vulnerability Report](https://docs.gitlab.com/ee/user/application_security/vulnerability_report/index.html) to prioritize and remediate the detected vulnerabilities. \n\n## DAST benefits\n\nHere are some of the benefits of incorporating DAST into your software development lifecycle:\n- simulates real-world hacking on running applications (pre-production versions recommended only)\n- produces high-confidence, low false-positive findings\n- identifies 9/10 of the OWASP Top 10:2021 (dependency scanning covers the 10th)\n- detects CWEs and provides developers and security teams with remediation guidance before your code goes into production\n- does not analyze source code, so scans are not limited by programming language or framework\n- has a FIPS Compliant analyzer available, ensuring data is protected at rest and at transit according to the Federal Information Processing Standard \nsupports complex, multi-step sign-in workflows\n- utilizes a headless browser to provide excellent crawling coverage and execute all client-side scripting interactions for modern web applications, including SPAs\n\n## Get started today\n\nGitLab DAST is an essential tool to include in your comprehensive security testing program. Our latest crawler provides excellent security coverage of modern applications, whether they are stateless or stateful single-page web apps).  Use DAST in conjunction with GitLab’s other security analyzers to remain compliant with numerous standards, reduce business risk, and detect and remediate cyber threats prior to releasing them into production.\n\n> Sign up for a free 30-day trial of GitLab Ultimate to [get going with GitLab DAST](https://about.gitlab.com/free-trial/devsecops/).\n\n## Additional resources\n- [DAST Migration Guide](https://docs.gitlab.com/ee/user/application_security/dast/browser_based_4_to_5_migration_guide.html)\n- [Tips to configure browser-based DAST scans](https://about.gitlab.com/blog/tips-to-configure-browser-based-dast-scans/)\n- [Introducing browser-based DAST and integrated passive checks](https://about.gitlab.com/blog/browser-based-dast-feature-announcement/)\n- [Introducing GitLab browser-based active checks in DAST](https://about.gitlab.com/blog/dast-release-first-gitlab-active-check/)\n",[708,9,799,857],{"slug":1030,"featured":90,"template":686},"detect-application-vulnerabilities-with-gitlabs-browser-based-dast","content:en-us:blog:detect-application-vulnerabilities-with-gitlabs-browser-based-dast.yml","Detect Application Vulnerabilities With Gitlabs Browser Based Dast","en-us/blog/detect-application-vulnerabilities-with-gitlabs-browser-based-dast.yml","en-us/blog/detect-application-vulnerabilities-with-gitlabs-browser-based-dast",{"_path":1036,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1037,"content":1043,"config":1049,"_id":1051,"_type":13,"title":1052,"_source":15,"_file":1053,"_stem":1054,"_extension":18},"/en-us/blog/develop-c-unit-testing-with-catch2-junit-and-gitlab-ci",{"title":1038,"description":1039,"ogTitle":1038,"ogDescription":1039,"noIndex":6,"ogImage":1040,"ogUrl":1041,"ogSiteName":672,"ogType":673,"canonicalUrls":1041,"schema":1042},"Develop C++ unit testing with Catch2, JUnit, and GitLab CI","Learn how to set up, write, and automate C++ unit tests using Catch2 with GitLab CI/CD. See examples from a working air quality app project and AI-powered help from GitLab Duo.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659684/Blog/Hero%20Images/AdobeStock_479904468__1_.jpg","https://about.gitlab.com/blog/develop-c-unit-testing-with-catch2-junit-and-gitlab-ci","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Develop C++ unit testing with Catch2, JUnit, and GitLab CI\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Fatima Sarah Khalid\"}],\n        \"datePublished\": \"2024-07-02\",\n      }",{"title":1038,"description":1039,"authors":1044,"heroImage":1040,"date":1046,"body":1047,"category":901,"tags":1048},[1045],"Fatima Sarah Khalid","2024-07-02","Continuous integration (CI) and automated testing are important DevSecOps workflows for software developers to detect bugs early, improve code quality, and streamline their development processes. \n\nIn this tutorial, you'll learn how to set up unit testing on a `C++` project with [Catch2](https://github.com/catchorg/Catch2) and GitLab CI for continuous integration. You'll also see how the AI-powered features of [GitLab Duo](https://about.gitlab.com/gitlab-duo/) can help. We’ll use [an air quality monitoring application](https://gitlab.com/gitlab-da/use-cases/ai/ai-applications/air-quality-app) as our reference project.\n\n## Prerequisites\n\n- Ensure you have [CMake](https://cmake.org/ \"CMake\") installed on your machine. \n- A modern `C++` compiler such as GCC or Clang is required. \n- An API key from [OpenWeatherMap](https://openweathermap.org/api) - requires signing up for a free account (1,000/calls per day are included for free). \n\n## Set up the application for testing\n\nThe reference project we’ll be using for demonstrating testing in this blog post is an air quality monitoring application that fetches air quality data from the OpenWeatherMap API based on the U.S zip codes only provided by the user.\n\nHere are the steps to set up the application for testing:\n\n1. Fork the [the reference project](https://gitlab.com/gitlab-da/use-cases/ai/ai-applications/air-quality-app) and clone the fork to your local environment.\n\n2. Generate an API key from  [OpenWeatherMap](https://openweathermap.org/) and export it into the environment. \n\n```shell\nexport API_KEY=\"YOURAPIKEY_HERE\"\n```\n\n3. Alternatively, you can add the key into your `.env` configuration, and source it with `source ~/.env`, or use a different mechanism to populate the environment.\n\n4. Compile and build the project code with the following instructions:\n\n```cpp\ncmake -S . -B build\ncmake --build build\n```\n\n5. Run the application using the executable and passing in a U.S zip code (90210 as an example): \n\n```cpp\n./build/air_quality_app 90210\n```\n\nHere’s an example of what running the program will look like in your terminal:  \n\n```bash\n❯ ./build/air_quality_app 90210\nAir Quality Index (AQI) for Zip Code 90210: 2 (Fair)\n```\n\n## Install Catch2\n\nNow that the application is set up and working, let's start working on adding testing using Catch2. Catch2 is a modern, `C++-native` testing framework for unit tests. \n\nYou can also ask GitLab Duo Chat within your IDE for an introduction to getting started with Catch2 as a `C++` testing framework. GitLab Duo Chat will provide getting started steps as well as an example test: \n\n![GitLab Duo Chat starting steps and example test](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749676997/Blog/Content%20Images/1.duo-chat-installing-catch2.png)\n\n1. First navigate to your project’s root directory and create an externals folder using the `mkdir` command.\n\n```shell\nmkdir externals\n```\n\n2. There are several ways to install Catch2 via [its CMake integration](https://github.com/catchorg/Catch2/blob/devel/docs/cmake-integration.md#top). We will use the option of installing it as a submodule and including it as part of the source code to simplify dependency management. To add Catch2 to your project in the `externals` folder: \n\n```shell\ngit submodule add https://github.com/catchorg/Catch2.git externals/Catch2\ngit submodule update --init --recursive\n```\n\n3. Update `CMakeLists.txt` to include Catch2’s directory as a subdirectory. This allows CMake to find and build Catch2 as a part of our project. \n\n```cpp\n# Assuming Catch2 in externals/Catch2\nadd_subdirectory(externals/Catch2)\n```\n\n4. Create a `tests.cpp` file in your project root to write our tests to: \n\n```shell\ntouch tests.cpp\n```\n\n5. Update `CMakeLists.txt` Link against Catch2. When defining your test executable in CMake, link it against Catch2:\n\n```cpp\n# Add tests executable and link it to Catch2\nadd_executable(tests test.cpp)\ntarget_link_libraries(tests PRIVATE Catch2::Catch2WithMain)\n```\n\n## Structure the project for testing\n\nBefore we start writing our tests, we should separate our application logic into separate files in order to maintain and test our code more efficiently. At the end of this section we should have:\n\n```\nmain.cpp containing only the main() function and application setup\nincludes/functions.cpp containing all functional code such as API calls and data processing: \nincludes/functions.h containing the declarations for the functions defined in functions.cpp.  It needs to define the preprocessor macro guards, and include all necessary headers. \n```\n\nApply the following changes to the files: \n\n1. `main.cpp`\n\n```cpp\n#include \u003Ciostream>\n#include \"functions.h\"\n\nint main(int argc, char* argv[]) {\n   if (argc \u003C 2) {\n       std::cerr \u003C\u003C \"Usage: \" \u003C\u003C argv[0] \u003C\u003C \" \u003CZip Code>\" \u003C\u003C std::endl;\n       return 1;\n   }\n\n   std::string zipCode = argv[1];\n   std::string apiKey = getApiKey();\n   if (apiKey.empty()) {\n       std::cerr \u003C\u003C \"API key not found.\" \u003C\u003C std::endl;\n       return 1;\n   }\n\n   auto [lat, lon] = geocodeZipcode(zipCode, apiKey);\n   if (lat == 0 && lon == 0) {\n       std::cerr \u003C\u003C \"Failed to geocode zipcode.\" \u003C\u003C std::endl;\n       return 1;\n   }\n\n   std::string response = fetchAirQuality(lat, lon, apiKey);\n   std::string airQualityInfo = parseAirQualityResponse(response);\n\n   std::cout \u003C\u003C \"Air Quality Index for Zip Code \" \u003C\u003C zipCode \u003C\u003C \": \" \u003C\u003C airQualityInfo \u003C\u003C std::endl;\n\n   return 0;\n}\n```\n\n2. Create a `functions.h:` in the `includes` folder: \n\n```cpp\n#ifndef FUNCTIONS_H\n#define FUNCTIONS_H\n\n#include \u003Cstring>\n#include \u003Cutility>\n#include \u003Cvector>\n\n// Declare the function prototype\nstd::string httpRequest(const std::string& url);\nbool loadEnvFile(const std::string& filename);\nstd::string getApiKey();\nstd::pair\u003Cdouble, double> geocodeZipcode(const std::string& zipCode, const std::string& apiKey);\nstd::string fetchAirQuality(double lat, double lon, const std::string& apiKey);\nstd::string parseAirQualityResponse(const std::string& response);\n\n#endif\n```\n\n3. Create a `functions.cpp` in the `includes` folder: \n\n```cpp\n#include \"functions.h\"\n#include \u003Cfstream>\n#include \u003Celnormous/HTTPRequest.hpp>\n#include \u003Cnlohmann/json.hpp>\n#include \u003Ciostream>\n#include \u003Ccstdlib> // For getenv\n\nstd::string httpRequest(const std::string& url) {\n   try {\n       http::Request request{url};\n       const auto response = request.send(\"GET\");\n       return std::string{response.body.begin(), response.body.end()};\n   } catch (const std::exception& e) {\n       std::cerr \u003C\u003C \"Request failed, error: \" \u003C\u003C e.what() \u003C\u003C std::endl;\n       return \"\";\n   }\n}\nstd::string getApiKey() {\n   const char* envApiKey = std::getenv(\"API_KEY\");\n   if (envApiKey) {\n       return std::string(envApiKey);\n   }\n   // If the environment variable is not set, fallback to the config file\n   std::ifstream configFile(\"config.txt\");\n   std::string line;\n   if (getline(configFile, line)) {\n       return line.substr(line.find('=') + 1);\n   }\n   return \"\";\n}\n\nstd::pair\u003Cdouble, double> geocodeZipcode(const std::string& zipCode, const std::string& apiKey) {\n   std::string url = \"http://api.openweathermap.org/geo/1.0/zip?zip=\" + zipCode + \",US&appid=\" + apiKey;\n   std::string response = httpRequest(url);\n   try {\n       auto json = nlohmann::json::parse(response);\n       if (json.contains(\"lat\") && json.contains(\"lon\")) {\n           double lat = json[\"lat\"];\n           double lon = json[\"lon\"];\n           return {lat, lon};\n       } else {\n           std::cerr \u003C\u003C \"Geocode response missing 'lat' or 'lon' fields: \" \u003C\u003C response \u003C\u003C std::endl;\n       }\n   } catch (const nlohmann::json::parse_error& e) {\n       std::cerr \u003C\u003C \"Failed to parse geocode response: \" \u003C\u003C e.what() \u003C\u003C \" - Response: \" \u003C\u003C response \u003C\u003C std::endl;\n   }\n   return {0, 0};\n}\n\nstd::string fetchAirQuality(double lat, double lon, const std::string& apiKey) {\n   std::string url = \"http://api.openweathermap.org/data/2.5/air_pollution?lat=\" + std::to_string(lat) + \"&lon=\" + std::to_string(lon) + \"&appid=\" + apiKey;\n   std::string response = httpRequest(url);\n   return response;\n}\n\nstd::string parseAirQualityResponse(const std::string& response) {\n   try {\n       auto json = nlohmann::json::parse(response);\n       if (json.contains(\"list\") && !json[\"list\"].empty() && json[\"list\"][0].contains(\"main\")) {\n           int aqi = json[\"list\"][0][\"main\"][\"aqi\"];\n           std::string aqiCategory;\n           switch (aqi) {\n               case 1:\n                   aqiCategory = \"Good\";\n                   break;\n               case 2:\n                   aqiCategory = \"Fair\";\n                   break;\n               case 3:\n                   aqiCategory = \"Moderate\";\n                   break;\n               case 4:\n                   aqiCategory = \"Poor\";\n                   break;\n               case 5:\n                   aqiCategory = \"Very Poor\";\n                   break;\n               default:\n                   aqiCategory = \"Unknown\";\n                   break;\n           }\n           return std::to_string(aqi) + \" (\" + aqiCategory + \")\";\n       } else {\n           return \"No AQI data available\";\n       }\n   } catch (const std::exception& e) {\n       std::cerr \u003C\u003C \"Failed to parse JSON response: \" \u003C\u003C e.what() \u003C\u003C std::endl;\n       return \"Error parsing AQI data\";\n   }\n}\n\n```\n\n4. Now that we have separated the source files, we also need to update our `CMakeLists.txt` to include `functions.cpp` in the `add_executable()` calls:\n\n```cpp\ncmake_minimum_required(VERSION 3.14)\nproject(air-quality-app)\n\n# Set the C++ standard for the project\nset(CMAKE_CXX_STANDARD 17)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\nset(CMAKE_CXX_EXTENSIONS OFF)\n\ninclude_directories(${CMAKE_SOURCE_DIR}/includes)\n\n# Define the main program executable\nadd_executable(air_quality_app main.cpp includes/functions.cpp)\n\n# Assuming Catch2 in externals/Catch2\nadd_subdirectory(externals/Catch2)\n\n# Add tests executable and link it to Catch2\nadd_executable(tests tests.cpp includes/functions.cpp)\ntarget_link_libraries(tests PRIVATE Catch2::Catch2WithMain)\n```\n\nTo verify that the changes are working, regenerate the CMake configuration and rebuild the source code with the following commands. The build will take longer now that we're compiling Catch2 files. \n\n```shell\nrm -rf build # delete existing build files\ncmake -S . -B build \ncmake --build build  \n```\n\nYou should be able to run the application without any errors.\n\n```shell\n./build/air_quality_app 90210\n```\n\n## Write tests in Catch2  \n\nCatch2 tests are made up of [macros and assertions](https://github.com/catchorg/Catch2/blob/devel/docs/assertions.md). Macros in Catch2 are used to define test cases and sections within those test cases. They help in organizing and structuring the tests. Assertions are used to verify that the code behaves as expected. If an assertion fails, the test case will fail, and Catch2 will report the failure.\n\nLet’s review a basic test scenario for an addition function to understand. Note: This test is read-only, as an example. \n\n```cpp\nint add(int a, int b) {\n   return a + b;\n}\n\nTEST_CASE(\"Addition works correctly\", \"[math]\") {\n   REQUIRE(add(1, 1) == 2);  // Test passes if 1+1 equals 2\n   REQUIRE(add(2, 2) != 5);  // Test passes if 2+2 does not equal 5\n}\n```\n\n- Each test begins with the `TEST_CASE` macro, which defines a test case container. The macro accepts two parameters: a string describing the test case and optionally a second string for tagging the test for easy filtering.\n- Tests are also composed of assertions, which are statements that check if conditions are true. Catch2 provides macros for assertion that include `REQUIRE`, which aborts the current test if the assertion fails, and `CHECK`, which logs the failure but continues with the current test.\n\n### Prepare to write tests with Catch2\n\nTo test the API retrieval functions in our air quality application, we’ll be using mock API requests. Mock API testing is a technique used to test how your application will interact with an external API without making any real API calls. Instead of sending requests to a live API server, we can simulate the responses using predefined data. Mock requests allow us to control the input data and specify exactly what the API would return for different requests, making sure that our tests aren't affected by changes in the real API responses or unexpected data. This also makes it easier for us to simulate and catch different failures.\n\nIn our `tests.cpp` file, let’s define the following function to run mock API requests.   \n\n```cpp\n#include \"includes/functions.h\"\n#include \u003Ccatch2/catch_test_macros.hpp>\n#include \u003Cstring>\n\n// Mock HTTP request function that simulates API responses\nstd::string mockHttpRequest(const std::string& url) {\n   if (url.find(\"geo\") != std::string::npos) {\n       // Mock response for geocoding\n       return R\"({\"lat\": 40.7128, \"lon\": -74.0060})\"; \n   } else if (url.find(\"air_pollution\") != std::string::npos) {\n       // Mock response for air quality\n       return R\"({\"list\": [{\"main\": {\"aqi\": 2}}]})\";\n   }\n   // Default mock response for unmatched endpoints\n   return \"{}\";\n}\n// Overriding the actual httpRequest function with the mockHttpRequest for testing\nstd::string httpRequest(const std::string& url) {\n   return mockHttpRequest(url);\n}\n```\n\n- This function simulates HTTP requests and returns predefined JSON responses based on the URL given as input. \n- It also checks the URL to determine which type of data is being requested based on the functionality of the application (geocoding, air pollution, or forecast data). If the URL doesn’t match the expected endpoint, it returns an empty JSON object. \n\nDon't compile the code just yet, as you'll see a linker error. Since we're overriding the original `httpRequest` function with our mock function for testing, we'll need a preprocessor macro to enable conditional compilation - indicating which `httpRequest` function should run when we're compiling tests. \n\n#### Define a preprocessor macro for testing  \n\nBecause we’ve overridden `httpRequest` in our `tests.cpp`, we need to exclude that code from `functions.cpp` when we’re testing. When building tests, we may need to ensure that certain parts of our code behave differently or are excluded. We can do this by defining a preprocessor macro `TESTING` which enables conditional compilation, allowing us to selectively include or exclude code when compiling the test target:  \n\nWe define the `TESTING` macro in our `CMakeLists.txt` at the end:  \n\n```cpp\n# Define TESTING macro for this target\ntarget_compile_definitions(tests PRIVATE TESTING)\n```\n\nAnd add the macro wrapper in  `functions.cpp` around the original `httpRequest` function:  \n\n```cpp\n#ifndef TESTING  // Exclude this part when TESTING is defined\nstd::string httpRequest(const std::string& url) {\n   try {\n       http::Request request{url};\n       const auto response = request.send(\"GET\");\n       return std::string{response.body.begin(), response.body.end()};\n   } catch (const std::exception& e) {\n       std::cerr \u003C\u003C \"Request failed, error: \" \u003C\u003C e.what() \u003C\u003C std::endl;\n       return \"\";\n   }\n}\n#endif\n```\n\nRegenerate the CMake configuration and rebuild the source code to verify it works.\n\n```shell\ncmake --build build  \n```\n\n### Write the first tests \n\nNow, let’s write some tests for our air quality application.\n\n#### Test 1: Verify API key retrieval \n\nThis test ensures that the `getApiKey` function retrieves the API key correctly from the environment variable or the configuration file. Add the test case to our `tests.cpp`:\n\n```cpp\n\nTEST_CASE(\"API Key Retrieval\", \"[api]\") {\n   // Set the API_KEY environment variable for testing\n   setenv(\"API_KEY\", \"test_key\", 1);\n   // Test if the key is retrieved correctly\n   REQUIRE(getApiKey() == \"test_key\");\n}\n```\n\nYou can verify that this tests passes by rebuilding the code and running the tests:\n\n```shell\ncmake --build build\n./build/tests\n```\n\n#### Test 2: Geocode the zip code\n\nThis test ensures that the `geocodeZipcode` function returns the correct latitude and longitude for a given zip code using the mock API response function we set up earlier. The  `geocodeZipcode` function is supposed to hit an API that returns geographic coordinates based on a zip code. \n\nIn `tests.cpp`, add this test case for the zip code 90210: \n\n```cpp\nTEST_CASE(\"Geocode Zip code\", \"[geocode]\") {\n   std::string apiKey = \"test_key\";\n   std::pair\u003Cdouble, double> coordinates = geocodeZipcode(\"90210\", apiKey);\n   // Check latitude\n   REQUIRE(coordinates.first == 40.7128);\n   // Check longitude \n   REQUIRE(coordinates.second == -74.0060);\n}\n```\n\nThe purpose of this test is to verify that the function `geocodeZipcode` can correctly parse the latitude and longitude from the API response. By hardcoding the expected response, we ensure that the test environment is controlled and predictable.\n\n #### Test 3: Air quality API test\n\nThis test ensures that the `fetchAirQuality` function correctly fetches air quality data using the mock API response function we set up earlier. It verifies that the function constructs the API request properly, sends it, and accurately parses the air quality index (AQI) from the mock JSON response. This validation helps ensure that the overall process of fetching and interpreting air quality data works as intended.\n\n```cpp\nTEST_CASE(\"Fetch Air Quality\", \"[airquality]\") {\n   std::string apiKey = \"test_key\";\n   double lat = 40.7128;\n   double lon = -74.0060;\n   std::string response = fetchAirQuality(lat, lon, apiKey);\n   // Check the response\n   REQUIRE(response == R\"({\"list\": [{\"main\": {\"aqi\": 2}}]})\");\n}\n```\n\n## Build and run the tests\n\nTo  build and compile our application, we'll use the same CMake commands as before:\n\n```cpp\ncmake -S . -B build\ncmake --build build\n\n```\n\nAfter building, we can run our tests by executing the test binary:  \n\n```cpp\n./build/tests\n\n```\n\nRunning this command will execute all defined tests, and you will see output indicating whether each test has passed or failed.\n\n![Output showing pass/fail of tests](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749676998/Blog/Content%20Images/2.running-catch2-tests.png)\n\n## Set up GitLab CI/CD\n\nTo automate the testing process each time we push some new code to our repository, let’s set up [GitLab CI/CD](https://about.gitlab.com/topics/ci-cd/). Create a new `.gitlab-ci.yml` configuration file in the root directory. \n\n```yaml\nimage: gcc:latest\n\nvariables:\n GIT_SUBMODULE_STRATEGY: recursive\n\nstages:\n - build\n - test\n\nbefore_script:\n - apt-get update && apt-get install -y cmake\n\ncompile:\n stage: build\n script:\n   - cmake -S . -B build\n   - cmake --build build\n artifacts:\n   paths:\n     - build/\n\ntest:\n stage: test\n script:\n   - ./build/tests --reporter junit -o test-results.xml\n artifacts:\n   reports:\n     junit: test-results.xml\n```\n\nThis CI/CD configuration will compile both the main application and the test suite, then run the tests, generating a JUnit XML report which GitLab uses to display the test results.  \n\n- In `before_script`, we added an installation for `cmake`, and `git submodule sync --recursive` which initializes and updates our submodules (catch2). \n- In the `test` stage, `--reporter junit -o test-results.xml` specifies that the test results should be treated as a JUnit report which allows GitLab CI to display results in the UI. This is super helpful when you have several tests in your application.  \n\nWe also need to [add an environmental variable](https://docs.gitlab.com/ee/ci/variables/#define-a-cicd-variable-in-the-ui) with the `API_KEY` in project settings on GitLab.\n\nDon’t forget to add all new files to Git, and commit and push the changes in a new MR:\n\n```shell\ngit checkout -b tests-catch2-cicd\n\ngit add includes/functions.{h,cpp} tests.cpp .gitlab-ci.yml \ngit add CMakeLists.txt main.cpp \n\ngit commit -vm “Add Catch2 tests and CI/CD configuration”\ngit push \n```\n\n## View the test report\n\nAfter pushing our code changes, we can review the results of our tests in the GitLab UI in the Pipeline view in the `Tests` tab:\n\n![GitLab pipeline view shows test results](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749676998/Blog/Content%20Images/2.0-passed-tests-UI.png)\n\n## Simulate a test failure\n\nTo demonstrate how our UI will handle test failures, we can intentionally introduce a bug into our code and observe the resulting behavior. \n\nLet's modify our `parseAirQualityResponse` function to introduce an error. We can change the AQI category for an AQI value of 2 from \"Fair\" to \"Poor.\" This change will cause the related test to fail, allowing us to see the test failure in the GitLab UI.\n\nIn `functions.cpp`, find the `parseAirQualityResponse` function and modify the switch statement for case `2` to set the `Poor` value instead of `Fair`:\n\n```cpp\n               // Intentional bug:\n               case 2:\n                   aqiCategory = \"Poor\";\n                   break;\n```\n\nIn tests.cpp, add a new test case that directly checks the output of the `parseAirQualityResponse` function. This test ensures that the `parseAirQualityResponse` function correctly parses and categorizes the air quality data from the mock API response. This function takes a JSON response, extracts the AQI value, and translates it into a human-readable category.\n\n```cpp\n\nTEST_CASE(\"Parse Air Quality Response\", \"[airquality]\") {\n   std::string mockResponse = R\"({\"list\": [{\"main\": {\"aqi\": 2}}]})\";\n   std::string result = parseAirQualityResponse(mockResponse);\n   // This should fail due to the intentional bug\n   REQUIRE(result == \"2 (Fair)\");\n}\n\n```\n\nCommit the changes, and push them into the MR. Open the MR in your browser. \n\nBy introducing an intentional bug in this function, we can see how a test failure is reported in GitLab's pipelines UI. We must add, commit, and push the changes to our repository to view the test failure in the pipeline. \n\n![Simulated test failure](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749676998/Blog/Content%20Images/2.1-failed-test-simulation.png)\n\n![Details of the simulated failed test](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749676998/Blog/Content%20Images/2.2-failed-test-simulation-details.png)\n\nOnce we've verified this simulated test failure, we can use `git revert` to roll back that commit. \n\n```shell\ngit revert\n```\n\n## Add and test a new feature\n\nLet’s put what you've learned together by creating a new feature in the air quality application and then writing a test for that feature using Catch2. The new feature will fetch the current weather forecast for the provided zip code.\n\nFirst, we'll define a `Weather` struct and add the function prototype in our `functions.h` file (inside the `#endif`):\n\n```cpp\n\nstruct Weather {\n   std::string main;\n   std::string description;\n   double temperature;\n};\n\nWeather getCurrentWeather(const std::string& apiKey, double lat, double lon);\n```\n\nThen, we implement the `getCurrentWeather` function in `functions.cpp`. This function calls the OpenWeatherMap API to retrieve the current weather and parses the JSON response. This code was generated using [GitLab Duo](https://about.gitlab.com/gitlab-duo/). If you start typing `Weather getCurrentWeather(const std::string& apiKey, double lat, double lon) {` to complete the function, GitLab Duo will provide the function contents for you, line by line. \n\n![GitLab Duo completing the function contents](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749676998/Blog/Content%20Images/3.get-current-weather-function-completion.png)\n\nHere's what your `getCurrentWeather()` function can look like: \n\n```cpp\n\nWeather getCurrentWeather(const std::string& apiKey, double lat, double lon) {\n   std::string url = \"http://api.openweathermap.org/data/2.5/weather?lat=\" + std::to_string(lat) + \"&lon=\" + std::to_string(lon) + \"&appid=\" + apiKey;\n   std::string response = httpRequest(url);\n   auto json = nlohmann::json::parse(response);\n   Weather weather;\n   if (!json.is_null()) {\n       weather.main = json[\"weather\"][0][\"main\"];\n       weather.description = json[\"weather\"][0][\"description\"];\n       weather.temperature = json[\"main\"][\"temp\"];\n   }\n   return weather;\n}\n```\n\nAnd, finally, we update our `main.cpp` file in the main function to output the current forecast (and converting Kelvin to Celsius for the output):  \n\n```cpp\n   Weather currentWeather = getCurrentWeather(apiKey, lat, lon);\n   if (currentWeather.main.empty()) {\n       std::cerr \u003C\u003C \"Failed to fetch current weather.\" \u003C\u003C std::endl;\n       return 1;\n   }\n\n   std::cout \u003C\u003C \"Current Weather: \" \u003C\u003C currentWeather.main \u003C\u003C \", \" \u003C\u003C currentWeather.description\n       \u003C\u003C \", temperature \" \u003C\u003C currentWeather.temperature - 273.15 \u003C\u003C \" °C\" \u003C\u003C std::endl;\n```\n\nWe can confirm that our new feature is working by building and running the application:  \n\n```shell\ncmake --build build\n./build/air_quality_app \n```\n\nAnd we should see the following output or similar in case the weather is different on the day the code is run :)\n\n```\nAir Quality Index for Zip Code 90210: 2 (Poor)\nCurrent Weather: Clouds, broken clouds, temperature 23.2 °C\n```\n\nWith all new functionality, there should be testing! We can also write a test to check whether the application is fetching and parsing a weather forecast correctly. This test checks that the function returns a list containing the correct number of forecast entries and that each entry has accurate data regarding time and temperature.\n\n```cpp\nTEST_CASE(\"Current Weather functionality\", \"[api]\") {\n   auto weather = getCurrentWeather(\"dummyApiKey\", 40.7128, -74.0060);\n   // Ensure main weather description is not empty\n   REQUIRE_FALSE(weather.main.empty());\n   // Validate that temperature is a reasonable value\n   REQUIRE(weather.temperature > 0); \n}\n```\n\nWe’ll also have to update our `mockHTTPRequest` function in `tests.cpp` to account for this new test. Modify the if-condition with a new else-if branch checking for the `weather` string in the URL:  \n\n```cpp\n// Mock HTTP request function that simulates API responses\nstd::string mockHttpRequest(const std::string &url)\n{\n   if (url.find(\"geo\") != std::string::npos)\n   {\n       // Mock response for geocoding\n       return R\"({\"lat\": 40.7128, \"lon\": -74.0060})\";\n   }\n   else if (url.find(\"air_pollution\") != std::string::npos)\n   {\n       // Mock response for air quality\n       return R\"({\"list\": [{\"main\": {\"aqi\": 2}}]})\";\n   }\n   else if (url.find(\"weather\") != std::string::npos)\n   {\n       // Mock response for current weather\n       return R\"({\n          \"weather\": [{\"main\": \"Clear\", \"description\": \"clear sky\"}],\n          \"main\": {\"temp\": 298.55}\n      })\";\n   }\n   return \"{}\";\n}\n```\n\nAnd verify that our tests are working by rebuilding and running our tests:  \n\n```shell\ncmake --build build \n./build/tests\n```\n\nAll tests should pass, including the new one for Current Weather Functionality. \n\n## Optimize tests.cpp with sections\n\nTo better organize our tests as the project grows and categorize each functionality, we can use Catch2’s `SECTION` macro. The `SECTION` macro allows you to define logically separate test scenarios within a single test case, providing a clean way to test different behaviors or conditions without requiring multiple separate test cases or multiple files. This approach keeps related tests bundled together and also improves test maintainability by allowing shared setup code to be executed repeatedly for each section.\n\nSince some of our functionality is preprocessing data to retrieve information, let’s section our tests as such:\n- preprocessing steps: \n\t- API key validation\n\t- geocoding validation\n-  API data retrieval:\n\t- air pollution retrieval \n\t- forecast retrieval\n\nHere’s what our `tests.cpp` will look like if organized by sections: \n\n```cpp\n#include \"functions.h\"\n#include \u003Ccatch2/catch_test_macros.hpp>\n#include \u003Cstring>\n\n// Mock HTTP request function that simulates API responses\nstd::string mockHttpRequest(const std::string &url)\n{\n   if (url.find(\"geo\") != std::string::npos)\n   {\n       // Mock response for geocoding\n       return R\"({\"lat\": 40.7128, \"lon\": -74.0060})\";\n   }\n   else if (url.find(\"air_pollution\") != std::string::npos)\n   {\n       // Mock response for air quality\n       return R\"({\"list\": [{\"main\": {\"aqi\": 2}}]})\";\n   }\n   else if (url.find(\"weather\") != std::string::npos)\n   {\n       // Mock response for current weather\n       return R\"({\n          \"weather\": [{\"main\": \"Clear\", \"description\": \"clear sky\"}],\n          \"main\": {\"temp\": 298.55}\n      })\";\n   }\n   return \"{}\";\n}\n\n// Overriding the actual httpRequest function with the mockHttpRequest for testing\nstd::string httpRequest(const std::string &url)\n{\n   return mockHttpRequest(url);\n}\n\n// Preprocessing Steps\nTEST_CASE(\"Preprocessing Steps\", \"[preprocessing]\") {\n   SECTION(\"API Key Retrieval\") {\n       // Set the API_KEY environment variable for testing\n       setenv(\"API_KEY\", \"test_key\", 1);\n       // Test if the key is retrieved correctly\n       REQUIRE_FALSE(getApiKey().empty());\n   }\n\n   SECTION(\"Geocode Functionality\") {\n       std::string apiKey = \"test_key\";\n       std::pair\u003Cdouble, double> coordinates = geocodeZipcode(\"90210\", apiKey);\n       // Check latitude\n       REQUIRE(coordinates.first == 40.7128);\n       // Check longitude \n       REQUIRE(coordinates.second == -74.0060);\n   }\n}\n\n// API Data Retrieval\nTEST_CASE(\"API Data Retrieval\", \"[data_retrieval]\") {\n   SECTION(\"Air Quality Functionality\") {\n       std::string apiKey = \"test_key\";\n       double lat = 40.7128;\n       double lon = -74.0060;\n       std::string response = fetchAirQuality(lat, lon, apiKey);\n       // Check the response\n       REQUIRE(response == R\"({\"list\": [{\"main\": {\"aqi\": 2}}]})\");\n   }\n\n   SECTION(\"Current Weather Functionality\") {\n       auto weather = getCurrentWeather(\"dummyApiKey\", 40.7128, -74.0060);\n       // Ensure main weather description is not empty\n       REQUIRE_FALSE(weather.main.empty());\n       // Validate that temperature is a reasonable value\n       REQUIRE(weather.temperature > 0);\n   }\n}\n```\n\nRebuild the code and run the tests again to verify.\n\n```shell\ncmake --build build \n./build/tests\n```\n\n## Next steps\n\nIn this post, we covered how to integrate unit testing into a `C++` project using Catch2 testing framework and GitLab CI/CD and set up basic tests for our reference air quality application project.\n\nTo explore these concepts further, you can check out the [Catch2 documentation](https://github.com/catchorg/Catch2) and [GitLab's Unit test report examples documentation](https://docs.gitlab.com/ee/ci/testing/unit_test_report_examples.html). \n\nFor an advanced async exercise, you could build upon this project by using GitLab Duo to implement a feature that retrieves and analyzes historical air quality data and add code quality checks into the CI/CD pipeline. Happy coding! \n",[798,9,774,797,1008],{"slug":1050,"featured":90,"template":686},"develop-c-unit-testing-with-catch2-junit-and-gitlab-ci","content:en-us:blog:develop-c-unit-testing-with-catch2-junit-and-gitlab-ci.yml","Develop C Unit Testing With Catch2 Junit And Gitlab Ci","en-us/blog/develop-c-unit-testing-with-catch2-junit-and-gitlab-ci.yml","en-us/blog/develop-c-unit-testing-with-catch2-junit-and-gitlab-ci",{"_path":1056,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1057,"content":1063,"config":1070,"_id":1072,"_type":13,"title":1073,"_source":15,"_file":1074,"_stem":1075,"_extension":18},"/en-us/blog/developer-intro-sast-dast",{"title":1058,"description":1059,"ogTitle":1058,"ogDescription":1059,"noIndex":6,"ogImage":1060,"ogUrl":1061,"ogSiteName":672,"ogType":673,"canonicalUrls":1061,"schema":1062},"SAST & DAST: Key security tests for development workflows","Bolster your code quality with static and dynamic application security testing. Learn why you need SAST and DAST for your projects.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749680714/Blog/Hero%20Images/intro-developer-sast-dast.jpg","https://about.gitlab.com/blog/developer-intro-sast-dast","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Why you need static and dynamic application security testing in your development workflows\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Vanessa Wegner\"}],\n        \"datePublished\": \"2019-08-12\",\n      }",{"title":1064,"description":1059,"authors":1065,"heroImage":1060,"date":1067,"body":1068,"category":680,"tags":1069},"Why you need static and dynamic application security testing in your development workflows",[1066],"Vanessa Wegner","2019-08-12","\n\nDevOps is a quickly growing practice for companies in almost every market. With\nthe influx of cyber attacks over the past decade, security has slowly crept\nforward in the SDLC to the point where we’re now hearing the term [DevSecOps](/blog/announcing-gitlab-devsecops/) in developer circles.\n\nTo keep things tidy and help developers manage additional security\nresponsibilities, tools for static and dynamic [application security](/topics/devsecops/) testing\n(SAST and DAST) have made their way into the fray. In this post, we’ll\nexplain what SAST and DAST are, how they fit into developers’ workflows, and\nwhen they should be used.\n\n## What is application security testing (AST)?\n\nApplication security testing (AST) refers to the process of testing code to make sure it is free of vulnerabilities. There are many ways to test code, though static application security testing (SAST) and dynamic application security testing (DAST) are two of the more well-known options. \n\nApplication security testing has traditionally been a manual (and time-consuming) process, but the growing popularity of DevOps and the risk of insecure code have driven the majority of development teams to automate at least some of the processes. These days, most organizations use a variety of security testing tools to complete AST.\n \n## What are SAST and DAST?\n\nWhat are SAST and DAST? As previously mentioned, under the AST umbrella, there live two different security testing approaches: SAST and DAST. Though different, neither is better than the other and the security \ntesting outcome is superior when both are used together to detect security vulnerabilities in web applications and source code. SAST is a security testing approach that is performed on the application's code, while DAST is an approach that is performed on the running application. Both SAST and DAST are \nessential components of a comprehensive security testing strategy for software applications.\n\nIn summary, SAST and DAST help to ensure that computer systems are both safe and secure. These security measures help make sure that information is protected from hackers and other people who may try to steal it. They are critical tools for successful DevSecOps. Each runs a set\nof automated tests, and both introduce security at the beginning of the\nsoftware development lifecycle.\n\n### Static application security testing (SAST)\n\n[SAST](https://docs.gitlab.com/ee/user/application_security/sast/) can\nbe used to analyze source code for known vulnerabilities – and is also a type\nof white box testing. The test will run before your code is deployed, ensuring\nthat developers are alerted to fixes during the development phase.\nSAST can help remediate situations where your code has a potentially dangerous\nattribute in a class or unsafe code that can lead to unintended code execution.\n\n![An example of a SAST summary within a GitLab merge request](https://about.gitlab.com/images/secure/sast.png){: .shadow.medium.center}\n\nWithin GitLab, SAST will automatically generate a summary of fixes and unresolved\nvulnerabilities following every code commit, but before your code is merged to the target\nbranch. Tools that allow SAST reports to sit within the developer’s work\ninterface enable ease of remediation and streamline testing procedures within\nthe development phase.\n\nSAST takes an inside-looking-out approach, looking for security problems that might have been missed during source code development. It is effective when used after development is complete but before the finished project (and any missed security vulnerabilities) is deployed. Lots of developers nowadays integrate SAST testing into their CI/CD pipelines.\n\n### Dynamic application security testing (DAST)\n\n[DAST](https://docs.gitlab.com/ee/user/application_security/dast/), a\ntype of black box testing, analyzes your running web applications or known\nruntime vulnerabilities. GitLab’s DAST tool runs live attacks on a review app\nduring QA, meaning developers can iterate on new apps and updates earlier and\nfaster.\n\nAs with SAST, DAST should auto-run so that the developer doesn’t have to take measures to initiate the test. In other situations, DAST can also be used to\ncontinuously monitor live web applications for issues like cross-site scripting\nor broken authentication flaws. Test results should inform developers of\npotential vulnerabilities and serve as a catalyst for ongoing updates.\n\nDAST tools help you see your web application through the eyes of a hacker in a deployed environment. It constantly scans for security vulnerabilities during web application runtime, as well as checking the other API or web services that your application connects to. This makes DAST excellent for testing your complete IT environment where your application or web services run.\n\n## Test early and often using SAST and DAST\n\nStatic and dynamic application security testing are two helpful tools to keep\nyour code secure, but don’t rely on them to handle all of your security needs.\nIt’s still important to do manual code reviews, test high-level behaviors and\nfunctionality, conduct database scanning, and ensure that your whole team is\noperating with a security-first mindset.\n\nCover image by [Mikael Kristenson](https://unsplash.com/@mikael_k?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\non [Unsplash](https://unsplash.com/?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n",[9,708,108],{"slug":1071,"featured":6,"template":686},"developer-intro-sast-dast","content:en-us:blog:developer-intro-sast-dast.yml","Developer Intro Sast Dast","en-us/blog/developer-intro-sast-dast.yml","en-us/blog/developer-intro-sast-dast",{"_path":1077,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1078,"content":1084,"config":1089,"_id":1091,"_type":13,"title":1092,"_source":15,"_file":1093,"_stem":1094,"_extension":18},"/en-us/blog/developers-write-secure-code-gitlab",{"title":1079,"description":1080,"ogTitle":1079,"ogDescription":1080,"noIndex":6,"ogImage":1081,"ogUrl":1082,"ogSiteName":672,"ogType":673,"canonicalUrls":1082,"schema":1083},"4 Ways developers can write secure code with GitLab","GitLab Secure is not just for your security team – it’s for developers too. Learn four ways to write secure code with GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666895/Blog/Hero%20Images/developers-write-secure.jpg","https://about.gitlab.com/blog/developers-write-secure-code-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"4 Ways developers can write secure code with GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Vanessa Wegner\"}],\n        \"datePublished\": \"2019-09-03\",\n      }",{"title":1079,"description":1080,"authors":1085,"heroImage":1081,"date":1086,"body":1087,"category":680,"tags":1088},[1066],"2019-09-03","\nWriting secure code is a standard part of day-to-day development work, but\nsecurity often appears to be a roadblock instead of a critical piece of the\npuzzle. To make security efforts easier, [GitLab Secure](/stages-devops-lifecycle/secure/)\noffers a number of different tools that help developers identify and remediate vulnerabilities\nwithin their code, _as they’re writing it_. Our goal is to seamlessly integrate\nsecurity into your code writing practices so you’re better able to protect\nyour business from growing cybersecurity threats.\n\n## Testing\n\nThere are a variety of testing tools available to developers within GitLab.\nGenerally, they alert developers to vulnerabilities within their code and report\nthem within the merge request so developers can adjust their code as they\ngo. In addition to the testing methods outlined below, developers can also [use\nother tools outside of GitLab](https://handbook.gitlab.com/handbook/product/gitlab-the-product/#plays-well-with-others) by integrating\nthe results of your scanners with our merge request security reports.\n\n### Static application security testing\n\nOur [static application security testing](https://docs.gitlab.com/ee/user/application_security/sast/index.html)\n(SAST) tool scans the application source code\nand binaries to spot potential vulnerabilities before deployment. It uses open\nsource tools that are installed as part of GitLab. Vulnerabilities are shown\nin-line with every merge request and results are collected and presented as a\nsingle report.\n\n### Secret detection\n\n[Secret detection](https://docs.gitlab.com/ee/user/application_security/sast/#secret-detection)\nwithin GitLab is able to detect secrets and credentials that\nhave been unintentionally pushed to the repository. This check is performed by\na specific analyzer during the SAST job, runs regardless of the programming\nlanguage of your app, and displays results within the SAST report.\n\n### Dynamic application security testing\n\nOur [DAST tool](https://docs.gitlab.com/ee/user/application_security/dast/index.html)\nanalyzes your web application for known runtime\nvulnerabilities. It conducts live attacks against a review app and can be created for every\nmerge request as part of GitLab’s [CI/CD capabilities](/topics/ci-cd/). Users can provide HTTP\ncredentials to test private areas. Vulnerabilities are shown in-line with every\nmerge request.\n\n### Dependency scanning\n\n[Dependency scanning](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/index.html)\nanalyzes external dependencies (e.g. libraries like Ruby gems) for known\nvulnerabilities on each code commit with GitLab CI/CD. This scan relies on open\nsource tools and on the integration with [Gemnasium](https://docs.gitlab.com/ee/user/project/import/index.html)\ntechnology (now part of\nGitLab) to show, in-line with every merge request, vulnerable dependencies\nin need of updating. Results are collected and available as a single report.\nDependency scanning also provides a list of your project’s dependencies with\ndifferent versions for languages and package managers supported by Gemnasium.\n\n### Container scanning\n\nIf you’re using GitLab CI/CD, [container scanning](https://docs.gitlab.com/ee/user/application_security/container_scanning/index.html)\nwill let you check Docker images (and containers) for\nknown vulnerabilities in the application environment. Analyze image contents\nagainst public vulnerability databases using the open source tool, [Clair](https://coreos.com/clair/docs/latest/),\nthat\nis able to scan any kind of Docker (or app) image. Vulnerabilities are shown\nin-line with every merge request.\n\n### License management\n\nUpon code commit, project dependencies are reviewed for [approved and blacklisted\nlicenses](https://docs.gitlab.com/ee/user/compliance/license_compliance/index.html)\ndefined by custom policies per project. Software licenses are\nidentified if they are not within policy, and new licenses are also listed if\nthey require a status designation. This scan relies on an open source tool,\nLicenseFinder, and license analysis results are shown in-line for every merge\nrequest for immediate resolution.\n\n### Code quality analysis\n\nWith the help of GitLab CI/CD, you can analyze your source code quality using\nGitLab [Code Quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html).\nCode Quality uses [Code Climate Engines](https://codeclimate.com/)\nand runs in pipelines using a Docker image built into the Code Quality\nproject. Once the\nCode Quality job has completed, GitLab checks the generated report, compares the\nmetrics between the source and target branches, and shows the information\nwithin the merge request. With pipelines that enable concurrent testing and\nparallel execution, teams quickly receive insight about every commit, allowing\nthem to deliver higher quality code faster.\n\n### The Security Dashboard\n\nSecurity dashboards in GitLab exist at both the project and group level. The\ngroup dashboard provides an overview of all the security vulnerabilities in your\ngroups and projects. In the dashboard, developers are able to drill down into a\nvulnerability for further details, see which project it comes from and the file\nit’s in, and view various metadata to help analyze the risk.\n\nThe dashboard also allows viewers to\n[interact with vulnerabilities](https://docs.gitlab.com/ee/user/application_security/index.html#interacting-with-the-vulnerabilities)\nby creating an issue for them or dismissing them. For ease of use, vulnerabilities\nwithin the group Security Dashboard can be filtered by severity, confidence, report type, and project.\n\nIn addition to the vulnerability overview, the group Security Dashboard also\nprovides a timeline that displays how many open vulnerabilities your projects\nhad at various points in time. While security scans are automatically run for\neach code update, you’ll have some default branches that are infrequently\nupdated. To keep your Security Dashboard up to date on those branches, you can\nuse GitLab to [configure a scheduled pipeline](https://docs.gitlab.com/ee/ci/pipelines/schedules.html)\nto run a daily security scan.\n\n## What’s next for GitLab Secure?\n\nWhile we already have a number of ways to help you write secure code and build\nsecure products and services, we’re always looking for ways to give you more.\nHere are a few of the things we’re working on:\n\n### Interactive application security testing\n\nInteractive application security testing (IAST) checks the runtime behavior of applications by\ninstrumenting the code and\nchecking for error conditions. It is composed by an agent that lives inside the\napplication environment, and an external component, like DAST, that can interact\nand trigger unintended results.\n\n### Fuzzing\n\n[Fuzzing](/direction/secure/dynamic-analysis/fuzz-testing/)\nis a testing technique focused on finding flaws and vulnerabilities in\napplications by sending arbitrary payloads instead of valid input. The idea is to\ntrigger exceptions and unintended code paths that may lead to crashes and\nunauthorized operations. Once a possible problem – like a crash – is found,\nattackers can attempt to find the exact conditions needed to trigger the bug\nand see if they can be fine-tuned to obtain a useful result. (It is worth noting\nthat fuzzing is primarily intended for security teams because it requires more\ntime to execute. While fuzzing is a useful testing method, it should not be a\ndevelopment blocker).\n\n### Vulnerability database\n\nGitLab integrates access to proprietary and open source application security\nscanning tools. In order to maintain the efficacy of those scans, we strive to\nkeep their underlying vulnerability databases up to date.\n\n### Auto remediation\n\nVulnerabilities that require manual intervention to create a fix and push it to\nproduction have a time window where attackers have the ability to leverage the\nvulnerability. Auto remediation aims to automate the vulnerability solution flow and\nautomatically create a fix. The fix is then tested, and if it passes all the\ntests already defined for the application, it is deployed to production.\n\nPhoto by [Daniel McCullough](https://unsplash.com/@d_mccullough?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\non [Unsplash](https://unsplash.com/search/photos/write?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n",[708,682,108,859,9],{"slug":1090,"featured":6,"template":686},"developers-write-secure-code-gitlab","content:en-us:blog:developers-write-secure-code-gitlab.yml","Developers Write Secure Code Gitlab","en-us/blog/developers-write-secure-code-gitlab.yml","en-us/blog/developers-write-secure-code-gitlab",{"_path":1096,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1097,"content":1103,"config":1110,"_id":1112,"_type":13,"title":1113,"_source":15,"_file":1114,"_stem":1115,"_extension":18},"/en-us/blog/efficient-code-review-tips",{"title":1098,"description":1099,"ogTitle":1098,"ogDescription":1099,"noIndex":6,"ogImage":1100,"ogUrl":1101,"ogSiteName":672,"ogType":673,"canonicalUrls":1101,"schema":1102},"How to carry out effective code reviews","From time management to unblocking, discover the secrets of more efficient code reviews.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749678861/Blog/Hero%20Images/pre-commit.jpg","https://about.gitlab.com/blog/efficient-code-review-tips","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to carry out effective code reviews\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Phil Hughes\"}],\n        \"datePublished\": \"2020-09-08\",\n      }",{"title":1098,"description":1099,"authors":1104,"heroImage":1100,"date":1106,"body":1107,"category":680,"tags":1108},[1105],"Phil Hughes","2020-09-08","\n\nThis blog post was originally published on the GitLab Unfiltered blog. It was reviewed and republished on 2020-09-15.\n{: .alert .alert-info .note}\n\nLike most companies, code review at GitLab is a major part of our workflow. But it's clear from the results of our [2020 Global DevSecOps Survey](/developer-survey/) that code review can be a major reason for delayed releases and overall frustration. A vast majority of companies conduct code reviews (some even on a daily basis) but that doesn't mean it isn't a potential time sink.\n\n## How to perform a code review?\n\nBut code reviews can be done efficiently, and I know this since I've been a maintainer for 3 years. Here's a look at my top four tips for code review based on a tried and true routine that allows me to do effective code reviews and merge code quickly and efficiently to aid in others not being blocked by me. Of course, this is what works for *me* – your mileage may vary. Here's how I do it:\n\n### Tips for code review no.1 - Time management\n\nAn early start to my day makes it easy to start reviewing merge requests first thing. I set myself a time to start reviewing and I will keep at it until my GitLab \"to do\" list no longer has any merge requests that need reviewing. Mornings work for me; it's the time of the day when I can focus the most and get the reviews done with minimal distractions.\n\nGetting to reviews after this time is hard. I have other work that needs doing as well so once I've reviewed all merge requests on my list I leave anything new until the next day. Of course, as with all rules, it ends up getting broken. **Depending on the size of merge requests, I may make sure I review them before my day ends to make sure anyone in other timezones aren't blocked by me.**\n\n### Tips for code review no.2 - Unblock others first\n\nIt's not great for the author of a merge request to have to wait X hours/days before they get feedback. The sooner they get feedback, the sooner the merge request can be merged and shipped. Making authors wait just creates uncertainty and may mean that other work gets held up.\n\nThis is why I find it important for me to review a merge request as quickly as possible. At GitLab we have a [2  day Service Level Objective (SLO)](/handbook/engineering/workflow/code-review/#review-response-slo) for feedback from reviewers. For myself, I always try to do better than that and respond within a day.\n\n### 3. Tips for code review no.3 - Focus on the code, not the feature\n\nThis is going to be a point that could create a lot of discussion: Instead of focusing on the feature, focus on the code.\n\nA lot of the merge requests I review are across different groups, with features that I don't fully understand or with features I have no way to test. I could spend a lot of my time reading into the feature and the issue to understand what it is, but that means spending more time not reviewing everyone else's code. Also, if I did this with **every** merge request, it would be hard for me to keep to my time limit.\n\nWho is better to review the feature itself then? The product designer (UX) or the product manager both understand the feature being worked on and are better suited to help find bugs or guide the feature in the correct way. It is important that someone in the UX team review the feature to make sure it matches the designs and vision they had created for the feature. If a merge request has no UX review by the time I get to reviewing it, I will normally ask the author (or ask a product designer myself) to have the UX reviewed _before_ I merge the merge request.\n\nHowever, this point is also something I don't _always_ stick to. If a merge request is touching an area that I am familiar with and I can tell from the code that a bug exists, I will test it locally and provide as much feedback as I can to help the author understand the bug. The more you – as a reviewer – work with the code, the easier finding bugs through the code becomes. I have been working on the GitLab codebase for over 4 years, so seeing where bugs could arise through looking at the code has become natural to me.\n\n### Tips for code review no.4 - Seek to understand: Ask questions\n\nIt is easy to suggest changes to the code that I am reviewing, however sometimes what I suggest may not be right. It is important that instead of just suggesting a change, you always try to ask if the author thinks it is the right change. Having a conversation around a change helps both the reviewer and the author understand the existing code as well as the code being suggested. Maybe the suggestion had already been tried by the author. Being open to talk about it helps get to the final solution.\n\nSometimes however suggestions for changes happen around legacy code, i.e., code that has existed for a long time without being updated to match our documentation. In these cases, the conclusion may end up being that a technical debt issue be created. This is ok. We should strive for [boring solutions](https://handbook.gitlab.com/handbook/values/#boring-solutions) first but also understand that a more optimal solution may be required in the future.\n\n## To sum it up\n\nReviewing code efficiently is a skill that gets learnt the more you do it. Spending time coming up with a workflow that works for you is just as important. Over the years I have been reviewing code, I have stuck to these tips as closely as possible. Yet, I am far from perfect; I am constantly learning about new and different ways to optimise my workflow for code review. I would love to hear other tips and workflows. It is through discussions that we can improve and push ourselves to be the best that we can be.\n",[1109,9,859],"frontend",{"slug":1111,"featured":6,"template":686},"efficient-code-review-tips","content:en-us:blog:efficient-code-review-tips.yml","Efficient Code Review Tips","en-us/blog/efficient-code-review-tips.yml","en-us/blog/efficient-code-review-tips",{"_path":1117,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1118,"content":1124,"config":1130,"_id":1132,"_type":13,"title":1133,"_source":15,"_file":1134,"_stem":1135,"_extension":18},"/en-us/blog/engineering-director-shadow",{"title":1119,"description":1120,"ogTitle":1119,"ogDescription":1120,"noIndex":6,"ogImage":1121,"ogUrl":1122,"ogSiteName":672,"ogType":673,"canonicalUrls":1122,"schema":1123},"The engineering director shadow experience at GitLab","Shadowing an engineering director at GitLab was an immersive, collaborative experience. Here's what you need to know.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667509/Blog/Hero%20Images/continuous-integration-from-jenkins-to-gitlab-using-docker.jpg","https://about.gitlab.com/blog/engineering-director-shadow","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"The engineering director shadow experience at GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"William Arias\"}],\n        \"datePublished\": \"2022-04-01\",\n      }",{"title":1119,"description":1120,"authors":1125,"heroImage":1121,"date":1127,"body":1128,"category":298,"tags":1129},[1126],"William Arias","2022-04-01","\n\nMy [engineering director shadow](/handbook/engineering/development/shadow/director-shadow-program.html) experience reminded me of a concept that gained relevance during the pandemic:\n\n> \"I am because we are\" set in the context of the actual state of the world: I will be safe when all and each of us is safe.\n\nThe inspiration of these ideas stem from the [Ubuntu Philosophy](https://en.wikipedia.org/wiki/Ubuntu_philosophy) and, if seen from another angle, could mean:\n\n> \"Ubuntu implies that everyone has different skills and strengths; people are not isolated, and through mutual support, [they can help each other to complete themselves](https://www.linkedin.com/pulse/open-source-enlightenment-2015-part-1-audrey-tang/).\"\n\nDuring the shadowing experience, I realized that it is easy to get comfortable with my own world and department-specific view, which can be very foreign to other teams. The reality is that we are all interconnected and each bit of group success is **our** success. Is there an incident? A bug? A delay in hiring? It affects not only the department [DRI](/handbook/people-group/directly-responsible-individuals/) (what we call at GitLab the Directly Responsible Individual), but it can have an impact on all of us, and it can be disguised in different ways. A reliability challenge is not only an engineering problem; if there is an unresolved issue that goes on for too long, it can end up hurting GitLab’s reputation and brand. The issue can impact not only the goals of engineering but also of other teams, including marketing.\n\nTo navigate this interconnectedness, treat all individual efforts as a consolidated unit efficiently and transparently. This is one of my key takeaways from the shadowing program: Having a fair amount of humanity, humbleness, and people-oriented skills is important. I went into this program assuming I was going to experience mostly hard, deterministic skills but the reality was very different.\n\n## A day in the life\n\nShadowing [Wayne Haber](/company/team/#whaber), director of engineering for Growth, Sec, and Data Science, is a unique experience, especially for someone who doesn't spend a lot of time with upper leadership at GitLab. Wayne begins the shadow week with a prep coffee chat where he walks you through what to expect from the week, some tips, and his general criteria for success in the program (take notes and offer feedback!).\n\nAs the week kicks off, you'll first notice you'll be taking part in meetings, a lot of meetings. This is not a bad thing. However, you are going to be treated to a backstage pass to what mission-critical meetings at GitLab look like, how relationships are developed, how KPIs are decided and set, and much more.\n\nDuring my time with Wayne, I attended a variety of meetings from skip levels to a 1:1 with Wayne's boss. In those meetings there were a lot of nuances to observe and an opportunity to soak up how our engineering directors apply [the CREDIT values](https://handbook.gitlab.com/handbook/values/). Wayne encourages people who take part to get involved in the meetings, be vocal, be willing to engage, take notes, and offer feedback. This is an atmosphere that helps to cultivate a sense of \"No Ego\" and promotes collaboration.\n\n## TL;DR\n\nI totally recommend taking one week to enjoy Wayne's adventures. It is an enriching and humbling opportunity to connect with colleagues that you might not come across if you are on another team. As mentioned before, we impact each other more than we might usually think!\n",[9,881,683],{"slug":1131,"featured":6,"template":686},"engineering-director-shadow","content:en-us:blog:engineering-director-shadow.yml","Engineering Director Shadow","en-us/blog/engineering-director-shadow.yml","en-us/blog/engineering-director-shadow",{"_path":1137,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1138,"content":1141,"config":1149,"_id":1151,"_type":13,"title":1152,"_source":15,"_file":1153,"_stem":1154,"_extension":18},"/en-us/blog/enhance-application-quality-with-ai-powered-test-generation",{"noIndex":6,"title":1139,"description":1140},"Enhance application quality with AI-powered test generation","Learn how GitLab Duo with Amazon Q improves the QA process by automatically generating comprehensive unit tests.",{"title":1139,"description":1140,"authors":1142,"heroImage":1144,"date":1145,"body":1146,"category":795,"tags":1147},[1143],"Cesar Saavedra","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659604/Blog/Hero%20Images/Screenshot_2024-11-27_at_4.55.28_PM.png","2025-07-03","You know how critical application quality is to your customers and reputation. However, ensuring that quality through comprehensive testing can feel like an uphill battle. You're dealing with time-consuming manual processes, inconsistent test coverage across your team, and those pesky issues that somehow slip through the cracks. It's frustrating when your rating drops because quality assurance becomes a bottleneck rather than a safeguard.\n\nHere's where [GitLab Duo with Amazon Q ](https://about.gitlab.com/blog/gitlab-duo-with-amazon-q-agentic-ai-optimized-for-aws/), which delivers agentic AI throughout the software development lifecycle for AWS customers, can help transform your QA process. This AI-powered capability can automatically generate comprehensive unit tests for your code, dramatically accelerating your quality assurance workflow. Instead of spending hours writing tests manually, you can let AI analyze your code and create tests that ensure optimal coverage and consistent quality across your entire application.\n\n## How GitLab Duo with Amazon Q works\n\nSo how does this work? Let's walk through the process together.\nWhen you're working on a new feature, you start by selecting the Java class you've added to your project through a merge request. You simply navigate to your merge request and click on the \"Changes\" tab to see the new code you've added.\n\nNext, you invoke Amazon Q by entering a quick action command. All you need to do is type `/q test` in the issue comment box. It's that simple – just a forward slash, the letter \"q\", and the word \"test\".\n\nOnce you hit enter, Amazon Q springs into action. It analyzes your selected code, understanding its structure, logic, and purpose. The AI examines your class methods, dependencies, and potential edge cases to determine what tests are needed.\n\nWithin moments, Amazon Q generates comprehensive unit test coverage for your new class. It creates tests that cover not just the happy path, but also edge cases and error conditions you might have overlooked. The generated tests follow your project's existing patterns and conventions, ensuring they integrate seamlessly with your codebase.\n\n## Why use GitLab Duo with Amazon Q?\n\nHere's the bottom line: You started with a critical challenge – maintaining high-quality applications while dealing with time constraints and inconsistent testing practices. GitLab Duo with Amazon Q addresses this by automating the test generation process, ensuring optimal code coverage and consistent testing standards. The result? Issues are detected before deployment, your applications maintain their quality, and you can develop software faster without sacrificing reliability.\n\nKey benefits of this feature:\n\n* Significantly reduces time spent writing unit tests\n* Ensures comprehensive test coverage across your codebase\n* Maintains consistent testing quality across all team members\n* Catches issues before they reach production\n* Accelerates your overall development velocity\n\nReady to see this game-changing feature in action? Watch how GitLab Duo with Amazon Q can transform your quality assurance process:\n\n\u003C!-- blank line -->\n\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/pxlYJVcHY28?si=MhIz6lnHxc6kFhlL\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\n## Get started with GitLab Duo with Amazon Q today\n\nWant to learn more about GitLab Duo with Amazon Q? Visit the [GitLab and AWS partner page](https://about.gitlab.com/partners/technology-partners/aws/) for detailed information.\n\n## Agentic AI resources\n- [Agentic AI guides and resources](https://about.gitlab.com/blog/agentic-ai-guides-and-resources/)\n- [What is agentic AI?](https://about.gitlab.com/topics/agentic-ai/)\n- [GitLab Duo with Amazon Q: Agentic AI optimized for AWS generally available](https://about.gitlab.com/blog/gitlab-duo-with-amazon-q-agentic-ai-optimized-for-aws/)\n- [GitLab Duo with Amazon Q documentation](https://docs.gitlab.com/user/duo_amazon_q/)",[797,857,9,1008,798,1148],"AWS",{"featured":90,"template":686,"slug":1150},"enhance-application-quality-with-ai-powered-test-generation","content:en-us:blog:enhance-application-quality-with-ai-powered-test-generation.yml","Enhance Application Quality With Ai Powered Test Generation","en-us/blog/enhance-application-quality-with-ai-powered-test-generation.yml","en-us/blog/enhance-application-quality-with-ai-powered-test-generation",{"_path":1156,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1157,"content":1163,"config":1168,"_id":1170,"_type":13,"title":1171,"_source":15,"_file":1172,"_stem":1173,"_extension":18},"/en-us/blog/future-proof-your-developer-career",{"title":1158,"description":1159,"ogTitle":1158,"ogDescription":1159,"noIndex":6,"ogImage":1160,"ogUrl":1161,"ogSiteName":672,"ogType":673,"canonicalUrls":1161,"schema":1162},"Future-proof your developer career","Roles are changing and AI is coming. We asked 14 DevOps practitioners, analysts, and GitLab execs how to future-proof your career.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749679588/Blog/Hero%20Images/future-of-software-future-proof-your-career.png","https://about.gitlab.com/blog/future-proof-your-developer-career","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Future-proof your developer career\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Valerie Silverthorne\"}],\n        \"datePublished\": \"2020-10-30\",\n      }",{"title":1158,"description":1159,"authors":1164,"heroImage":1160,"date":1165,"body":1166,"category":680,"tags":1167},[677],"2020-10-30","\n\n_This is the fourth and final part of our series on the future of software development. Part one examined [how the software developer role is changing](/blog/software-developer-changing-role/). Part two highlighted [“future” technologies likely to impact the way software is created](/blog/how-tomorrows-tech-affects-sw-dev/). Part three looked at [the role artificial intelligence (AI) will play in software development](/blog/ai-in-software-development/)._\n\nChanging roles, emerging technologies, and the promise (or threat) of artificial intelligence are colliding, creating a critical question for software developers: how should you future-proof your career?\n\nAnyone in the technology industry knows change is both swift and expected – remember [Moore’s Law](https://www.investopedia.com/terms/m/mooreslaw.asp)? But there’s change and then there’s a “big C” *Change* that would impact skills and potentially careers. The [World Economic Forum, writing on the Pluralsight blog](https://www.pluralsight.com/blog/career/tech-in-2025), shared a worrisome observation about the future: “Across nearly all industries, the impact of technological and other changes is shortening the shelf-life of employees’ existing skill sets... ”\n\nSo what skills will be sufficient to navigate the future? We asked 14 [DevOps](/topics/devops/) practitioners, analysts, and GitLab execs for their best advice.\n\n## Embrace the soft skills\n\nIn our 2020 [Global DevSecOps Survey](/developer-survey/), developers, security pros, ops team members, and testers were unanimous in their choice of the most important skills for the future: communication and collaboration. It’s not particularly surprising – DevOps team members are increasingly finding themselves working even more closely together and often in different or new areas of the company. Communication and collaboration in those cases can be the difference between success and failure.\n\n“You can’t have one brain that knows it all,” explains [Darwin Sanoy](/company/team/#DarwinJS), senior solutions architect, Americas, at GitLab. “You need communication and collaboration to work together.”\n\nOne way developers can fine-tune collab skills is to use their open source skills within their organizations, a practice known as “inner sourcing,” says [Jose Manrique Lopez de la Fuente](https://www.linkedin.com/in/jose-manrique-lopez-de-la-fuente-b869884/), CEO at Bitergia, and also a [GitLab Hero](/community/heroes/). “You’re not doing open source alone,” Manrique says. “There are hundreds of developers worldwide also doing it. So, with those skills I learned working with other developers, how can I be transparent with people who are not only connected to my team? How can I get more involved with what’s going on in the company?” The more developers practice this skill, the easier it will get, he predicts.\n\n## It’s not just about tech\n\nAlthough this seems counter-intuitive, future-proofing your career doesn’t necessarily mean boning up on new technologies. In [our survey](/developer-survey/), 28% of developers said [AI was an important skill to know for the future (and they’re probably not wrong)](https://www2.deloitte.com/us/en/insights/focus/signals-for-strategists/ai-assisted-software-development.html), but most experts think it’s not wise to place all your energy in just a single specialty.\n\n“It’s best if you migrate your career from specialty to specialty trying to ride the wave,” Darwin says. “Take a look at what is picking up momentum but is not bleeding edge yet.” GitLab’s director of product management, CI/CD [Jason Yavorska](/company/team/#jyavorska) suggests polishing up the basics. “You want solid tech skills like trouble-shooting, a current knowledge of modern stacks and a lot of basic things,” Jason explains. “You want to be a little bit more of a generalist than an expert in one field.”\n\nThis is definitely a time to take step back and look at the bigger picture, suggests [Philip Lamb](https://www.linkedin.com/in/philliplamb/), global partner senior solutions architect – DevOps at Red Hat. He’s also a proponent of the power of generalization. “Focus less on specific tooling, software, and instead focus more on process and establishing a clear understanding of the sea changes DevOps brings,” he says. And don't forget that DevOps is going to look different for every organization.\n\n## Choose wisely\n\nBut if there’s one thing to keep in mind, above anything else, it’s this: “Avoid what AI is going to be good at,” Jason says. Forrester Research (and many others) think AI [could be creating code in 10 years or less](/blog/ai-in-software-development/). “AI and machine learning could be the most disrupting things to come to your career,” he explains. “If you’ve built your job out of basic things you could find yourself redundant. Focus on things you (as a human) are capable of.”\n",[682,683,9],{"slug":1169,"featured":6,"template":686},"future-proof-your-developer-career","content:en-us:blog:future-proof-your-developer-career.yml","Future Proof Your Developer Career","en-us/blog/future-proof-your-developer-career.yml","en-us/blog/future-proof-your-developer-career",{"_path":1175,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1176,"content":1182,"config":1187,"_id":1189,"_type":13,"title":1190,"_source":15,"_file":1191,"_stem":1192,"_extension":18},"/en-us/blog/get-started-compliance-as-code",{"title":1177,"description":1178,"ogTitle":1177,"ogDescription":1178,"noIndex":6,"ogImage":1179,"ogUrl":1180,"ogSiteName":672,"ogType":673,"canonicalUrls":1180,"schema":1181},"Why building compliance as code in DevOps will benefit your entire company","Read here on how to integrate compliance as code into your DevOps cycle and why it's important to have in your business","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749680734/Blog/Hero%20Images/compliance-as-code-header.jpg","https://about.gitlab.com/blog/get-started-compliance-as-code","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Why building compliance as code in DevOps will benefit your entire company\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Vanessa Wegner\"}],\n        \"datePublished\": \"2019-08-19\",\n      }",{"title":1177,"description":1178,"authors":1183,"heroImage":1179,"date":1184,"body":1185,"category":680,"tags":1186},[1066],"2019-08-19","\n\nCompliance, both regulatory and self-imposed, is another area where the shift-left\nmovement has taken hold. By building compliance into your workflow with compliance as code methods, your\nteam can save time while producing secure, low-risk code.\n\n## What is compliance as code?\n\nCompliance as code methods ensure that the correct regulatory or company\ncompliance requirements are fulfilled with zero-touch on the path to production.\nIt builds compliance into development and operations.\n\nThe utilization of compliance as code tools enable stakeholders to ensure that production procesesses are compliant by means of defining how resources must be configured. Such a structure often allows these tools to automatically adjust resources into a compliant state in order to meet these pre-defined compliance requirements.\n\nThis type of minimal-friction compliance is a crucial solution for large\nenterprises – especially those subject to complex regulation (such as enterprises\noperating in healthcare or financial services). By building compliance into the\n[DevOps lifecycle](/topics/devops/), you will streamline the workflow and save developers valuable\ntime during review and testing.\n\n## Benefits of compliance as code\n\nAdopting compliance as code brings a number of advantages and new operational capabilities. \n\n- **It’s easier to stay compliant during compliance rule change periods.** When a change happens in regulatory compliance frameworks, awareness and remediation of any issues happen more quickly because teams don’t have to manually overhaul processes or re-train.\n- **More natural alignment between developers and risk assessment teams.** There is more unity between teams when the compliance controls are already defined as code. It’s then possible to embed compliance rules into delivery processes and enable compliant delivery by default. \n- ** A lot of time and money saved.** Automation cuts out costly and time-consuming manual work. When automated compliance as code is in place, there’s a reduced risk of costly fines and data breaches. \n- **It’s all scalable.** Adopting compliance as code means adopting consistency across teams and an organization, regardless of size. This consistency prevents ambiguity and bottlenecks in maintaining compliance. \n\n## Challenges of compliance as code\n\nDevOps means experiencing changes often and quickly, and despite the benefits that automated compliance as code brings, it can also be a challenge. It can sometimes be difficult for security to keep up with the speed of change.\n\nAnd sometimes, even automated compliance as code isn’t perfect. It’s important to remember that there’s no cap on how careful you should be when it comes to DevOps compliance. Despite having automation in place, a pair or two of human eyes open to keep watch is still useful – even if it means a possible increase in human error. \n\n## How to impliment compliance as code\n\nAs [Jim Bird wrote for O’Reilly](https://www.oreilly.com/learning/compliance-as-code),\ncompliance as code policies must be defined up front, and will bring together\nmanagement, compliance, internal audit, PMO, and infosec leaders. This group\nwill work together to define rules and control workflows. Management also needs\nto understand how operational and other risks will be handled throughout the\npipeline.\n\nHow your company does establish compliance as code policies [will depend on how your team is structured](/topics/devops/build-a-devops-team/)\nbut regardless of how your teams interact, transparency is required. To ensure\nthat information is shared and decisions are made collaboratively, consider\nestablishing the following guidelines:\n\n- **Peer reviews**: The first review cycle for larger changes should be manual, to\nensure no changes are made without at least one other person verifying the\nchange. Reviewers can be assigned randomly to ensure the quality of review.\n- **Static application security testing**: [Static\n(or white box) testing](/blog/developer-intro-sast-dast/) should be done for every code change, in addition to\nmanual reviews.\n- **Subject matter expert reviews for high-risk code**: For code that the management team defines as\nhigh-risk (such as security code), changes should be reviewed by a subject matter\nexpert.\n- **Regulated access controls**: Management should keep access in check, both so that\nchanges aren’t made by a single engineer, and so that every change flows through\nthe workflow and can be reviewed by anyone with access to the dashboard.\n\n### Enhance technology with culture\n\nTechnology and processes will only work if your team cultures are aligned with your goal – and culture starts\nat the top. Team leaders should promote and exemplify a security-first\nmentality and openness to collaborative change. This will be a new way of\nthinking for some, but it will help teams adopt the shift-left trend, ultimately\nsaving everyone time and reducing business risk.\n\n### Compliance and open source\n\nIn 2015, [The Linux Foundation found that more than 60% of companies build products with open source software](https://www.linuxfoundation.org/blog/2015/06/why-companies-that-use-open-source-need-a-compliance-program/), but more\nthan half of those companies don’t have formal procedures in place to ensure their\nsoftware complies with open source licenses and regulations. Companies should\ncreate a free and open source software (FOSS) compliance program not only to\nabide by copyright notices and license obligations, but also to protect company\nIP and third-party source code from disclosure.\n\n## How we do compliance at GitLab\n\nWe [began our formalized compliance program](/blog/choosing-a-compliance-framework/)\ntowards the end of our Series C funding round, which was fairly early compared\nto other businesses of our size. The benefit of starting early was that we were\nable to implement security controls while we were still developing and evolving\nour operating processes, instead of retrofitting security to the business. The\nkey decision in our approach was choosing between independent or aggregate\nsecurity controls: We chose the aggregate route, leveraging [Adobe’s CCF](https://blogs.adobe.com/security/2017/05/open-source-ccf.html),\nrather than implementing industry frameworks individually. This allowed us to\nmitigate overlapping asks to GitLab teams, which enabled an agile and efficient\nprogram standup, and gave the compliance group internal credibility.\n\n## Compliance as code provides benefits across your ecosystem\n\nThere are benefits to everyone from the developer to the third-party auditor when compliance is baked into code from the beginning. These benefits include:\n- **Time saved**: Your\nteams will spend less time passing code fixes back and forth.\n- **Compliance transparency**: Management will\nunderstand where and how your software abides by compliance requirements.\n- **Routine reporting streamlines auditing**: Reports throughout the DevOps lifecycle provide documentation and proofs of\nrecord that will help management track and streamline any regulatory audit\nprocedures.\n\n## Common compliance as code tools\n\nGoogle Cloud Platform, Amazon Web Services, and Azure are all cloud services that can be used in compliance as code. And oftentimes, these tools are even more effective when paired with native tools. \n\nThrough proper tool adoption, the three core actions of a compliance strategy can be automated: prevention, detection, and remediation.\n\nCover image by [Hack Capital](https://unsplash.com/@hackcapital?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/search/photos/code?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n",[707,859,682,708,9,709],{"slug":1188,"featured":6,"template":686},"get-started-compliance-as-code","content:en-us:blog:get-started-compliance-as-code.yml","Get Started Compliance As Code","en-us/blog/get-started-compliance-as-code.yml","en-us/blog/get-started-compliance-as-code",{"_path":1194,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1195,"content":1201,"config":1208,"_id":1210,"_type":13,"title":1211,"_source":15,"_file":1212,"_stem":1213,"_extension":18},"/en-us/blog/get-started-with-microservices-architecture",{"title":1196,"description":1197,"ogTitle":1196,"ogDescription":1197,"noIndex":6,"ogImage":1198,"ogUrl":1199,"ogSiteName":672,"ogType":673,"canonicalUrls":1199,"schema":1200},"Get started with microservices architecture","For DevOps teams ready to take the next step, adopting a microservices architecture is a smart choice. Here's what you need to know.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667875/Blog/Hero%20Images/trends-in-version-control-land-microservices-cover.jpg","https://about.gitlab.com/blog/get-started-with-microservices-architecture","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Get started with microservices architecture\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"GitLab\"}],\n        \"datePublished\": \"2022-09-20\",\n      }",{"title":1196,"description":1197,"authors":1202,"heroImage":1198,"date":1204,"body":1205,"category":901,"tags":1206},[1203],"GitLab","2022-09-20","A great way to jumpstart a DevOps practice is by adopting a microservices architecture. The [benefits of a microservices architecture](/blog/what-are-the-benefits-of-a-microservices-architecture/) are numerous and include improved scalability, enhanced fault isolation, and the ability to bring new features to market faster.\n\n## How to start building with microservices architecture\n\n### Identify decomposable aspects of the application\n\nOne of the main properties of a microservice is its independence, so identifying the decomposable parts of the application — those parts that can work autonomously — is essential. Getting the service boundaries wrong could result in unwanted changes to other services, so you need to understand the system’s domain.\n\nIn many cases, such breakdown aligns with the business domains and is reflected in development teams.\n\n### Determine the metrics to monitor\n\nWith a microservices application, it’s crucial to monitor the status of each service so it’s possible to react to changing demands in the production environment. Some common metrics to monitor include the CPU and memory usage of each host, the API response time, and the error rate.\n\nWithout monitoring, teams may not catch problems when they arise. For example, if a server is overwhelmed by traffic, other services may not respond because they’re trying to communicate with an over-burdened service. \n\nBeing able to visualize these potential issues helps prevent downtime. Therefore, establish metrics early so necessary adjustments can be made as soon as possible.\n\n## Best practices for deploying and managing microservices\n\n### Infrastructure automation\n\nWhen the number of microservices grows, an application can become difficult to manage. Each microservice has its own deployment schedule. \n\nSome features are hidden behind feature flags, some are collecting usage data through A/B testing, and some services might be using Canary deployments as part of a progressive deployment. \n\nAutomated testing is key so teams will have the ability to stop or roll back deployment when necessary.\n\n### Consumer-driven contract tests\n\nWhen other consumers depend on API endpoints in one microservice, it’s good practice to implement consumer-driven contract testing to ensure version compatibility. \n\nTraditionally, developers first create the APIs on the server side and have clients determine which endpoints to call. That means when the signature of an API changes, it can bring down the consumer.\n\nThis can’t happen with consumer-driven contract testing because, before deploying a microservice to production, consumers determine the required contract (API signature) and test to be sure they are still valid.\n\n### Monitor key metrics\n\nOnce key metrics have been determined, they must be constantly monitored and able to respond to any events detected. This can be difficult, but fortunately, there are tools that simplify monitoring and provide comprehensive visualization.\n\n## Microservices architecture and DevOps\n\nBy decomposing a software system into autonomous parts, [microservices architecture](/topics/microservices/) allows companies to apply the single responsibility principle to individual teams. It allows them to manage all aspects of a service independently: the team’s technical stack, team composition, deployment strategies, and even release schedules.\n\nMicroservices architecture, alongside continuous delivery, allows businesses to make decisions based on live production data, thereby expediting feedback loops and reducing the time to market.\n\nTo get started with microservices architecture, it’s a good idea to first develop strong intuitions in decomposing a large system and get a good knowledge base of CI/CD practices. Regardless of the architectural style you choose, these skills will be useful.",[682,9,1207],"google",{"slug":1209,"featured":6,"template":686},"get-started-with-microservices-architecture","content:en-us:blog:get-started-with-microservices-architecture.yml","Get Started With Microservices Architecture","en-us/blog/get-started-with-microservices-architecture.yml","en-us/blog/get-started-with-microservices-architecture",{"_path":1215,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1216,"content":1222,"config":1228,"_id":1230,"_type":13,"title":1231,"_source":15,"_file":1232,"_stem":1233,"_extension":18},"/en-us/blog/gitlab-auto-devops-in-action",{"title":1217,"description":1218,"ogTitle":1217,"ogDescription":1218,"noIndex":6,"ogImage":1219,"ogUrl":1220,"ogSiteName":672,"ogType":673,"canonicalUrls":1220,"schema":1221},"GitLab Auto DevOps in action","See how the only single application for the entire DevOps lifecycle helps you deliver better software, faster.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749664015/Blog/Hero%20Images/laptop.jpg","https://about.gitlab.com/blog/gitlab-auto-devops-in-action","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitLab Auto DevOps in action\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Aricka Flowers\"}],\n        \"datePublished\": \"2018-08-10\",\n      }",{"title":1217,"description":1218,"authors":1223,"heroImage":1219,"date":1225,"body":1226,"category":705,"tags":1227},[1224],"Aricka Flowers","2018-08-10","\n\nBetter and faster. These two words best describe the production goals of the IT leaders and engineers building today’s cutting-edge software. And GitLab [Auto DevOps](https://docs.gitlab.com/ee/topics/autodevops/) can help them hit those goals while improving their overall business outcomes.\n\nAs the only single application for the complete [DevOps](/topics/devops/) lifecycle, GitLab Auto DevOps gives development teams all the tools they need to deliver secure, high-quality software at previously unattainable speeds. The secret sauce that makes Auto DevOps so effective is the way it automatically sets up the required integrations and pipeline needed to get your software out of the door faster. With Auto DevOps, your code is automatically tested for quality, scanned for security vulnerabilities and licensing issues, packaged and then set up for monitoring and deployment, leaving engineers with time to place more attention on creating a better product.\n\nThis may all make sense in theory, but as they say, a picture is worth 1,000 words. And it is [rumored](https://idearocketanimation.com/4293-video-worth-1-million-words/?) that video is worth 1.8 million words. With that being said, why not take a look at GitLab Auto DevOps in action? \n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/4Uo_QP9rSGM\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\nWant to learn more about GitLab Auto DevOps? Check out our [documentation](https://docs.gitlab.com/ee/topics/autodevops/), [feature](https://docs.gitlab.com/ee/topics/autodevops/) and [product vision](/direction/) pages.\n\n\nCover photo by [Ash Edmonds](https://unsplash.com/photos/Koxa-GX_5zs) on [Unsplash](https://unsplash.com/)\n{: .note}\n\n",[682,881,9,709,708,752,799],{"slug":1229,"featured":6,"template":686},"gitlab-auto-devops-in-action","content:en-us:blog:gitlab-auto-devops-in-action.yml","Gitlab Auto Devops In Action","en-us/blog/gitlab-auto-devops-in-action.yml","en-us/blog/gitlab-auto-devops-in-action",{"_path":1235,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1236,"content":1242,"config":1251,"_id":1253,"_type":13,"title":1254,"_source":15,"_file":1255,"_stem":1256,"_extension":18},"/en-us/blog/gitlab-is-an-sca-contender",{"title":1237,"description":1238,"ogTitle":1237,"ogDescription":1238,"noIndex":6,"ogImage":1239,"ogUrl":1240,"ogSiteName":672,"ogType":673,"canonicalUrls":1240,"schema":1241},"Forrester names GitLab challenger in software composition","GitLab has been recognized by analysts as a challenger in Software Composition Analysis.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749669950/Blog/Hero%20Images/security-cameras.jpg","https://about.gitlab.com/blog/gitlab-is-an-sca-contender","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"GitLab is named a Challenger in The Forrester Wave™: Software Composition Analysis, Q2 2019\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Cindy Blake\"}],\n        \"datePublished\": \"2019-04-12\",\n      }",{"title":1243,"description":1238,"authors":1244,"heroImage":1239,"date":1246,"body":1247,"category":298,"tags":1248},"GitLab is named a Challenger in The Forrester Wave™: Software Composition Analysis, Q2 2019",[1245],"Cindy Blake","2019-04-12","\n\nWhile GitLab is best known in the traditional DevOps space, we have also begun to grow out our expertise in application security, which may come as a [surprise to security professionals](https://www.linkedin.com/pulse/ciso-cheat-sheet-git-cindy-blake-cissp), who may not have encountered us previously. We may have started out focused on traditional developer tools, however, as GitLab has added capabilities to cover the entire Software Development\nLifecycle (SDLC), this now includes not only a market-leading [Continuous Integration](/blog/gitlab-leader-continuous-integration-forrester-wave/)\nsolution but also, more recently, integrated [application security testing built into the CI/CD pipeline](/solutions/security-compliance/).\nOur single, end-to-end application enables security testing that is tightly aligned to today’s\nrapid, [iterative cycles of DevOps](/solutions/security-compliance/) development and the modern\ninfrastructure that accompanies cloud native applications.\n\n## Who was included?\n\nFor The Forrester Wave™: Software Composition Analysis, Q2 2019, participating vendors were required to\nhave most of the following capabilities out of the box:\n- Ability to provide remediation advice on both open source license risk and vulnerabilities;\n- Ability to integrate into SDLC automation tools;\n- Ability to provide proactive vulnerability management;\n- Ability to edit and create policies; and\n- Ability to visually report on open source risk.\n\nParticipating vendors were also required to have more than $10M in revenue and have\ninterest from Forrester clients or relevance to them.\n\n## GitLab is a new challenger\n\nHaving only added security capabilities in December 2017, GitLab has been excluded from\nother analyst application security reports that only look at more established players.\nIn our first official security-oriented analyst evaluation, we are excited not only to get the\nword out about GitLab’s security capabilities, but also to have this opportunity for analyst\nfeedback and insight into how GitLab compares. We take to heart not only areas where we\nshine – but also where improvement is needed. With GitLab,\n“[everyone can contribute](/community/contribute/),” and the feedback gained from\nForrester is another valuable contribution. We also welcome [your participation](/community/contribute/) and invite you to help us\nunderstand what you would like to see as our security capabilities grow.\n\nBased on this analyst report and analyst interaction feedback, we are already addressing improvement opportunities in our\n[roadmap](/direction/secure/#upcoming-releases) and [vision](/direction/secure/#direction).\n\n**Check out our [complete SCA response](/analysts/forrester-sca/) for links to specific updates and response comments.**\n\nAs a company dedicated to releasing incrementally, delivering first on breadth and then\non depth, it is not uncommon for GitLab to initially place in more of a challenger position,\nas our feature set generally does not have the same maturity as established players in the space.\nHowever, when GitLab enters a space, we do so boldly, with clear intentions and a solid strategy.\nGitLab’s strategy for application security testing and software composition analysis focuses\nmore equally on both the developer and the security professional than traditional solutions.\nYou will find some areas in strategy where we were not scored as highly as we believe we\nshould be, due to our more aggressive focus on development.\n\n## Updates since the evaluation\n\nGitLab has shipped a [major new release every month](/releases/categories/releases/)\nfor 90 consecutive months. Forrester evaluated GitLab 11.6 for this report while versions\n[11.7](/releases/2019/01/22/gitlab-11-7-released/), [11.8](/releases/2019/02/22/gitlab-11-8-released/), and\n[11.9](/releases/2019/03/22/gitlab-11-9-released/) have since been released. You will find several features\nthat Forrester felt were lacking have already been added, including improvements to the\nsecurity dashboard, additional languages added to SAST scanning, and secrets detection.\nWhen using Forrester’s scoring tool, be sure to adjust the criteria for our current capabilities.\nA list of what’s been added since Forrester’s evaluation can be found on our [complete SCA response](/analysts/forrester-sca/).\n\n## Forrester’s key takeaway: “Remediation, policy management, and reporting are key differentiators”\n\nForrester says, “As developers continue to use open source to accelerate the release of new\napplication functionality, remediation, policy management, and reporting will dictate which\nproviders will lead the pack. Vendors that can provide developers with remediation advice\nand even create patches position themselves to significantly reduce business risk.”\n\nThis takeaway is closely aligned with GitLab's [vision for application security testing](/direction/secure/#direction)\nand our work in progress for [auto remediation](https://gitlab.com/groups/gitlab-org/-/epics/133). While not available in the evaluated version (11.6), today’s GA release, (11.9), [can detect a more current patch available](/releases/2019/03/22/gitlab-11-9-released/#vulnerability-remediation-merge-request) and\nenable the developer to create a [new branch and apply the patch](https://docs.gitlab.com/ee/user/application_security/security_dashboard/#create-a-merge-request-from-a-vulnerability)\nwith one click. Upcoming versions will [automatically run the pipeline and present the results](https://gitlab.com/groups/gitlab-org/-/epics/275) to the developer to accept or reject.\nBy automating remediations that are readily apparent, developers and security can focus on\nvulnerabilities whose remediation is not as straightforward.\n\nThe fact that GitLab is a [single application](/) for the entire SDLC enables us to take\nremediation even further – actually running the pipeline in a separate branch,\neven [measuring the performance impact](https://gitlab.com/gitlab-org/gitlab-ee/issues/9382)\nof the patch. We isolate the cause and effect: the developer makes a code change, that code is\ntested and they see the results before merging the code with others’. It also allows us to do [Dynamic scanning](https://docs.gitlab.com/ee/user/application_security/dast/) in the same manner, before the\ncode is merged with anyone else’s. We do this by spinning up a\n[review app](https://docs.gitlab.com/ee/ci/review_apps/) in the pipeline report.\nThis fully functioning app reflects the developer’s code changes and can be used for user testing,\nperformance testing, and dynamic app security scanning.\n\n## GitLab's advice\n\nWe believe GitLab is ideal for enterprises who are:\n\n* Using GitLab for CI/CD.\n* Practicing iterative development via DevOps.\n* Using containers and serverless.\n\nFor the enterprise that has not invested in app sec tools, GitLab can quickly provide\nscanning, often necessary for regulatory compliance, with a single application.\nGitLab offers SAST, DAST, Dependency, Container Scanning, and License Management [with one app](/stages-devops-lifecycle/application-security-testing/) – no need to evaluate and buy from multiple vendors, then stitch together integration with the DevOps toolchain. In fact, GitLab customer, [Glympse Inc.](https://glympse.com/),\nstood up 40 repos with automated security testing, using all of the GitLab scans, in less time\nthan they could have installed just the individual tools – and as a bonus, they impressed their\nauditors with their process.\n\nFor the enterprise already deeply invested in traditional app sec tools, GitLab affords a\nbroader and [earlier scanning effort](/solutions/security-compliance/), using a tool that\ndevelopers are already using. GitLab can scan every code change, much the way that\nevery airplane passenger gets scanned through security. Save the deeper scans for\nlater and/or less frequent evaluation by the security team. Consider using GitLab on select\nprojects to experience the more efficient workflow and potentially reduce your scanning costs from costlier tools.\n\n## Our response\n\n We invite you to see our [complete response](/analysts/forrester-sca/), and as always, welcome\n [your contributions](/community/contribute/)!\n\n Cover image by [Scott Webb](https://unsplash.com/@scottwebb) on [Unsplash](https://unsplash.com/photos/yekGLpc3vro)\n{: .note}\n",[1249,881,731,1250,708,9],"cloud native","news",{"slug":1252,"featured":6,"template":686},"gitlab-is-an-sca-contender","content:en-us:blog:gitlab-is-an-sca-contender.yml","Gitlab Is An Sca Contender","en-us/blog/gitlab-is-an-sca-contender.yml","en-us/blog/gitlab-is-an-sca-contender",{"_path":1258,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1259,"content":1265,"config":1271,"_id":1273,"_type":13,"title":1274,"_source":15,"_file":1275,"_stem":1276,"_extension":18},"/en-us/blog/gitlab-pages-for-covid",{"title":1260,"description":1261,"ogTitle":1260,"ogDescription":1261,"noIndex":6,"ogImage":1262,"ogUrl":1263,"ogSiteName":672,"ogType":673,"canonicalUrls":1263,"schema":1264},"Using GitLab Pages to Report Local COVID-19 Rates","How I used GitLab pages to publish up-to-date local infection rates.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681476/Blog/Hero%20Images/thisisengineering-raeng-0jTZTMyGym8-unsplash.jpg","https://about.gitlab.com/blog/gitlab-pages-for-covid","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Using GitLab Pages to Report Local COVID-19 Rates\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Matt Nohr\"}],\n        \"datePublished\": \"2020-08-06\",\n      }",{"title":1260,"description":1261,"authors":1266,"heroImage":1262,"date":1268,"body":1269,"category":729,"tags":1270},[1267],"Matt Nohr","2020-08-06","\n\n{::options parse_block_html=\"true\" /}\n\n\n\n## Finding Local COVID Rates\n\nI live in the U.S. state of Minnesota. Recently the state government provided recommendations for how and when to open schools in the fall. The guidance was based on the infection rates of the COVID-19 disease. In simple terms, the higher the rates, the less in-person the school should be. The actual calculation I needed was:  \n\n```\ntotal number of cases in your area over the past 2 weeks per 10,000 residents\n````\n\nI have three kids in school, so when I heard this recommendation I went to find out this value for my area. It turned out to be a difficult statistic to find. Along with the announcement my state government released a set of data, but it was about three weeks behind the current rates. I found different sets of data available, but they either reported the daily case rate or a total count of infections, not this very specific calculation.\n\nSee Also:\n- [GitLab's Handbook on COVID-19 benefits](https://about.gitlab.com/handbook/total-rewards/benefits/covid-19/)\n- [How an analytics software startup took aim at COVID-19](https://about.gitlab.com/blog/startup-covid-tracking/)\n\n## GitLab Pages to the Rescue\n\nI started by manually calculating the values with the data that I could find. This worked, but every time there were updated statistics, I had to go back and recalculate the value. I wanted a way to have this information available for me and others with the up-to-date information whenever I looked at it.\n\nMy process and output quickly evolved:\n\n1. I decided I would just write a script to download the data and do the calculation for me\n1. Once I had this working I thought the next step would be to automatically graph the results \n1. Then I thought I could publish the graphs on a website \n1. If I was going to publish them, I thought the best thing to do to get this done quickly would be to use [GitLab Pages](https://docs.gitlab.com/ee/user/project/pages/).\n\nThe result is a simple static website: [https://mattnohr.gitlab.io/covid-county/](https://mattnohr.gitlab.io/covid-county/)\n\n![Example Chart from website](https://about.gitlab.com/images/blogimages/gitlab-pages-for-covid/output-chart.png){: .shadow.center}\n\n## How It Works\n\nThe basic flow for my new “system” is:\n\n```plantuml\n(*) --> \"Download data\"\n--> \"Calculate the rates\"\n--> \"Create a new .csv file with daily calculated values\"\n--> \"Publish .csv file to GitLab pages\"\n--> \"Use GitLab pages to serve static website that reads .csv\"\n--> (*)\n```\n\nThe first few steps are done with a simple [Kotlin](https://kotlinlang.org/) script that is run using the [Gradle build tool](https://gradle.org/). I used [GitLab CI/CD](https://docs.gitlab.com/ee/ci/) pipelines to run a job to do that automatically. You can find an [example gradle .gitlab-ci.yml file here](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml). The basics of this step for me look like:\n\n```yml\nbuild:\n  stage: build\n  script: gradle --build-cache run\n```\n\nThe next step was to get it published to GitLab pages. That also used a GitLab CI/CD job. It simply needed to move the .csv results out of the build directory into the “public” directory that is used to host GitLab pages. \n\n```yml\npages:\n  stage: deploy\n  dependencies:\n    - build\n  script:\n    - mv build/data.csv public/\n```\n\nThe actual static webpage uses [d3.js](https://d3js.org/) that is able to read the data from a .csv file and graph it.\n\nMy GitLab project can be found here: [https://gitlab.com/mattnohr/covid-county](https://gitlab.com/mattnohr/covid-county)\n\n## Running on a Schedule\n\nOnce I had the system up and running with GitLab CI, I was able to use [GitLab Pipeline Schedules](https://docs.gitlab.com/ee/ci/pipelines/schedules.html) to run the script a few times a day to get updated data. Now I do not have to worry about when the data is updated, I can just review my GitLab Pages site to see the latest values.\n\nPipeline Scheudles let you easily schedule pipelines daily, weekly, or monthly. Since I wanted this to run multiple times a day, I used a cron schedule to run the pipeline 4 times a day on weekdays:\n\n```\n0 8,12,16,20 * * 1-5\n```\n\n## Result\n\nNow I have a [simple website](https://mattnohr.gitlab.io/covid-county/) that has the most up-to-date calculations for this specific value for my local area. Now I just need to wait for our local school board to make a final decision on how schools will look!\n\n\u003C!-- image: image-url -->\nCover image by [@ThisisEngineering RAEng](https://unsplash.com/@thisisengineering) on [Unsplash](https://unsplash.com/photos/0jTZTMyGym8)\n{: .note}",[536,967,9,108],{"slug":1272,"featured":6,"template":686},"gitlab-pages-for-covid","content:en-us:blog:gitlab-pages-for-covid.yml","Gitlab Pages For Covid","en-us/blog/gitlab-pages-for-covid.yml","en-us/blog/gitlab-pages-for-covid",{"_path":1278,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1279,"content":1285,"config":1291,"_id":1293,"_type":13,"title":1294,"_source":15,"_file":1295,"_stem":1296,"_extension":18},"/en-us/blog/gitlab-top-devops-tooling-metrics-and-targets",{"title":1280,"description":1281,"ogTitle":1280,"ogDescription":1281,"noIndex":6,"ogImage":1282,"ogUrl":1283,"ogSiteName":672,"ogType":673,"canonicalUrls":1283,"schema":1284},"The top DevOps tooling metrics and targets at GitLab","Here is how we measure DevOps success and why we always try to look forward.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749665635/Blog/Hero%20Images/blog-performance-metrics.jpg","https://about.gitlab.com/blog/gitlab-top-devops-tooling-metrics-and-targets","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"The top DevOps tooling metrics and targets at GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Mek Stittri\"}],\n        \"datePublished\": \"2022-04-05\",\n      }",{"title":1280,"description":1281,"authors":1286,"heroImage":1282,"date":1288,"body":1289,"category":705,"tags":1290},[1287],"Mek Stittri","2022-04-05","\n\nA successful DevOps practice relies heavily on metrics. Here at GitLab, we use seven key DevOps metrics to measure engineering efficiency and productivity.  Like many teams, we use industry standard metrics, but in some cases, we approach this data with a unique GitLab point of view. Here’s the first in a multipart look at the DevOps metrics we at GitLab think are most critical for success. Compare your metrics and results with ours, and [let’s get a conversation started](https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/13202).\n\n## Master pipeline stability\n\nIt’s important to be able to measure the stability of the GitLab project’s master branch pipeline. This metric tells us how stable the main branch is, and ensures engineers are checking out code that’s in good shape. [Merge trains](https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/195) are key to this effort. \n\nOur target percentage for [master pipeline stability](/handbook/engineering/quality/performance-indicators/#master-pipeline-stability  ) is above 95%.\n\n![master pipeline stability](https://about.gitlab.com/images/blogimages/dometrics1.png)\n\n## Review app deployment success rate\n\nAt GitLab we take [review apps](https://docs.gitlab.com/ee/ci/review_apps/) seriously.  We measure their success rate so we can understand the stability of our first deployed environment after code change. Review apps are spun up at MR submission. It’s important to monitor our review app successful deployments because it’s the first place where code is integrated and deployed as one unit. This metric ensures the codebase can be installed, tested, and made available for the team to preview their changes before merging into the main master branch. \n\nOur target for [review application deployment success](/handbook/engineering/quality/performance-indicators/#review-app-deployment-success-rate) is above 99%. \n\n![review app deployment success](https://about.gitlab.com/images/blogimages/dometrics2.png)\n\n## Time to First Failure\n\nTime to First Failure (TtFF, pronounced as “teuf”) measures how fast we are providing feedback to engineers. This metric examines how long it takes from pipeline creation to the first actionable failed build. The idea is that if the commit is going to fail, it should fail fast and the fail signal should get to the engineers as quickly as possible. The shorter the time to first failure, the faster the feedback loop, and faster time to action to address those failures. \n\nOur [TtFF target](/handbook/engineering/quality/performance-indicators/#time-to-first-failure) is less than 15 minutes.\n\n![TtFF or Time to First Failure](https://about.gitlab.com/images/blogimages/dometrics3.png)\n\n## Open S1 bug age\n\nThis metric focuses on the age of open S1 bugs. Many organizations measure time to close bugs. At Gitlab we focus on the age of bugs remaining. We structure the metric to focus on work that is remaining and can be actioned on. If we only measure time to close of fixed defects, we may miss addressing older defects and unintentionally incentivize closing of only newer defects. We like to look forward by asking ourselves “What’s left?” and “What can be done now?” rather than only looking backward at what’s already been done.\n\nOur target for [S1 open bug age](/handbook/engineering/quality/performance-indicators/#s1-oba) is under 100 days.\n\n![Open S1 bug age](https://about.gitlab.com/images/blogimages/dometrics4.png)\n\n## Open S2 bug age\n\nThis metric is similar to the open S1 bug age, but is focused on S2 bugs. Again, we measure the age of remaining open bugs rather than focusing on bugs that have been closed.\n\nOur target for the [open S2 bug age](/handbook/engineering/quality/performance-indicators/#s2-oba) metric is below 300 days.\n\n![Open S2 bug age](https://about.gitlab.com/images/blogimages/dometrics5.png)\n\n## Merge request pipeline duration\n\nWhen a pipeline is started for a merge request, how long does it take to run? This metric focuses on the duration of merge request pipelines and its time efficiency.  Within the total duration we break the data down into multiple  stages The team then iterates and improves time efficiencies of each stage of the pipeline. This is a key building block for improving GitLab’s code cycle time and efficiency and ensures the code is merged in a timely manner.\n\nOur target for this metric is below 45 minutes.\n\n![MR pipeline duration](https://about.gitlab.com/images/blogimages/dometrics6.png)\n\n## MR pipeline costs\n\nWe use this metric at GitLab to help us determine our Merge Request Pipeline cost efficiency. We look at the total costs for the CI runners machines for MR pipelines. Once we’ve determined that figure, we divide it by the number of merge requests. This helps us monitor cost while fine-tuning efficiency. Speed and cost moves in different directions. To help speed up you can increase resources, but it comes at a cost. Monitoring this metric enables us to be balanced and have a healthy trade-off between optimizing for cost and speed.\n\nOur target for the [MR pipeline costs](/handbook/engineering/quality/performance-indicators/#merge-requests-pipeline-cost) metric is below 7.50.\n\n![MR pipeline costs](https://about.gitlab.com/images/blogimages/dometrics7.png)\n\n## What DevOps tooling metrics are most effective for your team?\n\nWe’d like to hear what you think of our choices, and our targets, and what works, or doesn’t, for you. [Chime in here](https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/13202).\n",[682,881,9],{"slug":1292,"featured":6,"template":686},"gitlab-top-devops-tooling-metrics-and-targets","content:en-us:blog:gitlab-top-devops-tooling-metrics-and-targets.yml","Gitlab Top Devops Tooling Metrics And Targets","en-us/blog/gitlab-top-devops-tooling-metrics-and-targets.yml","en-us/blog/gitlab-top-devops-tooling-metrics-and-targets",{"_path":1298,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1299,"content":1305,"config":1311,"_id":1313,"_type":13,"title":1314,"_source":15,"_file":1315,"_stem":1316,"_extension":18},"/en-us/blog/google-next-post",{"title":1300,"description":1301,"ogTitle":1300,"ogDescription":1301,"noIndex":6,"ogImage":1302,"ogUrl":1303,"ogSiteName":672,"ogType":673,"canonicalUrls":1303,"schema":1304},"What to check out at Google Cloud Next 2019","Support women who code by stopping by our booth, learn from a host of GitLab experts, and more.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749679235/Blog/Hero%20Images/cloud-native-predictions-2019.jpg","https://about.gitlab.com/blog/google-next-post","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"What to check out at Google Cloud Next 2019\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Mayank Tahilramani\"}],\n        \"datePublished\": \"2019-04-04\",\n      }",{"title":1300,"description":1301,"authors":1306,"heroImage":1302,"date":1308,"body":1309,"category":298,"tags":1310},[1307],"Mayank Tahilramani","2019-04-04","\n\nIt’s that time of the year to indulge in all things innovative and new at Google Cloud Next 2019.\nAs an attendee last year, I was excited to learn about Google’s vision on ‘bringing the cloud to you’\nwith a focus on hybrid cloud and unveiling of GKE On-Prem. GitLab’s partnership with Google\nhas grown a lot since we launched our quick and easy [integration with GKE](/partners/technology-partners/google-cloud-platform/)\nlast year and we hope you will come out to see some of the new things we have going on.\n\n### Don't be shy, come say hi 👋\n\nCome visit us at our booth (#S1607), get scanned, and GitLab will donate $5 to your\ncharity of choice: [Rail Girls](http://railsgirls.com/) or [Django Girls](https://djangogirls.org/).\nThis also enters you for a chance to win an iPad Pro!\n\nWhile you're there, we would love to showcase and talk about:\n\n* GitLab’s [AutoDevOps](https://docs.gitlab.com/ee/topics/autodevops/) functionality.\n* Using GitLab to [secure your applications](/stages-devops-lifecycle/secure/).\n* How to get started with [GitLab for GCP on GKE](/partners/technology-partners/google-cloud-platform/) and GKE On-Prem.\n* GitLab [Serverless with Knative](/topics/serverless/) and [Cloud Run](https://cloud.google.com/blog/products/serverless/announcing-cloud-run-the-newest-member-of-our-serverless-compute-stack),\n* ... and much more!\n\n### Sit back, relax, and listen to some of our experts live\n\n* Check out [Brandon Jung](/company/team/#brandoncjung) (VP of Alliances) discuss [GitLab’s move from Azure to GCP](https://cloud.withgoogle.com/next/sf/sessions?session=ARC207) which includes a technical\noverview of the migration as well as lessons learned. Check out our customer case study [here](https://cloud.google.com/customers/gitlab/).\n\n* Come listen to [Kathy Wang](/company/team/#wangkathy) (Senior Director of Security) tell our journey [Towards Zero Trust at GitLab.com](https://cloud.withgoogle.com/next/sf/sessions?session=SEC220) along with key lessons learned. ([You can read more about the evolution of Zero Trust here](/blog/evolution-of-zero-trust/).)\n\n* Learn something new with [Daniel Gruesso](/company/team/#danielgruesso) (Product Manager) showcasing GitLab’s serverless functionality to [Run a consistent serverless platform anywhere with Kubernetes and Knative](https://cloud.withgoogle.com/next/sf/sessions?session=HYB218).\n\n### Get hands on with Qwiklabs\n\nLearn from [Dan Gordon](/company/team/#dbgordon) (Senior Technical Marketing Manager) at our [Spotlight Lab: Introduction to GitLab on GKE](https://cloud.withgoogle.com/next/sf/sessions?session=301353-133371). Here you will have the chance to deploy GitLab on GKE, migrate a GitHub repository into a GitLab Project, and set up a CI/CD pipeline with AutoDevOps to deploy your code to GKE.\n\nSo stop by and say hello!\n\nWe are proud to be a sponsor at this event and would love to see as many of you at our booth (S1607) to discuss GitLab [Serverless](/topics/serverless/) with Knative and Cloud Run, GitLab’s integration with GKE, GitLab AutoDevOps for CI/CD, Security functionalities, as well as GitLab’s support for GKE On-Prem.\n",[967,966,108,682,230,731,708,9],{"slug":1312,"featured":6,"template":686},"google-next-post","content:en-us:blog:google-next-post.yml","Google Next Post","en-us/blog/google-next-post.yml","en-us/blog/google-next-post",{"_path":1318,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1319,"content":1325,"config":1330,"_id":1332,"_type":13,"title":1333,"_source":15,"_file":1334,"_stem":1335,"_extension":18},"/en-us/blog/graphql-vulnerability-api",{"title":1320,"description":1321,"ogTitle":1320,"ogDescription":1321,"noIndex":6,"ogImage":1322,"ogUrl":1323,"ogSiteName":672,"ogType":673,"canonicalUrls":1323,"schema":1324},"Using the GitLab GraphQL API for vulnerability reporting","Follow along as we teach you how to use GitLab GraphQL API to manage vulnerabilities programatically.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749682219/Blog/Hero%20Images/jeremy-bishop-FzrlPh20l7Q-unsplash.jpg","https://about.gitlab.com/blog/graphql-vulnerability-api","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Using the GitLab GraphQL API for vulnerability reporting\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Fernando Diaz\"}],\n        \"datePublished\": \"2022-02-02\",\n      }",{"title":1320,"description":1321,"authors":1326,"heroImage":1322,"date":1327,"body":1328,"category":708,"tags":1329},[962],"2022-02-02","\n\nAs part of GitLab Ultimate, you have access to the Vulnerability Report,\nwhich provides information about vulnerabilities from scans of the default\nbranch. It is available for projects, groups, and the Security Center.\nFrom the Vulnerability Report you can:\n\n- filter the list of vulnerabilities\n- view more details about a vulnerability\n- view vulnerable source location (if available)\n- view an issue raised for a vulnerability\n- change the status of vulnerabilities\n- export details of vulnerabilities\n\nYou also get to perform functions (create/read/update/delete) on vulnerabilities using the GitLab GraphQL API.\n\nIn this blog post, I'll go over some of the GitLab GraphQL API and show how\nvulnerabilities can be managed with the API. Then I'll go over how to create a\ncustom page where a user can report a vulnerability.\n\n## GitLab GraphQL API\n\nGraphQL is a query language for APIs that allows clients to request exactly\nthe data they need, making it possible to get all required data in a limited\nnumber of requests.\n\nWith the GitLab GraphQL API, you can perform many different functions on\nvulnerabilities which can be seen in the Vulnerability Reports. You can\nperform queries for data retrieval or mutations for creating, updating,\nand deleting data. \n\nThere are many other functions that can be performed on vulnerabilities using the\nGraphQL API, such as querying for vulnerability data, changing a vulnerability's\nstatus, and much more. You can see the rest of the GraphQL API functions by viewing\nthe graphql [reference page](https://docs.gitlab.com/ee/api/graphql/reference/).\n\n## Running a GraphQL query to create a vulnerability\n\nYou can run GraphQL queries in a curl request on the command line on your local\ncomputer. A GraphQL request can be made as a POST request to `/api/graphql` with\nthe query as the payload. You can authorize your request by generating a\npersonal access token to use as a bearer token.\n\nWe will be using [Mutation.vulnerabilityCreate](https://docs.gitlab.com/ee/api/graphql/reference/#mutationvulnerabilitycreate)\nin order to create a vulnerability.\n\n**1.** Create a new project or use an existing project.\n\n**2.** Create a [Personal Access Token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token).\n\n**Note:** Make sure it is `api` scoped.  \n\n**3.** Set the Personal Access Token in the environment variables.\n\n    ```\n    $ export ACCESS_TOKEN=\u003Cyour-personal-access-token>\n    ```\n\n**4.** Get your Project ID to use in the curl request.\n\n**Note:** Project ID can be found in your project page  \n\n![](https://about.gitlab.com/images/blogimages/2022-graphql-vuln-api/show_projectid.png)  \n\n**5.** Send a curl request to graphql api.\n\n    ```\n    $ curl -g --header \"Authorization: Bearer $ACCESS_TOKEN\" --header \"Content-Type: application/json\" --request POST --data '{\"query\": \"mutation { vulnerabilityCreate(input: {clientMutationId: \\\"Ferns-Vuln-Reporter-Xtreme\\\", name: \\\"YEETTT\\\", project: \\\"gid://gitlab/Project/30857578\\\", description: \\\"ax\\\", scanner: {name: \\\"dude-scanner2\\\", id: \\\"123456\\\", url: \\\"localhost\\\", version: \\\"1.0\\\"}, identifiers: [{name: \\\"dont worry about its ok\\\", url: \\\"localhost\\\"}]}) { clientMutationId \\n vulnerability {  id  } \\n errors } }\" }' https://gitlab.com/api/graphql\n\n    {\"data\":{\"vulnerabilityCreate\":{\"clientMutationId\":\"Ferns-Vuln-Reporter-Xtreme\",\"vulnerability\":{\"id\":\"gid://gitlab/Vulnerability/29086674\"},\"errors\":[]}}}\n    ```\n\nYou can see that the resonse will provide some data. Let's save the provided vulnerability\nid, 29086674.  \n\n**Note:** You can see where I used the Project ID in the query above,\nby searching for \"30857578\". Also feel free to customize the strings in\nthe request.  \n\n**6.** Go to your project and click on the `Security & Compliance > Vulnerability Report`.\n\n**7.** Replace `vulnerability_report` in the url with `/vulnerabilities/29086674`, and you should\nsee detailed information on the vulnerability you submitted.\n\n![](https://about.gitlab.com/images/blogimages/2022-graphql-vuln-api/vuln_saved.png)  \n\n## Creating a Vulnerability Report site\n\nNow let's put what we learned about the Vulnerability API into creating an application\nwe can use for others to report vulnerabilities.\n\nI created a basic application that uses the GraphQL API to create vulnerabilities for\na given project. It's a little GoLang web-application that deploys to Kubernetes and\ncontains a basic web-form.\n\n**Note:** To continue with this section, you need a Kubernetes Cluster, GitLab Account, and\nknowledge of the GitLab [Kubernetes-Agent](https://docs.gitlab.com/ee/user/clusters/agent/).\n\n**1.** Create a [Personal Access Token](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token).\n\n**Note:** Make sure it is `api` scoped.  \n\n**2.** Create a new project and select import.\n\n![](https://about.gitlab.com/images/blogimages/2022-graphql-vuln-api/import_project.png)  \n\n**3.** Import the [Vuln-Reporter](https://gitlab.com/tech-marketing/devsecops/vuln-reporter).\n\n![](https://about.gitlab.com/images/blogimages/2022-graphql-vuln-api/repo_url.png)  \n\n**4.** Connect to a Kubernetes Cluster using the [Kubernetes-Agent](https://docs.gitlab.com/ee/user/clusters/agent/install/index.html).\n\n**5.** Add the [Ingress Controller](https://docs.gitlab.com/ee/user/infrastructure/clusters/manage/management_project_applications/ingress.html) as a [Cluster Management Application](https://docs.gitlab.com/ee/user/clusters/management_project_template.html).\n\n**Note:** Once the Kubernetes Agent is installed, this can be done by simply adding\nthe `applications` folder, `helmfile.yaml`, and `apply` job present in this [Infrastrucuture project](https://gitlab.com/tech-marketing/devsecops/initech/infrastructure).\n\n**6.** Add the following variables under `Settings > CICD > Variables`:\n\n    - PROJECT_ID: The id of the project you want to report on.\n    - ACCESS_TOKEN: Your personal access token created earlier.\n\n**7.** Run the pipeline.\n\n**8.** Connect to Kubernetes Cluster and find the Load Balancer IP.\n\n    ```\n    $ kubectl get svc -n gitlab-managed-apps | grep ingress\n\n    ingress-ingress-nginx-controller             LoadBalancer   10.28.13.2    104.198.204.142   80:31853/TCP,443:31835/TCP   19d\n    ingress-ingress-nginx-controller-admission   ClusterIP      10.28.6.20    \u003Cnone>            443/TCP                      19d\n    ```\n\n**Note:** It's the `104.198.204.142` address, but it may be different for you. Just make sure it's\nan external address.  \n\n**9.** Go to `http://\u003CLoad-Balancer-IP>/reporter` in your browser.\n\n**10.** Add info and submit a Vulnerability.\n\n![](https://about.gitlab.com/images/blogimages/2022-graphql-vuln-api/submit_vuln.png)  \n\nAfter submitting you should get a link. Copy that link\ninto your browser.\n\n\n**11.** View the Vulnerability Report.\n\n![](https://about.gitlab.com/images/blogimages/2022-graphql-vuln-api/vuln_report.png)\n\n",[682,708,9],{"slug":1331,"featured":6,"template":686},"graphql-vulnerability-api","content:en-us:blog:graphql-vulnerability-api.yml","Graphql Vulnerability Api","en-us/blog/graphql-vulnerability-api.yml","en-us/blog/graphql-vulnerability-api",{"_path":1337,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1338,"content":1344,"config":1349,"_id":1351,"_type":13,"title":1352,"_source":15,"_file":1353,"_stem":1354,"_extension":18},"/en-us/blog/heres-how-to-get-integrated-secure-coding-advice-in-gitlab",{"title":1339,"description":1340,"ogTitle":1339,"ogDescription":1340,"noIndex":6,"ogImage":1341,"ogUrl":1342,"ogSiteName":672,"ogType":673,"canonicalUrls":1342,"schema":1343},"How to get integrated secure coding advice in GitLab","Secure Code Warrior now offers integrated security training and guidance within the GitLab DevOps Platform.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662877/Blog/Hero%20Images/security-cover-new.png","https://about.gitlab.com/blog/heres-how-to-get-integrated-secure-coding-advice-in-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to get integrated secure coding advice in GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"GitLab\"}],\n        \"datePublished\": \"2022-03-24\",\n      }",{"title":1339,"description":1340,"authors":1345,"heroImage":1341,"date":1346,"body":1347,"category":1250,"tags":1348},[1203],"2022-03-24","Busy developers want to write secure code and fix any issues. But they often lack the time and resources to get it done efficiently.\n\nTo resolve vulnerabilities faster, developers need actionable advice from trusted sources of secure coding right inside the tools they use every day. [Secure Code Warrior](https://www.securecodewarrior.com/) is proud to partner with GitLab to enable developers to ship safe code faster, utilizing actionable and highly relevant secure coding guidance that is accessible from within GitLab’s [DevOps Platform](/topics/devops-platform/). This integration was announced as part of [GitLab’s 14.9 release](/releases/2022/03/22/gitlab-14-9-released/#integrated-security-training).\n\n## Empower developers with actionable guidance integrated inside GitLab\n\nGitLab is enabling developer-led security by getting scan results into the hands of those who can make fixes fast. Secure Code Warrior further strengthens this vision by bringing to GitLab some of the world’s largest secure coding and remediation content (6500+ interactive coding challenges, 56+ languages:frameworks, 150+ vulnerability categories) that is used by hundreds of thousands of professional developers across many industries. With this integration, secure coding guidance that is highly relevant to the detected vulnerabilities is easily accessible to developers with the click of a link in GitLab.\n\n## How this integration delivers contextual secure coding training\n\nWhen GitLab’s vulnerability scanners detect code security issues in merge requests and/or pipeline scans, a security issue is created and the identified vulnerability descriptions or CWE IDs are added to the Vulnerability Details section. The integration uses the vulnerability information to get a link to learning resources that educate developers on finding and fixing that particular security problem.\n\n![Secure Code Warrior platform](https://about.gitlab.com/images/blogimages/scw1.png)\n\nFor example, if the vulnerability scanners detected a Cross-Site Request Forgery (CSRF) in the application code, the vulnerability detail would be updated with the relevant training link.\n\n## GitLab-Secure Code Warrior integration at a glance\n\nWhen users click on the link, they are taken to SCW’s platform as shown below.\n\n![Secure Code Warrior platform](https://about.gitlab.com/images/blogimages/scw2.png)\n\nBy completing an appropriate challenge they get the trusted guidance to resolve the CSRF vulnerability with confidence. This is also a highly effective way to retain the knowledge because:\n\n- Bite-sized coding challenges give developers targeted, hands-on skill building in that vulnerability, and how to resolve it\n- Contextual learning - presented in manageable chunks - continually reinforces good, secure coding patterns from a trusted source, not just enabling a patch\n- It reduces the time gap between learning and application of knowledge, ensuring lasting engagement and retention\n- Developers grow their muscle memory to recognize security issues while they code, eliminating common vulnerabilities from the start of software creation\n\n## Ship secure code faster with improved merge request rate\n\nAs more teams adopt this workflow path to resolve vulnerabilities faster, they will gradually improve their MR rate and release quality and create secure code at speed. By embedding secure coding training within developer workflows, this integration automates and scales remediation support to all development teams and lets AppSec focus on risk monitoring and strengthening the security posture of the organization.\n\nThe partnership between Secure Code Warrior and GitLab is just getting started; follow us as we enable developers to build and release secure software at speed. We’d love you to try it out, and your feedback can help shape the future of the product.\n\nGet more details on how to [enable this integration](https://docs.gitlab.com/ee/user/application_security/vulnerabilities/#enable-security-training-for-vulnerabilities).",[708,682,9],{"slug":1350,"featured":6,"template":686},"heres-how-to-get-integrated-secure-coding-advice-in-gitlab","content:en-us:blog:heres-how-to-get-integrated-secure-coding-advice-in-gitlab.yml","Heres How To Get Integrated Secure Coding Advice In Gitlab","en-us/blog/heres-how-to-get-integrated-secure-coding-advice-in-gitlab.yml","en-us/blog/heres-how-to-get-integrated-secure-coding-advice-in-gitlab",{"_path":1356,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1357,"content":1363,"config":1370,"_id":1372,"_type":13,"title":1373,"_source":15,"_file":1374,"_stem":1375,"_extension":18},"/en-us/blog/high-availability-git-storage-with-praefect",{"title":1358,"description":1359,"ogTitle":1358,"ogDescription":1359,"noIndex":6,"ogImage":1360,"ogUrl":1361,"ogSiteName":672,"ogType":673,"canonicalUrls":1361,"schema":1362},"Meet Praefect: The traffic manager making your Git data highly available","This router and transaction manager ensures there are multiple copies of each Git repository available in the event of an outage – no NFS required.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749669204/Blog/Hero%20Images/traffic-intersection.jpg","https://about.gitlab.com/blog/high-availability-git-storage-with-praefect","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Meet Praefect: The traffic manager making your Git data highly available\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Zeger-Jan van de Weg\"}],\n        \"datePublished\": \"2021-01-21\",\n      }",{"title":1358,"description":1359,"authors":1364,"heroImage":1360,"date":1366,"body":1367,"category":705,"tags":1368},[1365],"Zeger-Jan van de Weg","2021-01-21","\nAs critical software projects grow, scaling infrastructure to make the service [highly available](https://en.wikipedia.org/wiki/High_availability) is key. At GitLab, our biggest struggle in scaling was right in our name: Git.\n\n## The trouble with scaling Git\n\nGit is software that is distributed, but not usually run in a ‘highly available cluster,’ which is what GitLab needs. At first, we solved this with a [boring solution](https://handbook.gitlab.com/handbook/values/#boring-solutions), NFS – which exposes a shared filesystem across multiple machines and generally worked. As we’d soon find out, most NFS appliances were for bulk storage and not fast enough. This led to problems with GitLab’s Git access being slow.\n\nTo solve the speed problem we built [Gitaly, our service that provides high-level RPC access to Git repositories](https://docs.gitlab.com/ee/administration/gitaly/). \n\nWhen we started with [Gitaly v1.0](/blog/the-road-to-gitaly-1-0/), our goal was to remove the need for a network-attached filesystem access for Git data. When that was complete, the next problem to tackle was that all your data is only stored once. So, if you have a server down, or your hard disk dies, or something happens to this one copy, you're in deep trouble until a backup is restored. This is an issue for GitLab.com, but it’s also a big risk for our customers and community.\n\nBack at our [Summit in Cape Town](/company/culture/contribute/previous/#summit-in-cape-town-south-africa) in 2018, the Gitaly team (at the time, that was [Jacob Vosmaer](/company/team/?department=all#jacobvosmaer-gitlab) and me) and some other engineers discussed pursuing a fault-tolerant, highly available system for Git data. For about a month we went back and forth about how we would go about it – ranging from wild ideas to smaller iterations towards what we want. The challenge here was that the ultimate aim is always going to be 100% availability, but you’re never going to make that. So let's aim for a lot of nines (three nines being 99.9%, five being 99.999%, etc.) Ideally, we'd be able to iterate to 10 nines if we wanted to. \n\nEventually we chose the design of a proxy: introduce a new component in the GitLab architecture, which is Praefect, and then route all the traffic through it to Gitaly storage nodes to provide a [Gitaly Cluster](https://docs.gitlab.com/ee/administration/gitaly/praefect.html). Praefect inspects the request and tries to route it to the right Gitaly backend, checks that Gitaly is up, makes sure the copies of your data are up to date, and so on. \n\n## First iteration: Eventual consistency\n\nTo cut the scope, for our first iterations we settled on eventual consistency, which is fairly common – we even use it for some GitLab features. With Git data, if we are behind a minute, it's not a big deal because at GitLab at least 90% of operations on our Git data are just reads, compared to a very small volume of writes. If I run `git pull` and I'm one commit behind master, that's not ideal, but not a deal breaker in most cases. \n\nWith eventual consistency, each repository gets three copies: one primary and two secondary. We replicate your data from the primary to the other copies, so that if your primary is inaccessible, we can at least give you read access to the secondary copies until we recover the primary. There’s a chance the secondaries are one or two commits behind your primary, but it’s better than no access.\n\nWe rolled this out in [13.0](/releases/2020/05/22/gitlab-13-0-released/#gitaly-cluster-for-high-availability-git-storage) as generally available. \n\n## Strong consistency\n\nThe next stage was to work on strong consistency, where all of your three copies are always up to date. \n\nWhen you write to your Git repository, there’s a moment where Praefect says, “OK, I'm going to update branch A from #abc to #cbd.” If all three copies agree on the updates, then Praefect tells everyone to apply this update and now, almost at the same moment in time, they'll update the data to the same thing. Now you've got three copies that are up to date.\n\nSo, if one copy is offline for some reason – let’s say a network partition, or the disk is corrupted – we can serve from the other two copies. Then the data remains available, and you have more time to recover the third copy as an admin. Effectively, while you always have a designated primary, it's actually more like having _three_ primaries, because they are all in the same state. \n\nIf the default state of a system is consistent it requires maintaining this consistency on each mutation to the data that's performed. All possible requests to Gitaly are grouped into two classes: mutators and accessors. Meaning that there was a risk we had to migrate each mutator RPC individually. That would've been a major effort, and if possible, we wanted to push this problem to Git. Gitaly uses Git for the majority of write operations, and was thus the largest common denominator.\n\nSo Git had to become aware of transactions, which ideally isn't part of Git. There are more areas where it would be nice if Git was aware of business logic, but if we're honest with ourselves, it's not really Git's concern: authentication and authorization. At GitLab we use [Git Hooks](https://git-scm.com/docs/githooks.html#_hooks) for that. So the idea [applied and contributed](https://public-inbox.org/git/1de96b96e3448c8f7e7974f7c082fd08d2d14e96.1592475610.git.ps@pks.im/T/#m9ae42f583968aa1d8ca43bd3007333cf51a618cc) (thanks, [Patrick Steinhardt](/company/team/#pks-gitlab)!) was the same: when events happen with Git, execute a hook and allow Gitaly to execute business logic. Through the exit code of the hook, Git is signaled on how to proceed. In Git, these events are updates of any reference (for example, branches or tags). When this happens Git will then allow Gitaly to participate in a [three-phase commit](https://en.wikipedia.org/wiki/Three-phase_commit_protocol) transaction by communicating back to Praefect, and enforce consistency. So we got that released in Git, fixed a bug, and now we’re [rolling it out to almost all write requests](https://gitlab.com/gitlab-org/git/-/issues/79).\n\n## A defensible cost increase\n\nNow strong consistency is great, but we are effectively asking our customers, “Instead of one copy, why don't you triple your storage costs and your server costs and whatnot, and you have zero benefits unless something goes wrong.” That wasn't really appealing for most customers, but now we’ve sweetened the deal with increased performance and making the cost increase more manageable. \n\nSo, if you have three copies of your data that are up to date, then all of them could serve any request that doesn't mutate the data, right? Because you know they're up to date. Right now, [Pavlo](/company/team/?department=gitaly-team#8bitlife) is working on [read distribution, which we are making generally available in 13.8](https://gitlab.com/gitlab-com/www-gitlab-com/-/merge_requests/71960) (coming Jan. 22, 2021). [We rolled it out briefly before](https://gitlab.com/gitlab-com/www-gitlab-com/-/merge_requests/58694), but it didn’t scale as expected, so we’ve worked with QA to mitigate that.\n\nRight now, Praefect is rolled out to a very limited subset of projects on GitLab.com, because running it is expensive already. When I first proposed rolling it out for everyone, it was very quick to calculate that that will triple our Gitaly Clusters – not within the budget at all! So we're trying to iterate towards that goal. The first step is to work on allowing a [variable replication factor](https://docs.gitlab.com/ee/administration/gitaly/praefect.html#variable-replication-factor). It can be expensive to store a lot of data multiple times, so why don't we make it so that you can store some repositories three times and some just one time, and you don't get the guarantees and the availability of those with three copies.\n\n## Challenges and lessons learned\n\nSo we have Praefect, this new component, but it's not installed by default on GitLab Omnibus –\nyou have to enable it yourself. The [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit) uses it as well as the tests on GitLab.com, for GitLab projects, but that wasn’t always the case. When you have an optional part in your architecture, if you’re debugging or talking with customers, there is the additional mental burden of verifying what the architecture looks like. Without it, you can make much quicker assumptions on what's going on and why it's working or why it isn't. Officially, we have deprecated NFS, so it makes sense to make it a required component so we can depend on it being there.\n\nAlso, as we add more features to Praefect, if it’s still optional then some customers get those added benefits and some don’t.\n\n### We should have put it in production sooner\n\nOur first iteration was just proxying the traffic, doing nothing with it, and verifying that it works. We didn't put it in production because it offered nothing to the community. But, it includes new components in your architecture, which our SREs need to know about, and there were a couple of bugs we found out much later. I was hesitant to put something in production that didn't offer anything in return, but if we’d been a little more aggressive with putting it out there – even just for a small subset of projects – we would understand more quickly what we're running, what was working, and what wasn't. \n\n### Applying big architectural changes takes time\n\nIf you ask customers to make giant architectural changes, it's going to take longer than you think. When we released Praefect and Gitaly Clusters in 13.0, it was fairly rough around the edges and some things weren't working as you would expect, but it was a good time to release because now, six months later, we see customers finally starting to implement it. They want to validate, try it out on a subset, and then finally roll it out for their whole GitLab instance. While that took longer than I expected, it's cool to see the numbers going up now, and adoption is growing quite rapidly.\n\n## More than just a traffic manager\n\nPraefect does much more than just inspect the traffic. If Gitaly goes down, ideally you want to notice that before you actually fire a request, which Praefect does. It does failover, so if one fails and it was designated as a primary, then it fails over to a secondary, which is now designated as a primary. \n\nI'm really excited for the next few years and the kind of things we are planning to build in Praefect and what that will deliver to GitLab.com and our customers and community. Where before we didn’t have very granular control over what we were doing or why we were doing it, now we can intercept and optimize.\n\n## What's next\n\nWe're shipping [HA Distributed Reads](https://gitlab.com/gitlab-org/gitaly/-/issues/3334) in GitLab 13.8 (Jan. 22, 2021). For 13.9, we're shooting for [strong consistency in the Gitaly Cluster](https://gitlab.com/groups/gitlab-org/-/epics/1189) and [variable replication factor](https://gitlab.com/groups/gitlab-org/-/epics/3372).\n\nFor GitLab self-managed users, consider enabling Praefect if you have high availability requirements. Visit our [Gitaly Clusters documentation](https://docs.gitlab.com/ee/administration/gitaly/praefect.html) to get started.\n\n_Major thanks to [Rebecca Dodd](/company/team#rebecca) who contributed to this post._\n\nCover image by [Yoel J Gonzalez](https://unsplash.com/@yoeljgonzalez?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText\") on [Unsplash](https://unsplash.com/s/photos/traffic?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)\n{: .note}\n",[9,1369,881,230,731],"git",{"slug":1371,"featured":6,"template":686},"high-availability-git-storage-with-praefect","content:en-us:blog:high-availability-git-storage-with-praefect.yml","High Availability Git Storage With Praefect","en-us/blog/high-availability-git-storage-with-praefect.yml","en-us/blog/high-availability-git-storage-with-praefect",{"_path":1377,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1378,"content":1384,"config":1390,"_id":1392,"_type":13,"title":1393,"_source":15,"_file":1394,"_stem":1395,"_extension":18},"/en-us/blog/how-gitlabs-red-team-automates-c2-testing",{"title":1379,"description":1380,"ogTitle":1379,"ogDescription":1380,"noIndex":6,"ogImage":1381,"ogUrl":1382,"ogSiteName":672,"ogType":673,"canonicalUrls":1382,"schema":1383},"How GitLab's Red Team automates C2 testing ","Learn how to apply professional development practices to Red Teams using open source command and control tools.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749665667/Blog/Hero%20Images/built-in-security.jpg","https://about.gitlab.com/blog/how-gitlabs-red-team-automates-c2-testing","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How GitLab's Red Team automates C2 testing \",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Josh Feehs\"}],\n        \"datePublished\": \"2023-11-28\",\n      }",{"title":1379,"description":1380,"authors":1385,"heroImage":1381,"date":1387,"body":1388,"category":708,"tags":1389},[1386],"Josh Feehs","2023-11-28","At GitLab, our [Red Team](https://handbook.gitlab.com/handbook/security/threat-management/red-team/) conducts security exercises that emulate real-world threats. By emulating real-world threats, we help assess and improve the effectiveness of the people, processes, and technologies used to keep our organization secure. To operate effectively, we must utilize professional development practices like the threat actors we emulate.\n\n[Threat actors](https://www.securonix.com/blog/threat-labs-security-advisory-new-starkvortex-attack-campaign-threat-actors-use-drone-manual-lures-to-deliver-merlinagent-payloads/) use open source command and control (C2) tools such as [Merlin](https://github.com/Ne0nd0g/merlin). While convenient, these tools have intentionally detectable features to discourage illegitimate use. Red Teams often need to customize and combine different open source options to evade detections in the environments they target.\n\nIn this blog, you'll learn how our team applies professional development practices to using open source C2 tools. We'll share how we implement continuous testing for the Mythic framework, our design philosophy, and a public project you can fork and use yourself.\n\nOur solution, available in [this public project](https://gitlab.com/gitlab-com/gl-security/threatmanagement/redteam/redteam-public/continuousmage), improves our Red Team operations in two ways. First, it contains a suite of **pytest** tests for the Mythic C2 framework. These validate functionality of both the Mythic server and multiple Mythic-compatible agents. Second, it leverages **GitLab CI/CD pipelines** to automatically run these tests after each code change. This enables iterative development and rapid validation of updates to Mythic or Mythic-compatible C2 agents.\n\n## Prerequisites\n\nCurrently, a few prerequisites fall outside the scope of test automation:\n\n- A Linux VM with Mythic, its Python requirements, and the HTTP profile installed. See the [Mythic installation guide](https://docs.mythic-c2.net/installation). We suggest binding Mythic's admin interface to localhost only.\n- A fork of [the ContinuousMage GitLab project](https://gitlab.com/gitlab-com/gl-security/threatmanagement/redteam/redteam-public/continuousmage) in GitLab.com or your own GitLab instance. You'll build on top of this to run your own automation. We highly suggest making this fork private, so you don't expose your test infrastructure or C2 code changes.\n- GitLab Runner installed on the VM (configured with the [shell executor](https://docs.gitlab.com/runner/executors/shell.html)) and registered with your GitLab instance. See the docs on [installing](https://docs.gitlab.com/runner/install/) and [registering](https://docs.gitlab.com/runner/register/) a runner or follow the instructions provided when configuring your pipeline later in this blog. You'll assign this runner to your project when we configure CI/CD.\n- Your forked project cloned onto your VM. This allows testing code changes (or new tests) before triggering the pipeline.\n\n## Project structure\n\nThe project contains three main portions that we will detail in this blog post:\n\n1. `pytest` test code for running integration tests for Mythic and Mythic-compatible C2 agents\n2. The source of those Mythic-compatible C2 agents, as git submodules\n3. The GitLab CI/CD pipeline configuration that ties it all together\n\n## Part 1: pytests\n\n[pytest](https://docs.pytest.org/en/7.4.x/) is a framework for writing tests in Python. We can leverage pytest to do integration testing of Mythic since it has its own [Python package](https://pypi.org/project/mythic/). The test suite goals are:\n\n1. Be simple and atomic.\n2. Provide adequate coverage to validate tool readiness.\n\nWe'll walk through a simple test verifying an agent can run the `ls` command, highlighting key code sections for customization.\n\n### Implementation\n\n#### pytest file\n\nWhen run on a directory, `pytest` automatically discovers tests in files prefixed with `test_` and test functions starting with `test_`. Our tests are asynchronous, needing the `pytest.mark.asyncio` decorator, because the Mythic APIs we are testing are asynchronous. If your machine is missing test dependencies, run `python3 -m pip install mythic pytest pytest-asyncio`.\n\nA test function skeleton is as follows:\n\n```python\n@pytest.mark.asyncio\nasync def test_agent_ls():\n    # Will do the test here\n    continue\n```\n\n#### The GlMythic class\n\nThe `GlMythic` class wraps Mythic APIs for ease of use in testing. Because its `init` function is async, a coroutine creates the object:\n\n```python\n@pytest.mark.asyncio\nasync def test_agent_ls():\n    glmythic = await gl_mythic.create_glmythic()\n```\n\nBy default, it connects to the Mythic DB using the `MYTHIC_ADMIN_PASSWORD` environment variable and is configured to test the agent specified via the `AGENT_TYPE` environment variable. We will set these in the CI/CD config later.\n\n#### Interacting with Mythic via GlMythic\n\nWe'll include the remainder of the test code here, with comments, and then discuss the most important parts.\n\nAs a reminder, one of the key goals of this project was to make completely atomic tests. Each test only relies on a running Mythic server with the specific agent and HTTP containers loaded. As the test suite grows, it may be worth running a secondary set of tasks that relies on an already-existing agent connection. Currently, every test creates, downloads, and executes a new agent.\n\n### Test and deploy\n\n```python\n@pytest.mark.asyncio\nasync def test_agent_ls():\n\n    glmythic = await gl_mythic.create_glmythic()\n\n    # Unique payload_path per test\n    payload_path = \"/tmp/test_agent_ls\"\n\n    # Wraps agent create, download, and execute\n    proc = await glmythic.generate_and_run(payload_path=payload_path)\n\n    # Wait for callback\n    time.sleep(10)\n\n    # Uses the display_id field to determine most recent callback\n    # Assumes that the most recent callback is the one created by this test\n    callback = await glmythic.get_latest_callback()\n\n    # Issue the ls command, blocking on output\n    output = await mythic.issue_task_and_waitfor_task_output(\n        mythic=glmythic.mythic_instance,\n        command_name=\"ls\",\n        parameters=\"\",\n        callback_display_id=callback[\"display_id\"],\n        timeout=20,\n    )\n\n    # Clean up (no longer need the agent)\n    proc.terminate() \n    os.remove(payload_path)\n\n    # If the ls failed, there will be no output\n    # This test could also look for files in the repo (where the agent runs)\n    assert len(output) > 0\n\n```\n\nThe longest running portion of this test will be the call to `generate_and_run`, as agent builds within Mythic can take from seconds to minutes or even hang altogether. For your initial set of tests, sign in to the Mythic server and watch the **Payloads** screen for potential issues. In our testing, agent builds failed to complete around 5% of the time, depending on the agent. If you experience repeated build failures, reload your agent container with `sudo ./mythic-cli install folder \u003Cagent_directory> -f`.\n\nTo run the tests, run `pytest \u003Ctestfile_directory>`.\n\n## Part 2: Agent source as submodules\n\nBecause Mythic agents are often updated, we include the agent repos as git submodules in our test project. This allows us to update to new agent versions when they are released and use our project's version control to keep tool versions static for known good builds. These submodules are all located in the `agents` folder.\n\nWe'll discuss adding more agents to this project later in this blog.\n\n## Part 3: GitLab CI/CD pipeline\n\nNow that you have working pytests, you can automate your tests to run whenever you want. In our case, we chose to run our tests on merge requests and tagged commits (which are likely to be tool releases). We will be using [GitLab CI/CD pipelines](https://docs.gitlab.com/ee/ci/pipelines/) to perform our automated tests.\n\n### Configuring the pipeline\n\nNow is the time to set your GitLab CI/CD settings. To find these settings, go to your repository -> `Settings` -> `CI/CD`.\n\nThe first setting you'll want to set is your `Runner`. If you set up a runner as one of your prerequisite steps earlier, you can assign it here. If not, click `New project runner` and work through that process to create and set up your runner on your Mythic server. When you are prompted to choose a runner type on install, choose the [shell executor](https://docs.gitlab.com/runner/executors/shell.html). If your team uses shared runners for other CI/CD pipelines, you will want to make sure that shared runners are disabled for this project, given that your shared runners are unlikely to be able to talk to Mythic directly.\n\n![runner-settings](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683075/Blog/Content%20Images/runner-settings.png)\n\nNext, you need to set your `Variables`. The `GlMythic` class uses the `MYTHIC_ADMIN_PASSWORD` environment variable to be able to actually sign into Mythic, so you need to make sure that the pipeline runner's environment is set up correctly.\n\nTo do this, click the `Add variable` button and add the `MYTHIC_ADMIN_PASSWORD` variable with the appropriate value. If you don't know your Mythic admin password, on the Mythic server in the directory where you installed Mythic, `cat .env | grep MYTHIC_ADMIN_PASSWORD` will give you the password.\n\nBecause GitLab handles merge requests in a detached state, you need to unclick the `Protect Variable` box, because that would prevent the pipeline from viewing the variable on a merge request otherwise. Because the variable is not protected, any branch committed back to your server can access your CI variables. This may pose a security risk if you allow remote access to your Mythic server (versus binding to localhost) and if you allow arbitrary users to access your repository. For this reason, our public repo does not have the environment variables. We use a private copy to perform testing, and suggest you do the same.\n\nAdditionally, set the `AGENT_TYPE` variable to the name of the agent you want to use. At time of release, valid agent types are `poseidon` or `merlin`. The section about adding more agents to the test suite will go into more detail.\n\nYou can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser.\n\nNow that the pipeline is configured to use the runner and pick up the environment variables that you need, the only thing left to do is to set up your pipeline. This step is quite simple: If you add the `.gitlab-ci.yml` file to the root of your repository, GitLab will pick that up as the pipeline config on your next commit. Here is our example pipeline, which we will explain momentarily.\n\n```yaml\ninstall:\n  stage: install\n  script:\n    - sudo /opt/Mythic/mythic-cli install folder \"${CI_PROJECT_DIR}\"/agents/\"${AGENT_TYPE}\" -f\n  rules:\n    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n    - if: $CI_COMMIT_TAG\n\ntest:\n  stage: test\n  script:\n    - pytest \"${CI_PROJECT_DIR}\"/mythic-test\n  rules:\n    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n    - if: $CI_COMMIT_TAG\n```\n\nAll of the variables set above are made available by GitLab as part of every pipeline. This pipeline has two stages, `install` and `test`. Both stages are set to only run on merge requests or if the commit being evaluated has a specific tag. The `install` stage will install your C2 agent into Mythic using its local folder install. This makes sure that the Mythic server has your latest C2 code changes installed. Next, the `test` stage runs the set of pytest tests that we created. The `install` stage will run very quickly, and the `test` stage will run a little more slowly, given that it's doing the work of creating and interacting with Mythic agents.\n\n### Pipeline in action\n\nYou can do a couple of things to validate that your pipeline is working. First, if you are performing a merge request, there will be a section at the beginning of the merge request that will link to the pipeline. The screenshot below shows that the pipeline has passed, but you can click into the pipeline by clicking on its number even when it's running.\n\n![Pipeline passing](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683075/Blog/Content%20Images/merge-pipeline-pass.png)\n\nYou can then click into the stage that's running (or one that has already run) to view its output.\n\n![Pipeline task output](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683075/Blog/Content%20Images/pipeline-task-output.png)\n\nAnd there you are! You now have working `pytest` tests for a Mythic agent that run every time you make a merge request.\n\n## Adapting for other agents\n\nWe tested our test suite against Poseidon and Merlin. Although the initial tests (generate, download and exec, ls) work the same for both agents, Poseidon and Merlin require different parameters for their `upload` commands. Unfortunately, this means that not all tests will be agent agnostic.\n\nAs a result, each `GlMythic` object that is created is told what type of agent it is testing. The coroutine for creating an object allows you to pass in the agent type as a variable, and defaults to using the `AGENT_TYPE` environment variable to determine which agent is being tested.\n\n```python\nasync def create_glmythic(  username=\"mythic_admin\",\n                            password=os.getenv(\"MYTHIC_ADMIN_PASSWORD\"),\n                            server_ip=\"127.0.0.1\",\n                            server_port=7443,\n                            agent_type=os.getenv(\"AGENT_TYPE\")):\n```\n\n### Agent source\n\nTo add more agents for testing, the first thing to do is to import your agent as a git submodule:\n\n```bash\ncd agents\ngit submodule add \"${URL_TO_YOUR_AGENT}\"\n```\n\nCommit your changes, and your agent is tracked as part of the repo.\n\n### Test compatibility\n\nYou'll need to validate that existing tests work with your agent. For tests to work, the parameters passed to the commands must match those in the test suite, with `upload` to be most likely to fail.\n\nThis is okay! Within the `test_agent_upload` test function, you'll see example code that specifies a different upload command for Merlin and Poseidon. Simply follow this structure for your own agent, passing your agent's parameters to the `mythic.issue_task_and_waitfor_task_output` function call.\n\nIf you are using another open source C2 and are unsure of the correct parameters to pass, you can use the Mythic UI. Interact with one of your agents and run the `upload` command to see what params you need to pass. If you do this for Poseidon, it will look like the following:\n\n![upload-parameters](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683075/Blog/Content%20Images/upload-parameters.png)\n\nOur test suite should be pretty easy to add to any Linux-based Mythic agent that supports the [HTTP C2 profile](https://github.com/MythicC2Profiles/http). Because the GitLab Runner installs the agent into Mythic (and Mythic is made to run on Linux), the runner is expecting to be on a Linux machine. Additional effort and test modifications will be required to run the test suite against a Windows or MacOS agent.\n\n## A quick win\n\nAs we worked on this project, we were continuously running our test suite against both Poseidon and Merlin. Unexpectedly, in early October 2023, our test for Poseidon's `upload` function started to fail. After a quick investigation, we identified that a bug had been introduced, present in Poseidon 2.0.2, that caused file uploads to fail.\n\nWe took our information to one of the Poseidon developers, Cody Thomas ([@its_a_feature_](https://twitter.com/its_a_feature_)), and he quickly identified the underlying issue and [fixed the problem](https://github.com/MythicAgents/poseidon/commit/83de4712448d7ed948b3e2d2b2f378d530b3a42a).\n\nThis highlights the usefulness of continuous testing. Instead of running into a potential bug during a Red Team exercise, we identified the issue beforehand and were able to report the bug so the issue was fixed.\n\nWe sincerely thank the Mythic, Merlin, and Poseidon developers for open sourcing their hard work. Many Red Teams around the world are able to perform high-quality security assessments in part because of the hard work of C2 developers who open source their tools. We also want to specifically thank Cody Thomas for addressing this bug within 20 minutes of notification. His responsiveness and attention to detail are unmatched.\n\n## Share your feedback\n\nThis post has demonstrated both the value of continuous testing and shown how to implement continuous testing for your own use, using GitLab. If you have worked alongside these examples, you've implemented some continuous testing for the Mythic framework and have tests that you can use for Merlin, Poseidon, or your own Mythic agent(s).\n\nAt GitLab, we always seek feedback on our work. If you have any questions or comments, please open an issue on [our project](https://gitlab.com/gitlab-com/gl-security/threatmanagement/redteam/redteam-public/continuousmage). You can also propose improvements via a merge request. We believe that everyone should be able to contribute, so we welcome any contributions, big or small.\n\n> [Try GitLab Ultimate for free today.](https://gitlab.com/-/trials/new)\n\n## Related reading\n- [Stealth operations: The evolution of GitLab's Red Team](https://about.gitlab.com/blog/stealth-operations-the-evolution-of-gitlabs-red-team/)\n- [How we run Red Team operations remotely](https://about.gitlab.com/blog/how-we-run-red-team-operations-remotely)\n- [Use GitLab and MITRE ATT&CK Navigator to visualize adversary techniques](https://about.gitlab.com/blog/gitlab-mitre-attack-navigator)\n- [Monitor web attack surface with GitLab](https://about.gitlab.com/blog/monitor-web-attack-surface-with-gitlab)\n",[708,9,230,798],{"slug":1391,"featured":90,"template":686},"how-gitlabs-red-team-automates-c2-testing","content:en-us:blog:how-gitlabs-red-team-automates-c2-testing.yml","How Gitlabs Red Team Automates C2 Testing","en-us/blog/how-gitlabs-red-team-automates-c2-testing.yml","en-us/blog/how-gitlabs-red-team-automates-c2-testing",{"_path":1397,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1398,"content":1404,"config":1410,"_id":1412,"_type":13,"title":1413,"_source":15,"_file":1414,"_stem":1415,"_extension":18},"/en-us/blog/how-to-automate-testing-for-a-react-application-with-gitlab",{"title":1399,"description":1400,"ogTitle":1399,"ogDescription":1400,"noIndex":6,"ogImage":1401,"ogUrl":1402,"ogSiteName":672,"ogType":673,"canonicalUrls":1402,"schema":1403},"How to automate testing for a React application with GitLab","Learn how to add React automated tests to a GitLab CI pipeline with this tutorial.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666775/Blog/Hero%20Images/cover.jpg","https://about.gitlab.com/blog/how-to-automate-testing-for-a-react-application-with-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to automate testing for a React application with GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Jeremy Wagner\"}],\n        \"datePublished\": \"2022-11-01\",\n      }",{"title":1399,"description":1400,"authors":1405,"heroImage":1401,"date":1407,"body":1408,"category":705,"tags":1409},[1406],"Jeremy Wagner","2022-11-01","\n\nReact is a popular JavaScript library for building user interfaces. In this tutorial, I'll show you \nhow to create a new React application, run unit tests as part of the CI process in GitLab, and output\nthe test results and code coverage into the pipeline.\n\n## Prerequisites\n\nFor this tutorial you will need the following:\n\n- [Node.js](https://nodejs.org/en/) >= 14.0.0 and npm >= 5.6 installed on your system\n- [Git](https://git-scm.com/) installed on your system\n- A [GitLab](https://gitlab.com/-/trial_registrations/new) account\n\n## Getting started\n\nTo get started, [create a new project in GitLab](https://docs.gitlab.com/ee/user/project/working_with_projects.html#create-a-project).\n\nWhen you are on the \"Create new project\" screen, select \"Create blank project.\" Fill out the project information \nwith your project name and details. After you create the project, you will be taken to the project with an empty repository.\n\nNext, we will clone the repository to your local machine. Copy the SSH or HTTPS URL from the \"Clone\" button and run the following\ncommand in the terminal for your working directory:\n\n```\ngit clone \u003Cyour copied URL here>\n```\n\n## Create the React app\n\nYou will create a new React application by using [Create React App](https://reactjs.org/docs/create-a-new-react-app.html#create-react-app).\n\nFrom the terminal `cd` into your newly cloned project directory and run this command:\n\n```\nnpx create-react-app .\n```\n\nThe npx CLI tool will create a new React application inside of your current directory.\n\nTo run the application, run the following command in your terminal:\n\n```\nnpm run start\n```\n\nYou can view the application you created in your browser window at `https://localhost:3000`.\n\n![Create React App home page](https://about.gitlab.com/images/blogimages/2022-11-04-how-to-automate-testing-for-a-react-application-with-gitlab/create-react-app.png){: .shadow}\n\nStop your application by pressing `CTRL` + `c` in your terminal. \n\nPush your new application to GitLab by running the following commands:\n\n```\ngit add -A\ngit commit -m \"Initial creation of React application\"\ngit push\n```\n\n## Testing your application\n\nBy default, Create React App uses [jest](https://jestjs.io/) as the test runner and one unit test to run.\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport App from './App';\n\ntest('renders learn react link', () => {\n  render(\u003CApp />);\n  const linkElement = screen.getByText(/learn react/i);\n  expect(linkElement).toBeInTheDocument();\n});\n```\n\nInside your `package.json`, you should see that it comes with several scripts.\n\n```javascript\n\"scripts\": {\n    \"start\": \"react-scripts start\",\n    \"build\": \"react-scripts build\",\n    \"test\": \"react-scripts test\",\n    \"eject\": \"react-scripts eject\",\n  }\n```\n\nUse the test script to run the tests in your application by running the following command:\n\n```\nnpm run test\n```\n\nWhen prompted for \"Watch Usage,\" press `a` to run all of the tests. You will see that the existing test passes and it continues to watch for changes.\n\n![CLI passing tests](https://about.gitlab.com/images/blogimages/2022-11-04-how-to-automate-testing-for-a-react-application-with-gitlab/passing-test-cli.png){: .shadow}\n\nFor local development, watching for changes to run the tests is great; however, for our CI pipeline we would like to run the tests once, \ncreate a report so that we can see the results inside of our pipeline, and also determine the code coverage.\n\nExit the jest test watcher by pressing `CTRL` + `c` in your terminal. \n\n## Add unit test reporting and coverage\n\nTo view the unit test report, GitLab requires the runner to upload a JUnit report format XML file.\nWe will use `jest-junit` to generate this file. This is a unit test report for jest and will create an XML\nfile in the right format.\n\nTo install `jest-junit`, run the following command in your terminal:\n\n```\nnpm install --save-dev jest-junit\n```\n\nNow, add a new script to run the unit tests inside of your CI pipeline.\nAdd a `test:ci` script to your `package.json` that looks like this:\n\n```javascript\n\"scripts\": {\n    \"start\": \"react-scripts start\",\n    \"build\": \"react-scripts build\",\n    \"test\": \"react-scripts test\",\n    \"eject\": \"react-scripts eject\",\n    \"test:ci\": \"npm run test -- --testResultsProcessor=\\\"jest-junit\\\" --watchAll=false --ci --coverage\"\n  },\n```\n\n`--testResultsProcessor=\\\"jest-junit\\\"` tells jest to use the `jest-junit` library to create a unit test \nreport. `--watchAll=false` disables watch mode so that the tests will not rerun when something changes. `--ci` tells \nJest that it is running in a CI environment. `--coverage` tells Jest that test coverage information should be collected \nand reported in the output. For more information on these options, visit the [jest CLI options](https://jestjs.io/docs/cli) documentation.\n\n\nIf you run the new `test:ci` script, it will run the tests and create an XML file named `junit.xml` and print coverage statistics to the CLI.\n\n\n![CLI code coverage](https://about.gitlab.com/images/blogimages/2022-11-04-how-to-automate-testing-for-a-react-application-with-gitlab/coverage-cli.png){: .shadow}\n\n## Add unit tests to your CI pipeline\n\nIn the root of your application, create a file named `.gitlab-ci.yml`. \n\nDefine a test stage for your pipeline by adding the following code to your `.gitlab-ci.yml` file:\n\n```\nstages:\n  - test\n```\n\nNext, add a job named `unit-test` that will be responsible for running the unit tests in the test stage. Add the following code below the\ndefined stages:\n\n```\nunit-test:\n  image: node:latest\n  stage: test\n  before_script:\n    - npm install\n  script:\n    - npm run test:ci\n  coverage: /All files[^|]*\\|[^|]*\\s+([\\d\\.]+)/\n  artifacts:\n    paths:\n      - coverage/\n    when: always\n    reports:\n      junit:\n        - junit.xml\n```\n\nYour complete `.gitlab-ci.yml` file should look like this:\n\n```\nstages:\n  - test\n\nunit-test:\n  image: node:latest\n  stage: test\n  before_script:\n    - npm install\n  script:\n    - npm run test:ci\n  coverage: /All files[^|]*\\|[^|]*\\s+([\\d\\.]+)/\n  artifacts:\n    paths:\n      - coverage/\n    when: always\n    reports:\n      junit:\n        - junit.xml\n```\n\n\nBefore we push these changes to GitLab, add the following line to your `.gitignore`:\n\n```\njunit.xml\n```\n\nAdd your changes to GitLab by running these commands in your terminal:\n\n```\ngit add -a\ngit commit -m \"Adds .gitlab-ci.yml with unit testing\"\ngit push\n```\n\nWhen this command finishes, your code will be pushed to your project in GitLab and a pipeline will start \nautomatically running the `unit-test` job we defined earlier.\n\n![CI pipeline running](https://about.gitlab.com/images/blogimages/2022-11-04-how-to-automate-testing-for-a-react-application-with-gitlab/ci-pipeline-starting.png){: .shadow}\n\nWhen the pipeline completes, click the pipeline ID (_#680073569 in this case_).\n\nInside the pipeline, click the _Jobs_ tab and you should see the coverage for the unit-test job is 8.33%.\n\n![CI pipeline coverage](https://about.gitlab.com/images/blogimages/2022-11-04-how-to-automate-testing-for-a-react-application-with-gitlab/ci-pipeline-coverage.png){: .shadow}\n\nClick the _Tests_ tab and you can see the testing results for the unit-test job. \n\n![CI pipeline tests](https://about.gitlab.com/images/blogimages/2022-11-04-how-to-automate-testing-for-a-react-application-with-gitlab/ci-pipeline-tests.png){: .shadow}\n\nClick the name of the job _unit-test_ and you will see the status for each of the test suites run.\n\n![CI pipeline test details](https://about.gitlab.com/images/blogimages/2022-11-04-how-to-automate-testing-for-a-react-application-with-gitlab/ci-pipeline-test-details.png){: .shadow}\n\nCongratulations! You just added automated tests for your React application to your CI pipeline inside of GitLab and output the results to the pipeline.\n\nAll code for this tutorial can be found in this [project](https://gitlab.com/jwagner-demo/vandelay-industries/engineering/react-app).\n\nCover image by [Lautaro Andreani](https://unsplash.com/@lautaroandreani?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/react?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n\n## Related Posts\n- [The GitLab guide to modern software testing](https://about.gitlab.com/blog/the-gitlab-guide-to-modern-software-testing/)\n- [Unit Test Reports](https://docs.gitlab.com/ee/ci/testing/unit_test_reports.html)\n- [coverage keyword](https://docs.gitlab.com/ee/ci/yaml/#coverage)\n",[9,682,108],{"slug":1411,"featured":6,"template":686},"how-to-automate-testing-for-a-react-application-with-gitlab","content:en-us:blog:how-to-automate-testing-for-a-react-application-with-gitlab.yml","How To Automate Testing For A React Application With Gitlab","en-us/blog/how-to-automate-testing-for-a-react-application-with-gitlab.yml","en-us/blog/how-to-automate-testing-for-a-react-application-with-gitlab",{"_path":1417,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1418,"content":1424,"config":1430,"_id":1432,"_type":13,"title":1433,"_source":15,"_file":1434,"_stem":1435,"_extension":18},"/en-us/blog/how-to-benchmark-security-tools",{"title":1419,"description":1420,"ogTitle":1419,"ogDescription":1420,"noIndex":6,"ogImage":1421,"ogUrl":1422,"ogSiteName":672,"ogType":673,"canonicalUrls":1422,"schema":1423},"How to benchmark security tools: a case study using WebGoat","When tasked to compare security tools, it's critical to understand what's a fair benchmark. We take you step by step through WebGoat's lessons and compare them to SAST and DAST results.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749678166/Blog/Hero%20Images/benchmarking.jpg","https://about.gitlab.com/blog/how-to-benchmark-security-tools","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to benchmark security tools: a case study using WebGoat\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Isaac Dawson\"}],\n        \"datePublished\": \"2020-08-11\",\n      }",{"title":1419,"description":1420,"authors":1425,"heroImage":1421,"date":1426,"body":1427,"category":708,"tags":1428},[854],"2020-08-11","As your organization grows, the necessity for having automated security tools be a component of your development pipeline will increase. According to the latest [BSIMM10 study](https://www.bsimm.com/about.html), full-time security members represented just 1.37% of the number of developers. That's one security team member for every 73 developers. Without automated tooling assisting security teams, vulnerabilities may more easily find their way into production.\n\nWhen tasked to compare security tools, having the knowledge to judge a fair benchmark is paramount for success. We're going to take a very in-depth look at WebGoat's lessons in this blog post.\n\n## Running a fair benchmark\n\nThere are many factors that need to be taken into consideration when comparing various security tools.\n\n1. Is the tool I am testing right for my organization?\n2. Do the applications I chose to run the tools against reflect what my organization uses?\n3. Do the applications contain real security issues in real world scenarios, or are they synthetic?\n4. Are the results consistent across runs? Are they actionable? \n\n### Choosing the right tool\n\nSome tools are developer focused, while others may be tailored to security teams. Highly technical tools built for security teams may give better results, but if it requires domain expertise and significant tuning, it may end up being a worse choice for your organization. \n\nYour organization may also be more concerned about a tool which can run relatively quickly within your development pipeline. If your developers need results immediately, a SAST or DAST tool which takes hours or days to complete may be next to worthless.\n\n### Choosing applications\n\nIt's important when comparing tools that they are run against applications that have been developed in-house or closely mirror what your development teams are creating. A SAST tool that's excellent at finding Java security issues will not necessarily translate to one that's great at finding actionable issues in a NodeJS application. If your organization has a diverse set of languages you should run each tool against applications that reflect those environments.\n\n### Real world flaws\n\nApplications like [WebGoat](https://owasp.org/www-project-webgoat/) or [OWASP's Java Benchmark](https://github.com/OWASP/Benchmark) do not represent real world applications. Most vulnerabilities have been purposely injected into very simple data and code flows. The majority of flaws in WebGoat exist in the same Java class where the source of user input is defined. In reality, a large number of security issues will be hidden in nested layers of abstraction or multiple function or method calls. You'll want to ensure your test suite of applications includes real world applications and the tools can traverse these complex flows to find potential flaws.\n\nEven if a tool is excellent at finding language specific issues, it may or may not support the development team's choice in frameworks. Most SAST tools need to add support for specific frameworks. If the tool supports the language of choice but does not support the particular framework, there will be a higher chance for poor results.\n\n### Are results consistent and useful?\n\nAnalysis of applications should be run multiple times – DAST tools in particular have a difficult time with consistency due to the dynamic nature of testing live applications. Ensure each tool is run the same number of times to gather enough data points to make a clear decision.\n\nSecurity tools tend to over report issues and this can end up causing alert fatigue and reduce the value of the tool. It's important to tune the tools to reduce the number of non-actionable results. Just pay attention to how much time is required to maintain this tuning effort and be sure to include this in the final decision. \n\n## WebGoat as a benchmarking target\n\nWebGoat is a known vulnerable application that was built to help developers and people interested in web application security understand various flaws and risks to applications. Over the years it has seen numerous contributions to the lessons and became a de facto standard for learning about web application security.\n\nDue to the increase in popularity of this project, customers have chosen to rely on using it as a benchmark when assessing the capabilities of various SAST and DAST tools. The purpose of this post is to outline some potential pitfalls when using WebGoat as a target for analysis.\n\nWebGoat was built as a learning aid, not for benchmarking purposes. Certain methods chosen to demonstrate vulnerability do not actually result in real flaws being implemented in WebGoat. To a human attempting to exercise certain flaws, it may look like they've succeeded in finding an issue. In reality, the WebGoat application just makes it appear like they've discovered a flaw. \n\nThis can cause confusion when an automated tool is run against WebGoat. To a human, they expect the tool to find a flaw at a particular location. Since a number of lessons include synthetic flaws, the tools will fail to report them. \n\nFor this post, GitLab's Vulnerability Research Team used the v8.1.0 release of WebGoat, however a number of the issues identified will be applicable to older versions as well.\n\n## WebGoat's architecture\n\nThe focus of this post is on WebGoat and in particular as a target for benchmarking. The WebGoat repository has grown in size over the years and now includes multiple sub-projects:\n\n- webgoat-container - This project holds the static content as well as the [Spring Boot](https://spring.io/projects/spring-boot) Framework's lesson scaffolding. The frontend is built using [Backbone.js](https://backbonejs.org/).\n- webgoat-images - Contains a Vagrant file for training purposes.\n- webgoat-integration-tests - Contains test files\n- webgoat-lessons - Contains the source and resources for the lessons.\n- webgoat-server - The primary entry point for the SpringBootApplication.\n- webwolf - An entirely separate project for assisting users in exploiting various lessons.\n\nWhen building the WebGoat target application, the webgoat-container, webgoat-server and webgoat-lessons are all included into a single SpringBoot server packaged as a JAR artifact. \n\nFor the most part, lessons that contain legitimate flaws exist in a single class file. Certain SAST tools which implement extensive taint tracking capabilities may not be fully exercised. The end result being, SAST tools with limited capabilities will find just as many flaws as more advanced tools that can handle intra-procedural taint flows. It would be better to benchmark these tools against relatively complex applications versus comparing them with WebGoat's simplistic flaws.\n\n## Analysis methodology \n\nOnly the webgoat-container, webgoat-server and webgoat-lessons projects are included in our analysis of WebGoat for SAST/DAST tools. The other projects webgoat-images, webgoat-integration-tests and webwolf are not included. \n\nOur analysis follows the lessons and attempts to identify in the source where the flaws, if any, exist. If a lesson is a description or does not process user input, it is not included in the flaw category listing below. \n\nEach lesson is broken down to cover the following:\n\n- Flaw category\n- Lesson with title\n- Link as viewable from a browser\n- Source\n- Lesson's purpose \n- Relevant source code\n- Whether DAST/SAST would be able to find and the probability level\n    - Possible: A DAST/SAST tool should be able to find the flaw with little to no configuration changes or custom settings\n    - Probable: A DAST/SAST tool could find the flaw with some configuration changes or custom settings\n    - Improbable: A DAST/SAST tool most likely will not be able to find the flaw, due to either hardcoded values or other conditions that are not realistic\n    - Impossible: A DAST/SAST tool would not be able to find any flaw due to it being completely synthetic or other unrealistic circumstances\n- Reasoning why it can or can't be found with a SAST or DAST tool\n- Example attack where applicable.\n\nIn many places there are additional flaws that exist in the code but are not part of the lesson; we will highlight some of these but not exhaustively.\n\nPlease note this post contains spoilers, if you are new to WebGoat as a learning tool and wish to use it for study, it is recommended to do that first before reading our analysis.\n\n## (A1) Injection\n\n---\n\n\u003Cdetails>\n\u003Csummary markdown=\"span\">Findings\u003C/summary>\n\n### (A1) Injection > SQL Injection (intro) > What is SQL?\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/1\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson2.java\n\n**Lesson:**\n\nThis lesson is for practicing raw SQL queries – it allows anyone to run full SQL queries without parameterized statements.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson2.java#L55-65 \n\npublic AttackResult completed(@RequestParam String query) {\n    return injectableQuery(query);\n}\n\nprotected AttackResult injectableQuery(String query) {\n    try (var connection = dataSource.getConnection()) {\n        Statement statement = connection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY);\n        ResultSet results = statement.executeQuery(query);\n        StringBuffer output = new StringBuffer();\n\n        results.first();\n        ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nIt should be relatively straightforward for a SAST tool to identify that the query comes from a known source (line 55) and is used in a statement's executeQuery method.\n\n**DAST Reasoning:**\n\nWhile it should not be difficult for a DAST tool to find such a vulnerability, most DAST tools are not built with attack strings that attempt direct SQL queries. DAST SQL Injection tests are almost always trying to inject into the middle of an already existing SQL query. There is a good chance most DAST tools will not find the `/SqlInjection/attack2` endpoint's query parameter vulnerable.\n\n**Example Attack:** \n- `query=(SELECT repeat('a',50000000) from information_schema.tables)` will take ~3 seconds to complete.\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Data Manipulation Language (DML)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/2\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson3.java\n\n**Lesson:**\n\nThis lesson is for practicing raw SQL queries – it allows anyone to run UPDATE/INSERT SQL queries without parameterized statements.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson3.java#L56-65\n\npublic AttackResult completed(@RequestParam String query) {\n    return injectableQuery(query);\n}\n\nprotected AttackResult injectableQuery(String query) {\n    try (Connection connection = dataSource.getConnection()) {\n        try (Statement statement = connection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY)) {\n            Statement checkStatement = connection.createStatement(TYPE_SCROLL_INSENSITIVE,\n                    CONCUR_READ_ONLY);\n            statement.executeUpdate(query);\n        ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nIt should be relatively straightforward for a SAST tool to identify that the query comes from a known source on line 56 and is used in a statement's executeUpdate method.\n\n**DAST Reasoning:**\n\nWhile it should not be difficult for a DAST tool to find such a vulnerability, most DAST tools are not built with attack strings that attempt direct SQL queries. DAST SQL Injection tests are almost always trying to inject into the middle of an already existing SQL query. There is a good chance most DAST tools will not find the `/SqlInjection/attack3` endpoint's query parameter vulnerable.\n\n**Example Attack:** \n- `query=insert into employees (first_name) (SELECT repeat('a', 50000000) from employees)` will take ~2 seconds to error out (where `repeat('a', 5)` takes 200ms).\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Data Definition Language (DDL)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/3\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson4.java\n\n**Lesson:**\n\nThis lesson is for practicing raw SQL queries – it allows anyone to create tables via SQL queries without parameterized statements.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson4.java#L52-59\n\npublic AttackResult completed(@RequestParam String query) {\n    return injectableQuery(query);\n}\n\nprotected AttackResult injectableQuery(String query) {\n    try (Connection connection = dataSource.getConnection()) {\n        try (Statement statement = connection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY)) {\n            statement.executeUpdate(query);\n        ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nIt should be relatively straightforward for a SAST tool to identify that the query comes from a known source on line 53 and is used in a statement's executeUpdate method.\n\n**DAST Reasoning:**\n\nWhile it should not be difficult for a DAST tool to find such a vulnerability, most DAST tools are not built with attack strings that attempt direct SQL injection queries. DAST SQL Injection tests are almost always trying to inject into the middle of an already existing SQL query. There is a good chance most DAST tools will not find the /SqlInjection/attack4 endpoint's query parameter vulnerable to sql injection.\n\n**Example Attack:** \n- `query=insert into employees (first_name) (SELECT repeat('a', 50000000) from information_schema.tables)` will take ~2 seconds to error out (where `repeat('a', 5)` takes 200ms).\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Data Control Language (DCL)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/4\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5.java\n\n**Lesson:**\n\nThis lesson is for practicing raw SQL queries – it pretends to allow users to run grant/alter on tables via SQL queries. However, it is not calling any SQL functionality.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5.java#46-51\n\nString regex = \"(?i)^(grant alter table to [']?unauthorizedUser[']?)(?:[;]?)$\";\nStringBuffer output = new StringBuffer();\n\n// user completes lesson if the query is correct\nif (query.matches(regex)) {\n    output.append(\"\u003Cspan class='feedback-positive'>\" + query + \"\u003C/span>\");\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThis is a synthetic vulnerability that does not actually call any database functionality.\n\n**DAST Reasoning:**\n\nThis is a synthetic vulnerability that does not actually call any database functionality.\n\n---\n\n### (A1) Injection > SQL Injection (intro) > What is SQL injection?\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/5\n\n**Source:** \n- Static content only\n\n**Lesson:**\n\nThis lesson is for practicing SQL Injection query string generation, but does not call any server side functionality.\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThis lesson does not actually call any server side functionality.\n\n**DAST Reasoning:**\n\nThis lesson does not actually call any server side functionality.\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Try It! String SQL injection\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/8\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java\n\n**Lesson:**\n\nThis lesson provides a form for testing out SQL Injection against database functionality with the goal of returning all results from a table.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java#L53-L54\n\npublic AttackResult completed(@RequestParam String account, @RequestParam String operator, @RequestParam String injection) {\n    return injectableQuery(account + \" \" + operator + \" \" + injection);\n...\n\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java#L57-62\n\nprotected AttackResult injectableQuery(String accountName) {\n    String query = \"\";\n    try (Connection connection = dataSource.getConnection()) {\n        query = \"SELECT * FROM user_data WHERE first_name = 'John' and last_name = '\" + accountName + \"'\";\n        try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) {\n            ResultSet results = statement.executeQuery(query);\n    ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe `executeQuery` method is called from a dynamically concatenated string containing user input.\n\n**DAST Reasoning:**\n\nThe three inputs `account`, `operator` and `injection` are all concatenated together and each parameter could technically be used as an attack vector. In this case a DAST tool will most likely suffer from over reporting duplicate flaws, as the three input vectors all end up being used in the same resultant query string.\n\n**Example Attack(s):**\n- `account=Smith' or 1%3d1--&operator=&injection=`\n- `account=&operator=Smith' or 1%3d1--&injection=`\n- `account=&operator=&injection=Smith' or 1%3d1--`\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Try It! Numeric SQL injection\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/9\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5b.java\n\n**Lesson:**\n\nThis lesson provides a form for testing out SQL Injection when the column type is restricted to numerical values. The goal is to return all results from a table. While the resultant query does make use of prepared statements, it incorrectly concatenates user input for the `userid` column.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5b.java#L56-71\n\nString queryString = \"SELECT * From user_data WHERE Login_Count = ? and userid= \" + accountName;\ntry (Connection connection = dataSource.getConnection()) {\n        PreparedStatement query = connection.prepareStatement(queryString, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);\n        ...\n        ResultSet results = query.executeQuery();\n        ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nWhile the query is correctly used in a PreparedStatement only the `Login_Count` is parameterized, the `accountName` is still dynamically assigned, leading to SQL Injection.\n\n**DAST Reasoning:**\n\nDAST tools should attempt to inject into both parameters, and provided the correct attack string is used, should successfully identify that the `userid` parameter is vulnerable to SQL Injection.\n\n**Example Attack:**\n- `login_count=1&userid=1+or+1%3D1`\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Compromising confidentiality with String SQL injection\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/10\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java\n\n**Lesson:**\n\nThis lesson provides a form for testing out SQL Injection where the goal is to return all results from a table. \n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java#L59-65\n\nString query = \"SELECT * FROM employees WHERE last_name = '\" + name + \"' AND auth_tan = '\" + auth_tan + \"'\";\n\ntry (Connection connection = dataSource.getConnection()) {\n    try {\n        Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);\n        log(connection, query);\n        ResultSet results = statement.executeQuery(query);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe `name` and `auth_tan` user supplied values are dynamically assigned to a query string, leading to SQL Injection.\n\n**DAST Reasoning:**\n\nDAST tools should attempt to inject into both parameters which are valid attack vectors leading to exploitable SQL Injection.\n\n**Example Attack:**\n- `name=&auth_tan=1'+or+1%3D1--`\n- `name=1'+or+1%3D1--&auth_tan=`\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Compromising Integrity with Query chaining\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/11\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java\n\n**Lesson:**\n\nThis lesson provides a form for testing out SQL Injection where the goal is to modify a users salary. \n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java#L61-66\n\nString query = \"SELECT * FROM employees WHERE last_name = '\" + name + \"' AND auth_tan = '\" + auth_tan + \"'\";\ntry (Connection connection = dataSource.getConnection()) {\n    try {\n        Statement statement = connection.createStatement(TYPE_SCROLL_SENSITIVE, CONCUR_UPDATABLE);\n        SqlInjectionLesson8.log(connection, query);\n        ResultSet results = statement.executeQuery(query);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe `name` and `auth_tan` user supplied values are dynamically assigned to a query string, leading to SQL Injection.\n\n**DAST Reasoning:**\n\nDAST tools should attempt to inject into both parameters `name` and `auth_tan` which are valid attack vectors leading to exploitable SQL Injection.\n\n**Example Attack:**\n- `name=&auth_tan=1'+or+1%3D1--`\n- `name=1'+or+1%3D1--&auth_tan=`\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Compromising Availability\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/12\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson10.java\n\n**Lesson:**\n\nThis lesson provides a form for testing SQL Injection where the goal is remove evidence of attacks.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson10.java#L58-63\n\nString query = \"SELECT * FROM access_log WHERE action LIKE '%\" + action + \"%'\";\n\n        try (Connection connection = dataSource.getConnection()) {\n            try {\n                Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);\n                ResultSet results = statement.executeQuery(query);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe action user supplied values are dynamically concatenated to a query string, leading to SQL Injection.\n\n**DAST Reasoning:**\n\nDAST tools should attempt to inject into the action_string parameter, leading to exploitable SQL Injection.\n\n**Example Attack:**\n- `action_string=1'+or+1%3D1--`\n\n---\n\n### (A1) Injection > SQL Injection (advanced) > Try It! Pulling data from other tables\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjectionAdvanced.lesson/2\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java\n\n**Lesson:**\n\nThis lesson is to demonstrate how to extract data from tables other than the one the query was defined to execute against.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java#L60-67\n\nquery = \"SELECT * FROM user_data WHERE last_name = '\" + accountName + \"'\";\n//Check if Union is used\nif (!accountName.matches(\"(?i)(^[^-/*;)]*)(\\\\s*)UNION(.*$)\")) {\n    usedUnion = false;\n}\ntry (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,\n        ResultSet.CONCUR_READ_ONLY)) {\n    ResultSet results = statement.executeQuery(query);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe `accountName` user supplied value is dynamically assigned to a query string, leading to SQL Injection.\n\n**DAST Reasoning:**\n\nDAST tools should attempt to inject into the `userid_6a` parameter, leading to exploitable SQL Injection.\n\n**Example Attack:**\n- `userid_6a=1'+or+1%3D1--`\n\n---\n\n### (A1) Injection > SQL Injection (advanced) > (no title)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjectionAdvanced.lesson/4\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionChallenge.java\n\n**Lesson:**\n\nThis lesson is a challenge to attempt to extract data from the database that leads to the attacker being able to login as a different user by executing SQL Injection attacks.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionChallenge.java#L63-65\n\nString checkUserQuery = \"select userid from sql_challenge_users where userid = '\" + username_reg + \"'\";\nStatement statement = connection.createStatement();\nResultSet resultSet = statement.executeQuery(checkUserQuery);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe `username_reg` user supplied value is dynamically assigned to a query string, leading to SQL Injection.\n\n**DAST Reasoning:**\n\nDAST tools should attempt to inject into the `username_reg` parameter with a timing or blind sql injection based attack string, leading to exploitable SQL Injection.\n\n**Example Attack:**\n- `username_reg='%2b(select+repeat('a', 50000000)+from+information_schema.tables)%2b'&email_reg=asdf&password_reg=asdf&confirm_password_reg=asdf`\n\n---\n\n### (A1) Injection > SQL Injection (mitigation) > Input validation alone is not enough!! (Lesson 9)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjectionMitigations.lesson/8\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/SqlOnlyInputValidation.java\n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java\n\n**Lesson:** \n\nThis lesson demonstrates filtering of user input not being sufficient for protecting against SQL injection attacks. This particular case looks to see if the input string contains a space and returns an error if it does. \n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/SqlOnlyInputValidation.java#L48-L52\n\npublic AttackResult attack(@RequestParam(\"userid_sql_only_input_validation\") String userId) {\nif (userId.contains(\" \")) {\n    return failed(this).feedback(\"SqlOnlyInputValidation-failed\").build();\n}\nAttackResult attackResult = lesson6a.injectableQuery(userId);\n...\n\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java#56-67\n\npublic AttackResult injectableQuery(String accountName) {\n    String query = \"\";\n    try (Connection connection = dataSource.getConnection()) {\n        boolean usedUnion = true;\n        query = \"SELECT * FROM user_data WHERE last_name = '\" + accountName + \"'\";\n        //Check if Union is used\n        if (!accountName.matches(\"(?i)(^[^-/*;)]*)(\\\\s*)UNION(.*$)\")) {\n            usedUnion = false;\n        }\n        try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,\n                ResultSet.CONCUR_READ_ONLY)) {\n            ResultSet results = statement.executeQuery(query);\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nSAST tools would most likely be flag this as a flaw existing under the `webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java` class. The `SqlOnlyInputValidation.java` file is calling the same method, but the vulnerability exists in `SqlInjectionLesson6a.java`. Where the flaw is reported depends on how the SAST tool was designed. For example if the SAST tool is capable of tracking taint across intra-procedural calls it may flag the call to `lesson6a.injectableQuery(userId)` on line 52. If the SAST tool was designed to only parse the Abstract Syntax Tree (AST) it may only report the flaw in `SqlInjectionLesson6a.java` on line 67.\n\n**DAST Reasoning:**\n\nAny input filtering done on potential attack vectors makes it more difficult for DAST tools to identify exploitable issues. There is a chance some DAST tools would not find the `userid_sql_only_input_validation` parameter vulnerable to SQL Injection.\n\n**Example Attack:**\n- `userid_sql_only_input_validation='%2b(SELECT/**/repeat(char(60),50000000)from/**/information_schema.tables)%2b'`\n\n---\n\n### (A1) Injection > SQL Injection (mitigation) > Input validation alone is not enough!! (Lesson 10)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjectionMitigations.lesson/9\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/SqlOnlyInputValidationOnKeywords.java\n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java\n\n**Lesson:** \n\nThis lesson demonstrates filtering of user input not being sufficient for protecting against SQL Injection attacks. This particular case looks to see if the input string contains `SELECT` or `FROM` keywords and replaces them with empty strings, it additionally checks if the input contains a space and returns an error if it does. \n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/SqlOnlyInputValidationOnKeywords.java#L48-L53\n\npublic AttackResult attack(@RequestParam(\"userid_sql_only_input_validation_on_keywords\") String userId) {\n    userId = userId.toUpperCase().replace(\"FROM\", \"\").replace(\"SELECT\", \"\");\n    if (userId.contains(\" \")) {\n        return failed(this).feedback(\"SqlOnlyInputValidationOnKeywords-failed\").build();\n    }\n    AttackResult attackResult = lesson6a.injectableQuery(userId);\n    ...\n\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java#56-67\n\npublic AttackResult injectableQuery(String accountName) {\n    String query = \"\";\n    try (Connection connection = dataSource.getConnection()) {\n        boolean usedUnion = true;\n        query = \"SELECT * FROM user_data WHERE last_name = '\" + accountName + \"'\";\n        //Check if Union is used\n        if (!accountName.matches(\"(?i)(^[^-/*;)]*)(\\\\s*)UNION(.*$)\")) {\n            usedUnion = false;\n        }\n        try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,\n                ResultSet.CONCUR_READ_ONLY)) {\n            ResultSet results = statement.executeQuery(query);\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nSAST tools would most likely flag this as a flaw existing under the `webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java` class. The `SqlOnlyInputValidationOnKeywords.java` file is calling the same method, but the vulnerability exists in `SqlInjectionLesson6a.java`. Where the flaw is reported depends on how the SAST tool was designed. For example if the SAST tool is capable of tracking taint across intra-procedural calls it may flag the call to `lesson6a.injectableQuery(userId)` on line 53. If the SAST tool was designed to only parse the Abstract Syntax Tree (AST) it may only report the flaw in `SqlInjectionLesson6a.java` on line 67.\n\n**DAST Reasoning:**\n\nAny input filtering done on potential attack vectors makes it much more difficult for DAST tools to identify exploitable issues. There is a good chance most DAST tools would not find the `userid_sql_only_input_validation_on_keywords` parameter vulnerable.\n\n**Example Attack:**\n- `userid_sql_only_input_validation_on_keywords='%2b(SELselectECT/**/repeat(char(60),50000000)FRfromOM/**/information_schema.tables)%2b'`\n\n---\n\n### (A1) Injection > SQL Injection (mitigation) > (no title) (Lesson 12)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjectionMitigations.lesson/11\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/Servers.java\n\n**Lesson:** \n\nThis lesson includes a SQL Injection vulnerability in a column field of a SQL query, which can not be specified by a parameter in a parameterized query. As such, it uses a dynamically generated query string with user input for specifying the column name.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/Servers.java#L69-L74\n\n public List\u003CServer> sort(@RequestParam String column) throws Exception {\n    List\u003CServer> servers = new ArrayList\u003C>();\n\n    try (Connection connection = dataSource.getConnection();\n            PreparedStatement preparedStatement = connection.prepareStatement(\"select id, hostname, ip, mac, status, description from servers  where status \u003C> 'out of order' order by \" + column)) {\n        ResultSet rs = preparedStatement.executeQuery();\n        ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nWhile the SQL query is a prepared statement, the column field can not be specified as a `parameter` in a parameterized query. A SAST tool should identify that the query string is concatenated with user input on line 73.\n\n**DAST Reasoning:**\n\nA DAST tool, if it is able to find the `/WebGoat/SqlInjectionMitigations/servers` endpoint, should be able to exploit the column based SQL injection issue.\n\n**Example Attack:**\n- `/WebGoat/SqlInjectionMitigations/servers?column=(select+repeat('a',50000000)+from+information_schema.tables)`\n\n---\n\n### (A1) Injection > Path traversal > Path traversal while uploading files\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/PathTraversal.lesson/1\n\n**Source:** \n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUpload.java\n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java\n\n**Lesson:** \n\nThis lesson's goal is to upload a file to overwrite a system file. It uses a common insecure pattern of using user input to generate a file path and supplying the file contents to create a new file.\n\n```\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUpload.java#L31-L32\n\npublic AttackResult uploadFileHandler(@RequestParam(\"uploadedFile\") MultipartFile file, @RequestParam(value = \"fullName\", required = false) String fullName) {\n    return super.execute(file, fullName);\n}\n\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java#L41-43\n\nvar uploadedFile = new File(uploadDirectory, fullName);\nuploadedFile.createNewFile();\nFileCopyUtils.copy(file.getBytes(), uploadedFile);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool should be able to identify that the `new File` call's second parameter is tainted from the `uploadFileHandler` `fullName` parameter. \n\n**DAST Reasoning:**\n\nDAST tools have difficulties identifying insecure file upload path traversal attacks. As the attack is a two-step process, the tool most likely will not be able to identify where the uploaded file ultimately resides on the file system. The first step is uploading the file, while the second is for identifying where the uploaded file exists and attempting to access it. \n\nIn this lesson there is no way to retrieve a non `.jpg` suffixed file. The `var catPicture = new File(catPicturesDirectory, (id == null ? RandomUtils.nextInt(1, 11) : id) + \".jpg\");` limits what can be retrieved. Additionally, the only way to attempt to access uploaded files is through a completely unrelated web page. A DAST tool would not understand the relationship between the upload endpoint and the retrieval endpoint. \n\nWhile it is possible to attempt null byte attacks if the string contains `path-traversal-secret.jpg` the `FileCopyUtils` method uses `java.io.File.toPath` which disallows null bytes. For example attempting the attack: `/WebGoat/PathTraversal/random-picture?id=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd%00path-traversal-secret.jpg%00` will fail with the error: `java.nio.file.InvalidPathException: Nul character not allowed`.\n\nGiven the above, while it is possible to upload arbitrary files, it is not possible to access them, hence a DAST tool would be unable to identify this as a flaw. A human of course could use various techniques to attempt to overwrite system files, but a DAST tool is not built for such activities. \n\n---\n\n### (A1) Injection > Path traversal > Path traversal while uploading files (lesson 3)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/PathTraversal.lesson/2\n\n**Source:** \n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadFix.java\n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java\n\n**Lesson:** \n\nThis lesson's goal is to upload a file to overwrite a system file. It uses a common insecure pattern of using user input to generate a file path and supply the file contents to create a new file. In this lesson filtering is done on user input, replacing `../` with an empty string.\n\n```\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadFix.java#L24-L28\n\n    public AttackResult uploadFileHandler(\n            @RequestParam(\"uploadedFileFix\") MultipartFile file,\n            @RequestParam(value = \"fullNameFix\", required = false) String fullName) {\n        return super.execute(file, fullName != null ? fullName.replace(\"../\", \"\") : \"\");\n    }\n\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java#L41-43\n\nvar uploadedFile = new File(uploadDirectory, fullName);\nuploadedFile.createNewFile();\nFileCopyUtils.copy(file.getBytes(), uploadedFile);\n```\n\n**Can SAST Find?** \n- Yes\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool should be able to identify that the `new File` call's second parameter is tainted from the `uploadFileHandler` `fullName` parameter. \n\n**DAST Reasoning:**\n\nDAST tools have difficulties identifying insecure file upload path traversal attacks. As the attack is a two-step process, the tool most likely will not be able to identify where the uploaded file ultimately resides on the file system. The first step is uploading the file, while the second is for identifying where the uploaded file exists and attempting to access it. \n\nIn this lesson there is no way to retrieve a non `.jpg` suffixed file. The `var catPicture = new File(catPicturesDirectory, (id == null ? RandomUtils.nextInt(1, 11) : id) + \".jpg\");` limits what can be retrieved. Additionally, the only way to attempt to access uploaded files is through a completely unrelated web page. A DAST tool would not understand the relationship between the upload endpoint and the retrieval endpoint. \n\nWhile it is possible to attempt null byte attacks if the string contains `path-traversal-secret.jpg` the `FileCopyUtils` method uses `java.io.File.toPath` which disallows null bytes. For example attempting the attack: `/WebGoat/PathTraversal/random-picture?id=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd%00path-traversal-secret.jpg%00` will fail with the error: `java.nio.file.InvalidPathException: Nul character not allowed`.\n\nGiven the above, while it is possible to upload arbitrary files, it is not possible to access them, hence a DAST tool would be unable to identify this as a flaw. A human of course could use various techniques to attempt to overwrite system files, but a DAST tool is not built for such activities. \n\n---\n\n### (A1) Injection > Path traversal > Path traversal while uploading files (lesson 4)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/PathTraversal.lesson/3\n\n**Source:** \n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadRemoveUserInput.java\n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java\n\n**Lesson:** \n\nThis lessons goal is to upload a file to overwrite a system file. It uses a common insecure pattern of using user input to generate a file path and supplying the file contents to create a new file. In this lesson the filename is taken from the `MultipartFile.getOriginalFilename()` which is still user input, as it is possible to modify the filename part of a Multipart upload:\n```\nContent-Disposition: form-data; name=\"uploadedFileRemoveUserInput\"; filename=\"../../test.html\"\n```\n\n```\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadRemoveUserInput.java#L24-L28\n\npublic AttackResult uploadFileHandler(\n        @RequestParam(\"uploadedFileFix\") MultipartFile file,\n        @RequestParam(value = \"fullNameFix\", required = false) String fullName) {\n    return super.execute(file, fullName != null ? fullName.replace(\"../\", \"\") : \"\");\n}\n\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java#L41-43\n\nvar uploadedFile = new File(uploadDirectory, fullName);\nuploadedFile.createNewFile();\nFileCopyUtils.copy(file.getBytes(), uploadedFile);\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool should be able to identify that the `new File` call's second parameter is tainted from the uploadFileHandler fullName parameter, which originates from the `MultipartFile.getOriginalFilename()` from `ProfileUploadRemoveUserInput.java`. \n\n**DAST Reasoning:**\n\nDAST tools have difficulties identifying insecure file upload path traversal attacks. As the attack is a two-step process, the tool most likely will not be able to identify where the uploaded file ultimately resides on the file system. The first step is uploading the file, while the second is for identifying where the uploaded file exists and attempting to access it. \n\nIn this lesson there is no way to retrieve a non `.jpg` suffixed file. The `var catPicture = new File(catPicturesDirectory, (id == null ? RandomUtils.nextInt(1, 11) : id) + \".jpg\");` limits what can be retrieved. Additionally, the only way to attempt to access uploaded files is through a completely unrelated web page. A DAST tool would not understand the relationship between the upload endpoint and the retrieval endpoint. \n\nWhile it is possible to attempt null byte attacks if the string contains `path-traversal-secret.jpg` the `FileCopyUtils` method uses `java.io.File.toPath` which disallows null bytes. For example attempting the attack: `/WebGoat/PathTraversal/random-picture?id=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd%00path-traversal-secret.jpg%00` will fail with the error: `java.nio.file.InvalidPathException: Nul character not allowed`.\n\nGiven the above, while it is possible to upload arbitrary files, it is not possible to access them, hence a DAST tool would be unable to identify this as a flaw. A human of course could use various techniques to attempt to overwrite system files, but a DAST tool is not built for such activities. \n\n---\n\n### (A1) Injection > Path traversal > Retrieving other files with a path traversal\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/PathTraversal.lesson/4\n\n**Source:** \n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadRemoveUserInput.java\n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java\n\n**Lesson:** \n\nThis lesson's goal is to retrieve a particular file from the file system. It uses a rather uncommon method of validating the entire query string via `request.getQueryString()` to see if it contains `..` or `/`. While technically the call to `new File` is vulnerable to path traversal attacks, it is not actually an exploitable arbitrary local file inclusion vulnerability. It can only return files which are jpg's, and null-byte attacks are not possible due to calls to FileCopyUtils which validates that the path does not contain null bytes.\n\n```\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadRemoveUserInput.java#L75-94\n\npublic ResponseEntity\u003C?> getProfilePicture(HttpServletRequest request) {\n    var queryParams = request.getQueryString();\n    if (queryParams != null && (queryParams.contains(\"..\") || queryParams.contains(\"/\"))) {\n        return ResponseEntity.badRequest().body(\"Illegal characters are not allowed in the query params\");\n    }\n    try {\n        var id = request.getParameter(\"id\");\n        var catPicture = new File(catPicturesDirectory, (id == null ? RandomUtils.nextInt(1, 11) : id) + \".jpg\");\n\n        if (catPicture.getName().toLowerCase().contains(\"path-traversal-secret.jpg\")) {\n            return ResponseEntity.ok()\n                    .contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE))\n                    .body(FileCopyUtils.copyToByteArray(catPicture));\n        }\n        if (catPicture.exists()) {\n            return ResponseEntity.ok()\n                    .contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE))\n                    .location(new URI(\"/PathTraversal/random-picture?id=\" + catPicture.getName()))\n                    .body(Base64.getEncoder().encode(FileCopyUtils.copyToByteArray(catPicture)));\n        }\n    ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool should be able to identify that the `new File` call's second parameter is tainted. However, the File object is only used in calls to either `getName()` or `FileCopyUtils` which validates that the filename does not contain null bytes. So while it's technically vulnerable, it is not exploitable for arbitrary file access.\n\n**DAST Reasoning:**\n\nDAST tools have difficulties identifying insecure file upload path traversal attacks. As the attack is a two-step process, the tool most likely will not be able to identify where the uploaded file ultimately resides on the file system. The first step is uploading the file, while the second is for identifying where the uploaded file exists and attempting to access it. \n\nIn this lesson there is no way to retrieve a non `.jpg` suffixed file. The `var catPicture = new File(catPicturesDirectory, (id == null ? RandomUtils.nextInt(1, 11) : id) + \".jpg\");` limits what can be retrieved. \n\nWhile it is possible to attempt null byte attacks if the string contains `path-traversal-secret.jpg` the `FileCopyUtils` method uses `java.io.File.toPath`, which disallows null bytes. For example attempting the attack: `/WebGoat/PathTraversal/random-picture?id=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd%00path-traversal-secret.jpg%00` will fail with the error: `java.nio.file.InvalidPathException: Nul character not allowed`.\n\nGiven the above, a DAST tool would most likely be unable to identify this as a flaw. Most DAST tools use hardcoded filepaths such as `/etc/passwd` or `c:/windows/win.ini` when attempting to access arbitrary files.\n\n---\n\n\u003C/details>\n\n## (A2) Broken Authentication\n\n---\n\u003Cdetails>\n\u003Csummary markdown=\"span\">Findings\u003C/summary>\n\n### (A2) Broken Authentication > Authentication Bypasses > 2FA Password Reset\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/AuthBypass.lesson/1\n\n**Source:** \n- webgoat-lessons/auth-bypass/src/main/java/org/owasp/webgoat/auth_bypass/AuthBypass.java\n- webgoat-lessons/auth-bypass/src/main/java/org/owasp/webgoat/auth_bypass/AccountVerificationHelper.java\n\n**Lesson:**\nThis lesson is for bypassing 2FA password reset by removing POST parameters.\n\n```\nwebgoat-lessons/auth-bypass/src/main/java/org/owasp/webgoat/auth_bypass/AccountVerificationHelper.java#L69-86\n\npublic boolean verifyAccount(Integer userId, HashMap\u003CString, String> submittedQuestions) {\n    //short circuit if no questions are submitted\n    if (submittedQuestions.entrySet().size() != secQuestionStore.get(verifyUserId).size()) {\n        return false;\n    }\n\n    if (submittedQuestions.containsKey(\"secQuestion0\") && !submittedQuestions.get(\"secQuestion0\").equals(secQuestionStore.get(verifyUserId).get(\"secQuestion0\"))) {\n        return false;\n    }\n\n    if (submittedQuestions.containsKey(\"secQuestion1\") && !submittedQuestions.get(\"secQuestion1\").equals(secQuestionStore.get(verifyUserId).get(\"secQuestion1\"))) {\n        return false;\n    }\n\n    // else\n    return true;\n\n}\n\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThis lesson is a hypothetical case where it 'fails open.' In other words by not submitting the validated parameters, the checks are never executed and the verifyAccount method returns true.\n\nSAST tools can not determine 'reasoning' of a methods purpose. This method is not wired into any obvious framework or method that would hint for the SAST tool to know it's for authentication purposes.\n\n**DAST Reasoning:**\n\nMuch like above, this is a hypothetical case. There is no context around the lesson that could assist a DAST tool in knowing it's testing a password reset function. \n\n---\n\n### (A2) Broken Authentication > JWT tokens > JWT signing\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/JWT.lesson/3\n\n**Source:** \n- webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTVotesEndpoint.java\n\n**Lesson:**\nThis lesson is for modifying JWT claims to impersonate or elevate privileges. It is a multi-step process. First to get a JWT value you need to use the fake 'login' `/JWT/votings/login?user=` endpoint, which sets it in an HTTP cookie. Next, the goal is to bypass authorization checks and reset the votes by issuing a POST request to the `/JWT/votings` with a modified token. The modifications require setting the alg to None and changing the admin parameter to true. \n\n```\nwebgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTVotesEndpoint.java#L163-165\n\nJwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken);\nClaims claims = (Claims) jwt.getBody();\nboolean isAdmin = Boolean.valueOf((String) claims.get(\"admin\"));\n```\n\n**Can SAST Find?** \n- Probable\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nA SAST tool would need to be used in conjunction with a dependency scanning solution that looked for vulnerable libraries. This vulnerability exists due to the dependency on `io.jsonwebtoken` version 0.7.0. Using static analysis alone would be difficult for a SAST tool to identify that the `setSigningKey` would be ignored if the algorithm was set to `None`. Additionally, authorization issues are usually difficult for SAST to identify in terms of simple conditional statements.  \n\n**DAST Reasoning:**\n\nVulnerabilities that require multiple steps are usually difficult for DAST tools to identify. If the JWT was used in an authentication endpoint, it may be possible for a DAST tool to identify a vulnerability. It would attempt to modify the JWT and determine if it was able to successfully login. A DAST tool would be unable to determine that the secondary `/JWT/votings` endpoint suffers from verification flaw.\n\n---\n\n### (A2) Broken Authentication > JWT tokens > JWT cracking\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/JWT.lesson/4\n\n**Source:** \n- webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTSecretKeyEndpoint.java\n\n**Lesson:**\nThis lesson is for cracking a JWT value which uses an insecure cryptographic algorithm. `HS256` JWT values include a signature which can generated and compared by brute forcing tools. \n\n```\nwebgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTSecretKeyEndpoint.java#L57-68\n\npublic String getSecretToken() {\n    return Jwts.builder()\n            .setIssuer(\"WebGoat Token Builder\")\n            .setAudience(\"webgoat.org\")\n            .setIssuedAt(Calendar.getInstance().getTime())\n            .setExpiration(Date.from(Instant.now().plusSeconds(60)))\n            .setSubject(\"tom@webgoat.org\")\n            .claim(\"username\", \"Tom\")\n            .claim(\"Email\", \"tom@webgoat.org\")\n            .claim(\"Role\", new String[]{\"Manager\", \"Project Administrator\"})\n            .signWith(SignatureAlgorithm.HS256, JWT_SECRET).compact();\n    }\n\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nA SAST tool that included this particular `Jwts` library signatures would be able to identify that the `signWith` method is a potential sink and the first parameter determines if a vulnerability exists, depending on the argument's value.\n\n**DAST Reasoning:**\n\nA DAST tool with builtin JWT cracking functionality would be able to extract JWT values encountered during crawling and attempt to brute force the signature. Note the ability to actually determine vulnerability would be dependent on the weakness of the JWT key used for signing.\n\n---\n\n### (A2) Broken Authentication > JWT tokens > Refreshing a token (incomplete lesson)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/JWT.lesson/6\n\n**Source:** \n- webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTRefreshEndpoint.java\n\n**Lesson:**\n\nThis lesson as of v8.1.0 is still incomplete, as the lesson page details do not give enough information to solve. Only by looking at the source and getting the username/password can one solve this challenge. The underlying flaw is that the application does not properly associate refresh tokens with access tokens, allowing one who has captured a leaked refresh token to update someone else's access token.\n\n```\nwebgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTRefreshEndpoint.java#L116-122\n\ntry {\n    Jwt\u003CHeader, Claims> jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(token.replace(\"Bearer \", \"\"));\n    user = (String) jwt.getBody().get(\"user\");\n    refreshToken = (String) json.get(\"refresh_token\");\n} catch (ExpiredJwtException e) {\n    user = (String) e.getClaims().get(\"user\");\n    refreshToken = (String) json.get(\"refresh_token\");\n}\n...\n```\n\n**Can SAST Find?** \n- Improbable\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nA SAST tool would not be able to reason the logic behind requiring the access token and refresh token to be linked unless this logic was built into the JWT library. It could be possible with a very strictly defined set of a rule sets to find this flaw, but it is highly improbable. \n\n**DAST Reasoning:**\n\nA DAST tool could be configured to contain two user accounts and authorize to get both tokens. It could then attempt to switch one user's refresh token with the secondary user and determine if the server responded with a new access token. \n\n---\n\n### (A2) Broken Authentication > JWT tokens > Final challenges\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/JWT.lesson/7\n\n**Source:** \n- webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTFinalEndpoint.java\n\n**Lesson:**\nThis lesson is a highly improbable series of events that could lead to overwriting a signing key stored in a database. The underlying vulnerability here is actually SQL injection that is exploitable by modifying the `kid` JWT field. \n\n```\nL162-176\nJwt jwt = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {\n    @Override\n    public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {\n        final String kid = (String) header.get(\"kid\");\n        try (var connection = dataSource.getConnection()) {\n            ResultSet rs = connection.createStatement().executeQuery(\"SELECT key FROM jwt_keys WHERE id = '\" + kid + \"'\");\n            while (rs.next()) {\n                return TextCodec.BASE64.decode(rs.getString(1));\n            }\n        } catch (SQLException e) {\n            errorMessage[0] = e.getMessage();\n        }\n        return null;\n    }\n}).parseClaimsJws(token);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nA SAST tool would flag this particular issue as a SQL injection flaw. SAST tools with taint tracking capabilities, or one that has added signatures for sources and sinks of this JWT library, could follow that the token was decoded and the `kid` came from user input. It would then identify on line 167 user supplied input was used in generation of a dynamically generated SQL query. SAST tools which take a more grep like approach, would most likely flag line 167 as the vulnerability since string concatenation used in a SQL query.\n\n**DAST Reasoning:**\n\nA DAST tool could be configured to decode JWT claims and attempt SQL injection in the various fields.\n\n**Example Attack:**\n- Decode JWT\n- Modify the JWT header\n```\n{\n  \"typ\": \"JWT\",\n  \"kid\": \"'+(select repeat('a',50000000) from INFORMATION_SCHEMA.tables)+'\",\n  \"alg\": \"HS256\"\n}\n```\n- Re-encode the token\n- Send request during a timing attack verification phase.\n\n---\n\n### (A2) Broken Authentication > Password reset > Security questions\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/PasswordReset.lesson/3\n\n**Source:** \n- webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/password_reset/QuestionsAssignment.java\n\n**Lesson:**\n\nThis lesson is to demonstrate the ability of guessing or brute forcing security questions. The users and security questions are hardcoded. The goal is to guess one of them.\n\n```\nwebgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/password_reset/QuestionsAssignment.java#L45-51\n\nstatic {\n    COLORS.put(\"admin\", \"green\");\n    COLORS.put(\"jerry\", \"orange\");\n    ...\n}\n\nwebgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/password_reset/QuestionsAssignment.java#L63-68\n\nString validAnswer = COLORS.get(username.toLowerCase());\nif (validAnswer == null) {\n    return failed(this).feedback(\"password-questions-unknown-user\").feedbackArgs(username).build();\n} else if (validAnswer.equals(securityQuestion)) {\n    return success(this).build();\n}\n```\n\n**Can SAST Find?**\n- Impossible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool would be unable to determine that this lesson has any relation to login logic as it's doing a simple existence check of inputs against a map value.\n\n**DAST Reasoning:**\n\nA DAST tool would need to be configured to treat this form as a login form for it to detect any potential discrepancy between a valid and invalid security question answer.\n\n---\n\n### (A2) Broken Authentication > Password reset > Creating the password reset link\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/PasswordReset.lesson/5\n\n**Source:** \n- webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/password_reset/ResetLinkAssignmentForgotPassword.java\n- webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/password_reset/ResetLinkAssignment.java\n\n**Lesson:**\n\nThis lesson is to demonstrate incorrect usage of user supplied data (taken from the host header) in constructing a link. The goal is to abuse this to get a hypothetical user to click on a password reset link that points to the wrong host.\n\n```\nwebgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/password_reset/ResetLinkAssignmentForgotPassword.java#L60-74\n\nString resetLink = UUID.randomUUID().toString();\nResetLinkAssignment.resetLinks.add(resetLink);\nString host = request.getHeader(\"host\");\nif (hasText(email)) {\n    if (email.equals(ResetLinkAssignment.TOM_EMAIL) && (host.contains(\"9090\")||host.contains(\"webwolf\"))) { //User indeed changed the host header.\n        ResetLinkAssignment.userToTomResetLink.put(getWebSession().getUserName(), resetLink);\n        fakeClickingLinkEmail(host, resetLink);\n    } else {\n        try {\n            sendMailToUser(email, host, resetLink);\n        } catch (Exception e) {\n            return failed(this).output(\"E-mail can't be send. please try again.\").build();\n        }\n    }\n}\n```\n\n**Can SAST Find?** \n- Improbable\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThis is a hypothetical issue that most likely would not occur in the wild. There is no real way of a SAST tool to know that the extracted host header was incorrectly used in constructing a link.\n\n**DAST Reasoning:**\n\nA DAST tool would first need to be configured to treat this form as a login or password reset form. It would then also need to know that the conditions for getting a response required the host header to contain port 9090 or the string `webwolf`. This is a highly improbable set of conditions a DAST tool (or even a human without source) would need to know to have the flaw triggered. Additionally, this flaw requires a form of user interaction (although it's faked on line 66) which a DAST tool would not be able to do.\n\n---\n\u003C/details>\n\n## (A3) > Sensitive Data Exposure\n\n---\n\n\u003Cdetails>\n\u003Csummary markdown=\"span\">Findings\u003C/summary>\n\n---\n\n### (A3) > Sensitive Data Exposure > Insecure Login > Let's try\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/InsecureLogin.lesson/1\n\n**Source:** \n- webgoat-lessons/insecure-login/src/main/java/org/owasp/webgoat/insecure_login/InsecureLoginTask.java\n\n**Lesson:**\n\nThis lesson is to demonstrate sending credentials over plain text communications. It does not contain any real flaws.\n\n```\nwebgoat-lessons/insecure-login/src/main/java/org/owasp/webgoat/insecure_login/InsecureLoginTask.java#L35-37\n\nif (username.toString().equals(\"CaptainJack\") && password.toString().equals(\"BlackPearl\")) {\n    return success(this).build();\n}\n```\n\n**Can SAST Find?** \n- Possible (different issue)\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe source is only doing a simple equality test between user input and two constant values. There is a chance, depending on the SAST tool, that it would flag the password equality check as a hardcoded password. \n\n**DAST Reasoning:**\n\nIf this page were configured as a login page and it were accessed over HTTP, there is a chance a DAST tool would report this as credentials being sent over a plain text transport.\n\n---\n\u003C/details>\n\n## (A4) XML External Entities (XXE)\n\n---\n\n\u003Cdetails>\n\u003Csummary markdown=\"span\">Findings\u003C/summary>\n\n### (A4) XML External Entities (XXE) > XXE > Let’s try\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/XXE.lesson/3\n\n**Source:** \n- webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/SimpleXXE.java\n- webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java\n\nClass: Injection\n\n**Lesson:**\n\nThis lesson's goal is to exploit an XXE parser to have it return the contents of the OS root path. The target XML parser `JAXB` will decode the `SYSTEM` entity and return the contents of the specified entity.\n\n```\nwebgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java#L87-94\n\nprotected Comment parseXml(String xml) throws JAXBException, XMLStreamException {\n    var jc = JAXBContext.newInstance(Comment.class);\n    var xif = XMLInputFactory.newInstance();\n    var xsr = xif.createXMLStreamReader(new StringReader(xml));\n\n    var unmarshaller = jc.createUnmarshaller();\n    return (Comment) unmarshaller.unmarshal(xsr);\n}\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nSAST tools should have signatures for common XML parsers like `JAXB` and determine if they do or do not disable entity and DTD resolution prior to processing user input.\n\n**DAST Reasoning:**\n\nMost DAST tools should be able to find this issue even though the attack request's response does not immediately contain the result. A DAST tool should be configured to handle callback related attacks and attempt to force the XML parser to use a URL instead of reading a system file. If the parser is vulnerable, and no egress filtering is in place, the parser will end up initiating a request to the specified URL.\n\n**Example Attack:**\n- `\u003C?xml version=\"1.0\"?>\u003C!DOCTYPE text [\u003C!ENTITY xxe SYSTEM \"http://callbackserver:9090/test\">]>\u003Ccomment>\u003Ctext>&xxe;\u003C/text>\u003C/comment>`\n\n---\n\n### (A4) XML External Entities (XXE) > XXE > Modern REST framework\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/XXE.lesson/6\n\n**Source:** \n- webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/ContentTypeAssignment.java\n- webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java\n\n**Lesson:**\n\nThis lesson's goal is to exploit an XXE parser to have it return the contents of the OS root path, much like `http://localhost:8080/WebGoat/start.mvc#lesson/XXE.lesson/3`. The only difference is the `/WebGoat/xxe/content-type` endpoint accepts both JSON and XML. This is more of a hypothetical situation due to unrealistic conditional statements used to only allow valid responses if they are met. The same underlying `parseXml` is called for both of these lessons.\n\n```\nwebgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/ContentTypeAssignment.java#L56-64\n\nif (APPLICATION_JSON_VALUE.equals(contentType)) {\n            comments.parseJson(commentStr).ifPresent(c -> comments.addComment(c, true));\n            attackResult = failed(this).feedback(\"xxe.content.type.feedback.json\").build();\n        }\n\n        if (null != contentType && contentType.contains(MediaType.APPLICATION_XML_VALUE)) {\n            String error = \"\";\n            try {\n                Comment comment = comments.parseXml(commentStr);\n...\n\nwebgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java#L87-94\n\nprotected Comment parseXml(String xml) throws JAXBException, XMLStreamException {\n    var jc = JAXBContext.newInstance(Comment.class);\n    var xif = XMLInputFactory.newInstance();\n    var xsr = xif.createXMLStreamReader(new StringReader(xml));\n\n    var unmarshaller = jc.createUnmarshaller();\n    return (Comment) unmarshaller.unmarshal(xsr);\n}\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool will most likely disregard the conditional checks necessary to call the `comments.parseXml` on line 64 of `ContentTypeAssignment`. It should determine that the input string is parsed by an XML parser in `Comments.java` that did not disable entity or DTD resolution.\n\n**DAST Reasoning:**\n\nMost DAST tools will attempt to inject into parameter names and values, not transform the entire method from one to the other. The conditional checks in `ContentTypeAssignment` lines 56 and 61 are not realistic and would most likely block legitimate attack cases.\n\n---\n\n### (A4) XML External Entities (XXE) > XXE > Blind XXE assignment\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/XXE.lesson/10\n\n**Source:** \n- webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/BlindSendFileAssignment.java\n- webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java\n\n**Lesson:**\n\nThis lesson's goal is to exploit an XXE parser to have it post the contents of a file to a callback server. The same underlying `parseXml` is called for this lesson as well.\n\n```\nwebgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/BlindSendFileAssignment.java#L56-64\n\n public AttackResult addComment(@RequestBody String commentStr) {\n        //Solution is posted as a separate comment\n        if (commentStr.contains(CONTENTS)) {\n            return success(this).build();\n        }\n\n        try {\n            Comment comment = comments.parseXml(commentStr);\n            comments.addComment(comment, false);\n        } catch (Exception e) {\n            return failed(this).output(e.toString()).build();\n        }\n        return failed(this).build();\n    }\n...\n\nwebgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java#L87-94\n\nprotected Comment parseXml(String xml) throws JAXBException, XMLStreamException {\n    var jc = JAXBContext.newInstance(Comment.class);\n    var xif = XMLInputFactory.newInstance();\n    var xsr = xif.createXMLStreamReader(new StringReader(xml));\n\n    var unmarshaller = jc.createUnmarshaller();\n    return (Comment) unmarshaller.unmarshal(xsr);\n}\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThis flaw is the same as `(A4) XML External Entities (XXE) > XXE > Let’s try`\n\n**DAST Reasoning:**\n\nThis flaw is the same as `(A4) XML External Entities (XXE) > XXE > Let’s try`\n\n---\n\n\u003C/details>\n\n## (A5) Broken Access Control\n\n--- \n\n\u003Cdetails>\n\u003Csummary markdown=\"span\">Findings\u003C/summary>\n\n### (A5) Broken Access Control > Insecure Direct Object References > Authenticate First, Abuse Authorization Later\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/IDOR.lesson/1\n\n**Source:** \n- webgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORLogin.java\n\n**Lesson:**\n\nThis lesson is for assigning the current session to a user profile for subsequent lessons – it is not supposed to contain any vulnerabilities.\n\n```\nwebgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORLogin.java#L59-74\n\npublic AttackResult completed(@RequestParam String username, @RequestParam String password) {\n        initIDORInfo();\n        UserSessionData userSessionData = getUserSessionData();\n\n        if (idorUserInfo.containsKey(username)) {\n            if (\"tom\".equals(username) && idorUserInfo.get(\"tom\").get(\"password\").equals(password)) {\n                userSessionData.setValue(\"idor-authenticated-as\", username);\n                userSessionData.setValue(\"idor-authenticated-user-id\", idorUserInfo.get(username).get(\"id\"));\n                return success(this).feedback(\"idor.login.success\").feedbackArgs(username).build();\n            } else {\n                return failed(this).feedback(\"idor.login.failure\").build();\n            }\n        } else {\n            return failed(this).feedback(\"idor.login.failure\").build();\n        }\n    }\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThere is nothing to find as it's only adding server side session data.\n\n**DAST Reasoning:**\n\nThere is nothing to find as it's only adding server side session data.\n\n---\n\n### (A5) Broken Access Control > Insecure Direct Object References > Observing Differences & Behaviors\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/IDOR.lesson/2\n\n**Source:** \n- webgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORDiffAttributes.java\n\n**Lesson:**\n\nThis lesson demonstrates hidden authorization properties and does not contain any real flaws.\n\n```\nwebgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORDiffAttributes.java#L36-48\n\npublic AttackResult completed(@RequestParam String attributes) {\n    attributes = attributes.trim();\n    String[] diffAttribs = attributes.split(\",\");\n    if (diffAttribs.length \u003C 2) {\n        return failed(this).feedback(\"idor.diff.attributes.missing\").build();\n    }\n    if (diffAttribs[0].toLowerCase().trim().equals(\"userid\") && diffAttribs[1].toLowerCase().trim().equals(\"role\")\n            || diffAttribs[1].toLowerCase().trim().equals(\"userid\") && diffAttribs[0].toLowerCase().trim().equals(\"role\")) {\n        return success(this).feedback(\"idor.diff.success\").build();\n    } else {\n        return failed(this).feedback(\"idor.diff.failure\").build();\n    }\n}\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThere is nothing to find, as it's only doing a comparison between inputs and expected values.\n\n**DAST Reasoning:**\n\nThere is nothing to find, as it's only doing a comparison between inputs and expected values.\n\n---\n\n### (A5) Broken Access Control > Insecure Direct Object References > Guessing & Predicting Patterns\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/IDOR.lesson/3\n\n**Source:** \n- webgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORViewOwnProfileAltUrl.java\n\n**Lesson:**\n\nThis lesson demonstrates accessing your own profile by using a direct object reference (userid). This lesson requires that you are already logged in via the hypothetical login endpoint `/WebGoat/IDOR/login`.\n\n```\nwebgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORViewOwnProfileAltUrl.java#L42-62\n\npublic AttackResult completed(@RequestParam String url) {\n        try {\n            if (userSessionData.getValue(\"idor-authenticated-as\").equals(\"tom\")) {\n                //going to use session auth to view this one\n                String authUserId = (String) userSessionData.getValue(\"idor-authenticated-user-id\");\n                //don't care about http://localhost:8080 ... just want WebGoat/\n                String[] urlParts = url.split(\"/\");\n                if (urlParts[0].equals(\"WebGoat\") && urlParts[1].equals(\"IDOR\") && urlParts[2].equals(\"profile\") && urlParts[3].equals(authUserId)) {\n                    UserProfile userProfile = new UserProfile(authUserId);\n                    return success(this).feedback(\"idor.view.own.profile.success\").output(userProfile.profileToMap().toString()).build();\n                } else {\n                    return failed(this).feedback(\"idor.view.own.profile.failure1\").build();\n                }\n\n            } else {\n                return failed(this).feedback(\"idor.view.own.profile.failure2\").build();\n            }\n        } catch (Exception ex) {\n            return failed(this).feedback(\"an error occurred with your request\").build();\n        }\n    }\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThere is nothing to find, as it's only doing a comparison between inputs and expected values.\n\n**DAST Reasoning:**\n\nThere is nothing to find, as it's only doing a comparison between inputs and expected values. Additionally, the DAST tool would need to be configured to treat the `/WebGoat/IDOR/login` page as a login form to be able to successfully set the additional server side session data. However, a DAST tool must already be configured to login to the `/WebGoat/` end point and most DAST tools don't support logging in multiple times to different endpoints for the same scan.\n\n---\n\n### (A5) Broken Access Control > Insecure Direct Object References > Playing with the Patterns\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/IDOR.lesson/4\n\n**Source:** \n- webgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORViewOtherProfile.java\n- webgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDOREditOtherProfiile.java\n\n**Lesson:**\n\nThis lesson demonstrates accessing a mock users profile by using a direct object reference (userid). This lesson requires that you are already logged in via the hypothetical login endpoint `/WebGoat/IDOR/login`. \n\n```\nwebgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORViewOtherProfile.java#L48-67\n\npublic AttackResult completed(@PathVariable(\"userId\") String userId, HttpServletResponse resp) {\n        Map\u003CString, Object> details = new HashMap\u003C>();\n\n        if (userSessionData.getValue(\"idor-authenticated-as\").equals(\"tom\")) {\n            //going to use session auth to view this one\n            String authUserId = (String) userSessionData.getValue(\"idor-authenticated-user-id\");\n            if (userId != null && !userId.equals(authUserId)) {\n                //on the right track\n                UserProfile requestedProfile = new UserProfile(userId);\n                // secure code would ensure there was a horizontal access control check prior to dishing up the requested profile\n                if (requestedProfile.getUserId().equals(\"2342388\")) {\n                    return success(this).feedback(\"idor.view.profile.success\").output(requestedProfile.profileToMap().toString()).build();\n                } else {\n                    return failed(this).feedback(\"idor.view.profile.close1\").build();\n                }\n            } else {\n                return failed(this).feedback(\"idor.view.profile.close2\").build();\n            }\n        }\n        return failed(this).build();\n\nwebgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDOREditOtherProfiile.java#L41-88\npublic AttackResult completed(@PathVariable(\"userId\") String userId, @RequestBody UserProfile userSubmittedProfile) {\n\n    String authUserId = (String) userSessionData.getValue(\"idor-authenticated-user-id\");\n    // this is where it starts ... accepting the user submitted ID and assuming it will be the same as the logged in userId and not checking for proper authorization\n    // Certain roles can sometimes edit others' profiles, but we shouldn't just assume that and let everyone, right?\n    // Except that this is a vulnerable app ... so we will\n    UserProfile currentUserProfile = new UserProfile(userId);\n    if (userSubmittedProfile.getUserId() != null && !userSubmittedProfile.getUserId().equals(authUserId)) {\n        // let's get this started ...\n        currentUserProfile.setColor(userSubmittedProfile.getColor());\n        currentUserProfile.setRole(userSubmittedProfile.getRole());\n        // we will persist in the session object for now in case we want to refer back or use it later\n        userSessionData.setValue(\"idor-updated-other-profile\", currentUserProfile);\n        if (currentUserProfile.getRole() \u003C= 1 && currentUserProfile.getColor().toLowerCase().equals(\"red\")) {\n            return success(this)\n                    .feedback(\"idor.edit.profile.success1\")\n                    .output(currentUserProfile.profileToMap().toString())\n                    .build();\n        }\n...\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nSAST tools have difficulties determining authorization checks unless custom rule sets are configured.\n\n**DAST Reasoning:**\n\nIDOR-based attacks can be difficult for DAST tools to detect. The DAST tool would need to be configured to treat the `/WebGoat/IDOR/login` page as a login form to be able to successfully set the additional server side session data. After which it would somehow need to determine that the edit and view profile endpoint at `/WebGoat/IDOR/profile/{user}` should have the `{user}` field replaced with the userid of the same user, and then replaced as a different user to trigger the flaw. \n\n---\n\n\u003C/details>\n\n## (A7) Cross-Site Scripting (XSS)\n\n---\n\n\u003Cdetails>\n\u003Csummary markdown=\"span\">Findings\u003C/summary>\n\n### (A7) Cross-Site Scripting (XSS) > Cross Site Scripting > Try It! Reflected XSS\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CrossSiteScripting.lesson/6\n\n**Source:** \n- webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/xss/CrossSiteScriptingLesson5a.java\n- webgoat-container/src/main/resources/static/js/goatApp/view/LessonContentView.js \n\n**Lesson:**\n\nThis lesson demonstrates a self-reflected XSS vulnerability. The goal is to inject XSS into the `field1` parameter.\n\n```\nwebgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/xss/CrossSiteScriptingLesson5a.java#L56-79\n\ncart.append(\"Thank you for shopping at WebGoat. \u003Cbr />You're support is appreciated\u003Chr />\");\ncart.append(\"\u003Cp>We have charged credit card:\" + field1 + \"\u003Cbr />\");\ncart.append(\"                             ------------------- \u003Cbr />\");\ncart.append(\"                               $\" + totalSale);\n\n//init state\nif (userSessionData.getValue(\"xss-reflected1-complete\") == null) {\n    userSessionData.setValue(\"xss-reflected1-complete\", (Object) \"false\");\n}\n\nif (field1.toLowerCase().matches(\"\u003Cscript>.*(console\\\\.log\\\\(.*\\\\)|alert\\\\(.*\\\\))\u003C\\\\/script>\")) {\n    //return )\n    userSessionData.setValue(\"xss-reflected-5a-complete\", \"true\");\n    if (field1.toLowerCase().contains(\"console.log\")) {\n        return success(this).feedback(\"xss-reflected-5a-success-console\").output(cart.toString()).build();\n    } else {\n        return success(this).feedback(\"xss-reflected-5a-success-alert\").output(cart.toString()).build();\n    }\n} else {\n    userSessionData.setValue(\"xss-reflected1-complete\", \"false\");\n    return success(this)\n            .feedback(\"xss-reflected-5a-failure\")\n            .output(cart.toString())\n            .build();\n}\n...\n\nwebgoat-container/src/main/resources/static/js/goatApp/view/LessonContentView.js#L183-185\n\nrenderOutput: function (output) {\n    var s = this.removeSlashesFromJSON(output);\n    this.$curOutput.html(polyglot.t(s) || \"\");\n    ...\n```\n\n**Can SAST Find?** \n- Probable\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe `field1` parameter comes directly from user input and is output in the response on lines 70, 72 and 78. The user input is inserted into the cart `StringBuffer` on line 57. However the response for this endpoint is JSON and the reason this is exploitable is due to how the frontend renders the `output` JSON field in `LessonContentView.js`. A SAST tool would need to be able to scan JavaScript and possibly Java to detect this as a Cross-Site Scripting flaw.\n\n**DAST Reasoning:**\n\nThis is a straightforward XSS attack. A DAST tool would most likely attempt various XSS attack strings in each parameter value.\n\n**Example Attack:**\n- `/WebGoat/CrossSiteScripting/attack5a?QTY1=1&QTY2=1&QTY3=1&QTY4=1&field2=12345&field1=%3cimg%20src%3dx+onerror%3dalert(1)%3e`\n\n---\n\n### (A7) Cross-Site Scripting (XSS) > Cross Site Scripting > Identify potential for DOM-Based XSS\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CrossSiteScripting.lesson/9\n\n**Source:** \n- webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/xss/CrossSiteScriptingLesson6a.java\n\n**Lesson:**\n\nThis lesson does not contain any vulnerabilities. The goal is to simply identify that `start.mvc#test` is the hidden route.\n\n```\nwebgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/xss/CrossSiteScriptingLesson6a.java#L42-50\n\npublic AttackResult completed(@RequestParam String DOMTestRoute) {\n\n    if (DOMTestRoute.matches(\"start\\\\.mvc#test(\\\\/|)\")) {\n        //return )\n        return success(this).feedback(\"xss-reflected-6a-success\").build();\n    } else {\n        return failed(this).feedback(\"xss-reflected-6a-failure\").build();\n    }\n}\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThere is nothing to find, as it's only doing a comparison between inputs and expected values.\n\n**DAST Reasoning:**\n\nThere is nothing to find, as it's only doing a comparison between inputs and expected values.\n\n---\n\n### (A7) Cross-Site Scripting (XSS) > Cross Site Scripting > Try It! DOM-Based XSS\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CrossSiteScripting.lesson/10\n\n**Source:** \n- webgoat-container/src/main/resources/static/js/goatApp/view/GoatRouter.js\n- webgoat-container/src/main/resources/static/js/goatApp/controller/LessonController.js\n- webgoat-container/src/main/resources/static/js/goatApp/view/LessonContentView.js\n\n**Lesson:**\n\nThis lesson is a client side DOM-XSS vulnerability that exists in a different java project `webgoat-container`. The underlying vulnerability is in an insecure call to the `jQuery.html` method.\n\n```\nwebgoat-container/src/main/resources/static/js/goatApp/view/GoatRouter.js#L46-117\n\nvar GoatAppRouter = Backbone.Router.extend({\n\n    routes: {\n        'welcome': 'welcomeRoute',\n        'lesson/:name': 'lessonRoute',\n        'lesson/:name/:pageNum': 'lessonPageRoute',\n        'test/:param': 'testRoute',\n        'reportCard': 'reportCard'\n    },\n...\ntestRoute: function (param) {\n            this.lessonController.testHandler(param);\n            //this.menuController.updateMenu(name);\n        },\n...\n\nwebgoat-container/src/main/resources/static/js/goatApp/controller/LessonController.js#156-159\n\nthis.testHandler = function(param) {\n    console.log('test handler');\n    this.lessonContentView.showTestParam(param);\n};\n...\n\nwebgoat-container/src/main/resources/static/js/goatApp/view/LessonContentView.js#L220-222\n\nshowTestParam: function (param) {\n    this.$el.find('.lesson-content').html('test:' + param);\n},\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool that does intra-procedural taint tracking would need to have signatures for the Backbone javascript framework. It would need to follow taint down to the final vulnerability on line 221 of `LessonContentView.js`. A non-intra-procedural taint tracking SAST tool may simply look for any `html()` method calls and flag it as a potential sink for XSS.\n\n**DAST Reasoning:**\n\nA DAST tool would most likely need to have some form of SAST-like capabilities to know that the target application not only uses Backbone, but is able to extract the routes from the `Backbone.router`. It could then potentially attack all URL Fragment based route links. Since the `#test/` route is technically never referenced anywhere, it is unlikely that a DAST tool would be able to find this vulnerable route.\n\nAlso, since this is a client-side vulnerability, the DAST tool must be instrumenting a real browser, otherwise it would be nearly impossible to trigger the flaw since it is dynamically rewriting the DOM.\n\n**Example Attack:**\n- `http://localhost:8080/WebGoat/start.mvc#test/%3Cimg%20src=x%20onerror=alert(1)%3E`\n\n---\n\u003C/details>\n\n## (A8)  Insecure Deserialization\n\n---\n\n\u003Cdetails>\n\u003Csummary markdown=\"span\">Findings\u003C/summary>\n\n### (A8)  Insecure Deserialization > Insecure Deserialization > Let’s try\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/InsecureDeserialization.lesson/4\n\n**Source:** \n- webgoat-lessons/insecure-deserialization/src/main/java/org/owasp/webgoat/deserialization/InsecureDeserializationTask.java\n- webgoat-lessons/insecure-deserialization/src/main/java/org/dummy/insecure/framework/VulnerableTaskHolder.java\n\n**Lesson:**\n\nThis lesson demonstrates how object deserialization attacks can be exploited to run arbitrary code. In particular this lesson deals with java deserialization attacks.\n\n```\nwebgoat-lessons/insecure-deserialization/src/main/java/org/owasp/webgoat/deserialization/InsecureDeserializationTask.java#L54-56\n\ntry (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token)))) {\n    before = System.currentTimeMillis();\n    Object o = ois.readObject();\n...\n\nwebgoat-lessons/insecure-deserialization/src/main/java/org/dummy/insecure/framework/VulnerableTaskHolder.java#L38-59\n\nprivate void readObject( ObjectInputStream stream ) throws Exception {\n    //unserialize data so taskName and taskAction are available\n    stream.defaultReadObject();\n\n    //do something with the data\n    log.info(\"restoring task: {}\", taskName);\n    log.info(\"restoring time: {}\", requestedExecutionTime);\n\n    if (requestedExecutionTime!=null && \n            (requestedExecutionTime.isBefore(LocalDateTime.now().minusMinutes(10))\n            || requestedExecutionTime.isAfter(LocalDateTime.now()))) {\n        //do nothing is the time is not within 10 minutes after the object has been created\n        log.debug(this.toString());\n        throw new IllegalArgumentException(\"outdated\");\n    }\n\n    //condition is here to prevent you from destroying the goat altogether\n    if ((taskAction.startsWith(\"sleep\")||taskAction.startsWith(\"ping\"))\n            && taskAction.length() \u003C 22) {\n    log.info(\"about to execute: {}\", taskAction);\n    try {\n        Process p = Runtime.getRuntime().exec(taskAction);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nA SAST tool should be able to identify the `Object o = ois.readObject();` call on line 56 of `InsecureDeserializationTask.java` and flag as a potential Deserialization issue. SAST tools would most likely also flag the `Runetime.getRuntime().exec(taskAction)` call on line 59 as a potential for OS command injection.\n\n**DAST Reasoning:**\n\nDAST tools usually work off of intercepting requests and analyzing parameter values to determine what to inject. The `/WebGoat/InsecureDeserialization/task` endpoint is never triggered with a valid object, only a reference to what is expected exists in the HTML output. Due to this, there is no way for a DAST tool to know that this endpoint expects a serialized java object, and hence would not be able to attack it. \n\n---\n\n\u003C/details>\n\n## (A9) Vulnerable Components\n\n---\n\n\u003Cdetails>\n\u003Csummary markdown=\"span\">Findings\u003C/summary>\n\n### (A9) Vulnerable Components > Vulnerable Components > The exploit is not always in \"your\" code\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/VulnerableComponents.lesson/4\n\n**Source:** \n- webgoat-lessons/vulnerable-components/src/main/resources/html/VulnerableComponents.html\n\n**Lesson:**\n\nThis lesson demonstrates a vulnerable and non-vulnerable version of jquery-ui. The attack is built in to the source HTML form. \n\n```\nL45-57\n        \u003Ctd>\u003Cinput id=\"closetext\" value=\"OK\u003Cscript>alert('XSS')\u003C/script>\" type=\"TEXT\" />\u003Cinput\n            name=\"SUBMIT\" value=\"Go!\" type=\"SUBMIT\" onclick=\"webgoat.customjs.vuln_jquery_ui()\" />\u003C/td>\n        \u003Ctd>\u003C/td>\n    \u003C/tr>\n\u003C/table>\n\u003Cscript th:inline=\"javascript\">\n/*\u003C![CDATA[*/\nwebgoat.customjs.vuln_jquery_ui = function()\n{\n    webgoat.customjs.jqueryVuln('#dialog').dialog({ closeText: webgoat.customjs.jquery('#closetext').val() });\n};\n/*]]>*/\n    \u003C/script>\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nMost likely the SAST tool would *not* trigger on the exact line, but would be used in combination with a dependency scanning tool to identify the outdated version of jquery-ui.\n\n**DAST Reasoning:**\n\nA DAST tool would most likely click the form submission and inject it's own XSS value to trigger the flaw.\n\n**Example Attack:**\n- `OK\u003Cscript>alert('XSS')\u003C/script>`\n\n---\n\n### (A9) Vulnerable Components > Vulnerable Components > Exploiting CVE-2013-7285 (XStream)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/VulnerableComponents.lesson/11\n\n**Source:** \n- webgoat-lessons/vulnerable-components/src/main/java/org/owasp/webgoat/vulnerable_components/VulnerableComponentsLesson.java\n\n**Lesson:**\n\nThis lesson demonstrates a vulnerable version of `Xstream` that allows for XXE attacks.  \n\n```\nwebgoat-lessons/vulnerable-components/src/main/java/org/owasp/webgoat/vulnerable_components/VulnerableComponentsLesson.java#L37-68\n\nAttackResult completed(@RequestParam String payload) {\n        XStream xstream = new XStream(new DomDriver());\n        xstream.setClassLoader(Contact.class.getClassLoader());\n\n        xstream.processAnnotations(Contact.class);\n...\n\n        try {\n//        \tSystem.out.println(\"Payload:\" + payload);\n            Contact expl = (Contact) xstream.fromXML(payload);\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nMost likely the SAST tool would *not* trigger on the exact line, but would be used in combination with a dependency scanning tool to identify the vulnerable `Xstream` component. It may also model the `Xstream` library to determine if XXE injection attacks are possible.\n\n**DAST Reasoning:**\n\nA DAST tool may not attempt XML attacks since the form gives no hint that the expected form should POST as XML; the default content-type is `application/x-www-form-urlencoded; charset=UTF-8` with the parameter name of `payload`. However some DAST tools may attempt XXE attacks in all parameter value types, regardless of content-type.\n\n**Example Attack:** \n- `payload=\u003C?xml version=\"1.0\"?>\u003C!DOCTYPE text [\u003C!ENTITY xxe SYSTEM \"http://192.168.2.249:9090/test\">]>\u003Ccomment>\u003Ctext>&xxe;\u003C/text>\u003C/comment>`\n\n---\n\n\u003C/details>\n\n## (A8:2013) Request Forgeries\n\n---\n\n\u003Cdetails>\n\u003Csummary markdown=\"span\">Findings\u003C/summary>\n\n### (A8:2013) Request Forgeries > Cross-Site Request Forgeries > Basic Get CSRF Exercise\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CSRF.lesson/2\n\n**Source:** \n- webgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFConfirmFlag1.java\n- webgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFGetFlag.java\n- webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java\n\n**Lesson:**\n\nThis lesson demonstrates exploiting a form that is not protected by anti-CSRF measures.\n\n```\nwebgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFGetFlag.java#L49-51\n\n@RequestMapping(path = \"/csrf/basic-get-flag\", produces = {\"application/json\"}, method = RequestMethod.POST)\n@ResponseBody\npublic Map\u003CString, Object> invoke(HttpServletRequest req) {\n    ...\n\nwebgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFConfirmFlag1.java#L45-47\n\n@PostMapping(path = \"/csrf/confirm-flag-1\", produces = {\"application/json\"})\n@ResponseBody\npublic AttackResult completed(String confirmFlagVal) {\n...\n\nwebgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java#L72\n    ...\n    security.and().csrf().disable();\n    ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nProvided the target web framework has been modeled, it should contain signatures to determine that the service has been configured with anti-CSRF protections enabled. Depending on the framework, it may also look at each individual request mapping to determine vulnerability. In this case the `WebSecurityConfig.java` explicitly disabled CSRF protections. \n\n**DAST Reasoning:**\n\nDAST tools usually look at the `\u003Cform>` tag definition and try to identify any \"CSRF like\" tokens exist in parameters. Some DAST tools may also inspect the request itself to identify anti-CSRF tokens. Most DAST tools will likely flag both the `/WebGoat/csrf/basic-get-flag` and `/WebGoat/csrf/confirm-flag-1` as being vulnerable.\n\n---\n\n### (A8:2013) Request Forgeries > Cross-Site Request Forgeries > Post a review on someone else’s behalf\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CSRF.lesson/3\n\n**Source:** \n- webgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/ForgedReviews.java\n- webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java\n\n**Lesson:**\n\nThis lesson demonstrates exploiting a form that includes a weak form of anti-CSRF measures, as the CSRF token is a hardcoded value. \n\n```\nwebgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/ForgedReviews.java#L78-102\n\npublic AttackResult createNewReview(String reviewText, Integer stars, String validateReq, HttpServletRequest request) {\n    final String host = (request.getHeader(\"host\") == null) ? \"NULL\" : request.getHeader(\"host\");\n    final String referer = (request.getHeader(\"referer\") == null) ? \"NULL\" : request.getHeader(\"referer\");\n    final String[] refererArr = referer.split(\"/\");\n\n    Review review = new Review();\n    review.setText(reviewText);\n    review.setDateTime(DateTime.now().toString(fmt));\n    review.setUser(webSession.getUserName());\n    review.setStars(stars);\n    var reviews = userReviews.getOrDefault(webSession.getUserName(), new ArrayList\u003C>());\n    reviews.add(review);\n    userReviews.put(webSession.getUserName(), reviews);\n    //short-circuit\n    if (validateReq == null || !validateReq.equals(weakAntiCSRF)) {\n        return failed(this).feedback(\"csrf-you-forgot-something\").build();\n    }\n    //we have the spoofed files\n    if (referer != \"NULL\" && refererArr[2].equals(host)) {\n        return failed(this).feedback(\"csrf-same-host\").build();\n    } else {\n        return success(this).feedback(\"csrf-review.success\").build(); //feedback(\"xss-stored-comment-failure\")\n    }\n}\n\nwebgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java#L72\n    ...\n    security.and().csrf().disable();\n    ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nProvided the target web framework has been modeled it should contain signatures to determine that the service has been configured with anti-CSRF protections enabled. Depending on the framework it may also look at each individual request mapping to determine vulnerability. In this case the `WebSecurityConfig.java` explicitly disabled CSRF protections. Most likely a SAST tool will completely ignore the hard coded value check.\n\n**DAST Reasoning:**\n\nDAST tools usually look at the `\u003Cform>` tag definition and try to identify any \"CSRF like\" tokens exist in parameters. Some DAST tools may also inspect the request itself to identify anti-CSRF tokens. In this case a DAST tool may be confused by the seemingly configured CSRF token, when in reality the value is hard coded. A DAST tool will need to do an active request and compare the results to see if the CSRF token is ever updated/changed.\n\n---\n\n### (A8:2013) Request Forgeries > Cross-Site Request Forgeries > CSRF and content-type\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CSRF.lesson/6\n\n**Source:** \n- webgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFFeedback.java\n- webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java\n\n**Lesson:**\n\nThis lesson demonstrates exploiting a CSRF vulnerable form that calls an endpoint which doesn't validate the content-type properly. Newer browsers will append a `=` to the end of `text/plain` forms where only the name value exists. This form is still vulnerable to CSRF if attackers use `XMLHttpRequest` or `navigator.sendBeacon`.\n\n```\nwebgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFFeedback.java#L57-74\n\npublic AttackResult completed(HttpServletRequest request, @RequestBody String feedback) {\n        try {\n            objectMapper.enable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);\n            objectMapper.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);\n            objectMapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS);\n            objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);\n            objectMapper.enable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES);\n            objectMapper.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);\n            objectMapper.readValue(feedback.getBytes(), Map.class);\n        } catch (IOException e) {\n            return failed(this).feedback(ExceptionUtils.getStackTrace(e)).build();\n        }\n        boolean correctCSRF = requestContainsWebGoatCookie(request.getCookies()) && request.getContentType().contains(MediaType.TEXT_PLAIN_VALUE);\n        correctCSRF &= hostOrRefererDifferentHost(request);\n        if (correctCSRF) {\n            String flag = UUID.randomUUID().toString();\n            userSessionData.setValue(\"csrf-feedback\", flag);\n            return success(this).feedback(\"csrf-feedback-success\").feedbackArgs(flag).build();\n    ...\n\nwebgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java#L72\n    ...\n    security.and().csrf().disable();\n    ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nProvided the target web framework has been modeled it should contain signatures to determine that the service has been configured with anti-CSRF protections enabled. Depending on the framework it may also look at each individual request mapping to determine vulnerability. In this case the `WebSecurityConfig.java` explicitly disabled CSRF protections. \n\n**DAST Reasoning:**\n\nDAST tools usually look at the `\u003Cform>` tag definition and try to identify any \"CSRF like\" tokens exist in parameters. Some DAST tools may also inspect the request itself to identify anti-CSRF tokens. In this case a DAST tool would treat this form the same as any other and flag it as vulnerable to CSRF regardless of content-type.\n\n---\n\n### (A8:2013) Request Forgeries > Cross-Site Request Forgeries > Login CSRF attack\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CSRF.lesson/7\n\n**Source:** \n- webgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFLogin.java\n- webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java\n\n**Lesson:**\n\nThis lesson demonstrates exploiting a CSRF vulnerable form to force a victim to login under the attackers account.\n\n```\nwebgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFLogin.java#50-57\n\npublic AttackResult completed(HttpServletRequest request) {\n    String userName = request.getUserPrincipal().getName();\n    if (userName.startsWith(\"csrf\")) {\n        markAssignmentSolvedWithRealUser(userName.substring(\"csrf-\".length()));\n        return success(this).feedback(\"csrf-login-success\").build();\n    }\n    return failed(this).feedback(\"csrf-login-failed\").feedbackArgs(userName).build();\n}\n\nwebgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java#L72\n    ...\n    security.and().csrf().disable();\n    ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nProvided the target web framework has been modeled it should contain signatures to determine that the service has been configured with anti-CSRF protections enabled. Depending on the framework it may also look at each individual request mapping to determine vulnerability. In this case the `WebSecurityConfig.java` explicitly disabled CSRF protections. \n\n**DAST Reasoning:**\n\nDAST tools usually look at the `\u003Cform>` tag definition and try to identify any \"CSRF like\" tokens exist in parameters. Some DAST tools may also inspect the request itself to identify anti-CSRF tokens. In this case a DAST tool would treat this form the same as any other and flag it as vulnerable to CSRF.\n\n---\n\n### (A8:2013) Request Forgeries > Server-Side Request Forgery > Change the URL to display Jerry\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SSRF.lesson/1\n\n**Source:** \n- webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask1.java\n\n**Lesson:**\n\nThis lesson does not contain a real vulnerability, it is only for demonstration purposes for modifying parameters.\n\n```\nwebgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask1.java#44-66\n\nprotected AttackResult stealTheCheese(String url) {\n    try {\n        StringBuffer html = new StringBuffer();\n\n        if (url.matches(\"images/tom.png\")) {\n            html.append(\"\u003Cimg class=\\\"image\\\" alt=\\\"Tom\\\" src=\\\"images/tom.png\\\" width=\\\"25%\\\" height=\\\"25%\\\">\");\n            return failed(this)\n                    .feedback(\"ssrf.tom\")\n                    .output(html.toString())\n                    .build();\n        } else if (url.matches(\"images/jerry.png\")) {\n            html.append(\"\u003Cimg class=\\\"image\\\" alt=\\\"Jerry\\\" src=\\\"images/jerry.png\\\" width=\\\"25%\\\" height=\\\"25%\\\">\");\n            return success(this)\n                    .feedback(\"ssrf.success\")\n                    .output(html.toString())\n                    .build();\n        } else {\n            html.append(\"\u003Cimg class=\\\"image\\\" alt=\\\"Silly Cat\\\" src=\\\"images/cat.jpg\\\">\");\n            return failed(this)\n                    .feedback(\"ssrf.failure\")\n                    .output(html.toString())\n                    .build();\n        }\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThis endpoint does not contain any SSRF issues, it simply does a string match and returns different results.\n\n**DAST Reasoning:**\n\nThis endpoint does not contain any SSRF issues.\n\n---\n\n### (A8:2013) Request Forgeries > Server-Side Request Forgery > \n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SSRF.lesson/2\n\n**Source:** \n- webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java\n\n**Lesson:**\n\nThis lesson is for exploiting SSRF but limits the user to a single URL that must exactly match `http://ifconfig.pro`.\n\n```\nwebgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java#L49-74\n\nprotected AttackResult furBall(String url) {\n        try {\n            StringBuffer html = new StringBuffer();\n\n            if (url.matches(\"http://ifconfig.pro\")) {\n                URL u = new URL(url);\n                URLConnection urlConnection = u.openConnection();\n                BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));\n                String inputLine;\n\n                while ((inputLine = in.readLine()) != null) {\n                    html.append(inputLine);\n                }\n                in.close();\n\n                return success(this)\n                        .feedback(\"ssrf.success\")\n                        .output(html.toString())\n                        .build();\n            } else {\n                html.append(\"\u003Cimg class=\\\"image\\\" alt=\\\"image post\\\" src=\\\"images/cat.jpg\\\">\");\n                return failed(this)\n                        .feedback(\"ssrf.failure\")\n                        .output(html.toString())\n                        .build();\n            }\n\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nA SAST tool may report that this endpoint is vulnerable due to the user provided `url` parameter being used in a `URLConnection.openConnection()` call. In reality, this is not an exploitable flaw since the URL must exactly match `http://ifconfig.pro`\n\n**DAST Reasoning:**\n\nA DAST tool attempting SSRF injection attacks will most likely use a callback server to receive a forced request from the target application. Since the value is technically hardcoded, there is no way for a DAST tool to know if the endpoint is using a user provided value in construction of the `URLConnection.openConnection()` call.\n\n---\n\u003C/details>\n\n## Client side\n\n---\n\nThese lessons are just demonstrations of bypassing client side restrictions – since there is no real business logic to exploit they do not contain any real exploitable flaws. \n\n---\n\n## Challenges\n\n---\n\n\u003Cdetails>\n\u003Csummary markdown=\"span\">Findings\u003C/summary>\n\n### Challenges > Admin lost password\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/Challenge1.lesson/1\n\n**Source:** \n- webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge1/Assignment1.java\n- webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge1/ImageServlet.java\n- webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/SolutionConstants.java\n\n**Challenge:**\n\nThe purpose of this challenge is to find the hidden pin code embedded in the logo and replace the hardcoded password's 1234 with the value. \n\n```\n\nwebgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge1/ImageServlet.java#L23-37\n\nprotected void doGet(HttpServletRequest request, HttpServletResponse response)\n        throws ServletException, IOException {\n\n    byte[] in = new ClassPathResource(\"images/webgoat2.png\").getInputStream().readAllBytes();\n\n    String pincode = String.format(\"%04d\", PINCODE);\n\n    in[81216]=(byte) pincode.charAt(0);\n    in[81217]=(byte) pincode.charAt(1);\n    in[81218]=(byte) pincode.charAt(2);\n    in[81219]=(byte) pincode.charAt(3);\n\n    response.setContentType(MediaType.IMAGE_PNG_VALUE);\n    FileCopyUtils.copy(in, response.getOutputStream());\n}\n\nwebgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/SolutionConstants.java#L34-36\n\nString PASSWORD = \"!!webgoat_admin_1234!!\";\nString PASSWORD_TOM = \"thisisasecretfortomonly\";\nString ADMIN_PASSWORD_LINK = \"375afe1104f4a487a73823c50a9292a2\";\n```\n\n**Can SAST Find?** \n- Possible (different issue)\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThere is no real vulnerability here for a SAST tool to alert on that would match the purpose of the assignment. However, SAST tools will most likely flag hardcoded credentials in `SolutionConstants.java`.\n\n**DAST Reasoning:**\n\nThere is no real vulnerability here for a DAST tool to alert on that would match the purpose of the assignment. \n\n---\n\n### Challenges > Without password\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/Challenge5.lesson\n\n**Source:** \n- webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge5/Assignment5.java\n\n**Challenge:**\n\nThe purpose of this challenge is to login as `Larry` without knowing his password. This can be achieved by exploiting a SQL Injection vulnerability. \n\n```\n\nwebgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge5/Assignment5.java#L51-68\n\npublic AttackResult login(@RequestParam String username_login, @RequestParam String password_login) throws Exception {\n        if (!StringUtils.hasText(username_login) || !StringUtils.hasText(password_login)) {\n            return failed(this).feedback(\"required4\").build();\n        }\n        if (!\"Larry\".equals(username_login)) {\n            return failed(this).feedback(\"user.not.larry\").feedbackArgs(username_login).build();\n        }\n        try (var connection = dataSource.getConnection()) {\n            PreparedStatement statement = connection.prepareStatement(\"select password from challenge_users where userid = '\" + username_login + \"' and password = '\" + password_login + \"'\");\n            ResultSet resultSet = statement.executeQuery();\n\n            if (resultSet.next()) {\n                return success(this).feedback(\"challenge.solved\").feedbackArgs(Flag.FLAGS.get(5)).build();\n            } else {\n                return failed(this).feedback(\"challenge.close\").build();\n            }\n        }\n    }\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nWhile the SQL query is a prepared statement, `username_login` and `password_login` fields are dynamically inserted into the query statement. A SAST tool should identify that the query string is concatenated with user input on line 59.\n\n**DAST Reasoning:**\n\nA DAST tool would most likely not find this vulnerability due to the username being checked against the hardcoded `Larry` value. A DAST tool would need to be configured for this particular page to use the `Larry` username, and then attempt SQL Injection.\n\n---\n\n### Challenges > Admin password reset\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/Challenge7.lesson\n\n**Source:** \n- webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge7/Assignment7.java\n\n**Challenge:**\n\nThe purpose of this challenge is to find the synthetic `.git` repository accessible at the `/WebGoat/challenge/7/.git` endpoint. Then extract the git files, decompile classes and run them to generate the password reset link password. \n\n```\nwebgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge7/Assignment7.java#L76-80\n\n@GetMapping(value = \"/challenge/7/.git\", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)\n@ResponseBody\npublic ClassPathResource git() {\n    return new ClassPathResource(\"challenge7/git.zip\");\n}\n\nwebgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/SolutionConstants.java#L34-36\n\nString PASSWORD = \"!!webgoat_admin_1234!!\";\nString PASSWORD_TOM = \"thisisasecretfortomonly\";\nString ADMIN_PASSWORD_LINK = \"375afe1104f4a487a73823c50a9292a2\";\n```\n\n**Can SAST Find?** \n- Possible (different issue)\n\n**Can DAST Find?**\n- Possible (different issue)\n\n**SAST Reasoning:**\n\nThis is a fake vulnerability that hard codes a git index file to a spring `GetMapping` endpoint. However, a SAST tool would most likely flag the hardcoded credentials in `SolutionConstants.java` on line 36.\n\n**DAST Reasoning:**\n\nDAST tools commonly look for backup or known files, `.git` is usually ne of them. A DAST tool would attempt to find these files in each directory path and should report that the `/WebGoat/challenge/7/.git` git index is accessible. While it may attempt to decompile classes it would be unable to know that it's necessary to run a particular file.\n\n---\n\n### Challenges > Without account\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/Challenge8.lesson\n\n**Source:** \n- webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge8/Assignment8.java\n\n**Challenge:**\n\nThe purpose of this challenge is to add a vote without logging in. This is a synthetic vulnerability due to the fact that it only checks if the request is a `GET` request but there's no real authorization checks. It is \"exploitable\" because it does not account for `HEAD` request method types.\n\n```\n\nwebgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge7/Assignment7.java#L76-80\n\n@GetMapping(value = \"/challenge/8/vote/{stars}\", produces = MediaType.APPLICATION_JSON_VALUE)\n    @ResponseBody\n    public ResponseEntity\u003C?> vote(@PathVariable(value = \"stars\") int nrOfStars, HttpServletRequest request) {\n        //Simple implementation of VERB Based Authentication\n        String msg = \"\";\n        if (request.getMethod().equals(\"GET\")) {\n            var json = Map.of(\"error\", true, \"message\", \"Sorry but you need to login first in order to vote\");\n            return ResponseEntity.status(200).body(json);\n        }\n        Integer allVotesForStar = votes.getOrDefault(nrOfStars, 0);\n        votes.put(nrOfStars, allVotesForStar + 1);\n        return ResponseEntity.ok().header(\"X-Flag\", \"Thanks for voting, your flag is: \" + Flag.FLAGS.get(8)).build();\n    }\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nSince this is a synthetic vulnerability with no references to any authorization frameworks, there is nothing for a SAST tool to look for.\n\n**DAST Reasoning:**\n\nA DAST tool may attempt to switch request method types and do differential analysis to see if a `HEAD` request illicit a different response than a `GET` request.\n\n---\n\n\u003C/details>\n\n## Flaws outside of lessons\n\nThere are a large number of flaws that are not necessarily part of the lesson. However, SAST and DAST tools may still report on these issues as they are exploitable. \n\nSince tools will report on these issues, it is important to have a full set of all actual vulnerabilities that exist in WebGoat, or in any system used for benchmarking. \n\n## Conclusion\n\nWhile in GitLab's proprietary format, we decided to release our results so that other organizations using WebGoat as a target can identify which flaws are legitimate for both [SAST](https://gitlab.com/gitlab-org/vulnerability-research/blog/-/blob/master/security-benchmarking-webgoat/webgoat-expected-sast-results.json) and [DAST](https://gitlab.com/gitlab-org/vulnerability-research/blog/-/blob/master/security-benchmarking-webgoat/webgoat-expected-dast-results.json) based discovery. \n\nWebGoat is an excellent tool for learning about web application security. If your organization decides to use it to compare DAST and SAST tools you must be aware of the limitations and caveats during your analysis. \n\nWebGoat is by no means a \"real application\", while it does contain a common structure of a Spring Boot based application, its flaws are sometimes synthetic and code flow is not indicative of how real applications are built.\n\nGitLab recommends using more than one application as apart of your benchmarking process. This should include multiple languages, features and the levels of complexity that matches the applications used in your organization.\n\nCover image by [Bannon Morrissy](https://unsplash.com/@bannon15) on [Unsplash](https://unsplash.com)",[708,1429,9],"security research",{"slug":1431,"featured":6,"template":686},"how-to-benchmark-security-tools","content:en-us:blog:how-to-benchmark-security-tools.yml","How To Benchmark Security Tools","en-us/blog/how-to-benchmark-security-tools.yml","en-us/blog/how-to-benchmark-security-tools",{"_path":1437,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1438,"content":1444,"config":1450,"_id":1452,"_type":13,"title":1453,"_source":15,"_file":1454,"_stem":1455,"_extension":18},"/en-us/blog/how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd",{"title":1439,"description":1440,"ogTitle":1439,"ogDescription":1440,"noIndex":6,"ogImage":1441,"ogUrl":1442,"ogSiteName":672,"ogType":673,"canonicalUrls":1442,"schema":1443},"How to continuously test web apps and APIs with Hurl and GitLab CI/CD","Hurl as a CLI tool can be integrated into the DevSecOps platform to continuously verify, test, and monitor targets. It also offers integrated unit test reports in GitLab CI/CD.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659883/Blog/Hero%20Images/post-cover-image.jpg","https://about.gitlab.com/blog/how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to continuously test web apps and APIs with Hurl and GitLab CI/CD\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Michael Friedrich\"}],\n        \"datePublished\": \"2022-12-14\",\n      }",{"title":1439,"description":1440,"authors":1445,"heroImage":1441,"date":1447,"body":1448,"category":705,"tags":1449},[1446],"Michael Friedrich","2022-12-14","\nTesting websites, web applications, or generally everything reachable with the HTTP protocol, can be a challenging exercise. Thanks to tools like `curl` and `jq`, [DevOps workflows have become more productive](/blog/devops-workflows-json-format-jq-ci-cd-lint/) and even simple monitoring tasks can be automated with CI/CD pipeline schedules. Sometimes, use cases require specialized tooling with custom HTTP headers, parsing expected responses, and building end-to-end test pipelines. Stressful incidents also need good and fast tools that help analyze the root cause and quickly mitigate and fix problems.\n\n[Hurl](https://hurl.dev) is an open-source project developed and maintained by Orange, and uses libcurl from curl to provide HTTP test capabilities. It aims to tackle complex HTTP test challenges by providing a simple plain text configuration to describe HTTP requests. It can chain requests, capture values, and evaluate queries on headers and body responses. So far, so good: Hurl does not only support fetching data, it can be used to test HTTP sessions and XML (SOAP) and JSON (REST) APIs.\n\n## Getting Started\n\nHurl comes in various package formats to [install](https://hurl.dev/docs/installation.html). On macOS, a Homebrew package is available.\n\n```sh\n$ brew install hurl\n```\n\n## First steps with Hurl\n\nHurl proposes to start with the configuration file format first, which is a great way to learn the syntax step by step. The following example creates a new `gitlab-contribute.hurl` configuration file that will do two things: execute a GET HTTP request on `https://about.gitlab.com/community/contribute/` and check whether its HTTP response contains the HTTP protocol `2` and status code `200` (OK).\n\n```sh\n$ vim gitlab-contribute.hurl\n\nGET https://about.gitlab.com/community/contribute/\n\nHTTP/2 200\n$ hurl --test gitlab-contribute.hurl\ngitlab-contribute.hurl: Running [1/1]\ngitlab-contribute.hurl: Success (1 request(s) in 413 ms)\n--------------------------------------------------------------------------------\nExecuted files:  1\nSucceeded files: 1 (100.0%)\nFailed files:    0 (0.0%)\nDuration:        415 ms\n```\n\nInstead of creating configuration files, you can also use the `echo “...” | hurl` command pattern. The following command tests against about.gitlab.com and checks whether the HTTP response protocol is 1.1 and the status is OK (200). The two newline characters `\\n` are required for separation.\n\n```sh\n$ echo \"GET https://about.gitlab.com\\n\\nHTTP/1.1 200\" | hurl --test\n```\n\n![hurl CLI run against about.gitlab.com, failed request](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_assert_failure.png)\n\nThe command failed, and it says that the response protocol version is actually `2`. Let's adjust the test run to expect `HTTP/2`:\n\n```sh\necho \"GET https://about.gitlab.com\\n\\nHTTP/2 200\" | hurl --test\n```\n## Asserting HTTP responses\n\nHurl allows defining [assertions](https://hurl.dev/docs/asserting-response.html) to control when the tests fail. These can be defined for different HTTP response types:\n\n- Expected HTTP protocol version and status\n- Headers\n- Body\n\nThe configuration language allows users to define queries with predicates that allow to compare, chain, and execute different assertions.\n\nThis is the easiest way to verify that the HTTP response contains what is expected to be a string or sentence on the website, for example. If the string does not exist, this can indicate that it was changed unexpectedly, or that the website is down. Let's revisit the example with testing GET https://about.gitlab.com/community/contribute/ and add an expected string `Everyone can contribute` as a new assertion, `body contains \u003Cstring>` is the expected configuration syntax for [body asserts](https://hurl.dev/docs/asserting-response.html#body-assert).\n\n```sh\n$ vim gitlab-contribute.hurl\n\nGET https://about.gitlab.com/community/contribute/\n\nHTTP/2 200\n\n[Asserts]\nbody contains \"Everyone should contribute\"\n\n$ hurl --test gitlab-contribute.hurl\n```\n\n**Exercise:** Fix the test by updating the asserts line to `Everyone can contribute` and run Hurl again.\n\n### Asserting responses: JSON and XML\n\n[JSONPath](https://hurl.dev/docs/asserting-response.html#jsonpath-assert) automatically parses the JSON response (a built-in `jq with curl` parser so to speak), and allows users to compare the value to verify the asserts (more below). The XML format can be found in an [RSS feed on about.gitlab.com](https://about.gitlab.com/atom.xml) and parsed using [XPath](https://hurl.dev/docs/asserting-response.html#xpath-assert). The following example from `atom.xml` should be verified with Hurl:\n\n```xml\n\u003Cfeed xmlns=\"http://www.w3.org/2005/Atom\">\n\u003Ctitle>GitLab\u003C/title>\n\u003Cid>https://about.gitlab.com/blog\u003C/id>\n\u003Clink href=\"https://about.gitlab.com/blog/\"/>\n\u003Cupdated>2022-11-21T00:00:00+00:00\u003C/updated>\n\u003Cauthor>\n\u003Cname>The GitLab Team\u003C/name>\n\u003C/author>\n\u003Centry>\n...\n\u003C/entry>\n\u003Centry>\n...\n\u003C/entry>\n\u003Centry>\n…\n```\n\nIt is important to note that XML namespaces need to be specified for parsing. Hurl allows users to replace the first default namespace with the `_` character to avoid adding `http://www.w3.org/2005/Atom` everywhere, the XPath is now shorter with `string(//_:feed/_:entry)` to get a list of all entries. This value is captured in the `entries` variable, which can be compared to match a specific string, `GitLab` in this example. Additionally, the feed id and author name is checked.\n\n```\n$ vim gitlab-rss.hurl\n\nGET https://about.gitlab.com/atom.xml\n\nHTTP/2 200\n\n[Captures]\nentries: xpath \"string(//_:feed/_:entry)\"\n\n[Asserts]\nvariable \"entries\" matches \"GitLab\"\n\nxpath \"string(//_:feed/_:id)\" == \"https://about.gitlab.com/blog\"\nxpath \"string(//_:feed/_:author/_:name)\" == \"The GitLab Team\"\n\n$ hurl –test gitlab-rss.hurl\n```\n\nHurl allows users to capture the value from responses into [variables](https://hurl.dev/docs/templates.html#variables) that can be used later. This method can also be helpful to model end-to-end testing workflows: First, check the website health status and retrieve a CSRF token, and then try to log into the website by sending the token again.\n\nREST APIs that are expected to always return a specified field, or monitoring a website health state [becomes a breeze using Hurl](https://hurl.dev/docs/tutorial/chaining-requests.html#test-rest-api).\n\n## Use Hurl in GitLab CI/CD jobs\n\nThe easiest way to integrate Hurl into GitLab CI/CD is to use the official container image. The Hurl project provides a [container image on Docker Hub](https://hub.docker.com/r/orangeopensource/hurl), which did not work in CI/CD at first glance. After talking with the maintainers, the [entrypoint override](https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#override-the-entrypoint-of-an-image) was identified as a solution for using the image in GitLab CI/CD. Note that the Alpine based image uses the libcurl library that does not support HTTP/2 yet - the test results are different to a Debian base image (follow [this issue report](https://github.com/Orange-OpenSource/hurl/issues/1082) for the problem analysis).\n\nThe following example is kept short to run the container image, override the entrypoint, and run Hurl with passing in the test using the `echo` CLI command.\n\n```yaml\nhurl-standalone:\n  image:\n    name: ghcr.io/orange-opensource/hurl:latest\n    entrypoint: [\"\"]\n  script:\n    - echo -e \"GET https://about.gitlab.com/community/contribute/\\n\\nHTTP/1.1 200\" | hurl --test --color\n```\n\nThe Hurl test report is printed into the CI/CD job trace log, and returns succesfully.\n\n```sh\n$ echo -e \"GET https://about.gitlab.com/community/contribute/\\n\\nHTTP/1.1 200\" | hurl --test --color\n-: Running [1/1]\n-: Success (1 request(s) in 280 ms)\n--------------------------------------------------------------------------------\nExecuted files:  1\nSucceeded files: 1 (100.0%)\nFailed files:    0 (0.0%)\nDuration:        283 ms\nCleaning up project directory and file based variables\n00:00\nJob succeeded\n```\n\nThe next iteration is to create a CI/CD job template that provides generic attributes, and allows users to dynamically run the job with an environment variable called `HURL_URL`.\n\n```yaml\n# Hurl job template\n.hurl-tmpl:\n  # Use the upstream container image and override the ENTRYPOINT to run CI/CD script\n  # https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#override-the-entrypoint-of-an-image\n  image:\n    name: ghcr.io/orange-opensource/hurl:1.8.0\n    entrypoint: [\"\"]\n  variables:\n    HURL_URL: \"about.gitlab.com/community/contribute/\"\n  script:\n    - echo -e \"GET https://${HURL_URL}\\n\\nHTTP/1.1 200\" | hurl --test --color\n\nhurl-about-gitlab-com:\n  extends: .hurl-tmpl\n  variables:\n    HURL_URL: \"about.gitlab.com/jobs/\"\n```\n\nRunning GET commands with expected HTTP results is not the only use case, and the Hurl maintainers thought about this already. The next section explains how to create a custom container image; you can skip to the [DevSecOps workflows](#devSecOps-workflows-with-hurl) section to learn more about efficient Hurl configuration use cases.\n\n### Custom container image with Hurl\n\nMaintaining and building a custom container image adds more work, but also helps with avoiding running unknown container images in CI/CD pipelines. The latter is often a requirement for compliance and security. _Since the Hurl Debian package supports detecting HTTP/2 as a protocol, this blog post will focus on building a custom image, and run all tests using this image. If you plan on using the upstream container image, make sure to review the test configuration for the HTTP protocol version detection._\n\nThe Hurl documentation provides multiple ways to install Hurl. For this example, Debian 11 Bullseye (slim) is used. Hurl comes with a package dependency on `libxml2` which can either be installed manually with then running the `dpkg` command, or by using `apt install` to install a local package and automatically resolve the dependencies.\n\nThe following CI/CD example uses a job template which defines the Hurl version as environment variable to avoid repetitive use, and downloads and installs the Hurl Debian package. The `hurl-gitlab-com` job extends the CI/CD job template and runs a one-line test against `https://gitlab.com` and expects to return `HTTP/2` as HTTP protocol version, and `200` as status.\n\n```yaml\n# CI/CD job template\n.hurl-tmpl:\n  variables:\n    HURL_VERSION: 1.8.0\n  before_script:\n    - DEBIAN_FRONTEND=noninteractive apt update && apt -y install jq curl ca-certificates\n    - curl -LO \"https://github.com/Orange-OpenSource/hurl/releases/download/${HURL_VERSION}/hurl_${HURL_VERSION}_amd64.deb\"\n    - DEBIAN_FRONTEND=noninteractive apt -y install \"./hurl_${HURL_VERSION}_amd64.deb\"\n\nhurl-gitlab-com:\n  extends: .hurl-tmpl\n  script:\n    - echo -e \"GET https://gitlab.com\\n\\nHTTP/2 200\" | hurl --test --color\n```\n\nThe next section describes how to optimize the CI/CD pipelines for more efficient schedules and runs to monitor websites and not waste too many resources and CI/CD minutes. You can also skip it and [scroll down to more advanced Hurl examples in GitLab CI/CD](#devsecops-workflows-with-hurl).\n\n### CI/CD efficiency: Hurl container image\n\nThe installation steps for Hurl, and its dependencies, can waste resources and increase the pipeline job runtime every time. To make the CI/CD pipelines more efficient, we want to use a container image that already provides Hurl pre-installed. The following steps are required for creating a container image:\n\n- Use Debian 11 Slim (FROM).\n- Install dependencies to download Hurl (`curl`, `ca-certificates`). `jq` is installed for convenience to access it from CI/CD commands when needed later.\n- Download the Hurl Debian package, and use `apt install` to install its dependencies automatically.\n- Clear the apt lists cache to enforce apt update again, and avoid security issues.\n- Hurl is installed into the PATH, specify the default command being run. This allows running the container without having to specify a command.\n\nThe steps to install the packages are separated for better readability; an optimization for the `docker-build` job can happen by chaining the `RUN` commands into one long command.\n\n`Dockerfile`\n```\nFROM debian:11-slim\n\nENV DEBIAN_FRONTEND noninteractive\n\nARG HURL_VERSION=1.8.0\n\nRUN apt update && apt install -y curl jq ca-certificates\nRUN curl -LO \"https://github.com/Orange-OpenSource/hurl/releases/download/${HURL_VERSION}/hurl_${HURL_VERSION}_amd64.deb\"\n# Use apt install to determine package dependencies instead of dpkg\nRUN apt -y install \"./hurl_${HURL_VERSION}_amd64.deb\"\nRUN rm -rf /var/lib/apt/lists/*\n\nCMD [\"hurl\"]\n```\n\nNote that the `HURL_VERSION` variable can be overridden by passing the variable and value into the container build job later. It is intentionally not using an automated script that always uses the [latest release](https://github.com/Orange-OpenSource/hurl/releases) to avoid breaking the behavior, and enforces a controlled upgrade cycle for container images in production.\n\nOn GitLab.com SaaS, you can include the `Docker.gitlab-ci.yml` CI/CD template which will automatically detect the `Dockerfile` file and start building the image using the shared runners, and push it to the [GitLab container registry](https://docs.gitlab.com/ee/user/packages/container_registry/). For self-managed instances or own runners on GitLab.com SaaS, it is recommended to decide whether to use and setup [Docker-in-Docker](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html) or [Kaniko](https://docs.gitlab.com/ee/ci/docker/using_kaniko.html), Podman, or other container image build tools.\n\n```yaml\ninclude:\n  - template: Docker.gitlab-ci.yml\n```\n\nTo avoid running the Docker image build job every time, the job override definition specifies to [run it manually](https://docs.gitlab.com/ee/ci/yaml/#when). You can also use rules to [choose when to run the job](https://docs.gitlab.com/ee/ci/jobs/job_control.html), only when a Git tag is pushed for example.\n\n```yaml\ninclude:\n  - template: Docker.gitlab-ci.yml\n\n# Change Docker build to manual non-blocking\ndocker-build:\n  rules:\n    - if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n      when: manual\n      allow_failure: true\n```\n\nOnce the container image is pushed to the registry, navigate into `Packages and Registries > Container Registries` and inspect the tagged image. Copy the image path for the latest tagged version and use it for the `image` attribute in the CI/CD job configuration.\n\n### Hurl container image in GitLab CI/CD example\n\nThe full example uses the previously built container image, and specifies the default `HURL_URL` variable. This can later be overridden by job definitions.\n\n_Please note that the image URL `registry.gitlab.com/everyonecancontribute/dev/hurl-playground:latest` is only used for demo purposes and not actively maintained or updated._\n\n```yaml\ninclude:\n  - template: Docker.gitlab-ci.yml\n\n# Change Docker build to manual non-blocking\ndocker-build:\n  rules:\n    - if: '$CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'\n      when: manual\n      allow_failure: true\n\n# Hurl job template\n.hurl-tmpl:\n  image: registry.gitlab.com/everyonecancontribute/dev/hurl-playground:latest\n  variables:\n    HURL_URL: gitlab.com\n\n# Hurl jobs that check websites\nhurl-dnsmichi-at:\n  extends: .hurl-tmpl\n  variables:\n    HURL_URL: dnsmichi.at\n  script:\n    - echo -e \"GET https://${HURL_URL}\\n\\nHTTP/1.1 200\" | hurl --test --color\n\nhurl-opsindev-news:\n  extends: .hurl-tmpl\n  variables:\n    HURL_URL: opsindev.news\n  script:\n    - echo -e \"GET https://${HURL_URL}\\n\\nHTTP/2 200\" | hurl --test --color\n```\n\nThe CI/CD configuration can further be optimized:\n\n- Create job templates that execute the same scripts and only differ in the `HURL_URL` variable.\n- Use Hurl configuration files that allow specifying variables on the CLI or as environment variables. More on this in the next section.\n\n## DevSecOps workflows with Hurl\n\nHurl allows users to describe HTTP instructions in a configuration file with the `.hurl` suffix. You can add the configuration files to Git, and review and approve changes in merge requests - with the changes run in CI/CD and reporting back any failures before merging.\n\nInspect the `use-cases/` directory in the [example project](https://gitlab.com/everyonecancontribute/dev/hurl-playground), and fork it to make changes and commit and run the CI/CD pipelines and reports. You can also clone the project and run the `tree` command in the terminal.\n\n```sh\n$ tree use-cases\nuse-cases\n├── dnsmichi.at.hurl\n├── gitlab-com-api.hurl\n├── gitlab-contribute.hurl\n└── hackernews.hurl\n```\n\nHurl supports the glob option which collects all configuration files matching a specific pattern.\n\n![Hurl configuration file run](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_multiple_config_files_run.png)\n\n### Chaining requests\n\nSimilar to CI/CD pipelines, jobs, and stages, testing HTTP endpoints with Hurl can require multiple steps. First, ping the website for being reachable, and then try parsing expected results. Separating the requirements into two steps helps to analyze errors.\n\n- HTTP endpoint reachable, but expected string not in response - static website was changed, REST API misses a field, etc.\n- HTTP endpoint is unreachable, don’t try to understand why the follow-up tests fail.\n\nThe following example first sends a ping probe to the dev instance, and a check towards the production environment in the second request.\n\n```sh\n$ vim use-cases/everyonecancontribute-com.hurl\n\nGET https://everyonecancontribute.dev\n\nHTTP/2 200\n\nGET https://everyonecancontribute.com\n\nHTTP/2 200\n$ hurl --test use-cases/everyonecancontribute-com.hurl\n```\n\nIn this scenario, the TLS certificate of the dev instance expired, and Hurl halts the test immediately.\n\n![Hurl chained requests, failing the first test with TLS certificate problems](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_chained_request_fail.png)\n\n### Hurl reports as JUnit test reports\n\nTreat website monitoring and web app tests as unit and end-to-end tests. The Hurl developers thought of that too - the CLI command provides different output options for the report: `--report-junit \u003Coutputpath>` integrates with [GitLab JUnit report](https://docs.gitlab.com/ee/ci/testing/unit_test_reports.html) support into merge requests and pipeline views.\n\nThe following configuration generates a JUnit report file into the value of the `HURL_JUNIT_REPORT` variable. It exists to avoid typing the path three times. The Hurl tests are run from the `use-cases/` directory using a glob pattern.\n\n```yaml\n# Hurl job template\n.hurl-tmpl:\n    image: registry.gitlab.com/everyonecancontribute/dev/hurl-playground:latest\n    variables:\n        HURL_URL: gitlab.com\n        HURL_JUNIT_REPORT: hurl_junit_report.xml\n\n# Hurl tests from configuration file, generating JUnit report integration in GitLab CI/CD\nhurl-report:\n    extends: .hurl-tmpl\n    script:\n      - hurl --test use-cases/*.hurl --report-junit $HURL_JUNIT_REPORT\n    after_script:\n      # Hack: Workaround for 'id' instead of 'name' in JUnit report from Hurl. https://gitlab.com/gitlab-org/gitlab/-/issues/299086\n      - sed -i 's/id/name/g' $HURL_JUNIT_REPORT\n    artifacts:\n      when: always\n      paths:\n        - $HURL_JUNIT_REPORT\n      reports:\n        junit: $HURL_JUNIT_REPORT\n```\n\nThe JUnit format returned by Hurl 1.8.0 defines the `id` attribute, but the GitLab JUnit integration expects the `name` attribute to be present. While writing this blog post, [the problem was discussed](https://github.com/Orange-OpenSource/hurl/issues/1067#issuecomment-1343264751) with the maintainers, and [the `name` attribute was implemented](https://github.com/Orange-OpenSource/hurl/issues/1078) and will be available in future releases. As a workaround with Hurl 1.8.0, the CI/CD [after_script](https://docs.gitlab.com/ee/ci/yaml/#after_script) section uses `sed` to replace the attributes after generating the report.\n\nThe [following example](https://gitlab.com/everyonecancontribute/dev/hurl-playground/-/merge_requests/10) fails on purpose with checking a different HTTP protocol version.\n\n```\nGET https://opsindev.news\n\n# This will fail on purpose\nHTTP/1.1 200\n\n[Asserts]\nbody contains \"Michael Friedrich\"\n```\n\n![Hurl test report in JUnit format integrated into GitLab](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_gitlab_junit_integration_merge_request_widget_overlay.png)\n\nOnce the JUnit integration with Hurl tests from a glob pattern work, you can continue adding new `.hurl` configuration files to the GitLab repository and start testing in MRs, which will require review and approval workflows for production then.\n\n### Web review apps\n\nWebsite monitoring is only one aspect of using Hurl: Testing web applications deployed in review environments in the cloud, and in cloud-native clusters provides a native integration into [DevSecOps](https://about.gitlab.com/topics/devsecops/) workflows. The CI/CD pipelines will fail when Hurl tests are failing, and more insights are provided using merge request widgets reports.\n\n[Cloud Seed](https://docs.gitlab.com/ee/cloud_seed/index.html) provides the ability to deploy a web application to a major cloud provider, for example Google Cloud. After the deployment is successful, additional CI/CD jobs can be configured that verify that the deployed web app version does not introduce a regression, and provides all required data elements, API endpoints, etc. A similar workflow can be achieved by using review app environments with [webservers (Nginx, etc.), Docker, AWS, and Kubernetes](https://docs.gitlab.com/ee/ci/review_apps/#review-apps-examples). The review app [environment URL](https://docs.gitlab.com/ee/ci/environments/#create-a-dynamic-environment) is important for instrumenting the Hurl tests dynamically. The CI/CD variable [`CI_ENVIRONMENT_URL`](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html) is available when `environment:url` is specified in the review app configuration.\n\nThe following example tests the review app for [this blog post when written in a merge request](https://gitlab.com/gitlab-com/www-gitlab-com/-/merge_requests/115548):\n\n```yaml\n# Test review apps with hurl for this blog post.\nhurl-review-test:\n  extends: .review-environment # inherits the environment settings\n  needs: [uncategorized-build-and-review-deploy] # waits until the website (sites/uncategorized) is deployed\n  stage: test\n  rules: # YAML anchor that runs the job only on merge requests\n    - \u003C\u003C: *if-merge-request-original-repo\n  image:\n    name: ghcr.io/orange-opensource/hurl:1.8.0\n    entrypoint: [\"\"]\n  script:\n    - echo -e \"GET ${CI_ENVIRONMENT_URL}\\n\\nHTTP/1.1 200\" | hurl --test --color\n```\n\nThe environment is specified in the [.review-environment job template](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/91d6fd72a424a3d913e79ebc2aefb23bbab85863/.gitlab-ci.yml#L332) and used to [deploy the website review job](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/91d6fd72a424a3d913e79ebc2aefb23bbab85863/.gitlab-ci.yml#L532). The relevant configuration snippet is shown here:\n\n```yaml\n.review-environment:\n  variables:\n    DEPLOY_TYPE: review\n  environment:\n    name: review/$CI_COMMIT_REF_SLUG\n    url: https://$CI_COMMIT_REF_SLUG.about.gitlab-review.app\n    on_stop: review-stop\n    auto_stop_in: 30 days\n```\n\nThe deployment of the www-gitlab-com project [uses buckets in Google Cloud](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/91d6fd72a424a3d913e79ebc2aefb23bbab85863/scripts/deploy) that serve the website content in the review app. There are different types of web applications that require different deployment methods - as long as the environment URL variable is available in CI/CD and the deployment URL is accessible from the GitLab Runner executing the CI/CD job, you can continously test web apps with Hurl!\n\n![Hurl test in GitLab CI/CD for review app environments](https://about.gitlab.com/images/blogimages/hurl-continuous-website-testing/hurl_gitlab_cicd_review_app_environment_tests_www-gitlab-com.png)\n\n## Development tips\n\nUse the [`--verbose` parameter](https://hurl.dev/docs/tutorial/debug-tips.html) to see the full request and response flow. Hurl also provides tips which `curl` command could be run to fetch more data. This can be helpful when starting to use or develop a new REST API, or learning to understand the JSON structure of HTTP responses. Chaining the `curl` command with `jq` (the `curl ... | jq` pattern) can still be helpful to fetch data, and build the HTTP tests in a second terminal or editor window.\n\n```sh\n$ curl -s 'https://gitlab.com/api/v4/projects' | jq\n$ curl -s 'https://gitlab.com/api/v4/projects' | jq -c '.[]' | jq\n\n{\"id\":41375401,\"description\":\"An example project for a GitLab pipeline.\",\"name\":\"Calculator\",\"name_with_namespace\":\"Iva Tee / Calculator\",\"path\":\"calculator\",\"path_with_namespace\":\"snufkins_hat/calculator\",\"created_at\":\"2022-11-26T00:32:33.825Z\",\"default_branch\":\"master\",\"tag_list\":[],\"topics\":[],\"ssh_url_to_repo\":\"git@gitlab.com:snufkins_hat/calculator.git\",\"http_url_to_repo\":\"https://gitlab.com/snufkins_hat/calculator.git\",\"web_url\":\"https://gitlab.com/snufkins_hat/calculator\",\"readme_url\":\"https://gitlab.com/snufkins_hat/calculator/-/blob/master/README.md\",\"avatar_url\":null,\"forks_count\":0,\"star_count\":0,\"last_activity_at\":\"2022-11-26T00:32:33.825Z\",\"namespace\":{\"id\":58849237,\"name\":\"Iva Tee\",\"path\":\"snufkins_hat\",\"kind\":\"user\",\"full_path\":\"snufkins_hat\",\"parent_id\":null,\"avatar_url\":\"https://secure.gravatar.com/avatar/a3efe834950275380d5f19c9b17c922c?s=80&d=identicon\",\"web_url\":\"https://gitlab.com/snufkins_hat\"}}\n```\n\nThe GitLab projects API returns an array of elements, where we can inspect the `id` and `name` attributes for a simple test - the first element’s name must not be empty, the second element’s id needs to be greater than 0.\n\n```sh\n$ vim gitlab-com-api.hurl\n\nGET https://gitlab.com/api/v4/projects\n\nHTTP/2 200\n\n[Asserts]\njsonpath \"$[0].name\" != \"\"\njsonpath \"$[1].id\" > 0\n\n$ hurl --test gitlab-com-api.hurl\n\ngitlab-com-api.hurl: Running [1/1]\ngitlab-com-api.hurl: Success (1 request(s) in 728 ms)\n--------------------------------------------------------------------------------\nExecuted files:  1\nSucceeded files: 1 (100.0%)\nFailed files:    0 (0.0%)\nDuration:        730 ms\n```\n\n## More use cases\n\n- Work with HTTP sessions and [cookies](https://hurl.dev/docs/request.html#cookies), test [forms with parameters](https://hurl.dev/docs/request.html#form-parameters).\n- Review existing API tests of your applications.\n- Build advanced chained workflows with GET, POST, PUT, DELETE, and more HTTP methods.\n- Integrate simple ping/HTTP monitoring health checks into the DevSecOps Platform using alerts and incident management.\n\nIf the Hurl checks cannot be integrated directly inside the project where the application is developed and deployed, another idea could be to create a standalone GitLab project that has CI/CD pipeline schedules enabled. It can continuously run the Hurl tests, and parse the reports or trigger an event when the pipeline is failing, and [create an alert](https://docs.gitlab.com/ee/operations/incident_management/alerts.html) by sending a JSON payload from the Hurl results to the [HTTP endpoint](https://docs.gitlab.com/ee/operations/incident_management/integrations.html#single-http-endpoint). Developers can send MRs to update the Hurl tests, and maintainers review and approve the new test suites being rolled out into production. Alternatively, move the complete CI/CD configuration into a group/project with different permissions, and specify the CI/CD configuration as remote URL in the web application project. This compliance level helps to control who can make changes to important tests and CI/CD configuration.\n\nHurl supports `--json` as parameter to only return the JSON formatted test result and build own custom reports and integrations.\n\n```sh\n$ echo -e \"GET https://about.gitlab.com/teamops/\\n\\nHTTP/2 200\" | hurl --json | jq\n```\n\nFor folks in DevRel, monitoring certain websites for keywords or checking APIs whether values increase a certain threshold can be interesting. Here is an example for monitoring Hacker News using the Algolia search API, inspired by the [Zapier integration used for GitLab Slack](/handbook/marketing/developer-relations/workflows-tools/zapier/#zaps-for-hacker-news). The `QueryStringParams` section allows users to define the query parameters as a readable list, which is easier to modify. The `jsonpath` checks searches for the `hits` key and its count being zero (not on the Hacker News front page means OK in this example).\n\n```\n$ vim hackernews.hurl\n\nGET https://hn.algolia.com/api/v1/search\n[QueryStringParams]\nquery: gitlab\n#query: hurl\ntags: front_page\n\nHTTP/2 200\n\n[Asserts]\njsonpath \"$.hits\" count == 0\n\n$ hurl --test hackernews.hurl\n```\n\n## Limitations\n\nHurl works great for testing websites and web applications that serve static content, and by sending different HTTP request types, data, etc., and ensuring that responses match expectations. Compared to other end-to-end testing solutions (Selenium, etc.), Hurl does not provide a JavaScript engine and only can parse the raw DOM or JSON response. It does not support a DOM managed and rendered by JavaScript front-end frameworks. UI integration tests also need to be performed with different tools, similar to full end-to-end test workflows. Other examples are [accessibility testing](https://docs.gitlab.com/ee/ci/testing/accessibility_testing.html) and [browser performance testing](https://docs.gitlab.com/ee/ci/testing/browser_performance_testing.html). If you are curious how end-to-end testing is done for GitLab, the product, peek into the [development documentation](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/).\n\n## Conclusion\n\nHurl provides an easy way to test HTTP endpoints (such as websites and APIs) in a fast and reliable way. The CLI commands can be integrated into CI/CD workflows, and the configuration syntax and files provide a single source of truth for everything. Additional support for JUnit report formats ensure that website testing is fully integrated into the [DevSecOps](https://about.gitlab.com/topics/devsecops/) platform, and increases visibility and extensibility with automating tests, and monitoring. There are known limitations with dynamic JavaScript websites and advanced UI/end-to-end testing workflows.\n\nHurl is open source, [created and maintained by Orange](https://opensource.orange.com/en/open-source-orange/), and written in Rust. This blog post inspired contributions to the [Debian/Ubuntu installation documentation](https://github.com/Orange-OpenSource/hurl/pull/1084) and [default issue templates](https://github.com/Orange-OpenSource/hurl/pull/1083).\n\n**Tip:** Practice using Hurl on the command line, and remember it when the next production incident shows a strange API behavior with POST requests.\n\nThanks to [Lee Tickett](/company/team/#leetickett-gitlab) who inspired me to test Hurl in GitLab CI/CD and write this blog post after seeing huge interest in a [Twitter share](https://twitter.com/dnsmichi/status/1595820546062778369).\n\nCover image by [Aaron Burden](https://unsplash.com/@aaronburden) on [Unsplash](https://unsplash.com)\n{: .note}\n",[9,774,682],{"slug":1451,"featured":6,"template":686},"how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd","content:en-us:blog:how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd.yml","How To Continously Test Web Apps Apis With Hurl And Gitlab Ci Cd","en-us/blog/how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd.yml","en-us/blog/how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd",{"_path":1457,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1458,"content":1463,"config":1469,"_id":1471,"_type":13,"title":1472,"_source":15,"_file":1473,"_stem":1474,"_extension":18},"/en-us/blog/how-to-fuzz-go",{"title":1459,"description":1460,"ogTitle":1459,"ogDescription":1460,"noIndex":6,"ogImage":935,"ogUrl":1461,"ogSiteName":672,"ogType":673,"canonicalUrls":1461,"schema":1462},"How to fuzz Go code with go-fuzz continuously","Learn how (and why!) to fuzz Go code","https://about.gitlab.com/blog/how-to-fuzz-go","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to fuzz Go code with go-fuzz continuously\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Yevgeny Pats\"}],\n        \"datePublished\": \"2020-12-03\",\n      }",{"title":1459,"description":1460,"authors":1464,"heroImage":935,"date":1466,"body":1467,"category":729,"tags":1468},[1465],"Yevgeny Pats","2020-12-03","\n\n{::options parse_block_html=\"true\" /}\n\n\n\n## What is fuzzing?\n\nFuzzing or fuzz testing is an automated software technique that involves providing semi-random data as\ninput to the test program in order to uncover bugs and crashes.\n\n## Why fuzz Go Code?\n\n[Golang](https://golang.org/) is a safe language and memory corruption issues are a thing of the past so we don’t need to fuzz our code,\nright? Wrong 😃. Any code, and especially code where stability, quality, and coverage are important, is worth fuzzing.\nFuzzing can uncover logical bugs and denial-of-service  in critical components can lead to security issues as well.\n\nAs a reference to almost infinite amount of bugs found with go-fuzz (only the documented one) you can look [here](https://github.com/dvyukov/go-fuzz#trophies)\n\n## Enter go-fuzz\n\n[go-fuzz](https://github.com/dvyukov/go-fuzz) is the current de-facto standard fuzzer for go and was initially developed by [Dmitry Vyukov](https://twitter.com/dvyukov).\nIt is a coverage guided fuzzer which means it uses coverage instrumentation and feedback to generate test-cases which proved to be very successful both by go-fuzz and originally by fuzzers like AFL.\n\ngo-fuzz algorithm and in general coverage guided fuzzers works as follows:\n\n```\n// pseudo code\nInstrument program for code coverage\nfor {\n  Choose random input from corpus\n  Mutate input\n  Execute input and collect coverage\n  If new coverage/paths are hit add it to corpus (corpus - directory with test-cases)\n}\n```\n\n## Building & Running\nIf you are already familiar with this part you can skip to \"Running go-fuzz from GitLab-CI\" section.\nwe will use [go-fuzzing-example](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example) as a simple example.\nFor the sake of the example we have a simple [function](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example/-/blob/master/parse_complex.go) with an off-by-one bug:\n\n```go\npackage parser\n\nfunc ParseComplex(data [] byte) bool {\n\tif len(data) == 5 {\n\t\tif data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z' && data[4] == 'I' && data[5] == 'T' {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n```\n\nOur fuzz [function](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example/-/blob/master/parse_complex_fuzz.go) will look like this and will be called by go-fuzz in a infinite loop with the generated data according to the coverage-guided algorithm\n\n```\n// +build gofuzz\n\npackage parser\n\nfunc Fuzz(data []byte) int {\n\tParseComplex(data)\n\treturn 0\n}\n```\n\nTo run the fuzzer we need to build an instrumented version of the code together with the fuzz function.\nThis is done with the following simple steps:\n\n```\ndocker run -it golang /bin/bash\n\n# Download this example\ngo get gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example\ncd /go/src/gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example\n\n# download go-fuzz and clang (libfuzzer)\ngo get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build\napt update && apt install -y clang\n\n# building instrumented version of the code together with libFuzzer integration\ngo-fuzz-build -libfuzzer -o parse-complex.a .\nclang -fsanitize=fuzzer parse-complex.a -o parse-complex\n\n./parse-complex\n\n#490479 NEW    ft: 11 corp: 7/37b lim: 477 exec/s: 11962 rss: 25Mb L: 6/6 MS: 1 ChangeByte-\n#524288 pulse  ft: 11 corp: 7/37b lim: 509 exec/s: 11915 rss: 25Mb\n#1048576        pulse  ft: 11 corp: 7/37b lim: 1030 exec/s: 11915 rss: 25Mb\npanic: runtime error: index out of range [6] with length 6\n\ngoroutine 17 [running, locked to thread]:\ngitlab.com/fuzzing-examples/example-go.ParseComplex.func6(...)\n        /go/src/gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example/parse_complex.go:5\ngitlab.com/fuzzing-examples/example-go.ParseComplex(0x36f1cd0, 0x6, 0x6, 0x7ffeaa0d1f80)\n        /go/src/gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example/parse_complex.go:5 +0x1b8\ngitlab.com/fuzzing-examples/example-go.Fuzz(...)\n        /go/src/gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example/parse_complex_fuzz.go:6\nmain.LLVMFuzzerTestOneInput(0x36f1cd0, 0x6, 0x18)\n        gitlab.com/fuzzing-examples/example-go/go.fuzz.main/main.go:35 +0x85\nmain._cgoexpwrap_98ba7f745c88_LLVMFuzzerTestOneInput(0x36f1cd0, 0x6, 0x5a4d80)\n        _cgo_gotypes.go:64 +0x37\n==1664== ERROR: libFuzzer: deadly signal\n    #0 0x450ddf in __sanitizer_print_stack_trace (/go/src/gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example/parse-complex+0x450ddf)\n    #1 0x430f4b in fuzzer::PrintStackTrace() (/go/src/gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example/parse-complex+0x430f4b)\n    #2 0x414b7b in fuzzer::Fuzzer::CrashCallback() (/go/src/gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example/parse-complex+0x414b7b)\n    #3 0x414b3f in fuzzer::Fuzzer::StaticCrashSignalCallback() (/go/src/gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/go-fuzzing-example/parse-complex+0x414b3f)\n    #4 0x7f57c561d72f  (/lib/x86_64-linux-gnu/libpthread.so.0+0x1272f)\n    #5 0x4b3a00 in runtime.raise runtime/sys_linux_amd64.s:164\n\nNOTE: libFuzzer has rudimentary signal handlers.\n      Combine libFuzzer with AddressSanitizer or similar for better crash reports.\nSUMMARY: libFuzzer: deadly signal\nMS: 1 ChangeByte-; base unit: eef4acc7500228bd0f65760be21896f230e0e39f\n0x46,0x55,0x5a,0x5a,0x49,0x4e,\nFUZZIN\nartifact_prefix='./'; Test unit written to ./crash-14b5f09dd74fe15430d803af773ba09a0524670d\nBase64: RlVaWklO\n```\n\nThis finds the bug in a few seconds, prints the “FUZZI” string that triggers the vulnerability, and saves the crash to a file.\n\n## Running go-fuzz from Gitlab-CI\nThe best way to integrate go-fuzz fuzzing with Gitlab CI/CD is by adding additional stage & step to your `.gitlab-ci.yml`.\nIt is straightforward and [fully documented](https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/#configuration).\n\n```\ninclude:\n  - template: Coverage-Fuzzing.gitlab-ci.yml\n\nfuzz_test_parse_complex:\n    extends: .fuzz_base\n    image: golang\n    script:\n        - go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build\n        - apt update && apt install -y clang\n        - go-fuzz-build -libfuzzer -o parse-complex.a .\n        - clang -fsanitize=fuzzer parse-complex.a -o parse-complex\n        - ./gl-fuzz run --regression=$REGRESSION -- ./parse-complex\n\n```\n\nFor each fuzz target you will will have to create a step which extends the `.fuzz_base` template that runs the following:\n\n- Builds the fuzz target.\n- Runs the fuzz target via gl-fuzz CLI.\n- For `$CI_DEFAULT_BRANCH` (can be override by `$COV_FUZZING_BRANCH`) will run fully fledged fuzzing sessions.\nFor everything else including MRs will run fuzzing regression with the accumlated corpus and fixed crashes.\n\nThis will run your fuzz tests in a blocking manner inside your pipeline. There is also a possability to run longer fuzz sessions asynchronously described in the [docs](https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/#continuous-fuzzing-long-running-async-fuzzing-jobs)\n\nCheck out our [full documentation](https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/) and the [example repo](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing/rust-fuzzing-example)\nand try adding fuzz testing to your own repos!\n",[881,708,9],{"slug":1470,"featured":6,"template":686},"how-to-fuzz-go","content:en-us:blog:how-to-fuzz-go.yml","How To Fuzz Go","en-us/blog/how-to-fuzz-go.yml","en-us/blog/how-to-fuzz-go",{"_path":1476,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1477,"content":1483,"config":1488,"_id":1490,"_type":13,"title":1491,"_source":15,"_file":1492,"_stem":1493,"_extension":18},"/en-us/blog/how-to-integrate-custom-security-scanners-into-gitlab",{"title":1478,"description":1479,"ogTitle":1478,"ogDescription":1479,"noIndex":6,"ogImage":1480,"ogUrl":1481,"ogSiteName":672,"ogType":673,"canonicalUrls":1481,"schema":1482},"How to integrate custom security scanners into GitLab","Learn how to extend the DevSecOps platform by adding custom security scanners to your workflows (includes an easy-to-follow tutorial).","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097082/Blog/Hero%20Images/Blog/Hero%20Images/securitycheck_securitycheck.png_1750097081856.png","https://about.gitlab.com/blog/how-to-integrate-custom-security-scanners-into-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to integrate custom security scanners into GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Fernando Diaz\"}],\n        \"datePublished\": \"2024-02-27\",\n      }",{"title":1478,"description":1479,"authors":1484,"heroImage":1480,"date":1485,"body":1486,"category":708,"tags":1487},[962],"2024-02-27","GitLab, the most comprehensive DevSecOps platform, has everything you need to plan, manage, build, deploy, secure, govern, and monitor your applications. However, there are instances where you may want to extend GitLab with third-party or custom tools. For example, you might need to migrate to a DevSecOps platform from separate solutions, evaluate third-party tools, or integrate proprietary or custom-built solutions into GitLab.\n\nHere's what is covered:\n- [GitLab DevSecOps platform extensibility](#gitlab-devsecops-platform-extensibility)\n- [GitLab security scanner integration](#gitlab-security-scanner-integration)\n  - [Merge request security widget](#merge-request-security-widget)\n  - [Pipeline Security section](#pipeline-security-section)\n  - [Vulnerability Report](#vulnerability-report)\n  - [Vulnerability pages](#vulnerability-pages)\n  - [Security dashboard](#security-dashboard)\n  - [Scan Result Policy integration](#scan-result-policy-integration)\n- [Tutorial: Integrating custom security scanners](#tutorial-integrating-custom-security-scanners)\n  - [Creating a custom security scanner](#creating-a-custom-security-scanner)\n  - [Integrating a custom security scanner with GitLab](#integrating-a-custom-security-scanner-with-gitlab)\n\n## GitLab DevSecOps platform extensibility\n\nGitLab can be extended in many ways to support enhanced functionality that your organization may require. Some common examples of these integrations include:\n\n- external application integrations such as Jenkins and Slack\n- external issue tracking integrations such as Bugzilla and Jira\n- external authentication provider integrations such as LDAP and SAML\n- external security scanner integrations such as Fortify and Checkmarx\n- ability to respond to leaked secrets such as AWS and GCP access keys\n\nYou can see all the available integrations in the [Integrate with GitLab documentation](https://docs.gitlab.com/ee/integration/). (Note: Not all integrations are listed in the documentation.)\n\n## GitLab security scanner integration\n\n[Third-party security scanners](https://docs.gitlab.com/ee/integration/#security-improvements) or [custom-built security scanners](https://gitlab.com/gitlab-de/tutorials/security-and-governance/custom-scanner-integration) can be integrated into GitLab to populate the merge request widget, Pipeline Security section, Vulnerability Report, vulnerability pages, Security dashboard, and Scan Result Policies. Let's review each integration.\n\n### Merge request security widget\n\nA merge request contains a security widget which displays a summary of the newly detected vulnerabilities.\n\n![integrating security scanners - image 1](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097089/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750097088837.png)\n\n\u003Ccenter>\u003Ci>Merge request security widget\u003C/i>\u003C/center>\n\u003Cp>\u003C/p>\n\nWhen you click on a vulnerability, you will see a popup that contains the following information:\n- status\n- description\n- project\n- file\n- identifiers\n- severity\n- tool\n- scanner provider\n\n![integrating security scanners - image 2](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097089/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750097088838.png)\n\n\u003Ccenter>\u003Ci>Actionable vulnerabilities with details\u003C/i>\u003C/center>\n\n\u003Cp>\u003C/p>\n\nThese vulnerabilities are also actionable, which means that they can either be dismissed or a confidential issue can be created.\n\nThe results of a custom scanner can be used to populate the security widget. The vulnerability data is populated from the JSON schema the scanner emits.\n\n### Pipeline Security section\n\nAll enabled security analyzers run in the pipeline and output their results as artifacts. These artifacts are processed, including deduplication, and the results are listed on the Pipeline Security tab. From here, you can also download the resulting JSON files.\n\n![integrating security scanners - image 3](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097089/Blog/Content%20Images/Blog/Content%20Images/image11_aHR0cHM6_1750097088840.png)\n\n\u003Ccenter>\u003Ci>Pipeline Security tab\u003C/i>\u003C/center>\n\u003Cp>\u003C/p>\n\nThe results of a custom scanner can be used to populate the Pipeline Security tab. The columns are filled in using the JSON schema the scanner emits.\n\n### Vulnerability Report\n\nThe Vulnerability Report provides information about vulnerabilities from scans of the default branch, including:\n\n- totals of vulnerabilities per severity level\n- filters for common vulnerability attributes\n- details of each vulnerability, presented in tabular layout\n\n![integrating security scanners - image 4](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097089/Blog/Content%20Images/Blog/Content%20Images/image8_aHR0cHM6_1750097088842.png)\n\n\u003Ccenter>\u003Ci>Vulnerability Report\u003C/i>\u003C/center>\n\u003Cp>\u003C/p>\n\nThe results of a custom scanner on the default branch can be used to populate the Vulnerability Report.\n\n### Vulnerability pages\n\nClicking on a vulnerability present within the Vulnerability Report takes you to its vulnerability page. Each vulnerability in a project has a vulnerability page that provides details such as:\n\n- description\n- when it was detected\n- current status\n- location detected\n- available actions\n- linked issues\n- actions log\n- solutions\n- identifier\n- training\n\nYou can use the data provided in the vulnerability page to triage a detected vulnerability as well as assist in its remediation.\n\n![integrating security scanners - image 5](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097089/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750097088844.png)\n\n\u003Ccenter>\u003Ci>Vulnerability page for secret detection vulnerability\u003C/i>\u003C/center>\n\u003Cp>\u003C/p>\n\nThe results of a custom scanner can be used to populate the vulnerability page. The vulnerability data is populated from the JSON schema the scanner emits.\n\n### Security dashboard\n\nSecurity dashboards are used to assess the security posture of your applications. GitLab provides you with a collection of metrics, ratings, and charts for the vulnerabilities detected by the security scanners run on your project. The security dashboard provides data such as:\n\n- vulnerability trends over a 30-, 60-, or 90-day timeframe for all projects in a group\n- a letter grade rating for each project based on vulnerability severity\n- the total number of vulnerabilities detected within the last 365 days and their severity levels\n\n![integrating security scanners - image 6](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097089/Blog/Content%20Images/Blog/Content%20Images/image7_aHR0cHM6_1750097088846.png)\n\n\u003Ccenter>\u003Ci>Group-level Security dashboard\u003C/i>\u003C/center>\n\u003Cp>\u003C/p>\n\nFrom the group-level Security dashboard you can click on a project to access its specific Security dashboard, which provides the 365-day view.\n\n![integrating security scanners - image 7](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097089/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750097088847.png)\n\n\u003Ccenter>\u003Ci>Project-level Security dashboard\u003C/i>\u003C/center>\n\u003Cp>\u003C/p>\n\n### Scan Result Policy integration\n\nScan Result Policies are used to require approval based on the findings of one or more security scan jobs. This can prevent insecure code from being merged to production. Scan Result Policies are evaluated after a CI scanning job is fully executed, where policies are evaluated based on the job artifact reports that are published in the completed pipeline.\n\nFor example, you can create a Scan Result Policy that requires approval from project maintainers if a secret detection scanner finds any vulnerabilities. Here's how:\n\n1. On the left sidebar, select **Search or go to** and search for the project you wish to add a policy to.\n2. On the project left sidebar, go to **Secure > Policies**\n3. Select **New policy**\n4. In the **Scan result policy** section, select **Select policy**.\n5. Complete the fields:\n- Name: The name of the Policy\n- Description: The description of the Policy\n- Policy status: Whether it is enabled or not\n- Rules: The conditions that must be met for an action (require approval) to take place\n\n![integrating security scanners - image 8](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097089/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750097088849.png)\n\u003Ccenter>\u003Ci>Scan Result Policy rules\u003C/i>\u003C/center>\n\u003Cp>\u003C/p>\n\n- Actions: The action to be taken whenever the conditions in the rules (defined vulnerabilities/licenses detected) are met\n\n![integrating security scanners - image 9](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097089/Blog/Content%20Images/Blog/Content%20Images/image9_aHR0cHM6_1750097088850.png)\n\n\u003Ccenter>\u003Ci>Scan Result Policy actions\u003C/i>\u003C/center>\n\u003Cp>\u003C/p>\n\n- Override project approval settings: If selected, the following choices will overwrite project settings but only affect the branches selected in the policy\n\n![integrating security scanners - image 11](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097089/Blog/Content%20Images/Blog/Content%20Images/image6_aHR0cHM6_1750097088851.png)\n\n \u003Ccenter>\u003Ci>Scan Result Policy approval settings\u003C/i>\u003C/center>\n \u003Cp>\u003C/p>\n\n6. Press the \"Configure with a merge request\" button.\n\nOnce the Scan Result Policy has been merged, whenever you create a merge request and the criteria defined in the rules are met, then the defined action will be triggered. In this case, at least one approval will be required from a maintainer before the code can be merged.\n\n![integrated security scanner - image 10](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750097089/Blog/Content%20Images/Blog/Content%20Images/image10_aHR0cHM6_1750097088852.png)\n\n\u003Ccenter>\u003Ci>Blocked merge request due to detected vulnerabilities\u003C/i>\u003C/center>\n\u003Cp>\u003C/p>\n\nThe results of a custom scanner can be fully integrated with Scan Result Policies. If the custom scanner detects a vulnerability, then approval will be required before the code can be merged. The scanner you select in a Scan Result Policy must be leveraging the appropriate JSON schema.\n\n## Tutorial: Integrating custom security scanners\n\nNow let’s get to the fun part – integrating a custom security scanner. In this tutorial, you will learn how to create a custom security scanner, as well as how to integrate it with GitLab. We will be leveraging the following projects:\n\n- [Fern Pattern Scanner](https://gitlab.com/gitlab-de/tutorials/security-and-governance/custom-scanner-integration/fern-pattern-scanner): Scans your files looking for specific patterns such as passwords, private keys, and social security numbers.\n- [Secret list](https://gitlab.com/gitlab-de/tutorials/security-and-governance/custom-scanner-integration/secret-list): Contains a list of user passwords, clients, and keys. This project is used to showcase how a custom security scanner can be integrated into GitLab.\n\nYou can watch the following video to see how the application was created and how it is used in detail:\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/timMbl5SP-w?si=R2DKtZ5MmBR1rQFL\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\n### Creating a custom security scanner\n\nNow let’s create a custom scanner that can be integrated into GitLab. Before a custom scanner can be fully integrated with GitLab, the scanner must:\n- scan a directory for defined patterns\n- emit a JSON following the appropriate schema\n- be containerized and accessible\n- provide a template to allow it to be run on another project\n\nWhen the [Fern Pattern scanner](https://gitlab.com/gitlab-de/tutorials/security-and-governance/custom-scanner-integration/fern-pattern-scanner) is run on a project using the provided template, it performs the following steps:\n1. Loads a set of rules which define patterns (regex) to detect.\n- Allow rules to be configurable to meet the changing needs of your organization.\n2. Scans files for defined patterns.\n3. Emits a JSON report following the Secret Detection schema.\n- Go templates are used in this project to create a JSON.\n- Depending on what your scanner will look for, make sure you use the appropriate schema.\n\nOnce the JSON report is loaded as an artifact into GitLab, it will populate the merge request widget, Vulnerability Report, vulnerability pages, Scan Result Policies, and Security dashboards as defined above.\n\n### Integrating a custom security scanner with GitLab\n\nOnce you have created your custom scanner that meets all the needs for integration, you can run it on GitLab.\n\nRunning a custom scanner is as easy as adding a template. We can see how the Fern Pattern scanner template is loaded by examining the `.gitlab-ci.yml` in the [Secret List](https://gitlab.com/gitlab-da/tutorials/security-and-governance/custom-scanner-integration/secret-list) project.\n\n1. Create a [.gitlab-ci.yml file](https://docs.gitlab.com/ee/ci/quick_start/#create-a-gitlab-ciyml-file) in the project you want the scanner to run on.\n2. Include the [Custom Scanner template](https://docs.gitlab.com/ee/ci/yaml/includes.html).\n    - You should also be able to configure the template with environment variables.\n3. Commit the file to the main branch.\n\nOnce the file has been committed, you can see that the custom scanner will run in your pipeline. Once the pipeline is complete, the scanner will populate all the areas defined above in the [GitLab security scanner integration](#gitlab-security-scanner-integration) section.\n\n## Read more\n\nCheck out these resources to learn more about GitLab and the other ways you can extend your DevSecOps platform:\n\n- [Security Scanner GitLab Integration](https://docs.gitlab.com/ee/development/integrations/secure.html)\n- [GitLab Partner Integrations](https://docs.gitlab.com/ee/integration/)\n- [Custom Security Scanner Projects Group](https://gitlab.com/gitlab-de/tutorials/security-and-governance/custom-scanner-integration)\n- [Automatic Response to a Secret Leak](https://docs.gitlab.com/ee/user/application_security/secret_detection/automatic_response.html)\n",[798,708,9,481],{"slug":1489,"featured":90,"template":686},"how-to-integrate-custom-security-scanners-into-gitlab","content:en-us:blog:how-to-integrate-custom-security-scanners-into-gitlab.yml","How To Integrate Custom Security Scanners Into Gitlab","en-us/blog/how-to-integrate-custom-security-scanners-into-gitlab.yml","en-us/blog/how-to-integrate-custom-security-scanners-into-gitlab",{"_path":1495,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1496,"content":1502,"config":1508,"_id":1510,"_type":13,"title":1511,"_source":15,"_file":1512,"_stem":1513,"_extension":18},"/en-us/blog/how-to-leverage-modern-software-testing-skills-in-devops",{"title":1497,"description":1498,"ogTitle":1497,"ogDescription":1498,"noIndex":6,"ogImage":1499,"ogUrl":1500,"ogSiteName":672,"ogType":673,"canonicalUrls":1500,"schema":1501},"How to leverage modern software testing skills in DevOps","Test automation is finally happening, but do teams have the necessary modern software testing skills? Here's what you need to know","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749668307/Blog/Hero%20Images/test-automation-devops.jpg","https://about.gitlab.com/blog/how-to-leverage-modern-software-testing-skills-in-devops","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to leverage modern software testing skills in DevOps\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Lauren Minning\"}],\n        \"datePublished\": \"2022-07-05\",\n      }",{"title":1497,"description":1498,"authors":1503,"heroImage":1499,"date":1505,"body":1506,"category":680,"tags":1507},[1504],"Lauren Minning","2022-07-05","\nTesting is a critical step in the software development lifecycle but also the part of the process most DevOps teams trip over. The solution — test automation — has been talked about for years but has been far easier said than done. However, with new technologies on the rise, test automation is taking off. DevOps teams need to be prepared with modern software testing skills. Here's how to get started.\n\n## The benefits of automated software testing\n\nIn [GitLab's 2021 Global DevSecOps Survey](/developer-survey/) of over 4,000 developers, security professionals, and operations team members, respondents agreed on one universal truth: Software testing is the biggest reason why development is delayed. \n\nIt’s critical to get software testing right because it’s financially disastrous to get it wrong. How much money do software mistakes add up to? Somewhere in the trillions. Yes, with a “t.”\n\n[DevOps.com](https://devops.com/this-is-not-just-a-test-devops-and-the-need-to-automate/) reported that software failures in companies’ operations systems cost a total of almost $1.6 trillion in the U.S. in 2019 alone. \n\nBut testing has traditionally been difficult to do efficiently and not particularly popular with developers. The solution? Test automation combined with modern software testing skills.\n\n## It’s a hands-on start\n\nDevOps teams looking to up their test game need to take a step back... into _manual_ testing.\n\n(The irony is not lost on us.)\n\nA manual testing mindset can actually improve all facets of automated software testing. As devs perform basic tests on their code as it’s being written, channeling their inner manual tester can be helpful. Whether it’s looking at the requirements again or running failed fixes *one more time*, that attention to detail should be brought into how automated test cases are built and executed. \n\n## Take the modern view\n\nOnce developers have incorporated some old-school habits into their test cases, it’s time to consider some fresh perspectives, up to and including a deep understanding of the organization’s goals and objectives.\n\nAccording to [Modern Testing](https://www.moderntesting.org), there are key principles of modern testing that every developer needs to be aware of for successful testing at any stage:\n\n- Job one is to make the business better. \n- Rely on trusted resources like [Lean Thinking](https://www.lean.org/explore-lean/what-is-lean/) and the [Theory of Constraints](https://www.leanproduction.com/theory-of-constraints/#:~:text=The%20Theory%20of%20Constraints%20is,referred%20to%20as%20a%20bottleneck).\n- Fail fast but focus on success.\n- Always be the customer when testing.\n- Do data-driven work. \n- Testers are evangelists. \n\n## Get certified\n\nAs the saying goes, every little bit helps. Though it is not required, a training program or certification course in software testing can enhance team capabilities.\n\nIf there's interest in this option, research courses online that might fit. From beginners to experienced testers, there’s something for everyone.\n\nNot sure where to start? Teams can explore the International Software Testing Qualifications Board (ISTQB) [Foundation Level Certification for CTFL certification](https://astqb.org/certifications/foundation-level-certification/). This is required before taking any other certifications (see [the full list of ISTQB prerequisites](https://astqb.org/certifications/#prerequisites)). After CTFL, there are many interesting certification options. \n\nThe [American Software Qualifications Board](https://astqb.org/certifications/), which offers the ISTQB certifications, is another great resource and has a helpful [Software Testing Career Road Map](https://astqb.org/benefits/road-map/). \n\n## Embrace new technologies\n\n[Artificial intelligence and machine learning](/blog/ai-in-software-development/) are at the core of test automation, so a thorough understanding of the technologies is a key modern software testing skill to have onboard. If AI/ML is already in use, ask to shadow or “apprentice” those working with it. Organize a Q&A for the DevOps team with an expert, and pull together a suggested reading list. The more understanding and experience, the easier it will be to get the most out of an ML bot.\n\n## Dive into the metrics\n\nAutomation is not only going to lead to faster releases, it's going to make it possible to do even more testing, which is great but, of course, also means there will be even _more_ data than ever before. It can be easy to feel overwhelmed by it all, so it's critical DevOps teams decide and [focus on the metrics that matter most](/blog/gitlab-top-devops-tooling-metrics-and-targets/) to the organization. It could be pipeline stability, time to first failure, or the \"age\" of open bugs... but whatever they are, they're important to continue to measure and understand.\n\n## The bottom line on modern software testing skills\n\nTesters, who’ve often been overlooked when it comes to DevOps fame and glory, have an opportunity to reinvent themselves and their QA roles if they can take advantage of modern software testing skills. It’s a critical step in the process that is finally getting some much needed attention and tech investment, so it makes sense to take it seriously.\n",[682,9,708],{"slug":1509,"featured":6,"template":686},"how-to-leverage-modern-software-testing-skills-in-devops","content:en-us:blog:how-to-leverage-modern-software-testing-skills-in-devops.yml","How To Leverage Modern Software Testing Skills In Devops","en-us/blog/how-to-leverage-modern-software-testing-skills-in-devops.yml","en-us/blog/how-to-leverage-modern-software-testing-skills-in-devops",{"_path":1515,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1516,"content":1522,"config":1530,"_id":1532,"_type":13,"title":1533,"_source":15,"_file":1534,"_stem":1535,"_extension":18},"/en-us/blog/how-were-building-up-performance-testing-of-gitlab",{"title":1517,"description":1518,"ogTitle":1517,"ogDescription":1518,"noIndex":6,"ogImage":1519,"ogUrl":1520,"ogSiteName":672,"ogType":673,"canonicalUrls":1520,"schema":1521},"How GitLab's QA Team Leverages Performance Testing Tools","We built our open source GitLab Performance tool to evaluate pain points and implement solutions on every GitLab environment.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681087/Blog/Hero%20Images/performance-server-front.jpg","https://about.gitlab.com/blog/how-were-building-up-performance-testing-of-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How our QA team leverages GitLab’s performance testing tool (and you can too)\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Grant Young\"}],\n        \"datePublished\": \"2020-02-18\",\n      }",{"title":1523,"description":1518,"authors":1524,"heroImage":1519,"date":1526,"body":1527,"category":705,"tags":1528},"How our QA team leverages GitLab’s performance testing tool (and you can too)",[1525],"Grant Young","2020-02-18","\n\nWe’ve set up several initiatives aimed at testing and improving the performance of GitLab, which is why the Quality team built a new tool to test GitLab's performance.\n\nPerformance testing is an involved process and distinct from other testing disciplines. The strategies and tooling in this space are specialized and require dedicated resources to achieve results. When I joined the company and became the first member of this team, the task was to expand our nascent performance efforts to a much larger scale. For this, we needed to build out a new tool that we aptly named the [GitLab Performance tool](https://gitlab.com/gitlab-org/quality/performance) (GPT).\n\nWe're happy to announce the general release of [GPT](https://gitlab.com/gitlab-org/quality/performance/-/releases). In this blog post, we'll share how GPT is used to performance test GitLab, and how you can use it as well to test your own environments.\n\nHowever, before we get into what the GPT is, we need to first touch on what we use it with.\n\n## Reference Architectures and test data\n\nIn our experience, the challenging part of performance testing isn’t actually to do with the testing itself, but instead configuring the right environments and data to test against.\n\nAs such, one of the initiatives we’ve been driving is the design of several [GitLab Reference Architectures](https://docs.gitlab.com/ee/administration/reference_architectures/index.html#available-reference-architectures) that can handle large numbers of users. We wanted to create these architectures as a way to standardize our recommended configurations to ensure we were presenting customers with options for performant, scalable, and highly available GitLab setups.\n\nIn order to create a tool like this, we needed to add realistic data into these environments to test against, e.g., large projects with commits and merge requests. As a first iteration, we started with our very own GitLab project.\n\nOnce we got our environments running and configured we were ready to test them with the GPT.\n\n## What is the GitLab Performance tool (GPT)?\n\nThe GPT can be used to run numerous load tests to verify the performance of any GitLab environment. All that’s required is to a knowledge of what throughput the intended environment can handle (as requests per second) and to ensure that the environment has the necessary data prepared.\n\nThe GPT is built upon one of the leading tools in the industry, [k6](https://k6.io/). Here are some examples of what the GPT provides:\n\n* A broad test suite that comes out-of-the-box and covers various endpoints across the GitLab product with added ability to add your own custom tests as desired. [See the latest out-of-the-box test details](https://gitlab.com/gitlab-org/quality/performance/-/wikis/current-test-details) with more being added frequently.\n* [Options](https://gitlab.com/gitlab-org/quality/performance/-/blob/master/docs/k6.md#options) for customizing test runs, such as specifying desired GitLab environment data or defining what throughput to use with default examples given.\n* [Ability to run multiple tests sequentially as well as be selective about which are chosen](https://gitlab.com/gitlab-org/quality/performance/-/blob/master/docs/k6.md#running-the-tests-with-the-tool).\n* [Enhanced reporting and logging](https://gitlab.com/gitlab-org/quality/performance/-/blob/master/docs/k6.md#running-the-tests-with-the-tool).\n* [Built-in test success thresholds](https://gitlab.com/gitlab-org/quality/performance/-/blob/master/docs/k6.md#test-thresholds) based on [time to first byte](https://en.wikipedia.org/wiki/Time_to_first_byte), throughput achieved and successful responses.\n\nThe talented team at [Load Impact](https://loadimpact.com/) created [k6](https://k6.io/), which is the core of the GPT. We realized quickly that we didn’t need to reinvent the wheel because k6 met most of our needs: It is written in Go, so is very performant and is an open source solution. Thanks to the team for not only developing k6 but also for reaching out to us soon after we started to collaborate.\n\n## How we use GPT\n\nWe use the GPT in several automated [GitLab CI pipelines](/blog/guide-to-ci-cd-pipelines/) for quick feedback on how GitLab is performing. The CI pipelines typically run daily or weekly against our reference architecture environments, which themselves are running on the latest pre-release code. We review the test results as they come in and then investigate any failures. In line with our [Transparency value](https://handbook.gitlab.com/handbook/values/#transparency), we also publish all of the [latest results](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/Latest) for anyone to view on the [GPT wiki](https://gitlab.com/gitlab-org/quality/performance/-/wikis/home).\n\nThe GPT is also used in a comparison test pipeline to see how GitLab’s performance changes in every release cycle. These results are important because they show the whole picture of our performance evolution. The [benchmark comparison results](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/GitLab-Versions) are also available on the [GPT wiki](https://gitlab.com/gitlab-org/quality/performance/-/wikis/home).\n\nBy using the GPT, we’ve been able to identify several performance pain points of GitLab and collaborate with our dev teams to prioritize improvements. The process has been fruitful so far and we’re excited to already see improvements in the performance numbers with each release of GitLab. The 12.6 release for example showed [several notable improvements across the board](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Benchmarks/GitLab-Versions#comparisions), one even as high as a 92% reduction! You can see the issues we've raised so far through this work over on our [issue tracker](https://gitlab.com/gitlab-org/gitlab/issues?scope=all&utf8=%E2%9C%93&state=all&label_name[]=Quality%3Aperformance-issues).\n\n## How you can use GPT\n\nWe decided early that we wanted to follow the same open source principles as our main product, so we build the GPT with all users in mind rather than making it a strictly internal tool. So not only do we let others use it, we encourage it! This is beneficial for us and customers, as we receive feedback from diverse viewpoints that we hadn’t considered. Some examples of this are [improving the recommended spec guidelines based on throughput](https://gitlab.com/gitlab-org/quality/performance/issues/172) or [making it easier for users who have private clouds to use the GPT offline](https://gitlab.com/gitlab-org/quality/performance/issues/106).\n\nIf you want to use the GPT for yourself, the best place to start is with its [documentation](https://gitlab.com/gitlab-org/quality/performance#documentation). As mentioned earlier, most of the effort to use the GPT is preparing the intended environment. The docs will take you through this along with how to use the tool itself.\n\n## The GPT in action\n\nFinally after writing all about the GPT we should actually show it in action. Here's how it looks when running a load test for the [List Group Projects API](https://docs.gitlab.com/ee/api/groups.html#list-a-groups-projects) against our [10k Reference Architecture](https://docs.gitlab.com/ee/administration/reference_architectures/10k_users.html):\n\n[![asciicast](https://asciinema.org/a/O96Wc5fyxvLb1IDyviTwbujg8.svg)](https://asciinema.org/a/O96Wc5fyxvLb1IDyviTwbujg8?autoplay=1)\n\nRead the GPT [documentation](https://gitlab.com/gitlab-org/quality/performance/blob/master/docs/k6.md#test-output-and-results) for more details on output and results.\n\n## What’s next?\n\nOur aim is to make GitLab’s performance best in class. This is only the start of our performance testing journey with GPT and we are excited about the additional ways we can continue to help improve the customer experience.\n\n[Some examples of our plans for the next few releases](https://gitlab.com/gitlab-org/quality/performance/issues) include expanding test coverage to more of GitLab’s features and entry points (API, Web, Git) and expanding our work on the reference architectures, test data, and user behavior patterns to be as representative and realistic as possible.\n\nShare your feedback and/or suggestions on GPT here or on our [GPT project](https://gitlab.com/gitlab-org/quality/performance)! We welcome your ideas or contributions.\n\nCover image by [Taylor Vick](https://unsplash.com/@tvick?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/server?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText).\n{: .note}\n",[1529,9],"performance",{"slug":1531,"featured":6,"template":686},"how-were-building-up-performance-testing-of-gitlab","content:en-us:blog:how-were-building-up-performance-testing-of-gitlab.yml","How Were Building Up Performance Testing Of Gitlab","en-us/blog/how-were-building-up-performance-testing-of-gitlab.yml","en-us/blog/how-were-building-up-performance-testing-of-gitlab",{"_path":1537,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1538,"content":1544,"config":1551,"_id":1553,"_type":13,"title":1554,"_source":15,"_file":1555,"_stem":1556,"_extension":18},"/en-us/blog/inside-look-how-gitlabs-test-platform-team-validates-ai-features",{"title":1539,"description":1540,"ogTitle":1539,"ogDescription":1540,"noIndex":6,"ogImage":1541,"ogUrl":1542,"ogSiteName":672,"ogType":673,"canonicalUrls":1542,"schema":1543},"Inside look: How GitLab's Test Platform team validates AI features","Learn how we continuously analyze AI feature performance, including testing latency worldwide, and get to know our new AI continuous analysis tool.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099033/Blog/Hero%20Images/Blog/Hero%20Images/blog-image-template-1800x945%20%2811%29_78Dav6FR9EGjhebHWuBVan_1750099033422.png","https://about.gitlab.com/blog/inside-look-how-gitlabs-test-platform-team-validates-ai-features","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Inside look: How GitLab's Test Platform team validates AI features\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Mark Lapierre\"},{\"@type\":\"Person\",\"name\":\"Vincy Wilson\"}],\n        \"datePublished\": \"2024-06-03\",\n      }",{"title":1539,"description":1540,"authors":1545,"heroImage":1541,"date":1548,"body":1549,"category":795,"tags":1550},[1546,1547],"Mark Lapierre","Vincy Wilson","2024-06-03","AI is increasingly becoming a centerpiece of software development - many companies are integrating it throughout their DevSecOps workflows to improve productivity and increase efficiency. Because of this now-critical role, AI features should be tested and analyzed on an ongoing basis. In this article, we take you behind the scenes to learn how [GitLab's Test Platform team](https://handbook.gitlab.com/handbook/engineering/infrastructure/test-platform/) does this for [GitLab Duo](https://about.gitlab.com/gitlab-duo/) features by conducting performance validation, functional readiness, and continuous analysis across GitLab versions. With this three-pronged approach, GitLab aims to ensure that GitLab Duo features are performing optimally for our customers.\n\n> Discover the future of AI-driven software development with our GitLab 17 virtual launch event. [Watch today!](https://about.gitlab.com/seventeen/)\n\n## AI and testing\n\nAI's non-deterministic nature, where the same input can produce different outputs, makes ensuring a great user experience a challenge. So, when we integrated AI deep into the GitLab DevSecOps Platform, we had to adapt to our best practices to address this challenge. \n\nThe [Test Platform team's mission ](https://handbook.gitlab.com/handbook/engineering/infrastructure/test-platform/) is to help enable the successful development and deployment of high-quality software applications with continuous analysis and efficiency to help ensure customer satisfaction. The key to achieving this is by delivering tools that help increase standardization, repeatability, and test consistency. \n\nApplying this to GitLab Duo, our AI suite of tools to power DevSecOps workflows, means being able to continuously analyze its performance and identify opportunities for improvement. Our goal is to gain clear, actionable insights that will help us to enhance GitLab Duo's capabilities and, as a result, better meet our customers' needs. \n\n## The need for continuous analysis of AI\n\nTo continuously assess GitLab Duo, we needed a mechanism for analyzing feature performance across releases. Therefore, we created an AI continuous analysis tool to automate the collection and analysis of data to achieve this. \n\n![diagram of how the AI continuous analysis tool works](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099041/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750099041503.png)\n\n\u003Ccenter>\u003Ci>How the AI continuous analysis tool works\u003C/i>\u003C/center>\n\n### Building the AI continuous analysis tool\n\nTo gain detailed, user-centric insights, we needed to gather data in the appropriate context – in this case, the integrated development environment (IDE), as it is where most of our users access GitLab Duo. We narrowed this down further by opting for the Visual Studio Code IDE, a popular choice within our community. Once the environment was chosen, we automated entering code prompts and recording the provided suggestions. The interactions with the IDE are handled by the [WebdriverIO VSCode service](https://github.com/webdriverio-community/wdio-vscode-service), and CI operations are handled through [GitLab CI/CD](https://docs.gitlab.com/ee/ci/). This automation significantly scaled up data collection and eliminated repetitive tasks for GitLab team members. To start, we have focused on measuring the performance of GitLab Duo Code Suggestions, but plan to expand to other GitLab AI features in the future.\n\n### Analyzing the data\n\nAt the core of our AI continuous analysis tool is a mechanism for collecting and analyzing code suggestions. This involves automatically entering code prompts, recording the suggestions provided, and logging timestamps of relevant events. We measure the time from when the tool provides an input until a suggestion is displayed in the UI. In addition, we record the logs created by the IDE, which report the time it took for each suggestion response to be received. With this data, we can compare the latency of suggestions in terms of how long it takes the backend AI service to send a response to the IDE, and how long it takes for the IDE to display the suggestion for the user. We then can compare latency and other metrics of GitLab Duo features across multiple releases. The GitLab platform has the ability to analyze [code quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html) and [application security](https://docs.gitlab.com/ee/user/application_security/), so we leverage these capabilities to enable the AI continuous analysis tool to analyze the quality and security of the suggestions provided by GitLab Duo.\n\n### Improving AI-driven suggestions\n\nOnce the collected data is analyzed, the tool automatically generates a single report summarizing the results. The report includes key statistics (e.g., mean latency and/or latency at various percentiles), descriptions of notable differences or patterns, links to raw data, and CI/CD pipeline logs and artifacts. The tool also records a video of each prompt and suggestion, which allows us to review specific cases where differences are highlighted. This creates an opportunity for the UX researchers and development teams to take action on the insights gained, helping to improve the overall user experience and system performance.\n\nThe tool is at an early stage of development, but it's already helped us to improve the experience for GitLab Duo Code Suggestions users. Moving forward, we plan to expand our tool’s capabilities, incorporate more metrics and consume and provide input to our [Centralized Evaluation Framework](https://about.gitlab.com/direction/ai-powered/ai_model_validation/ai_evaluation/), which validates AI models, to enhance our continuous analysis further.\n\n## Performance validation\n\nAs AI has become integral to GitLab's offerings, optimizing the performance of AI-driven features is essential. Our performance tests aim to evaluate and monitor the performance of our GitLab components, which interact with AI service backends. While we can monitor the performance of these external services as part of our production environment's observability, we cannot control them. Thus, including third-party services in our performance testing would be expensive and yield limited benefits. Although third-party AI providers contribute to overall latency, the latency attributable to GitLab components is still important to check. We aim to detect changes that might lead to performance degradation by monitoring GitLab components. \n\n### Building AI performance validation test environment\n\nIn our AI test environments, the [AI Gateway](https://docs.gitlab.com/ee/architecture/blueprints/ai_gateway/#summary), which is a stand-alone service to give access to AI features to GitLab users, has been configured to return mocked responses, enabling us to test the performance of AI-powered features without interacting with third-party AI service providers. We conduct AI performance tests on [reference architecture environments of various sizes](https://docs.gitlab.com/ee/administration/reference_architectures/). Additionally, we evaluate new tests in their own isolated environment before they're added to the larger environments.\n\n### Testing multi-regional latency\n\nMulti-regional latency tests need to be run from various geolocations to validate that requests are being served from a suitable location close to the source of the request. We do this today with the use of the [GitLab Environment Toolkit](https://gitlab.com/gitlab-org/gitlab-environment-toolkit). The toolkit provisions an environment in the identified region to test (note: both the AI Gateway and the provisioned environment are in the same region), then uses the [GitLab Performance Tool](https://gitlab.com/gitlab-org/quality/performance) to run tests to measure time to first byte (TTFB). TTFB is our way of measuring time to the first part of the response being rendered, which contributes to the perceived latency that a customer experiences. To account for this measurement, our tests have a check to help ensure that the [response itself isn't empty](https://gitlab.com/gitlab-org/quality/performance/-/blob/cee8bef023e590e6ca75828e49f5c7c596581e06/k6/tests/experimental/api_v4_code_suggestions_generation_streaming.js#L70). \n\nOur tests are expanding further to continue to measure perceived latency from a customer’s perspective. We have captured a set of baseline response times that indicate how a specific set of regions performed when the test environment was in a known good state. These baselines allow us to compare subsequent environment updates and other regions to this known state to evaluate the impact of changes. These baseline measurements can be updated after major updates to ensure they stay relevant in the future. \n\nNote: As of this article's publication date, we have AI Gateway deployments across the U.S., Europe, and Asia. To learn more, visit our [handbook page](https://handbook.gitlab.com/handbook/engineering/development/data-science/ai-powered/ai-framework/#-aigw-region-deployments).\n\n## Functionality\n\nTo help continuously enable customers to confidently leverage AI reliably, we must continuously work to ensure our AI features function as expected.\n\n### Unit and integration tests\n\nFeatures that leverage AI models still require rigorous automated tests, which help engineers develop new features and changes confidently. However, since AI features can involve integrating with third-party AI providers, we must be careful to stub any external API calls to help ensure our tests are fast and reliable.\n\nFor a comprehensive look at testing at GitLab, look at our [testing standards and style guidelines](https://docs.gitlab.com/ee/development/testing_guide/). \n\n### End-to-end tests \n\nEnd-to-end testing is a strategy for checking whether the application works as expected across the entire software stack and architecture. We've implemented it in two ways for GitLab Duo testing: using real AI-generated responses and mock-generated AI responses.\n\n![validating features - image 2](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099041/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750099041504.png)\n\n\u003Ccenter>\u003Ci>End-to-end test workflow\u003C/i>\u003C/center>\n\n#### Using real AI-generated responses\n\nAlthough costly, end-to-end tests are important to help ensure the entire user experience functions as expected. Since AI models are non-deterministic, end-to-end test assertions for validating real AI-generated responses should be loose enough to help ensure the feature functions without relying on a response that may change. This might mean an assertion that checks for some response with no errors or for a response we are certain to receive.\n\nAI-driven functionality is not accessible only from within the GitLab application, so we must also consider user workflows for other applications that leverage these features. For example, to cover the use case of a developer requesting code suggestions in [IntelliJ IDEA](https://www.jetbrains.com/idea/) using the GitLab Duo plugin, we need to drive the IntelliJ application to simulate a user workflow. Similarly, to ensure that the GitLab Duo Chat experience is consistent in VS Code, we must drive the VS Code application and exercise the GitLab Workflow extension. Working to ensure these workflows are covered helps us maintain a consistently great developer experience across all GitLab products. \n\n#### Using mock AI-generated responses\n\nIn addition to end-to-end tests using real AI-generated responses, we run some end-to-end tests against test environments configured to return mock responses. This allows us to verify changes to GitLab code and components that don’t depend on responses generated by an AI model more frequently.\n\n> For a closer look at end-to-end testing, read our [end-to-end testing guide](https://docs.gitlab.com/ee/development/testing_guide/end_to_end/). \n\n### Exploratory testing and dogfooding\n\nAI features are built by humans for humans. At GitLab, exploratory testing and dogfooding greatly benefit us. GitLab team members are passionate about what features get shipped, and insights from internal usage are invaluable in shaping the direction of AI features.\n\n[Exploratory testing](https://about.gitlab.com/topics/devops/devops-test-automation/#test-automation-stages) allows the team to creatively exercise features to help ensure edge case bugs are identified and resolved. Dogfooding encourages team members to use AI features in their daily workflows, which helps us identify realistic issues from realistic users. For a comprehensive look at how we dogfood AI features, look at [Developing GitLab Duo: How we are dogfooding our AI features](https://about.gitlab.com/blog/developing-gitlab-duo-how-we-are-dogfooding-our-ai-features/).\n\n## Get started with GitLab Duo\nHopefully this article gives you insight into how we are validating AI features at GitLab. We have integrated our team's process into our overall development as we iterate on GitLab Duo features. We encourage you to try GitLab Duo in your organization and reap the benefits of AI-powered workflows.\n\n> Start a [free trial of GitLab Duo](https://about.gitlab.com/gitlab-duo/#free-trial) today!\n\n_Members of the GitLab Test Platform team contributed to this article._\n",[797,799,481,881,9,1529],{"slug":1552,"featured":90,"template":686},"inside-look-how-gitlabs-test-platform-team-validates-ai-features","content:en-us:blog:inside-look-how-gitlabs-test-platform-team-validates-ai-features.yml","Inside Look How Gitlabs Test Platform Team Validates Ai Features","en-us/blog/inside-look-how-gitlabs-test-platform-team-validates-ai-features.yml","en-us/blog/inside-look-how-gitlabs-test-platform-team-validates-ai-features",{"_path":1558,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1559,"content":1565,"config":1572,"_id":1574,"_type":13,"title":1575,"_source":15,"_file":1576,"_stem":1577,"_extension":18},"/en-us/blog/inside-our-new-development-team-lead-persona",{"title":1560,"description":1561,"ogTitle":1560,"ogDescription":1561,"noIndex":6,"ogImage":1562,"ogUrl":1563,"ogSiteName":672,"ogType":673,"canonicalUrls":1563,"schema":1564},"What are the best and worst parts about being a development team lead?","Dev leads, we feel you. Here's a deep dive into our interviews with development team leads, and the new persona they informed.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749668224/Blog/Hero%20Images/inside-our-new-development-team-lead-persona.jpg","https://about.gitlab.com/blog/inside-our-new-development-team-lead-persona","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"What are the best and worst parts about being a development team lead?\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Katherine Okpara\"}],\n        \"datePublished\": \"2019-01-18\",\n      }",{"title":1560,"description":1561,"authors":1566,"heroImage":1562,"date":1568,"body":1569,"category":705,"tags":1570},[1567],"Katherine Okpara","2019-01-18","\nWelcome back to our series on the [new GitLab personas](/handbook/product/personas/)! I recently [wrote about what we learned from product managers during interviews](/blog/inside-our-new-product-manager-persona/) for our [UX research project to develop personas](https://gitlab.com/gitlab-org/ux-research/issues/77) for all product areas. In this post, I'll share some of the insights from our efforts to better understand development team leads, and introduce the resulting persona, [Delaney](/handbook/product/personas/#delaney-development-team-lead).\n\n## The research\n\nHere are some of the findings from my [six interviews](https://gitlab.com/gitlab-org/ux-research/issues/95) conducted for the persona.\n\nDevelopment team leads are often responsible for meeting with product managers and stakeholders to discuss scheduled feature requests, convert concepts into practical solutions, ensure that capacity is properly estimated, and assign work to developers. They are also involved in other duties such as creating design and functional specifications, writing code, documenting and automating processes, and mentoring other developers.\n\n### So, what’s the hardest part about being a development team lead?\n\nDue to the nature of their work, the challenges development team leads face often cross into several domains.\n\n#### Vague requirements and poor communication\n\nIt can be difficult to know the status of certain requirements when other team members don't update the various tools that are being used. Important information can get lost along the way, which often leads to repetitive discussions or fixing incorrect work. Many of the people we spoke with are looking for ways to have this information readily accessible and consistently communicated throughout their teams.\n\n> \"Sometimes the back and forth can be annoying, when the requirements aren’t clear and I have to go back a step to understand what is going on or a component is not what I wanted. At a previous company, the back and forth was especially drawn out since the team did not work closely together. At [my current] company, this problem isn’t as severe since I work closely with the team and can quickly ask for clarification if I need to. Working more efficiently saves a lot of time.\"\n\n####  Difficulty making accurate estimations of timeline and capacity\n\nA team lead must have a good understanding of the skillsets available on their team and use this insight to balance business objectives. In order to get a better sense of the experience levels of different team members, they often hold one-on-one meetings or conduct reviews during and after a development cycle.\n\n> \" ... This goes back to the burndown chart – if it's being used correctly, it can help you see where you’ll end up. In order for that to happen, you need your estimations to be accurate. And in order for _that_ to happen you need to figure out the accuracy of the baseline and experience of the developer. For example, someone who is more junior has less of a reference point. I have to assign extra points to stories, if there are unknown variables.\"\n\n#### Delivering on time\n\nWhen demand surpasses current capacity, it can be stressful to resolve existing problems without creating new issues that result from hasty work. It can also be difficult to explain technical limitations to stakeholders who are not involved in the development process.\n\n> \"Someone might see a code review request but feel conflicted since they only have two days left to finish their own tasks. So sometimes testers and customers are waiting on these code reviews to move forward ... The biggest thing would be having all those tickets, all of those changes, closely correlated with the actual changes in Git. 'For this particular feature, here are all the changes in Git.' You don’t have to read the codebase or fire up the whole application. You have the information all in one place and don’t need to hunt down information.\"\n\n#### Changing mindsets in organizations to adopt faster, iterative approaches\n\nSome development teams are slowed down by inefficient toolchains or outdated workflows because their organizations are resistant to change and adopting new practices. Introducing new ideas and methodologies can be an especially complex process in organizations that create products for industries with more restrictions and regulations than others.\n\n> \"Most blockers that arise are put in their own way. I would prefer to iterate while they rather plan everything out for long periods of time. Their own processes get in their way because they don’t think they can move faster. Many of their processes are filled with errors and take days or weeks. They’ve always done things a certain way and are not really willing to make a change.\"\n\n### What motivates a development team lead?\n\nOne of the biggest goals for many development team leads is the drive to continually optimize processes and deliver value to the product. They must also build a level of communication that enables them to assign tasks to the appropriate people, explain why certain feature requests are or are not feasible, and continue to implement strategic solutions.\n\n### What’s the best part about being a development team lead?\n\nThe best part of being a Development Team Lead is problem solving on a variety of levels – from tools to methodologies to team relations and more. When teams are well supported by their leaders and organizations, they are better equipped to meet the expectations that will move both the product and business forward!\n\n## The persona\n\n[![Delaney, Development Team Lead persona](https://about.gitlab.com/images/blogimages/delaney-dev-team-lead-persona.png)](/handbook/product/personas/#delaney-development-team-lead)\n\n### Want to share your experiences of GitLab with me?\n\nJoin [GitLab First Look](/community/gitlab-first-look/) and help us build an even better picture of who GitLab’s users really are!\n\n[Photo](https://unsplash.com/photos/atSaEOeE8Nk) by [Steven Lelham](https://unsplash.com/@slelham) on Unsplash\n{: .note}\n",[9,709,1571],"UX",{"slug":1573,"featured":6,"template":686},"inside-our-new-development-team-lead-persona","content:en-us:blog:inside-our-new-development-team-lead-persona.yml","Inside Our New Development Team Lead Persona","en-us/blog/inside-our-new-development-team-lead-persona.yml","en-us/blog/inside-our-new-development-team-lead-persona",{"_path":1579,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1580,"content":1586,"config":1591,"_id":1593,"_type":13,"title":1594,"_source":15,"_file":1595,"_stem":1596,"_extension":18},"/en-us/blog/inside-our-new-product-manager-persona",{"title":1581,"description":1582,"ogTitle":1581,"ogDescription":1582,"noIndex":6,"ogImage":1583,"ogUrl":1584,"ogSiteName":672,"ogType":673,"canonicalUrls":1584,"schema":1585},"What do product managers need to do their best work?","Check out some of the findings that led to our new Product Manager Persona.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749678857/Blog/Hero%20Images/investigating-how-product-managers-use-gitlab.jpg","https://about.gitlab.com/blog/inside-our-new-product-manager-persona","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"What do product managers need to do their best work?\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Katherine Okpara\"}],\n        \"datePublished\": \"2018-11-12\",\n      }",{"title":1581,"description":1582,"authors":1587,"heroImage":1583,"date":1588,"body":1589,"category":680,"tags":1590},[1567],"2018-11-12","\nRecently I spoke with several product managers and asked them about their experiences, as part of our [effort to create personas](/blog/personas-and-empathy-building/) for every one of GitLab's [product areas](/handbook/product/categories/). I gained a lot of insight through these interviews, including a better understanding of their daily duties, goals and motivations, challenges they face in their roles, and the tools they use throughout the software development lifecycle. Many of the findings have been included in our new [Product Manager Persona, Parker](/handbook/product/personas/), to help our own PMs brainstorm improvements and next steps for GitLab features. You can peruse the highlights and the persona itself below, and let us know what you think by tweeting us [@gitlab](https://twitter.com/gitlab)!\n\n## The research\n\nHere are some of the findings from my [eight interviews](https://gitlab.com/gitlab-org/ux-research/issues/88) conducted for the persona.\n\n### So, what’s the hardest part about being a product manager?\n\nThe product manager persona represents people who are responsible for prioritizing feature requests, product roadmapping, and tracking progress of the development of software applications. Since many of these factors depend on how other team members perform, most challenges related to communication and ensuring that their team delivers on time.\n\n#### Staying updated on team progress and important decisions\n\nIt can be difficult to know the status of certain requirements when other team members do not update the various tools that are being used. Important information can get lost along the way, which often leads to repetitive discussions or fixing incorrect work. Users were looking for ways to have this information readily accessible and consistently communicated throughout their teams.\n\n> \"Getting other people to use the tools. I need to make sure that other people are updating the Jira board for example – in my experience, many developers don’t exactly love to do this since it’s a tedious task. Or, if they have a question, adding it in the task so that we can keep a record of everything that’s being worked on. Sometimes someone will send me a question on Slack and I’ll copy-paste that into the ticket since sometimes it’s easier for me to do that than to ask someone to get used to doing that.\"\n\n#### Prioritizing features to build when dealing with limited resources\n\nProduct managers are often responsible for defining and scoping features, incorporating company objectives into the product roadmap, and giving developers and designers the requirements they need to deliver strong features. As a result, product teams often have trouble balancing feature requests with development capacity.\n\n> \"...Being able to find balance between being strategic and being practical. Being able to look into the future and be ambitious while at the same time having to put out fires and manage the day-to-day. Another challenge is staying in touch with the end user. We do not have as much time to be on top of the market and to interview customers. We're not as able to get market feedback and do market research as well...\"\n\n#### Simplifying information\u2028 for the different stakeholders involved in the product\n\nThe need to give clients and stakeholders timelines and estimates that are accurate but also realistic can be very stressful for a product manager. This is largely due to the fact that a cycle is often unpredictable. It can also be challenging to explain why certain features have been delayed or deprioritized, when customers and upper-level management are not working closely with the team.\n\n>\u2028\"Some of the challenges of working with the technical team leads is that they will forget to update things or they’ll give me a summary that is super technical so I have to ask more questions to make sure that I understand and have the ability to explain to other product managers where the developers are stuck, because they need more definition on what that feature should look like.\"\n\n### What motivates a product manager?\n\nProduct managers generally are motivated by the desire to deliver high-quality features in a timely manner. When company objectives shift, they want to have a standard process for communication, so that they can be in sync with all team members. They need to see an overview of all the relevant information related to a feature or product, so that they can monitor progress throughout a cycle. Additionally, they want to be able to help their teams accomplish more of their goals over time.\n\n### What’s the best part about being a product manager?\n\nAll in all, the interviewees all expressed the joy they receive from simply doing their jobs, whether that’s improving life for users or speeding up processes within the company. The best part of being a product manager is the opportunity to bring a concept to life and solve real problems for their users.\n\n## The persona\n\n![Parker, Product Manager persona](https://about.gitlab.com/images/blogimages/product-manager-persona.png){: .shadow.center}\n\nKeep an eye out for the rest of our series on the [new personas](/handbook/product/personas/)!\n\n[Photo](https://unsplash.com/photos/YiRQIglwYig) by [Hello I'm Nik](https://unsplash.com/@helloimnik) on Unsplash\n{: .note}\n",[9,881,709],{"slug":1592,"featured":6,"template":686},"inside-our-new-product-manager-persona","content:en-us:blog:inside-our-new-product-manager-persona.yml","Inside Our New Product Manager Persona","en-us/blog/inside-our-new-product-manager-persona.yml","en-us/blog/inside-our-new-product-manager-persona",{"_path":1598,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1599,"content":1605,"config":1611,"_id":1613,"_type":13,"title":1614,"_source":15,"_file":1615,"_stem":1616,"_extension":18},"/en-us/blog/integrate-external-security-scanners-into-your-devsecops-workflow",{"title":1600,"description":1601,"ogTitle":1600,"ogDescription":1601,"noIndex":6,"ogImage":1602,"ogUrl":1603,"ogSiteName":672,"ogType":673,"canonicalUrls":1603,"schema":1604},"Integrate external security scanners into your DevSecOps workflow","Learn how to bring Snyk scan results into the merge request widget by parsing JSON artifacts and leveraging the SARIF file format.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098768/Blog/Hero%20Images/Blog/Hero%20Images/blog-image-template-1800x945%20%282%29_1khno1AUtxuL6zzmEmjK7v_1750098768560.png","https://about.gitlab.com/blog/integrate-external-security-scanners-into-your-devsecops-workflow","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Integrate external security scanners into your DevSecOps workflow\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sam Morris\"}],\n        \"datePublished\": \"2024-04-08\",\n      }",{"title":1600,"description":1601,"authors":1606,"heroImage":1602,"date":1608,"body":1609,"category":708,"tags":1610},[1607],"Sam Morris","2024-04-08","Each day you build software there is another opportunity for security vulnerabilities to creep into production. So it is becoming more important than ever to shift security left and put security tests and the vulnerabilities they detect at the forefront of your software development lifecycle.\n\nWhile GitLab offers a wide range of different security scanners, our AI-powered DevSecOps platform provides full visibility into the security of your software. We seek to allow you to not only run scans, but also to view results, bake in approval processes via merge request policies, and display current vulnerabilities in your default branch for future triage in our Vulnerability Report.\n\n## How do security scans run?\n\nGitLab Ultimate displays your vulnerabilities directly in the merge request widget and it updates on every commit. These scans typically run via jobs in a pipeline, whether in the project’s `.gitlab-ci.yml` pipeline or in a separately-controlled [compliance pipeline](https://docs.gitlab.com/ee/user/group/compliance_pipelines.html), [security policy](https://docs.gitlab.com/ee/user/application_security/policies/scan-execution-policies.html), or [included pipeline configuration](https://docs.gitlab.com/ee/ci/yaml/includes.html) from a separate .yml file. You can run GitLab’s native security scanners or you can run an external scanner. For this blog post, I took running Snyk scans for a spin to see how I could feed the dependency scan results as vulnerability records back into GitLab. Additionally, I utilized a Static Analysis Results Interchange Format (SARIF) converter to read SAST results directly from Snyk without custom scripting. \n\n## Using external scanners\n\nGitLab is highly extensible, and the platform allows for you to integrate myriad tools. You can use one of our built-in security scanners, or use an external scanner via a job in a pipeline or policy. GitLab serves as a single platform for governance and enforcement, allowing you to bring your own scanners and see the results early in the DevSecOps lifecycle.\n\nAll you have to do to get started is run a security job, and from there you can obtain the results in the merge request and the vulnerability report.\n\n## Run an external scan from GitLab CI\n\nIn this example pipeline, I run a Snyk scan externally in the test stage in a job I overrode called `gemnasium-maven-dependency_scanning`. First, I install the required packages (npm, Maven, Python3, and Snyk) and then I authorize with my SNYK_TOKEN variable saved in the variables section of my project. Finally, I run a `snyk test` command with the Snyk CLI and output the results to the JSON. This saves my results to the snyk_data_file.json, which I will parse in a script detailed in the next section and save to the required artifact file `gl-dependency-scanning-report.json`.\n\n```\nstages:\n  - test\n\nvariables:\n\ninclude:\n  - template: Jobs/Dependency-Scanning.gitlab-ci.yml  \n\ngemnasium-maven-dependency_scanning:\n  image: node:latest\n  stage: test\n  services:\n  - openjdk:11-jre-slim-buster\n  before_script:\n    - apt-get update\n    - apt-get install default-jdk -y\n  script:\n    # Install npm, snyk, and maven\n    - npm install -g npm@latest\n    - npm install -g snyk\n    - npm install maven\n    - npm install python3\n    # Run snyk auth, snyk monitor, snyk test to break build and out report\n    - snyk auth $SNYK_TOKEN\n    - chmod +x mvnw\n    - snyk test --all-projects --json-file-output=snyk_data_file.json || true\n    - python3 convert-snyk-to-gitlab.py\n\n  # Save report to artifacts\n  artifacts:\n    when: always\n    paths: \n      - gl-dependency-scanning-report.json\n\n```\n\n### Parse the JSON\n\nYou can see scan results in the merge request widget from any external scanner as long as the artifact of the successful security job is named appropriately, for example, `gl-dependency-scanning-report.json`.  \n\nHere is an example script that converts the Snyk JSON output to the GitLab JSON output. In this example, I open the Snyk data file and load the vulnerability data. I create a new list of dependency files and a new list of vulnerabilities that contain data GitLab needs to display in the vulnerability records, such as the identifier, severity, category, description, and location. I added a few placeholder sections for required fields that I did not need to display in my record. Finally, I saved the contents I parsed out to a new JSON file called `gl-dependency-scanning-report.json`, which is the required name for the file to be read by GitLab and have its contents displayed in the widget.\n\n```\nimport json\nfrom types import SimpleNamespace\n\nwith open(\"snyk_data_file.json\") as snyk_data_file:\n    snyk_data = json.load(snyk_data_file, object_hook=lambda d: SimpleNamespace(**d))\n\ngitlab_vulns = []\ndependency_files = []\nfor i in snyk_data:\n    dependency_files.append({\"path\": i.path, \"package_manager\": i.packageManager, \"dependencies\": []})\n    for v in i.vulnerabilities:\n        gitlab_identifiers = []\n        for vuln_type, vuln_names in v.identifiers.__dict__.items():\n            if vuln_names: \n                for vuln_name in vuln_names:\n                    gitlab_identifiers.append({\"type\": vuln_type, \"name\": vuln_name, \"value\": vuln_name.partition(\"-\")[2]})\n                gitlab_vulns.append({\"id\": v.id, \"category\": \"dependency_scanning\", \"severity\": v.severity.capitalize(), \"identifiers\": gitlab_identifiers, \"description\": v.description, \"location\": {\"file\": i.displayTargetFile, \"dependency\": {\"package\": {\"name\": \"PLACEHOLDER\"}, \"version\": \"PLACEHOLDER\"}}})\n\n# Dummy data for scan and dependency files \nfull_json = {\"version\": \"15.0.6\", \"dependency_files\": dependency_files, \"scan\": {\"analyzer\": {\"id\": \"snyk\", \"name\": \"Snyk\", \"vendor\": {\"name\": \"Snyk\"}, \"version\": \"1.0.2\"}, \"scanner\": {\"id\": \"my-snyk-scanner\", \"name\": \"My Snyk Scanner\", \"version\": \"1.0.2\", \"vendor\": {\"name\": \"Snyk\"}}, \"end_time\": \"2022-01-28T03:26:02\", \"start_time\": \"2020-01-28T03:26:02\", \"status\": \"success\", \"type\": \"dependency_scanning\"}, \"vulnerabilities\": gitlab_vulns}\n\nwith open(\"gl-dependency-scanning-report.json\", \"w\") as gitlab_file:\n    json.dump(full_json, gitlab_file, default=vars)\n\n```\n\nNow, the vulnerability findings are visible in the merge request widget.\n\n![security scanning detection](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098776/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750098776479.png)\n\n## What are SARIF and the SARIF converter?\n\nSARIF is a file format for the output of static analysis tools. It is incredibly useful when leveraging different security scanners, as all of their output is formatted the same way. This allows for a generic, repeatable, and scalable approach to application security.\n\nThere is a community-maintained [SARIF converter](https://gitlab.com/ignis-build/sarif-converter), which takes SARIF files and converts them into ingestible reports. It supports many scanners, including Snyk. This converter works for both SAST and code quality findings. We are going to focus on SAST for this blog.\n\n### Use a SARIF converter to get SAST results\n\nTo leverage the SARIF results, first I trigger a Snyk scan as we did in the previous example, but I save the output to a SARIF file. After this, I use the aforementioned converter to create a new JSON file that I save as a report.\n\n```\nsnyk:\n  image: node:latest\n  stage: test\n  services:\n  - openjdk:11-jre-slim-buster\n  before_script:\n    - apt-get update\n    - apt-get install default-jdk -y\n    - wget -O sarif-converter https://gitlab.com/ignis-build/sarif-converter/-/releases/permalink/latest/downloads/bin/sarif-converter-linux\n    - chmod +x sarif-converter\n  script:\n    # Install npm, snyk, and maven\n    - npm install -g npm@latest\n    - npm install -g snyk\n    - npm install maven\n    # Run snyk auth, snyk monitor, snyk test to break build and out report\n    - snyk auth $SNYK_TOKEN\n    - chmod +x mvnw\n    - snyk test --all-projects --sarif-file-output=snyk.sarif  || true\n    - ./sarif-converter --type sast snyk.sarif snyk.json\n\n  artifacts:\n    reports:\n      sast: snyk.json\n\n```\n\nAfter saving the JSON as an artifact, the results are visible in the merge request widget.\n\n![security scanning - image 2](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750098776/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750098776479.png)\n\n## Get started\n\nIn this blog post, you learned how to use both custom scripting and a SARIF converter to view external scanner vulnerabilities in the GitLab merge request widget. These operations can be completed from the pipeline as shown, but also from compliance pipelines and pipeline execution policies, which allow for the enforcement of external scanners. With GitLab Ultimate. you have access to a full DevSecOps platform that allows you to use our scanners or bring your own, but build a shift-left workflow that empowers developers to remediate vulnerabilities before they hit production.\n\n> [Trial GitLab Ultimate today](https://gitlab.com/-/trials/) to begin merging external scanners.\n\n## More security scanning resources\n\n* [Security scanner integration documentation](https://docs.gitlab.com/ee/development/integrations/secure.html)\n* [How to integrate custom security scanners into GitLab](https://about.gitlab.com/blog/how-to-integrate-custom-security-scanners-into-gitlab/)\n* [GitLab Trust Center](https://about.gitlab.com/security/)\n",[708,798,9],{"slug":1612,"featured":6,"template":686},"integrate-external-security-scanners-into-your-devsecops-workflow","content:en-us:blog:integrate-external-security-scanners-into-your-devsecops-workflow.yml","Integrate External Security Scanners Into Your Devsecops Workflow","en-us/blog/integrate-external-security-scanners-into-your-devsecops-workflow.yml","en-us/blog/integrate-external-security-scanners-into-your-devsecops-workflow",{"_path":1618,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1619,"content":1625,"config":1632,"_id":1634,"_type":13,"title":1635,"_source":15,"_file":1636,"_stem":1637,"_extension":18},"/en-us/blog/kubecon-na-2019-are-you-about-to-break-prod",{"title":1620,"description":1621,"ogTitle":1620,"ogDescription":1621,"noIndex":6,"ogImage":1622,"ogUrl":1623,"ogSiteName":672,"ogType":673,"canonicalUrls":1623,"schema":1624},"KubeCon NA: Are you about to break Prod?","Use Pulumi and GitLab to build a pipeline that validates your application, infrastructure, and deployment process.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666262/Blog/Hero%20Images/default-blog-image.png","https://about.gitlab.com/blog/kubecon-na-2019-are-you-about-to-break-prod","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"KubeCon NA: Are you about to break Prod?\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Erin Krengel, Pulumi\"}],\n        \"datePublished\": \"2020-01-27\",\n      }",{"title":1620,"description":1621,"authors":1626,"heroImage":1622,"date":1628,"body":1629,"category":1630,"tags":1631},[1627],"Erin Krengel, Pulumi","2020-01-27","\n\nA couple of months ago, my [Pulumi](https://www.pulumi.com/) colleague Sean Holung, staff sofware engineer, and I had the opportunity to present [\"Are you about to break prod? Acceptance Testing with Ephemeral Environments\"](https://www.youtube.com/watch?v=jAQhDZiRzBQ) at KubeCon NA 2019. In this talk, we covered what is an ephemeral environment, how to create one, and then we walked the audience through a concrete example. Given our limited time, we had to move quickly through a ton of information. This post will recap our presentation and add a few more details we weren't able to cover.\n\nAs software engineers, our job is to deliver business value. To do this, we need to be delivering software both quickly and reliably.\n\nSo the question we ask you is: are you about to break prod? Everyone will break production at some point because there are things we miss. As independent software lead Alexandra Johnson sums up so well in a tweet: \"Failures are part of the cost of building and shipping large systems.\" Building a robust pipeline allows us to move quickly in the case of failure and gain confidence around making changes to our infrastructure and applications.\n\n{::options parse_block_html=\"false\" /}\n\n\u003Cdiv class=\"center\">\n\n\u003Cblockquote class=\"twitter-tweet\">\u003Cp lang=\"en\" dir=\"ltr\">Big takeaway from \u003Ca href=\"https://twitter.com/hashtag/KubeCon?src=hash&amp;ref_src=twsrc%5Etfw\">#KubeCon\u003C/a>: none of us want to break prod, but failures are part of the cost of building and shipping large systems. Using tools like \u003Ca href=\"https://twitter.com/hashtag/AcceptanceTesting?src=hash&amp;ref_src=twsrc%5Etfw\">#AcceptanceTesting\u003C/a> (\u003Ca href=\"https://twitter.com/eckrengel?ref_src=twsrc%5Etfw\">@eckrengel\u003C/a>) and \u003Ca href=\"https://twitter.com/hashtag/ChaosEngineering?src=hash&amp;ref_src=twsrc%5Etfw\">#ChaosEngineering\u003C/a> (\u003Ca href=\"https://twitter.com/Ana_M_Medina?ref_src=twsrc%5Etfw\">@Ana_M_Medina\u003C/a>) can increase your confidence in your infrastructure changes!\u003C/p>&mdash; Alexandra Johnson (@alexandraj777) \u003Ca href=\"https://twitter.com/alexandraj777/status/1198373475049623552?ref_src=twsrc%5Etfw\">November 23, 2019\u003C/a>\u003C/blockquote> \u003Cscript async src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\">\u003C/script>\n\n\u003C/div>\n\nWith this in mind, we use Pulumi and GitLab to build a pipeline that validates both our application, infrastructure, and deployment process. \n\n## Ephemeral environments\n\nWhat is an ephemeral environment? It is a short-lived environment that mimics a production environment. To maintain agility, boundaries are defined in the environment to only encompass the first-level dependencies of the particular microservice that is being deployed. It means you don't have to spin up every single microservice or piece of infrastructure that's running in production. Yet you may need to spin up extra pieces of infrastructure to properly test the microservice. For example, you may need to create a subscription to pull from a PubSub topic your microservice writes to. This subscription would allow your acceptance tests to pull from a topic in order to validate an outbound message is published.\n\n## Why this is important\n\nInfrastructure is a key part of an application's behavior. The architecture and requirements are continually evolving. How can you incorporate these into a testing suite to give us a high degree of confidence?\n\nEphemeral environments allow you to integrate infrastructure and deployment processes into a testing suite. They ensure your testing environment is always in-sync with production and therefore allow you to iterate quickly to meet new requirements.\n\nEphemeral environments also encourage you to lean on automated tests over manual tests. If you use ephemeral environments as a replacement for a testing environment, there is not enough time to go in and run a manual check. Shifting your mindset to automated tests can be challenging, yet it's imperative that we do so. Automated tests guarantee your application behaves as expected today as well as months from now when you're out on vacation.\n\n## Our demo application\n\nTo demonstrate the effectiveness of integrating acceptance testing with ephemeral environments into your deployment process, we created a simple demo application. The service is written in Go and accepts a message on the `/message` endpoint, then places it in a storage bucket and sends a notification about the new object on a PubSub topic. The code for this application lives in our [main.go](https://gitlab.com/rocore/demo-app/blob/master/main.go) file. While you can walk through this code yourself, the most important thing to call out is that our application is *configurable*. This means we take configuration in at the very beginning of our main function and shut down the application if the values are not present.\n\n```go\nfunc main() {\n    ...\n\t// Get configuration from environment variables. These are\n\t// required configuration values, so we use an helper\n\t// function get the values and exit if the value is not set.\n\tproject := getConfigurationValue(\"PROJECT\")\n\ttopicName := getConfigurationValue(\"TOPIC\")\n\tbucketName := getConfigurationValue(\"BUCKET\")\n    ...\n}\n\nfunc getConfigurationValue(envVar string) string {\n\tvalue := os.Getenv(envVar)\n\tif value == \"\" {\n\t\tlog.Fatalf(\"%s not set\", envVar)\n\t}\n\tlog.Printf(\"%s: %s\", envVar, value)\n\treturn value\n}\n```\n\n### Infrastructure\n\nThere are many pieces of infrastructure to spin up and we can use Pulumi to easily wire it all together. Our architecture looks like this:\n\n![Pulumi Architecture](https://about.gitlab.com/images/blogimages/pulumidemoarch.jpg){: .medium.center}\n\nYou can check out the Pulumi code that we use to reproduce both our ephemeral environments as well as production in the [infrastructure/index.ts](https://gitlab.com/rocore/demo-app/blob/master/infrastructure/index.ts) file. The neat thing about using Pulumi is that we can create the Google Cloud Platform (GCP) resources we need and then directly reference them in our Kubernetes deployment. Using Pulumi ensures we're always configuring our application with the correct GCP resources for that environment.\n\nFor example, in our Kubernetes deployment, we set the environment variables by using the topic and bucket variables created just above.\n\n```typescript\n// Create a K8s Deployment for our application.\nconst appLabels = { appClass: name };\nconst deployment = new k8s.apps.v1.Deployment(name, {\n    metadata: { labels: appLabels },\n    spec: {\n        selector: { matchLabels: appLabels },\n        template: {\n            metadata: { labels: appLabels },\n            spec: {\n                containers: [{\n                    ...\n                    env: [\n                        { name: \"TOPIC\", value: topic.name }, // referencing topic just created\n                        { name: \"BUCKET\", value: bucket.name }, // referencing bucket just created\n                        { name: \"PROJECT\", value: project },\n                        {\n                            name: \"GOOGLE_APPLICATION_CREDENTIALS\",\n                            value: \"/var/secrets/google/key.json\"\n                        },\n                    ],\n                    ...\n                }]\n            }\n        }\n    },\n});\n```\n\n### Acceptance tests\n\nThe acceptance tests validate that our service, when stood up, functions as expected. They are run against an ephemeral environment. The tests live in the `acceptance/acceptance_test.go` [file](https://gitlab.com/rocore/demo-app/blob/master/acceptance/acceptance_test.go). You'll notice we're once again using the helper function `getConfigurationValue`. Our acceptance test must also be configured to ensure they're validating against the correct resources for that particular ephemeral environment.\n\nSince the service is only accessible from within the Kubernetes cluster, we use a Kubernetes job to run our acceptance tests. Using a Kubernetes job is a good technique to use when your CI is running externally, such as from GitLab, and you do not want to expose your service publicly. Our ephemeral environment plus acceptance test looks like this:\n\n![Acceptance Tests](https://about.gitlab.com/images/blogimages/pulumiacceptancetestarch.jpg){: .medium.center}\n \nWe spin up a Kubernetes Job and additional resources by using an if statement at the bottom of our `infrastructure/index.ts` file. The conditional depends on the environment's name as follows:\n\n```typescript\n// If it's a test environment, set up acceptance tests.\nlet job: k8s.batch.v1.Job | undefined;\nif (ENV.startsWith(\"test\")) {\n    job = acceptance.setupAcceptanceTests({\n        ...\n    });\n}\n\n// Export the acceptance job name, so we can get the logs from our\n// acceptance tests.\nexport const acceptanceJobName = job ? job.metadata.name : \"unapplicable\";\n```\n\nThat covers all the major aspects of our application and infrastructure, and if you'd like to view the code in detail, it is available in our `demo-app` [GitLab repository](https://gitlab.com/rocore/demo-app).\n\n## Our pipeline\n\nWhen developing a new service, we must establish a solid deployment strategy upfront. We want to make sure we're building in quality from day one. As we develop the service, we can add acceptance tests for every feature we add while the context and requirements are still fresh in our minds. This ensures we have thorough coverage of our app's functionality.\n\nWe used GitLab to set up our pipeline. We chose GitLab because it's straightforward to set up and allows us to run our pipeline on our Docker image of choice. We use a [base-image](https://gitlab.com/rocore/global-infra/blob/master/base-image/Dockerfile) that has all our dependencies installed and then reference that Docker image and tag in our `demo-app` pipeline. The Docker image allows us to bundle and version the dependencies for building our application and infrastructure.\n\n![GitLab Pipelines](https://about.gitlab.com/images/blogimages/pulumibloggitlabci.png){: .shadow.medium.center}\n \n1. **Test and Build** - This runs our unit tests and builds both our application and acceptance test images. To build our images, we used [Kaniko](https://github.com/GoogleContainerTools/kaniko), a tool for building images within a container or Kubernetes cluster. GitLab has excellent documentation on [how to incorporate Kaniko](https://docs.gitlab.com/ee/ci/docker/using_kaniko.html) into your pipeline. The application image is an immutable image that is used for both running our acceptance tests and deploying to production.\n1. **Acceptance Test** - This is what spins up our ephemeral environments and runs our acceptance tests. This acts as a quality gate catching issues before production.\n\n    Our ephemeral environment and Kubernetes job are all spun up in the `script` portion of the acceptance test job definition. We do a bit of setup for our new acceptance test stack and then run `pulumi up`. Here is the print out from our acceptance tests.\n\n    ```bash\n    ...\n    $ pulumi stack init rocore/$ENV-app\n    Logging in using access token from PULUMI_ACCESS_TOKEN\n    Created stack 'rocore/test-96425413-app'\n    $ pulumi config set DOCKER_TAG $DOCKER_TAG\n    $ pulumi config set ENV $ENV\n    $ pulumi config set gcp:project rocore-k8s\n    $ pulumi config set gcp:zone us-west1-a\n    $ pulumi up --skip-preview\n    Updating (rocore/test-96425413-app):\n    ...\n    Resources:\n        + 16 created\n\n    Duration: 4m10s\n\n    Permalink: https://app.pulumi.com/rocore/demo-app/test-96425413-app/updates/1\n    ```\n\n    The `after_script` destroys our stack as well as prints the logs of both our Kubernetes job and deployment, which help with debugging if our tests were to fail. We use the `after_script` to make sure that we always clean up and print logs even when our acceptance tests fail.\n    \n    ```bash\n    ...\n    $ pulumi stack select rocore/$ENV-app\n    $ kubectl logs -n rocore --selector=appClass=$ENV-demo-app-acc-test --tail=200\n    === RUN   TestSimpleHappyPath\n    === RUN   TestSimpleHappyPath/message_is_sent_to_PubSub_topic\n    === RUN   TestSimpleHappyPath/message_is_stored_in_bucket\n    ","open-source",[9,966,731,108,230,276],{"slug":1633,"featured":6,"template":686},"kubecon-na-2019-are-you-about-to-break-prod","content:en-us:blog:kubecon-na-2019-are-you-about-to-break-prod.yml","Kubecon Na 2019 Are You About To Break Prod","en-us/blog/kubecon-na-2019-are-you-about-to-break-prod.yml","en-us/blog/kubecon-na-2019-are-you-about-to-break-prod",{"_path":1639,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1640,"content":1646,"config":1652,"_id":1654,"_type":13,"title":1655,"_source":15,"_file":1656,"_stem":1657,"_extension":18},"/en-us/blog/microcks-and-gitlab-part-one",{"title":1641,"description":1642,"ogTitle":1641,"ogDescription":1642,"noIndex":6,"ogImage":1643,"ogUrl":1644,"ogSiteName":672,"ogType":673,"canonicalUrls":1644,"schema":1645},"Speed up API and microservices delivery with Microcks and GitLab - Part 1","Learn how to configure Microcks for GitLab and what the use cases are for this open source Kubernetes-native tool.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683021/Blog/Hero%20Images/lightsticks.png","https://about.gitlab.com/blog/microcks-and-gitlab-part-one","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Speed up API and microservices delivery with Microcks and GitLab - Part 1\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Madou Coulibaly\"}],\n        \"datePublished\": \"2023-09-27\",\n      }",{"title":1641,"description":1642,"authors":1647,"heroImage":1643,"date":1649,"body":1650,"category":705,"tags":1651},[1648],"Madou Coulibaly","2023-09-27","\n\nAPI development is all the rage these days for customer and partner integration, frontend-to-backend communication, microservices orchestration, and more. Yet APIs have their challenges, including how to create a fast feedback loop on design, how different teams can work with autonomy without having to wait for each other's API implementation, and how to cope with backward compatibility tests when shipping newer versions of the API. \n\n[Microcks](https://microcks.io), an open source, Kubernetes-native tool for API mocking and testing, addresses these challenges. With Microcks, which is accepted as a Sandbox project in the [Cloud Native Computing Foundation](https://cncf.io), developers can leverage their [OpenAPI](https://www.openapis.org/), [GraphQL](https://graphql.org/), [gRPC](https://grpc.io/), [AsyncAPI](https://www.asyncapi.com/), and [Postman Collection](https://www.postman.com/collection/) assets to quickly mock and simulate APIs before writing them. Couple Microcks with GitLab and you have a powerful combination to foster collaboration, encourage rapid changes, and provide a robust delivery platform for API-based applications.\n\nIn this ongoing blog series, we will introduce you to Microcks use cases and how they fit with the GitLab platform. We'll also discuss technical integration points that will help ease the developer burden, including identity management, Git repositories, and pipeline integrations.\n\n## What is Microcks?\nMicrocks addresses two major use cases: \n- **Simulating (or mocking) an API or a microservice** from a set of descriptive assets. This can be done as soon as you start the design phase to set up a feedback loop very quickly, or later on to ease the pain of provisioning environments with a lot of dependencies.\n- **Validating the conformance of your application regarding your API specification** by running contract-test. This validation can be integrated into your CI/CD pipeline so that conformance can be checked on each and every iteration. This is of great help to enforce backward compatibility of your API of microservices interfaces.\n\nMicrocks offers a uniform and consistent approach for the various kinds of request/response APIs (REST, GraphQL, gRPC, Soap) and event-driven APIs (currently supporting eight different protocols), thereby bringing consistency for users and for automations all along your API lifecycle.\n\n## How Microcks fits into the software development lifecycle\nMicrocks is a solution based on containers and can be deployed in several configurations. It can be deployed on the developer laptop through [Docker](https://microcks.io/documentation/installing/docker-compose/), [Podman](https://microcks.io/documentation/installing/podman-compose/) or [Docker Desktop Extension](https://microcks.io/documentation/installing/docker-desktop-extension/) to assist with mocking complex environments. When it comes to team collaboration, Microcks can be deployed as a centralized instance that connects to the Git repositories of the organization, discovers the API artifacts, and then provides shared up-to-date API simulations.\n\n![diagram of how Microcks fits into development lifecycle](https://about.gitlab.com/images/blogimages/2023-09-27-microcks-and-gitlab-part-1-speed-up-api-and-microservices-delivery/microcks.png){: .shadow.small.center}\n\nTo ease the burden on developers (and administrators), Microcks can be configured to use your GitLab platform as an identity provider. With that configuration, integrating Microcks is seamless, and API simulations are automatically shared among development teams. Microcks fosters collaboration by providing everyone with the same “source of truth” and avoiding drift risks. The tool can also be used to lower the pain and the cost of deploying and maintaining complex QA environments because simulations are inexpensive to deploy or redeploy on-demand. Microcks deployment follows a GitOps approach.\n\nBeyond this sharing of simulations, Microcks also integrates well with CI/CD pipelines. As you release API-based applications, there is always concern about conformance of the contractualized expectations you defined using specifications like OpenAPI, GraphQL, and the like. Usually, the hardest part isn't delivering the `1.0` of this API; problems come later when you're trying to deliver the `1.3`. This latest version must still be backward compatible with the 1.0 contract if you don't want to make your consumers angry and frustrated.\n\nThis conformance validation is very well assured by Microcks using contract-testing principles. So we encourage you to plug Microcks into some `test` related jobs in your GitLab pipeline and delegate this conformance validation to your Microcks instance.\n\n![microcks-in-gitlab-workflow](https://about.gitlab.com/images/blogimages/2023-09-27-microcks-and-gitlab-part-1-speed-up-api-and-microservices-delivery/microcks-in-gitlab-workflow.png){: .shadow.medium.center}\n\n\nEmbedding Microcks conformance testing in your pipeline is actually easy thanks to our lightweight CLI that you'll integrate in pipeline jobs. You can choose to reuse an existing Microcks instance to record results and keep history of your success or pop up a new ephemeral instance as it's lightweight and fast to bootstrap.\n\n## How to set up GitLab as an identity provider in Microcks\n\nTo start off this series, we will detail how to configure Microcks to use your GitLab platform as an identity provider. This is in fact very easy as authentication in Microcks is based on [Keycloak](https://keycloak.org) (another CNCF project) and GitLab can be set as an identity provider in Keycloak (see [official documentation](https://www.keycloak.org/docs/latest/server_admin/index.html#gitlab)).\n\n**Note:** This configuration is optional as Microcks can use any other identity provider Keycloak integrates with.\n\nKeycloak is a very common solution that may be deployed already at your organization. If not, Microcks comes with a Keycloak distribution that is pre-configured for its usage with a realm called `microcks`. We have used this realm to validate this configuration.\n\n### Create a GitLab Group Application\nThe first thing is to create a new [Group Application](https://docs.gitlab.com/ee/integration/oauth_provider.html#create-a-group-owned-application) on your GitLab instance as follows:\n- `Name`: `microcks-via-keycloak`\n- `Redirect URI`: `https://keycloak.acme.org/realms/microcks/broker/gitlab/endpoint`\n- `Scopes`: `read_user`, `openid`, `profile` and `email`\n\n![gitlab-application-form](https://about.gitlab.com/images/blogimages/2023-09-27-microcks-and-gitlab-part-1-speed-up-api-and-microservices-delivery/gitlab-application-form.png){: .shadow.medium.center}\n\n\nThis application uses your Keycloak instance with `https://keycloak.acme.org/realms/microcks/broker/gitlab/endpoint` as the redirect URI. As a result, we obtain an `Application ID` and an associated `Secret` we have to keep aside for the next step.\n\n![gitlab-application](https://about.gitlab.com/images/blogimages/2023-09-27-microcks-and-gitlab-part-1-speed-up-api-and-microservices-delivery/gitlab-application.jpeg){: .shadow.medium.center}\n\n\n### Add GitLab as identity provider in Keycloak\nThe next step takes place in the Keycloak admin console. Once the correct `microcks` realm is selected, you'll just have to go to the **Identity providers** section and add a GitLab provider. Simply paste here the `Application ID` you got earlier as `Client ID` and the `Secret` as `Client Secret`. You can also choose a `Display order` if you plan to have multiple identity providers.\n\n![keycloak-identity-provider](https://about.gitlab.com/images/blogimages/2023-09-27-microcks-and-gitlab-part-1-speed-up-api-and-microservices-delivery/keycloak-identity-provider.jpg){: .shadow.medium.center}\n\n\nThen, from the **Authentication** section in the admin console, choose the browser flow and configure the `Identity Provider Redirector` as follows:\n\n- `Alias`: `GitLab`\n- `Default Identify Provider`: `gitlab`\n\n![keycloak-redirector](https://about.gitlab.com/images/blogimages/2023-09-27-microcks-and-gitlab-part-1-speed-up-api-and-microservices-delivery/keycloak-redirector.jpg){: .shadow.medium.center}\n\n### Test your Microcks configuration\nNow open the Microcks URL into your browser and you'll be directly redirected to the GitLab login page. Enter your GitLab credentials and you will be authenticated and redirected to Microcks. \n\n![microcks-homepage](https://about.gitlab.com/images/blogimages/2023-09-27-microcks-and-gitlab-part-1-speed-up-api-and-microservices-delivery/microcks-homepage.jpeg){: .shadow.medium.center}\n\n## What's next?\nIn upcoming blogs, we'll detail how GitLab can be used in the two major use cases for Microcks. We'll see how Microcks integrates with GitLab Git repositories to discover API specifications and produce simulations, and how to integrate Microcks conformance tests into your GitLab CI/CD pipelines.\n\n_[Laurent Broudoux](https://www.linkedin.com/in/laurentbroudoux/) is a cloud-native architecture expert and enterprise integration problem lover. He has helped organizations in adopting distributed and cloud paradigms while capitalizing on their critical existing assets. He is the founder and lead developer of the [Microcks.io](https://microcks.io/) open-source project: a Kubernetes-native tool for API mocking and testing. For this, he is using his 10+ years experience as an architect in financial services where he defined API transformation strategies, including governance and delivery process._\n\n_[Madou Coulibaly](https://gitlab.com/madou) is a senior solutions architect at GitLab._\n",[1148,9,108,1249,230],{"slug":1653,"featured":6,"template":686},"microcks-and-gitlab-part-one","content:en-us:blog:microcks-and-gitlab-part-one.yml","Microcks And Gitlab Part One","en-us/blog/microcks-and-gitlab-part-one.yml","en-us/blog/microcks-and-gitlab-part-one",{"_path":1659,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1660,"content":1666,"config":1672,"_id":1674,"_type":13,"title":1675,"_source":15,"_file":1676,"_stem":1677,"_extension":18},"/en-us/blog/microservices-integrated-solution",{"title":1661,"description":1662,"ogTitle":1661,"ogDescription":1662,"noIndex":6,"ogImage":1663,"ogUrl":1664,"ogSiteName":672,"ogType":673,"canonicalUrls":1664,"schema":1665},"Tackling the microservices repository explosion challenge","Microservices have spawned an explosion of dependent projects with multiple repos, creating the need for an integrated solution – we're working on it right now.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662898/Blog/Hero%20Images/microservices-explosion.jpg","https://about.gitlab.com/blog/microservices-integrated-solution","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"It's raining repos: The microservices repo explosion, and what we're doing about it\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Aricka Flowers\"}],\n        \"datePublished\": \"2018-11-26\",\n      }",{"title":1667,"description":1662,"authors":1668,"heroImage":1663,"date":1669,"body":1670,"category":705,"tags":1671},"It's raining repos: The microservices repo explosion, and what we're doing about it",[1224],"2018-11-26","\nGone are the days of \"set it and forget it\"-style software development. The increased demand for code and operations on all projects, especially [microservices](/topics/microservices/), means more repos. This calls for a more integrated solution to incorporate testing, security updates, monitoring, and more, says GitLab CEO [Sid Sijbrandij](/company/team/#sytses):\n\n>\"The bar's going up for software development. It's no longer enough to just write the code; you also have to write the tests. It's no longer enough to just ship it; you also have to monitor it. You can no longer make it once and forget about it; you have to stay current with security updates. For every product you make you have to integrate more of these tools. It used to be that only the big projects got all these things, but now every single service you ship should have these features, because other projects are dependent on it. One security vulnerability can be enough to take a company down.\"\n\nAn increasing number of project repos means exponential growth in the number of tools needed to handle them – bad news for those saddled managing project dependencies. A streamlined workflow is essential to alleviate this burden – here's how we want to help you get there.\n\n### Everything under one roof\n\n\"With GitLab, we want to enable you to simply commit your code and have all the tools you need integrated out of the box,\" Sid said. \"You don't have to do anything else. It's monitored; we measure whether your dependencies have a vulnerability and fix it for you automatically. I think that's the big benefit of GitLab; that you don't have to go into stitching together 10 tools for every project that you make.\"\n\nBy using an integrated solution to manage an ever-growing number of microservices, you can avoid having engineers siloed off with their respective teams and tools. Creating visibility among teams and getting rid of the need for handoffs leads to a faster [DevOps lifecycle](/topics/devops/) while also ensuring that your projects deploy and remain stable, Sid explains.\n\n\"Our customers that switched from a fragmented setup and were only able to get projects through that cycle a few times a year are now deploying a few times a week,\" Sid said. \"The ability to go from planning to monitoring it in production is what GitLab brings to the table. We have an ample amount of customer case studies showing how we helped improve their speed.\"\n\n### Better support for microservices\n\nWe are boning up our support of microservices, and have a number of features in the works to improve this area, including [group level Kubernetes clusters](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758), a [global Docker registry browser](https://gitlab.com/gitlab-org/gitlab-ce/issues/49336), and adding the [ability to define multiple pipelines](https://gitlab.com/gitlab-org/gitlab-ce/issues/22972). This is to build on what's already there:\n\n\"We have great support for microservices. GitLab has [multi-project pipelines](/blog/use-multiproject-pipelines-with-gitlab-cicd/) and [can trigger pipelines from multi-projects via API](https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html),\" Sid detailed. \"The CI Working Group of the CNCF (Cloud Native Computing Foundation), the most cloud native organization in the world probably, uses GitLab to test their projects. We've got great support for things like [Kubernetes](/solutions/kubernetes/) and cloud native technologies. In GitLab, every project you have can be attached to a Kubernetes cluster, and GitLab uses that to run everything that’s going on. We know that a lot of our users and customers are using microservices, and we work great with them.\"\n\n### Future focus: best-in-class solutions\n\nGitLab is much more than just version control. Having started with the planning, creating and verifying stages in 2011 and 2012, we’ve had time to make those capabilities very strong. We are now strengthening our offerings in the other steps of the DevOps lifecycle: managing, packaging, releasing, configuring, monitoring and security.\n\n\"We are seeing enormous progress in those areas, but they can't go head to head with the best-in-class solutions just yet. So that's going be the theme for GitLab next year, to make sure each of our solutions is best in class instead of just the three things we started with,\" Sid says. \"And we won't take our eyes off the ball.\"\n\n[Cover image](https://unsplash.com/photos/wplxPRCF7gA) by [Ruben Bagues](https://unsplash.com/@rubavi78) on Unsplash\n{: .note}\n",[774,230,966,708,9],{"slug":1673,"featured":6,"template":686},"microservices-integrated-solution","content:en-us:blog:microservices-integrated-solution.yml","Microservices Integrated Solution","en-us/blog/microservices-integrated-solution.yml","en-us/blog/microservices-integrated-solution",{"_path":1679,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1680,"content":1686,"config":1692,"_id":1694,"_type":13,"title":1695,"_source":15,"_file":1696,"_stem":1697,"_extension":18},"/en-us/blog/migration-guide-github-advanced-security-to-gitlab-ultimate",{"title":1681,"description":1682,"ogTitle":1681,"ogDescription":1682,"noIndex":6,"ogImage":1683,"ogUrl":1684,"ogSiteName":672,"ogType":673,"canonicalUrls":1684,"schema":1685},"Migration guide: GitHub Advanced Security to GitLab Ultimate","Understand the similarities and differences between GitLab Ultimate and GitHub Advanced Security. Then follow this in-depth tutorial to make the move to the GitLab DevSecOps platform.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666187/Blog/Hero%20Images/blog-image-template-1800x945__6_.png","https://about.gitlab.com/blog/migration-guide-github-advanced-security-to-gitlab-ultimate","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Migration guide: GitHub Advanced Security to GitLab Ultimate\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Fernando Diaz\"}],\n        \"datePublished\": \"2024-05-01\",\n      }",{"title":1681,"description":1682,"authors":1687,"heroImage":1683,"date":1688,"body":1689,"category":708,"tags":1690},[962],"2024-05-01","GitLab is the most comprehensive AI-powered DevSecOps platform, enabling organizations to deliver more secure software faster with one platform for your entire software delivery lifecycle. GitHub provides an Advanced Security add-on, which enables additional security features within GitHub. However, it lacks the depth and breadth of security features provided natively by GitLab. Organizations looking to migrate to GitLab Ultimate to enhance their security across all areas of the SDLC can use this guide to compare the two offerings and as a tutorial to move to the GitLab platform.\n\nThis article includes:\n\n- [A comparison between GitLab Ultimate and GitHub Advanced Security](#a-comparison-between-gitlab-ultimate-and-github-advanced-security)\n- [How to migrate a GitHub repository to GitLab](#how-to-migrate-a-github-repository-to-gitlab)\n- [How to migrate from GitHub Advanced Security to GitLab Ultimate feature-by-feature](#how-to-migrate-feature-by-feature)\n- [An introduction to additional GitLab Ultimate's security features](#additional-gitlab-ultimate-security-features)\n\n## A comparison between GitLab Ultimate and GitHub Advanced Security\n\n[GitLab Ultimate](https://about.gitlab.com/pricing/ultimate/) is GitLab's top subscription tier for enterprises looking to deliver secure software faster. GitHub Advanced Security is an add-on to GitHub Enterprise, which enables additional security features.\n\n### Similarities between GitLab Ultimate and GitHub Advanced Security\n\nGitLab Ultimate and GitHub Advanced Security both provide:\n- Static Application Security Testing ([SAST](https://docs.gitlab.com/ee/user/application_security/sast/)), secret scanning, and dependency scanning\n- contextual vulnerability intelligence and resolution advice\n- a list of dependencies or software bill of materials ([SBOM](https://about.gitlab.com/blog/the-ultimate-guide-to-sboms/))\n- security metrics and insights\n\n### Differences between GitLab Ultimate and GitHub Advanced Security\n\nGitLab Ultimate differs from GitHub Advanced Security in the following ways:\n\n- GitLab natively provides additional code scanners such as container scanning, Dynamic Application Security Testing ([DAST](https://docs.gitlab.com/ee/user/application_security/dast/)), Web API fuzz testing, and more. These scanners are a mix of optimized proprietary and open source technologies with custom rulesets. For a full list, see the [GitLab AppSec documentation](https://docs.gitlab.com/ee/user/application_security/secure_your_application.html).\n- GitLab provides [granular security guardrails](https://docs.gitlab.com/ee/user/application_security/policies/) to prevent insecure code from being merged without approval.\n- GitLab security scanners can be run in [air-gapped or limited-connectivity environments](https://docs.gitlab.com/ee/user/application_security/offline_deployments/).\n- GitLab provides the [Compliance Center](https://docs.gitlab.com/ee/user/compliance/compliance_center/), which enables oversight of compliance violations across an entire organization.\n\nGitLab Ultimate also provides additional security and compliance capabilities, portfolio and value stream management, live upgrade assistance, and more. See the [GitLab Ultimate documentation](https://about.gitlab.com/pricing/ultimate/) to learn more about these additional features.\n\n## How to migrate a GitHub repository to GitLab\n\nGitLab provides a built-in importer, which allows you to import your GitHub projects from either GitHub.com or GitHub Enterprise to GitLab. The importer allows you to migrate not only the GitHub Repository to GitLab, but several other objects, including issues, collaborators (members), and pull requests. For a complete list of what can be migrated, see the [GitHub imported data documentation](https://docs.gitlab.com/ee/user/project/import/github.html#imported-data). You can perform the migration as follows:\n1. On the left sidebar, at the top, select **Create new (+)**.\n2. Select **New project/repository** under the **In GitLab** section.\n3. Select **Import project**.\n\n![Import project selection](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/1-Import-Project.png)\n\n4. Press the **GitHub** button.\n    - If using GitLab self-managed, then you must [enable the GitHub importer](https://docs.gitlab.com/ee/administration/settings/import_and_export_settings.html#configure-allowed-import-sources).\n    - Note that other importers can be initiated in the same way.\n5. Now, you can do one of the following:\n    - Authorize with GitHub Oauth by selecting **Authorize with GitHub**.\n    - Use a GitHub personal access token:\n       - Go to [https://github.com/settings/tokens/new](https://github.com/settings/tokens/new).\n       - In the **Note** field, enter a token description.\n       - Select the **repo** scope.\n       - Optionally, to import Collaborators, select the **read:org** scope.\n       - Press the **Generate token** button.\n       - On the GitLab import page, in the Personal Access Token field, paste the GitHub personal access token.\n6. Press the **Authenticate** button.\n7. Select the items you wish to migrate.\n8. Select the projects you wish to migrate and to where.\n9. Press the **Import** button.\n\nYour imported project should now be in your workspace. For additional guidance on migrating from GitHub to GitLab, watch this video:\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/0Id5oMl1Kqs?si=HEpZVy94cpfPfAky\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\nYou can also perform the migration using a [GitHub personal access token](https://docs.gitlab.com/ee/user/project/import/github.html#use-a-github-personal-access-token) or the [GitLab REST API](https://docs.gitlab.com/ee/user/project/import/github.html#use-the-api). The importer also allows importing from other sources such as Bitbucket or Gitea. To learn more, read the [importer documentation](https://docs.gitlab.com/ee/user/project/import/).\n\n## How to migrate feature-by-feature\n\nLet’s go over how to leverage each feature provided by GitHub Advanced Security in GitLab Ultimate. You must have a [GitLab Ultimate license](https://about.gitlab.com/pricing/ultimate/) to continue. GitLab provides a [free 30-day trial](https://about.gitlab.com/free-trial/devsecops/) to get you started.\n\n### Code scanning\nGitHub provides code scanning to provide contextual vulnerability intelligence and advice for static source code. The same can be done within GitLab by enabling [SAST](https://docs.gitlab.com/ee/user/application_security/sast/). GitLab SAST scanners cover a wider set of programming languages and frameworks than GitHub’s [CodeQL](https://docs.github.com/en/code-security/code-scanning/introduction-to-code-scanning/about-code-scanning-with-codeql#about-codeql).\n\nTo enable code scanning in GitLab, you can simply add the [SAST template](https://docs.gitlab.com/ee/user/application_security/sast/#configure-sast-in-your-cicd-yaml) to your `.gitlab-ci.yml`:\n\n```yaml\ninclude:\n  - template: Jobs/SAST.gitlab-ci.yml\n```\n\nOnce the template has been added, any time new code is checked in, SAST will auto-detect the [programming languages](https://docs.gitlab.com/ee/user/application_security/sast/#supported-languages-and-frameworks ) used in your project. It will then scan the source code for known vulnerabilities.\n\n**Note:** Security scanners can also be added to your project using GitLab's [security configuration](https://docs.gitlab.com/ee/user/application_security/configuration/), which can automatically create a merge request to update your pipeline. To learn more, see the [Configure SAST by using the UI documentation](https://docs.gitlab.com/ee/user/application_security/sast/#configure-sast-by-using-the-ui).\n\nSAST results of the diff between the feature-branch and the target-branch display in the merge request widget. The merge request widget displays SAST results and resolutions that were introduced by the changes made in the merge request.\n\n![Security scanning in merge request](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/2-SAST-MR-View.png)\n\nEach vulnerability displays data to assist with remediation, including detailed description, severity, location, and resolution information:\n\n![SAST vulnerability details](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/3-SAST-MR-View-Detailed.png)\n\nYou can take action on these vulnerabilities:\n\n- **Dismiss vulnerability**: Allows a developer to dismiss the vulnerability with a comment. This assists the security team performing a review.\n- **Create issue**: Allows an issue to be created to keep track of a vulnerability that requires additional oversight.\n\nThese changes can also be seen inline when changing to the **Changes** view within the merge request.\n\n![SAST vulnerability changes view](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/4-SAST-MR-View-Changes.png)\n\n#### Customizing SAST scanners\n\nGitLab allows you to override a SAST job definition so you can change properties like variables, dependencies, or rules. You can do this by declaring a job with the same name as the SAST job to override. Then, place this new job after the template inclusion and specify any additional keys under it.\n\nFor example, the following configuration:\n- overwrites the version the `semgrep-sast` scanner uses\n- runs a script to fetch modules from private projects before running `gosec-sast`\n- configures all scanners to search at a maximum depth of 10\n\n```yaml\ninclude:\n  - template: Jobs/SAST.gitlab-ci.yml\n\nvariables:\n  SEARCH_MAX_DEPTH: 10\n\nsemgrep-sast:\n  variables:\n    SAST_ANALYZER_IMAGE_TAG: \"3.7\"\n\ngosec-sast:\n  before_script:\n    - |\n      cat \u003C\u003CEOF > ~/.netrc\n      machine gitlab.com\n      login $CI_DEPLOY_USER\n      password $CI_DEPLOY_PASSWORD\n      EOF\n```\n\n**Note:** The available SAST jobs can be found in the [`SAST.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/SAST.gitlab-ci.yml). Configurations can be found in the [Available SAST CI/CD variables documentation](https://docs.gitlab.com/ee/user/application_security/sast/#available-cicd-variables).\n\n#### Customizing SAST rulesets\n\nFor each SAST analyzer, GitLab processes the code then uses rules to find possible weaknesses in source code. These rules determine what types of weaknesses the scanner reports.\n\n- For Semgrep-based SAST scanners, GitLab creates, maintains, and supports the rules that are used. It combines the Semgrep open source engine, GitLab-managed detection rules, and GitLab proprietary technology for vulnerability tracking and false positive detection.\n- For other SAST analyzers, the rules are defined in the upstream projects for each scanner.\n\nYou can customize the behavior of the SAST scanners by defining a ruleset configuration file in the repository being scanned:\n- Disable predefined rules (available for all analyzers)\n- Override predefined rules (available for all analyzers)\n- Replace predefined rules by synthesizing a custom configuration using passthroughs\n\nFor more information and examples on configuring SAST rules, see the [SAST rules](https://docs.gitlab.com/ee/user/application_security/sast/rules.html) and [Customizing rulesets documentation](https://docs.gitlab.com/ee/user/application_security/sast/customize_rulesets.html).\n\n### Secret scanning\n\nGitHub provides secret scanning, which can find, block, and revoke leaked secrets. The same can be done within GitLab by enabling [Secret Detection](https://docs.gitlab.com/ee/user/application_security/secret_detection/).\n\nTo enable Secret Detection in GitLab, you can simply add the following template to your `.gitlab-ci.yml`:\n\n```yaml\ninclude:\n  - template: Jobs/Secret-Detection.gitlab-ci.yml\n```\n\nOnce the template has been added, any time new code is checked in (or a pipeline is run), the secret scanner will scan the source code for known secrets. Pipeline Secret Detection scans different aspects of your code, depending on the situation. For all methods except the “Default branch”, Pipeline Secret Detection scans commits, not the working tree. See the [Secret detection coverage documentation](https://docs.gitlab.com/ee/user/application_security/secret_detection/pipeline/#coverage) to learn more about how secret scanning works.\n\nWhen creating a merge request, Secret Detection scans every commit made on the source branch. Just like in SAST, each detected vulnerability provides the following information (such as location) and identifiers to assist with the remediation process:\n\n![Secret Detection vulnerability details](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/5-Secret-Detection-MR-Detailed.png)\n\nSimilar to SAST, you can take action on these vulnerabilities straight from the merge request, including dismissing vulnerabilities, and creating issues.\n\n#### Customizing Secret Detection jobs\n\nGitLab allows you to override a Secret Detection job definition so you change properties like variables, dependencies, or rules. You can do this by declaring a job with the same name as the Secret Detection job. Then place this new job after the template inclusion and specify any additional keys under it. For example, the following configuration:\n\n- overwrites the stage the secret detection job runs on to `security`\n- enables the historic scanning\n- changes the Secrets Analyzer version to 4.5\n\n```yaml\ninclude:\n  - template: Jobs/Secret-Detection.gitlab-ci.yml\n\nsecret_detection:\n  stage: security\n  variables:\n    SECRET_DETECTION_HISTORIC_SCAN: \"true\"\n    SECRETS_ANALYZER_VERSION: \"4.5\"\n```\n\n**Note:** The available Secret Detection jobs can be found in the [SAST.gitlab-ci.yml template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Jobs/Secret-Detection.gitlab-ci.yml). Available configurations can be found in the [Available Secret Detection CI/CD variables documentation](https://docs.gitlab.com/ee/user/application_security/secret_detection/pipeline/#customizing-analyzer-settings).\n\n#### Customizing Secret Detection rulesets\n\nThe Secret Detection analyzer allows you to customize which secrets are reported in the GitLab UI. The following customization options can be used separately, or in combination:\n\n- disable predefined rules\n- override predefined rules\n- synthesize a custom configuration\n- specify a remote configuration file\n\nFor example, by creating the file `.gitlab/secret-detection-ruleset.toml`, in the root directory of your project, the default GitLeaks package is extended to ignore test tokens from detection:\n\n```yaml\n### extended-gitleaks-config.toml\ntitle = \"extension of gitlab's default gitleaks config\"\n\n[extend]\n### Extends default packaged path\npath = \"/gitleaks.toml\"\n\n[allowlist]\n  description = \"allow list of test tokens to ignore in detection\"\n  regexTarget = \"match\"\n  regexes = [\n    '''glpat-1234567890abcdefghij''',\n  ]\n```\n\nFor more information on overriding the predefined analyzer rules, check out the [Secret Detection documentation](https://docs.gitlab.com/ee/user/application_security/secret_detection/pipeline/#override-predefined-analyzer-rules).\n\n#### Automatic response to leaked secrets\n\nGitLab Secret Detection automatically responds when it finds certain types of leaked secrets. Automatic responses can:\n- automatically revoke the secret\n- notify the partner that issued the secret and the partner can then revoke the secret, notify its owner, or otherwise protect against abuse\n\nGitLab can also notify partners when credentials they issue are leaked in public repositories on GitLab.com. If you operate a cloud or SaaS product and you’re interested in receiving these notifications, you can implement a Partner API, which is called by the GitLab Token Revocation API.\n\nSee the [Automatic response to leaked secrets documentation](https://docs.gitlab.com/ee/user/application_security/secret_detection/automatic_response.html) to learn more.\n\n### Supply chain security\n\nGitHub enables you to secure, manage, and report on software supply chains with automated security and version updates and one-click SBOMs. GitLab can meet your supply chain security needs using the Dependency Scanning and Dependency List (SBOM) features.\n\nTo enable Dependency Scanning in GitLab, you can simply add the following template to your `.gitlab-ci.yml`:\n\n```yaml\ninclude:\n  - template: Jobs/Dependency-Scanning.gitlab-ci.yml\n```\n\nOnce the template has been added, any time new code is checked in, Dependency Scanning will auto-detect the [package managers](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#supported-languages-and-package-managers) used in your project. It will then scan the dependencies used for known vulnerabilities.\n\nDependency Scanning results of the diff between the feature-branch and the target-branch display in the merge request widget. The merge request widget displays Dependency Scanning results and resolutions that were introduced by the changes made in the merge request. Within a merge request, each vulnerability displays relevant information to assist with remediation such as identifiers, evidence, and solutions:\n\n![Dependency Scanner vulnerability details](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/6-Dependency-Scanner-MR-View-Detailed.png)\n\nSimilar to SAST and Secret Detection, you can take action on these vulnerabilities straight from the merge request, including dismissing vulnerabilities and creating issues.\n\n#### Configuring Dependency Scanning\n\nTo override a job definition (for example, to change properties like variables or dependencies), declare a new job with the same name as the one to override. Place this new job after the template inclusion and specify any additional keys under it. For example, the following code:\n\n- disables automatic remediation of vulnerable dependencies\n- requires a build job to complete before Dependency Scanning\n\n```yaml\ninclude:\n  - template: Jobs/Dependency-Scanning.gitlab-ci.yml\n\ngemnasium-dependency_scanning:\n  variables:\n    DS_REMEDIATE: \"false\"\n  dependencies: [\"build\"]\n```\n\nTo learn more about configuring the dependency scanners, see the [Customizing analyzer behavior documentation](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/#customizing-analyzer-behavior).\n\n#### Generating an SBOM\n\nGitLab provides a Dependency List (SBOM) to review your project or group dependencies and key details about those dependencies, including their known vulnerabilities. This list is a collection of dependencies in your project, including existing and new findings. The Dependency List is generated after the dependency scanner runs successfully on the [default branch](https://docs.gitlab.com/ee/user/project/repository/branches/default.html). To access the Dependency List:\n\n1. On the left sidebar, select **Search or go to** and find your project.\n2. Select **Secure > Dependency List**.\n\n![Dependency list (SBOM)](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/7-Dependency-List.png)\n\nFrom here you can see the following information on your dependencies:\n\n| Field\t| Description |\n| ----- | ----------- |\n| Component\t| The dependency’s name and version. |\n| Packager | The packager used to install the dependency. |\n| Location | For system dependencies, this lists the image that was scanned. For application dependencies, this shows a link to the packager-specific lock file in your project that declared the dependency. It also shows the dependency path to a top-level dependency, if any, and if supported. |\n| License | Links to dependency’s software licenses. A warning badge that includes the number of vulnerabilities detected in the dependency. |\n| Projects | Links to the project with the dependency. If multiple projects have the same dependency, the total number of these projects is shown. To go to a project with this dependency, select the Project's number, then search for and select its name. The project search feature is supported only on groups that have up to 600 occurrences in their group hierarchy. |\n\n\u003Cp>\u003C/p>\n\nSee the [Dependency List documentation](https://docs.gitlab.com/ee/user/application_security/dependency_list/) to learn more.\n\n### Security and compliance administration\n\nGitHub Advanced Security allows you to view security metrics and insights and assess code security risk. Now let’s examine how to do the same with GitLab Ultimate.\n\n#### Viewing security metrics and insights\n\nGitLab provides [Security dashboards](https://docs.gitlab.com/ee/user/application_security/security_dashboard/) to help assess the security posture of your applications. These dashboards display a collection of metrics, ratings, and charts for the vulnerabilities detected by the security scanners run on your project:\n\n- vulnerability trends over a 30-, 60-, or 90-day timeframe for all projects in a group\n- a letter grade rating for each project based on vulnerability severity\n- the total number of vulnerabilities detected within the past 365 days, including their severity\n\nTo access the Security dashboard:\n\n1. On the left sidebar, select **Search or go to** and find your project or group.\n2. From the side tab, select **Secure > Security** dashboard.\n3. Filter and search for what you need.\n\nThe group view displays your security posture for all projects in your group:\n\n![Group Security dashboard](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/8-SD-Group.png)\n\nThe project view displays your security posture for just the project:\n\n![Project Security dashboard](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/9-SD-Project.png)\n\n#### Assess code security risk\n\nGitLab Ultimate features a [Vulnerability Report](https://docs.gitlab.com/ee/user/application_security/vulnerability_report/), which provides information about vulnerabilities from scans of the default branch. It contains cumulative results of all successful jobs, regardless of whether the pipeline was successful. At all levels, the Vulnerability Report contains:\n\n- totals of vulnerabilities per severity level\n- filters for common vulnerability attributes\n- details of each vulnerability, presented in tabular layout\n\n![Vulnerability Report](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/10-Vulnerability-Report.png)\n\nClicking on a vulnerability enables access to its [Vulnerability Page](https://docs.gitlab.com/ee/user/application_security/vulnerabilities/), which contains details of the vulnerability including a description, location, identifiers, and more. Below is an example of the Vulnerability Page for an SQL Injection vulnerability detected by our SAST scanner:\n\n![SQL Injection Vulnerability Page](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/11-Vulnerability-Page-1.png)\n\nFrom here the security team can collaborate by [changing the status of a vulnerability](https://docs.gitlab.com/ee/user/application_security/vulnerabilities/#change-the-status-of-a-vulnerability) along with a reason and [creating issues to better track changes](https://docs.gitlab.com/ee/user/application_security/vulnerabilities/#create-a-gitlab-issue-for-a-vulnerability).\n\nFrom the Vulnerability Page, you can also leverage [GitLab Duo](https://about.gitlab.com/gitlab-duo/), our AI-powered suite of features, to explain the vulnerability and [automatically create a merge request that resolves the vulnerability](https://docs.gitlab.com/ee/user/application_security/vulnerabilities/#vulnerability-resolution).\nGitLab Duo's [Vulnerability Explanation](https://docs.gitlab.com/ee/user/application_security/vulnerabilities/#vulnerability-explanation) uses a large language model to:\n\n- summarize the vulnerability.\n- help developers and security analysts to understand the vulnerability, how it could be exploited, and how to fix it\n- provide a suggested mitigation\n\n![SQL Injection GitLab Duo AI explanation](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/13-Explain-Vulnerability.png)\n\n## Additional GitLab Ultimate security features\n\nGitLab Ultimate contains many more security features that cannot be found within GitHub Advanced Security. A few examples of these additional security features are: additional security scanners for the complete software development lifecycle (SDLC), granular security guardrails, and custom permissions.\n\n### Security scanners for the entire SDLC\n\nOur portfolio of security scanners extends spans the SDLC.\n\n| Scanner Name | Scans | Languages/Files scanned |\n|  -------------- | ----- | ------------------------- |\n| [Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) | Static source code | C/C++, Java, Python, Go, JavaScript, C#, and more |\n| [Dynamic Application Security Testing (DAST)](https://docs.gitlab.com/ee/user/application_security/dast/) | Running web application, live API | Language-agnostic |\n| [Infrastructure as Code (IaC) Scanning](https://docs.gitlab.com/ee/user/application_security/iac_scanning/) | IaC files |Terraform, AWS Cloud Formation, Ansible, and more |\n| [Container Scanning](https://docs.gitlab.com/ee/user/application_security/container_scanning/) | Static and running container images | Dockerfile |\n| [Dependency Scanning and License Scanning](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/) | Application dependencies | Requirements.txt, Yarn, Gradle, Npm, and more |\n| [Web API Fuzz Testing](https://docs.gitlab.com/ee/user/application_security/api_fuzzing/) | Sends random/malformed data to web-api | OpenAPI, GraphQL, HAR, Postman Collection |\n| [Coverage-guided Fuzz Testing](https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/) | Sends random/malformed data to function | C/C++, Go, Swift, Python, Rust, Java, JavaScript, AFL |\n\n\u003Cp>\u003C/p>\n\nGitLab also allows you to integrate [third-party scanners](https://about.gitlab.com/blog/integrate-external-security-scanners-into-your-devsecops-workflow/) and [custom scanners](https://about.gitlab.com/blog/how-to-integrate-custom-security-scanners-into-gitlab/) into the platform. Once integrated, the scanner results are automatically presented in various places in GitLab, such as the Pipeline view, merge request widget, and Security dashboard. See the [Security Scanner Integration documentation](https://docs.gitlab.com/ee/development/integrations/secure.html) to learn more.\n\n### Granular security and compliance policies\n\nPolicies in GitLab provide security and compliance teams with [a way to enforce controls globally in their organization](https://about.gitlab.com/blog/meet-regulatory-standards-with-gitlab/). Security teams can ensure:\n\n- security scanners are enforced in development team pipelines with proper configuration\n- all scan jobs execute without any changes or alterations\n- proper approvals are provided on merge requests based on results from those findings\n\n![Merge Request Security Policies](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/14-MR-Policy.png)\n\nCompliance teams can centrally enforce multiple approvers on all merge requests and ensure various settings are enabled on projects in scope of organizational requirements, such as enabling or locking merge request and repository settings. To learn more see the [GitLab Security Policy](https://docs.gitlab.com/ee/user/application_security/policies/) documentation.\n\n### Custom roles and granular permissions\n\n[GitLab Ultimate provides custom roles](https://about.gitlab.com/blog/how-to-tailor-gitlab-access-with-custom-roles/), which allow an organization to create user roles with the precise privileges and permissions required for that organization’s needs.\n\nFor example, a user could create a “Security Auditor” role with permissions to view security vulnerabilities in the system, but not be able to view source code, nor perform any changes within the repository. This granular set of permissions enables well-defined separation of duties.\n\n![Custom role creation](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/15-Custom-Roles.png)\n\nTo learn more see the [Custom Roles](https://docs.gitlab.com/ee/user/custom_roles.html) and [available Granular Permissions](https://docs.gitlab.com/ee/user/custom_roles/abilities.html) documentation.\n\n### Compliance Center\n\nThe Compliance Center is the central location for compliance teams to manage their compliance standards’ adherence reporting, violations reporting, and compliance frameworks for their group. The Compliance Center includes the following:\n\n- [Compliance standards adherence dashboard](https://docs.gitlab.com/ee/user/compliance/compliance_center/compliance_standards_adherence_dashboard.html) lists the adherence status of projects complying to the GitLab standard.\n- [Compliance violations report](https://docs.gitlab.com/ee/user/compliance/compliance_center/compliance_violations_report.html) shows a high-level view of merge request activity for all projects in the group.\n- [Compliance frameworks report](https://docs.gitlab.com/ee/user/compliance/compliance_center/compliance_frameworks_report.html) shows all the compliance frameworks in a group.\n- [Compliance projects report](https://docs.gitlab.com/ee/user/compliance/compliance_center/compliance_projects_report.html) shows the compliance frameworks that are applied to projects in a group.\n\n![Compliance Center](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749674404/Blog/Content%20Images/16-Compliance-Center.png)\n\nThese dashboards assist with making sure separation of duties is being followed to optimize compliance within your organization. To learn more see the [Compliance Center documentation](https://docs.gitlab.com/ee/user/compliance/compliance_center/).\n\n## Read more\n\nThis article covers only a portion of the wide range of security features GitLab Ultimate offers. Check out these resources to learn more about how GitLab Ultimate can help enhance your organizational security and developer efficiency:\n\n- [Why GitLab Ultimate](https://about.gitlab.com/pricing/ultimate/)\n- [Getting Started with DevSecOps Tutorial](https://gitlab-da.gitlab.io/tutorials/security-and-governance/devsecops/simply-vulnerable-notes/)\n- [Getting Started with DevSecOps Sample Project](https://gitlab.com/gitlab-da/tutorials/security-and-governance/devsecops/simply-vulnerable-notes)\n- [Import your project from GitHub to GitLab documentation](https://docs.gitlab.com/ee/user/project/import/github.html)\n- [Migrating from GitHub Actions documentation](https://docs.gitlab.com/ee/ci/migration/github_actions.html)\n- [Tutorial: Create and run your first GitLab CI/CD pipeline](https://docs.gitlab.com/ee/ci/quick_start/)\n- [Tutorial: Create a complex pipeline](https://docs.gitlab.com/ee/ci/quick_start/tutorial.html)\n- [CI/CD YAML syntax reference](https://docs.gitlab.com/ee/ci/yaml/)",[798,1691,708,481,9],"zero trust",{"slug":1693,"featured":90,"template":686},"migration-guide-github-advanced-security-to-gitlab-ultimate","content:en-us:blog:migration-guide-github-advanced-security-to-gitlab-ultimate.yml","Migration Guide Github Advanced Security To Gitlab Ultimate","en-us/blog/migration-guide-github-advanced-security-to-gitlab-ultimate.yml","en-us/blog/migration-guide-github-advanced-security-to-gitlab-ultimate",{"_path":1699,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1700,"content":1706,"config":1712,"_id":1714,"_type":13,"title":1715,"_source":15,"_file":1716,"_stem":1717,"_extension":18},"/en-us/blog/moving-to-headless-chrome",{"title":1701,"description":1702,"ogTitle":1701,"ogDescription":1702,"noIndex":6,"ogImage":1703,"ogUrl":1704,"ogSiteName":672,"ogType":673,"canonicalUrls":1704,"schema":1705},"How GitLab switched to Headless Chrome for testing","A detailed explanation with examples of how GitLab made the switch to headless Chrome.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749680270/Blog/Hero%20Images/headless-chrome-cover.jpg","https://about.gitlab.com/blog/moving-to-headless-chrome","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How GitLab switched to Headless Chrome for testing\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Mike Greiling\"}],\n        \"datePublished\": \"2017-12-19\",\n      }",{"title":1701,"description":1702,"authors":1707,"heroImage":1703,"date":1709,"body":1710,"category":705,"tags":1711},[1708],"Mike Greiling","2017-12-19","\n\nGitLab recently switched from PhantomJS to headless Chrome for both our\nfrontend tests and our RSpec feature tests. In this post we will detail the\nreasons we made this transition, the challenges we faced, and the solutions we\ndeveloped. We hope this will benefit others making the switch.\n\n\u003C!-- more -->\n\nWe now have a truly accurate way to test GitLab within a real, modern browser.\nThe switch has improved our ability to write tests and debug them while running\nthem directly in Chrome. Plus the change forced us to confront and clean up a\nnumber of hacks we had been using in our tests.\n\n## Switching to headless Chrome from PhantomJS: background\n\n[PhantomJS](http://phantomjs.org) has been a part of GitLab's test framework\n[for almost five years](https://gitlab.com/gitlab-org/gitlab-ce/commit/ba25b2dc84cc25e66d6fa1450fee39c9bac002c5).\nIt has been an immensely useful tool for running browser integration tests in a\nheadless environment at a time when few options were available. However, it\nhad some shortcomings:\n\nThe most recent version of PhantomJS (v2.1.1) is compiled with a three-year-old\nversion of [QtWebKit](https://trac.webkit.org/wiki/QtWebKit) (a fork of WebKit\nv538.1 according to the user-agent string). This puts it on par with something\nlike Safari 7 on macOS 10.9. It resembles a real modern browser, but it's not\nquite there. It has a different JavaScript engine, an older rendering engine,\nand a host of missing features and quirks.\n\nAt this time, GitLab supports [the current and previous major\nrelease](https://docs.gitlab.com/ee/install/requirements.html#supported-web-browsers) of\nFirefox, Chrome, Safari, and Microsoft Edge/IE. This puts PhantomJS and its\ncapabilities somewhere near or below our lowest common denominator. Many modern\nbrowser features either [do not work](http://phantomjs.org/supported-web-standards.html),\nor [require vendor prefixes](http://phantomjs.org/tips-and-tricks.html) and\npolyfills that none of our supported browsers require. We could selectively\nadd these polyfills, prefixes, and other workarounds just within our test\nenvironment, but doing so would increase technical debt, cause confusion, and\nmake the tests less representative of a true production environment. In most\ncases we had opted to simply omit them or hack around them (more on this\n[later](#trigger-method)).\n\nHere's a screenshot of the way PhantomJS renders a page from GitLab, followed\nby the same page rendered in Google Chrome:\n\n![Page Rendered by PhantomJS](https://about.gitlab.com/images/blogimages/moving-to-headless-chrome/render-phantomjs.png){: .shadow.center}\n\n![Page Rendered by Google Chrome](https://about.gitlab.com/images/blogimages/moving-to-headless-chrome/render-chrome.png){: .shadow.center}\n\nYou can see in PhantomJS the filter tabs are rendered horizontally, the icons\nin the sidebar render on their own lines, the global search field is\noverflowing off the navbar, etc.\n\nWhile it looks ugly, in most cases we could still use this to run functional\ntests, so long as elements of the page remain visible and clickable, but this\ndisparity with the way GitLab rendered in a real browser did introduce several\nedge cases.\n\n## What is headless Chrome\n\nIn April of this year, [news spread](https://news.ycombinator.com/item?id=14101233)\nthat Chrome 59 would support a [native, cross-platform headless\nmode](https://www.chromestatus.com/features/5678767817097216). It was\npreviously possible to simulate a headless Chrome browser in CI/CD [using\nvirtual frame buffer](https://gist.github.com/addyosmani/5336747), but this\nrequired a lot of memory and extra complexities. A native headless mode is a\ngame changer. It is now possible to run integration tests in a headless\nenvironment on a real, modern web browser that our users actually use!\n\nSoon after this was revealed, Vitaly Slobodin, PhantomJS's chief developer,\nannounced that the project [would no longer be\nmaintained](https://github.com/ariya/phantomjs/issues/15105#issuecomment-322850178):\n\n\u003Cdiv class=\"center\">\n\n\u003Cblockquote class=\"twitter-tweet\" data-cards=\"hidden\" data-lang=\"en\">\u003Cp lang=\"en\" dir=\"ltr\">This is the end - \u003Ca href=\"https://t.co/GVmimAyRB5\">https://t.co/GVmimAyRB5\u003C/a>\u003Ca href=\"https://twitter.com/hashtag/phantomjs?src=hash&amp;ref_src=twsrc%5Etfw\">#phantomjs\u003C/a> 2.5 will not be released. Sorry, guys!\u003C/p>&mdash; Vitaly Slobodin (@Vitalliumm) \u003Ca href=\"https://twitter.com/Vitalliumm/status/852450027318464513?ref_src=twsrc%5Etfw\">April 13, 2017\u003C/a>\u003C/blockquote>\n\u003Cscript async src=\"https://platform.twitter.com/widgets.js\" charset=\"utf-8\">\u003C/script>\n\n\u003C/div>\n\nIt became clear that we would need to make the transition away from PhantomJS at\nsome point, so we [opened up an issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/30876),\ndownloaded the Chrome 59 beta, and started looking at options.\n\n### Frontend tests (Karma)\n\nOur frontend test suite utilizes the [Karma](http://karma-runner.github.io/)\ntest runner, and updating this to work with Google Chrome was surprisingly\nsimple ([here's the merge request](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12036)).\nThe [karma-chrome-launcher](https://github.com/karma-runner/karma-chrome-launcher)\nplugin was very quickly updated to support headless mode starting from\n[version 2.1.0](https://github.com/karma-runner/karma-chrome-launcher/releases/tag/v2.1.0),\nand it was essentially a drop-in replacement for the PhantomJS launcher. Once\nwe [re-built our CI/CD build images](https://gitlab.com/gitlab-org/gitlab-build-images/merge_requests/41)\nto include Google Chrome 59 (and fiddled around with some pesky timeout\nsettings), it worked!  We were also able to remove some rather ugly\nPhantomJS-specific hacks that Jasmine required to spy on some built-in browser\nfunctions.\n\n### Backend feature tests (RSpec + Capybara)\n\nOur feature tests use RSpec and [Capybara](https://github.com/teamcapybara/capybara)\nto perform full end-to-end integration testing of database, backend, and\nfrontend interactions. Before switching to headless Chrome, we had used\n[Poltergeist](https://github.com/teampoltergeist/poltergeist) which is a\nPhantomJS driver for Capybara. It would spin up a PhantomJS browser instance\nand direct it to browse, fill out forms, and click around on pages to verify\nthat everything behaved as it should.\n\nSwitching from PhantomJS to Google Chrome required a change in drivers from\nPoltergeist to Selenium and [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/).\nSetting this up was pretty straightforward. You can install ChromeDriver on\nmacOS with `brew install chromedriver` and the process is similar on any given\npackage manager in Linux. After this we added the `selenium-webdriver` gem to\nour test dependencies and configured Capybara like so:\n\n```ruby\nrequire 'selenium-webdriver'\n\nCapybara.register_driver :chrome do |app|\n  options = Selenium::WebDriver::Chrome::Options.new(\n    args: %w[headless disable-gpu no-sandbox]\n  )\n  Capybara::Selenium::Driver.new(app, browser: :chrome, options: options)\nend\n\nCapybara.javascript_driver = :chrome\n```\n\nGoogle says the [`disable-gpu` option is necessary for the time\nbeing](https://developers.google.com/web/updates/2017/04/headless-chrome#cli)\nuntil some bugs are resolved. The `no-sandbox` option also appears to be\nnecessary to get Chrome running inside a Docker container for [GitLab's CI/CD\nenvironment](/topics/ci-cd/). Google provides a [useful guide for working with headless Chrome\nand Selenium](https://developers.google.com/web/updates/2017/04/headless-chrome).\n\nIn our final implementation we changed this to conditionally add the `headless`\noption unless you have `CHROME_HEADLESS=false` in your environment. This makes\nit easy to disable headless mode while debugging or writing tests. It's also\npretty fun to watch tests execute on the browser window in real time:\n\n```shell\nexport CHROME_HEADLESS=false\nbundle exec rspec spec/features/merge_requests/filter_merge_requests_spec.rb\n```\n\n![Tests Executing in Chrome](https://about.gitlab.com/images/blogimages/moving-to-headless-chrome/headlessless-chrome-tests.gif){: .shadow.center}\n\n### What is the differences between Poltergeist and Selenium?\n\nThe process of switching drivers here was not nearly as straightforward as\nit was with the frontend test suite. Dozens of tests started failing as soon\nas we changed our Capybara configuration, and this was due to some major\ndifferences in the way Selenium/ChromeDriver implemented Capybara's driver API\ncompared to Poltergeist/PhantomJS. Here are some of the challenges we ran into:\n\n1.  **JavaScript modals are no longer accepted automatically**\n\n    We often use JavaScript `confirm(\"Are you sure you want to do X?\");` click\n    events when performing a destructive action such as deleting a branch or\n    removing a user from a group. Under Poltergeist a `.click` action would\n    automatically accept modals like `alert()` and `confirm()`, but under\n    Selenium, you now need to wrap these with one of `accept_alert`,\n    `accept_confirm`, or `dismiss_confirm`. e.g.:\n\n    ```ruby\n    # Before\n    page.within('.some-selector') do\n      click_link 'Delete'\n    end\n\n    # After\n    page.within('.some-selector') do\n      accept_confirm { click_link 'Delete' }\n    end\n    ```\n\n1.  **Selenium `Element.visible?` returns false for empty elements**\n\n    If you have an empty `div` or `span` that you want to access in your test,\n    Selenium does not consider these \"visible.\" This is not much of an issue\n    unless you set `Capybara.ignore_hidden_elements = true` as we do in our\n    feature tests. Where `find('.empty-div')` would have worked fine in\n    Poltergeist, we now need to use `visible: :any` to\n    select such elements.\n\n    ```ruby\n    # Before\n    find('.empty-div')\n\n    # After\n    find('.empty-div', visible: :any)\n    # or\n    find('.empty-div', visible: false)\n    ```\n\n    More on [Capybara and hidden elements](https://makandracards.com/makandra/7617-change-how-capybara-sees-or-ignores-hidden-elements).\n\n1.  {:#trigger-method} **Poltergeist's `Element.trigger('click')` method does not exist in Selenium**\n\n    In Capybara, when you use `find('.some-selector').click`, the element you\n    are clicking must be both visible and unobscured by any overlapping\n    element. Situations where links could not be clicked would sometimes occur\n    with Poltergeist/PhantomJS due to its poor CSS support sans-prefixes.\n    Here's one example:\n\n    ![Overlapping elements](https://about.gitlab.com/images/blogimages/moving-to-headless-chrome/overlapping-element.png){: .shadow.center}\n\n    The broken layout of the search form here was actually placing an invisible\n    element over the top of the \"Update all\" button, making it unclickable.\n    Poltergeist offers a `.trigger('click')` method to work around this.\n    Rather than actually clicking the element, this method would trigger a DOM\n    event to simulate a click. Utilizing this method was a bad practice, but\n    we ran into similar issues so often that many developers formed a habit\n    of using it everywhere. This began to lead to some lazy and sloppy test\n    writing. For instance, someone might use `.trigger` as a shortcut to click\n    on an link that was obscured behind an open dropdown menu, when a properly\n    written test should `.click` somewhere to close the dropdown, and _then_\n    `.click` on the item behind it.\n\n    Selenium does not support the `.trigger` method. Now that we were using a\n    more accurate rendering engine that won't break our layouts, many of these\n    instances could be resolved by simply replacing `.trigger('click')` with\n    `.click`, but due to some of the bad practice uses mentioned above, this\n    didn't always work.\n\n    There are of course some ways to hack a `.trigger` replacement. You could\n    simulate a click by focusing on an element and hitting the \"return\" key,\n    or use JavaScript to trigger a click event, but in most cases we decided to\n    take the time and actually correct these poorly implemented tests so that a\n    normal `.click` could again be used. After all, if our tests are meant to\n    simulate a real user interacting with the page, we should limit ourselves\n    to the actions a real user would be expected to use.\n\n    ```ruby\n    # Before\n    find('.obscured-link').trigger('click')\n\n    # After\n\n    # bad\n    find('.obscured-link').send_keys(:return)\n\n    # bad\n    execute_script(\"document.querySelector('.obscured-link').click();\")\n\n    # good\n    # do something to make link accessible, then\n    find('.link').click\n    ```\n\n1.  **`Element.send_keys` only works on focus-able elements**\n\n    We had a few places in our code where we would test out our keyboard\n    shortcuts using something like `find('.boards-list').native.send_keys('i')`.\n    It turns out Chrome will not allow you to `send_keys` to any element that\n    cannot be \"focused\", e.g. links, form elements, the document body, or\n    presumably anything with a tab index.\n\n    In all of the cases where we were doing this, triggering `send_keys` on the\n    body element would work since that's ultimately where our event handler was\n    listening anyway:\n\n    ```ruby\n    # Before\n    find('.some-div').native.send_keys('i')\n\n    # After\n    find('body').native.send_keys('i')\n    ```\n\n1.  **`Element.send_keys` does not support non-BMP characters (like emoji)**\n\n    In a few tests, we needed to fill out forms with emoji characters. With\n    Poltergeist we would do this like so:\n\n    ```ruby\n    # Before\n    find('#note-body').native.send_keys('@💃username💃')\n    ```\n\n    In Selenium we would get the following error message:\n\n    ```\n    Selenium::WebDriver::Error::UnknownError:\n        unknown error: ChromeDriver only supports characters in the BMP\n    ```\n\n    To work around this, we added [a JavaScript method to our test bundle that\n    would simulate input and fire off the same DOM events](https://gitlab.com/gitlab-org/gitlab-ce/blob/a8b9852837/app/assets/javascripts/test_utils/simulate_input.js)\n    that an actual keyboard input would generate on every keystroke, then\n    wrapped this with a [ruby helper](https://gitlab.com/gitlab-org/gitlab-ce/blob/a8b9852837/spec/support/input_helper.rb)\n    method that could be called like so:\n\n    ```ruby\n    # After\n    include InputHelper\n\n    simulate_input('#note-body', \"@💃username💃\")\n    ```\n\n1.  **Setting cookies is much more complicated**\n\n    It's quite common to want to set some cookies before `visit`ing a page that\n    you intend to test, whether it's to mock a user session, or toggle a\n    setting. With Poltergeist, this process is really simple. You can use\n    `page.driver.set_cookie`, provide a simple key/value pair, and it will just\n    work as expected, setting a cookie with the correct domain and scope.\n\n    Selenium is quite a bit more strict. The method is now\n    `page.driver.browser.manage.add_cookie`, and it comes with two caveats:\n\n    - You cannot set cookies until you `visit` a page in the domain you intend\n      to scope your cookies to.\n    - Annoyingly, you cannot alter the `path` parameter (or at least we could\n      never get this to work), so it is best to set cookies at the root path.\n\n    Before you `visit` your page, Chrome's url is technically sitting at\n    something like `about:blank;`. When you attempt to set a cookie there, it\n    will refuse because there is no hostname, and you cannot coerce one by\n    providing a domain as an argument. The [Selenium\n    documentation](http://docs.seleniumhq.org/docs/03_webdriver.jsp#cookies)\n    suggests that you do the following:\n\n    > If you are trying to preset cookies before you start interacting with a\n    > site and your homepage is large / takes a while to load, an alternative is\n    > to find a smaller page on the site (typically the 404 page is small, e.g.\n    > `http://example.com/some404page`).\n\n    ```ruby\n    # Before\n    before do\n      page.driver.set_cookie('name', 'value')\n    end\n\n    # After\n    before do\n      visit '/some-root-path'\n      page.driver.browser.manage.add_cookie(name: 'name', value: 'value')\n    end\n    ```\n\n1.  **Page request/response inspection methods are missing**\n\n    Poltergeist very conveniently implemented methods like `page.status_code`\n    and `page.response_headers` which are also present in Capybara's default\n    `RackTest` driver, making it easy to inspect the raw response from the\n    server, in addition to the way that response is rendered by the browser. It\n    also allowed you to inject headers into the requests made to the server,\n    e.g.:\n\n    ```ruby\n    # Before\n    before do\n      page.driver.add_header('Accept', '*/*')\n    end\n\n    it 'returns a 404 page'\n      visit some_path\n\n      expect(page.status_code).to eq(404)\n      expect(page).to have_css('.some-selector')\n    end\n    ```\n\n    Selenium does not implement these methods, and [the authors do not intend\n    to add support for them](https://github.com/seleniumhq/selenium-google-code-issue-archive/issues/141#issuecomment-191404986),\n    so we needed to develop a workaround. Several people have suggested running\n    a proxy alongside ChromeDriver that would intercept all traffic to and from\n    the server, but this seemed to us like overkill. Instead, we opted to\n    create a [lightweight Rack middleware](https://gitlab.com/gitlab-org/gitlab-ce/blob/a8b9852837/lib/gitlab/testing/request_inspector_middleware.rb)\n    and a corresponding [helper class](https://gitlab.com/gitlab-org/gitlab-ce/blob/a8b9852837/spec/support/inspect_requests.rb)\n    that would intercept the traffic for inspection. This is similar to our\n    [RequestBlockerMiddleware](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/testing/request_blocker_middleware.rb)\n    that we were already using to intelligently `wait_for_requests` to complete\n    within our tests. It works like this:\n\n    ```ruby\n    # After\n    it 'returns a 404 page'\n      requests = inspect_requests do\n        visit some_path\n      end\n\n      expect(requests.first.status_code).to eq(404)\n      expect(page).to have_css('.some-selector')\n    end\n    ```\n\n    Within the `inspect_requests` block, the Rack middleware will log all\n    requests and responses, and return them as an array for inspection. This\n    will include the page being `visit`ed as well as the subsequent XHR and\n    asset requests, but the initial path request will be the first in the array.\n\n    You can also inject headers using the same helper like so:\n\n    ```ruby\n    # After\n    inspect_requests(inject_headers: { 'Accept' => '*/*' }) do\n      visit some_path\n    end\n    ```\n\n    This middleware should be injected early in the stack to ensure any other\n    middleware that might intercept or modify the request/response will be\n    seen by our tests. We include this line in our test environment config:\n\n    ```ruby\n    config.middleware.insert_before('ActionDispatch::Static', 'Gitlab::Testing::RequestInspectorMiddleware')\n    ```\n\n1.  **Browser console output is no longer output to the terminal**\n\n    Poltergeist would automatically output any `console` messages directly into\n    the terminal in real time as tests were run. If you had a bug in the frontend\n    code that caused a test to fail, this feature would make debugging much\n    easier as you could inspect the terminal output of the test for an error\n    message or a stack trace, or inject your own `console.log()` into the\n    JavaScript to see what is going on. With Selenium this is sadly no longer the\n    case.\n\n    You can, however, collect browser logs by configuring Capybara like so:\n\n    ```ruby\n    capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(\n      loggingPrefs: {\n        browser: \"ALL\",\n        client: \"ALL\",\n        driver: \"ALL\",\n        server: \"ALL\"\n      }\n    )\n\n    # ...\n\n    Capybara::Selenium::Driver.new(\n      app,\n      browser: :chrome,\n      desired_capabilities: capabilities,\n      options: options\n    )\n    ```\n\n    This will allow you to access logs with the following, i.e. in the event of\n    a test failure:\n\n    ```ruby\n    page.driver.manage.get_log(:browser)\n    ```\n\n    This is far more cumbersome than it was in Poltergeist, but it's the best\n    method we've found so far. Thanks to [Larry Reid's blog post](http://technopragmatica.blogspot.com/2017/10/switching-to-headless-chrome-for-rails_31.html)\n    for the tip!\n\n## Results\n\nRegarding performance, we attempted to quantify the change with a\nnon-scientific analysis of 10 full-suite RSpec test runs _before_ this change,\nand 10 more runs from _after_ this change, factoring out any tests that were\nadded or removed between these pipelines. The end result was:\n\n**Before:** 5h 18m 52s\n**After:** 5h 12m 34s\n\nA savings of about six minutes, or roughly 2 percent of the total compute time, is\nstatistically insignificant, so I'm not going to claim we improved our test\nspeed with this change.\n\nWhat we did improve was test accuracy, and we vastly improved the tools at our\ndisposal to write and debug tests. Now, all of the Capybara screenshots\ngenerated when a CI/CD job fails look exactly as they do on your own browser\nrather than resembling the broken PhantomJS screenshot above. Inspecting a\nfailing test locally can now be done interactively by turning off headless\nmode, dropping a `byebug` line into the spec file, and watching the browser\nwindow as you type commands into the prompt. This technique proved extremely\nuseful while working on this project.\n\nYou can find all of the changes we made in [the original merge request page\non GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12244).\n\n## What are some additional uses for headless Chrome?\n\nWe have also been utilizing headless Chrome to analyze frontend performance, and have found it to be useful in detecting issues.\n\nWe'd like to make it easier for other companies to embrace as well, so as part of the upcoming 10.3 release of GitLab we are releasing [Browser Performance Testing](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html). Leveraging [GitLab CI/CD](/solutions/continuous-integration/), headless Chrome is launched against a set of pages and an overall performance score is calculated. Then for each merge request the scores are compared between the source and target branches, making it easier detect performance regressions prior to merge.\n\n## Acknowledgements\n\nI sincerely hope this information will prove useful to anybody else looking to\nmake the switch from PhantomJS to headless Chrome for their Rails application.\n\nThanks to the Google team for their very helpful documentation, thanks to the\nmany bloggers out there who shared their own experiences with hacking headless\nChrome in the early days of its availability, and special thanks to Vitaly\nSlobodin and the rest of the contributors to PhantomJS who provided us with an\nextremely useful tool that served us for many years. 🙇‍\n\n\u003Cstyle>\n\n.center {\n  text-align: center;\n  display: block;\n  margin-right: auto;\n  margin-left: auto;\n}\n\ncode, kbd {\n  font-size: 80%;\n}\n\n\u003C/style>\n",[881,1109,9],{"slug":1713,"featured":6,"template":686},"moving-to-headless-chrome","content:en-us:blog:moving-to-headless-chrome.yml","Moving To Headless Chrome","en-us/blog/moving-to-headless-chrome.yml","en-us/blog/moving-to-headless-chrome",{"_path":1719,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1720,"content":1726,"config":1733,"_id":1735,"_type":13,"title":1736,"_source":15,"_file":1737,"_stem":1738,"_extension":18},"/en-us/blog/one-devops-platform-can-help-you-achieve-devsecops",{"title":1721,"description":1722,"ogTitle":1721,"ogDescription":1722,"noIndex":6,"ogImage":1723,"ogUrl":1724,"ogSiteName":672,"ogType":673,"canonicalUrls":1724,"schema":1725},"One DevOps platform can help you achieve DevSecOps","GitLab drives innovation in the AST market to secure cloud-native applications.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749679348/Blog/Hero%20Images/locks.jpg","https://about.gitlab.com/blog/one-devops-platform-can-help-you-achieve-devsecops","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"One DevOps platform can help you achieve DevSecOps\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Sandra Gittlen\"}],\n        \"datePublished\": \"2022-05-09\",\n      }",{"title":1721,"description":1722,"authors":1727,"heroImage":1723,"date":1729,"body":1730,"category":708,"tags":1731},[1728],"Sandra Gittlen","2022-05-09","\n\nApplication security testing (AST) is a fast-moving and important area for software development. DevOps methodologies have spurred the need to integrate testing within the developer’s workflow. GitLab believes the more ingrained AST is in the software factory, the more secure applications will be and the easier it will be for companies to meet compliance demands. We believe our [strategic platform approach](/why-gitlab), where security and compliance are embedded in DevOps from planning to production, provides efficiency and value unmatched by traditional application security vendors.\n\nGartner® has named GitLab a Challenger in the [2022 Gartner Magic Quadrant™ for Application Security Testing](https://page.gitlab.com/resources-report-gartner-magic-quadrant-ast.html). According to Gartner, “a major driver for the evolution of the AST market is the need to support enterprise [DevSecOps](/topics/devsecops/) and cloud-native application initiatives.”\n\n“We are excited to see continued momentum for our unique approach that embeds security into the DevOps workflow,” says Hillary Benson, GitLab director of product management. This is the third year that GitLab has been recognized in the Gartner Magic Quadrant for Application Security Testing. “We believe that our recognition as a Challenger in the Magic Quadrant represents an evolving market understanding of the value of an approach that empowers and enables developers to find and fix vulnerabilities – and the simplicity of leveraging a DevOps platform to do so.”\n\n> **You can read more about the results and download a copy of the report by visiting [our commentary page](/analysts/gartner-ast22/).**\n\n\nGitLab’s complete DevOps platform approach provides automation needed by DevOps, along with policy and vulnerability management needed by security professionals. GitLab’s Ultimate tier provides an integrated, vetted, and managed set of scanners to meet the security and compliance needs of modern-day application development and [cloud-native](/topics/cloud-native/) environments. \n\n## A unique approach to AST\n\nWe continue to innovate in the application security space. Let’s look at how we’re different from many of the more traditional stand-alone AST technologies. It’s these very differences that provide benefits achievable by using a single platform for DevOps and security. For example: \n\nWe build comprehensive scans into the CI pipeline to enable a more interactive testing environment. This is a unique approach as others in the category focus their offering on instrumentation-based interactive AST. With GitLab, the developer gets a more complete view of the security flaws as they are created – when they are most efficiently resolved.\n\nSimilarly, while analysts place emphasis on lightweight spell-check-like SAST features, we have found that these features are less important to GitLab users, again because of our built-in approach. A metaphor may be helpful to explain. We are all accustomed to saving documents frequently so edits are not lost. Developers do the same while editing software. Changes made are “committed” frequently to the code repository. Upon hitting the ‘commit’ button, GitLab performs a true, [SAST scan](/direction/secure/static-analysis/sast/) on code changes, which gives developers instant and more complete feedback. And DevOps teams can choose to enable  [DAST scanning](https://docs.gitlab.com/ee/user/application_security/dast/) that uses GitLab’s review app feature to assess changes pre-merge. Similarly,  [dependencies](https://docs.gitlab.com/ee/user/application_security/dependency_scanning/), containers, infrastructure as code, and more can all be scanned, at the push of the commit button.\n\nIn addition, GitLab also is keen on providing DevOps teams just-in-time education about vulnerabilities and fixes. Now, via partnerships with [Kontra](/blog/kontra-and-gitlab-integrate-vulnerability-education-into-the-devops-workflow/) and [Secure Code Warrior](/blog/heres-how-to-get-integrated-secure-coding-advice-in-gitlab/), GitLab provides developers with crisp training on how to mitigate the specific vulnerability they just created. This helps developers learn proper coding techniques instead of flagging the problem to figure out later.\n\n## Concentrating on compliance\n\nShifting compliance left and embedding it deep into the software development lifecycle, a.k.a. [continuous software compliance](/solutions/compliance/), is also a priority for GitLab.\n\n“We enable organizations to create policies that align with their compliance regulations and enforce them throughout the application development workflow,” Benson says. “Rather than juggling multiple policy enforcement applications, you have a single lens for visibility across the entire lifecycle.” For instance, a company can develop granular compliance pipeline policies that require a SAST to run for every commit in a certain project or a chain of MR approvals that developers can’t circumvent. “Those types of common controls and separation of duties simplify software audits and speed up application deployments.”\n\nGitLab is honored to be recognized in the Gartner Magic Quadrant, and will continue to empower and unite developers and security professionals alike using repeatable, defensible processes that automate security and compliance policies from development through production.\n\n> **[Start a free Ultimate trial](https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial)**\n \n_Gartner, “Magic Quadrant for Application Security Testing,” Dale Gardner, Mark Horvath, Dionisio Zumerle, April 18, 2022. Gartner does not endorse any vendor, product or service depicted in our research publications, and does not advise technology users to select only those vendors with the highest ratings or other designation. Gartner research publications consist of the opinions of Gartner's research organization and should not be construed as statements of fact. Gartner disclaims all warranties, expressed or implied, with respect to this research, including any warranties of merchantability or fitness for a particular purpose. GARTNER and Magic Quadrant are registered trademarks and service marks of Gartner, Inc. and/or its affiliates in the U.S. and internationally and are used herein with permission. All rights reserved._\n\nCover image by [Fly:D](https://unsplash.com/photos/ZNOxwCEj5mw) on Unsplash\n{: .note}\n",[682,1008,1732,708,9],"research",{"slug":1734,"featured":6,"template":686},"one-devops-platform-can-help-you-achieve-devsecops","content:en-us:blog:one-devops-platform-can-help-you-achieve-devsecops.yml","One Devops Platform Can Help You Achieve Devsecops","en-us/blog/one-devops-platform-can-help-you-achieve-devsecops.yml","en-us/blog/one-devops-platform-can-help-you-achieve-devsecops",{"_path":1740,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1741,"content":1747,"config":1752,"_id":1754,"_type":13,"title":1755,"_source":15,"_file":1756,"_stem":1757,"_extension":18},"/en-us/blog/personas-and-empathy-building",{"title":1742,"description":1743,"ogTitle":1742,"ogDescription":1743,"noIndex":6,"ogImage":1744,"ogUrl":1745,"ogSiteName":672,"ogType":673,"canonicalUrls":1745,"schema":1746},"How we use personas to build empathy for different types of users","Welcome to our series on the new GitLab personas!","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749678789/Blog/Hero%20Images/how-we-use-personas-to-gain-empathy.jpg","https://about.gitlab.com/blog/personas-and-empathy-building","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How we use personas to build empathy for different types of users\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Katherine Okpara\"}],\n        \"datePublished\": \"2018-10-12\",\n      }",{"title":1742,"description":1743,"authors":1748,"heroImage":1744,"date":1749,"body":1750,"category":705,"tags":1751},[1567],"2018-10-12","\nLast year we discussed our motivations for using personas at GitLab, including [why they're important](/blog/the-importance-of-ux-personas/) and how to [create them through UX research](/blog/discovering-gitlabs-personas/). Since then, our teams have had many conversations about improving the design of our product and continuing to empathize with our users. As a result, we created an initiative to fully incorporate personas into our design process. This will help everyone learn more about the different people who use GitLab!\n\n#### What’s New\nWe’ve made several changes in format since the first iteration of personas:\n\n- Gender-neutral name: humanizing the persona while still ensuring that it is inclusive\n- Job description: helping your audience learn about what the user does and who they work with\n- “Jobs-to-be-done” (JTBD) framework: making the information more concise and easier to digest\n- Alternative job titles: understanding how the research findings apply to other user groups with similar needs and challenges\n\nTypically, the most insightful personas are a realistic representation of user needs. They help you understand who you’re designing for and allow other people in all departments of your company to hear directly from users. In this series, we’ll share findings from our recent round of research and highlight what we’ve learned about each role.\n\n#### Want to learn more?\nYou can now view the personas [in our handbook](/handbook/product/personas/). Here's a quick summary of what's inside:\n* [Parker, Product Manager](/handbook/product/personas/#parker-product-manager)\n* [Delaney, Development Team Lead](/handbook/product/personas/#delaney-development-team-lead)\n* [Devon, DevOps Engineer](/handbook/product/personas/#devon-devops-engineer)\n* [Sasha, Software Developer](/handbook/product/personas/#sasha-software-developer)\n* [Sydney, Systems Administrator](/handbook/product/personas/#sidney-systems-administrator)\n* [Sam, Security Analyst](/handbook/product/personas/#sam-security-analyst)\n\nHow does your team use personas in the design process? Connect with us [@gitlab](https://twitter.com/gitlab), and stay tuned for the next posts, where we’ll dive deep into the findings, limitations, and opportunities of each.\n\n[Photo](https://unsplash.com/photos/fgiFAtH0QBU) by [gabrielle cole](https://unsplash.com/@gabriellefaithhenderson) on Unsplash.\n{: .note}\n",[9,709],{"slug":1753,"featured":6,"template":686},"personas-and-empathy-building","content:en-us:blog:personas-and-empathy-building.yml","Personas And Empathy Building","en-us/blog/personas-and-empathy-building.yml","en-us/blog/personas-and-empathy-building",{"_path":1759,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1760,"content":1766,"config":1772,"_id":1774,"_type":13,"title":1775,"_source":15,"_file":1776,"_stem":1777,"_extension":18},"/en-us/blog/quickly-onboarding-engineers-successfully",{"title":1761,"description":1762,"ogTitle":1761,"ogDescription":1762,"noIndex":6,"ogImage":1763,"ogUrl":1764,"ogSiteName":672,"ogType":673,"canonicalUrls":1764,"schema":1765},"How to quickly (and successfully) onboard engineers","It's a tough hiring market today. Here's how GitLab gets engineers onboard fast and sets them up for success.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749670635/Blog/Hero%20Images/kubernetesterms.jpg","https://about.gitlab.com/blog/quickly-onboarding-engineers-successfully","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to quickly (and successfully) onboard engineers\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"David O'Regan\"}],\n        \"datePublished\": \"2022-07-21\",\n      }",{"title":1761,"description":1762,"authors":1767,"heroImage":1763,"date":1769,"body":1770,"category":705,"tags":1771},[1768],"David O'Regan","2022-07-21","\n\nNo one ever said hiring was easy. As a matter of fact, talent hiring and retention are some of the hardest aspects to get right for any software company. \n\nAccording to [a recent article at Developer Pitstop](https://developerpitstop.com/how-long-do-software-engineers-stay-at-a-job/) the average engineer will only stay at a job for an average of two years before moving on, and this tenure is shrinking as time goes on. \n\nWhen we look at the average timeline for engineers in a new role we usually see something like:\n\n> - Learning and adaptation (3 / 6 months):\n>   Coming to grips with the new company, team, and their processes.\n> \n> - Creating value for the organization (6 / 12 months):\n>   Adding value to the business by becoming a functioning member of the team.\n> \n> - Becoming a role expert (6 / 18 months):\n>   Owning the role completely and helping to shape the direction of the team.\n\n## Software engineer onboarding\n\nAt GitLab we pride ourselves on an outstanding onboarding process to reduce the amount of time an engineer will spend in the `learning and adaptation` bracket and accelerate their evolution into the `creating value for the organization` bracket. We do this for two main reasons:\n\n- **Quicker integration**: We aim to have engineers ship production code in less than one week, and fully onboard them in less than three months.\n- **Reduce turnover**: Engineers who have an awesome onboarding experience tend to stay with the same company longer.\n\n**The bottom line is that with these benefits, investing in an amazing onboarding process gives you the highest ROI on your hiring initiatives.**\n\nSo, now that we know **why** we need to ensure we onboard quickly and correctly, let's talk about **how** we do it at GitLab. \n\n## Software engineer onboarding process: An overview\n\n- 💯 Before day one \n- 💥 It's all about the onboarding issue\n- 🥂 Pick the right onboarding buddy\n- 👌 Pair, pair, and more pairing\n- 🖐 All the coffee chats\n- 🤘 Tailor the experience to the role\n- 🚢 Ship some code in a week or less\n- 💬 Let's get (and give) some feedback\n\n![onboarding](https://about.gitlab.com/images/blogimages/onboarding.png){: .shadow}\n\n## 💯 Before day one\n\nThe best processes for onboarding software engineers start as soon as the candidate has officially accepted the offer. This is done in a few ways:\n\n- An onboarding issue is created with tasks for the hiring manager, their buddy, and People Experience (HR).\n- The hiring manager selects the right onboarding buddy for the engineer and communicates expectations (more on this later).\n- The engineer's accounts (Email, GitLab account, Okta, etc) are created and their hardware is shipped.\n- GitLab reaches out via email to let the candidate know what the onboarding process looks like.\n- The hiring manager reaches out to the engineer via email to set up a coffee chat on Day 1 as the initial process might seem overwhelming.\n\nFor us, the most important aspect is communication with the engineer to ensure they are set up for success. We provide them with access to their onboarding issue, helpful video guides for getting started, and a primer on how to navigate our [handbook like a pro](https://about.gitlab.com/handbook/people-group/general-onboarding/). The reason this is so important is that we know if we stop communicating with the engineer after signing, we are at risk of creating uncertainty, introducing inefficiency, or even losing them to another offer during that time.\n\n## 💥 It's all about the onboarding issue\n\nAt GitLab, our [onboarding issue](https://about.gitlab.com/handbook/people-group/general-onboarding/#onboarding-issue-template-links) is the most effective tool we have for successfully onboarding a new engineer quickly. Hiring managers use this issue almost exclusively, both for building momentum and for following our value of transparency. We use this issue, instead of Slack or email, to create a single source of truth for everyone during the process and to prevent fragmented communication. For anyone new at GitLab, the first few weeks can seem like a lot to get on top of, so the hiring manger wants to be mindful of opportunities to consolidate communication and reduce context switching. \n\nOur onboarding issues are confidential because they contain sensitive account information, but the templates of the issue are [public](https://about.gitlab.com/handbook/people-group/general-onboarding/#onboarding-issue-template-links) and they look something like this:\n\n```\n- Accounts and access\n- Day 1: Getting started: Accounts and paperwork\n- Day 2: Remote working and our values\n- Day 3: Security & compliance\n- Day 4: Social & benefits\n- Day 5: Git & push some code\n- Weeks 2 - 4: Explore\n- Job-specific tasks\n```\n\nAs a hiring manager, you want to ensure that you have fleshed out the `job specific tasks` ahead of time with things that are important for the specific role the engineer will be working in. This will generally include things like ensuring they have database access, pointing them to the working groups that will support their work, and letting them know the right Slack channels to support their development. \n\n## 🥂 Pick the right onboarding buddy\n\nThe advantages of the buddy system have been [well documented for years](https://www.pmi.org/learning/library/implementing-buddy-system-workplace-9376). At GitLab we lean heavily on the onboarding buddy [model](https://about.gitlab.com/handbook/people-group/general-onboarding/onboarding-buddies/) and rather than having multiple people support the new engineer, it will generally be the hiring manager and a single buddy. \n\nThe advantages of an onboarding buddy at GitLab are several:\n\n- **Domain expert**: A onboarding buddy knows the domain the new engineer is going to be working in. They have already written, reviewed, and merged code into production in the same way we want the new engineer to. They know the process, pitfalls, and gotchas of the domain. \n- **Single context / Accountabilibuddy**: A single onboarding buddy drastically reduces context switching and \"paralysis by analysis.\" They know they always have someone to ask and this creates a psychologically safe space for them. GitLab can often be a scary environment to navigate when you are new due to impostor syndrome and we want to curb that. \n- **GitLabisms**: At GitLab, we have code and then we have \"GitLabisms.\" These are things that are specific to GitLab, be it workflows or custom tooling. These are often more complicated to become familiar with than the code itself. The onboarding buddy should have experience with these already and be able to point the engineer in the right direction when they are stuck. \n- **Mentor**: Mentoring is one of the single best things an engineer can do to grow themselves and become more sure of their own skills. By being an onboarding buddy, they are given a growth opportunity to cover their own blindspots and upskill. \n\nAs a rule of thumb, the onboarding buddy should ideally be someone from the engineer's new team who is working in the same domain, i.e. a senior frontend engineer mentors a new intermediate frontend engineer, both of which are from the same team. While this rule is not set in stone, it is often less effective to have an onboarding buddy be cross-team due to a lack of domain expertise.\n\n## 👌 Pair, pair, and more pairing\n\nPairing when programming and when working on tasks is a very effective way to help new engineers build up their knowledge without needing to pour through documentation. \n\nIn general, we would recommend that the engineer pair with their onboarding buddy on their first few merge requests to get used to the workflow and pitfalls of working with the GitLab Development Kit. But this is not where it should stop. We encourage pairing across the board at GitLab either via open pairing sessions such as our Frontend Pairing office hours, having a manager pair with an engineer, or pairing with a stable counterpart such as your team's UX designer. \n\nWhen it comes to onboarding, pairing is helpful. We do this because we want to:\n\n- **Create psychological safety**: We all feel impostor syndrome. This is worse when you're new to a job and don't know the ecosystem yet. Regular pairing helps to undo that worry as you see people are just people and even staff/principal engineers forget the closing brace!\n- **Create relationships/network**: In an all-remote company, it becomes important to know who to reach out to in moments of need. Regular pairing helps to foster these relationships and creates a safety net with your peers. \n- **Demonstrate our values**: We believe in [CREDIT](https://handbook.gitlab.com/handbook/values/) at GitLab. Regular pairing supports all our core values and helps to encourage us to be mindful of them when working. \n- **Give and get real-time feedback**: When pairing, we can get real-time feedback on our process and how we're approaching solutions. This is extremely important for new engineers who might not be familiar with core GitLab concepts such as [iteration](https://handbook.gitlab.com/handbook/values/#iteration) (\"How can we break this down?\").\n\n## 🖐 All the coffee chats\n\nBeing distributed means we do communication differently at GitLab. One key to successfully onboarding a new engineer is to get them comfortable with our communication style. \n\nTo do this, we encourage regular [coffee chats](https://about.gitlab.com/company/culture/all-remote/informal-communication/#coffee-chats) and a culture of zero shame about it. \n\nEncourage your new hire to set up regular coffee chats with people across the company to help build rapport and become comfortable with GitLab as a whole. \n\nTo help empower new hires, have them ask the following question in their initial 10  - 15 chats:\n\n> What is the one thing I can do to be successful at GitLab?\n\n## 🤘 Tailor the experience to the role\n\nAs a hiring manger, you need to understand that people learn and grow in different ways. No single method will work for everyone and it is your job to ensure your new hire feels supported in how they want to learn. \n\nDuring the onboarding, observe your new hire and touch base with them in your weekly 1:1 for what they are and **are not** enjoying about the experience so far. Once you have this information, iterate on it and tailor their onboarding to include more of what they prefer. \n\nAsk constructive questions that can have actionable tasks each week to ensure a better process for them:\n\n> Do you want to pair more? Do you want more alone time? Are there particular areas you need more guidance in? Are there things I can do to better support you?\n\nYou should aim to strike a balance during their onboarding for a mixture of practical work and time dedicated to studying. Work with the direct report to establish the best balance for them as an individual. \n\n## 🚢 Ship some code in a week or less\n\nThis is arguably the most important aspect of successfully onboarding an engineer and setting them up for success. The sooner they can push code to production, the sooner they can begin to refine their skills and work effectively with the team. \n\nThe best software companies in the world set a timeline of shipping code in a week. At GitLab, this is not a hard-and-fast rule, but in the **Create** stage is what we strive for. \n\nTo ensure an engineer can ship code within a week, we need to ensure they are supported in a few ways:\n\n- **Tooling**: At GitLab we have a fantastic [local development kit](https://gitlab.com/gitlab-org/gitlab-development-kit) which sets up an engineer to begin delivering code. We support this kit heavily as a first-class citizen and are constantly refining the tooling and [docs](https://gitlab.com/gitlab-org/gitlab-development-kit/-/tree/main/doc) to ensure everyone can contribute. For a new hire, consider having their first pairing session to be setting up their GDK – this will get them one step closer to shipping quality code. \n- **Dev process**: At GitLab, we always strike to [break down work into its smallest deliverable](https://about.gitlab.com/handbook/product/product-principles/#the-minimal-viable-change-mvc) that can be picked up by an engineer without deep contextual understanding. We do this to support the open source community as much as our own engineers. \n\n## 💬 Let's get some feedback\n\nAs a hiring manager, you want to ensure you build a stable feedback loop into your processes and this includes onboarding. During your 1:1s you should include a weekly feedback cycle for both **you** and your direct report. \n\nThese feedback cycles should take the form of:\n\n- **Appreciation (Collaboration / Results / Diversity / Iteration / Transparency)**: A moment of appreciation for something positive that is highlighted inline with our values. \n- **Coaching (Collaboration / Results / Diversity / Iteration / Transparency)**: A growth opportunity that is highlighted inline with our values. \n\nThese weekly feedback loops allow the engineer to highlight things that could be done better in both the context of the onboarding and their day-to-day experience. \n\nLastly, it is optional but encouraged to hold an onboarding retrospective when the initial onboarding issue is closed with the following points to talk through:\n\n- What went well?\n- What didn't go so well? \n- What could be improved? \n- Action items\n\n## 💾 TL;DR \n\nThe most successful software companies have a solidified onboarding process and continue to expand on it, setting up both the company and engineers for long-term success. The above methods are how we do it at GitLab. \n\n## 💻 Remote development and the developer experience\n\nAt GitLab we have recently been hiring for our [Remote Development effort](https://about.gitlab.com/direction/create/ide/remote_development/) and many of these items are in play with the engineers we are bringing into the company. We want to improve these processes to make onboarding even easier, mitigating the need for even setting up a specific local development toolchain to be able to ship production code. \n\nIf you think you might be interested in a role at GitLab working on Remote Development, check out our open listings [here](https://boards.greenhouse.io/gitlab/jobs/6201785002).\n\nRead more about [leading endingeering teams](/blog/cadence-is-everything-10x-engineering-organizations-for-10x-engineers/).\n",[682,9,707],{"slug":1773,"featured":6,"template":686},"quickly-onboarding-engineers-successfully","content:en-us:blog:quickly-onboarding-engineers-successfully.yml","Quickly Onboarding Engineers Successfully","en-us/blog/quickly-onboarding-engineers-successfully.yml","en-us/blog/quickly-onboarding-engineers-successfully",{"_path":1779,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1780,"content":1786,"config":1794,"_id":1796,"_type":13,"title":1797,"_source":15,"_file":1798,"_stem":1799,"_extension":18},"/en-us/blog/risk-mapping",{"title":1781,"description":1782,"ogTitle":1781,"ogDescription":1782,"noIndex":6,"ogImage":1783,"ogUrl":1784,"ogSiteName":672,"ogType":673,"canonicalUrls":1784,"schema":1785},"Search team directs testing efforts with risk mapping","A justification of how the search team decided to try risk mapping as an ongoing exercise to determine where test automation should be written, and some guidance on how to create a risk map.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749669590/Blog/Hero%20Images/niklas_hamann-fyvNzhJTQBA-unsplash.jpg","https://about.gitlab.com/blog/risk-mapping","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How the Search Team at GitLab Implemented a Risk Map to Direct Automated Testing Efforts\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Erick Banks\"},{\"@type\":\"Person\",\"name\":\"John McGuire\"}],\n        \"datePublished\": \"2020-09-03\",\n      }",{"title":1787,"description":1782,"authors":1788,"heroImage":1783,"date":1791,"body":1792,"category":729,"tags":1793},"How the Search Team at GitLab Implemented a Risk Map to Direct Automated Testing Efforts",[1789,1790],"Erick Banks","John McGuire","2020-09-03","\n\n{::options parse_block_html=\"true\" /}\n\n\n\n**_’What's the good of Mercator's North Poles and Equators,_**\n\n**_Tropics, Zones, and Meridian Lines?’_**\n\n**_So the Bellman would cry: and the crew would reply_**\n\n**_‘They are merely conventional signs!’_**\n\n**-Lewis Carroll, \"The Hunting of the Snark\"**\n\nWhen I first started at GitLab I was hired to automate a specific task for the advanced search team: search for security holes in our data redaction logic. Shortly after my being hired this mandate became moot. Simultaneously, the search team was also in the middle of transitioning our product manager to a new team and introducing a new one. This left me with some existential angst around my role as the software engineer in test for the search team. I mean, there’s always work to do, but *what* work would be *most helpful*?\n\nAfter a few months of seeing bug reports being filed by users and GitLab team members, I thought it would be best to try to direct our testing efforts where we have the most unmitigated risk. But, how would we come to know with any degree of certainty where that is? To find out I made a risk map.\n\nRisk maps are not new. [The United States Federal Emergency Management Agency](https://www.fema.gov/flood-maps/tools-resources/risk-map) uses them, [insurance companies](https://www.techriskreport.com/2019/10/preparing-for-data-breaches-data-mapping-response-team-and-insurance/) use them, and [software companies](https://www.pmi.org/learning/library/risk-analysis-project-management-7070) use them.\n\nThe point of the risk map is to show you and your team where in the project you are responsible for is the most unmitigated risk. It can then be used to inform what areas of the project should be the focus of adding testing, preferably automation, though not exclusive of manual testing ([exploratory testing](https://www.tricentis.com/blog/creating-an-exploratory-testing-charter/), [bug bashes](https://www.classy.org/blog/run-bug-bash/), etc.).\n\n## “The Map is Not the Territory”\n\nRisk maps are not perfect reflections of where the risk exists in your project. Any criticism of the map, “It gets stale too quickly”, “You’re distorting where the real risk lies”, “It takes too much time to keep updated”, have validity and could apply just as well to maps of physical places. In my case I would say that even a low resolution map, supported by data, of where risk is in the project is better than no map at all. Without such a map I would continue working by just relying on my gut instinct of where the risk is, or worse, I would be in the reactive state of fixing things after they’ve gone wrong. Isn’t an old, out of date map sometimes useful?\n\n## Should You Make A Risk Map?\nOf course I can’t be prescriptive about this. If you’re reading this, you are likely the best judge of if you and your team will get utility from making one. I can say that in my situation: new to the search team, transitioning to a new product manager, and no clear signal as to where the riskiest part of the project was, it made sense for me to make one.\n\n## How to get started\nIf you do decide you want to make a risk map for your team, here are some steps and tips that may help.\n\nFirst you’ll want to get a sense of what can go wrong. I call these “risk facets”. For example: a simplified view of [Elasticsearch](https://www.elastic.co/home) (the underlying tech we use for our advanced search feature at Gitlab) is that users insert records (Merge Requests, Issues, Users, Comments etc.) they want to be searchable into GitLab, that record gets indexed, and then, later, a user tries to recall that record. In this simplified view the facets could be problems around record insertion, indexing, and recall. These could be multiplexed by considerations of: speed, efficiency, and cost. So, that may yield a risk map with nine rows, or risk facets: record insertion - speed, record  insertion - efficiency, record insertion - cost, record indexing - speed, etc.\n\nA helpful starting point for understanding what risk facets may be for your project is to look at the list of features it has. This is not likely to be an exhaustive list of the risk facets your project presents, but it is a good place to start.\n\nSome of these facets may have a label (or, more likely, a combination of labels) that accurately map issues to the risk facet. If your project is anything like mine, many facets will **not** have these corresponding labels. For future extensibility and automated aggregation of issues around each facet, it is important to create labels or create combinations of labels that can accurately map issues to a single risk facet.\n\nSo, take some time to read through recent issues. Look at the issues your users are filing. See if there are some shared areas around which the issues are filed. These are likely the more important risk facets. I did this over several weeks of reading issues as they were filed and extrapolating where problems could arise. I then created a list of these facets in a notebook until I found that I had a substantial amount of facets that could be tracked. Next, I transferred those risk facets to the rows of the table in the risk map issue.\n\n![Example Chart from website](https://about.gitlab.com/images/blogimages/egb/risk-map/facets.png){: .shadow.center}\n\nDo not, as I did, confuse *solutions* to risk facets for the risk facets themselves. For example, I erroneously added a row to my map for “regex - optimization”, which is a solution to the risk facet “regex - efficiency”.\n\nAfter a time of gathering the risk facets and adding them to the rows of the table in the issue it’s time to add the other columns to the table and track if those risk facets are being implicitly or explicitly tested and where. It felt important to differentiate between implicit and explicit testing because explicitly testing for every risk facet is prohibitively expensive in either time, cost, effort, etc. (or some combination thereof). Just because we aren’t explicitly testing some facet doesn’t mean there isn’t some kind of test coverage for that facet. Since the exercise is aimed at showing the team where risk facets are, their severity, and if they are being mitigated or not, showing where the implicit testing is happening is important. Fill in the implicit/explicit testing columns and add the links that point to where this testing is happening.\n\n![Example Chart from website](https://about.gitlab.com/images/blogimages/egb/risk-map/facet-coverage.png){: .shadow.center}\n\nThe last three columns compose your [risk matrix](https://en.wikipedia.org/wiki/Risk_matrix). They are: *risk level*, *impact*, and *likelihood*, so ordered so that the most important of the three columns, *risk level* (which is just a composite of the other two columns), is more likely to be visible before horizontally scrolling off the screen, are most likely to need input from the rest of the team. Actively solicit other team members to help fill out these columns.\n\n![Example Chart from website](https://about.gitlab.com/images/blogimages/egb/risk-map/riskmatrix.png){: .shadow.center}\n\nIt’s important to recognize that this map will never be done. You’ll never “finish it”. Incompleteness is to be expected. But what should emerge is a picture of where most of the testing is being done, and where most of the risk is being carried in the project. You can then use this to help the team align testing toward facets to better mitigate risk.\n\n![Example Chart from website](https://about.gitlab.com/images/blogimages/egb/risk-map/risk-map-sample.png){: .shadow.center}\n## Product Management\n\nTo Product Managers a risk map helps quantify the amount of energy to spend mitigating a possible risk. With a goal of not over investing in a mitigation, as well as avoiding catastrophe.  Product Managers can also help identify primary and secondary effects that can create a need to change the level of risk assessed. \nHistory is full of examples where improper risk assessments lead to preventable disasters. [PMI.org Deepwater Horizon\nLessons in Probabilities](https://www.pmi.org/learning/library/comparison-risk-events-with-risk-management-9919)\n## Next steps\n\n- Identify where the highest risk is and try to mitigate it.\n- Decide with the team how often we should update the risk map.\n- Add labels so that each issue for the search team falls into one unique risk facet.\n- Automate some of the creation of the map by aggregating issues and MRs based on the newly created labels.\n- Compare my efforts at doing this exercise with other teams (should they decide to do it as well).\n- Add features like:\n  - Sorting by risk level\n  - Toggle by open/closed/all for issues/MRs.\n  - Toggle displaying issues found by customers.\n\nLink to Risk Map: [https://gitlab.com/gitlab-org/gitlab/-/issues/229431](https://gitlab.com/gitlab-org/gitlab/-/issues/229431)\n\nLink to Risk Mapping Epic: [https://gitlab.com/groups/gitlab-org/-/epics/4253](https://gitlab.com/groups/gitlab-org/-/epics/4253)\n\nCover image by [Niklas Hamann](https://unsplash.com/@hamann?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/server?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText).\n{: .note}\n",[9,708,1732],{"slug":1795,"featured":6,"template":686},"risk-mapping","content:en-us:blog:risk-mapping.yml","Risk Mapping","en-us/blog/risk-mapping.yml","en-us/blog/risk-mapping",{"_path":1801,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1802,"content":1807,"config":1813,"_id":1815,"_type":13,"title":1816,"_source":15,"_file":1817,"_stem":1818,"_extension":18},"/en-us/blog/scaling-down-how-we-prototyped-an-image-scaler-at-gitlab",{"title":1803,"description":1804,"ogTitle":1803,"ogDescription":1804,"noIndex":6,"ogImage":935,"ogUrl":1805,"ogSiteName":672,"ogType":673,"canonicalUrls":1805,"schema":1806},"Scaling down: How we shrank image transfers by 93%","Our approach to delivering an image scaling solution to speed up GitLab site rendering","https://about.gitlab.com/blog/scaling-down-how-we-prototyped-an-image-scaler-at-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Scaling down: How we shrank image transfers by 93%\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Matthias Käppler\"}],\n        \"datePublished\": \"2020-11-02\",\n      }",{"title":1803,"description":1804,"authors":1808,"heroImage":935,"date":1810,"body":1811,"category":729,"tags":1812},[1809],"Matthias Käppler","2020-11-02","\n\n{::options parse_block_html=\"true\" /}\n\n\n\nThe [Memory](https://about.gitlab.com/handbook/engineering/development/enablement/data_stores/application_performance/) team recently shipped an improvement to our image delivery functions\nthat drastically reduces the amount of data we serve to clients. Learn here how we went from knowing nothing about\n[Golang](https://golang.org/) and image scaling to a working on-the-fly image scaling solution built into \n[Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse).\n\n## Introduction\n\nImages are an integral part of GitLab. Whether it is user and project avatars, or images embedded in issues\nand comments, you will rarely load a GitLab page that does not include images in some way shape or form.\nWhat you may not be aware of is that despite most of these images appearing fairly small when presented\non the site, until recently we were always serving them in their original size.\nThis meant that if you would visit a merge request, then all user avatars that appeared merely as thumbnails\nin sidebars or comments would be delivered by the GitLab application in the same size they were uploaded in,\nleaving it to the browser rendering engine to scale them down as necessary. This meant serving\nmegabytes of image data in a single page load, just so the frontend would throw most of it away!\n\nWhile this approach was simple and served us well for a while, it had several major drawbacks:\n\n- **Perceived latency suffers.** The perceived latency is the time that passes between a user\n  requesting content, and that content actually becoming visible or being ready to engage with.\n  If the browser has to download several megabytes of image data, and then has to furthermore\n  scale down those images to fit the cells they are rendered into, the user experience unnecessarily suffers.\n- **Egress traffic cost.** On gitlab.com, we store all images in object storage, specifically GCS\n  (Google Cloud Storage). This means that our Rails app first needs to resolve an image entity to\n  a GCS bucket URL where the binary data resides, and have the client\n  download the image through that endpoint. This means that for every image served, we cause\n  traffic from GCS to the user that we have to pay for, and the more data we serve, the higher the cost.\n\nWe therefore took on the challenge to both improve rendering performance and reduce traffic costs\nby implementing [an image scaler that would downscale images](https://gitlab.com/groups/gitlab-org/-/epics/3822)\nto a requested size before delivering them to the client.\n\n### Phase 1: Understanding the problem\n\nThe first problem is always: understand the problem! What is the status quo exactly? How does it work?\nWhat is broken about it? What should we focus on?\n\nWe had a pretty good idea of the severity of the problem, since we regularly run performance tests\nthrough [sitespeed.io](https://www.sitepeed.io) that highlight performance problems on our site.\nIt had identified images sizes as one of the most severe issues:\n\n![sitespeed performance test](https://gitlab.com/groups/gitlab-org/-/uploads/a06d8bfde802995c577afca843be7e96/Bildschirmfoto_2020-07-15_um_11.45.44.png)\n\nTo better inform a possible solution, an essential step was to [collect enough data](https://gitlab.com/gitlab-org/gitlab/-/issues/227387)\nto help identify the areas we should focus on. Here are some of the highlights:\n\n- **Most images requested are avatars.** We looked at the distribution of requests for certain types of images.\n  We found that about 70% of them were for avatars, while the remaining 30% accounted for embedded images.\n  This suggested that any solution would have the biggest reach if we focused on avatars first. Within the\n  avatar cohort we found that about 62% are user avatars, 22% are project avatars, and 16% are group avatars,\n  which isn't surprising. \n- **Most avatars requested are PNGs or JPEGs.** We also looked at the distribution of image formats. This is partially\n  affected by our upload pipeline and how images are processed (for instance, we always crop user avatars and store them as PNGs)\n  but we were still surprised to see that both formats made up 99% of our avatars (PNGs 76%, JPEGs 23%). Not much\n  love for GIFs here!\n- **We serve 6GB of avatars in a typical hour.** Looking at a representative window of 1 hour of GitLab traffic, we saw\n  almost 6GB of data move over the wire, or 144GB a day. Based on experiments with downscaling a representative user avatar,\n  we estimated that we could reduce this to a mere 13GB a day on average, saving 130GB of bandwidth each day!\n\nThis was proof enough for us that there were significant gains to be made here. Our first intuition was: could this\nbe done by a CDN? Some modern CDNs like Cloudflare [already support image resizing](https://support.cloudflare.com/hc/en-us/articles/360028146432-Understanding-Cloudflare-Image-Resizing)\nin some of their plans. However, we had two major concerns about this:\n\n1. **Supporting our self-managed customers.** While gitlab.com is the largest GitLab deployment we know of, we have hundreds of thousands\n  of customers who run their own GitLab installation. If we were to only resize images that pass through a CDN in front of gitlab.com,\n  none of those customers would benefit from it.\n1. **Pricing woes.** While there are request budgets based on your CDN plan, we were worried about the operational cost this would\n  add for us and how to reliably predict it.\n\nWe therefore decided to look for a solution that would work for all GitLab users, and that would be more under\nour own control, which led us to phase 2: experimentation!\n\n### Phase 2: Experiments, experiments, experiments!\n\nA frequent challenge for [our team (Memory)](https://about.gitlab.com/handbook/engineering/development/enablement/data_stores/application_performance/)\nis that we need to venture into parts of GitLab's code base\nthat we are unfamiliar with, be it with the technology, the product area, or both. This was true in this\ncase as well. While some of us had some exposure to image scaling services, none of us had ever built or\nintegrated one.\n\nOur main goal in phase 2 was therefore to identify what the possible approaches to image scaling were,\nexplore them by researching existing solutions or even building proof-of-concepts (POCs), and grade\nthem based on our findings. The questions we asked ourselves along the way were:\n\n- **When should we scale?** Upfront during upload or on-the-fly when an image is requested?\n- **Who does the work?** Will it be a dedicated service? Can it happen asynchronously in Sidekiq?\n- **How complex is it?** Whether it's an existing service we integrate, or something we build ourselves,\n  does implementation or integration complexity justify its relatively simple function?\n- **How fast is it?** We shouldn't forget that we set out to solve a performance issue. Are we sure that\n  we are not making the server slower by the same amount of time we save in the client?\n\nWith this in mind, we identifed [multiple architectural approaches](https://gitlab.com/groups/gitlab-org/-/epics/3979) to consider,\neach with their own pros and cons. These issues also doubled as a form of [architectural decision log](https://github.com/joelparkerhenderson/architecture_decision_record#what-is-an-architecture-decision-record)\nso that decisions for or against an approach are recorded.\n\nThe major approaches we considered are outlined next.\n\n#### Static vs. dynamic scaling\n\nThere are two basic ways in which an image scaler can operate: it can either create thumbnails of\nan existing image ahead of time, e.g. during the original upload as a background job. Or it can\nperform that work on demand, every time an image is requested. To make a long story short: while\nit took a lot of back and forth, and even though we had [a working POC](https://gitlab.com/gitlab-org/gitlab/-/issues/232616),\nwe eventually discarded the idea of scaling statically, at\nleast for avatars. Even though [CarrierWave](https://github.com/carrierwaveuploader/carrierwave) (the Ruby uploader\nwe employ) has an integration\nwith MiniMagick and is able to perform that kind of work, it suffered from several issues:\n\n1. **Maintenance heavy.** Since image sizes may change over time, a strategy is needed to backfill sizes\n  that haven't been computed yet. This raised questions especially for self-managed customers where\n  we do not control the GitLab installation.\n1. **Statefulness.** Since thumbnails are created alongside the original image, it was unclear how to perform\n  cleanups should they become necessary, since CarrierWave does not store these as separate database\n  entities that we could easily query.\n1. **Complexity.** The POC we created turned out to be more complex than anticipated and felt like we\n  were shoehorning this feature onto existing code. This was exacerbated by the fact that at the time\n  we were running a very old version of CarrierWave that was already a maintenance liability, and upgrading it\n  would have added scope creep and delays to an already complex issue.\n1. **Flexibility.** The actual scaler implementation in CarrierWave is buried three layers down the Ruby dependency stack,\n  and it was difficult to replace the actual scaler binary (which would become a\n  problem when trying to secure this solution as we will see in a moment.)\n\nFor these reasons we decided to scale images on-the-fly instead.\n\n### Dynamic scaling: Workhorse vs. dedicated proxy\n\nWhen scaling images on-the-fly the question becomes: where? Early on there was a suggestion to use\n[imgproxy](https://github.com/imgproxy/imgproxy), a \"fast and secure standalone server for resizing and converting remote images\".\nThis sounded tempting, since it is a \"batteries included\" offering, it's free to use, and it is a great\nway to isolate the task of image scaling from other production work loads, which has benefits around\nsecurity and fault isolation.\n\nThe main problem with imgproxy was exactly that, however: a standalone server. \n[Introducing a new service to GitLab](https://docs.gitlab.com/ee/development/adding_service_component.html#adding-a-new-service-component-to-gitlab)\nis a complex task, since we strive to appear as a [single application](https://about.gitlab.com/handbook/product/single-application/) to the end user,\nand documenting, packaging, configuring, running and monitoring a new service just for rescaling images seemed excessive.\nIt therefore wasn't in line with our prerogative of focusing on the\n[minimum viable change](https://about.gitlab.com/handbook/product/product-principles/#the-minimal-viable-change-mvc).\nMoreover, imgproxy had significant overlap with existing architectural components at GitLab, since we already\nrun a reverse proxy: [Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse).\n\nWe therefore decided that the fastest way to deliver an MVC was to build out this functionality in Workhorse\nitself. Fortunately we found that we already had an established pattern for dealing with special, performance\nsensitive workloads, which meant that we could\nlearn from existing solutions for similar problems (such as image delivery from remote storage), and we could\nlean on its existing integration with the Rails application for request authentication and running business\nlogic such as validating user inputs, which helped us tremendously to focus on the actual problem: scaling images.\n\nThere was a final decision to make, however: scaling images is a very different kind of workload from \nserving ordinary requests, so an open question was how to integrate a scaler into Workhorse in a way\nthat would not have knock-on effects on other tasks Workhorse processes need to execute.\nThe two competing approaches discussed were to either shell out to an executable that performs the scaling,\nor run a [sidecar process](https://docs.microsoft.com/en-us/azure/architecture/patterns/sidecar#:~:text=Sidecars%20are%20supporting%20processes%20or,fate%20of%20its%20parent%20application.)\nthat would take over image scaling work loads from the main Workhorse process.\n\n### Dynamic scaling: Sidecar vs. fork-on-request\n\nThe main benefit of a sidecar process is that it has its own life-cycle and memory space, so it can be tuned\nseparately from the main serving process, which improves fault isolation. Moreover, you only pay the\ncost for starting the process once. However, it also comes with\nadditional overhead: if the sidecar dies, something has to restart it, so we would have to look at\nprocess supervisors such as `runit` to do this for us, which again comes with a significant amount\nof configuration overhead. Since at this point we weren't even sure how costly it would be to serve\nimage scaling requests, we let our MVC principle guide us and decided to first explore the simpler\nfork-on-request approach, which meant shelling out to a dedicated scaler binary on each image scaling\nrequest, and only consider a sidecar as a possible future iteration.\n\nForking on request was [explored as a POC](https://gitlab.com/gitlab-org/gitlab/-/issues/230519)\nfirst, and was quickly made production ready and deployed\nbehind a feature toggle. We initially ended up settling on [GraphicsMagick](http://www.graphicsmagick.org/)\nand its `gm` binary to perform the actual image scaling for us, both because it is a battle tested library, but also\nbecause there was precedent at GitLab to use it for existing features, which allowed us to ship\na solution even faster.\n\nThe overall request flow finally looked as follows:\n\n```mermaid\nsequenceDiagram\n    Client->>+Workhorse: GET /image?width=64\n    Workhorse->>+Rails: forward request\n    Rails->>+Rails: validate request\n    Rails->>+Rails: resolve image location\n    Rails-->>-Workhorse: Gitlab-Workhorse-Send-Data: send-scaled-image\n    Workhorse->>+Workhorse: invoke image scaler\n    Workhorse-->>-Client: 200 OK\n```\n\nThe \"secret sauce\" here is the `Gitlab-Workhorse-Send-Data` header synthesized by Rails. It carries\nall necessary parameters for Workhorse to act on the image, so that we can maintain a clean separation\nbetween application logic (Rails) and serving logic (Workhorse).\nWe were fairly happy with this solution in terms of simplicity and ease of maintenance, but we\nstill had to verify whether it met our expectations for performance and security.\n\n### Phase 3: Measuring and securing the solution\n\nDuring the entire development cycle, we frequently measured the performance of the various appraoches\nwe tested, so as to understand how they would affect request latency and memory use.\nFor latency tests we relied on [Apache Bench](https://httpd.apache.org/docs/2.4/programs/ab.html), since\nrecalling our initial mission, we were mostly interested in reducing the request latency a user might experience.\n\nWe also ran benchmarks encoded as Golang tests that specifically [compared different scaler implementations](https://gitlab.com/ayufan/image-resizing-test)\nand how performance changed with different image formats and image sizes. We learned a lot from these\ntests, especially where we would typically lose the most time, which often was in encoding/decoding\nan image, and not in resizing an image per se.\n\nWe also took security very seriously from the start. Some image formats such as SVGs are notorious\nfor remote code execution attacks, but there were other concerns such as DOS-ing the service with\ntoo many scaler requests or PNG compression bombs. We therefore\nput very strict requirements in place around what sizes (both dimensionally but also in bytes) and\nformats we will accept.\n\nUnfortunately one fairly severe issue remained that turned out to be a deal breaker with our simple\nsolution: `gm` is a complex piece of software, and shelling out to a 3rd party binary written in C still\nleaves the door open for a number of security issues. The decision was to [sandbox the binary](https://gitlab.com/groups/gitlab-org/-/epics/4373)\ninstead, but this turned out\nto be a lot more difficult than anticipated. We evaluated but discarded multiple approaches to sandboxing\nsuch as via `setuid`, `chroot` and `nsjail`, as well as building a custom binary on top of [seccomp](https://en.wikipedia.org/wiki/Seccomp).\nHowever, due to performance, complexity or other concerns we discarded all of them in the end.\nWe eventually decided to sarifice some performance for the sake of protecting our users as best we can and \nwrote a scaler binary in Golang, based on an existing [imaging](https://github.com/disintegration/imaging)\nlibrary, which had none of these issues.\n\n### Results, conclusion and outlook\n\nIn roughly two months we took an innocent sounding but in fact complex topic, image scaling, and went\nfrom \"we know nothing about this\" to a fully functional solution that is now running on gitlab.com.\nWe faced many headwinds along the way, in part because we were unfamiliar with both the topic and\nthe technology behind Workhorse (Golang), but also because we underestimated the challenges of delivering\nan image scaler that will be both fast and secure, an often difficult trade-off. A major lesson learned\nfor us is that security cannot be an afterthought; it has to be part of the design from day one and\nmust be part of informing the approach taken.\n\nSo was it a success? Yes! While the feature didn't have as much of an impact on overall perceived client\nlatency as we had hoped, we still dramatically improved a number of metrics. First and foremost, the\ndreaded \"properly size image\" reminder that topped our sitepeed metrics reports is resolved. This is also evident\nin the average image size processed by clients, which for image heavy pages fell off a cliff (that's good -- lower is\nbetter here):\n\n![image size metric](https://gitlab.com/groups/gitlab-org/-/uploads/b453aedaf2132db1292898508fd6a0c1/Bildschirmfoto_2020-10-06_um_07.02.56.png)\n\nSite-wide we saw a staggering **93% reduction** in image transfer size of page content delivered to clients.\nThese gains also translate into savings for GCS egress traffic, and hence Dollar cost savings, by an equivalent amount.\n\nA feature is never done of course, and there are a number of things we are looking to improve in the future:\n\n- Improving metrics and observability\n- Improving performance through more aggressive caching\n- Adding support for WebP and other features such as image blurring\n- Supporting content images embedded into GitLab issues and comments\n\nThe Memory team meanwhile will slowly step back from this work, however, and hand it over to product teams\nas product requirements evolve.\n",[709,9,1529],{"slug":1814,"featured":6,"template":686},"scaling-down-how-we-prototyped-an-image-scaler-at-gitlab","content:en-us:blog:scaling-down-how-we-prototyped-an-image-scaler-at-gitlab.yml","Scaling Down How We Prototyped An Image Scaler At Gitlab","en-us/blog/scaling-down-how-we-prototyped-an-image-scaler-at-gitlab.yml","en-us/blog/scaling-down-how-we-prototyped-an-image-scaler-at-gitlab",{"_path":1820,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1821,"content":1827,"config":1834,"_id":1836,"_type":13,"title":1837,"_source":15,"_file":1838,"_stem":1839,"_extension":18},"/en-us/blog/sentry-integration-blog-post",{"title":1822,"description":1823,"ogTitle":1822,"ogDescription":1823,"noIndex":6,"ogImage":1824,"ogUrl":1825,"ogSiteName":672,"ogType":673,"canonicalUrls":1825,"schema":1826},"Sentry's GitLab integration streamlines error remediation","Your code has bugs, my code has bugs, everyone’s code has bugs (probably). Let’s fix that.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749679964/Blog/Hero%20Images/sentry-io-blog.jpg","https://about.gitlab.com/blog/sentry-integration-blog-post","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Streamline and shorten error remediation with Sentry’s new GitLab integration\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Eva Sasson\"}],\n        \"datePublished\": \"2019-01-25\",\n      }",{"title":1828,"description":1823,"authors":1829,"heroImage":1824,"date":1831,"body":1832,"category":1630,"tags":1833},"Streamline and shorten error remediation with Sentry’s new GitLab integration",[1830],"Eva Sasson","2019-01-25","\n\n\u003C!-- blank line -->\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/KUHk1uuXWhA?rel=0\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\u003C!-- blank line -->\n\nSentry is open source error tracking that gives visibility across your entire stack and provides the details you need to fix bugs, ASAP. Because the only thing better than visibility and details is more visibility and details, Sentry improved their [GitLab integration](https://docs.sentry.io/workflow/integrations/global-integrations/gitlab/?utm_source=GitLab&utm_medium=blog&utm_campaign=GitLab_GTM) by adding [release](https://docs.sentry.io/workflow/releases/?platform=browser&utm_source=GitLab&utm_medium=blog&utm_campaign=GitLab_GTM) and [commit](https://docs.sentry.io/workflow/releases/?platform=browser&utm_source=GitLab&utm_medium=blog&utm_campaign=GitLab_GTM#link-repository) tracking as well as [suspect commits](https://docs.sentry.io/workflow/releases/?platform=browser&utm_source=GitLab&utm_medium=blog&utm_campaign=GitLab_GTM#after-linking-a-repository).\n\n### Streamline your workflow with issue management and creation\n\nWhen you receive an alert about an error, the last thing you want to do is to jump around 20 different tools trying to find out exactly what happened and where. Developers with both Sentry and GitLab in their application lifecycle benefit from issue management and issue creation to their GitLab accounts directly in the Sentry UI, alleviating some of the hassle of back-and-forth tool toggling.\n\n![GitLab account in Sentry](https://about.gitlab.com/images/blogimages/sentry/gitlab-sentry-integration.png){: .shadow.large.center}\n\nOf course, less tool jumping results in a more streamlined triaging process and shortened time to issue resolution – something that benefits the whole team.\n\n![Creating GitLab issue](https://about.gitlab.com/images/blogimages/sentry/create-gitlab-issue.png){: .shadow.medium.center}\n\nHave a GitLab issue that wasn’t created in Sentry? No problem. Existing issues are also easily linked.\n\n![Import GitLab issue](https://about.gitlab.com/images/blogimages/sentry/import-gitlab-issue.png){: .shadow.medium.center}\n\n### Find and fix bugs faster with release and commit tracking\n\nWhy stop at streamlining the triaging process, when we can also make issue resolution more efficient? Sentry’s GitLab integration now utilizes GitLab commits to find and fix bugs faster.\n\nWith the newly added release and commit tracking, an enhanced release overview page uncovers new and resolved issues, files changed, and authors. Developers can also resolve issues via commit messages or merge requests, see suggested assignees for issues, and receive detailed deploy emails.\n\nWant a big flashing arrow that points to an error’s root cause? Sentry’s suspect commits feature exposes the commit that likely introduced an error as well as the developer who wrote the broken code.\n\n![Suspect commits feature](https://about.gitlab.com/images/blogimages/sentry/suspect-commits-feature.png){: .shadow.medium.center}\n\nKeep in mind that this feature is available for Sentry users on “Teams” plans and above.\n{: .note}\n\nCheck out [Sentry’s GitLab integration documentation](https://docs.sentry.io/workflow/integrations/global-integrations/gitlab/?utm_source=GitLab&utm_medium=blog&utm_campaign=GitLab_GTM) to get started.\n\n### What’s next?\n\nAgain, why stop there, when we can do even more? GitLab is currently working to bring Sentry into the GitLab interface. Soon, GitLab and Sentry users will see their Sentry errors listed in their GitLab projects. Read the documentation on [the integration here](https://docs.gitlab.com/ee/operations/error_tracking.html).\n\n### About the guest author\n\nEva Sasson is a Product Marketer at [Sentry.io](https://sentry.io/welcome/), an open source error-tracking tool that gives developers the contextual information they need to resolve issues quickly, and integrates with the other development tools across the stack.\n",[108,707,682,230,731,708,9,732,709],{"slug":1835,"featured":6,"template":686},"sentry-integration-blog-post","content:en-us:blog:sentry-integration-blog-post.yml","Sentry Integration Blog Post","en-us/blog/sentry-integration-blog-post.yml","en-us/blog/sentry-integration-blog-post",{"_path":1841,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1842,"content":1848,"config":1853,"_id":1855,"_type":13,"title":1856,"_source":15,"_file":1857,"_stem":1858,"_extension":18},"/en-us/blog/software-test-at-gitlab",{"title":1843,"description":1844,"ogTitle":1843,"ogDescription":1844,"noIndex":6,"ogImage":1845,"ogUrl":1846,"ogSiteName":672,"ogType":673,"canonicalUrls":1846,"schema":1847},"An inside look at software testing at GitLab","Director of quality engineering Mek Stittri talks test technology and the future of automation at GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749680800/Blog/Hero%20Images/softwaretestlaunch.jpg","https://about.gitlab.com/blog/software-test-at-gitlab","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"An inside look at software testing at GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Valerie Silverthorne\"}],\n        \"datePublished\": \"2019-08-30\",\n      }",{"title":1843,"description":1844,"authors":1849,"heroImage":1845,"date":1850,"body":1851,"category":680,"tags":1852},[677],"2019-08-30","\n\n_In our [just-released survey of over 4,000 developers, security\nprofessionals, and operations team members](/developer-survey/), there was one thing everyone agreed on: 50% of each group\nsaid software testing is the biggest reason why development is delayed. Testers have long\nbeen the underdogs in the SDLC and that viewpoint is apparently very slow to change.\nTo understand what’s really going on, and how things work at GitLab, we\nasked [Mek Stittri](/company/team/#mekdev), director of quality engineering, to share his\nperspective on what’s working with test today and what’s in need of improvement._\n\n## Why is test a continued DevOps problem?\n\nIt’s a two-part answer, Mek says. First, there are simply not enough tests run and second, the tests that are used are often flaky (meaning their results aren’t necessarily trustworthy).\n\nTackling the issue of not running enough tests, Mek says it’s an area GitLab is addressing. “At GitLab, I think we are better than other companies where developers write unit tests and integration tests every time a change goes in,” he says. “That is great, but that testing is at a lower level, and it doesn't really map to a business use case.” To write better tests a team needs test requirements, but there can be so many different sets of stakeholders that it can be tough to get their input about *test* requirements and not just feature requirements. “We are improving it here at GitLab where our VP of Product [Scott Williamson](https://gitlab.com/sfwgitlab) is doing a great job. We have a section for test requirements right now (in the issue and merge request templates). It's now a blank and free form for people to fill in, but it should be highlighted going forward as a required section taking input from product discovery and validation as a deliverable.”\n\nThe bottom line: the stakeholders who are delivering the code need to understand the end goal better. “Unit tests test code at a smaller scale, and that’s great, but it doesn’t really verify the functionality works end to end as a whole. We need more coverage and more understanding of what needs to be tested.”\n\n![The Apollo 11 launch framework](https://about.gitlab.com/images/blogimages/apollo11framework.png){: .shadow.small.center}\nApollo 11 is held up by a framework and software is no different.\n{: .note.text-center}\n\nMek likens this process to Apollo 11. Everyone is excited about the rocket (the software features, in other words) but no one pays attention to the red scaffolding on the right that’s actually holding the rocket up. “That’s the side that nobody looks at but it’s a lot of work,” he says. “It’s taller than the rocket. We need to build that platform to have adequate testing (functional, performance, etc).” The ideal situation to get a company there? Start building the test framework and add test coverage at the exact same time the product is being built. “You assemble it together, run it, it’s passing and we go for launch and it’s shipped. We’re not there yet. And I can assure you a lot of companies out there aren’t there yet either.”\n\n## About those flaky tests…\n\n“There are a lot of test automation engineers and test developers out there, but not all of them know how to write and design a good test,” Mek explains. Automated tests needs to function like a flow of self-retrying dominoes where if one step is not completed it needs to keep retrying to reach the next step. Tests need to mimic what a manual tester would do, he says. No manual tester is going to click on a button and then wait 10 minutes. The tester will click again, or try other strategies. “At GitLab [we put emphasis on test framework reliability](/handbook/engineering/quality/#test-framework-reliability-and-efficiency) and we treat each user workflow step like a piece of retrying dominoes. We need to make sure all the dominoes fall over so the workflow is completed,” Mek says.\n\n>We need more coverage and more understanding of what needs to be tested.\n\nSo companies need to think through how the tests work, but also test the right things. If that happens, quality can be everyone’s responsibility in the end, Mek says. “We want developers to contribute to the end-to-end test so you want to make a test framework that is easy to use and easy to read. I think this all factors in.” And Mek points out it really is in everyone’s best interests to think about quality first. “Let's make the process better so we work smarter, right? We achieve more without having to work weekends or get pinged during your family dinner. Nobody wants that.”\n\n## Test automation and machine learning\n\nTest automation is a cornerstone of successful [DevOps](/topics/devops/) but it remains difficult for many companies to achieve. Mek’s take: “We need to design the product such that the test automation framework can integrate into it well,” he says flatly. That requires good collaboration with development teams due to frontend UI locators and backend APIs that are the interfaces to enable better and stable test automation. “Go back to Apollo 11,” Mek says. “It's like the connections along the rocket's fuselage. I need to integrate with this to make sure things are working fine. The probes and sensors need to be there. So if those aren't there, then your test automation engineers need to code around these obstacles. It's not working smart.” In other words, the test automation framework should not take the longer route when executing user interactions to the application because this can be the source of unstable and in-efficient tests.\n\nOne step that can help companies – including GitLab – get there is [machine learning](https://medium.com/machine-learning-for-humans/why-machine-learning-matters-6164faf1df12). “We are having discussions here at GitLab about where we want a bot,” Mek says. “I think machine learning will come and help, but the input and output needs to be clearly defined so you have a clear implementation direction, TensorFlow, Linear Regression, or whatever techniques. You can write a bot that just lives in the product, meaning it looks at all the UI locators (dedicated to test automation) on a page and randomly clicks one of those links.” This GitLab bot of the future will work 24/7, clicking, clicking, clicking on the page until it errors out or runs into a 404, Mek says. The goal is to create a bot that is like a “menacing QA engineer” that can be programmed to keep banging on the problematic areas until everything is solved. To get there will require lots of data – machine learning literally needs to learn from data and experience – and although there are a handful of companies experimenting with this now, this is all still very early stage.\n\n## Where we’re headed with testing\n\nMek and his team hope to increase both quality and productivity this year which may be a bit of a balancing act, since more “quality” equals more testing which can result in a longer development cycle and perhaps reduced productivity (this is why we say test automation engineers are often unappreciated!). “My department is working this quarter to have a full suite of automated tests for our enterprise features. We want to have a big checkbox for the enterprise features every time we deploy. We need this because it is mapping to the business use case.” But Mek and team need to do all of that while shortening the test runtime for developers. “You want more test coverage but we need to keep the runtime low because we can’t have developers and release managers wait two hours.”\n\nThe plan is to add more runners, optimize them, de-duplicate some tests and make sure the process is as streamlined as it can be. “Right now it takes about an hour or so, but I would love to have it down to 30 minutes where we certify that this merge request going in checks all the boxes and all the enterprise features are not broken. We need to set ourselves an aggressive goal and I would say 30 minutes is a good first step.”\n\nCover image by [Kurt Cotoaga](https://unsplash.com/@kydroon) on [Unsplash](https://unsplash.com)\n{: .note}\n",[881,9,682,709],{"slug":1854,"featured":6,"template":686},"software-test-at-gitlab","content:en-us:blog:software-test-at-gitlab.yml","Software Test At Gitlab","en-us/blog/software-test-at-gitlab.yml","en-us/blog/software-test-at-gitlab",{"_path":1860,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1861,"content":1866,"config":1872,"_id":1874,"_type":13,"title":1875,"_source":15,"_file":1876,"_stem":1877,"_extension":18},"/en-us/blog/test-automation-devops",{"title":1862,"description":1863,"ogTitle":1862,"ogDescription":1863,"noIndex":6,"ogImage":1499,"ogUrl":1864,"ogSiteName":672,"ogType":673,"canonicalUrls":1864,"schema":1865},"Trust, but verify: The importance of software test automation","Guest author Steve Ropa explains what a Cold War era motto has to do with test automation (seriously) and bringing development and operations closer together.","https://about.gitlab.com/blog/test-automation-devops","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Trust, but verify: The importance of software test automation\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Steve Ropa\"}],\n        \"datePublished\": \"2018-04-23\",\n      }",{"title":1862,"description":1863,"authors":1867,"heroImage":1499,"date":1869,"body":1870,"category":680,"tags":1871},[1868],"Steve Ropa","2018-04-23","\nThis article is about software [test automation and DevOps](/topics/devops/), but bear with me a moment before we go there. Back during the Cold War, there were many discussions about how to improve relations and reduce the number of nuclear missiles countries were pointing at each other. During these talks, one of the biggest sticking points was how much the two countries could trust each other, and if they couldn’t, how they maintained their sovereignty while under some form of arms control agreement. They eventually agreed upon a regime of agreeing to perform certain actions, with periodic inspections on each side to confirm that said actions would happen, based on the Russian proverb, “trust, but verify.”\n\n## Trust but verify: DevOps requires trust\n\n“Trust, but verify” is commonly used with reference to security, but needs to be the motto for a good DevOps automation pipeline as well. First, for all the developers who ask, “Why should I care about DevOps?” I just want to mention that the key to success in *any* type of development is a short feedback loop and truly safe code. DevOps is a fantastic mechanism for helping us achieve that, by taking the valuable technical practices we’ve learned and applying them frequently and smoothly. DevOps as a cultural phenomenon relies heavily on Lean principles and on the concept of “trust-based management” where we put our trust in the teams to be professional craftsmen. This is important and can’t be overstated. We hire professional developers to do professional development, and we must trust them to get the job done.\n\n## Trust but verify: Today’s world requires verification\n\nAnd yet, there is a challenge. In most cases, software is not being created in a vacuum. We must make sure we are integrating well with the rest of the system. Sometimes, we have legal requirements like [Sarbanes-Oxley compliance](https://en.wikipedia.org/wiki/Sarbanes%E2%80%93Oxley_Act). In the past, we would schedule a large block of time to do all the integration and compliance testing. Now, we are saying, “Trust us, we will do the right thing” and deploying to production as quickly as is practicable. This is where automated testing comes into play.\n\n## Three categories of verification\n\nLet’s break this into three categories: Unit, System, and Compliance. This may not be the breakdown we usually think of, but bear with me. Unit testing is the most commonly discussed level of testing among modern developers. We write small, concise tests that exercise the code under development, ensuring it does what we intend it to do. Preferably we are doing test-driven development, and writing the tests prior to the code, but that is a blog for another time. Under the heading of “trust, but verify,” rather than slow down our development cycle with code review gates and, dare I say it, maybe even merge requests, we allow teams to check code in whenever they feel the need. Then we verify that we haven’t done anything untoward by running the automated unit tests on the build environment. This level of verification isn’t enough to ensure a truly high-quality system, but the lack of this level of verification is a definite step on the road to perdition.\n\n## System-level verification\n\nI like to use System testing to describe the various next level tests. This would be any system-wide tests, such as Acceptance, Integration, and possibly Performance testing. Unfortunately, many development teams stop at automated unit tests. They give lip service to automating other tests “when we have time.” And we know when that is. So, it gets skipped. Or is treated as a luxury. Since we often don’t have enough time to do all this testing manually either, we end up with defects and low-quality user experiences, which erodes trust in our teams’ ability to create and innovate. This downward spiral usually ends with organizations building giant processes and Change Advisory Boards to slow down the pace of change, all in the name of safety.\n\nSo instead, we trust our team to create innovative software, working closely with their customer or other representatives of said customer. To verify the teams are creating the right software, we represent the needs of the customer in terms of tests. These tests are automated to begin with, again preferably before creating the actual product. Then, we include the automated System tests into the DevOps pipeline, running with each check in. Now we can feel safe that the system is stable and represent the customers’ needs to the best of our ability to understand them.\n\n## Safety\n\nLastly, we need to verify that our teams are creating safe software. There are some excellent tools for automated security and safe programming scans. Include these as well into your pipeline. If they take too long, you can consider an alternate pipeline that runs less frequently, but start by running with each check in, until you feel that it just is getting too bogged down.\n\nIn the end, we are back to the basic statement, “trust but verify.” We won’t put massive processes and boards of review in place, slowing down the pace of development. We won’t create giant overarching architectures and just allow our developers to “fill in the blanks.” We will present them with the needs of the system and trust them to develop great software. Meanwhile, we will support them by verifying, many times a day, that they are still on track. Hey, if it worked for nuclear weapons, surely it can work for software.\n\n## About the guest author\n\nSteve Ropa is a Co-founder and Master Craftsman at the [Rocky Mountain Programmers Guild](https://www.rmprogrammers.com/) in Denver, Colorado, where he brings his long career of successful software delivery to elevate developers and teams to new levels of performance and Craftsmanship.\n\n[Photo](https://unsplash.com/photos/GNyy-D-SNN8?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) by Guillaume Lebelt on [Unsplash](https://unsplash.com/search/photos/patterns?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n",[682,9],{"slug":1873,"featured":6,"template":686},"test-automation-devops","content:en-us:blog:test-automation-devops.yml","Test Automation Devops","en-us/blog/test-automation-devops.yml","en-us/blog/test-automation-devops",{"_path":1879,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1880,"content":1885,"config":1890,"_id":1892,"_type":13,"title":1893,"_source":15,"_file":1894,"_stem":1895,"_extension":18},"/en-us/blog/the-gitlab-guide-to-modern-software-testing",{"title":1881,"description":1882,"ogTitle":1881,"ogDescription":1882,"noIndex":6,"ogImage":1499,"ogUrl":1883,"ogSiteName":672,"ogType":673,"canonicalUrls":1883,"schema":1884},"The GitLab guide to modern software testing","If test is your DevOps team's Public Enemy No. 1, it's time to rethink your strategy. Here's what you need to know about modern software testing.","https://about.gitlab.com/blog/the-gitlab-guide-to-modern-software-testing","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"The GitLab guide to modern software testing\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Valerie Silverthorne\"}],\n        \"datePublished\": \"2022-08-18\",\n      }",{"title":1881,"description":1882,"authors":1886,"heroImage":1499,"date":1887,"body":1888,"category":901,"tags":1889},[677],"2022-08-18","\nWhat's the trickiest part of DevOps? It's software testing, hands down. Year after year, respondents to our [annual DevSecOps surveys](/developer-survey/) have called out testing as the most likely reason for release delays. And that's not all they said: \"Testing takes too long,\" \"There are too many tests,\" \"We need to do more testing,\" “We need more automated testing but don't have time,\" \"Testing happens too late,\" etc.\n\nClearly something this fraught needs all the help, so here is our best advice to get testing \"just right\" in any modern DevOps practice. \n\n## Use the right metrics\n\nAll of the testing in the world doesn't matter if a DevOps team is measuring the wrong things. At GitLab, we use industry-standard metrics, but we look at them a bit differently. When it comes to S1 and S2 bugs we don’t count the time to close but rather the age of the bugs that remain open. Our reasoning? We want to look forward, but we also don't want to [incentivize closing only newer bugs](/blog/gitlab-top-devops-tooling-metrics-and-targets/). So it's important to make sure DevOps teams are looking at the right metrics and with shared goals in mind.\n\n## Forget flaky\n\nTests are noisy, and they can be flaky, setting off alarms and disrupting developer flow, often for no reason. That's at the heart of developer frustration with testing, and one of the biggest problems DevOps teams need to solve. GitLab's Vice President of Quality [Mek Stittri](/company/team/#meks) suggests re-thinking how automated tests are created. Tests need to be validating the right things, but that must include looking at how all of the code components work together and not just at pieces of code. Finally, it doesn't hurt to [develop a manual testing mindset](/blog/software-test-at-gitlab/).\n\n## Make it modern\n\nIn fact, a manual testing mindset, where test designers create tests that actually mimic what real users do, is a key underpinning of modern software testing in DevOps. Testers need to consider getting certified, embracing new technologies like AI, and, perhaps most importantly, be [evangelists for quality](/blog/how-to-leverage-modern-software-testing-skills-in-devops/) on a DevOps team.\n\n## Make automation work harder\n\nSoftware testing may be the most annoying DevOps step, but there's no doubt that automating the process makes everything work more smoothly. Teams with test automation [have fewer complaints about release delays](/blog/want-faster-releases-your-answer-lies-in-automated-software-testing/). And teams that have taken it up a notch and added AI/ML into their test automation process are even more upbeat about testing. After all, bots [don't need to take a lunch break or a vacation](/blog/the-software-testing-life-cycle-in-2021-a-more-upbeat-outlook/). Finally, if automation is well thought out, QA and developers can [actually work together to get code out the door](/blog/what-blocks-faster-code-release/).\n\n## Test for everything\n\nFor all the developer finger-pointing around software testing, it's also clear from our surveys that _more_ testing – of everything – has to happen. When considering how to modernize a software testing strategy, don't forget that \"nice to haves\" like [accessibility testing](/blog/introducing-accessibility-testing-in-gitlab/) aren't actually optional but critical for success.\n\nAnd also don't overlook the potential of newer test techniques like [fuzzing](/blog/why-continuous-fuzzing/), which can work with [Go](/blog/how-to-fuzz-go/), [Rust](/blog/how-to-fuzz-rust-code/), and other languages, and take testing into places other methodologies cannot.\n\n## The bottom line\n\nTesting doesn't have to be the enemy of speedy releases or the object of so much frustration. Start fresh with a modern software testing approach and and make it easy for teams to get the most out of QA.\n",[9,682,1529],{"slug":1891,"featured":6,"template":686},"the-gitlab-guide-to-modern-software-testing","content:en-us:blog:the-gitlab-guide-to-modern-software-testing.yml","The Gitlab Guide To Modern Software Testing","en-us/blog/the-gitlab-guide-to-modern-software-testing.yml","en-us/blog/the-gitlab-guide-to-modern-software-testing",{"_path":1897,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1898,"content":1904,"config":1911,"_id":1913,"_type":13,"title":1914,"_source":15,"_file":1915,"_stem":1916,"_extension":18},"/en-us/blog/tips-to-configure-browser-based-dast-scans",{"title":1899,"description":1900,"ogTitle":1899,"ogDescription":1900,"noIndex":6,"ogImage":1901,"ogUrl":1902,"ogSiteName":672,"ogType":673,"canonicalUrls":1902,"schema":1903},"Tips to configure browser-based DAST scans","Learn how to use the browser-based analyzer with common dynamic application security testing settings, based on web application attributes, to ensure successful scans.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659561/Blog/Hero%20Images/securitycheck.png","https://about.gitlab.com/blog/tips-to-configure-browser-based-dast-scans","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Tips to configure browser-based DAST scans\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Julie Byrne\"},{\"@type\":\"Person\",\"name\":\"Jerez Solis\"}],\n        \"datePublished\": \"2023-11-14\",\n      }",{"title":1899,"description":1900,"authors":1905,"heroImage":1901,"date":1908,"body":1909,"category":708,"tags":1910},[1906,1907],"Julie Byrne","Jerez Solis","2023-11-14","The GitLab Dynamic Application Security Testing ([DAST](https://docs.gitlab.com/ee/user/application_security/dast/)) scan uses an actively running environment to crawl the application and find misconfigurations of your application server or incorrect assumptions about security controls that may not be visible from the source code. GitLab now provides a proprietary [browser-based analyzer](https://docs.gitlab.com/ee/user/application_security/dast/browser_based.html) for scanning applications that make heavy use of JavaScript, including single-page web applications. The DAST scan needs to be configured properly to account for various web application attributes, including authentication mechanism, authenticated landing page, and page load times. In this tutorial, you will learn common configurations that have helped our customers use the browser-based analyzer to successfully implement DAST scans. \n\n## General considerations\n\nThe browser-based DAST scan takes the URL of the application it's supposed to scan from the `DAST_WEBSITE` environment variable. This should point to a test environment - you should not run a DAST scan against a production environment, even if you are only running a passive scan. For ephemeral environments that are deployed as part of the CI/CD pipeline, you can save the URL of the environment as an artifact `environment_url.txt`, which will then be used by the [DAST scan template job](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml) to set the `DAST_WEBSITE` variable.  The [GitLab Auto DevOps deploy template job](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml) has an example of this. \n\nDepending on the size of the web application, a DAST scan may take an hour or more to complete. You will want to ensure that whatever runner is used to perform the DAST scan has a [job timeout value](https://docs.gitlab.com/ee/ci/runners/configure_runners.html#set-maximum-job-timeout-for-a-runner) set to be long enough to allow the scan to complete. Similarly, you should ensure that the [project level CI/CD timeout](https://docs.gitlab.com/ee/ci/pipelines/settings.html#set-a-limit-for-how-long-jobs-can-run) is sufficient to allow the job to complete. **Note:** Shared runners on gitlab.com have a runner timeout of 180 minutes, regardless of the project CI/CD timeout set.\n\n## Configuration options for websites requiring authentication\n\nMany web applications require a user to log in to access the site. Logins can be implemented as basic http authentication or, more commonly, as form authentication.  For form authentication, the login form might be implemented in one of several ways:\n\n1. username and password fields on the main website landing page\n2. a login button that opens a modal (also called a modal window or lightbox) that displays in front of the page and disables all other page content until the login is completed or the modal is closed; there is not a separate URL associated with a modal window\n3. a login button that opens a new window, with its own URL \n\nAdditionally, the form may be either a single-step form, where the username and password fields are on the same page, or a multi-step form, where the username and password fields are on separate pages.  \n\nWhen running a DAST scan, the analyzer must know how to [authenticate](https://docs.gitlab.com/ee/user/application_security/dast/authentication.html).  We need to specify these details via the appropriate variables.  \n\nThe `DAST_USERNAME` and `DAST_PASSWORD` variables specify the login credentials to be used. The variable values should be set via masked variables at the project level, not included within the `.gitlab-ci.yml` file.\n\n### URL variable values\n\nVarious URL values must also be specified: \n   - `DAST_AUTH_URL` - the URL of the login page\n   - `DAST_WEBSITE` - specifies the URL of the page the user is redirected to after logging in\n\n**Note:** If your website uses authentication with a login button that opens a new window with its own URL, you should specify the URL of that new window as the `DAST_AUTH_URL` value.  \n\nGitLab enhancements are currently being implemented to support cases where additional actions must be taken post-login prior to being brought to the main site. See [this epic](https://gitlab.com/groups/gitlab-org/-/epics/11585)  for details: DAST browser-based analyzer multi-step login form does not support 'keep me signed in' workflow (AzureAD).\n\n### Field variable values\n\n`FIELD` variables specify the page elements used. These values can typically be identified by inspecting the page source. For single-step login pages, you will need to specify:\n - `DAST_USERNAME_FIELD`\n - `DAST_PASSWORD_FIELD`\n - `DAST_SUBMIT_FIELD` \n\nFor multi-step logins, you would instead specify:\n\n - `DAST_FIRST_SUBMIT_FIELD` - the button clicked after entering the username\n - `DAST_SUBMIT_FIELD` - the button clicked after entering the password\n\nIf your login button opens a modal, you should also specify `DAST_BROWSER_PATH_TO_LOGIN_FORM`, which provides the path of elements to click to get from the initial login URL to the login fields.\n\n#### Examples\n\n![username email field](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683113/Blog/Content%20Images/username_email_field.png)\n\nIn this example, you can target the input element for the username field in different ways. Keep in mind that the selector you chose should be resilient to the application changing. We recommend to use the `id` and `name` attributes, as these are generally unique on a page and rarely change.\n\n```\nDAST_USERNAME_FIELD: \"id:user_login\"\nDAST_USERNAME_FIELD: \"name:user[login]\"\nDAST_USERNAME_FIELD: \"css:input[type=text]\"\n```\n\nThe same process can be followed for the password field. For example:\n\n```\nDAST_PASSWORD_FIELD: \"id:user_password\"\nDAST_PASSWORD_FIELD: \"name:user[password]\"\nDAST_PASSWORD_FIELD: \"css:input[type=password]\"\n```\n\n**Submit/Sign in/Login button**\n\n![login](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683114/Blog/Content%20Images/login.png)\n\nYou can target the submit/sign in/login button using this selector:\n\n```\nDAST_SUBMIT_FIELD: \"css:button[type=submit]\"\n```\n\n**Note:** If the submit button is not a `\u003Cbutton>`, but an input element in the form of `\u003Cinput type=\"submit\" name=\"login\">`, you can use one of the following selectors:\n\n```\nDAST_SUBMIT_FIELD: \"css:input[type=submit]\"\nDAST_SUBMIT_FIELD: \"css:input[name=login]\"\n```\n\n### Other variables to set\n\nIf the username and password fields are on separate pages, DAST has to wait after submitting the username before looking for the password field.\n\nThe `DAST_BROWSER_ACTION_STABILITY_TIMEOUT` variable, with a default value of 800ms, specifies the wait time. This time can be increased if the login response time is slow.\n\nIf the website has a large JavaScript file that is required to load the target application, it is recommended that you use the variable `DAST_BROWSER_MAX_RESPONSE_SIZE_MB` to increase the limit for response sizes. The default is 10MB but can be increased for 50MB or more, if necessary.\n\n## Tools for troubleshooting\n\nSeveral tools will help with DAST scan troubleshooting:\n\n- [authentication report](https://docs.gitlab.com/ee/user/application_security/dast/authentication.html#configure-the-authentication-report) - This report can be produced during the scan and saved as a CI/CD job artifact to assist with understanding the cause of an authentication failure. The report contains steps performed during the login process, HTTP requests and responses, the Document Object Model (DOM), and screenshots. To configure the report, set `DAST_AUTH_REPORT` to `true` and configure an artifacts attribute for the DAST job, e.g.:\n\n```\ndast:\n   variables:\n      DAST_WEBSITE: \"https://example.com\"\n      DAST_AUTH_REPORT: \"true\"\n    artifacts:\n      paths: [gl-dast-debug-auth-report.html]\n      when: always\n```\n\n- [analyzer logs](https://docs.gitlab.com/ee/user/application_security/dast/browser_based_troubleshooting.html#browser-based-analyzer-logging) - Setting `DAST_BROWSER_LOG` to `auth:debug` or `auth:trace` will provide additional logging that may help identify an issue with the scan. \n\nThe browser-based DAST scan configuration depends on the specific attributes of the web application you're testing, including how authentication is implemented to access the web site, what buttons are used, and how fast your browser loads once the user has authenticated. Using the appropriate variables to guide the analyzer through the authentication process will ensure that you are able to run a successful scan. And robust error logging and the authentication report will provide additional pointers to where the configuration might be incorrect and need to be adjusted.\n\nTry DAST scanning with [a free trial of GitLab Ultimate](https://about.gitlab.com/free-trial/).\n\n## Read more\n- [How to configure DAST full scans for complex web applications](https://about.gitlab.com/blog/how-to-configure-dast-full-scans-for-complex-web-applications/)\n- [How we're using DAST 2 for easier scan configuration](https://about.gitlab.com/blog/how-were-using-dast2-for-easier-scan-configuration/)\n",[708,9,9,798],{"slug":1912,"featured":6,"template":686},"tips-to-configure-browser-based-dast-scans","content:en-us:blog:tips-to-configure-browser-based-dast-scans.yml","Tips To Configure Browser Based Dast Scans","en-us/blog/tips-to-configure-browser-based-dast-scans.yml","en-us/blog/tips-to-configure-browser-based-dast-scans",{"_path":1918,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1919,"content":1925,"config":1932,"_id":1934,"_type":13,"title":1935,"_source":15,"_file":1936,"_stem":1937,"_extension":18},"/en-us/blog/tracking-down-missing-tcp-keepalives",{"title":1920,"description":1921,"ogTitle":1920,"ogDescription":1921,"noIndex":6,"ogImage":1922,"ogUrl":1923,"ogSiteName":672,"ogType":673,"canonicalUrls":1923,"schema":1924},"Tracking TCP Keepalives: Lessons in Docker, Golang & GitLab","An in-depth recap of debugging a bug in the Docker client library.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749680874/Blog/Hero%20Images/network.jpg","https://about.gitlab.com/blog/tracking-down-missing-tcp-keepalives","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"What tracking down missing TCP Keepalives taught me about Docker, Golang, and GitLab\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Stan Hu\"}],\n        \"datePublished\": \"2019-11-15\",\n      }",{"title":1926,"description":1921,"authors":1927,"heroImage":1922,"date":1929,"body":1930,"category":705,"tags":1931},"What tracking down missing TCP Keepalives taught me about Docker, Golang, and GitLab",[1928],"Stan Hu","2019-11-15","\n\nThis blog post was originally published on the GitLab Unfiltered blog. It was reviewed and republished on 2019-12-03.\n{: .alert .alert-info .note}\n\nWhat began as failure in a GitLab static analysis check led to a\ndizzying investigation that uncovered a subtle [bug in the Docker client\nlibrary code](https://github.com/docker/for-linux/issues/853) used by\nthe GitLab Runner. We ultimately worked around the problem by upgrading\nthe Go compiler, but in the process we uncovered an unexpected change in\nthe Go TCP keepalive defaults that fixed an issue with Docker and GitLab\nCI.\n\nThis investigation started on October 23, when backend engineer [Luke\nDuncalfe](/company/team/#.luke) mentioned, \"I'm seeing\n[`static-analysis` failures with no output](https://gitlab.com/gitlab-org/gitlab/-/jobs/331174397).\nIs there something wrong with this job?\" He opened [a GitLab\nissue](https://gitlab.com/gitlab-org/gitlab/issues/34951) to discuss.\n\nWhen Luke ran the static analysis check locally on his laptop, he saw\nuseful debugging output when the test failed. For example, an extraneous\nnewline would accurately be reported by Rubocop. However, when the same\ntest ran in GitLab's automated test infrastructure, the test failed\nquietly:\n\n![Failed job](https://about.gitlab.com/images/blogimages/docker-tcp-keepalive-debug/job-failure.png){: .shadow.center}\n\nNotice how the job log did not include any clues after the `bin/rake\nlint:all` step. This made it difficult to determine whether a real\nproblem existed, or whether this was just a flaky test.\n\nIn the ensuing days, numerous team members reported the same problem.\nNothing kills productivity like silent test failures.\n\n## Was something wrong with the test itself?\n\nIn the past, we had seen that if that specific test generated enough\nerrors, [the output buffer would fill up, and the continuous integration\n(CI) job would lock\nindefinitely](https://gitlab.com/gitlab-org/gitlab-foss/issues/61432). We\nthought we had [fixed that issue months\nago](https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/28402). Upon\nfurther review, that fix seemed to eliminate any chance of a thread\ndeadlock.\n\nDid we have to flush the buffer? No, because the Linux kernel will do\nthat for an exiting process already.\n\n## Was there a change in how CI logs were handled?\n\nWhen a test runs in GitLab CI, the [GitLab\nRunner](https://gitlab.com/gitlab-org/gitlab-runner/) launches a Docker\ncontainer that runs commands specified by a `.gitlab-ci.yml` inside the\nproject repository. As the job runs, the runner streams the output to\nthe GitLab API via PATCH requests. The GitLab backend saves this data\ninto a file. The following sequence diagram shows how this works:\n\n```plantuml\n== Get a job! ==\nRunner -> GitLab: POST /api/v4/jobs/request\nGitLab -> Runner: 201 Job was scheduled\n\n== Job sends logs (1 of 2) ==\nRunner -> GitLab: PATCH /api/v4/job/:id/trace\nGitLab -> File: Save to disk\nGitLab -> Runner: 202 Accepted\n\n== Job sends logs (2 of 2) ==\nRunner -> GitLab: PATCH /api/v4/job/:id/trace\nGitLab -> File: Save to disk\nGitLab -> Runner: 202 Accepted\n```\n\n[Henrich Lee Yu](/company/team/#engwan) mentioned\nthat we had recently [disabled a feature flag that changed how GitLab\nhandled CI job\nlogs](https://docs.gitlab.com/ee/administration/job_logs.html#new-incremental-logging-architecture). [The\ntiming seemed to line\nup](https://gitlab.com/gitlab-org/gitlab/issues/34951#note_236723888).\n\nThis feature, called live CI traces, eliminates the need for a shared\nPOSIX filesystem (e.g., NFS) when saving job logs to disk by:\n\n1. Streaming data into memory via Redis\n2. Persisting the data in the database (PostgreSQL)\n3. Archiving the final data into object storage\n\nWhen this flag is enabled, the flow of CI job logs looks something like\nthe following:\n\n```plantuml\n== Get a job! ==\nRunner -> GitLab: POST /api/v4/jobs/request\nGitLab -> Runner: 201 Job was scheduled\n\n== Job sends logs ==\nRunner -> GitLab: PATCH /api/v4/job/:id/trace\nGitLab -> Redis: Save chunk\nGitLab -> Runner: 202 Accepted\n...\n== Copy 128 KB chunks from Redis to database ==\nGitLab -> Redis: GET gitlab:ci:trace:id:chunks:0\nGitLab -> PostgreSQL: INSERT INTO ci_build_trace_chunks\n...\n== Job finishes ==\n\nRunner -> GitLab: PUT /api/v4/job/:id\nGitLab -> Runner: 200 Job was updated\n\n== Archive trace to object storage ==\n```\n\nLooking at the flow diagram above, we see that this approach has more\nsteps. After receiving data from the runner, something could have gone\nwrong with handling a chunk of data. However, we still had many\nquestions:\n\n1. Did the runners send the right data in the first place?\n1. Did GitLab drop a chunk of data somewhere?\n1. Did this new feature actually have anything to do with the problem?\n1. Are they really making another Gremlins movie?\n\n## Reproducing the bug: Simplify the `.gitlab-ci.yml`\n\nTo help answer those questions, we simplified the `.gitlab-ci.yml` to\nrun only the `static-analysis` step. We inserted a known Rubocop error,\nreplacing a `eq` with `eql`. We first ran this test on a separate GitLab\ninstance with a private runner. No luck there – the job showed the right\noutput:\n\n```\nOffenses:\n\nee/spec/models/project_spec.rb:55:42: C: RSpec/BeEql: Prefer be over eql.\n        expect(described_class.count).to eql(2)\n                                         ^^^\n\n12669 files inspected, 1 offense detected\n```\n\nHowever, we repeated the test on our staging server and found that we\nreproduced the original problem. In addition, the live CI trace feature\nflag had been activated on staging. Since the problem occurred with and\nwithout the feature, we could eliminate that feature as a possible\ncause.\n\nPerhaps something with the GitLab server environment caused a\nproblem. For example, could the load balancers be rate-limiting the\nrunners? As an experiment, we pointed a private runner at the staging\nserver and re-ran the test. This time, it succeeded: the output was\nshown. That seemed to suggest that the problem had more to do with the\nrunner than with the server.\n\n## Docker Machine vs. Docker\n\nOne key difference between the two tests: One runner used a shared,\nautoscaled runner using a [Docker\nMachine](https://docs.docker.com/machine/overview/) executor, and the\nprivate runner used a [Docker\nexecutor](https://docs.gitlab.com/runner/executors/docker.html).\n\nWhat does Docker Machine do exactly? The following diagram may help\nillustrate:\n\n![Docker Machine](https://docs.docker.com/machine/img/machine.png){: .medium.center}\n\nThe top-left shows a local Docker instance. When you run Docker from the\ncommand-line interface (e.g., `docker attach my-container`), the program\njust makes [REST calls to the Docker Engine\nAPI](https://docs.docker.com/engine/api/v1.40/).\n\nThe rest of the diagram shows how Docker Machine fits into the\npicture. Docker Machine is an entirely separate program. The GitLab\nRunner shells out to `docker-machine` to create and destroy virtual\nmachines using cloud-specific (e.g. Amazon, Google, etc.) drivers. Once\na machine is running, the runner then uses the Docker Engine API to run,\nwatch, and stop containers.\n\nNote that this API is used securely over an HTTPS connection. This is an\nimportant difference between the Docker Machine executor and Docker\nexecutor: The former needs to communicate across the network, while the\nlatter can either use a local TCP socket or UNIX domain socket.\n\n## Google Cloud Platform timeouts\n\nWe've known for a while that Google Cloud [has a 10-minute idle\ntimeout](https://cloud.google.com/compute/docs/troubleshooting/general-tips),\nwhich has caused issues in the past:\n\n> Note that idle connections are tracked for a maximum of 10 minutes,\n> after which their traffic is subject to firewall rules, including the\n> implied deny ingress rule. If your instance initiates or accepts\n> long-lived connections with an external host, you should adjust TCP\n> keep-alive settings on your Compute Engine instances to less than 600\n> seconds to ensure that connections are refreshed before the timeout\n> occurs.\n\nWas the problem caused by this timeout? With the Docker Machine\nexecutor, we found that we could reproduce the problem with a simple\n`.gitlab-ci.yml`:\n\n```yaml\nimage: \"busybox:latest\"\n\ntest:\n  script:\n    - date\n    - sleep 601\n    - echo \"Hello world!\"\n    - date\n    - exit 1\n```\n\nThis would reproduce the failure, where we would never see the `Hello\nworld!` output. Changing the `sleep 601` to `sleep 599` would make the\nproblem go away. Hurrah! All we have to do is tweak the system TCP\nkeepalives, right? Google provided these sensible settings:\n\n```sh\nsudo /sbin/sysctl -w net.ipv4.tcp_keepalive_time=60 net.ipv4.tcp_keepalive_intvl=60 net.ipv4.tcp_keepalive_probes=5\n```\n\nHowever, enabling these kernel-level settings didn't solve the\nproblem. Were keepalives even being sent? Or was there some other issue?\nWe turned our attention to network traces.\n\n## Eavesdropping on Docker traffic\n\nIn order to understand what was happening, we needed to be able to\nmonitor the network communication between the runner and the Docker\ncontainer. But how exactly does the GitLab Runner stream data from a\nDocker container to the GitLab server?  The following diagram\nillustrates the flow:\n\n```plantuml\nRunner -> Docker: POST /containers/name/attach\nDocker -> Runner: \u003Ccontainer output>\nDocker -> Runner: \u003Ccontainer output>\nRunner -> GitLab: PATCH /api/v4/job/:id/trace\nGitLab -> File: Save to disk\nGitLab -> Runner: 202 Accepted\n```\n\nFirst, the runner makes a [POST request to attach to the container\noutput](https://docs.docker.com/engine/api/v1.40/#operation/ContainerAttach).\nAs soon as a process running in the container outputs some data, Docker\nwill transmit the data over this HTTPS stream. The runner then copies\nthis data to GitLab via the PATCH request.\n\nHowever, as mentioned earlier, traffic between a GitLab Runner and the\nremote Docker machine is encrypted over HTTPS on port 2376. Was there an\neasy way to disable HTTPS? Searching through the code of Docker Machine,\nwe found that it did not appear to be supported out of the box.\n\nSince we couldn't disable HTTPS, we had two ways to eavesdrop:\n\n1. Use a man-in-the-middle proxy (e.g. [mitmproxy](https://mitmproxy.org/))\n1. Record the traffic and decrypt the traffic later using the private keys\n\n## Ok, let's be the man-in-the-middle!\n\nThe first seemed more straightforward, since [we already had experience\ndoing this with the Docker\nclient](https://docs.gitlab.com/ee/administration/packages/container_registry.html#running-the-docker-daemon-with-a-proxy).\n\nHowever, after [defining the proxy variables for GitLab\nRunner](https://docs.gitlab.com/runner/configuration/proxy.html#adding-proxy-variables-to-the-runner-config),\nwe found we were only able to intercept the GitLab API calls with\n`mitmproxy`. The Docker API calls still went directly to the remote\nhost. Something wasn't obeying the proxy configuration, but we didn't\ninvestigate further. We tried the second approach.\n\n## Decrypting TLS data\n\nTo decrypt TLS data, we would need to obtain the encryption keys. Where\nwere these located for a newly-created system with `docker-machine`? It\nturns out `docker-machine` worked in the following way:\n\n1. Call the Google Cloud API to create a new machine\n1. Create a `/root/.docker/machine/machines/:machine_name` directory\n1. Generate a new SSH keypair\n1. Install the SSH key on the server\n1. Generate a new TLS certificate and key\n1. Install and configure Docker on the newly-created machine with TLS certificates\n\nAs long as the machine runs, the directory will contain the information\nneeded to decode this traffic. We ran `tcpdump` and saved the private keys.\n\nOur first attempt at decoding the traffic failed. Wireshark could not\ndecode the encrypted traffic, although general TCP traffic could still\nbe seen. Researching more, we found out why: If the encrypted traffic\nused a [Diffie-Hellman key\nexchange](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange),\nhaving the private keys would not suffice! This is by design, a property\ncalled [perfect forward\nsecrecy](https://en.m.wikipedia.org/wiki/Forward_secrecy).\n\nTo get around that limitation, we modified the GitLab Runner to disable\ncipher suites that used the Diffie-Hellman key exchange:\n\n```diff\ndiff --git a/vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go b/vendor/github.com/docker/go-connections/tlsconfig/config_client_ciphers.go\nindex 6b4c6a7c0..a3f86d756 100644\n",[266,1369,536,774,1207,774,1148,9,799],{"slug":1933,"featured":6,"template":686},"tracking-down-missing-tcp-keepalives","content:en-us:blog:tracking-down-missing-tcp-keepalives.yml","Tracking Down Missing Tcp Keepalives","en-us/blog/tracking-down-missing-tcp-keepalives.yml","en-us/blog/tracking-down-missing-tcp-keepalives",{"_path":1939,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1940,"content":1946,"config":1952,"_id":1954,"_type":13,"title":1955,"_source":15,"_file":1956,"_stem":1957,"_extension":18},"/en-us/blog/use-gitlab-with-vscode",{"title":1941,"description":1942,"ogTitle":1941,"ogDescription":1942,"noIndex":6,"ogImage":1943,"ogUrl":1944,"ogSiteName":672,"ogType":673,"canonicalUrls":1944,"schema":1945},"How we created a GitLab Workflow Extension for VS Code","Now you can leverage GitLab from within Visual Studio Code with our official GitLab Workflow Extension.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749681469/Blog/Hero%20Images/gitlab-vscode-blog-image2.jpg","https://about.gitlab.com/blog/use-gitlab-with-vscode","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How we created a GitLab Workflow Extension for VS Code\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Roman Kuba\"}],\n        \"datePublished\": \"2020-07-31\",\n      }",{"title":1941,"description":1942,"authors":1947,"heroImage":1943,"date":1949,"body":1950,"category":705,"tags":1951},[1948],"Roman Kuba","2020-07-31","\n\nThis blog post was originally published on the GitLab Unfiltered blog. It was reviewed and republished on 2020-08-04.\n{: .alert .alert-info .note}\n\nThe people who work at GitLab are encouraged to build the things they want and need, which helps us expand the ways we work with our growing product. We're thrilled to announce that we've created an official GitLab Workflow Extension for VS Code.\n\n## How did we get here?\n\n[More than two years ago](/blog/gitlab-vscode-extension/), [Fatih Acet](https://gitlab.com/fatihacet), a former senior frontend engineer, [Plan](/handbook/engineering/development/dev/plan-project-management/), started working on a [VS Code extension](/blog/gitlab-vscode-extension/) to allow users to interact with GitLab from within their code editor. At GitLab, [everything starts with a Merge Request](/handbook/communication/#start-with-a-merge-request), which is exactly how Fatih started building the extension. Fatih, along with more than 25 contributors, continued to expand on the extension by adding new features. The extension has been installed more than 160,000 times.\n\nIt’s been remarkable to see the way the community collaborated to build the extension, making it a tool that is valuable to their work. The GitLab Workflow Extension is the perfect case study of how [developers can create meaningful work at this company](/direction/create/editor_extension/#where-we-are-headed).\n\nWhen Fatih decided to move on from GitLab in March 2020, we had an opportunity to take over the GitLab Workflow Extension, turning it into a tool GitLab would officially maintain and support. We jumped at the opportunity to maintain an auxilary project outside of the main GitLab project. As we continue to move fast and create the best experiences possible for our users, we expect this extension to become a [key component of our strategy](/direction/create/editor_extension/#overview).\n\n## How to use the extension\n\nIf you want to start using the extension, you can install it from within VS Code directly by searching for [GitLab Workflow](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow) which is now published through an official GitLab account.\n\nIf you were already using the extension, it automatically updated to the GitLab publisher, and you might have already seen a few updates coming in.\n\n## What improvements have we made?\n\nWhen we took over the extension, we worked with other teams across GitLab to immediately perform an [application security review](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/170). Along the way, we made sure to create a [security release-process](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/blob/master/docs/security-releases.md). We did this to ensure that users were safe to continue using the extension and so that we could fix any problems that surface. We also worked through some automation to help with publishing the extension and [begin to lay a foundation for future testing](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/199).\n\nWe also shipped [version 3.0.0](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/blob/master/CHANGELOG.md#v300-2020-06-25) which was spearheaded by our community and helped to resolve some long-standing bugs and issues. The extension has come a long way in just a few short weeks. We’re excited by the progress we’re making and the engagement we’re continuing to see, but there is still a lot that needs to be done.\n\n## What’s next?\n\nNothing in software development is perfect, and so we are aware of the shortcomings of the extension, some inconsistencies, and some long open feature requests. You can see our many to-dos on our GitLab Workflow Extension [issues list](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues). For now, we’re focused on triaging the existing issues and capturing any new bugs. You should see much more involvement from our [Create:Editor](/handbook/engineering/development/dev/create/ide/) team as we continue these efforts, and we’re looking forward to engaging with the community on these items.\n\nWe’re also evaluating the best path forward for maintaining the extension, by focusing on the test-suite and code-quality, so we won’t break things by accident. You can join us in our discussion on [this issue](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/issues/204). While this might slow down some new feature releases in the short-term, we’re confident these are the right long-term decisions to ensure you have an extension you can trust, so you can make the GitLab Extension an integral part of your workflow.\n\n## Everyone can contribute\n\nThe extension is open source, and we're improving the \"[How to Contribute](https://gitlab.com/gitlab-org/gitlab-vscode-extension/-/blob/master/CONTRIBUTING.md)\" guides alongside some other documentation. We want to have a space where everyone can contribute and make this extension better for all of us.\n\n## Check out more engineering content on GitLab\n\n- [How to build containers with the AWS Fargate Custom Executor for GitLab Runner and AWS CodeBuild](/blog/aws-fargate-codebuild-build-containers-gitlab-runner/)\n- [How application security engineers can use GitLab to secure their projects](/blog/secure-stage-for-appsec/)\n- [Best practices to keep your Kubernetes runners moving](/blog/best-practices-for-kubernetes-runners/)\n\n\n## Read more on Visual Studio and GitLab:\n\n- [Four new tools for your Visual Studio Code and GitLab tool belt](/blog/vscode-workflow-new-features/)\n\n- [Visual Studio code editor: Eight tips for using GitLab VS Code](/blog/vscode-workflows-for-working-with-gitlab/)\n\n- [VS Code extension development with GitLab](/blog/vscode-extension-development-with-gitlab/)\n\n- [How to do GitLab merge request reviews in VS Code](/blog/mr-reviews-with-vs-code/)\n\n",[9,881],{"slug":1953,"featured":6,"template":686},"use-gitlab-with-vscode","content:en-us:blog:use-gitlab-with-vscode.yml","Use Gitlab With Vscode","en-us/blog/use-gitlab-with-vscode.yml","en-us/blog/use-gitlab-with-vscode",{"_path":1959,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1960,"content":1966,"config":1972,"_id":1974,"_type":13,"title":1975,"_source":15,"_file":1976,"_stem":1977,"_extension":18},"/en-us/blog/wag-labs-blog-post",{"title":1961,"description":1962,"ogTitle":1961,"ogDescription":1962,"noIndex":6,"ogImage":1963,"ogUrl":1964,"ogSiteName":672,"ogType":673,"canonicalUrls":1964,"schema":1965},"How Wag! cut their release process from 40 minutes to just 6","The popular dog-walking app is rolling out new features faster and with more confidence as they adopt GitLab for more of their DevOps workflows.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749678923/Blog/Hero%20Images/dog-walking.jpg","https://about.gitlab.com/blog/wag-labs-blog-post","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How Wag! cut their release process from 40 minutes to just 6\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Aricka Flowers\"}],\n        \"datePublished\": \"2019-01-16\",\n      }",{"title":1961,"description":1962,"authors":1967,"heroImage":1963,"date":1968,"body":1969,"category":1630,"tags":1970},[1224],"2019-01-16","\nDo you own a dog and work outside of the home? If you do, or even just know someone who does, you know that finding a trustworthy caretaker is of the utmost importance. With dog walkers in cities and towns across the U.S., the folks at [Wag!](https://wagwalking.com/about) have proven to be a source of reliable caretakers for countless fur parents. In three years, the company has powered more than one billion walks via its app for on-demand dog walking, sitting, and boarding, that boasts of millions of users.\n\nWag! recently signed on with GitLab to make the most of their engineering hours and bring their customers new features and updates at a faster clip.\n\n### From version control, to CI, to the full pipeline\n\nHaving previously used GitLab as their main source of truth for repositories, Wag! initially planned to return to the app solely for [continuous integration (CI)](/solutions/continuous-integration/). But after giving it a whirl, they quickly expanded their strategy to include the use of other features.\n\n\"We started our GitLab project about seven or eight months ago,\" explains [Dave Bullock](https://www.linkedin.com/in/eecue), director of engineering at Wag! \"The original idea was to just use it as our CI platform. But as we built that out, we started using it for more and more tasks, and ended up using it for our full [CI/CD pipeline](/topics/ci-cd/). That includes both our application, so the CI/CD that powers the API, along with our infrastructure. We use GitLab with Terraform to test, review, save, and deploy all of our infrastructure as well as the application on two separate pipelines. Every team uses it in their application, whether it's the Android application, the web application, the API, or our infrastructure; it's all being tested, built, and deployed through GitLab.\"\n\n### Streamlining to a single application\n\nPart of GitLab's appeal stemmed from the [ability to do everything in one place](/topics/single-application/). Wag! was searching for an [integrated solution](/solutions/continuous-integration/) that would streamline their development process, and they found it in GitLab.\n\n\"We were previously using a combination of Travis and other random technologies, and we just wanted something with a little bit better interface, a little more control, and something that we owned as far as the hosting and the management,\" says Bullock. \"We really wanted to move towards a single, full-service application.\"\n\n>\"We just wanted something with a better interface, a little more control, and something that we owned as far as the hosting and the management. We really wanted to move towards a single, full-service application.\"\n\nThe impact of that choice is also being felt on the infrastructure side. Wag!'s infrastructure engineers no longer have to manually stage and test their work. They are now following the same basic workflow that is used for their app, while integrating Terraform to manage their infrastructure.\n\n\"Basically, one of our DevOps team members will make a change, cut a pull request, and it'll be reviewed by the team. If it looks good, we'll say, 'Okay, cool. Merge it into master,'\" Bullock explains. \"If it's one of the modules, we'll tag that module, update the reference to it, and then the CI pipeline will kick off. It'll test the syntax, look for any security issues, and alert a Slack channel if there are any. It'll then stage a full version of the environment and test it. So, it stages all the pieces: the database, cache, and everything else, and tests it all to make sure that it works, just like we would be testing our production website.\n\n\"If that passes, then it allows you to see what your changes are going to do before you apply them,\" he continues. \"We call it Terraform plan. So, it runs Terraform plan on each piece of our infrastructure, and it'll tell us something like, 'Hey, we see 34 changes and 2 destructions and 1 creation in this environment. Click here to review.' Then the group will review it and if it looks good, we'll apply it in production. Having that as a full pipeline is really great.\"\n\n>“Now it's so easy to deploy something and roll it back if there's an issue. It's taken the stress and the fear out of deploying into production.” – Dave Bullock, Director of Engineering\n\n### Easy learning curve\n\nSome of the Wag! engineers had working experience with GitLab, while others had not. Nonetheless, Bullock found the onboarding of his teams to be a fairly easy process due to the intuitive nature of the interface.\n\n\"I think once you kind of understand how CI works, it's basically about following things step by step,\" he says. \"Pipelines were a new concept to a lot of the team, but once you see it happening visually, it's really easy to understand what's going on, expand and add to it. It's a really useful interface. Seeing all those green dots or red dots makes it really clear what's going on.\"\n\n### Built-in security, shaving down test times and faster releases\n\nAs part of their ramp up in GitLab, the dog-walking service recently furled [automated security scanning and license management](/solutions/security-compliance/) into their workflow, with Bullock noting how \"great\" it is to have those features baked into the pipeline so that immediate action can be taken when needed.\n\nWag! currently issues three releases a day, with plans to bump that number up to eight or more. Since adopting GitLab, they have seen a massive improvement in the amount of time spent on the release process. **What previously took 40 minutes to an hour to accomplish, now takes just six minutes.**\n\n\"Traditionally, the release process was slow, fragile, and limited to only a few key release engineers who had access to 10 different systems to monitor, make changes, and log into to make updates and pull in the latest code. It was not optimal. Now it's literally a single pane of glass. A lot of it just happens automatically when you merge `develop` into `master` and tag it.\"\n\nThe release process time should improve even more once Wag! engineers switch from manually pushing parts of the release through to automating the process.\n\n\"Right now, we're still clicking through the interface and saying, 'Okay, do this, now let's monitor,'\" says Bullock. \"But I think as we become more comfortable with it, we'll go to fully automated deployments. Literally, just let it go and deploy. If we see an uptick in errors, we'll let it roll back on its own. But as it is now, it's so easy to deploy something and roll it back ourselves if there's an issue. It's taken the stress and the fear out of deploying into production.\"\n\n### Adopting DevOps\n\nWag!'s engineering team has big plans for 2019. They are currently in the process of moving their repositories from GitHub to GitLab and are planning to switch from Amazon ECS to [Kubernetes](/solutions/kubernetes/). This is all part of their roadmap to implementing DevOps.\n\n\"I think we're going to start working on the project in Q1 and it will be really awesome to have all the bells and functionality,\" Bullock says. \"We're excited about Auto DevOps and a lot of new things GitLab has coming down the pipeline. We're going to push pretty hard on that this year.\n\n\"I'm a big fan of DevOps in general, so I think the closer that you can bring the development engineers to the ops side, the better things work,\" he adds. \"I would love for every software engineer or backend engineer to take ownership of the environment that their code runs in, or at least be able to experiment with it and kind of instantly just spin up a full working environment that is the same as our production environment, which we do now, but not with Kubernetes. I think removing that friction is great.\"\n\n### Growing with GitLab\n\nGitLab's releases are a treat the folks at Wag! look forward to checking out each month. The rollout of new features, which are partly determined by user feedback, tend to correlate with the engineering needs of the growing dog-walking and boarding service.\n\n\"I think it's exciting that as we're growing and adding interesting pieces to our infrastructure and application, we're seeing GitLab grow with your monthly release cycles,\" says Bullock. \"Every month there's some new stuff that we're like, 'Oh cool, we could use that, that's perfect.' It's nice to have GitLab as a partner that's growing with us, and it's exciting to see the parallels of new features that you're launching and how it's solving our problems and optimizing things. There's all kinds of cool stuff, and every time we start using a new piece of GitLab, I feel like, 'Okay, that's great, we're really getting our money’s worth.'\"\n\nPhoto by [Andrii Podilnyk](https://unsplash.com/photos/dWSl8REfpoQ?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/search/photos/dog-walk?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)\n{: .note}\n",[1971,774,707,682,708,9,732,709],"customers",{"slug":1973,"featured":6,"template":686},"wag-labs-blog-post","content:en-us:blog:wag-labs-blog-post.yml","Wag Labs Blog Post","en-us/blog/wag-labs-blog-post.yml","en-us/blog/wag-labs-blog-post",{"_path":1979,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":1980,"content":1986,"config":1992,"_id":1994,"_type":13,"title":1995,"_source":15,"_file":1996,"_stem":1997,"_extension":18},"/en-us/blog/want-faster-releases-your-answer-lies-in-automated-software-testing",{"title":1981,"description":1982,"ogTitle":1981,"ogDescription":1982,"noIndex":6,"ogImage":1983,"ogUrl":1984,"ogSiteName":672,"ogType":673,"canonicalUrls":1984,"schema":1985},"Want faster releases? Your answer lies in automated software testing","The trouble with testing? Nearly everything! Here's why automated software testing is so hard to get right, and how a DevOps platform can help.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663397/Blog/Hero%20Images/logoforblogpost.jpg","https://about.gitlab.com/blog/want-faster-releases-your-answer-lies-in-automated-software-testing","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Want faster releases? Your answer lies in automated software testing\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Valerie Silverthorne\"}],\n        \"datePublished\": \"2021-09-30\",\n      }",{"title":1981,"description":1982,"authors":1987,"heroImage":1983,"date":1988,"body":1989,"category":901,"tags":1990},[677],"2021-09-30","\n\nFor three years in a row, our Global DevSecOps Survey found testing was the number one reason (by large margins) for release delays. A lack of automated software testing, combined with too many manual tests conducted too late in the process, was a story told time after time, and it certainly was one without any kind of happy ending.\n\nDespite the undeniable progress DevOps has brought to software development, integrating automated software testing into the lifecycle has remained an elusive goal for many teams. Here’s a look at why testing is such a difficult step to get right, and how an integrated DevOps Platform can bring much-needed structure to the process.\n\n## The state of automated software testing\n\nAccording to our [2021 Survey](/developer-survey/), it’s safe to say respondents are _frustrated_ with software testing.\n\n_“Testing can be slow in both writing and running.”_\n\n_“Testing delays everything.”_\n\nWhile there is forward momentum (almost 25% of teams say they’re fully automated - more than double the number from 2020), the same percentage reported zero automation or that they’re just beginning to think about it. \n\n_“Automated testing is ignored ‘due to time constraints.’”_\n\nBut even teams that haven’t ignored automated software testing are hamstrung because the vast majority don’t give developers scan results **within their IDEs.** Fewer than 25% of teams enable [SAST](/blog/developer-intro-sast-dast/) lite scanners in a web IDE and only 20% put results in a web pipeline report for developers. The situation is even worse when it comes to DAST, dependency and container scans: just 16% make DAST/dependency scan data available, and 14% do the same for container scans. While the percentage of teams reporting full automated software testing increased from 2020 to 2021, the percentage giving devs access to key test data barely changed in the same time frame.\n\n## Context switching makes everything hard\n\nThe fact that developers can’t easily get access to test results is a huge productivity blocker. “The best time to (fix bugs) is when I’m in \"flow\" - right when I’m writing the code and have a mental model of all of the things and how they are interconnected,” explained [Brendan O’Leary](/company/team/#brendan), senior developer evangelist at GitLab, in a blog post last year talking about [the developer-security divide](/blog/developer-security-divide/). “So that’s basically the same day or same week as when I wrote it.”\n\n**Elevating your DevOps skills? Join us at [Commit at KubeCon](/events/commit/) - Oct. 11!**\n\nSo while not getting results “in the flow” is a huge stumbling block, developers are adamant about the importance of testing. When we asked developer respondents in our 2021 Survey what they wished they could do more of, testing was, by far, the number one response. \n\nWhat’s the solution to this conundrum? More automation, [more AI/ML](/blog/ai-in-software-development/) and a [DevOps platform](/solutions/devops-platform/) to make everything seamlessly interconnected, visible and actionable.\n\nGeo-sharing company Glympse offers an object lesson on [the benefits of a DevOps platform](/customers/glympse/). The company was using approximately 20 tools to get its software out the door, but after moving to GitLab’s DevOps Platform, the process was dramatically streamlined. Deployments have dropped from four hours to  less than 30 minutes, and deployment fatigue, particularly around testing and code reviews, has vanished. \n\n## The struggle is real, but worth it\n\nFor teams who’ve tamed the automated software testing beast, and are humming along in their DevOps practices, the benefits are substantial. Here’s what they told us in our 2021 Survey:\n\n_“We are not relying on developers to have remembered to create and run tests for their code before deploying.”_\n\n_“We automate everything possible, to be able to test our product ‘like in real life’ without any downside. This increases confidence and simplifies tests for everything.”_\n\n_“Integration testing has been a big plus in how confident we are to release automatically and deliver a version. We are now able to deliver any day.”_\n\n_“It helps that devs don't need to keep track of test running; they just need to push and pipeline will check their code before merge to master.”_\n",[9,682,1991],"developer survey",{"slug":1993,"featured":6,"template":686},"want-faster-releases-your-answer-lies-in-automated-software-testing","content:en-us:blog:want-faster-releases-your-answer-lies-in-automated-software-testing.yml","Want Faster Releases Your Answer Lies In Automated Software Testing","en-us/blog/want-faster-releases-your-answer-lies-in-automated-software-testing.yml","en-us/blog/want-faster-releases-your-answer-lies-in-automated-software-testing",{"_path":1999,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":2000,"content":2005,"config":2010,"_id":2012,"_type":13,"title":2013,"_source":15,"_file":2014,"_stem":2015,"_extension":18},"/en-us/blog/why-continuous-fuzzing",{"title":2001,"description":2002,"ogTitle":2001,"ogDescription":2002,"noIndex":6,"ogImage":935,"ogUrl":2003,"ogSiteName":672,"ogType":673,"canonicalUrls":2003,"schema":2004},"Why (Continuous) Fuzzing","Learn what fuzzing is, what's so good at fuzzing code continuously and why to do it here!","https://about.gitlab.com/blog/why-continuous-fuzzing","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Why (Continuous) Fuzzing\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Yevgeny Pats\"}],\n        \"datePublished\": \"2020-12-10\",\n      }",{"title":2001,"description":2002,"authors":2006,"heroImage":935,"date":2007,"body":2008,"category":729,"tags":2009},[1465],"2020-12-10","\n\n{::options parse_block_html=\"true\" /}\n\n\n\n\nIn this post we will focus on why continuous fuzzing is needed and what are the challenges in implementing continuous fuzzing.\nPrevious posts/papers regarding why fuzzing in general is important as well as why it’s a good idea even to integrate\nit as part of the Go toolchain can be found [here](https://docs.google.com/document/d/1N-12_6YBPpF9o4_Zys_E_ZQndmD06wQVAM_0y9nZUIE) (written by Dmitry Vyukov and Romain Baugue – highly recommended, the link talks about go but concepts can be applied to other languages).\n\n## Fuzzing Quick Recap?\n\nEssentially fuzzing consist of two types of jobs:\n\n- Fuzzing – A job that can run infinitely. This job automatically generates interesting test-cases that cover more paths, as well as monitors for crashes and other memory related problems.\n- Regression – Run the fuzzer through a set of specific test-cases. Usually these were generated by the previously mentioned long running jobs. Regression jobs are generally very short.\n\n\n## Continuous Fuzzing Challenges\n\nThere are a few challenges/questions that arise from how to integrate fuzzing to the current CI.\nWe will walk through some of them as there are a lot of other open questions that really depends on the development workflow and the specific project.\n\n**Challenge 1 –  Long Running Jobs**: Fuzzing is a long-running (infinite) job unlike a CI that we try to keep as short as possible to provide fast feedback for commits/MRs.\n\n**Solution 1 – Async Jobs**: This where we need to spawn a different server or use a platform like GitLab to run the fuzzers [asynchronously](https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/#continuous-fuzzing-long-running-async-fuzzing-jobs).\nThe platform will notify the administrators, developers, or the relevant security people of any new vulnerabilities that the fuzzers find. In GitLab, these will be reported on our [Security Dashboard](https://docs.gitlab.com/ee/user/application_security/security_dashboard/). This could take days or months of running continuously.\n\n\n**Challenge 2 – Many Targets * Many Versions = Lots of time and money**: Which versions should should you fuzz? We need to decide wisely which versions to fuzz, as blindly fuzzing all possible versions in a project infinitely for many targets will cost a lot of money and compute resources.\n\n**Solution 2 – Master + Stable**: One approach that we saw popular with users is fuzzing the development branch (master) and release branch. The development branch is fuzzed continuously and the fuzzer is updated every time new code is pushed to master.\nThe updated fuzzers check the additional code but keeps the corpus from previous runs.\nThis way the fuzzers can essentially always continue from where they stopped and only work on the additional code.\nGitLab helps with managing the corpus and keeping it in minimised state.\n\n**Challenge 3 – Learning from old mistakes**: Once we setup continues fuzzing we aggregate very valuable test-cases and crashes that we fix along the way.\nWe would love to use all those precious test-cases to check every MR before it get’s merged.\n\n**Solution 3 – Regression Fuzz Tests**: For every MR just like unit-test we run the fuzzers through all the generated test-cases and the fixed crashes which is usually a very quick process which fits a classic CI.\nGitLab helps running the fuzzers with the aggregated corpus from previous job, fail the CI and alert the developer immediately via the security dashboard.\nShort regresion can also be combined with short fuzz tests runs that run inline with the CI to help find also new bugs in MRs.\n\n## Summary\n\nThis was a quick walkthrough of the some of the challenges of integrating continuous fuzzing to projects from our experience.\n\nCheck out our [full documentation](https://docs.gitlab.com/ee/user/application_security/coverage_fuzzing/) and the [example repositories](https://gitlab.com/gitlab-org/security-products/demos/coverage-fuzzing)\nand try adding fuzz testing to your own repos!\n",[881,708,9],{"slug":2011,"featured":6,"template":686},"why-continuous-fuzzing","content:en-us:blog:why-continuous-fuzzing.yml","Why Continuous Fuzzing","en-us/blog/why-continuous-fuzzing.yml","en-us/blog/why-continuous-fuzzing",{"_path":2017,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":2018,"content":2024,"config":2030,"_id":2032,"_type":13,"title":2033,"_source":15,"_file":2034,"_stem":2035,"_extension":18},"/en-us/blog/why-gitlab-uses-a-monthly-release-cycle",{"title":2019,"description":2020,"ogTitle":2019,"ogDescription":2020,"noIndex":6,"ogImage":2021,"ogUrl":2022,"ogSiteName":672,"ogType":673,"canonicalUrls":2022,"schema":2023},"How we maintain product velocity with a monthly release cycle","This workplace has gone 85 months without missing a release.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749678822/Blog/Hero%20Images/monthlyrelease.jpg","https://about.gitlab.com/blog/why-gitlab-uses-a-monthly-release-cycle","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How we maintain product velocity with a monthly release cycle\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Suri Patel\"}],\n        \"datePublished\": \"2018-11-21\",\n      }",{"title":2019,"description":2020,"authors":2025,"heroImage":2021,"date":2027,"body":2028,"category":298,"tags":2029},[2026],"Suri Patel","2018-11-21","\nWe eagerly await the 22nd of each month, because we get to share the hard work of our team and community\nwith our users. Our [release](/releases/) schedule illustrates our belief in the\n[importance of velocity](/handbook/engineering/development/principles/#the-importance-of-velocity) and our\ncommitment to delivering value in the form of features. Monthly releases might\nseem aggressive to outsiders, but we believe our ability to deliver\nfeatures quickly is a competitive advantage that has helped us find success.\n\n## Three cheers for a monthly release cycle!\n\nA monthly release cycle is great for users, because it adds value at a\npredictable pace. Most of GitLab's installations are self managed, helping users\nidentify when new functionality will arrive. As a user, you can look forward to the 22nd of\nevery month, knowing that's when new features become available.\n\n![A screenshot of a user's comment, which says that he is looking forward to February 22 more than 21, which is his birthday](https://about.gitlab.com/images/blogimages/releasebirthday.png){: .shadow}\n\n### A predictable schedule helps with planning team capacity\n\nThe team has also found that it's easier to determine how much capacity we have\nwhen we're planning our workload, because there's no need to agree on\ndeadlines. GitLab team-members know exactly how many days they have to work on something,\nand we can plan in advance. Working with such a short timeframe forces us to\nbuild features as small as possible. We can't say, \"I need two releases for this,\" so we have to really think about how we\ncan make a feature smaller, an idea that's reinforced by our\n[iteration value](https://handbook.gitlab.com/handbook/values/#iteration).\n\n> \"Sometimes, team members will propose having longer release cycles, because\nwe'd be able to fit more into it, or we'd be able to do better quality control,\nbut I think that's short sighted. It's true that in a longer release cycle, you\ncan fit more in, but that will require bigger steps, and we know that\niteration is what makes us achieve velocity. So, the smaller the steps we take,\nthe faster we can go and the easier it is to do quality control. We think a\nmonthly release cycle is the optimal frequency.\" -- [Sid Sijbrandij](/company/team/#sytses), GitLab CEO\n\n## Challenges posed by monthly releases\n\n### It's not always easy to stick to the plan\n\nIt's not all sunshine and rainbows. While we're strong proponents of the monthly release, we realize that there are\nquite a few challenges. A few of the drawbacks relate to our actual deadline\nand staying on track with the [direction](/direction/) of GitLab. For example,\na release might be far away, but we want to ship something now. Or, we want to\nship something, but the release just passed, and now we have to wait. Sometimes\nwe try to cram in something just before the merge window closes even\nthough it's not ready. When these situations arise, we remember to\n[shift objectives](/blog/why-we-shift-objectives-and-not-release-dates-at-gitlab/)\nand focus on providing value to our users.\n\n### There's a big impact on morale\n\nOne of the most bittersweet aspects of a monthly release cycle is that morale can soar\nor suffer, depending on the processes that are put in place. We're thrilled\nwhen we make something and quickly see the benefits. With our schedule, GitLab team-members see\ntheir hard work in action within a month, which is way better than\nin some enterprise organizations in which someone might make something that won't\nget released for six months. At GitLab, people can rapidly effect change with\ntheir contributions and feel like their work matters.\n\nThere were times in the past, however, when our release schedule dampened morale.\nIn the early days, we had a few times when things came down to the wire, and we\nbattled bugs right until the end, making the 22nd look like an impossibility.\nThe rush towards the end caused quite a bit of stress on the team, so we decided\nto create a bigger buffer between the release and the closing of our merge window.\nWe also developed a\n[release process](https://gitlab.com/gitlab-org/release/docs/blob/master/README.md)\nto help keep us on track. Our\n[release template](https://gitlab.com/gitlab-org/release-tools/blob/master/templates/monthly.md.erb)\nhas helped us identify problems earlier so that we're not feeling pressured at\nthe end, which could be detrimental to everyone's motivation.\n\n## How to determine the right cadence\n\nWhile we've found success with monthly releases, we understand that it's not\nright for everyone. If you're a SaaS provider, you don't need releases and can\njust ship whenever something is finished. If you're shipping software that people\nmanage themselves, you're going to have to do versioning, so that people know\nwhat version they're on and are aware of releases, patches, and upgrades. A\nmonthly release cycle forces you into this, so in that case, this cadence would\nwork well for your team. If there's hardware in the loop, extensive testing, or\nhuman testing that's needed, then a longer release cadence is required, because\nyou have to factor in significant fixed costs.\n\nIdentifying the right cadence for your organization comes down to experimentation\nand what your users will accept. Some user bases may only want to update every\nquarter or twice a year, while others want more frequent versions to stay current\nwith industry changes. Because we strongly believe that smaller steps let you\ndeliver faster, we encourage you to start with monthly releases and work towards\nsettling on the pacing that works best. [Collaborate](https://handbook.gitlab.com/handbook/values/#collaboration)\nwith your team to create processes that simplify actions and hold\n[retrospectives](/handbook/engineering/management/group-retrospectives/) to make\nadjustments. We've found that our\n[retrospectives](/blog/our-retrospective-and-kickoff-are-public/) are\nextremely helpful in identifying consistent problem areas.\n\n[Cover image](https://www.pexels.com/photo/22-apartment-architecture-building-210790/) licensed under [CC 0](https://www.pexels.com/creative-commons-images/)\n{: .note}\n",[9,881],{"slug":2031,"featured":6,"template":686},"why-gitlab-uses-a-monthly-release-cycle","content:en-us:blog:why-gitlab-uses-a-monthly-release-cycle.yml","Why Gitlab Uses A Monthly Release Cycle","en-us/blog/why-gitlab-uses-a-monthly-release-cycle.yml","en-us/blog/why-gitlab-uses-a-monthly-release-cycle",{"_path":2037,"_dir":243,"_draft":6,"_partial":6,"_locale":7,"seo":2038,"content":2044,"config":2050,"_id":2052,"_type":13,"title":2053,"_source":15,"_file":2054,"_stem":2055,"_extension":18},"/en-us/blog/a-deep-dive-into-the-security-analyst-persona",{"title":2039,"description":2040,"ogTitle":2039,"ogDescription":2040,"noIndex":6,"ogImage":2041,"ogUrl":2042,"ogSiteName":672,"ogType":673,"canonicalUrls":2042,"schema":2043},"A deep dive into the Security Analyst persona","See how we created our new Security Analyst persona, and how we are already putting it to use.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749663736/Blog/Hero%20Images/a-deep-dive-into-the-security-analyst-persona.jpg","https://about.gitlab.com/blog/a-deep-dive-into-the-security-analyst-persona","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"A deep dive into the Security Analyst persona\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Andy Volpe\"}],\n        \"datePublished\": \"2019-02-12\",\n      }",{"title":2039,"description":2040,"authors":2045,"heroImage":2041,"date":2047,"body":2048,"category":708,"tags":2049},[2046],"Andy Volpe","2019-02-12","\nAs GitLab grows, so does our need for new, more area-specific personas. Recently, as part of our [effort to create personas](/blog/personas-and-empathy-building/), I was given a chance to craft one. As the UX designer for [the Secure team](/handbook/engineering/development/sec/secure/) here at GitLab, I jumped at the opportunity to learn more about security professionals, and how we may create products and features to meet their needs. Throughout the entire process, I gained a greater sense of empathy and a deeper understanding of the needs, goals, and pain points of security professionals. The result was our new [Security Analyst Persona, Sam](/handbook/product/personas/#sam-security-analyst). However, I will add a caveat that this is not the end of the process, but the beginning of how we can better support security professionals with new features and functionality that address their specific needs. You can peruse the highlights and the persona itself below, and let us know what you think by tweeting us [@gitlab](https://twitter.com/gitlab)!\n\n## The research\n\nHere are some takeaways from the [10 interviews](https://gitlab.com/gitlab-org/ux-research/issues/97) I conducted to create the Security Analyst persona.\n\nWe’ve learned that the Security Analyst is a bit of a generalist when it comes to their day-to-day tasks. From the research, I found that there isn’t one specific task that defines their day, but a grouping of tasks under the umbrella of security. I’ve written the summary of the persona to reflect the somewhat general nature of the Security Analysts' role:\n\n>\"I wear lots of hats, but the majority of my time is spent monitoring and flagging events, running down high-priority tasks and working with other teams to implement new systems.\"\n\n### What motivates a Security Analyst?\n\nSecurity Analysts strive for order in the chaos and, based on our research, are taking steps to achieve that order. One specific example:\n\n>When I’m monitoring my dashboards, I want to see everything I am monitoring in one tool, so I can do my job easier and more efficiently.\n\nMoving between different tools and dashboards was identified as a significant problem area for Security Analysts. They found it hard to create a workflow that was conducive to remediating security issues while having to work across multiple tools.\n\nAnother motivation I found during the research was that Security Analysts desire to be more proactive than reactive in their work. I’ve summarized this by adding the objective below:\n\n>When security testing, I want to be more proactive than reactive, so I can anticipate potential threats or vulnerabilities before the bad guys do.\n\nBy being more proactive or shifting left in their work, Security Analysts are able to identify and remediate potential vulnerabilities before they become a problem or even lead to an attack.\n\n### What are some of the frustrations Security Analysts have?\n\n>I’m frustrated I don’t have the resources to complete this project to its specifications.\n\nand\n\n>I’m frustrated when I know how to fix a security issue but the red tape at my company doesn’t allow me to in a timely manner.\n\nA common theme seen throughout the research was that of constrained resources and time. Often we found that security teams were small in comparison to other teams within their organization. This resource discrepancy leads to work being done at such a pace that the project can’t be completed to its specifications or in a timely manner.\n\n### How are we using the security Analyst persona at GitLab?\n\nWe are all-in on making the Security Persona a first-class persona here at GitLab. Recently we launched the [Group-level Security Dashboard](https://docs.gitlab.com/ee/user/application_security/security_dashboard/), which allows security professionals to monitor all their projects, in one view, for vulnerabilities, and gives them the ability to take action on those vulnerabilities right from the dashboard itself.\n\nAside from security dashboards, we are constantly dreaming up more security features and enhancements that will help users keep their instances, groups, and projects secure. You can [see our roadmap here](/direction/#future-releases) for more on what's coming.\n\n## The persona\n\n![Sam, Security Analyst persona](https://about.gitlab.com/images/blogimages/security-analyst-persona.png){: .shadow.center}\n\nKeep an eye out for the rest of our series on the [new personas](/handbook/product/personas/)!\n\n[Photo](https://unsplash.com/photos/z55CR_d0ayg) by [Andrew Neel](https://unsplash.com/@andrewtneel) on Unsplash\n{: .note}\n",[9,881,708,1571,709],{"slug":2051,"featured":6,"template":686},"a-deep-dive-into-the-security-analyst-persona","content:en-us:blog:a-deep-dive-into-the-security-analyst-persona.yml","A Deep Dive Into The Security Analyst Persona","en-us/blog/a-deep-dive-into-the-security-analyst-persona.yml","en-us/blog/a-deep-dive-into-the-security-analyst-persona",8,[665,691,716,739,759,782,806,825,844],1753733120028]