Skip to content

Commit 456a023

Browse files
feat: Add support for Trend AI Guard
1 parent 050db7d commit 456a023

File tree

6 files changed

+638
-1
lines changed

6 files changed

+638
-1
lines changed

conf.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"pangea",
1111
"promptsecurity",
1212
"panw-prisma-airs",
13-
"walledai"
13+
"walledai",
14+
"trend-ai"
1415
],
1516
"credentials": {
1617
"portkey": {

plugins/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ import { handler as defaultregexReplace } from './default/regexReplace';
6767
import { handler as defaultallowedRequestTypes } from './default/allowedRequestTypes';
6868
import { handler as javelinguardrails } from './javelin/guardrails';
6969
import { handler as f5GuardrailsScan } from './f5-guardrails/scan';
70+
import { handler as trendAiGuard } from './trend-ai/guard';
71+
7072
export const plugins = {
7173
default: {
7274
regexMatch: defaultregexMatch,
@@ -177,4 +179,7 @@ export const plugins = {
177179
'f5-guardrails': {
178180
scan: f5GuardrailsScan,
179181
},
182+
'trend-ai': {
183+
guard: trendAiGuard,
184+
},
180185
};

plugins/trend-ai/guard.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import {
2+
HookEventType,
3+
PluginContext,
4+
PluginHandler,
5+
PluginParameters,
6+
} from '../types';
7+
import { post, getText, HttpError } from '../utils';
8+
import { VERSION } from './version';
9+
10+
export const handler: PluginHandler = async (
11+
context: PluginContext,
12+
parameters: PluginParameters,
13+
eventType: HookEventType,
14+
options?: {
15+
env: Record<string, any>;
16+
getFromCacheByKey?: (key: string) => Promise<any>;
17+
putInCacheWithValue?: (key: string, value: any) => Promise<any>;
18+
}
19+
) => {
20+
let error = null;
21+
let verdict = true;
22+
let data = null;
23+
24+
// Validate required parameters
25+
if (!parameters.credentials?.v1Url) {
26+
return {
27+
error: { message: `'parameters.credentials.v1Url' must be set` },
28+
verdict: true,
29+
data,
30+
};
31+
}
32+
33+
if (!parameters.credentials?.apiKey) {
34+
return {
35+
error: { message: `'parameters.credentials.apiKey' must be set` },
36+
verdict: true,
37+
data,
38+
};
39+
}
40+
41+
// Extract text from context
42+
const text = getText(context, eventType);
43+
if (!text) {
44+
return {
45+
error: { message: 'request or response text is empty' },
46+
verdict: true,
47+
data,
48+
};
49+
}
50+
const applicationName = parameters.applicationName;
51+
52+
// Validate application name is provided and has correct format
53+
if (!applicationName) {
54+
return {
55+
error: { message: 'Application name is required' },
56+
verdict: true,
57+
data,
58+
};
59+
}
60+
61+
if (!/^[a-zA-Z0-9_-]+$/.test(applicationName)) {
62+
return {
63+
error: {
64+
message:
65+
'Application name must contain only letters, numbers, hyphens, and underscores',
66+
},
67+
verdict: true,
68+
data,
69+
};
70+
}
71+
72+
// Prepare request headers
73+
const headers: Record<string, string> = {
74+
'Content-Type': 'application/json',
75+
Accept: 'application/json',
76+
Authorization: `Bearer ${parameters.credentials?.apiKey}`,
77+
'TMV1-Application-Name': applicationName,
78+
};
79+
80+
// Set Prefer header
81+
const preferValue = parameters.prefer || 'return=minimal';
82+
headers['Prefer'] = preferValue;
83+
84+
const requestOptions = { headers };
85+
86+
// Prepare request payload for applyGuardrails endpoint
87+
const request = {
88+
prompt: text,
89+
};
90+
91+
let response;
92+
try {
93+
response = await post(
94+
parameters.credentials?.v1Url,
95+
request,
96+
requestOptions,
97+
parameters.timeout
98+
);
99+
} catch (e) {
100+
if (e instanceof HttpError) {
101+
error = {
102+
message: `API request failed: ${e.message}. body: ${e.response.body}`,
103+
};
104+
} else {
105+
error = e as Error;
106+
}
107+
}
108+
109+
if (response) {
110+
data = response;
111+
112+
if (response.action && response.action === 'Block') {
113+
verdict = false;
114+
}
115+
}
116+
117+
return {
118+
error,
119+
verdict,
120+
data,
121+
};
122+
};

plugins/trend-ai/manifest.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"id": "trend-ai",
3+
"description": "Trend AI Guard for scanning LLM inputs and outputs",
4+
"credentials": {
5+
"type": "object",
6+
"properties": {
7+
"v1ApiKey": {
8+
"type": "string",
9+
"label": "Trend AI Token",
10+
"description": "Trend AI Guard token Get setup here (https://docs.trendmicro.com/en-us/documentation/article/trend-vision-one-ai-scanner-ai-guard)",
11+
"encrypted": true
12+
},
13+
"v1Url": {
14+
"type": "string",
15+
"label": "Trend AI URL",
16+
"description": "Trend AI Guard URL (e.g., https://api.xdr.trendmicro.com/v3.0/aiSecurity/applyGuardrails)"
17+
}
18+
},
19+
"required": ["v1Url", "apiKey"]
20+
},
21+
"functions": [
22+
{
23+
"name": "Trend AI Guard for scanning LLM inputs and outputs",
24+
"id": "aiGuard",
25+
"supportedHooks": ["beforeRequestHook", "afterRequestHook"],
26+
"type": "guardrail",
27+
"description": [
28+
{
29+
"type": "subHeading",
30+
"text": "Analyze and scan text for security threats and policy violations using Trend AI Guard services."
31+
}
32+
],
33+
"parameters": {
34+
"prefer": {
35+
"type": "string",
36+
"label": "Response Detail Level",
37+
"description": "Controls the level of detail in the response. 'return=representation' returns detailed response with scanner results, 'return=minimal' returns short response with only action and reasons.",
38+
"enum": ["return=representation", "return=minimal"],
39+
"default": "return=minimal"
40+
},
41+
"applicationName": {
42+
"type": "string",
43+
"label": "Application Name",
44+
"description": "The name of the AI application whose prompts are being evaluated. Must contain only letters, numbers, hyphens, and underscores."
45+
},
46+
"required": ["applicationName"]
47+
}
48+
}
49+
]
50+
}

0 commit comments

Comments
 (0)