Resource library

QA How-To

OWASP ZAP Authenticated DAST Pipeline (2026)

Build an OWASP ZAP authenticated DAST pipeline with Docker, verified form login, scoped scans, risk gates, reports, and GitHub Actions for safer CI runs.

19 min read | 2,754 words

TL;DR

Build the pipeline with ZAP 2.17.0 Automation Framework, a dedicated low-privilege account, explicit include and exclude paths, an authenticated verification request, bounded spider and active-scan jobs, reports, and an exitStatus gate. Run it from Docker locally, then use the same pinned plan in GitHub Actions.

Key Takeaways

  • Prove authentication against a protected page before allowing the spider or active scanner to run.
  • Keep the target, destructive exclusions, session method, and test user in one reviewable Automation Framework plan.
  • Pass credentials through CI secrets and never commit them or print them in scan logs.
  • Generate HTML and JSON reports before the exitStatus job applies the release gate.
  • Pin ZAP 2.17.0 and then pin the approved container digest for reproducible pipeline behavior.
  • Run active DAST only against an isolated target that your team has explicit permission to attack.
  • Treat a clean ZAP result as evidence about the discovered surface, not proof that the application is secure.

An OWASP ZAP authenticated DAST pipeline signs in as a controlled test user, proves that the session can reach protected content, explores only an approved target, runs bounded active checks, and turns reviewed risk levels into CI results. The authentication proof is the critical part. A scanner that silently falls back to the login page can produce a green report while missing most of the application.

This tutorial builds the pipeline with ZAP 2.17.0, Docker, the ZAP Automation Framework, and GitHub Actions. The example uses a cookie-backed form login at /login and a protected /account page. Replace those application-specific values with routes and indicators from your own staging system. The official project is now ZAP by Checkmarx; this article retains the assigned search phrase OWASP ZAP.

Active scanning sends attack payloads and may create, update, or delete data. Use only an isolated environment that you own or have explicit written permission to test. Coordinate limits, monitoring, data reset, and stop conditions before the first active run. For broader threat design beyond scanner findings, read API security testing with OWASP.

TL;DR

Pipeline control Concrete implementation Failure prevented
Authentication Form login, cookie session, protected-page indicator Anonymous scan reported as authenticated
Scope One context with include and destructive exclude regexes Requests escaping the approved application
Discovery Authenticated request, bounded spider, minimum URL test Active scan running against an empty site tree
Evidence HTML and JSON reports generated before the gate Findings lost when the job exits nonzero
Gate High alerts fail; medium alerts remain visible initially Either ignored risk or an unusably noisy rollout
Reproducibility ZAP 2.17.0 tag, followed by an approved digest Surprise scanner and add-on changes in CI

The finished command is zap.sh -cmd -autorun /zap/wrk/security/zap/plan.yaml. The same plan runs on a workstation and on the CI runner, so authentication, crawl limits, reports, and risk policy do not drift between environments.

What You Will Build

You will create a practical security stage with these outcomes:

  • A form-authenticated ZAP context that reads credentials from environment variables.
  • A verification request to /account that must return HTTP 200 and contain Sign out.
  • Explicit exclusions for logout, account deletion, billing, and other destructive workflows.
  • A traditional authenticated spider followed by passive and active scanning with time limits.
  • HTML and JSON artifacts that survive a failed security gate.
  • A GitHub Actions job restricted to an allowlisted staging host.

The directory used in the examples is security/zap/. You will add plan.yaml there and let the scan create security/zap/reports/.

Prerequisites

Use ZAP Core 2.17.0 through ghcr.io/zaproxy/zaproxy:2.17.0. The commands require Docker Engine 27.0 or newer, Bash 5.2, curl 8.5 or newer, and jq 1.7.

You also need a dedicated staging account with the lowest role that still reaches representative protected features. Seed deterministic data for that user, prevent email or payment side effects, and reset its state between scans. Do not use a developer's personal account, a production identity, or an administrator unless the scan has a separately approved admin phase.

