GitHub Actions: Prisma AIRS Model Security
Add pre-deployment AI model scanning to GitHub Actions CI/CD 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 covered in the other AIRS guides.
| 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 GitHub Actions integration adds a security gate to the CI/CD pipeline. When an AI model configuration changes, the workflow authenticates with Prisma AIRS via OAuth2, installs the Model Security SDK from a private PyPI repository, and scans the model. A scan result of ALLOWED permits deployment; BLOCKED halts the pipeline 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 GitHub Actions workflow requires a repository with Actions enabled and sufficient permissions to create secrets.
- Open the target GitHub repository.
- Navigate to
Settings>Actions>General. - Confirm Actions permissions is set to
Allow all actions and reusable workflows(or a policy that permits the required actions). - Confirm the account has
AdminorMaintainrole on the repository (required to create repository secrets).
The Settings > Actions page loads without permission errors, and the Secrets and variables submenu is visible in the left sidebar.
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 CI/CD 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.,
github-actions-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. The scan script references this profile by its UUID.
- 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 from the profile details.
- 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
GitHub Actions secrets store the OAuth2 credentials and scan configuration. The workflow references these secrets as environment variables at runtime.
- Open the GitHub repository and navigate to
Settings>Secrets and variables>Actions. - Click
New repository secretand create each secret listed below.
| Secret Name | Value | Description |
|---|---|---|
MODEL_SECURITY_CLIENT_ID | Client ID from Step 1.3 | OAuth2 client ID for Prisma AIRS authentication |
MODEL_SECURITY_CLIENT_SECRET | Client Secret from Step 1.3 | OAuth2 client secret for Prisma AIRS authentication |
MODEL_SECURITY_PROFILE_ID | Profile UUID from Step 1.4 | Security profile UUID that defines scan rules |
MODEL_SECURITY_API_ENDPOINT | API Endpoint from Step 1.4 | Prisma AIRS API base URL |
TSG_ID | TSG ID from Step 1.3 | Tenant Service Group identifier |
These secrets 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 communicates through the Model Security SDK, not the Runtime Scan API.
All five secrets appear in the Repository secrets list with Updated timestamps. Each shows as a masked value; exact values are never displayed after creation.
Phase 3: Workflow Setup
The integration requires three categories of files: the GitHub Actions workflow definition, the authentication and scan scripts, and a model configuration file that triggers the pipeline.
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 (AWS SageMaker, Azure ML, on-premises, etc.).
The file structure is understood. Proceed to create each file in the following steps.
The Model Security SDK is distributed through a private PyPI repository. This script authenticates with the Prisma AIRS token endpoint to obtain an access token, then uses that token to retrieve the private PyPI URL.
Create scripts/get_pypi_url.sh:
#!/bin/bash
#
# Model Security Private PyPI Authentication Script
# Authenticates with SCM and retrieves PyPI repository URL
#
set -euo pipefail
# Check required environment variables
: "${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}"
# Set default endpoints
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}"
# Get SCM access token via OAuth2 client credentials
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 from response" >&2
exit 1
fi
# Use access token to retrieve private PyPI URL
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 from response" >&2
exit 1
fi
echo "$PYPI_URL"
Make the script executable:
chmod +x scripts/get_pypi_url.sh
The script performs two API calls: (1) POST to the token endpoint at https://auth.apps.paloaltonetworks.com/oauth2/access_token with client credentials to get an access token, then (2) GET to the API endpoint's /mgmt/v1/pypi/authenticate path using that token to retrieve the private PyPI URL. The jq tool must be available on the runner.
The file scripts/get_pypi_url.sh exists, is executable, and contains the OAuth2 authentication logic.
The scan script loads the model configuration, initializes the Model Security SDK client, and calls the scan API. A result of BLOCKED causes a non-zero exit, which fails the GitHub Actions job and prevents deployment.
Create scripts/scan_model.py:
#!/usr/bin/env python3
"""
Prisma AIRS Model Security Scanner
Scans an AI model against Palo Alto Prisma AIRS security policies
using the official Model Security SDK. Exits non-zero if the model
fails the security assessment, blocking the CI/CD pipeline.
"""
import argparse
import os
import sys
import yaml
from dotenv import load_dotenv
from model_security_client.api import ModelSecurityAPIClient
load_dotenv()
def print_scan_results(result, model_name):
"""Pretty-print the scan results for CI log visibility."""
print()
print("=" * 64)
print(" PRISMA AIRS MODEL SECURITY SCAN RESULTS")
print("=" * 64)
print(f" Model: {model_name}")
print(f" Scan UUID: {getattr(result, 'uuid', 'N/A')}")
print(f" Outcome: {result.eval_outcome}")
print(f" Summary: {getattr(result, 'eval_summary', 'N/A')}")
print(f" Sec. Group: {getattr(result, 'security_group_name', 'N/A')}")
print(f" Rules: {getattr(result, 'enabled_rule_count_snapshot', 'N/A')}")
print(f" Files Scanned:{getattr(result, 'total_files_scanned', 'N/A')}")
print(f" Files Skipped:{getattr(result, 'total_files_skipped', 'N/A')}")
violations = (
getattr(result, 'rule_violations', None)
or getattr(result, 'findings', [])
)
if violations:
print(f" Violations: {len(violations)}")
for v in violations:
severity = getattr(v, 'severity', getattr(v, 'level', 'unknown'))
desc = getattr(v, 'description', getattr(v, 'rule_name', 'No description'))
print(f" [{str(severity).upper()}] {desc}")
print("=" * 64)
print()
def main():
parser = argparse.ArgumentParser(
description="Scan an AI model with Prisma AIRS Model Security"
)
parser.add_argument(
"--config", required=True,
help="Path to the model configuration YAML file",
)
args = parser.parse_args()
with open(args.config) as f:
config = yaml.safe_load(f)
if not config.get("security", {}).get("scan_enabled", True):
print("Security scan is disabled in model config. Skipping.")
sys.exit(0)
# Allow overriding security_profile_id from environment variable
profile_id = os.environ.get("MODEL_SECURITY_PROFILE_ID")
if profile_id:
config.setdefault("security", {})["security_profile_id"] = profile_id
security_group_uuid = config["security"]["security_profile_id"]
if not security_group_uuid:
print("ERROR: No security_profile_id configured.")
sys.exit(1)
model_name = config["model"]["huggingface_id"]
model_uri = f"https://huggingface.co/{model_name}"
api_endpoint = os.environ.get(
"MODEL_SECURITY_API_ENDPOINT",
"https://api.sase.paloaltonetworks.com/aims",
)
print("Initializing Prisma AIRS Model Security SDK...")
client = ModelSecurityAPIClient(base_url=api_endpoint)
labels = {
"deployment_target": "vertex_ai",
"machine_type": config["deployment"]["machine_type"],
"version": config["model"].get("version", "unknown").replace(".", "-"),
"pipeline": "github-actions",
}
print(f"Scanning model: {model_name}")
print(f" URI: {model_uri}")
print(f" Security Group: {security_group_uuid}")
print(f" Labels: {labels}")
result = client.scan(
security_group_uuid=security_group_uuid,
model_uri=model_uri,
labels=labels,
)
print_scan_results(result, model_name)
outcome = str(result.eval_outcome)
if "BLOCKED" in outcome:
print("BLOCKED: Model blocked by Prisma AIRS security policy.")
print("The model will NOT be deployed.")
sys.exit(1)
elif "ALLOWED" in outcome:
print("ALLOWED: Model approved by Prisma AIRS security policy.")
print("The model is cleared for deployment.")
else:
print(f"WARNING: Unexpected scan outcome '{outcome}'. Blocking deployment.")
sys.exit(1)
if __name__ == "__main__":
main()
The file scripts/scan_model.py exists. Running python scripts/scan_model.py --help outputs the argument parser usage without errors (assuming dependencies are installed).
The workflow file defines the CI/CD pipeline. It triggers on changes to the model configuration file, runs the security scan, and conditionally deploys the model.
Create .github/workflows/model-security-scan.yml:
name: Model Security Scan & Deploy
on:
push:
branches: [main]
paths:
- 'config/model-config.yaml'
pull_request:
branches: [main]
paths:
- 'config/model-config.yaml'
workflow_dispatch:
jobs:
# Detect whether the model config file changed
detect-changes:
name: Detect Model Changes
runs-on: ubuntu-latest
outputs:
model_changed: ${{ steps.filter.outputs.model }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
model:
- 'config/model-config.yaml'
# ── Prisma AIRS Model Security Scan ──────────────────────────
# This job authenticates with Prisma AIRS via OAuth2,
# installs the Model Security SDK from a private PyPI,
# and scans the model. BLOCKED = pipeline failure.
security-scan:
name: Prisma AIRS Model Security Scan
needs: detect-changes
if: >
needs.detect-changes.outputs.model_changed == 'true'
|| github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.11'
# Authenticate and get private PyPI URL
- name: Get Model Security SDK PyPI URL
id: pypi
env:
MODEL_SECURITY_CLIENT_ID: ${{ secrets.MODEL_SECURITY_CLIENT_ID }}
MODEL_SECURITY_CLIENT_SECRET: ${{ secrets.MODEL_SECURITY_CLIENT_SECRET }}
MODEL_SECURITY_API_ENDPOINT: ${{ secrets.MODEL_SECURITY_API_ENDPOINT }}
TSG_ID: ${{ secrets.TSG_ID }}
run: echo "url=$(bash scripts/get_pypi_url.sh)" >> "$GITHUB_OUTPUT"
# Install SDK from private PyPI
- name: Install dependencies
run: |
pip install -r requirements.txt
pip install "model-security-client[all]" \
--extra-index-url "${{ steps.pypi.outputs.url }}"
# Run the scan — exits non-zero on BLOCKED
- name: Run Prisma AIRS Model Security Scan
env:
MODEL_SECURITY_CLIENT_ID: ${{ secrets.MODEL_SECURITY_CLIENT_ID }}
MODEL_SECURITY_CLIENT_SECRET: ${{ secrets.MODEL_SECURITY_CLIENT_SECRET }}
MODEL_SECURITY_API_ENDPOINT: ${{ secrets.MODEL_SECURITY_API_ENDPOINT }}
MODEL_SECURITY_PROFILE_ID: ${{ secrets.MODEL_SECURITY_PROFILE_ID }}
TSG_ID: ${{ secrets.TSG_ID }}
run: python scripts/scan_model.py --config config/model-config.yaml
# ── Deploy (replace with your infrastructure) ────────────────
deploy-model:
name: Deploy Model
needs: security-scan
if: >
(github.event_name == 'push' && github.ref == 'refs/heads/main')
|| github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: echo "Add your deployment logic here"
The workflow triggers on three events: (1) a push to main that modifies config/model-config.yaml, (2) a pull request targeting main with the same path filter, and (3) a manual workflow_dispatch. On pull requests, only the scan runs; deployment is skipped. On pushes to main, both scan and deploy run if the scan passes.
The file .github/workflows/model-security-scan.yml exists. Navigate to the repository's Actions tab in GitHub, and the workflow Model Security Scan & Deploy appears in the left sidebar.
Phase 4: Scan Configuration
The model configuration file specifies which model to scan and whether scanning is enabled. Changes to this file trigger the GitHub Actions workflow.
Create config/model-config.yaml:
# Model Configuration
# Changing this file triggers the Prisma AIRS Model Security Scan pipeline.
model:
huggingface_id: "google/gemma-3-1b-it"
display_name: "gemma-3-1b-it"
description: "Google Gemma 3 1B Instruction-Tuned model deployed via Vertex AI Model Garden"
version: "2.1"
deployment:
machine_type: "g2-standard-12"
accelerator_type: "NVIDIA_L4"
accelerator_count: 1
region: "us-central1"
security:
scan_enabled: true
security_profile_id: "" # Set via MODEL_SECURITY_PROFILE_ID env var or GitHub Variable
| Field | Description | Example |
|---|---|---|
model.huggingface_id | The HuggingFace model identifier. The scan script constructs the model URI from this value. | google/gemma-3-1b-it |
model.display_name | Human-readable name for log output. | gemma-3-1b-it |
model.version | Version string applied as a scan label for tracking. | 2.1 |
deployment.machine_type | GCP machine type for the model endpoint. Applied as a scan label. | g2-standard-12 |
deployment.accelerator_type | GPU accelerator type for inference. | NVIDIA_L4 |
deployment.accelerator_count | Number of accelerators attached to the endpoint. | 1 |
deployment.region | GCP region for deployment. | us-central1 |
security.scan_enabled | Set to false to skip scanning (the scan script exits 0 immediately). | true |
security.security_profile_id | Profile UUID. Leave empty when overriding via the MODEL_SECURITY_PROFILE_ID environment variable. | a1b2c3d4-... |
The file config/model-config.yaml passes YAML lint validation. Running python -c "import yaml; yaml.safe_load(open('config/model-config.yaml'))" produces no errors.
The scan script requires pyyaml for configuration parsing and python-dotenv for environment variable loading. The Model Security SDK itself is installed separately from the private PyPI.
Create requirements.txt:
pyyaml>=6.0
python-dotenv>=1.0
The model-security-client[all] SDK is not listed in requirements.txt. It is installed from a private PyPI repository using the authenticated URL retrieved by get_pypi_url.sh. See Model Security SDK installation guide for details.
The file requirements.txt exists with pyyaml and python-dotenv listed.
The scan script applies labels to each scan result, enabling filtering and correlation in Strata Cloud Manager. Modify the labels dictionary in scripts/scan_model.py to match the deployment environment.
labels = {
"pipeline": "github-actions",
"deployment_target": "vertex_ai", # Replace with target platform
"version": config["model"].get("version", "unknown").replace(".", "-"),
"team": "ml-platform", # Optional: owning team
"environment": "production", # Optional: target environment
}
These labels appear in the Strata Cloud Manager scan activity logs and support filtering scans by pipeline source, deployment target, and team ownership.
The labels dictionary in scan_model.py includes at minimum a pipeline key set to github-actions.
Phase 5: Verification
Trigger the workflow by modifying the model configuration and pushing the change, or by using the manual dispatch option.
Option A: Push a Configuration Change
- Edit
config/model-config.yaml(e.g., update theversionfield). - Commit and push the change to the
mainbranch (or open a pull request).
git add config/model-config.yaml
git commit -m "chore: bump model version to trigger security scan"
git push origin main
Option B: Manual Dispatch
- Navigate to the repository's
Actionstab. - Select the
Model Security Scan & Deployworkflow. - Click
Run workflow> select the branch > clickRun workflow.
The Actions tab shows a new workflow run in In progress or Queued state for the Model Security Scan & Deploy workflow.
The scan script produces structured output in the GitHub Actions logs showing the model name, scan UUID, outcome, and any violations.
- Open the workflow run from the
Actionstab. - Click the
Prisma AIRS Model Security Scanjob. - Expand the
Run Prisma AIRS Model Security Scanstep. - Locate the scan results block in the log output.
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 security-scan job shows a green checkmark. The log output contains ALLOWED: Model approved by Prisma AIRS security policy and a valid Scan UUID.
All scan results are recorded in Strata Cloud Manager for audit and compliance tracking, 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 GitHub Actions log.
- Confirm the scan outcome, rule evaluations, and model details match expectations.
Scan results include the pipeline: github-actions label, enabling filtering to isolate CI/CD-originated scans from manual or SDK-initiated scans.
The scan entry appears in the Model Security activity log with the correct model name, outcome, and github-actions 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 secrets match the values from the service account in Strata Cloud Manager. |
SDK installation fails with pip install error |
Private PyPI URL was not retrieved in the previous step | Check the Get Model Security SDK PyPI URL step log. If the PyPI URL step failed silently, the subsequent pip install cannot find the model-security-client package. |
Scan returns unexpected BLOCKED result |
Security profile rules are more restrictive than expected | Check the MODEL_SECURITY_PROFILE_ID points to the intended security profile. Review the profile's rules in Strata Cloud Manager and adjust thresholds. |
| Workflow does not trigger on push | The changed file does not match the paths filter |
The workflow triggers only on changes to config/model-config.yaml. Confirm the file path matches exactly. Use workflow_dispatch for manual testing. |
jq: command not found |
The runner image does not include jq |
The ubuntu-latest runner includes jq by default. If using a custom runner, add sudo apt-get install -y jq before the PyPI authentication step. |
| Scan passes but deploy job does not run | The deploy job runs only on pushes to main or manual dispatch |
Pull request triggers run the scan only. Merge the PR to main to trigger the deploy job. |
The troubleshooting table covers the issue encountered. If the issue persists, collect the full GitHub Actions log output and the Scan UUID for a support case with Palo Alto Networks.