Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"pangea",
"promptsecurity",
"panw-prisma-airs",
"walledai"
"walledai",
"trend-ai"
],
"credentials": {
"portkey": {
Expand Down
5 changes: 5 additions & 0 deletions plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ import { handler as defaultregexReplace } from './default/regexReplace';
import { handler as defaultallowedRequestTypes } from './default/allowedRequestTypes';
import { handler as javelinguardrails } from './javelin/guardrails';
import { handler as f5GuardrailsScan } from './f5-guardrails/scan';
import { handler as trendAiGuard } from './trend-ai/guard';

export const plugins = {
default: {
regexMatch: defaultregexMatch,
Expand Down Expand Up @@ -177,4 +179,7 @@ export const plugins = {
'f5-guardrails': {
scan: f5GuardrailsScan,
},
'trend-ai': {
guard: trendAiGuard,
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function name 'guard' doesn't match the function ID 'aiGuard' defined in the manifest. For consistency, these should match.

Suggested change
guard: trendAiGuard,
aiGuard: trendAiGuard,

Copilot uses AI. Check for mistakes.
},
};
122 changes: 122 additions & 0 deletions plugins/trend-ai/guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import {
HookEventType,
PluginContext,
PluginHandler,
PluginParameters,
} from '../types';
import { post, getText, HttpError } from '../utils';
import { VERSION } from './version';

export const handler: PluginHandler = async (
context: PluginContext,
parameters: PluginParameters,
eventType: HookEventType,
options?: {
env: Record<string, any>;
getFromCacheByKey?: (key: string) => Promise<any>;
putInCacheWithValue?: (key: string, value: any) => Promise<any>;
}
) => {
let error = null;
let verdict = true;
let data = null;

// Validate required parameters
if (!parameters.credentials?.v1Url) {
return {
error: { message: `'parameters.credentials.v1Url' must be set` },
verdict: true,
data,
};
}

if (!parameters.credentials?.apiKey) {
return {
error: { message: `'parameters.credentials.apiKey' must be set` },
verdict: true,
data,
};
}

// Extract text from context
const text = getText(context, eventType);
if (!text) {
return {
error: { message: 'request or response text is empty' },
verdict: true,
data,
};
}
const applicationName = parameters.applicationName;

// Validate application name is provided and has correct format
if (!applicationName) {
return {
error: { message: 'Application name is required' },
verdict: true,
data,
};
}

if (!/^[a-zA-Z0-9_-]+$/.test(applicationName)) {
return {
error: {
message:
'Application name must contain only letters, numbers, hyphens, and underscores',
},
verdict: true,
data,
};
}

// Prepare request headers
const headers: Record<string, string> = {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${parameters.credentials?.apiKey}`,
'TMV1-Application-Name': applicationName,
};

// Set Prefer header
const preferValue = parameters.prefer || 'return=minimal';
headers['Prefer'] = preferValue;

const requestOptions = { headers };

// Prepare request payload for applyGuardrails endpoint
const request = {
prompt: text,
};

let response;
try {
response = await post(
parameters.credentials?.v1Url,
request,
requestOptions,
parameters.timeout
);
} catch (e) {
if (e instanceof HttpError) {
error = {
message: `API request failed: ${e.message}. body: ${e.response.body}`,
};
} else {
error = e as Error;
}
}

if (response) {
data = response;

if (response.action && response.action === 'Block') {
verdict = false;
}
}

return {
error,
verdict,
data,
};
};
50 changes: 50 additions & 0 deletions plugins/trend-ai/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"id": "trend-ai",
"description": "Trend AI Guard for scanning LLM inputs and outputs",
"credentials": {
"type": "object",
"properties": {
"v1ApiKey": {
"type": "string",
"label": "Trend AI Token",
"description": "Trend AI Guard token Get setup here (https://docs.trendmicro.com/en-us/documentation/article/trend-vision-one-ai-scanner-ai-guard)",
"encrypted": true
},
"v1Url": {
"type": "string",
"label": "Trend AI URL",
"description": "Trend AI Guard URL (e.g., https://api.xdr.trendmicro.com/v3.0/aiSecurity/applyGuardrails)"
}
},
"required": ["v1Url", "apiKey"]
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The required field references 'apiKey', but the credentials schema defines 'v1ApiKey'. These names should match for proper validation.

Suggested change
"required": ["v1Url", "apiKey"]
"required": ["v1Url", "v1ApiKey"]

Copilot uses AI. Check for mistakes.
},
"functions": [
{
"name": "Trend AI Guard for scanning LLM inputs and outputs",
"id": "aiGuard",
"supportedHooks": ["beforeRequestHook", "afterRequestHook"],
"type": "guardrail",
"description": [
{
"type": "subHeading",
"text": "Analyze and scan text for security threats and policy violations using Trend AI Guard services."
}
],
"parameters": {
"prefer": {
"type": "string",
"label": "Response Detail Level",
"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.",
"enum": ["return=representation", "return=minimal"],
"default": "return=minimal"
},
"applicationName": {
"type": "string",
"label": "Application Name",
"description": "The name of the AI application whose prompts are being evaluated. Must contain only letters, numbers, hyphens, and underscores."
},
"required": ["applicationName"]
}
Comment on lines +45 to +47
Copy link

Copilot AI Dec 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The 'required' array is incorrectly nested inside the 'parameters' object. It should be placed at the same level as 'parameters', not inside it, to properly indicate which parameters are required for the function.

Suggested change
},
"required": ["applicationName"]
}
}
},
"required": ["applicationName"]

Copilot uses AI. Check for mistakes.
}
]
}
Loading