Confirm the local tools and ZAP version:

docker --version
curl --version | head -n 1
jq --version
docker run --rm ghcr.io/zaproxy/zaproxy:2.17.0 zap.sh -version

Verification: The last command must print 2.17.0. If it prints a weekly build or another core version, stop and correct the image reference before authoring the plan.

Document these application facts before continuing: the exact base URL, login page, login submission URL, request encoding, username and password field names, session mechanism, protected verification URL, logged-in marker, logged-out marker, and dangerous routes. The JWT authentication testing guide is useful when your application uses bearer tokens rather than cookies.

Step 1: Define Scope and Safety Boundaries

Start with an environment file for local use. Keep the real file outside Git and commit only a .example version. The example below assumes URL-encoded form fields named email and password.

export TARGET_URL='https://staging.example.com'
export ZAP_USERNAME='dast.user@example.com'
read -r -s -p 'DAST password: ' ZAP_PASSWORD
export ZAP_PASSWORD
printf '\nTarget: %s\nUser: %s\n' "$TARGET_URL" "$ZAP_USERNAME"

Create a written scope table in the security test plan, even if it does not live in the Automation Framework YAML. Include the target hostname, environment owner, permitted schedule, expected maximum duration, alert recipients, abort contact, data reset owner, and forbidden integrations. Explicitly list routes that trigger password changes, logout, deletion, checkout, refunds, external notifications, bulk jobs, exports, or privileged support actions.

A context URL is not a complete safety control. The active scanner replays discovered requests and mutates parameters, so a valid in-scope request can still produce harmful business effects. Use disposable data, stub downstream integrations, disable scheduled workers where appropriate, and add server-side protection for the DAST identity.

Verification: Check that the target is the expected staging host before any scan starts.

case "$TARGET_URL" in
  https://staging.example.com) curl --fail --silent --show-error "$TARGET_URL/health" ;;
  *) echo 'Refusing unapproved DAST target' >&2; exit 64 ;;
esac

A successful health response proves reachability, not authorization to scan. Keep the human approval and operational window in your change or test record.

Step 2: Prove the Login Contract Outside ZAP

Test the login with curl before encoding it into ZAP. This separates application, credential, and network failures from scanner configuration failures. The command stores cookies in a temporary jar, then requests the protected page.

cookie_jar=$(mktemp)
trap 'rm -f "$cookie_jar"' EXIT

curl --fail --silent --show-error \
  --cookie-jar "$cookie_jar" \
  --data-urlencode "email=$ZAP_USERNAME" \
  --data-urlencode "password=$ZAP_PASSWORD" \
  "$TARGET_URL/login" >/dev/null

curl --fail --silent --show-error \
  --cookie "$cookie_jar" \
  "$TARGET_URL/account" | grep -F 'Sign out'

Verification: The second response must contain Sign out and return zero. Also repeat the protected request without --cookie; it should redirect, return 401 or 403, or contain the logged-out marker Sign in. If both authenticated and anonymous requests look identical, /account is a poor verification URL. Pick a stable response that actually distinguishes session state.

Do not choose a volatile marker such as a user's last-login timestamp. If login needs CSRF extraction, MFA, JavaScript-populated storage, SAML, or OpenID Connect redirects, use ZAP browser-based or client-script authentication instead of pretending the flow is a simple form. The session method must also match the app: cookie for server sessions, headers for extracted access tokens, or a supported script for custom behavior.

Step 3: Create the OWASP ZAP Authenticated DAST Pipeline Plan

Create security/zap/plan.yaml. This is a complete Automation Framework plan for the form and cookie contract verified in Step 2. It reads process environment variables using ${NAME} and uses ZAP credential placeholders inside the login body.

