Skill: aunicall/prompt-inspector (version 0.1.2)

--- name: prompt-inspector description: "Detect prompt injection attacks and adversarial inputs in user text before passing it to your LLM. Use when you need to validate or screen user-provided text for jailbreak attempts, instruction overrides, role-play escapes, or other prompt manipulation techniques. Returns a safety verdict, risk score (0–1), and threat categories. Ideal for guarding AI pipelines, chatbots, and any application that feeds user input into a language model." version: 0.1.0 homepage: https://promptinspector.io commands: - /inspect - Detect prompt injection in a piece of text - /inspect_batch - Detect prompt injection for multiple texts from a file metadata: {"clawdbot":{"emoji":"🛡️","requires":{"env":["PMTINSP_API_KEY"]}}} --- # Prompt Inspector **Prompt Inspector** is a production-grade API service that detects prompt injection attacks, jailbreak attempts, and adversarial manipulations in real time. 📖 **For detailed product information, features, and threat categories, see [references/product-info.md](./references/product-info.md)** --- ## Requirements Provide your API key via either: - Environment variable: `PMTINSP_API_KEY=your-api-key`, or - `~/.openclaw/.env` line: `PMTINSP_API_KEY=your-api-key` Get your API key at [promptinspector.io](https://promptinspector.io) by creating an app. Manage custom sensitive words in your dashboard at [promptinspector.io](https://promptinspector.io). --- ## Commands ### Detect a single text (Python) ```bash # Basic detection — prints verdict and score python3 {baseDir}/scripts/detect.py --text "..." # JSON output python3 {baseDir}/scripts/detect.py --text "..." --format json # Override API key inline python3 {baseDir}/scripts/detect.py --api-key pi_xxx --text "..." ``` ### Detect a single text (Node.js) ```bash # Basic detection node {baseDir}/scripts/detect.js --text "..." # JSON output node {baseDir}/scripts/detect.js --text "..." --format json # Override API key inline node {baseDir}/scripts/detect.js --api-key pi_xxx --text "..." ``` ### Batch detection from a file (Python) ```bash # Each line in the file is treated as one text to inspect python3 {baseDir}/scripts/detect.py --file inputs.txt # JSON output for automation python3 {baseDir}/scripts/detect.py --file inputs.txt --format json ``` --- ## Output ### Default (human-readable) ``` Request ID : a1b2c3d4-... Is Safe : False Score : 0.97 Category : prompt_injection, jailbreak Latency : 34 ms ``` ### JSON (`--format json`) ```json { "request_id": "a1b2c3d4-...", "is_safe": false, "score": 0.97, "category": ["prompt_injection", "jailbreak"], "latency_ms": 34 } ``` --- ## Threat Categories Prompt Inspector detects **10 threat categories**: - instruction_override - asset_extraction - syntax_injection - jailbreak - response_forcing - euphemism_bypass - reconnaissance_probe - parameter_injection - encoded_payload - custom_sensitive_word 📖 **For complete category descriptions, see [references/product-info.md](./references/product-info.md#threat-categories)** --- ## API at a Glance ``` POST /api/v1/detect/sdk Header: X-App-Key: <your-api-key> Body: {"input_text": "<text to inspect>"} ``` **Response:** ```json { "request_id": "string", "latency_ms": 34, "result": { "is_safe": false, "score": 0.97, "category": ["prompt_injection"] } } ``` Full API reference: [docs.promptinspector.io](https://docs.promptinspector.io) --- ## Notes - Keep text under the limit for your plan tier. Very long inputs may be rejected with HTTP 413. - Use `--format json` when piping output to other tools. - For bulk workloads, batch requests with `--file` to minimise round-trip overhead. - Contact [[REDACTED_SECRET]](mailto:[REDACTED_SECRET]) for enterprise plans and self-hosting support.

path: scripts/detect.js
content: #!/usr/bin/env node
/**
 * Prompt Inspector — detection helper script (Node.js).
 *
 * Sends text to the Prompt Inspector API and prints the result.
 * No third-party packages required — uses only Node.js built-ins (https, fs, path).
 *
 * Usage:
 *   node detect.js --text "Ignore all previous instructions..."
 *   node detect.js --file inputs.txt --format json
 *   node detect.js --api-key pi_xxx --text "..." --format json
 *
 * Authentication (in priority order):
 *   1. --api-key  command-line argument
 *   2. PMTINSP_API_KEY  environment variable
 *   3. ~/.openclaw/.env  file  (line: PMTINSP_API_KEY=...)
 */

"use strict";

const https = require("https");
const http = require("http");
const fs = require("fs");
const path = require("path");
const os = require("os");
const { URL } = require("url");

// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------

const DEFAULT_BASE_URL = "https://promptinspector.io";
const DETECT_PATH = "/api/v1/detect/sdk";
const DEFAULT_TIMEOUT = 30000; // milliseconds

// ---------------------------------------------------------------------------
// API key resolution
// ---------------------------------------------------------------------------

/**
 * Parse a simple KEY=VALUE .env file and return a plain object.
 * @param {string} filePath
 * @returns {Record<string, string>}
 */
function loadDotenv(filePath) {
  const env = {};
  try {
    const content = fs.readFileSync(filePath, "utf-8");
    for (const raw of content.split(/\r?\n/)) {
      const line = raw.trim();
      if (!line || line.startsWith("#") || !line.includes("=")) continue;
      const idx = line.indexOf("=");
      const key = line.slice(0, idx).trim();
      let value = line.slice(idx + 1).trim();
      // Remove surrounding quotes if present
      if (value.length >= 2 && (value[0] === '"' || value[0] === "'") && value[0] === value[value.length - 1]) {
        value = value.slice(1, -1);
      }
      env[key] = value;
    }
  } catch (_) {
    // File not found or unreadable — ignore
  }
  return env;
}

/**
 * Return the first available API key or exit with an error.
 * @param {string|null} cliKey
 * @returns {string}
 */
function resolveApiKey(cliKey) {
  // 1. CLI argument
  if (cliKey) return cliKey;

  // 2. Environment variable
  if (process.env.PMTINSP_API_KEY) return process.env.PMTINSP_API_KEY;

  // 3. ~/.openclaw/.env
  const dotenvPath = path.join(os.homedir(), ".openclaw", ".env");
  const dotenv = loadDotenv(dotenvPath);
  if (dotenv.PMTINSP_API_KEY) return dotenv.PMTINSP_API_KEY;

  console.error(
    "Error: No API key found.\n" +
    "  Provide it via --api-key, the PMTINSP_API_KEY environment variable,\n" +
    "  or add PMTINSP_API_KEY=... to ~/.openclaw/.env\n\n" +
    "  Get your key at https://promptinspector.io"
  );
  process.exit(1);
}

// ---------------------------------------------------------------------------
// API call
// ---------------------------------------------------------------------------

/**
 * Call the Prompt Inspector detection endpoint.
 * @param {string} text
 * @param {string} apiKey
 * @param {string} baseUrl
 * @param {number} timeout  milliseconds
 * @returns {Promise<object>}  parsed JSON response
 */
function detect(text, apiKey, baseUrl, timeout) {
  return new Promise((resolve, reject) => {
    const endpoint = new URL(DETECT_PATH, baseUrl.replace(/\/$/, ""));
    const body = JSON.stringify({ input_text: text });
    const isHttps = endpoint.protocol === "https:";
    const transport = isHttps ? https : http;

    const options = {
      hostname: endpoint.hostname,
      port: endpoint.port || (isHttps ? 443 : 80),
      path: endpoint.pathname,
      method: "POST",
      headers: {
        "X-App-Key": apiKey,
        "Content-Type": "application/json",
        "Accept": "application/json",
        "Content-Length": Buffer.byteLength(body),
      },
    };

    const req = transport.request(options, (res) => {
      let raw = "";
      res.setEncoding("utf-8");
      res.on("data", (chunk) => { raw += chunk; });
      res.on("end", () => {
        if (res.statusCode === 200) {
          try {
            resolve(JSON.parse(raw));
          } catch (_) {
            reject(new Error("Failed to parse API response as JSON."));
          }
          return;
        }
        // Handle HTTP error codes
        let detail = "";
        try { detail = JSON.parse(raw).detail || ""; } catch (_) { detail = raw.slice(0, 200); }
        const messages = {
          401: "Authentication failed. Check your API key.",
          403: "Authentication failed. Check your API key.",
          413: "Input text is too long for your current plan.",
          422: `Invalid request: ${detail}`,
          429: "Rate limit exceeded. Please slow down your requests.",
        };
        const msg = messages[res.statusCode] ||
          `API error (HTTP ${res.statusCode}): ${detail || "unexpected error"}`;
        reject(new Error(msg));
      });
    });

    req.setTimeout(timeout, () => {
      req.destroy(new Error(`Request timed out after ${timeout / 1000}s.`));
    });

    req.on("error", (err) => {
      if (err.message.includes("timed out")) {
        reject(new Error(`Request timed out after ${timeout / 1000}s.`));
      } else {
        reject(new Error(`Connection failed — ${err.message}`));
      }
    });

    req.write(body);
    req.end();
  });
}

