Skip to content

Commit cfb5238

Browse files
authored
Merge pull request #1088 from jroberts2600/panw-prisma-airs-plugin
Add panw-prisma-airs plugin with tests and update config/index
2 parents 6d19ea0 + 7091bc9 commit cfb5238

File tree

5 files changed

+131
-1
lines changed

5 files changed

+131
-1
lines changed

conf.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
"pillar",
88
"patronus",
99
"pangea",
10-
"promptsecurity"
10+
"promptsecurity",
11+
"panw-prisma-airs"
1112
],
1213
"credentials": {
1314
"portkey": {

plugins/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { handler as azurePii } from './azure/pii';
4646
import { handler as azureContentSafety } from './azure/contentSafety';
4747
import { handler as promptSecurityProtectPrompt } from './promptsecurity/protectPrompt';
4848
import { handler as promptSecurityProtectResponse } from './promptsecurity/protectResponse';
49+
import { handler as panwPrismaAirsintercept } from './panw-prisma-airs/intercept';
4950
import { handler as defaultjwt } from './default/jwt';
5051

5152
export const plugins = {
@@ -128,4 +129,7 @@ export const plugins = {
128129
protectPrompt: promptSecurityProtectPrompt,
129130
protectResponse: promptSecurityProtectResponse,
130131
},
132+
'panw-prisma-airs': {
133+
intercept: panwPrismaAirsintercept,
134+
},
131135
};
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import {
2+
HookEventType,
3+
PluginContext,
4+
PluginHandler,
5+
PluginParameters,
6+
} from '../types';
7+
import { getText, post } from '../utils';
8+
9+
const AIRS_URL =
10+
'https://service.api.aisecurity.paloaltonetworks.com/v1/scan/sync/request';
11+
12+
const fetchAIRS = async (payload: any, apiKey: string, timeout?: number) => {
13+
const opts = {
14+
headers: { 'x-pan-token': apiKey },
15+
};
16+
return post(AIRS_URL, payload, opts, timeout);
17+
};
18+
19+
export const handler: PluginHandler = async (
20+
ctx: PluginContext,
21+
params: PluginParameters,
22+
hook: HookEventType
23+
) => {
24+
const apiKey =
25+
(params.credentials?.AIRS_API_KEY as string | undefined) ||
26+
process.env.AIRS_API_KEY ||
27+
'';
28+
29+
let verdict = true;
30+
let data: any = null;
31+
let error: any = null;
32+
33+
try {
34+
const text = getText(ctx, hook); // prompt or response
35+
36+
const payload = {
37+
tr_id:
38+
typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
39+
? crypto.randomUUID()
40+
: Math.random().toString(36).substring(2) + Date.now().toString(36),
41+
ai_profile: {
42+
profile_name: params.profile_name ?? 'dev-block-all-profile',
43+
},
44+
metadata: {
45+
ai_model: params.ai_model ?? 'unknown-model',
46+
app_user: params.app_user ?? 'portkey-gateway',
47+
},
48+
contents: [
49+
{ [hook === 'beforeRequestHook' ? 'prompt' : 'response']: text },
50+
],
51+
};
52+
53+
const res: any = await fetchAIRS(payload, apiKey, params.timeout);
54+
55+
if (!res || typeof res.action !== 'string') {
56+
throw new Error('Malformed AIRS response');
57+
}
58+
if (res.action === 'block') {
59+
verdict = false;
60+
}
61+
data = res;
62+
} catch (e: any) {
63+
error = e;
64+
verdict = false;
65+
}
66+
67+
return { verdict, data, error };
68+
};
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"id": "panwPrismaAirs",
3+
"name": "PANW Prisma AIRS Guardrail",
4+
"description": "Blocks prompt/response when Palo Alto Networks Prisma AI Runtime Security returns action=block.",
5+
"credentials": [
6+
{
7+
"id": "AIRS_API_KEY",
8+
"name": "AIRS API Key",
9+
"type": "string",
10+
"required": true
11+
}
12+
],
13+
"functions": [
14+
{
15+
"id": "intercept",
16+
"name": "PANW Prisma AIRS Guardrail",
17+
"type": "guardrail",
18+
"supportedHooks": ["beforeRequestHook", "afterRequestHook"],
19+
"parameters": {
20+
"type": "object",
21+
"properties": {
22+
"profile_name": { "type": "string" },
23+
"ai_model": { "type": "string" },
24+
"app_user": { "type": "string" }
25+
},
26+
"required": ["profile_name"]
27+
}
28+
}
29+
]
30+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { handler as panwPrismaAirsHandler } from './intercept';
2+
3+
describe('PANW Prisma AIRS Guardrail', () => {
4+
const mockContext = {
5+
request: { text: 'This is a test prompt.' },
6+
response: { text: 'This is a test response.' },
7+
};
8+
9+
const params = {
10+
credentials: { AIRS_API_KEY: 'dummy-key' },
11+
profile_name: 'test-profile',
12+
ai_model: 'gpt-unit-test',
13+
app_user: 'unit-tester',
14+
timeout: 3000,
15+
};
16+
17+
it('should return a result object with verdict, data, and error', async () => {
18+
const result = await panwPrismaAirsHandler(
19+
mockContext,
20+
params,
21+
'beforeRequestHook'
22+
);
23+
expect(result).toHaveProperty('verdict');
24+
expect(result).toHaveProperty('data');
25+
expect(result).toHaveProperty('error');
26+
});
27+
});

0 commit comments

Comments
 (0)