env:
  contexts:
    - name: ci-authenticated
      urls:
        - ${TARGET_URL}
      includePaths:
        - ${TARGET_URL}.*
      excludePaths:
        - ${TARGET_URL}/logout.*
        - ${TARGET_URL}/account/delete.*
        - ${TARGET_URL}/billing/.*
        - ${TARGET_URL}/admin/jobs/.*
      authentication:
        method: form
        parameters:
          loginPageUrl: ${TARGET_URL}/login
          loginRequestUrl: ${TARGET_URL}/login
          loginRequestBody: email={%username%}&password={%password%}
        verification:
          method: poll
          loggedInRegex: Sign out
          loggedOutRegex: Sign in
          pollFrequency: 5
          pollUnits: requests
          pollUrl: ${TARGET_URL}/account
          pollPostData: ''
      sessionManagement:
        method: cookie
        parameters: {}
      users:
        - name: dast-user
          credentials:
            username: ${ZAP_USERNAME}
            password: ${ZAP_PASSWORD}
  parameters:
    failOnError: true
    failOnWarning: false
    continueOnFailure: false
    progressToStdout: true

jobs:
  - type: requestor
    parameters:
      user: dast-user
    requests:
      - name: prove protected access
        url: ${TARGET_URL}/account
        method: GET
        responseCode: 200
    tests:
      - name: protected response contains authenticated marker
        type: url
        url: ${TARGET_URL}/account
        responseBodyRegex: Sign out
        onFail: error

  - type: spider
    parameters:
      context: ci-authenticated
      user: dast-user
      url: ${TARGET_URL}
      maxDuration: 5
      maxDepth: 8
      maxChildren: 50
      threadCount: 4
      acceptCookies: true
      logoutAvoidance: true
      postForm: false
    tests:
      - name: authenticated crawl discovered at least ten URLs
        type: stats
        statistic: automation.spider.urls.added
        operator: '>='
        value: 10
        onFail: error

  - type: passiveScan-wait
    parameters:
      maxDuration: 5

  - type: activeScan
    parameters:
      context: ci-authenticated
      user: dast-user
      url: ${TARGET_URL}
      policy: Default Policy
      maxRuleDurationInMins: 3
      maxScanDurationInMins: 20

  - type: passiveScan-wait
    parameters:
      maxDuration: 5

  - type: report
    parameters:
      template: modern
      reportDir: /zap/wrk/security/zap/reports
      reportFile: zap-report.html
      reportTitle: Authenticated staging DAST
      displayReport: false

  - type: report
    parameters:
      template: traditional-json
      reportDir: /zap/wrk/security/zap/reports
      reportFile: zap-report.json
      reportTitle: Authenticated staging DAST
      displayReport: false

  - type: exitStatus
    parameters:
      errorLevel: High
      warnLevel: Medium
      okExitValue: 0
      errorExitValue: 1
      warnExitValue: 0
    alwaysRun: true

The requestor job is an authentication circuit breaker. It uses dast-user, expects HTTP 200, and checks the actual protected response for the logged-in marker. The spider and active scanner also name the same user. Omitting user from any of those jobs can change coverage without producing an obvious syntax error.

The first rollout fails only for High alerts because warnExitValue is zero. Medium findings remain in the reports. Once the team has triaged the baseline and established exception ownership, change warnExitValue to 2 to block on Medium too.

Verification: Confirm that every destructive route found in Step 1 has an anchored exclusion and that the login, logout, and verification patterns match the application's actual URLs. Pay special attention to regex metacharacters in dynamic hostnames or paths.

Step 4: Validate the Plan Without Attacking the Target

Use -autocheck to validate the plan structure before -autorun. The check loads the plan but does not execute its jobs. Pass all three environment variables because the plan resolves them while loading.

docker run --rm \
  --env TARGET_URL \
  --env ZAP_USERNAME \
  --env ZAP_PASSWORD \
  --volume "$PWD:/zap/wrk/:rw" \
  ghcr.io/zaproxy/zaproxy:2.17.0 \
  zap.sh -cmd -autocheck /zap/wrk/security/zap/plan.yaml