// ---------------------------------------------------------------------------
// Output formatting
// ---------------------------------------------------------------------------

/**
 * @param {object} data  Raw API response
 * @param {string} fmt   "human" | "json"
 */
function printResult(data, fmt) {
  const result = data.result || {};
  if (fmt === "json") {
    const out = {
      request_id: data.request_id || "",
      is_safe: result.is_safe !== undefined ? result.is_safe : true,
      score: result.score !== undefined ? result.score : null,
      category: result.category || [],
      latency_ms: data.latency_ms || 0,
    };
    console.log(JSON.stringify(out, null, 2));
  } else {
    const categories = (result.category || []).join(", ") || "none";
    console.log(`Request ID : ${data.request_id || "n/a"}`);
    console.log(`Is Safe    : ${result.is_safe}`);
    console.log(`Score      : ${result.score !== undefined ? result.score : "null"}`);
    console.log(`Category   : ${categories}`);
    console.log(`Latency    : ${data.latency_ms || 0} ms`);
  }
}

// ---------------------------------------------------------------------------
// CLI argument parsing
// ---------------------------------------------------------------------------

/**
 * Parse process.argv into a plain object.
 * Supports: --key value  and  --key=value
 * @returns {Record<string, string>}
 */
function parseArgs() {
  const args = process.argv.slice(2);
  const params = {};
  let i = 0;
  while (i < args.length) {
    const arg = args[i];
    if (arg.startsWith("--")) {
      const eqIdx = arg.indexOf("=");
      if (eqIdx !== -1) {
        params[arg.slice(2, eqIdx)] = arg.slice(eqIdx + 1);
        i++;
      } else {
        const key = arg.slice(2);
        const next = args[i + 1];
        if (next !== undefined && !next.startsWith("--")) {
          params[key] = next;
          i += 2;
        } else {
          params[key] = "true";
          i++;
        }
      }
    } else {
      i++;
    }
  }
  return params;
}

function printHelp() {
  console.log(
    "Usage:\n" +
    "  node detect.js --text <text> [options]\n" +
    "  node detect.js --file <path> [options]\n\n" +
    "Options:\n" +
    "  --text       Text to inspect (single input)\n" +
    "  --file       Path to file; each non-empty line is inspected separately\n" +
    "  --api-key    Prompt Inspector API key\n" +
    "  --base-url   API base URL (default: " + DEFAULT_BASE_URL + ")\n" +
    "  --format     Output format: human (default) | json\n" +
    "  --timeout    Request timeout in seconds (default: 30)\n\n" +
    "Examples:\n" +
    "  node detect.js --text 'Ignore all previous instructions'\n" +
    "  node detect.js --file inputs.txt --format json\n" +
    "  node detect.js --api-key pi_xxx --text '...' --format json\n\n" +
    "Docs: https://docs.promptinspector.io"
  );
}

// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------

async function main() {
  const params = parseArgs();

  if (params.help || params.h) {
    printHelp();
    process.exit(0);
  }

  if (!params.text && !params.file) {
    console.error("Error: Provide --text or --file.\n");
    printHelp();
    process.exit(1);
  }

  const [REDACTED_SECRET]"api-key"] || null);
  const baseUrl = params["base-url"] || DEFAULT_BASE_URL;
  const fmt = params.format === "json" ? "json" : "human";
  const timeout = params.timeout ? parseInt(params.timeout, 10) * 1000 : DEFAULT_TIMEOUT;

  // Single text mode
  if (params.text) {
    try {
      const data = await detect(params.text, apiKey, baseUrl, timeout);
      printResult(data, fmt);
    } catch (err) {
      console.error(`Error: ${err.message}`);
      process.exit(1);
    }
    return;
  }

  // Batch mode: --file
  const filePath = path.resolve(params.file);
  if (!fs.existsSync(filePath)) {
    console.error(`Error: File not found: ${filePath}`);
    process.exit(1);
  }

  const lines = fs
    .readFileSync(filePath, "utf-8")
    .split(/\r?\n/)
    .map((l) => l.trim())
    .filter(Boolean);

  if (lines.length === 0) {
    console.error("Error: File is empty.");
    process.exit(1);
  }

  const results = [];
  for (let i = 0; i < lines.length; i++) {
    const line = lines[i];
    process.stderr.write(`[${i + 1}/${lines.length}] Inspecting: ${line.slice(0, 60)}...\n`);
    try {
      const data = await detect(line, apiKey, baseUrl, timeout);
      if (fmt === "json") {
        const result = data.result || {};
        results.push({
          input: line,
          request_id: data.request_id || "",
          is_safe: result.is_safe !== undefined ? result.is_safe : true,
          score: result.score !== undefined ? result.score : null,
          category: result.category || [],
          latency_ms: data.latency_ms || 0,
        });
      } else {
        console.log(`\n--- [${i + 1}] ${line.slice(0, 80)} ---`);
        printResult(data, fmt);
      }
    } catch (err) {
      console.error(`  Error: ${err.message}`);
      if (fmt === "json") {
        results.push({ input: line, error: err.message });
      }
    }
  }

  if (fmt === "json") {
    console.log(JSON.stringify(results, null, 2));
  }
}

