CI/CD Integration GitHub Actions GitLab CI CLI

CI/CD Integration

Integrate Penetrify's AI penetration testing engine into any CI/CD pipeline. Gate every deployment on real security results — scans run against your staging environment in minutes and fail the build before vulnerable code reaches production.

Penetrify supports GitHub Actions (native action), GitLab CI (Docker image), and any platform that can run a shell command (via the CLI). Choose your platform below for a copy-paste integration.

How it works

Every integration follows the same four-step flow, regardless of platform:

  1. Deploy to staging — your pipeline deploys the application to a staging environment with a known URL. The scan runs against this URL, not production.
  2. Trigger a scan — the Penetrify step calls the API with your target URL and API key. The AI agent begins testing for real-world vulnerabilities: SQLi, XSS, broken access control, IDOR, SSRF, and more.
  3. Wait for results — the step polls the scan status until it completes (typically 8–18 minutes for a standard scan). The wait-for-results: false flag skips waiting for fire-and-forget mode.
  4. Gate the build — if any finding meets or exceeds your configured severity threshold, the step exits with code 1, failing the build. Findings are written to a report artifact.
ℹ️

Scans run entirely on Penetrify's infrastructure. Your pipeline runner only needs outbound HTTPS access to api.penetrify.cloud. No agent is installed on your application servers.

Quick start

Three steps to your first scan in the pipeline:

  1. Sign up at app.penetrify.cloud and create an API key under Settings → API Keys.
  2. Add the key as a secret in your CI/CD platform (e.g. PENETRIFY_API_KEY). It must never appear in plain text in your YAML files.
  3. Add the scan step below to your pipeline YAML, after your staging deployment job.

GitHub Actions

The official penetrify/scan-action runs natively on any GitHub-hosted or self-hosted runner. No Docker pull required. Results can be uploaded to Security → Code scanning as SARIF.

Full pipeline example

.github/workflows/security.yml
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  deploy-staging:
    runs-on: ubuntu-latest
    outputs:
      staging_url: ${{ steps.deploy.outputs.url }}
    steps:
      - uses: actions/checkout@v4
      - name: Deploy to staging
        id: deploy
        run: echo "url=https://staging-${{ github.sha }}.myapp.io" >> $GITHUB_OUTPUT

  penetrify-scan:
    needs: deploy-staging
    runs-on: ubuntu-latest
    permissions:
      security-events: write
      contents: read
    steps:
      - name: Run Penetrify security scan
        uses: penetrify/scan-action@v1
        with:
          url: ${{ needs.deploy-staging.outputs.staging_url }}
          api-key: ${{ secrets.PENETRIFY_API_KEY }}
          fail-on: critical,high
          report-format: sarif

      - name: Upload results to GitHub Security
        if: always()
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: penetrify-results.sarif

      - name: Upload HTML report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: security-report
          path: penetrify-report.html

Action inputs

InputRequiredDefaultDescription
api-key ✅ Yes Your Penetrify API key. Always store as a GitHub Secret.
url ✅ Yes Full URL of the target application. Must be reachable from Penetrify's scanners.
scan-type ❌ No full Scan depth: full, quick, api. Use quick on PRs for faster feedback.
fail-on ❌ No high Comma-separated severity levels that fail the build: critical, high, medium, low.
report-format ❌ No html Output format: html, json, sarif, markdown.
output ❌ No penetrify-report Output filename (without extension). Extension is added automatically based on format.
wait-for-results ❌ No true Wait for scan completion. Set false for fire-and-forget.
timeout ❌ No 1800 Seconds to wait before marking the scan as timed out.
config-file ❌ No Path to a Penetrify config file for advanced scan settings.
💡

For PR checks, use scan-type: quick and fail-on: critical to keep the feedback loop tight. Reserve scan-type: full for pushes to main and scheduled audits.

GitLab CI

Use the official penetrify/cli Docker image in any GitLab CI stage. Findings can be uploaded as a GitLab Security report artifact for display in the merge request security widget.

.gitlab-ci.yml
stages:
  - build
  - deploy
  - security
  - release

deploy-staging:
  stage: deploy
  script:
    - ./scripts/deploy-staging.sh
  environment:
    name: staging
    url: https://staging.myapp.io

penetrify-scan:
  stage: security
  image: penetrify/cli:latest
  needs: [deploy-staging]
  variables:
    PENETRIFY_API_KEY: $PENETRIFY_API_KEY
    SCAN_URL: https://staging.myapp.io
  script:
    - penetrify scan $SCAN_URL
        --fail-on critical,high
        --report-format gl-dast
        --output gl-dast-report.json
  artifacts:
    when: always
    reports:
      dast: gl-dast-report.json
    paths: [gl-dast-report.json]
    expire_in: 30 days
  allow_failure: false
ℹ️

Using report-format: gl-dast produces a GitLab-compatible DAST report that surfaces findings in the Security → Vulnerability Report tab and in merge request security widgets. Requires GitLab Ultimate for the security widget.