Verification: The container must exit zero without messages such as Unknown job, Invalid parameter, or an unresolved variable. Syntax validation cannot prove that authentication works, exclusions are safe, or the account has enough access. Those are runtime assertions handled by the requestor and crawl tests.

Pinning 2.17.0 fixes the ZAP core version, but stable images may be rebuilt with new base layers and add-ons. After the plan passes in a controlled upgrade branch, capture the multi-architecture image digest used by your runner:

docker pull ghcr.io/zaproxy/zaproxy:2.17.0
docker image inspect --format '{{index .RepoDigests 0}}' ghcr.io/zaproxy/zaproxy:2.17.0

Use the returned image@sha256:... reference in CI.

Step 5: Run and Verify the Authenticated Scan Locally

Create the artifact directory and run the same plan. The mounted workspace must be writable by the container's zap user. On a controlled CI workspace, grant write access only to the report directory rather than the whole repository.

mkdir -p security/zap/reports
chmod 0777 security/zap/reports
docker run --rm \
  --env TARGET_URL \
  --env ZAP_USERNAME \
  --env ZAP_PASSWORD \
  --volume "$PWD:/zap/wrk/:rw" \
  ghcr.io/zaproxy/zaproxy:2.17.0 \
  zap.sh -cmd -autorun /zap/wrk/security/zap/plan.yaml

Verification: Read the progress output in order. The protected request test must pass before the spider begins. The spider test must report at least ten added URLs, both passive queues must finish, and the reports must appear before exitStatus. Confirm the files directly:

test -s security/zap/reports/zap-report.html
test -s security/zap/reports/zap-report.json
jq -e '.site | type == "array"' security/zap/reports/zap-report.json

Ten URLs is illustrative. Replace it with a reviewed lower bound derived from your stable authenticated surface. A sudden drop from 120 routes to 10 can still satisfy a weak threshold, so trend route counts and maintain protected canary URLs for high-value areas. Discovery quality controls the ceiling of DAST coverage.

Inspect the application's access logs too. Requests should carry the DAST user's session, remain under the allowlisted host and paths, and avoid excluded side effects.

Step 6: Inspect Findings and Calibrate the Gate

Summarize alerts by risk before reading individual evidence. The traditional JSON report contains sites and their alert arrays.

jq '[.site[]?.alerts[]?] | group_by(.riskcode) | map({risk: (.[0].riskdesc // "Unknown"), count: length})' \
  security/zap/reports/zap-report.json

Verification: Open the HTML report and select at least one alert from each populated risk. Confirm the URL is in scope, the request belongs to the authenticated scan, the evidence does not expose unnecessary secrets, and the behavior is reproducible with the smallest safe request. Check the application state after the scan for created records, triggered notifications, queued jobs, or account changes.

Triage is not simply changing every inconvenient alert to false positive. Record the rule ID, affected route, evidence, confidence, owner, decision, expiry, and retest date. Suppress a finding only with a narrow alert filter tied to evidence. Broad global suppression can conceal a real vulnerability introduced on a new route.

A useful gate separates three outcomes. Scanner or authentication failure is a broken test and must never pass. A reviewed High finding blocks release. Medium and Low findings follow the team's risk policy during adoption, but remain visible with ownership. Pair DAST with designed authorization and misuse tests from API security testing basics.

Step 7: Add the OWASP ZAP Authenticated DAST Pipeline to GitHub Actions

Create .github/workflows/authenticated-dast.yml with a manual trigger first. The workflow allowlists one staging origin, retrieves credentials from GitHub environment secrets, captures the Docker exit code, uploads reports even when ZAP fails, and enforces the captured result last.

name: Authenticated DAST

on:
  workflow_dispatch:
    inputs:
      target_url:
        description: Approved staging origin
        required: true
        default: https://staging.example.com
        type: string

permissions:
  contents: read

concurrency:
  group: authenticated-dast-staging
  cancel-in-progress: false