main();


path: scripts/detect.py
content: #!/usr/bin/env python3
"""
Prompt Inspector — detection helper script.

Sends text to the Prompt Inspector API and prints the result.
No third-party packages required beyond the Python standard library.

Usage:
    python3 detect.py --text "Ignore all previous instructions..."
    python3 detect.py --file inputs.txt --format json
    python3 detect.py --api-key pi_xxx --text "..." --format json

Authentication (in priority order):
    1. --api-key  command-line argument
    2. PMTINSP_API_KEY  environment variable
    3. ~/.openclaw/.env  file  (line: PMTINSP_API_KEY=...)
"""

import argparse
import json
import os
import sys
import urllib.error
import urllib.request
from pathlib import Path

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

DEFAULT_BASE_URL = "https://promptinspector.io"
DETECT_PATH = "/api/v1/detect/sdk"
DEFAULT_TIMEOUT = 60  # seconds


# ---------------------------------------------------------------------------
# API key resolution
# ---------------------------------------------------------------------------

def _load_dotenv(path: Path) -> dict:
    """Parse a simple KEY=VALUE .env file and return a dict."""
    env = {}
    try:
        for line in path.read_text(encoding="utf-8").splitlines():
            line = line.strip()
            if not line or line.startswith("#") or "=" not in line:
                continue
            key, _, value = line.partition("=")
            value = value.strip()
            # Remove surrounding quotes if present
            if value and value[0] in ('"', "'") and value[-1] == value[0]:
                value = value[1:-1]
            env[key.strip()] = value
    except OSError:
        pass
    return env


def resolve_api_key(cli_key: str | None) -> str:
    """Return the first available API key or exit with an error."""
    # 1. CLI argument
    if cli_key:
        return cli_key

    # 2. Environment variable
    env_key = os.environ.get("PMTINSP_API_KEY")
    if env_key:
        return env_key

    # 3. ~/.openclaw/.env
    dotenv_path = Path.home() / ".openclaw" / ".env"
    dotenv = _load_dotenv(dotenv_path)
    dotenv_key = dotenv.get("PMTINSP_API_KEY")
    if dotenv_key:
        return dotenv_key

    print(
        "Error: No API key found.\n"
        "  Provide it via --api-key, the PMTINSP_API_KEY environment variable,\n"
        "  or add PMTINSP_API_KEY=... to ~/.openclaw/.env\n\n"
        "  Get your key at https://promptinspector.io",
        file=sys.stderr,
    )
    sys.exit(1)


# ---------------------------------------------------------------------------
# API call
# ---------------------------------------------------------------------------

def detect(text: str, api_key: str, base_url: str, timeout: int) -> dict:
    """
    Call the Prompt Inspector detection endpoint.

    Returns the parsed JSON response dict.
    Raises SystemExit on error.
    """
    url = base_url.rstrip("/") + DETECT_PATH
    payload = json.dumps({"input_text": text}).encode("utf-8")
    
    req = urllib.request.Request(url, data=payload, method="POST")
    req.add_header("X-App-Key", api_key)
    req.add_header("Content-Type", "application/json")
    req.add_header("Accept", "application/json")
    req.add_header("Content-Length", str(len(payload)))
    req.add_header("User-Agent", "python-httpx/0.28.1")

    try:
        with urllib.request.urlopen(req, timeout=timeout) as resp:
            body = resp.read().decode("utf-8")
            return json.loads(body)
    except urllib.error.HTTPError as exc:
        body = exc.read().decode("utf-8", errors="replace")
        _handle_http_error(exc.code, body)
    except urllib.error.URLError as exc:
        print(f"Error: Connection failed — {exc.reason}", file=sys.stderr)
        sys.exit(1)
    except TimeoutError:
        print(f"Error: Request timed out after {timeout}s.", file=sys.stderr)
        sys.exit(1)


