How to integrate Checkmarx ZAP in GitLab

Updated: 7 October, 2025

17 July, 2025

In this technical article, we will explore how to improve your DevSecOps processes by integrating Checkmarx ZAP in your GitLab’s CI/CD pipeline. Note that doing this for Github is largely similar with Github actions.

Application security is becoming a priority on a worldwide scale. Both the US (NIST SSDF and Memorandum M-22-18) and the EU (CRA) have recently introduced legislations to aim at high common level of application security and cybersecurity. Meanwhile, the application security state-of-the-practice in large and small firms is far from ideal (to put it mildly). The attack sophistication and absolute numbers are increasing. Paradoxically, we have known the systematic solution to the security challenge. We (the actual security experts) name it a secure software development lifecycle or an AppSec program.

There are several AppSec programs out there (NIST SSDFMicrosoft SDLCBSIMM, etc) with OWASP SAMM being by far the simplest one to start with. The cornerstones of any AppSec program are People, Process, Knowledge, Tools, Training and Risk. Thus, tooling is just one wheel in the whole machine. To make tooling efficient we should start from the process and fit the tools in the puzzle, rather than trying to reverse engineer the puzzle from the tools. 

In this blog, we focus on a small part of our AppSec program, namely secure deploy. We provide a high-level overview of how the process is structured and how we’ve integrated the tool in our process.

What is the Secure Deployment practice

One of the final stages in delivering secure software is ensuring the security and integrity of developed applications are not compromised during deployment. It focuses on removing manual error by automating the deployment process as much as possible, and making its success contingent upon the outcomes of integrated security verification checks. Secure Deployment also fosters Separation of Duties by making adequately trained, non-developers responsible for deployment.

It also goes beyond the mechanics of deployment. Secure Deployment focuses on protecting the privacy and integrity of sensitive data, such as passwords, tokens, and other secrets, required for applications to operate in production environments. In its simplest form, suitable production secrets are moved from repositories and configuration files into adequately managed digital vaults. In more advanced forms, secrets are dynamically generated at deployment time and routine processes detect and mitigate the presence of any unprotected secrets in the environment.

Overall setup

ZAP offers several ways of automating and different ways to scan. The currently recommended way is through ZAP Automation Framework. We use a “baseline” (passive) scan on a nightly schedule. This scan is perfect for running daily because it is lightweight and does not perform any complex attacks. Once a week we also run an “active scan” which is very offensive towards the system and runs for hours. We run both scans against a non-production environment that is an exact replica of the production server (minus the production data obviously).

Listen to the summary of this article on the AppSec Management Podcast.

Authentication setup in ZAP

Authentication setup has been modified in the latest versions of ZAP and this is perhaps one of the most challenging tasks to get right. We will be using the Client script authentication to setup the authentication with ZAP. It is the recommended approach.

We will be also using our own SAMMY acceptance environment as our target application. We will be using a Firefox plugin called ZAP by Checkmarx Recorder extension to create a scripted recording of the authentication process with Zest. Once you press record you should simply login with a valid username and password.

Record Zest Setup with ZAP
Record Zest Setup with ZAP
SAMMY login screen
SAMMY login screen

Working with 2 factor authentication (in our case Google TOTP) might be even more challenging with ZAP. So we have slightly modified our application to show the 2FA login code in all stress and debug environments to make things easier for the development team. So the code is automatically pre-populated.

2FA workaround
2FA workaround

After that we click the “Stop and Download Recording” button. The .zst file should be downloaded.

Headless mode

Make sure to update the .zst file and set the headless mode to true, as we will be running this script in the CI/CD pipeline.

Setup headless mode for ZAP
Setup a headless mode for ZAP

Secret management best practices