jobs:
  zap:
    runs-on: ubuntu-24.04
    timeout-minutes: 35
    environment: dast-staging
    steps:
      - uses: actions/checkout@v4

      - name: Validate target allowlist
        env:
          TARGET_URL: ${{ inputs.target_url }}
        run: |
          case "$TARGET_URL" in
            https://staging.example.com) ;;
            *) echo 'Refusing unapproved DAST target' >&2; exit 64 ;;
          esac

      - name: Prepare report directory
        run: |
          mkdir -p security/zap/reports
          chmod 0777 security/zap/reports

      - name: Run authenticated ZAP plan
        id: zap
        env:
          TARGET_URL: ${{ inputs.target_url }}
          ZAP_USERNAME: ${{ secrets.ZAP_USERNAME }}
          ZAP_PASSWORD: ${{ secrets.ZAP_PASSWORD }}
        run: |
          set +e
          docker run --rm \
            --env TARGET_URL \
            --env ZAP_USERNAME \
            --env ZAP_PASSWORD \
            --volume "$GITHUB_WORKSPACE:/zap/wrk/:rw" \
            ghcr.io/zaproxy/zaproxy:2.17.0 \
            zap.sh -cmd -autorun /zap/wrk/security/zap/plan.yaml
          zap_code=$?
          set -e
          echo "exit_code=$zap_code" >> "$GITHUB_OUTPUT"

      - name: Upload restricted DAST reports
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: authenticated-dast-report
          path: security/zap/reports/
          if-no-files-found: warn
          retention-days: 14

      - name: Enforce ZAP result
        if: always()
        env:
          ZAP_EXIT_CODE: ${{ steps.zap.outputs.exit_code }}
        run: |
          test -n "$ZAP_EXIT_CODE" || exit 1
          exit "$ZAP_EXIT_CODE"

Verification: Store ZAP_USERNAME and ZAP_PASSWORD in the protected dast-staging GitHub environment, require an approver, and run the workflow. The artifact should contain both reports. Deliberately change the password once in a test branch: the protected-page assertion must fail, reports may be absent because scanning stops early, and the final job result must be red. Restore the secret immediately.

Do not trigger active DAST on untrusted pull requests. Forked code can alter the plan or exfiltrate secrets, while pull request inputs can redirect the scanner. A protected environment, reviewed workflow, fixed allowlist, read-only repository permission, and manual approval form a safer trust boundary. For CI fundamentals such as required checks and artifact flow, use how to add CI to a test framework and GitHub Actions for Playwright.

Step 8: Adapt Authentication and Expand Coverage Safely

The example works when the server accepts URL-encoded credentials and maintains a cookie session. Adapt the smallest part that differs. For a JSON login, use method: json, provide a JSON request body with {%username%} and {%password%}, then configure header-based session management to extract and replay the returned token. For standard HTTP authentication, use method: http with hostname, port, and realm. For browser-driven SSO, use browser authentication or record a client script in the ZAP desktop, then test it headlessly before CI.

Application behavior ZAP approach Verification evidence
Server cookie after HTML form Form authentication plus cookie session Protected HTML marker
Token returned by JSON API JSON authentication plus header session Protected JSON field or status
Basic, Digest, or NTLM challenge HTTP authentication Protected endpoint response
JavaScript storage or redirect SSO Browser or client-script authentication Browser-loaded protected route
MFA required for every login Dedicated automation policy or preapproved test bypass Audited nonproduction control plus protected response

Keep a separate user and expected route set for each meaningful role. Scanning only as an administrator can miss lower-role authorization behavior and may increase destructive capability. Separate artifacts and account state make findings attributable. ZAP still cannot reason reliably about object ownership, tenant boundaries, or business abuse, so add deterministic functional security tests around those rules.

Verification: For every authentication variant, add a protected request that fails anonymously and succeeds as the named user. Compare discovery counts and canary URLs with the previous approved run. If coverage changes sharply, investigate authentication and navigation before accepting the new baseline.

Troubleshooting