def _handle_http_error(status: int, body: str) -> None:
    """Print a friendly error message for known HTTP status codes and exit."""
    try:
        detail = json.loads(body).get("detail", "")
    except Exception:
        detail = body[:200]

    messages = {
        401: "Authentication failed. Check your API key.",
        403: "Authentication failed. Check your API key.",
        413: "Input text is too long for your current plan.",
        422: f"Invalid request: {detail}",
        429: "Rate limit exceeded. Please slow down your requests.",
    }
    msg = messages.get(status, f"API error (HTTP {status}): {detail or 'unexpected error'}")
    print(f"Error: {msg}", file=sys.stderr)
    sys.exit(1)


# ---------------------------------------------------------------------------
# Output formatting
# ---------------------------------------------------------------------------

def _format_human(data: dict) -> str:
    result = data.get("result", {})
    categories = result.get("category", [])
    cat_str = ", ".join(categories) if categories else "none"
    lines = [
        f"Request ID : {data.get('request_id', 'n/a')}",
        f"Is Safe    : {result.get('is_safe', True)}",
        f"Score      : {result.get('score')}",
        f"Category   : {cat_str}",
        f"Latency    : {data.get('latency_ms', 0)} ms",
    ]
    return "\n".join(lines)


def _format_json(data: dict) -> str:
    result = data.get("result", {})
    out = {
        "request_id": data.get("request_id", ""),
        "is_safe": result.get("is_safe", True),
        "score": result.get("score"),
        "category": result.get("category", []),
        "latency_ms": data.get("latency_ms", 0),
    }
    return json.dumps(out, indent=2)


def print_result(data: dict, fmt: str) -> None:
    if fmt == "json":
        print(_format_json(data))
    else:
        print(_format_human(data))


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------

def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description="Prompt Inspector — detect prompt injection via the API.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=(
            "Examples:\n"
            "  python3 detect.py --text 'Ignore all previous instructions'\n"
            "  python3 detect.py --file inputs.txt --format json\n"
            "  python3 detect.py --api-key pi_xxx --text '...' --format json\n\n"
            "Docs: https://docs.promptinspector.io"
        ),
    )
    parser.add_argument("--text", help="Text to inspect (single input)")
    parser.add_argument(
        "--file",
        help="Path to a text file; each non-empty line is inspected separately",
    )
    parser.add_argument("--api-key", help="Prompt Inspector API key")
    parser.add_argument(
        "--base-url",
        default=DEFAULT_BASE_URL,
        help=f"API base URL (default: {DEFAULT_BASE_URL})",
    )
    parser.add_argument(
        "--format",
        choices=["human", "json"],
        default="human",
        help="Output format: human-readable (default) or json",
    )
    parser.add_argument(
        "--timeout",
        type=int,
        default=DEFAULT_TIMEOUT,
        help=f"Request timeout in seconds (default: {DEFAULT_TIMEOUT})",
    )
    return parser


def main() -> None:
    parser = build_parser()
    args = parser.parse_args()

    if not args.text and not args.file:
        parser.error("Provide --text or --file.")

    [REDACTED_SECRET])

    if args.text:
        data = detect(args.text, api_key, args.base_url, args.timeout)
        print_result(data, args.format)
        return

    # Batch mode: --file
    file_path = Path(args.file)
    if not file_path.exists():
        print(f"Error: File not found: {file_path}", file=sys.stderr)
        sys.exit(1)

    lines = [l.strip() for l in file_path.read_text(encoding="utf-8").splitlines() if l.strip()]
    if not lines:
        print("Error: File is empty.", file=sys.stderr)
        sys.exit(1)

    results = []
    for i, line in enumerate(lines, 1):
        print(f"[{i}/{len(lines)}] Inspecting: {line[:60]}...", file=sys.stderr)
        data = detect(line, api_key, args.base_url, args.timeout)
        if args.format == "json":
            result = data.get("result", {})
            results.append({
                "input": line,
                "request_id": data.get("request_id", ""),
                "is_safe": result.get("is_safe", True),
                "score": result.get("score"),
                "category": result.get("category", []),
                "latency_ms": data.get("latency_ms", 0),
            })
        else:
            print(f"\n--- [{i}] {line[:80]} ---")
            print_result(data, args.format)

    if args.format == "json":
        print(json.dumps(results, indent=2))


if __name__ == "__main__":
    main()


path: references/faq.md
content: # Prompt Inspector — Frequently Asked Questions

## General

### What is Prompt Inspector?

Prompt Inspector is a real-time API service that detects prompt injection attacks, jailbreak attempts, and adversarial manipulations in text before it reaches your language model. It acts as a security layer for any LLM-based application.

### What types of attacks does it detect?

**Category**
+ `prompt_injection`
+ `jailbreak`
+ `instruction_override`
+ `role_play_escape`
+ `data_exfiltration`


### Is Prompt Inspector open source?