CLI / Script

The Penetrify CLI runs anywhere Node.js is available and can be used in any CI/CD platform by installing it as a build step. Use it for custom pipelines, scripted integrations, or platforms without a native action.

Install

# npm (requires Node 18+)
npm install -g @penetrify/cli

# Or run without installing via npx
npx @penetrify/cli scan ...

Basic usage

pipeline scan step
# Authenticate once — stores token in ~/.penetrify
penetrify auth login --api-key $PENETRIFY_API_KEY

# Run a full scan and fail on critical or high findings
penetrify scan https://staging.myapp.io \
  --fail-on critical,high \
  --report-format html \
  --output ./security-report.html

# Exit code 0 = no blocking findings → pipeline continues
# Exit code 1 = critical or high findings found → pipeline fails

Authenticated scan

# Pass credentials for authenticated surface testing
penetrify scan https://staging.myapp.io \
  --auth-user ci@myapp.io \
  --auth-pass $CI_TEST_PASSWORD \
  --fail-on critical,high \
  --report-format json \
  --output ./security-report.json
💡

Credentials passed via --auth-user / --auth-pass are never stored or logged. They are used only for the duration of the scan session to maintain an authenticated browser context.

Other platforms

Any platform that can run a shell command can integrate Penetrify via the CLI. Below are minimal snippets for common platforms.

CircleCI

.circleci/config.yml
version: 2.1

jobs:
  security-scan:
    docker:
      - image: cimg/node:20.0
    steps:
      - run:
          name: Install Penetrify CLI
          command: npm install -g @penetrify/cli
      - run:
          name: Run security scan
          command: |
            penetrify scan $STAGING_URL \
              --api-key $PENETRIFY_API_KEY \
              --fail-on critical,high \
              --report-format html \
              --output /tmp/security-report.html
      - store_artifacts:
          path: /tmp/security-report.html
          destination: security-report.html

Jenkins

Jenkinsfile
pipeline {
  agent any
  stages {
    stage('Deploy Staging') { /* ... */ }
    stage('Security Scan') {
      steps {
        withCredentials([string(credentialsId: 'penetrify-api-key', variable: 'PENETRIFY_API_KEY')]) {
          sh '''
            npm install -g @penetrify/cli
            penetrify scan https://staging.myapp.io \
              --api-key $PENETRIFY_API_KEY \
              --fail-on critical,high \
              --report-format html \
              --output security-report.html
          '''
        }
      }
      post {
        always {
          archiveArtifacts artifacts: 'security-report.html'
        }
      }
    }
  }
}

Bitbucket Pipelines

bitbucket-pipelines.yml
image: node:20

pipelines:
  branches:
    main:
      - step:
          name: Deploy to staging
          script:
            - ./scripts/deploy.sh
      - step:
          name: Security scan
          script:
            - npm install -g @penetrify/cli
            - penetrify scan $STAGING_URL --api-key $PENETRIFY_API_KEY --fail-on critical,high
          artifacts:
            - penetrify-report.html

Azure DevOps

azure-pipelines.yml
trigger:
  - main

pool:
  vmImage: ubuntu-latest

steps:
  - script: npm install -g @penetrify/cli
    displayName: Install Penetrify CLI

  - script: |
      penetrify scan $(STAGING_URL) \
        --api-key $(PENETRIFY_API_KEY) \
        --fail-on critical,high \
        --report-format html \
        --output $(Build.ArtifactStagingDirectory)/security-report.html
    displayName: Run security scan
    env:
      PENETRIFY_API_KEY: $(PENETRIFY_API_KEY)

  - task: PublishBuildArtifacts@1
    condition: always()
    inputs:
      PathtoPublish: $(Build.ArtifactStagingDirectory)
      ArtifactName: security-report

AWS CodePipeline (via CodeBuild)

buildspec.yml
version: 0.2

phases:
  install:
    runtime-versions:
      nodejs: 20
    commands:
      - npm install -g @penetrify/cli
  build:
    commands:
      - penetrify scan $STAGING_URL --api-key $PENETRIFY_API_KEY --fail-on critical,high --report-format html --output security-report.html

artifacts:
  files:
    - security-report.html

CLI reference

Full reference for the penetrify CLI commands used in pipeline integrations.

penetrify scan

FlagDefaultDescription
--api-key <key> $PENETRIFY_API_KEY API key. Falls back to PENETRIFY_API_KEY env var if omitted.
--scan-type <type> full full — comprehensive scan; quick — fast, targeted; api — REST/GraphQL APIs only.
--fail-on <levels> high Comma-separated severity levels: critical, high, medium, low. Exits 1 if any finding matches.
--report-format <fmt> html html, json, sarif, markdown, gl-dast.
--output <path> penetrify-report Output path. Extension added automatically unless path includes one.
--auth-user <user> Username or email for authenticated scanning. Paired with --auth-pass.
--auth-pass <pass> Password for authenticated scanning. Use an env var — never hardcode.
--timeout <seconds> 1800 Maximum wait time. Exits 2 (timeout) if exceeded.
--no-wait Fire-and-forget mode. Triggers scan and exits immediately with code 0.
--config <path> Path to a config file for advanced settings.