Problem: The requestor gets 200 but the body is the login page -> Assert a stable protected-body marker as well as the status. Inspect redirects and choose a URL whose anonymous response is visibly different.

Problem: ZAP repeatedly logs in or loses the session -> Confirm the cookie or header strategy, verify that the logout exclusion matches, check session timeout and concurrent-login limits, and lower spider threads if parallel requests invalidate sessions.

Problem: Environment values are blank at runtime -> Pass each name with --env, verify GitHub secret names exactly, and check for a quoted literal that prevents expansion. Never print the password while diagnosing it.

Problem: The spider finds only public pages -> Put user: dast-user on requestor, spider, and activeScan. After authentication passes, use AJAX or Client Spider, API definitions, or seeded requestor jobs for browser-only routes.

Problem: Reports are missing after failure -> Place report jobs before exitStatus, upload artifacts with if: always(), and make the report directory writable. An early authentication failure can legitimately stop before report generation.

Problem: The scan is noisy or slow -> Bound crawl and scan duration, baseline alerts, and filter only reviewed rule-route combinations. Use passive checks more frequently, but never improve runtime by dropping authentication or hiding execution failures.

Where To Go Next

First, add role-specific protected canaries and authorization tests. The API security testing with OWASP guide helps map scanner coverage to access control, resource consumption, SSRF, inventory, and third-party trust risks. Use JWT security testing when token claims, expiry, audience, refresh, and revocation need targeted verification.

Next, operate the scan like any other release control. Track duration, authentication failures, discovered URLs, alert changes, accepted-risk expiry, and remediation time. If the same environment hosts functional automation, coordinate datasets and use flaky test quarantine in CI principles only for unstable tests, never for confirmed security findings.

Finally, add complementary layers. Run static and dependency checks before deployment, deterministic authorization tests on every change, authenticated DAST against the deployed build, and periodic expert penetration testing for chained and business-logic weaknesses. No single scanner sees the entire system.

Interview Questions and Answers

Use the seven model answers in the structured interviewQnA set to rehearse pipeline design, authentication proof, damage prevention, false-positive handling, report ordering, browser authentication, and DAST limitations. A credible answer connects each control to evidence: a protected response, named-user traffic, bounded execution, preserved artifacts, or a reviewed risk decision. In an interview, explain the failure mode the control prevents instead of listing ZAP features.

Best Practices

  • Use one disposable least-privilege identity per role and scan, with deterministic seeded data.
  • Prove a protected response before discovery and fail closed when proof disappears.
  • Keep include paths narrow and exclusions explicit, reviewed, and covered by server-side safeguards.
  • Turn off spider form submission unless the test design specifically approves generated mutations.
  • Bound crawl depth, children, threads, rule duration, total duration, and workflow timeout.
  • Generate evidence before applying the exit code, then restrict artifact access and retention.
  • Pin the core version and approved image digest, but schedule controlled scanner updates.
  • Treat exceptions as expiring risk decisions with owners, not permanent blanket suppressions.
  • Trend authenticated route and canary coverage so a shrinking scan cannot look healthy.
  • Keep DAST away from production unless a separately reviewed plan explicitly permits a constrained mode.

Conclusion

A dependable OWASP ZAP authenticated DAST pipeline does more than submit credentials. It proves protected access, binds every discovery and attack job to the same user, limits scope and runtime, preserves evidence, and fails according to a deliberate risk policy. Those controls turn an opaque scanner invocation into a test the QA, security, and platform teams can review together.

Start by making the requestor assertion fail correctly with an invalid password. Then restore the credential, confirm authenticated canary coverage, inspect side effects, and approve the baseline before making the job a required release check. That sequence gives the green result a defensible meaning.

Interview Questions and Answers

How would you design an authenticated DAST stage with OWASP ZAP?

I start with written scope, disposable identities, destructive exclusions, runtime limits, and operational approval. In an Automation Framework plan, I configure authentication and session management, assert a protected response, crawl as the same user, run bounded passive and active jobs, generate reports, and apply an exitStatus policy. I execute the identical pinned plan locally and in CI.

