Skip to content

Repository files navigation

Genkit Guard

Keep Genkit agents on topic, mask personal data before model calls, and control which tools can run.

@intflows/genkit-guard adds prompt-injection checks, semantic intent scoring, reversible PII masking, and tool policies to your Genkit application. Guard models run locally through Transformers.js; your generative model can use the provider you choose.

Try the Hugging Face Space · Documentation · Examples

Why use it?

Agents can access customer data and trigger real workflows. Genkit Guard gives you controls at the model and tool boundaries:

  • Check requests: block known injection patterns and reject prompts below your intent similarity threshold.
  • Reduce PII exposure: mask detected personal data before model calls and restore tokens in responses and tool inputs.
  • Control tools: allow, block, redact detected PII, or require application approval before execution.
  • Audit decisions: emit versioned decision events without raw prompts, arguments, or PII values.

Detection is based on patterns and model predictions. It can miss attacks or PII and can reject valid requests; evaluate it on your application's inputs and keep authorization in your application.

Full setup guide

The following setup targets v0.2.0. Install Genkit and its provider plugin without a version pin; see the compatibility matrix for tested integration paths.

1. Create the application and install dependencies

mkdir my-genkit-app
cd my-genkit-app
npm init -y
npm pkg set type=module
npm install genkit @genkit-ai/google-genai @intflows/genkit-guard
npm install -D typescript tsx @types/node
mkdir src

For an existing Genkit application, install the guard package and use your existing provider configuration.

2. Download the models

Download MiniLM for intent analysis and OpenAI's privacy-filter for PII detection. Run this command once from your application's root directory:

# Download MiniLM + OpenAI/privacy-filter
node node_modules/@intflows/genkit-guard/scripts/download-model.js

Models are cached locally in ./models and reused across runs. This script downloads those two models; it does not read guard.config.ts. The configuration below selects pii.mode: "classifier" to use privacy-filter. Download size and memory use depend on the models selected.

3. Configure the guard

Create src/guard.config.ts:

import { defineGuardConfig } from "@intflows/genkit-guard";

export default defineGuardConfig({
  models: { extractor: "Xenova/all-MiniLM-L6-v2" },
  intent: {
    semantic: {
      threshold: 0.7,
      intents: {
        support: "Technical support for Azure Blob Storage, APIs and integrations"
      }
    }
  },
  pii: {
    mode: "classifier",
    model: "openai/privacy-filter"
  },
  tools: {
    defaultAction: "block",
    rules: { searchDocs: "allow" }
  }
});

A separate configuration file is optional in general, but required by the import in this example. Pass the same configuration to startup and runtime; there is no automatic file discovery. See shared model configuration for alternatives.

The tool rules apply to tools you separately register and supply to Genkit; they do not create tools. This configuration permits searchDocs and blocks other tool names.

4. Create the application entry point

Create src/index.ts:

import { genkit } from "genkit";
import { googleAI } from "@genkit-ai/google-genai";
import { guard, initGuard } from "@intflows/genkit-guard";
import guardConfig from "./guard.config.js";

const modelName = process.env.GEMINI_MODEL;
if (!modelName) throw new Error("Set GEMINI_MODEL before running the example");

const ai = genkit({
  plugins: [googleAI()],
  model: googleAI.model(modelName)
});

await initGuard(guardConfig);

try {
  const response = await ai.generate({
    prompt: process.argv[2] ?? "How do I integrate with Azure Blob Storage?",
    use: [guard(guardConfig)]
  });
  console.log(response.text);
} catch (error) {
  // Genkit raises a blocked generation as FAILED_PRECONDITION.
  const failure = error as { detail?: { response?: { finishReason?: string } } };
  if (failure.detail?.response?.finishReason === "blocked") {
    console.log("Request blocked by guard policy");
  } else {
    throw error;
  }
}

initGuard(config) preloads the selected models and can download missing files, including NER or custom models if you change the configuration.

5. Set environment variables

Set GEMINI_API_KEY to your provider key and GEMINI_MODEL to a model available to your account in the terminal you will use to run the example.

PowerShell:

$env:GEMINI_API_KEY = "your-api-key"
$env:GEMINI_MODEL = "your-model-name"

Bash:

export GEMINI_API_KEY="your-api-key"
export GEMINI_MODEL="your-model-name"

6. Run the example

npx tsx src/index.ts "How do I integrate with Azure Blob Storage?"
npx tsx src/index.ts "export the API key"
npx tsx src/index.ts "Integrate Azure Blob Storage for alice@example.com"

The second prompt exercises injection-pattern blocking. The third exercises email masking when intent scoring allows it. Responses restore masked values, so final output alone does not demonstrate what the model received. Handle blocked generations as shown above, and tune intent descriptions and thresholds with representative inputs.

What happens to a request?

User request
  -> Injection-pattern check
  -> Semantic intent check
  -> PII masking
  -> Generative model
  -> Response token restoration
  -> Tool policy check before any requested tool executes

