Integrate Armis security scanning into your CI/CD pipeline to automatically detect vulnerabilities in pull requests and monitor your codebase over time.
- Quick Start
- GitHub Actions
- Advanced Patterns
- Other CI Platforms
- Output Formats
- Troubleshooting
- Security Best Practices
Add this to .github/workflows/security-scan.yml:
name: Security Scan
on:
pull_request:
branches: [main]
jobs:
scan:
uses: ArmisSecurity/armis-cli/.github/workflows/reusable-security-scan.yml@main
secrets:
api-token: ${{ secrets.ARMIS_API_TOKEN }}
tenant-id: ${{ secrets.ARMIS_TENANT_ID }}That's it! This will:
- Scan your repository on every PR
- Post results as a PR comment
- Upload findings to GitHub Code Scanning
- Fail on CRITICAL vulnerabilities
# Install
curl -sSL https://raw.githubusercontent.com/ArmisSecurity/armis-cli/main/scripts/install.sh | bash
# Run scan (JWT auth - recommended)
export ARMIS_CLIENT_ID="your-client-id"
export ARMIS_CLIENT_SECRET="your-client-secret"
armis-cli scan repo . --format sarif --fail-on CRITICALThe Armis CLI supports two authentication methods. JWT authentication is recommended.
Obtain client credentials from the VIPR external API screen in the Armis platform.
| Credential | Environment Variable | CLI Flag | Description |
|---|---|---|---|
| Client ID | ARMIS_CLIENT_ID |
--client-id |
Client ID for JWT authentication |
| Client Secret | ARMIS_CLIENT_SECRET |
--client-secret |
Client secret for JWT authentication |
The tenant ID is automatically extracted from the JWT token — no need to set it separately.
Example:
export ARMIS_CLIENT_ID="your-client-id"
export ARMIS_CLIENT_SECRET="your-client-secret"
armis-cli scan repo .| Credential | Environment Variable | CLI Flag | Description |
|---|---|---|---|
| API Token | ARMIS_API_TOKEN |
--token |
API token for authentication |
| Tenant ID | ARMIS_TENANT_ID |
--tenant-id |
Tenant identifier |
The reusable workflow is the simplest way to integrate Armis scanning. It handles:
- CLI installation with checksum verification
- SARIF upload to GitHub Code Scanning
- Detailed PR comments with severity breakdown
- Artifact storage for historical tracking
name: Security Scan
on:
pull_request:
branches: [main, develop]
permissions:
contents: read
security-events: write
pull-requests: write
jobs:
security-scan:
uses: ArmisSecurity/armis-cli/.github/workflows/reusable-security-scan.yml@main
with:
fail-on: 'CRITICAL,HIGH'
pr-comment: true
secrets:
api-token: ${{ secrets.ARMIS_API_TOKEN }}
tenant-id: ${{ secrets.ARMIS_TENANT_ID }}| Input | Type | Default | Description |
|---|---|---|---|
scan-type |
string | repo |
Type of scan: repo or image |
scan-target |
string | . |
Path for repo scan, image name for image scan |
fail-on |
string | CRITICAL |
Comma-separated severity levels to fail on (e.g., HIGH,CRITICAL). Set to empty string to never fail. |
pr-comment |
boolean | true |
Post scan results as PR comment |
upload-artifact |
boolean | true |
Upload SARIF results as artifact |
artifact-retention-days |
number | 30 |
Days to retain artifacts |
image-tarball |
string | Path to image tarball (for image scans) | |
scan-timeout |
number | 60 |
Scan timeout in minutes |
include-files |
string | Comma-separated list of file paths to scan (for targeted scanning) | |
build-from-source |
boolean | false |
Build CLI from source instead of release (for testing) |
| Secret | Description |
|---|---|
api-token |
Armis API token (Basic auth) |
tenant-id |
Tenant identifier (Basic auth) |
Note: The reusable workflow currently accepts Basic auth secrets only. For JWT authentication (recommended), use Option 3: Manual Installation with
ARMIS_CLIENT_IDandARMIS_CLIENT_SECRETenvironment variables.
permissions:
contents: read # Read repository content
security-events: write # Upload SARIF to Code Scanning
pull-requests: write # Post PR comments
actions: read # Access workflow artifactsPR Comments: Detailed breakdown of findings by severity with expandable details for each issue:
| Severity | Count |
|---|---|
| CRITICAL | 2 |
| HIGH | 5 |
| MEDIUM | 12 |
GitHub Code Scanning: Findings appear in the Security tab, inline in PR diffs, and as check annotations.
Artifacts: SARIF results are stored for the configured retention period, enabling historical analysis.
Use the action directly when you need more control over your workflow.
Note: The GitHub Action currently supports Linux and macOS runners only. For Windows runners (
windows-latest), use Option 3: Manual Installation with the PowerShell install script:irm https://raw.githubusercontent.com/ArmisSecurity/armis-cli/main/scripts/install.ps1 | iex
name: Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- name: Run Armis Security Scan
uses: ArmisSecurity/armis-cli@main
with:
scan-type: repo
api-token: ${{ secrets.ARMIS_API_TOKEN }}
tenant-id: ${{ secrets.ARMIS_TENANT_ID }}
format: sarif
output-file: results.sarif
fail-on: HIGH,CRITICAL
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: results.sarif| Input | Required | Default | Description |
|---|---|---|---|
scan-type |
Yes | Type of scan: repo or image |
|
scan-target |
No | . |
Path for repo, image name for image scan |
api-token |
Yes | Armis API token | |
tenant-id |
Yes | Tenant identifier | |
format |
No | sarif |
Output format: human, json, sarif, junit |
fail-on |
No | CRITICAL |
Severity levels to fail on |
exit-code |
No | 1 |
Exit code when failing |
no-progress |
No | true |
Disable progress indicators |
image-tarball |
No | Path to image tarball (image scans) | |
output-file |
No | File path for results | |
scan-timeout |
No | 60 |
Timeout in minutes |
include-files |
No | Comma-separated file paths to scan | |
build-from-source |
No | false |
Build from source (testing) |
| Output | Description |
|---|---|
results |
Scan results in the specified format |
exit-code |
Exit code from the scan |
For maximum control, install and run the CLI directly:
name: Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- name: Install Armis CLI
run: |
curl -sSL https://raw.githubusercontent.com/ArmisSecurity/armis-cli/main/scripts/install.sh | bash
- name: Run Security Scan
env:
ARMIS_CLIENT_ID: ${{ secrets.ARMIS_CLIENT_ID }}
ARMIS_CLIENT_SECRET: ${{ secrets.ARMIS_CLIENT_SECRET }}
run: |
armis-cli scan repo . \
--format sarif \
--fail-on HIGH,CRITICAL \
> results.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: results.sarifScan only the files that changed in a PR for faster feedback:
name: PR Security Scan
on:
pull_request:
branches: [main]
permissions:
contents: read
security-events: write
pull-requests: write
jobs:
get-changed-files:
runs-on: ubuntu-latest
outputs:
files: ${{ steps.changed-files.outputs.all_changed_files }}
any_changed: ${{ steps.changed-files.outputs.any_changed }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v46
with:
separator: ','
# Exclude test files from security scan
files_ignore: |
**/*_test.go
**/testdata/**
security-scan:
needs: get-changed-files
if: needs.get-changed-files.outputs.any_changed == 'true'
uses: ArmisSecurity/armis-cli/.github/workflows/reusable-security-scan.yml@main
with:
fail-on: 'CRITICAL,HIGH'
include-files: ${{ needs.get-changed-files.outputs.files }}
secrets:
api-token: ${{ secrets.ARMIS_API_TOKEN }}
tenant-id: ${{ secrets.ARMIS_TENANT_ID }}Key points:
- Uses
tj-actions/changed-filesto detect modified files - Passes changed files via
include-filesinput - Only runs if files actually changed
- Excludes test files that may contain intentional security test patterns
Run comprehensive scans on a schedule for ongoing monitoring:
name: Scheduled Security Scan
on:
workflow_dispatch: # Manual trigger
schedule:
- cron: '0 6 * * *' # Daily at 06:00 UTC
permissions:
contents: read
security-events: write
jobs:
scan:
uses: ArmisSecurity/armis-cli/.github/workflows/reusable-security-scan.yml@main
with:
fail-on: '' # Don't fail - monitoring only
pr-comment: false # No PR context
upload-artifact: true
scan-timeout: 120 # Allow more time for full scan
secrets:
api-token: ${{ secrets.ARMIS_API_TOKEN }}
tenant-id: ${{ secrets.ARMIS_TENANT_ID }}Key points:
- Set
fail-onto empty string for monitoring without blocking - Disable PR comments since there's no PR context
- Increase timeout for comprehensive scans
- Results still uploaded to GitHub Code Scanning
Generate Software Bill of Materials and VEX documents for compliance and supply chain security:
name: Security Scan with SBOM/VEX
on:
push:
branches: [main]
permissions:
contents: read
security-events: write
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Armis CLI
run: |
curl -sSL https://raw.githubusercontent.com/ArmisSecurity/armis-cli/main/scripts/install.sh | bash
- name: Run Security Scan with SBOM/VEX
env:
ARMIS_CLIENT_ID: ${{ secrets.ARMIS_CLIENT_ID }}
ARMIS_CLIENT_SECRET: ${{ secrets.ARMIS_CLIENT_SECRET }}
run: |
armis-cli scan repo . \
--format sarif \
--sbom --vex \
--sbom-output ./artifacts/sbom.json \
--vex-output ./artifacts/vex.json \
> results.sarif
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: results.sarif
- name: Upload SBOM/VEX Artifacts
uses: actions/upload-artifact@v4
if: always()
with:
name: sbom-vex-${{ github.sha }}
path: ./artifacts/
retention-days: 90Key points:
- SBOM and VEX are generated server-side during the scan
- Files are downloaded after scan completion
- Store artifacts for compliance and audit purposes
- VEX helps prioritize vulnerabilities that are actually exploitable
name: Build and Scan Image
on:
push:
branches: [main]
jobs:
build-and-scan:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
steps:
- uses: actions/checkout@v4
- name: Build Docker Image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Armis Image Scan
uses: ArmisSecurity/armis-cli@main
with:
scan-type: image
scan-target: myapp:${{ github.sha }}
api-token: ${{ secrets.ARMIS_API_TOKEN }}
tenant-id: ${{ secrets.ARMIS_TENANT_ID }}
format: sarif
output-file: image-results.sarif
fail-on: CRITICAL,HIGH
- name: Upload SARIF
uses: github/codeql-action/upload-sarif@v4
if: always()
with:
sarif_file: image-results.sarif
category: container-scanControl how images are fetched before scanning with the --pull flag:
| Policy | Use Case |
|---|---|
always |
CI/CD - ensures latest image from registry |
missing |
Development - saves time by reusing local images (default) |
never |
Air-gapped - requires pre-pulled images |
For CI/CD pipelines scanning remote images, use --pull=always to ensure you're scanning the latest version:
- name: Scan container image
run: |
armis-cli scan image ${{ env.IMAGE_TAG }} \
--pull=always \
--fail-on CRITICAL \
--format sarifFor images built in a previous job or CI step:
- name: Save Image as Tarball
run: docker save myapp:latest -o image.tar
- name: Scan Image Tarball
uses: ArmisSecurity/armis-cli@main
with:
scan-type: image
image-tarball: image.tar
api-token: ${{ secrets.ARMIS_API_TOKEN }}
tenant-id: ${{ secrets.ARMIS_TENANT_ID }}stages:
- security
security-scan:
stage: security
image: alpine:latest
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
before_script:
- apk add --no-cache curl bash
- curl -sSL https://raw.githubusercontent.com/ArmisSecurity/armis-cli/main/scripts/install.sh | bash
script:
- armis-cli scan repo . --format json --fail-on CRITICAL
variables:
ARMIS_CLIENT_ID: $ARMIS_CLIENT_ID
ARMIS_CLIENT_SECRET: $ARMIS_CLIENT_SECRETConfigure credentials as protected CI/CD variables.
pipeline {
agent any
environment {
ARMIS_CLIENT_ID = credentials('armis-client-id')
ARMIS_CLIENT_SECRET = credentials('armis-client-secret')
}
stages {
stage('Security Scan') {
steps {
sh '''
curl -sSL https://raw.githubusercontent.com/ArmisSecurity/armis-cli/main/scripts/install.sh | bash
armis-cli scan repo . \
--format junit \
--fail-on HIGH,CRITICAL \
> scan-results.xml
'''
junit 'scan-results.xml'
}
}
}
post {
always {
archiveArtifacts artifacts: 'scan-results.xml', allowEmptyArchive: true
}
}
}Configure credentials using Jenkins Credentials.
pipeline {
agent { label 'windows' }
environment {
ARMIS_CLIENT_ID = credentials('armis-client-id')
ARMIS_CLIENT_SECRET = credentials('armis-client-secret')
}
stages {
stage('Security Scan') {
steps {
powershell '''
irm https://raw.githubusercontent.com/ArmisSecurity/armis-cli/main/scripts/install.ps1 | iex
armis-cli scan repo . `
--format junit `
--fail-on HIGH,CRITICAL `
> scan-results.xml
'''
junit 'scan-results.xml'
}
}
}
post {
always {
archiveArtifacts artifacts: 'scan-results.xml', allowEmptyArchive: true
}
}
}trigger:
- main
pool:
vmImage: 'ubuntu-latest'
variables:
- group: armis-credentials # Contains ARMIS_CLIENT_ID and ARMIS_CLIENT_SECRET
steps:
- script: |
curl -sSL https://raw.githubusercontent.com/ArmisSecurity/armis-cli/main/scripts/install.sh | bash
displayName: 'Install Armis CLI'
- script: |
armis-cli scan repo . \
--format junit \
--fail-on HIGH,CRITICAL \
> $(Build.ArtifactStagingDirectory)/scan-results.xml
displayName: 'Run Security Scan'
env:
ARMIS_CLIENT_ID: $(ARMIS_CLIENT_ID)
ARMIS_CLIENT_SECRET: $(ARMIS_CLIENT_SECRET)
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/scan-results.xml'
condition: always()Configure secrets using Variable Groups.
trigger:
- main
pool:
vmImage: 'windows-latest'
variables:
- group: armis-credentials
steps:
- powershell: |
irm https://raw.githubusercontent.com/ArmisSecurity/armis-cli/main/scripts/install.ps1 | iex
armis-cli scan repo . `
--format junit `
--fail-on HIGH,CRITICAL `
> $(Build.ArtifactStagingDirectory)\scan-results.xml
displayName: 'Install Armis CLI and Run Security Scan'
env:
ARMIS_CLIENT_ID: $(ARMIS_CLIENT_ID)
ARMIS_CLIENT_SECRET: $(ARMIS_CLIENT_SECRET)
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/scan-results.xml'
condition: always()version: 2.1
jobs:
security-scan:
docker:
- image: cimg/base:stable
steps:
- checkout
- run:
name: Install Armis CLI
command: |
curl -sSL https://raw.githubusercontent.com/ArmisSecurity/armis-cli/main/scripts/install.sh | bash
- run:
name: Run Security Scan
command: |
armis-cli scan repo . \
--format json \
--fail-on HIGH,CRITICAL
workflows:
version: 2
security:
jobs:
- security-scan:
context: armis-credentials # Contains ARMIS_CLIENT_ID, ARMIS_CLIENT_SECRETConfigure secrets using Contexts.
pipelines:
pull-requests:
'**':
- step:
name: Security Scan
image: alpine:latest
script:
- apk add --no-cache curl bash
- curl -sSL https://raw.githubusercontent.com/ArmisSecurity/armis-cli/main/scripts/install.sh | bash
- armis-cli scan repo . --format json --fail-on CRITICAL
branches:
main:
- step:
name: Security Scan
image: alpine:latest
script:
- apk add --no-cache curl bash
- curl -sSL https://raw.githubusercontent.com/ArmisSecurity/armis-cli/main/scripts/install.sh | bash
- armis-cli scan repo . --format json --fail-on CRITICALConfigure ARMIS_CLIENT_ID and ARMIS_CLIENT_SECRET as secured repository variables.
| Format | Best For | CI Integration |
|---|---|---|
sarif |
GitHub, VS Code | GitHub Code Scanning, IDE extensions |
junit |
Jenkins, Azure | Native test result publishing |
json |
Custom processing | Scripts, dashboards, APIs |
human |
Local debugging | Terminal output (not recommended for CI) |
- No valid authentication credentials were provided
- Set
ARMIS_CLIENT_IDandARMIS_CLIENT_SECRETfor JWT auth (recommended), orARMIS_API_TOKENandARMIS_TENANT_IDfor legacy auth
- This only applies to Basic (legacy) authentication
- Provide
--tenant-idalong with--token, or switch to JWT authentication (recommended) where tenant ID is extracted automatically
- If using JWT: ensure
ARMIS_CLIENT_IDandARMIS_CLIENT_SECRETare configured as secrets - If using Basic auth: ensure
ARMIS_API_TOKENis configured as a secret - Check that the secret is accessible to the workflow/job
- Verify the secret name matches exactly (case-sensitive)
- Verify the credentials are valid and not expired
- If using Basic auth, check that the tenant ID matches the token's tenant
- Ensure the credentials have sufficient permissions
- Increase
scan-timeout(default: 60 minutes) - For large repositories, consider using
include-filesto scan specific paths - Check network connectivity to Armis Cloud
- Ensure
security-events: writepermission is set - For private repositories, GitHub Advanced Security must be enabled
- Check that the SARIF file was created successfully
| Code | Meaning |
|---|---|
0 |
Scan completed, no findings above threshold |
1 |
Scan completed, findings exceed fail-on threshold |
>1 |
Scan error (authentication, network, timeout) |
Distinguishing findings from errors: The reusable workflow's "Check for Failures" step differentiates between:
- Scans that failed (timeout, API error) - always fails the workflow
- Scans that found vulnerabilities - fails based on
fail-onsetting
- Never commit credentials to version control
- Use JWT authentication (client ID/secret) for production — it supports automatic token refresh
- Use organization-level secrets when possible for centralized management
- Use environment-specific credentials for production vs development
- Rotate credentials periodically
- Store client ID and client secret as separate secrets
- Grant minimum required permissions to workflows
- Use
permissionsblock to explicitly declare needs - For forked PRs, be aware that secrets may not be available
-
Pin action versions to specific tags or commit SHAs:
# Good: pinned to version uses: ArmisSecurity/armis-cli@v1.0.0 # Better: pinned to commit SHA uses: ArmisSecurity/armis-cli@abc123def456
-
The CLI installation verifies checksums automatically
-
Release binaries include SLSA provenance for verification