What is the strongest signal that a ZAP scan is authenticated?

A protected endpoint requested as the configured user returns both the expected status and a stable logged-in response marker, while the anonymous version does not. I supplement that assertion with protected canary URLs and server logs showing the DAST identity. Route count alone is not enough because public pages can inflate it.

How do you prevent an active scanner from damaging staging data?

I isolate the environment and tenant, use a least-privilege disposable account, stub external integrations, seed resettable data, exclude dangerous routes, disable unapproved form submission, and cap duration and concurrency. I monitor the scan and define an abort owner. URL exclusions are only one containment layer.

How do you manage false positives from ZAP in CI?

I reproduce the smallest safe request, validate scope and authentication, and record rule ID, route, evidence, confidence, owner, decision, and expiry. If suppression is justified, I make the alert filter narrow and reviewable. I never hide scanner or authentication failures as false positives.

Why should report generation occur before the exitStatus job?

A nonzero security result should stop promotion but must not erase diagnostic evidence. Generating HTML and machine-readable reports first lets CI upload them under an always-run step. The final step then enforces the captured ZAP exit code.

When would you choose browser-based authentication over form authentication?

I choose it when login depends on JavaScript, client-side storage, SSO redirects, complex CSRF handling, WebAuthn, or another flow that direct HTTP requests cannot reproduce reliably. I record or configure the browser flow in ZAP Desktop, run it headlessly, and keep the same protected-response proof in automation.

Can a clean authenticated ZAP report prove an application is secure?

No. It means configured rules found no reportable issue in the surface and states that ZAP reached. Business logic, cross-user authorization, tenant isolation, races, chained exploits, and undiscovered routes require other tests and human analysis. I present DAST as one evidence layer, not certification.

Frequently Asked Questions

How do I run an OWASP ZAP authenticated DAST pipeline?

Define authentication, session management, a named user, scope, discovery, reports, and exit policy in a ZAP Automation Framework YAML plan. Run it with the ZAP 2.17.0 Docker image locally, then execute the identical plan in CI with credentials supplied from protected secrets.

How can I verify that ZAP is still logged in during a scan?

Request a stable protected URL as the configured user and assert an authenticated body marker, not just HTTP 200. Also monitor protected canary URLs, discovery counts, and application logs for the DAST identity because a session can expire after initial login.

Can OWASP ZAP handle form-based authentication?

Yes. Configure `method: form`, the login page and request URLs, a login body containing ZAP credential placeholders, verification indicators, cookie session management, and a named user. Use browser or client-script authentication when CSRF, MFA, SSO, or client-side storage makes the flow more complex.

Should authenticated ZAP scanning run against production?

Active scanning can mutate data, trigger workflows, create load, and call downstream services. Prefer an isolated staging environment with written authorization, disposable identities, stub integrations, monitoring, limits, reset capability, and stop conditions. Any production scan needs a separately approved and tightly constrained plan.

Which ZAP Docker image should a CI pipeline use in 2026?

This tutorial uses `ghcr.io/zaproxy/zaproxy:2.17.0`, the current full ZAP release available when the article was prepared. After validation, pin the approved repository digest for reproducibility and schedule deliberate upgrades so the core, base image, and add-ons do not become stale.

What exit code should fail an authenticated DAST pipeline?

Any authentication, configuration, discovery, or execution error should fail because the test evidence is invalid. Use the Automation Framework `exitStatus` job for alert policy, commonly blocking High findings first and later Medium findings after the initial baseline is triaged.

Why does my authenticated ZAP scan find only public pages?

The user may be missing from the spider or activeScan job, session replay may be wrong, or the application may require browser-based navigation. Verify a protected response first, inspect redirects and cookies or headers, then use AJAX or Client Spider, API definitions, or seeded requests where traditional crawling is insufficient.

Related Guides