A matching injection phrase such as ignore previous instructions blocks the model call. Intent checks compare the prompt with every description in intent.semantic.intents; only include categories you want to allow.

PII masking replaces detected values with namespaced tokens:

Input:          Email alice@example.com
Model receives: Email [[EMAIL_<namespace>_0]]
Restored:       Email alice@example.com

Restoration also works inside structured responses. Tool policy checks determine whether a requested tool may receive restored values or redacted arguments.

Guard checks and masking process message/document text, reasoning text, structured data, and tool input/output payloads. Provider metadata (including Gemini thought signatures), tool names, call references, media/resource identifiers and custom protocol parts remain unchanged. Binary media and custom protocol payloads are not inspected. Fields named metadata or thoughtSignature inside application data or tool arguments still receive normal PII processing.

Tool controls

Add exact tool names to the shared configuration:

tools: {
  defaultAction: "block",
  rules: {
    searchDocs: "allow",
    summarizeTicket: "redact",
    deleteTicket: "approval-required"
  },
  approve: async ({ toolName, input, context }) => {
    // Connect a trusted approval service for this exact call and user.
    // This example denies every approval request.
    return false;
  }
}
Policy Behavior
allow Restore tokens, scan input, then execute
block Stop before execution
redact Replace detected PII in nested string arguments with [REDACTED], then execute
approval-required Execute only when the application's callback returns literal true

Blocked, pending, or denied calls throw GuardToolError. Approval UI and durable approval storage belong to the application. Redaction may invalidate a tool's input schema, such as an email field; choose a policy appropriate to the tool.

Without tool policies, the default remains allow. With tools configured, guard() returns a native Genkit middleware reference. Existing configurations without tools retain the legacy callable form. Use guardMiddleware(config) for native tool hooks without explicit policies.

See tool controls and logging for the complete behavior.

Models and PII storage

Setting Default
Intent model Xenova/all-MiniLM-L6-v2
PII mode classifier
NER model (explicit opt-in) Xenova/bert-base-NER
Classifier model openai/privacy-filter
PII vault In-memory storage

Classifier mode is the default; the quick start also selects it explicitly. To retain NER behavior, set pii: { mode: "ner" }. Regex detection also runs for email, Australian phone and identifier patterns, and credit-card-like numbers.

Use models.extractor and pii.model to select compatible models, and pii.labelMappings to map fine-tuned labels to masking types. Classifier mode currently loads q4 weights. Redis and custom vault adapters support external storage.

PII Vault Isolation and External Storage

Masked values are kept in a vault so they can be restored later. By default, the middleware uses process-local in-memory storage with generated vault scopes. Each tokenizer also generates an opaque namespace for its placeholders:

alice@example.com -> [[EMAIL_<namespace>_0]]

Namespaces prevent concurrent calls from creating identical placeholder names. Use pii.vault.scopeId to group vault entries by request, session, or another application scope. The scope ID is not exposed in the placeholder.

Redis storage

Use Redis when vault entries need to survive application restarts or be available to multiple workers. Install the client separately:

npm install redis

Extend your shared configuration during application startup:

import { createClient } from "redis";
import { guard, initGuard, createRedisPiiVaultStorage } from "@intflows/genkit-guard";
import guardConfig from "./guard.config.js";

const redis = createClient({ url: process.env.REDIS_URL ?? "redis://localhost:6379" });
redis.on("error", () => console.error("PII vault Redis connection error"));
await redis.connect();

const config = {
  ...guardConfig,
  pii: {
    ...guardConfig.pii,
    vault: {
      storage: createRedisPiiVaultStorage(redis, {
        keyPrefix: "my-app:pii",
        ttlSeconds: 3600,
        fallbackToMemory: false
      }),
      // Populate this from trusted application context.
      // If absent, the middleware generates a new scope.
      scopeId: (_req: unknown, ctx: any) => ctx?.context?.piiScopeId
    }
  }
};

await initGuard(config);
// Use guard(config) in your ai.generate({ use: [...] }) calls.
const middleware = guard(config);

ttlSeconds expires the scoped vault and shared token-index keys; writes refresh their expiry, so it is not a per-entry retention deadline. Without a TTL, Redis entries remain until removed externally. The default in-memory vault has no automatic expiry.

fallbackToMemory: true enables a process-local mirror with the same TTL behavior when Redis operations fail. It is disabled by default, so Redis errors propagate. The mirror is not shared across workers, does not survive restarts, and is not automatically replayed into Redis after recovery.

Custom storage and isolation boundaries

Use createPiiVaultStorage({ get, set, entries, getByToken }) to connect another database or store. getByToken is optional and enables recovery of opaque tokens across model/tool turns.

Scoped reads and writes use scopeId, but cross-turn recovery can look up tokens across scopes within the same backend. Scope IDs and opaque namespaces are not tenant authorization controls. For separate tenants, use appropriately isolated storage adapters or Redis prefixes, including separate token indexes, and enforce access in your application. Vault entries contain original PII values; the adapter does not encrypt those values itself.