Although the configuration is against a non-production server we will still treat passwords in this script as sensitive. So as a best practice you should configure these as CI/CD variables rather than keeping them in plain text in your repo. So we will replace the username, password and even the URL with variables  “STRESS_TEST_USERNAME”, “STRESS_TEST_PASSWORD” and “TARGET/login” (for https://sammy-stress.codific.com/login).

Finally, we will rename the script to zap-auth.zst and we are ready with the authentication setup.

Final authentication script in ZAP
Replacing sensitive information in the .zst file with CI/CD variables

Context setup in ZAP

Open ZAP Desktop and create a new context. In the context we will set some URLs to exclude and the main URL to be included. Under “Users’ we will enter the username that we will use for scanning.

URLs to include
URLs to exclude
Username to be used for scanning

Baseline automation plan setup

Now we will create baseline automation plan, which will be the passive scan.

Create the baseline automation setup
Create the baseline automation setup

We will also add additional job “alertFilter”, because we want to change some rule risks. For example the “Content Security Policy (CSP) Header Not Set (10038)” risk is with “Medium” risk by default and we will make it “Info”. We will do the same for a couple of other rules.

Alert filter configuration
Changing alert levels for findings

Next, we will go to the “spider” job and choose a context, authenticated user and we will put 10 minute time limit for the scanner job. We will do the same for “spiderAjax”.

Set up the spider job with a 10min timeout
Setup the spider ajax job

We need to setup a couple of report jobs.

  1. Traditional HTML Report. In the report job we will change the name to baselinescan and we will set the template to Traditional HTML Report.
  2. Traditional Markdown Report. We will add a second report job that will read from our pipeline and decide if the pipeline should fail. We will add Alert Count and Alert Details sections.
  3. Authentication report – JSON. The third report job will be used for debugging our authentication.
Traditional HTML Report
Traditional HTML Report
Traditional Markdown Report
Traditional Markdown Report
Authentication report for debugging purposes
Authentication report for debugging purposes

This is what our final plan is looking like.

Complete ZAP baseline scan plan

Baseline ZAP scanner YAML file

Save the plan you have prepared as described in the previous section as a YAML file. Make sure to update it as follows:

  1. Replace the hardcoded URL with “TARGET”
  2. Replace the hardcoded username with “STRESS_TEST_USERNAME”
  3. Replace the project directory with “CI_PROJECT_DIR”
  4. Under “Default context” add URL parameter with the value “TARGET”
  5. Under “Default context” authentication section add the script path.
  6. Under “Default context” users credentials section add “STRESS_TEST_USERNAME”
  7. In each report section replace the “reportDir” with “CI_PROJECT_DIR”
  8. In each report section replace the “reportFile” with “ZAP_REPORT” or “ZAP_AUTH_REPORT” or “ZAP_ALERT_REPORT”

The final “zap_baseline.yaml” file should look something like this:

env :
  contexts :
    - name : Default Context
      urls :
        - TARGET
      includePaths :
        - TARGET.*
      excludePaths :
        - TARGET/contact
        - TARGET/profile/delete-organization
        - TARGET/user/delete.*
      authentication :
        method : client
        parameters :
          scriptEngine : Mozilla Zest
          diagnostics : false
          script : CI_PROJECT_DIR/zap/testauth.zst
      sessionManagement :
        method : cookie
      technology : { }
      structure : { }
      users :
        - name : STRESS_TEST_USERNAME
          credentials : { }
  parameters : { }
jobs :
  - type : passiveScan-config
    parameters : { }
  - type : alertFilter
    alertFilters :
      - ruleId : 10038
        ruleName : Content Security Policy (CSP) Header Not Set (10038)
        context : Default Context
        newRisk : Info
    parameters : { }
  - type : spider
    parameters :
      context : Default Context
      user : STRESS_TEST_USERNAME
      maxDuration : 10
      parseDsStore : null
    tests :
      - name : At least 100 URLs found
        type : stats
        onFail : INFO
        statistic : automation.spider.urls.added
        operator : '>='
        value : 100
  - type : spiderAjax
    parameters :
      context : Default Context
      user : STRESS_TEST_USERNAME
      maxDuration : 10
      browserId : null
    tests :
      - name : At least 100 URLs found
        type : stats
        onFail : INFO
        statistic : spiderAjax.urls.added
        operator : '>='
        value : 100
  - type : passiveScan-wait
    parameters : { }
  - type : report
    parameters :
      template : traditional-html
      theme : null
      reportDir : CI_PROJECT_DIR
      reportFile : ZAP_REPORT
      reportTitle : ZAP by Checkmarx Scanning Report
    risks :
      - info
      - low
      - medium
      - high
    confidences :
      - falsepositive
      - low
      - medium
      - high
      - confirmed
    sections :
      - instancecount
      - alertdetails
      - alertcount
  - type : report
    parameters :
      template : traditional-md
      theme : null
      reportDir : CI_PROJECT_DIR
      reportFile : ZAP_ALERT_REPORT
      reportTitle : ZAP by Checkmarx Scanning Report
    risks :
      - info
      - low
      - medium
      - high
    confidences :
      - falsepositive
      - low
      - medium
      - high
      - confirmed
    sections :
      - alertdetails
      - alertcount
  - type : report
    parameters :
      template : auth-report-json
      theme : null
      reportDir : CI_PROJECT_DIR
      reportFile : ZAP_AUTH_REPORT
      reportTitle : ZAP by Checkmarx Scanning Report
    risks :
      - info
      - low
      - medium
      - high
    confidences :
      - falsepositive
      - low
      - medium
      - high
      - confirmed
    sections :
      - summary
      - diagnosticswebelements
      - diagnostics
      - diagnosticslocalstorage
      - diagnosticssessionstorage
      - diagnosticsscreenshots
      - diagnosticsmessages
      - afenv
      - statistics
  - type : exitStatus
    parameters : { }

We will create additional “zap_config.conf” file where we are going to list our csrf tokens in the app, so ZAP will know how to find them.

anticsrf.tokens(0).token.name=user[_token]
anticsrf.tokens(0).token.enabled=true
anticsrf.tokens(1).token.name=user_add[_token]
anticsrf.tokens(1).token.enabled=true
anticsrf.tokens(2).token.name=contact_form[_token]
anticsrf.tokens(2).token.enabled=true
anticsrf.tokens(3).token.name=token
anticsrf.tokens(3).token.enabled=true
anticsrf.tokens(4).token.name=_csrf_token
anticsrf.tokens(4).token.enabled=true
anticsrf.tokens(5).token.name=documentation[_token]
anticsrf.tokens(5).token.enabled=true
anticsrf.tokens(6).token.name=documentation[_token]
anticsrf.tokens(6).token.enabled=true

Running ZAP scanner in the CI/CD pipeline

Create a bash script that runs the pipeline. This bash script will replace all the variables with the correct values, run the ZAP automation plan, wait for the report and read it. If there are “High” or “Medium” findings the bash script will fail the pipeline.

#!/bin/bash

sed -i "s/STRESS_TEST_PASSWORD/$STRESS_TEST_PASSWORD/g" zap/zap_baseline.yaml
sed -i "s/STRESS_TEST_USERNAME/$STRESS_TEST_USERNAME/g" zap/zap_baseline.yaml
sed -i "s/ZAP_REPORT/$ZAP_REPORT/g" zap/zap_baseline.yaml
sed -i "s/ZAP_AUTH_REPORT/$ZAP_AUTH_REPORT/g" zap/zap_baseline.yaml

# NOTE:
# We need this because the variable contains / in it.
# More info here https://unix.stackexchange.com/questions/255789/is-there-a-way-to-prevent-sed-from-interpreting-the-replacement-string/255869#255869
ESCAPED_CI_PROJECT_DIR=$(sed -e 's/[&\\/]/\\&/g; s/$/\\/' -e '$s/\\$//' <<<"$CI_PROJECT_DIR")
sed -i "s/CI_PROJECT_DIR/$ESCAPED_CI_PROJECT_DIR/g" zap/zap_baseline.yaml

# NOTE:
# We need this because the variable contains / in it.
# More info here https://unix.stackexchange.com/questions/255789/is-there-a-way-to-prevent-sed-from-interpreting-the-replacement-string/255869#255869
ESCAPED_TARGET=$(sed -e 's/[&\\/]/\\&/g; s/$/\\/' -e '$s/\\$//' <<<"$TARGET")
sed -i "s/TARGET/$ESCAPED_TARGET/g" zap/zap_baseline.yaml
sed -i "s/ZAP_ALERT_REPORT/$ZAP_ALERT_REPORT/g" zap/zap_baseline.yaml
sed -i "s/TARGET/$ESCAPED_TARGET/g" zap/zap-auth.zst
sed -i "s/STRESS_TEST_PASSWORD/$STRESS_TEST_PASSWORD/g" zap/zap-auth.zst
sed -i "s/STRESS_TEST_USERNAME/$STRESS_TEST_USERNAME/g" zap/zap-auth.zst

/zap/zap.sh -configfile $CI_PROJECT_DIR/zap/zap_config.conf -cmd -autorun $CI_PROJECT_DIR/zap/zap_baseline.yaml 
returnCode=0 
anyAlertsAboveLow=$(grep -E "^\| (High|Medium) \|" $CI_PROJECT_DIR/$ZAP_ALERT_REPORT.md | grep -oE '[0-9]+' | awk '$1>0')

if [ ! -z "$anyAlertsAboveLow" ]
then
  head -n 20 $CI_PROJECT_DIR/$ZAP_ALERT_REPORT.md
  echo "DAST RESULT: There are some vulnerabilities that ZAP has found (those visible here may not be the only ones). See the detailed report for more information."
  returnCode=1
fi

exit $returnCode

This is what our GitLab pipeline YAML file looks like:

GitLab CI/CD pipeline description in YAML

When we run the job this is what our pipeline terminal will look like if ZAP finds vulnerabilities exceeding the pre-defined configuration.

Automation plan succeeded!
# ZAP Scanning Report
ZAP by [Checkmarx](https://checkmarx.com/).
## Summary of Alerts
| Risk Level | Number of Alerts |
| --- | --- |
| High | 0 |
| Medium | 2 |
| Low | 4 |
| Informational | 0 |
## Alert Detail
DAST RESULT: There are some vulnerabilities that ZAP has found (those visible here may not be the only ones). See the detailed report for more information.
Uploading artifacts for failed job
00:02
Uploading artifacts...
/builds/codific/php/sammy/baselinescan.html: found 1 matching artifact files and directories 
/builds/codific/php/sammy/alert-report.md: found 1 matching artifact files and directories 
/builds/codific/php/sammy/auth-report.json: found 1 matching artifact files and directories 
Uploading artifacts as "archive" to coordinator... 201 Created  correlation_id=f38338cce0d3d087b0c195e464b86b3a id=10778668442 responseStatus=201 Created token=69_6pwzYK
Cleaning up project directory and file based variables
00:00
ERROR: Job failed: exit code 1

ZAP finding notifications in Slack

As mentioned earlier, we have configured OWASP ZAP to run as a scheduled task. This decision is open to debate, as it’s also possible to run ZAP as part of your build or deployment pipeline.

The key reasons we chose not to do that are risk and speed. We want our build and deploy pipelines to remain as fast and lightweight as possible. ZAP is just one of many tools running in our pipeline—alongside unit tests, integration tests, acceptance tests, and static analysis tools like SAST and SCA.

We’ve been using ZAP for over four years, and it has proven extremely helpful. On a few occasions, it detected critical vulnerabilities introduced during development. That said, in 99% of cases, ZAP does not find any new issues. This is why we’re comfortable running it as a scheduled task every morning instead of embedding it directly in the pipeline.

To ensure visibility, we’ve integrated ZAP with Slack. When ZAP finds potential issues, a notification is sent to the relevant project channel so the team is immediately aware and can take action if needed.

Notifying the dev team in Slack on ZAP scan findings
Notifying the dev team in Slack on ZAP scan findings

ZAP full active scan setup

For the active scan the setup is largely the same, except that we choose “Full scan” from the Automation framework options. All the config and steps are the same as for the baseline scan.

ZAP full scan profile
ZAP full scan profile

In the full scan job we fill out the authenticated user, the context as well as the max duration for the job. You can play a bit with the advanced option and the policy rules. For our case initially we are going to set 500ms delay and lower the threads to 12. We are also going to lower the “Default strength” to “Low” instead of “Medium”. The point of all this is to make the active scan not so aggressive towards the test system and to be more gentle to the Gitlab runner.

Active scan authenticated user setup
Active scan authenticated user setup
Active scan context
Context configuration
Timeout and aggression

Full ZAP scan YAML file

As a result here is what the full active scan exported plan in YAML version looks like.

env :
  contexts :
    - name : Default Context
      urls :
        - TARGET
      includePaths :
        - TARGET.*
      excludePaths :
        - TARGET/contact
        - TARGET/profile/delete-organization
        - TARGET/user/delete.*
      authentication :
        method : client
        parameters :
          scriptEngine : Mozilla Zest
          diagnostics : false
          script : CI_PROJECT_DIR/zap/testauth.zst
      sessionManagement :
        method : cookie
      technology : { }
      structure : { }
      users :
        - name : STRESS_TEST_USERNAME
          credentials : { }
  parameters : { }
jobs :
  - type : passiveScan-config
    parameters : { }
  - type : alertFilter
    alertFilters :
      - ruleId : 10038
        ruleName : Content Security Policy (CSP) Header Not Set (10038)
        context : Default Context
        newRisk : Info
    parameters : { }
  - type : spider
    parameters :
      context : Default Context
      user : STRESS_TEST_USERNAME
      maxDuration : 10
      parseDsStore : null
    tests :
      - name : At least 100 URLs found
        type : stats
        onFail : INFO
        statistic : automation.spider.urls.added
        operator : '>='
        value : 100
  - type : spiderAjax
    parameters :
      context : Default Context
      user : STRESS_TEST_USERNAME
      maxDuration : 10
      browserId : null
    tests :
      - name : At least 100 URLs found
        type : stats
        onFail : INFO
        statistic : spiderAjax.urls.added
        operator : '>='
        value : 100
  - type : passiveScan-wait
    parameters : { }
  - type : activeScan
    parameters :
      context : Default Context
      user : STRESS_TEST_USERNAME
      maxScanDurationInMins : 60
      delayInMs : 500
      threadPerHost : 12
    policyDefinition :
      defaultStrength : low
      defaultThreshold : medium
  - type : report
    parameters :
      template : traditional-html
      theme : null
      reportDir : CI_PROJECT_DIR
      reportFile : ZAP_REPORT
      reportTitle : ZAP by Checkmarx Scanning Report
    risks :
      - info
      - low
      - medium
      - high
    confidences :
      - falsepositive
      - low
      - medium
      - high
      - confirmed
    sections :
      - instancecount
      - alertdetails
      - alertcount
  - type : report
    parameters :
      template : traditional-md
      theme : null
      reportDir : CI_PROJECT_DIR
      reportFile : ZAP_ALERT_REPORT
      reportTitle : ZAP by Checkmarx Scanning Report
    risks :
      - info
      - low
      - medium
      - high
    confidences :
      - falsepositive
      - low
      - medium
      - high
      - confirmed
    sections :
      - alertdetails
      - alertcount
  - type : report
    parameters :
      template : auth-report-json
      theme : null
      reportDir : CI_PROJECT_DIR
      reportFile : ZAP_AUTH_REPORT
      reportTitle : ZAP by Checkmarx Scanning Report
    risks :
      - info
      - low
      - medium
      - high
    confidences :
      - falsepositive
      - low
      - medium
      - high
      - confirmed
    sections :
      - summary
      - diagnosticswebelements
      - diagnostics
      - diagnosticslocalstorage
      - diagnosticssessionstorage
      - diagnosticsscreenshots
      - diagnosticsmessages
      - afenv
      - statistics
  - type : exitStatus
    parameters : { }

We also need to use either the same or slightly different bash script that is going to run ZAP with this plan.

#!/bin/bash

sed -i "s/STRESS_TEST_PASSWORD/$STRESS_TEST_PASSWORD/g" zap/zap_full.yaml
sed -i "s/STRESS_TEST_USERNAME/$STRESS_TEST_USERNAME/g" zap/zap_full.yaml
sed -i "s/ZAP_REPORT/$ZAP_REPORT/g" zap/zap_full.yaml
sed -i "s/ZAP_AUTH_REPORT/$ZAP_AUTH_REPORT/g" zap/zap_full.yaml

# NOTE:
# We need this because the variable contains / in it.
# More info here https://unix.stackexchange.com/questions/255789/is-there-a-way-to-prevent-sed-from-interpreting-the-replacement-string/255869#255869
ESCAPED_CI_PROJECT_DIR=$(sed -e 's/[&\\/]/\\&/g; s/$/\\/' -e '$s/\\$//' <<<"$CI_PROJECT_DIR")
sed -i "s/CI_PROJECT_DIR/$ESCAPED_CI_PROJECT_DIR/g" zap/zap_full.yaml

# NOTE:
# We need this because the variable contains / in it.
# More info here https://unix.stackexchange.com/questions/255789/is-there-a-way-to-prevent-sed-from-interpreting-the-replacement-string/255869#255869
ESCAPED_TARGET=$(sed -e 's/[&\\/]/\\&/g; s/$/\\/' -e '$s/\\$//' <<<"$TARGET")
sed -i "s/TARGET/$ESCAPED_TARGET/g" zap/zap_full.yaml
sed -i "s/ZAP_ALERT_REPORT/$ZAP_ALERT_REPORT/g" zap/zap_full.yaml
sed -i "s/TARGET/$ESCAPED_TARGET/g" zap/zap-auth.zst
sed -i "s/STRESS_TEST_PASSWORD/$STRESS_TEST_PASSWORD/g" zap/zap-auth.zst
sed -i "s/STRESS_TEST_USERNAME/$STRESS_TEST_USERNAME/g" zap/zap-auth.zst

/zap/zap.sh -configfile $CI_PROJECT_DIR/zap/zap_config.conf -cmd -autorun $CI_PROJECT_DIR/zap/zap_full.yaml
returnCode=0
anyAlertsAboveLow=$(grep -E "^\| (High|Medium) \|" $CI_PROJECT_DIR/$ZAP_ALERT_REPORT.md | grep -oE '[0-9]+' | awk '$1>0')

if [ ! -z "$anyAlertsAboveLow" ]
then
  head -n 20 $CI_PROJECT_DIR/$ZAP_ALERT_REPORT.md
  echo "DAST RESULT: There are some vulnerabilities that ZAP has found (those visible here may not be the only ones). See the detailed report for more information."
  returnCode=1
fi

exit $returnCode

Thus here is what our GitLab’s CI/CD YAML file looks like for the ZAP full active scan.

Full ZAP active scan YAML file for GitLab CI/CD
Full ZAP active scan YAML file for GitLab CI/CD

Conclusion

Integrating ZAP into GitLab CI/CD pipelines provides automated security testing that scales with your development process. The combination of daily passive scanning and weekly active scanning creates a robust security testing strategy that catches vulnerabilities early in the development lifecycle.

What does the Codific team build with ZAP and Gitlab?

Codific is a team of security software engineers that leverage privacy by design principles to build secure cloud solutions. We build applications in different verticals such as HR-tech, Ed-Tech and Med-Tech. Secure collaboration and secure sharing are at the core of our solutions.

Videolab is used by top universities, academies and hospitals to put the care in healthcare. Communication skills, empathy and other soft skills are trained by sharing patient interview recordings for feedback.

SARA is used by top HR-Consultants to deliver team assessments, psychometric tests, 360 degree feedback, cultural analysis and other analytical HR tools.

SAMMY Is a Software Assurance Maturity Model management tool. It enables companies to formulate and implement a security assurance program tuned to the risks they are facing. That way other companies can help us build a simple and safe digital future. Obviously our AppSec program and SAMMY itself is built on top of it.

We believe in collaboration and open innovation, we would love to hear about your projects an see how we can contribute in developing secure software and privacy by design architecture. Contact us.

Authors

Subscribe to the AppSec Newsletter

For the past 15 years Aram has been involved in application security as a researcher, industry expert, and core contributor to the OWASP SAMM project. Aram is the founder and CEO of Codific, a Belgian cybersecurity product firm. At Codific, he works at the intersection of software engineering and application security, helping organizations build secure and reliable systems that protect what matters most. Aram holds a PhD in application security from DistriNet KU Leuven, which gives him a broad understanding of the security landscape. His work on refining and streamlining the LINDDUN privacy engineering methodology has been incorporated into both ISO and NIST standards. Aram is also a core contributing member of the OWASP SAMM project, which is the industry standard framework for managing application security programs.

Related Posts