penetrify auth

# Save API key to ~/.penetrify (avoids passing --api-key every run)
penetrify auth login --api-key $PENETRIFY_API_KEY

# Print current auth status
penetrify auth status

# Remove saved credentials
penetrify auth logout

Config file

Advanced scan settings can be stored in a penetrify.config.json file checked into your repository. Pass it with --config or the action's config-file input. Config file values are overridden by CLI flags.

penetrify.config.json
{
  "scan": {
    "type": "full",
    "fail_on": ["critical", "high"],
    "timeout": 3600
  },
  "exclude": {
    "paths": ["/health", "/metrics", "/admin/setup"],
    "checks": ["clickjacking"]
  },
  "report": {
    "formats": ["html", "json"],
    "output_dir": "./security-reports"
  },
  "notifications": {
    "slack_webhook": "$SLACK_WEBHOOK_URL"
  }
}
ℹ️

Environment variable references ($VAR_NAME) in config values are expanded at scan time, so you can safely commit the config file without exposing secrets.

Severity levels

The --fail-on flag (or fail-on input) controls which findings block the build. Setting it to a level will fail on that level and above.

LevelFails build when…Recommended for
critical Only critical findings present PR checks — keeps the bar high to avoid blocking merges
high High or critical findings present Default — good balance for production deploys
medium Medium, high, or critical findings present Scheduled audits or compliance-sensitive applications
low Any finding present Maximum strictness — use with care

Exit codes

The CLI exits with a specific code that your pipeline can inspect to take different actions.

CodeMeaningAction
0 Scan completed — no blocking findings Pipeline continues normally
1 Scan completed — findings at or above fail-on threshold Pipeline fails; review report artifact
2 Scan timed out before completion Increase --timeout or use --no-wait
3 Authentication or API error Check API key and network access to api.penetrify.cloud
4 Target unreachable Ensure staging URL is publicly accessible during scan

Authenticated scans

To test the full authenticated surface of your application, pass login credentials to the scan. Penetrify's AI agent will log in, maintain the session, and test all routes available to that user.

Credential types

TypeFlagsNotes
Username + password --auth-user + --auth-pass Standard form-based or HTTP Basic auth
API key / Bearer token --auth-token Sent as Authorization: Bearer <token> header
Custom header --auth-header "X-API-Key: value" Any arbitrary auth header
Config file --config penetrify.config.json Full auth config including MFA, OAuth, custom flows
⚠️

Always pass credentials via environment variables — never hardcode them in YAML files. Create a dedicated CI user with limited permissions scoped to the staging environment.

Report artifacts

Penetrify can generate reports in multiple formats simultaneously. Configure report.formats in your config file to produce all desired outputs in one scan.

FormatFlag valueUse case
HTML report html Human-readable report with finding details, screenshots, reproduction steps
JSON json Machine-readable format for custom integrations, dashboards, issue trackers
SARIF sarif GitHub Code Scanning, Azure DevOps Advanced Security
Markdown markdown PR comment automation, Confluence, Notion
GitLab DAST gl-dast GitLab Security Dashboard and merge request security widget

Troubleshooting

Target is not reachable (exit code 4)

Penetrify's scanners connect over the public internet. Ensure your staging URL is publicly accessible during the scan window. If your environment is behind a firewall, allowlist Penetrify's scanner IP range (available in your dashboard under Settings → Scanner IPs) or contact support for VPN tunnel options on the Enterprise plan.

Authentication fails mid-scan

Ensure your CI test account has a session timeout longer than the expected scan duration (30–60 min for full scans). Some applications expire sessions aggressively — check your session TTL setting. You can also configure token refresh in the config file.

Scan times out (exit code 2)

Increase --timeout (default: 1800 seconds). Full scans of large applications can take 30–60 minutes. For pipeline time budgets, use --scan-type quick for PR checks and --scan-type full for scheduled overnight audits only.

Build fails on known false positives

Dismiss findings in your Penetrify dashboard — dismissed findings do not count toward the threshold on subsequent scans. Alternatively, use exclude.checks in your config file to skip specific check types, or raise the --fail-on threshold.

SARIF upload fails on GitHub (403)

Add permissions: security-events: write to the job in your GitHub Actions YAML. Without this permission, the SARIF upload step will fail with a 403.

Invalid API key (exit code 3)

Verify the secret name exactly matches the variable you reference (e.g. PENETRIFY_API_KEY). Check the key is active in Settings → API Keys on penetrify.cloud. Keys can be rotated at any time from the dashboard.

Still stuck?

Open an issue on GitHub or email info@penetrify.cloud.