The core engine is open source at [github.com/aunicall/prompt-inspector](https://github.com/aunicall/prompt-inspector). The hosted API at [promptinspector.io](https://promptinspector.io) provides a fully managed, production-ready deployment.

---

## Authentication & API Keys

### Where do I get an API key?

Sign up at [promptinspector.io](https://promptinspector.io) and generate a key from your dashboard.

### How do I set my API key for the helper scripts?

The scripts check three locations in order:

1. `--api-key pi_xxx` (CLI argument)
2. `export PMTINSP_API_KEY=pi_xxx` (environment variable)
3. `~/.openclaw/.env` file with the line `PMTINSP_API_KEY=pi_xxx`

The environment variable approach is recommended for day-to-day use:

```bash
export PMTINSP_API_KEY=your-api-key
```

### I'm getting `Authentication failed`. What should I check?

- Ensure you copied the full key without leading/trailing spaces.
- Verify the key is active in your dashboard at [promptinspector.io](https://promptinspector.io).
- If you rotated your key, make sure the old key is not still being used.

---

## API Usage

### What is the API endpoint?

```
POST https://promptinspector.io/api/v1/detect/sdk
```

### What does a minimal API request look like?

```bash
curl -X POST https://promptinspector.io/api/v1/detect/sdk \
  -H "X-App-Key: your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"input_text": "..."}'
```

### What does the response look like?

```json
{
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "latency_ms": 28,
  "result": {
    "is_safe": false,
    "score": 0.97,
    "category": ["prompt_injection"]
  }
}
```

### What does `score: null` mean?

When `is_safe` is `true` and no threat is detected, the score is returned as `null` (not applicable). Your code should handle this case — treat `null` the same as a score of `0`.

### What is the maximum text length?

This depends on your plan tier:

| Plan       | Max text length |
| ---------- | --------------- |
| Free       | 2,000 chars     |
| Starter    | 10,000 chars    |
| Pro        | 50,000 chars    |
| Enterprise | Custom          |

Exceeding the limit returns HTTP 413. Upgrade your plan at [promptinspector.io](https://promptinspector.io).

---

## Helper Scripts

### Do the scripts require installing any packages?

No. Both `detect.py` (Python 3.8+) and `detect.js` (Node.js 14+) use only standard library modules. No `pip install` or `npm install` needed.

### How do I inspect multiple texts at once?

Use the `--file` option. Put each text on its own line in a plain text file:

```bash
python3 detect.py --file my_inputs.txt --format json > results.json
```

```bash
node detect.js --file my_inputs.txt --format json > results.json
```

### How do I use a self-hosted Prompt Inspector instance?

Pass the `--base-url` option:

```bash
python3 detect.py --base-url https://your-server.com --text "..."
node detect.js  --base-url https://your-server.com --text "..."
```

### The script prints to stderr while running in batch mode — is that intentional?

Yes. Progress messages (e.g., `[1/10] Inspecting: ...`) are written to **stderr** so they don't pollute the **stdout** JSON output when piping with `--format json`.

---

## Errors & Troubleshooting

### HTTP 401 / 403 — Authentication failed

Your API key is missing, incorrect, or expired. See [Authentication & API Keys](#authentication--api-keys) above.

### HTTP 413 — Input too long

Your text exceeds the maximum length for your plan. Truncate the input or upgrade your plan.

### HTTP 422 — Invalid request

The request body is malformed. Ensure `input_text` is a non-empty string.

### HTTP 429 — Rate limit exceeded

You have exceeded the request rate for your plan. Slow down your requests or upgrade to a higher-tier plan.

### Connection timeout

- Check your network connection and firewall settings.
- Verify the `--base-url` is correct.
- Try increasing the timeout: `--timeout 60`.

---

## Plans & Pricing

### Is there a free tier?

Yes. The Free plan includes 1,000 requests per month with up to 2,000 characters per text.

### How do I upgrade?

Visit [promptinspector.io](https://promptinspector.io) and go to your billing dashboard.

### Do you offer enterprise or self-hosted plans?

Yes. Contact [[REDACTED_SECRET]](mailto:[REDACTED_SECRET]) for enterprise pricing and self-hosting options.

---

## Further Help

- **Docs:** [docs.promptinspector.io](https://docs.promptinspector.io)
- **Email:** [[REDACTED_SECRET]](mailto:[REDACTED_SECRET])
- **GitHub:** [github.com/aunicall/prompt-inspector](https://github.com/aunicall/prompt-inspector)


path: references/product-info.md
content: # Prompt Inspector — Product Information

## About Prompt Inspector

> **Shield your AI from adversarial prompts.**

**Prompt Inspector** is a production-grade API service that detects prompt injection attacks, jailbreak attempts, and adversarial manipulations in real time — before they reach your language model.

Whether you're building a customer-facing chatbot, an internal AI assistant, or an automated LLM pipeline, Prompt Inspector acts as a security layer that keeps malicious user inputs from hijacking your AI's behavior.

- **Website:** [promptinspector.io](https://promptinspector.io)
- **Documentation:** [docs.promptinspector.io](https://docs.promptinspector.io)
- **Open Source:** [github.com/aunicall/prompt-inspector](https://github.com/aunicall/prompt-inspector)
- **Contact:** [[REDACTED_SECRET]](mailto:[REDACTED_SECRET])

---

## Why Prompt Inspector?

### Real-time Detection
Sub-100 ms latency, built for high-throughput production use. Designed to handle thousands of requests per second without becoming a bottleneck in your AI pipeline.

### Threat Categorization
Returns specific threat categories (e.g., `instruction_override`, `jailbreak`, `syntax_injection`) so you can understand exactly what type of attack was attempted and respond accordingly.

### Risk Scoring
Continuous 0–1 risk score allows you to tune your own thresholds. Not every input is black-and-white — the score gives you flexibility to implement soft warnings, rate limiting, or graduated responses based on risk level.

### Simple REST API
One endpoint, one JSON request, one JSON response. No complex SDKs to learn, no multi-step authentication flows. Just send text, get a verdict.

### Official SDKs
- **Python:** `pip install prompt-inspector`
- **Node.js:** `npm install prompt-inspector`

Both SDKs handle authentication, retries, and error handling automatically.

### Self-Hostable
Open source core available at [github.com/aunicall/prompt-inspector](https://github.com/aunicall/prompt-inspector). Deploy on your own infrastructure for maximum control, data privacy, and compliance with internal security policies.

---

## Threat Categories

Prompt Inspector detects 10 distinct threat categories across 5 attack domains:

### Logic & Control Payloads
+ `instruction_override`
+ `asset_extraction`

### Structural Payloads
+ `syntax_injection`

### Semantic Payloads
+ `jailbreak`
+ `response_forcing`
+ `euphemism_bypass`

### Agent Exec Payloads
+ `reconnaissance_probe`
+ `parameter_injection`

### Obfuscated Payloads
+ `encoded_payload`

### Tenant Customization
+ `custom_sensitive_word`


### Semantic Payloads
+ `jailbreak`
+ `response_forcing`
+ `euphemism_bypass`



### Agent Exec Payloads
+ `reconnaissance_probe`
+ `parameter_injection`

### Obfuscated Payloads
+ `encoded_payload`

### Tenant Customization
+ `custom_sensitive_word`


---

## Detection Response Fields

When a threat is detected, the API returns:

| Field | Type | Description |
| ----- | ---- | ----------- |
| `is_safe` | boolean | `false` if any threat detected |
| `score` | float (0–1) | Risk score; higher = more dangerous |
| `category` | array of strings | List of detected threat categories (can be multiple) |
| `request_id` | string | Unique ID for logging and debugging |
| `latency_ms` | integer | Server-side processing time |

---

## Use Cases

- **Chatbot Protection** — Block jailbreak attempts before they reach your customer-facing AI
- **Agent Security** — Prevent parameter injection attacks on tool-calling LLMs
- **Content Moderation** — Detect euphemism bypass and encoded payloads in user-generated content
- **Compliance** — Enforce custom sensitive word policies for regulated industries
- **Research & Red Teaming** — Analyze adversarial prompt datasets and measure model robustness

---

## Getting Started

1. Sign up at [promptinspector.io](https://promptinspector.io)
2. Create an app and generate an API key in Free or Pro plan.
3. Start detecting.
For detailed integration guides, see [usage.md](./usage.md) and [faq.md](./faq.md).


path: references/usage.md
content: # Prompt Inspector — Usage Guide

## Overview

Prompt Inspector protects your LLM-based applications by detecting adversarial user inputs before they reach your language model. This guide covers common integration patterns and usage scenarios.

---

## Authentication

All API requests require an API key sent in the `X-App-Key` HTTP header.

**Obtain your key:** Sign up at [promptinspector.io](https://promptinspector.io).

The helper scripts resolve the key in this order:

| Priority | Source                              |
| -------- | ----------------------------------- |
| 1        | `--api-key` CLI argument            |
| 2        | `PMTINSP_API_KEY` environment variable |
| 3        | `~/.openclaw/.env` → `PMTINSP_API_KEY=...` |

---

## Quick Start

### Python

```bash
# Set your key once
export PMTINSP_API_KEY=your-api-key

# Inspect a single text
python3 {baseDir}/scripts/detect.py --text "User input content."
```

**Output:**
```
Request ID : a1b2c3d4-e5f6-...
Is Safe    : False
Score      : 0.97
Category   : prompt_injection
Latency    : 28 ms
```

### Node.js

```bash
export PMTINSP_API_KEY=your-api-key

node {baseDir}/scripts/detect.js --text "User input content."
```

---

## Output Formats

### Human-readable (default)

Suitable for interactive use and debugging.

```bash
python3 {baseDir}/scripts/detect.py --text "..." --format human
```

```
Request ID : a1b2c3d4-...
Is Safe    : False
Score      : 0.97
Category   : prompt_injection, jailbreak
Latency    : 34 ms
```

### JSON

Suitable for scripting, piping, and programmatic use.

```bash
python3 {baseDir}/scripts/detect.py --text "..." --format json
```

```json
{
  "request_id": "a1b2c3d4-...",
  "is_safe": false,
  "score": 0.97,
  "category": ["prompt_injection", "jailbreak"],
  "latency_ms": 34
}
```

---

## Batch Detection

Inspect multiple texts from a file — one text per line:

```bash
# Create a test file
cat > inputs.txt <<EOF
Hello, how are you?
What is the capital of France?
EOF

# Run batch detection
python3 {baseDir}/scripts/detect.py --file inputs.txt

# JSON output for downstream processing
python3 {baseDir}/scripts/detect.py --file inputs.txt --format json > results.json
```

Node.js equivalent:

```bash
node {baseDir}/scripts/detect.js --file inputs.txt --format json > results.json
```

---

## Self-Hosted / Custom Endpoint

If you run Prompt Inspector on your own infrastructure:

```bash
python3 {baseDir}/scripts/detect.py \
  --base-url https://your-server.com \
  --text "..."
```

```bash
node {baseDir}/scripts/detect.js \
  --base-url https://your-server.com \
  --text "..."
```

---

## Understanding the Response

| Field        | Type            | Description                                                    |
| ------------ | --------------- | -------------------------------------------------------------- |
| `request_id` | string          | Unique ID for this detection request (useful for logging)      |
| `is_safe`    | boolean         | `true` = input appears safe; `false` = threat detected         |
| `score`      | float or null   | Risk score 0–1. Higher = more likely adversarial. `null` if safe |
| `category`   | array of string | Detected threat categories (empty array if safe)               |
| `latency_ms` | integer         | Server-side processing time in milliseconds                    |

### Threat Categories

| Category               | Description                                              |
| ---------------------- | -------------------------------------------------------- |
| `prompt_injection`     | Classic instruction override / prompt hijacking          |
| `jailbreak`            | Attempts to bypass safety guidelines or restrictions     |
| `instruction_override` | Commands to ignore system prompts or previous context    |
| `role_play_escape`     | Persona-switching attacks (e.g., "pretend you are DAN")  |
| `data_exfiltration`    | Attempts to extract system prompts or sensitive data     |

---

## Integration Patterns

### Pattern 1 — Hard block

Reject any input where `is_safe` is `false`:

```python
result = client.detect(user_input)
if not result.is_safe:
    return "Your input was flagged as potentially unsafe."
# Forward to LLM
```

### Pattern 2 — Score threshold

Tune sensitivity with a custom threshold:

```python
result = client.detect(user_input)
THRESHOLD = 0.8
if result.score is not None and result.score >= THRESHOLD:
    return "High-risk input detected."
```

### Pattern 3 — Category-based routing

Allow some categories while blocking others:

```python
result = client.detect(user_input)
BLOCKED = {"prompt_injection", "jailbreak", "data_exfiltration"}
if set(result.category) & BLOCKED:
    return "This type of input is not allowed."
```

---

## Rate Limits & Plan Tiers

| Plan       | Requests / month | Max text length |
| ---------- | ---------------- | --------------- |
| Free       | 1,000            | 2,000 chars     |
| Starter    | 50,000           | 10,000 chars    |
| Pro        | Unlimited        | 50,000 chars    |
| Enterprise | Custom           | Custom          |

Upgrade or check usage at [promptinspector.io](https://promptinspector.io).

---

## Further Resources

- **API Reference:** [docs.promptinspector.io](https://docs.promptinspector.io)
- **Python SDK:** `pip install prompt-inspector`
- **Node.js SDK:** `npm install prompt-inspector`
- **Open Source:** [github.com/aunicall/prompt-inspector](https://github.com/aunicall/prompt-inspector)
- **Support:** [[REDACTED_SECRET]](mailto:[REDACTED_SECRET])


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (HIGH)