Jenkins: Prisma AIRS Model Security

Add pre-deployment AI model scanning to Jenkins Declarative Pipelines using the Prisma AIRS Model Security SDK

Model Security, Not Runtime Security

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 PhaseSupportedDescription
Model file scan✅Scans model weights/binaries for security risks before deployment
PromptN/AUse an AI Runtime Security integration for prompt scanning
ResponseN/AUse an AI Runtime Security integration for response scanning
StreamingN/ANot applicable to model file scanning
Pre-tool callN/ANot applicable to model file scanning
Post-tool callN/ANot 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.

SCM Poll model-config.yaml Jenkins Pipeline Detect Changes Python venv Install SDK OAuth2 Auth Client ID + Secret Token Endpoint Private PyPI URL AIRS Model Scan Malware detection Vulnerability check Supply chain risk Compliance review ALLOWED Deploy BLOCKED Build fails Non-main branches: scan only (no deploy) | Main branch: scan + deploy on ALLOWED | FORCE_RUN: bypass change detection
What Gets Scanned

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

▶ Step 1.1: Confirm Jenkins Server Requirements

The Declarative Pipeline requires a Jenkins instance with specific plugins and agent tools.

RequirementMinimum VersionPurpose
Jenkins2.400+Declarative Pipeline support
Pipeline pluginLatestPipeline DSL and stage management
Credentials pluginLatestSecret text and secret file credential types
Python3.11+Model Security SDK and scan script
curlAnyOAuth2 token and PyPI URL retrieval
jqAnyJSON response parsing in shell scripts
  1. Navigate to Manage Jenkins > Manage Plugins.
  2. Confirm the Pipeline and Credentials plugins are installed.
  3. On the Jenkins agent, run python3 --version, curl --version, and jq --version to confirm tool availability.
Verification

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.

▶ Step 1.2: Confirm Prisma AIRS Model Security License

Model Security scanning requires an active Prisma AIRS subscription with the Model Security add-on enabled.

  1. Log in to Strata Cloud Manager.
  2. Navigate to AI Access Security.
  3. Confirm the Model Security section is visible and accessible.
Note

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.

Verification

The Model Security dashboard loads and displays scan activity logs (empty is fine for new deployments).

▶ Step 1.3: Create a Prisma Cloud Service Account

The pipeline authenticates with Prisma AIRS using OAuth2 client credentials from a dedicated service account.

  1. In Strata Cloud Manager, navigate to Settings > Identity & Access > Service Accounts.
  2. Click Add > Add Service Account.
  3. Set Name to a descriptive identifier (e.g., jenkins-model-security).
  4. Assign the appropriate role for Model Security API access.
  5. Click Save.
  6. Record the Client ID and Client Secret immediately — the secret is shown only once.
  7. Note the TSG ID (Tenant Service Group ID) from the account details page.
Store Credentials Securely

The Client Secret cannot be retrieved after initial creation. Store it in a password manager or secrets vault before navigating away from the page.

Verification

The service account appears in the Service Accounts list with status Active. The Client ID, Client Secret, and TSG ID are recorded.

▶ Step 1.4: Configure a Model Security Profile

