Jenkins: Prisma AIRS Model Security
Add pre-deployment AI model scanning to Jenkins Declarative Pipelines using the Prisma AIRS Model Security SDK
This guide integrates Prisma AIRS Model Security: pre-deployment scanning of AI model files (weights, binaries, configs) before they reach production infrastructure. This is different from AI Runtime Security, which scans prompts and responses at inference time.
Model Security uses OAuth2 client credentials (Client ID + Client Secret from a Prisma Cloud Service Account), not an AIRS API key. The authentication flow, SDK, and API endpoints are distinct from the Runtime Security integrations.
| Scanning Phase | Supported | Description |
|---|---|---|
| Model file scan | ✅ | Scans model weights/binaries for security risks before deployment |
| Prompt | N/A | Use an AI Runtime Security integration for prompt scanning |
| Response | N/A | Use an AI Runtime Security integration for response scanning |
| Streaming | N/A | Not applicable to model file scanning |
| Pre-tool call | N/A | Not applicable to model file scanning |
| Post-tool call | N/A | Not applicable to model file scanning |
Architecture Overview
The Jenkins integration adds a security gate to the Declarative Pipeline. When a model configuration change is detected via SCM polling (or manual trigger), the pipeline authenticates with Prisma AIRS via OAuth2, installs the Model Security SDK from a private PyPI repository, and scans the model. A result of ALLOWED permits deployment; BLOCKED fails the pipeline stage and prevents the model from reaching production.
Prisma AIRS Model Security evaluates AI model files for:
- Model provenance and supply chain risks — verifying model source and integrity
- Known vulnerabilities — checking against CVEs and security advisories
- Malicious payloads — detecting embedded malicious code or backdoors in model weights
- Compliance violations — ensuring models meet organizational security policies
- Training data risks — identifying potential data poisoning indicators
Phase 1: Prerequisites
The Declarative Pipeline requires a Jenkins instance with specific plugins and agent tools.
| Requirement | Minimum Version | Purpose |
|---|---|---|
| Jenkins | 2.400+ | Declarative Pipeline support |
| Pipeline plugin | Latest | Pipeline DSL and stage management |
| Credentials plugin | Latest | Secret text and secret file credential types |
| Python | 3.11+ | Model Security SDK and scan script |
curl | Any | OAuth2 token and PyPI URL retrieval |
jq | Any | JSON response parsing in shell scripts |
- Navigate to
Manage Jenkins>Manage Plugins. - Confirm the Pipeline and Credentials plugins are installed.
- On the Jenkins agent, run
python3 --version,curl --version, andjq --versionto confirm tool availability.
Jenkins is running version 2.400+. The Pipeline and Credentials plugins appear in the Installed tab. Python 3.11+, curl, and jq are available on the agent.
Model Security scanning requires an active Prisma AIRS subscription with the Model Security add-on enabled.
- Log in to Strata Cloud Manager.
- Navigate to
AI Access Security. - Confirm the Model Security section is visible and accessible.
If Model Security is not visible, contact the Palo Alto Networks account team to enable the add-on. The Model Security documentation covers licensing prerequisites.
The Model Security dashboard loads and displays scan activity logs (empty is fine for new deployments).
The pipeline authenticates with Prisma AIRS using OAuth2 client credentials from a dedicated service account.
- In Strata Cloud Manager, navigate to
Settings>Identity & Access>Service Accounts. - Click
Add>Add Service Account. - Set Name to a descriptive identifier (e.g.,
jenkins-model-security). - Assign the appropriate role for Model Security API access.
- Click
Save. - Record the Client ID and Client Secret immediately — the secret is shown only once.
- Note the TSG ID (Tenant Service Group ID) from the account details page.
The Client Secret cannot be retrieved after initial creation. Store it in a password manager or secrets vault before navigating away from the page.
The service account appears in the Service Accounts list with status Active. The Client ID, Client Secret, and TSG ID are recorded.
The security profile defines which scan rules and thresholds apply to model evaluations.
- In Strata Cloud Manager, navigate to
AI Access Security>Model Security. - Create or select a Security Profile (also called a Security Group).
- Configure the desired scan rules — malware detection, vulnerability checks, compliance policies.
- Save the profile and record the Profile UUID.
- Note the API Endpoint URL (e.g.,
https://api.sase.paloaltonetworks.com/aims).
The security profile appears in the list with at least one scan rule enabled. The Profile UUID and API Endpoint URL are recorded.
Phase 2: Credential Configuration
Jenkins stores credentials in its built-in Credentials store. The Declarative Pipeline references these by their Credential ID using the withCredentials binding.
- Navigate to
Manage Jenkins>Manage Credentials. - Select the appropriate scope (e.g.,
Jenkins>Global credentials). - Click
Add Credentialsand create each credential listed below.
| Credential ID | Type | Description |
|---|---|---|
model-security-client-id | Secret text | Prisma AIRS OAuth Client ID from Step 1.3 |
model-security-client-secret | Secret text | Prisma AIRS OAuth Client Secret from Step 1.3 |
model-security-profile-id | Secret text | Security Profile UUID from Step 1.4 |
model-security-api-endpoint | Secret text | API endpoint URL (e.g., https://api.sase.paloaltonetworks.com/aims) |
tsg-id | Secret text | Tenant Service Group ID from Step 1.3 |
Ensure credentials are created in a scope accessible to the pipeline job. If using folder-level credentials, the pipeline job must reside within that folder. Global credentials are accessible to all jobs.
These credentials are not the same as PRISMA_AIRS_API_KEY, PRISMA_AIRS_PROFILE_NAME, or PRISMA_AIRS_URL used by AI Runtime Security integrations. Model Security uses OAuth2 client credentials and the Model Security SDK.
All five credentials appear in the Jenkins Credentials list. Each shows the Credential ID, type (Secret text), and description. Navigate to Manage Jenkins > Manage Credentials to confirm.
Phase 3: Pipeline Setup
The integration requires a Jenkinsfile at the repository root, supporting scripts, and a model configuration file.
The reference implementation includes optional deployment scripts (deploy_model.sh, test_model.py, undeploy_model.sh) targeting Google Cloud Vertex AI. Replace these with deployment logic for the target infrastructure.
The file structure is understood. Proceed to create each file in the following steps.
The pipeline requires two scripts: get_pypi_url.sh for OAuth2 authentication and PyPI URL retrieval, and scan_model.py for the model security scan. These scripts are identical to the GitHub Actions versions with one difference: the pipeline label is set to jenkins.
Create scripts/get_pypi_url.sh:
#!/bin/bash
set -euo pipefail
: "${MODEL_SECURITY_CLIENT_ID:?Error: MODEL_SECURITY_CLIENT_ID not set}"
: "${MODEL_SECURITY_CLIENT_SECRET:?Error: MODEL_SECURITY_CLIENT_SECRET not set}"
: "${TSG_ID:?Error: TSG_ID not set}"
API_ENDPOINT="${MODEL_SECURITY_API_ENDPOINT:-https://api.sase.paloaltonetworks.com/aims}"
TOKEN_ENDPOINT="${MODEL_SECURITY_TOKEN_ENDPOINT:-https://auth.apps.paloaltonetworks.com/oauth2/access_token}"
# OAuth2 client credentials flow
TOKEN_RESPONSE=$(curl -sf -X POST "$TOKEN_ENDPOINT" \
-H "Content-Type: application/x-www-form-urlencoded" \
-u "$MODEL_SECURITY_CLIENT_ID:$MODEL_SECURITY_CLIENT_SECRET" \
-d "grant_type=client_credentials&scope=tsg_id:$TSG_ID") || {
echo "Error: Failed to obtain SCM access token" >&2
exit 1
}
SCM_TOKEN=$(echo "$TOKEN_RESPONSE" | jq -r '.access_token')
if [[ -z "$SCM_TOKEN" || "$SCM_TOKEN" == "null" ]]; then
echo "Error: Failed to extract access token" >&2
exit 1
fi
PYPI_RESPONSE=$(curl -sf -X GET "$API_ENDPOINT/mgmt/v1/pypi/authenticate" \
-H "Authorization: Bearer $SCM_TOKEN") || {
echo "Error: Failed to retrieve PyPI URL" >&2
exit 1
}
PYPI_URL=$(echo "$PYPI_RESPONSE" | jq -r '.url')
if [[ -z "$PYPI_URL" || "$PYPI_URL" == "null" ]]; then
echo "Error: Failed to extract PyPI URL" >&2
exit 1
fi
echo "$PYPI_URL"
Create scripts/scan_model.py with the same content as the GitHub Actions scan script, changing the pipeline label:
labels = {
"deployment_target": "vertex_ai",
"machine_type": config["deployment"]["machine_type"],
"version": config["model"].get("version", "unknown").replace(".", "-"),
"pipeline": "jenkins", # Identifies scan origin in SCM dashboard
}
Make the shell script executable:
chmod +x scripts/get_pypi_url.sh
Both scripts exist in the scripts/ directory. get_pypi_url.sh is executable. scan_model.py contains "pipeline": "jenkins" in the labels dictionary.
The Jenkinsfile defines a Declarative Pipeline with three stages: change detection, security scan, and deployment. The scan stage uses withCredentials to inject secrets as environment variables.
Create Jenkinsfile at the repository root:
pipeline {
agent any
options {
timestamps()
timeout(time: 60, unit: 'MINUTES')
disableConcurrentBuilds()
}
triggers {
pollSCM('H/5 * * * *') // Poll for changes every 5 minutes
}
parameters {
booleanParam(
name: 'FORCE_RUN',
defaultValue: false,
description: 'Run the pipeline even if model config has not changed'
)
booleanParam(
name: 'SKIP_DEPLOY',
defaultValue: false,
description: 'Run security scan only, skip deployment'
)
}
environment {
CONFIG_FILE = 'config/model-config.yaml'
}
stages {
stage('Detect Changes') {
steps {
script {
env.MODEL_CHANGED = 'false'
if (params.FORCE_RUN) {
echo 'FORCE_RUN enabled — skipping change detection.'
env.MODEL_CHANGED = 'true'
return
}
def changes = currentBuild.changeSets.collectMany {
it.items.collectMany { item ->
item.affectedFiles.collect { it.path }
}
}
if (changes.any { it == 'config/model-config.yaml' }) {
echo 'Model config changed — proceeding with scan.'
env.MODEL_CHANGED = 'true'
} else {
echo 'No model config changes detected.'
}
}
}
}
// ── Prisma AIRS Model Security Scan ──────────────────
stage('Prisma AIRS Model Security Scan') {
when {
expression { env.MODEL_CHANGED == 'true' }
}
steps {
withCredentials([
string(credentialsId: 'model-security-client-id',
variable: 'MODEL_SECURITY_CLIENT_ID'),
string(credentialsId: 'model-security-client-secret',
variable: 'MODEL_SECURITY_CLIENT_SECRET'),
string(credentialsId: 'model-security-api-endpoint',
variable: 'MODEL_SECURITY_API_ENDPOINT'),
string(credentialsId: 'model-security-profile-id',
variable: 'MODEL_SECURITY_PROFILE_ID'),
string(credentialsId: 'tsg-id',
variable: 'TSG_ID'),
]) {
sh '''
echo "=== Setting up Python environment ==="
python3 -m venv .venv
. .venv/bin/activate
echo "=== Installing base dependencies ==="
pip install --quiet -r requirements.txt
echo "=== Authenticating with Prisma AIRS ==="
PYPI_URL=$(bash scripts/get_pypi_url.sh)
echo "=== Installing Model Security SDK ==="
pip install --quiet "model-security-client[all]" \
--extra-index-url "${PYPI_URL}"
echo "=== Running Model Security Scan ==="
python scripts/scan_model.py \
--config ${CONFIG_FILE}
'''
}
}
}
// ── Deploy (replace with your infrastructure) ────────
stage('Deploy Model to Vertex AI') {
when {
allOf {
expression { env.MODEL_CHANGED == 'true' }
expression { !params.SKIP_DEPLOY }
expression {
env.GIT_BRANCH == 'main' ||
env.GIT_BRANCH == 'origin/main'
}
}
}
steps {
sh 'echo "Add your deployment logic here"'
}
}
}
// NOTE: The reference implementation includes a full 'Deploy Model to
// Vertex AI' stage with GCP service account auth, gcloud CLI setup,
// and a deploy_model.sh script, plus a 'Test Model Endpoint' stage.
// See the source repository for the complete Jenkinsfile.
post {
success { echo 'Pipeline completed successfully.' }
failure { echo 'Pipeline failed. Check logs above.' }
always { sh 'rm -rf .venv || true' }
}
}
The deploy stage above is a placeholder. The reference implementation includes a full Deploy Model to Vertex AI stage with GCP service account authentication, gcloud CLI setup, and a deploy_model.sh deployment script, plus a Test Model Endpoint validation stage. Replace the placeholder with deployment logic for the target infrastructure.
The pipeline exposes two parameters: FORCE_RUN bypasses change detection and runs the scan unconditionally; SKIP_DEPLOY runs the scan without triggering deployment (useful for testing scan rules). Access these via Build with Parameters in the Jenkins UI.
The post.always block removes the Python virtual environment (.venv) after every build to prevent stale SDK versions from persisting on the agent. The SDK is reinstalled fresh on each run from the private PyPI.
The Jenkinsfile exists at the repository root. Create a Pipeline job in Jenkins, point it to the repository, and run Build with Parameters > FORCE_RUN: true. The Detect Changes stage should log FORCE_RUN enabled.
Phase 4: Scan Configuration
The model configuration file specifies which model to scan. Changes to this file are detected by SCM polling, triggering the pipeline.
Create config/model-config.yaml:
# Model Configuration
# Changes to this file are detected by Jenkins SCM polling.
model:
huggingface_id: "google/gemma-3-1b-it"
display_name: "gemma-3-1b-it"
description: "Google Gemma 3 1B Instruction-Tuned model"
version: "1.0"
security:
scan_enabled: true
security_profile_id: "" # Set via MODEL_SECURITY_PROFILE_ID credential
The file config/model-config.yaml passes YAML validation. Running python3 -c "import yaml; yaml.safe_load(open('config/model-config.yaml'))" produces no errors.
Create requirements.txt with the base dependencies. The Model Security SDK is installed separately from the private PyPI.
pyyaml>=6.0
python-dotenv>=1.0
The file requirements.txt exists with pyyaml and python-dotenv listed.
The Jenkinsfile includes a pollSCM trigger that checks the repository for changes every 5 minutes. Adjust the cron expression in the Jenkinsfile to match the desired polling interval.
triggers {
pollSCM('H/5 * * * *') // Every 5 minutes (H adds distributed jitter)
}
| Cron Expression | Interval | Use Case |
|---|---|---|
H/5 * * * * | Every 5 minutes | Active development with frequent model changes |
H/15 * * * * | Every 15 minutes | Standard development cadence |
H * * * * | Every hour | Low-frequency model updates |
The H prefix adds distributed jitter to prevent all Jenkins jobs from polling simultaneously. Replace H with a specific minute (e.g., */5) only if precise timing is required.
In the Jenkins job configuration page, the Poll SCM trigger shows the configured schedule. The Polling Log link (on the job page) shows recent poll activity.
Phase 5: Verification
Trigger the pipeline using one of three methods.
Option A: Push a Configuration Change
- Edit
config/model-config.yaml(e.g., update theversionfield). - Commit and push. The next SCM poll cycle triggers the pipeline.
Option B: Manual Build with FORCE_RUN
- Open the Jenkins job page.
- Click
Build with Parameters. - Check
FORCE_RUNand clickBuild.
Option C: Scan-Only Mode
- Click
Build with Parameters. - Check both
FORCE_RUNandSKIP_DEPLOY. - Click
Build— the scan runs but the Deploy stage is skipped.
A new build appears in the Build History sidebar. The build is In Progress or Queued.
The scan script produces structured output in the Jenkins console log.
- Open the build from the
Build Historysidebar. - Click
Console Output. - Locate the
=== Running Model Security Scan ===section. - Find the scan results block.
Expected output for a passing scan:
================================================================
PRISMA AIRS MODEL SECURITY SCAN RESULTS
================================================================
Model: google/gemma-3-1b-it
Scan UUID: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Outcome: EvalOutcome.ALLOWED
Summary: Model passed all security checks
...
================================================================
ALLOWED: Model approved by Prisma AIRS security policy.
The model is cleared for deployment.
The build shows a blue ball (success). The console output contains ALLOWED: Model approved by Prisma AIRS security policy and a valid Scan UUID.
All scan results are recorded in Strata Cloud Manager regardless of the pipeline outcome.
- Log in to Strata Cloud Manager.
- Navigate to
AI Access Security>Model Security. - Locate the scan entry matching the Scan UUID from the Jenkins console.
- Confirm the outcome, rule evaluations, and model details.
Scan results include the pipeline: jenkins label, enabling filtering to isolate Jenkins-originated scans from other sources.
The scan entry appears in the Model Security activity log with the correct model name, outcome, and jenkins pipeline label.
Troubleshooting
| Issue | Cause | Resolution |
|---|---|---|
get_pypi_url.sh fails with Failed to obtain SCM access token |
Invalid Client ID, Client Secret, or TSG ID | Verify the model-security-client-id, model-security-client-secret, and tsg-id credentials in Jenkins match the values from the service account. |
| SDK installation fails | Private PyPI URL was not retrieved in the previous step | Check the Authenticating with Prisma AIRS output in the console log. If the PyPI URL step failed, the SDK cannot be installed. |
Scan returns unexpected BLOCKED |
Security profile rules are more restrictive than expected | Verify model-security-profile-id points to the intended profile. Review rules in Strata Cloud Manager. |
| Credentials not found | Credentials are in a scope not accessible to the pipeline job | Verify credentials are in the correct Jenkins scope (global or folder-level). The pipeline job must be within the credential scope. |
| SCM polling not triggering | Poll schedule misconfigured or repository URL inaccessible | Check the pollSCM schedule in the Jenkinsfile (H/5 * * * *). Verify the repository URL in the job configuration is correct and reachable from the Jenkins server. |
python3: command not found |
Python is not installed or not in PATH on the Jenkins agent | Install Python 3.11+ on the agent. Alternatively, use a Docker agent with Python pre-installed: agent { docker { image 'python:3.11-slim' } }. |
The troubleshooting table covers the issue encountered. If the issue persists, collect the full Jenkins console output, the Scan UUID, and the Jenkins version for a support case with Palo Alto Networks.