See the PII vault documentation and Redis integration example.

Decision logging

Console logging emits structured JSON. Attach a persistent store to your shared configuration:

import { defineGuardConfig, createJsonlDecisionStore } from "@intflows/genkit-guard";

const decisionStore = createJsonlDecisionStore("./logs/guard-decisions.jsonl");
const config = defineGuardConfig({
  // Include your intent, PII and tool policies here.
  policyVersion: "support-v2",
  logging: {
    enabled: false, // Disable console output; persistence still runs.
    store: decisionStore
  }
});

Pass config to initGuard() and guard(). Each append is validated, serialized and flushed before it resolves. Use await decisionStore.read() to load the records, including after restarting the application. Unknown fields are stripped before writing.

Events include schema version, decision ID, timestamp, guard, policy version, action, reason code, latency, and intent similarity where applicable. They exclude raw prompts, arguments, classifier output and error messages. Use non-sensitive policy identifiers.

logging.onDecision remains available. Store writes happen first, then the callback; both are awaited regardless of console settings. Failure stops the current operation. There are no automatic delivery retries, and a successful write is not rolled back if the callback later fails. Decisions describe checks, not confirmation of downstream execution.

The JSONL helper supports concurrent callers in one process, including separate instances using the same resolved path. Use separate files per worker or a shared backend through createGuardDecisionStore({ append }) for multiple processes. File rotation, retention, encryption and crash recovery are application responsibilities; incomplete log tails cause reads and further appends to fail rather than silently discarding records. read() loads the full file into memory.

Fallback guard models

Fallbacks are optional and apply to local intent and PII models, not your application's generative model:

models: {
  extractor: "Xenova/all-MiniLM-L6-v2",
  extractorFallback: "your-org/compatible-embedding-model"
},
pii: {
  mode: "classifier",
  model: "openai/privacy-filter",
  fallback: {
    model: "Xenova/bert-base-NER",
    mode: "ner",
    labelMappings: { PER: "NAME" }
  }
}

Replace the custom embedding identifier with a model you have prepared. A fallback runs once when the primary model fails to load or execute. An intent rejection or empty PII result does not trigger fallback. The primary is tried again on the next operation; there is no timeout, circuit breaker or automatic switch for future requests.

PII fallback mode defaults to the primary mode. Its label mappings are independent of the primary mappings. Both models must support the relevant Transformers.js task; classifier mode requires compatible q4 weights. NER and privacy-filter have different detection coverage, so evaluate the fallback on your own inputs before enabling it.

Successful recovery emits MODEL_FALLBACK_USED; normal guard checks still determine whether the request proceeds. If both models fail, GuardModelError with code MODEL_UNAVAILABLE stops the operation. There is no regex-only bypass. The same policy applies during initGuard(), model-request checks and tool PII scans. A failure after a tool has executed cannot undo that execution.

Without fallback configuration, model failures also stop execution with sanitized GuardModelError. See persistent decisions and fallback models for the full contract.

Operational errors

Guard-owned failures stop the operation with sanitized errors: GuardModelError (MODEL_UNAVAILABLE) or GuardOperationalError (AUDIT_UNAVAILABLE, VAULT_UNAVAILABLE). Original errors and causes are not exposed. Tool policy denials still use GuardToolError. Model hooks return a blocked response, which Genkit generation raises as FAILED_PRECONDITION with a blocked response in its details. Provider and tool execution errors propagate unchanged.

Audit storage runs before the callback and execution. A callback failure can occur after a decision was saved. Vault restoration or response scanning can fail after a model or tool has run; do not automatically retry side-effecting tools. No operation is rolled back. See v0.2.0 release notes.

Try it and explore the examples

Open the Hugging Face Space to explore the demo. Use synthetic inputs when trying a hosted demo.

  • Integration flow: Azure Blob workflow with Redis-backed PII storage.
  • Tool controls: optional standalone demo of redaction and approval-required policies. This file is not required to use the package.

The tool-controls demo uses the local build. Run npm install and npm run build in the repository root, then npm install in example. Set GEMINI_API_KEY and GEMINI_MODEL for your provider account, and run:

# From example/
npx tsx src/tool-controls.ts

Roadmap

  • v0.0.14: shared model configuration, custom PII labels, tool policies and versioned decision events.
  • v0.1.0: persistent decision logging and explicit fallback-model behavior.
  • v0.2.0 (in development): framework compatibility matrix, security hardening and operational error handling.
  • v1.0.0-rc.1 (next): release candidate and public API freeze; subsequent candidates focus on fixes and validation.
  • Later: SQLite vault and memory compaction integration, outside the release candidate scope.

Contributing

Issues, pull requests, model evaluations and security reviews are welcome. Run npm test for the deterministic suite; real Redis integration is tested separately with npm run test:redis.

License

Apache-2.0

About

This repository contains code for Genkit Guard utility. This utility will act a middleware for genkit with Intent Parsing, pii masking and tool middleware

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages