Skip to content

[New Plugin] Lakera Guard#1637

Open
teddyamkie-lakera wants to merge 2 commits into
Portkey-AI:mainfrom
teddyamkie-lakera:plugin/lakera-guard
Open

[New Plugin] Lakera Guard#1637
teddyamkie-lakera wants to merge 2 commits into
Portkey-AI:mainfrom
teddyamkie-lakera:plugin/lakera-guard

Conversation

@teddyamkie-lakera
Copy link
Copy Markdown

Closes #1636

Summary

  • Adds Lakera Guard as a native guardrail provider via the /v2/guard API
  • Supports beforeRequestHook (prompt screening) and afterRequestHook (response screening)
  • Automatically redacts PII when only pii/* detectors fire, passes through clean content, and blocks on any other policy hit
  • Optional project_id parameter to target a specific Lakera policy
  • Optional apiBase credential for regional endpoints

Test plan

  • Run npx jest plugins/lakera — 9 unit tests covering redaction helpers (overlapping spans, unicode, invalid spans, message isolation)
  • Run make format; make lint (or npm run format && npm run format:check)
  • Manual test: configure the plugin in the Portkey dashboard with a valid Lakera API key and send a prompt injection / PII-containing message

🤖 Generated with Claude Code

Adds Lakera Guard as a guardrail provider for the Portkey AI Gateway.
Supports beforeRequestHook and afterRequestHook with prompt attack blocking
and PII redaction via the Lakera /v2/guard API.
Comment thread plugins/index.ts
Copy link
Copy Markdown
Member

@narengogi narengogi left a comment

Choose a reason for hiding this comment

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

you can delete the helper methods you created, common utilities are available in utils file

}
const projectID = parameters.projectID;

const messages = extractMessages(context, eventType);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

you can use the getText() method from import { getText } from '../utils';

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new native guardrail plugin that integrates with Lakera Guard’s /v2/guard endpoint and applies PII redaction (via Lakera payload spans) when the only detected violations are pii/*.

Changes:

  • Introduces Lakera plugin handler that calls /v2/guard for both beforeRequestHook and afterRequestHook.
  • Adds redaction utilities for masking multiple spans across messages, plus helper unit tests.
  • Registers the new plugin in the central plugins/index.ts registry.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
plugins/lakera/main-function.ts New Lakera Guard plugin handler (request/response screening + optional redaction).
plugins/lakera/redaction.ts Span normalization/merging + message masking helpers used by the handler.
plugins/lakera/manifest.json Plugin manifest describing credentials, parameters, and hook support.
plugins/lakera/test-file.test.ts Unit tests for redaction helper functions.
plugins/index.ts Registers the lakera.guard handler in the plugins map.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +170 to +184
const indices = new Set(
payload
.map((p) => p.message_id)
.filter((id) => id !== undefined && id !== null)
);
for (const idx of indices) {
const i = Number(idx);
if (i < 0 || i >= out.length) {
warnings.push(
`payload references message_id=${i} but only ${out.length} messages`
);
continue;
}
const msg = out[i];
const content = msg.content;
Comment on lines +186 to +208
if (eventType === 'beforeRequestHook') {
const reqJson = context.request?.json
? JSON.parse(JSON.stringify(context.request.json))
: {};
reqJson.messages = maskedMsgs;
transformedData.request.json = reqJson;
transformed = true;
} else {
const respJson = context.response?.json
? JSON.parse(JSON.stringify(context.response.json))
: {};
const choices = respJson.choices || [];
if (choices[0]?.message && maskedMsgs.length > 0) {
const last = maskedMsgs[maskedMsgs.length - 1];
if (last && last.role === 'assistant') {
choices[0].message = choices[0].message || {};
choices[0].message.content = last.content;
respJson.choices = choices;
}
}
transformedData.response.json = respJson;
transformed = true;
}
Comment on lines +78 to +122
export const handler: PluginHandler = async (
context: PluginContext,
parameters: PluginParameters,
eventType: HookEventType
) => {
let error: any = null;
let verdict = false;
let data: any = null;
let transformed = false;
const transformedData: Record<string, any> = {
request: { json: null, text: null },
response: { json: null, text: null },
};

try {
const apiKey = parameters.credentials?.apiKey as string | undefined;
if (!apiKey) {
throw new Error(
'Missing Lakera apiKey: set credentials.apiKey in the guardrail config'
);
}
const projectID = parameters.projectID;

const messages = extractMessages(context, eventType);
if (!messages.length) {
return {
error: null,
verdict: true,
data: { explanation: 'no messages to screen' },
};
}

const apiBase = String(
(parameters.credentials?.apiBase as string | undefined) ??
'https://api.lakera.ai'
).replace(/\/$/, '');
const url = `${apiBase}/v2/guard`;
const body: Record<string, unknown> = {
messages,
payload: true,
breakdown: true,
};
if (projectID) {
body.project_id = projectID;
}
Comment on lines +1 to +8
import {
applyMasksToMessages,
applyPayloadMasksToString,
dedupePayloadItems,
isOnlyPiiViolation,
mergeOverlappingIntervals,
normalizeSpan,
} from './redaction';
Comment on lines +72 to +77
const i = text.indexOf('👋');
const payload = [
{ message_id: 0, start: i, end: i + 1, detector_type: 'pii/x' },
];
const { text: out } = applyPayloadMasksToString(text, payload, 0, false);
expect(out).not.toContain('👋');
Comment on lines +21 to +26
"functions": [
{
"name": "Guard — screen content",
"id": "guard",
"supportedHooks": ["beforeRequestHook", "afterRequestHook"],
"type": "guardrail",
@MalouLakera
Copy link
Copy Markdown

Taking over this work while @teddyamkie-lakera is OOO. I've addressed the review comments and opened a new PR: #1647

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] Lakera Guard guardrail plugin

5 participants