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:
- Deploy to staging — your pipeline deploys the application to a staging environment with a known URL. The scan runs against this URL, not production.
- 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.
- Wait for results — the step polls the scan status until it completes (typically 8–18 minutes for a standard scan). The
wait-for-results: falseflag skips waiting for fire-and-forget mode. - 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:
- Sign up at app.penetrify.cloud and create an API key under Settings → API Keys.
- 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. - 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
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
| Input | Required | Default | Description |
|---|---|---|---|
| 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.
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
# 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
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
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
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
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)
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
| Flag | Default | Description |
|---|---|---|
| --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.
{
"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.
| Level | Fails 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.
| Code | Meaning | Action |
|---|---|---|
| 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
| Type | Flags | Notes |
|---|---|---|
| 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.
| Format | Flag value | Use 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.