The security profile defines which scan rules and thresholds apply to model evaluations.

  1. In Strata Cloud Manager, navigate to AI Access Security > Model Security.
  2. Create or select a Security Profile (also called a Security Group).
  3. Configure the desired scan rules — malware detection, vulnerability checks, compliance policies.
  4. Save the profile and record the Profile UUID.
  5. Note the API Endpoint URL (e.g., https://api.sase.paloaltonetworks.com/aims).
Verification

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

▶ Step 2.1: Create Jenkins Credentials for Model Security

Jenkins stores credentials in its built-in Credentials store. The Declarative Pipeline references these by their Credential ID using the withCredentials binding.

  1. Navigate to Manage Jenkins > Manage Credentials.
  2. Select the appropriate scope (e.g., Jenkins > Global credentials).
  3. Click Add Credentials and create each credential listed below.
Credential IDTypeDescription
model-security-client-idSecret textPrisma AIRS OAuth Client ID from Step 1.3
model-security-client-secretSecret textPrisma AIRS OAuth Client Secret from Step 1.3
model-security-profile-idSecret textSecurity Profile UUID from Step 1.4
model-security-api-endpointSecret textAPI endpoint URL (e.g., https://api.sase.paloaltonetworks.com/aims)
tsg-idSecret textTenant Service Group ID from Step 1.3
Credential Scope Matters

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.

Different From Runtime Security Credentials

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.

Verification

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

▶ Step 3.1: Understand the Repository Structure

The integration requires a Jenkinsfile at the repository root, supporting scripts, and a model configuration file.

. ├── Jenkinsfile # Declarative Pipeline definition ├── config/ │ └── model-config.yaml # Model configuration (pipeline trigger) ├── scripts/ │ ├── scan_model.py # Prisma AIRS Model Security scan script │ └── get_pypi_url.sh # Private PyPI authentication script ├── requirements.txt # Python dependencies (pyyaml, python-dotenv) └── README.md
Note

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.

Verification

The file structure is understood. Proceed to create each file in the following steps.

▶ Step 3.2: Create the Supporting Scripts

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
Verification

Both scripts exist in the scripts/ directory. get_pypi_url.sh is executable. scan_model.py contains "pipeline": "jenkins" in the labels dictionary.

▶ Step 3.3: Create the Jenkinsfile

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' }
    }
}
Deploy Stage Simplified

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.

Pipeline Parameters

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.

Virtual Environment Cleanup

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.

Verification

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

▶ Step 4.1: Create the Model Configuration File

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
Verification

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.

▶ Step 4.2: Create the Python Dependencies File

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
Verification

The file requirements.txt exists with pyyaml and python-dotenv listed.

▶ Step 4.3: Configure SCM Polling

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 ExpressionIntervalUse Case
H/5 * * * *Every 5 minutesActive development with frequent model changes
H/15 * * * *Every 15 minutesStandard development cadence
H * * * *Every hourLow-frequency model updates
Note

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.

Verification

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

▶ Step 5.1: Trigger the Pipeline

Trigger the pipeline using one of three methods.

Option A: Push a Configuration Change

  1. Edit config/model-config.yaml (e.g., update the version field).
  2. Commit and push. The next SCM poll cycle triggers the pipeline.

Option B: Manual Build with FORCE_RUN

  1. Open the Jenkins job page.
  2. Click Build with Parameters.
  3. Check FORCE_RUN and click Build.

Option C: Scan-Only Mode

  1. Click Build with Parameters.
  2. Check both FORCE_RUN and SKIP_DEPLOY.
  3. Click Build — the scan runs but the Deploy stage is skipped.
Verification

A new build appears in the Build History sidebar. The build is In Progress or Queued.

▶ Step 5.2: Verify Scan Results in Jenkins Console

The scan script produces structured output in the Jenkins console log.

  1. Open the build from the Build History sidebar.
  2. Click Console Output.
  3. Locate the === Running Model Security Scan === section.
  4. 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.
Verification

The build shows a blue ball (success). The console output contains ALLOWED: Model approved by Prisma AIRS security policy and a valid Scan UUID.

▶ Step 5.3: Verify Results in Strata Cloud Manager

All scan results are recorded in Strata Cloud Manager regardless of the pipeline outcome.

  1. Log in to Strata Cloud Manager.
  2. Navigate to AI Access Security > Model Security.
  3. Locate the scan entry matching the Scan UUID from the Jenkins console.
  4. Confirm the outcome, rule evaluations, and model details.
Note

Scan results include the pipeline: jenkins label, enabling filtering to isolate Jenkins-originated scans from other sources.

Verification

The scan entry appears in the Model Security activity log with the correct model name, outcome, and jenkins pipeline label.

Troubleshooting

▶ Common Issues and Resolutions
IssueCauseResolution
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' } }.
Verification

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.