caido-plugin-dev

star 21

Comprehensive skill for developing Caido plugins (frontend, backend, or both). Use this skill when creating Caido security testing tool plugins, extending Caido functionality, or building custom automation for web application security auditing within the Caido platform.

caido-community By caido-community schedule Updated 12/1/2025

name: caido-plugin-dev description: Comprehensive skill for developing Caido plugins (frontend, backend, or both). Use this skill when creating Caido security testing tool plugins, extending Caido functionality, or building custom automation for web application security auditing within the Caido platform.

Caido Plugin Development

Overview

Caido is a lightweight web security auditing toolkit designed for penetration testing and vulnerability assessment. This skill provides comprehensive guidance for developing Caido plugins using the official SDK ecosystem.

Key Resources:

About Caido

Caido is a modern web security auditing platform similar to Burp Suite, designed for penetration testers and security researchers. It operates on a client-server architecture and can run locally or on remote instances (VPS, Docker, cloud).

Core Architecture

  • Client-Server Model: Supports both desktop application and CLI deployment
  • Technology Stack: Vue.js frontend, Rust backend, GraphQL API
  • Data Storage: SQLite database for persistent project data
  • Proxy System: HTTP/HTTPS traffic interception with dynamic certificate generation

Main Features and Tabs

Understanding Caido's core features is essential for plugin development, as plugins often extend or integrate with these existing capabilities:

Proxy & Traffic Management:

  • Intercept: Real-time request/response interception and modification - users can pause, inspect, and modify HTTP traffic before it's sent or received
  • HTTP History: Complete log of all proxied traffic with filtering and search capabilities
  • Search: Advanced searching across all captured traffic using HTTPQL query language
  • Sitemap: Visual tree representation of discovered URLs and application structure

Testing and Automation:

  • Replay (Repeater): Manual request crafting and modification tool - send and resend individual HTTP requests with custom modifications, similar to Burp's Repeater
  • Automate: Fuzzing and automated testing capabilities - run payloads against endpoints, brute-force attacks, parameter testing
  • Workflows: Multi-step automation sequences that can be passive (automatic analysis) or active (user-triggered)
  • Match & Replace: Automatic request/response modification rules that apply to all traffic

Security Analysis:

  • Findings: Centralized vulnerability management - document and track security issues discovered during testing
  • Assistant: AI-powered feature that suggests attack vectors and generates proof-of-concept exploits
  • Scopes: Define which domains/subdomains are in-scope for testing to focus analysis

Data Management:

  • Projects: Organize work by project with persistent storage and backup/restore
  • Filters: Create saved filters using HTTPQL to categorize and segment traffic
  • Environment Variables: Store and manage configuration values that can be used across workflows and plugins
  • Export: JSON/CSV export capabilities for reporting and data analysis

HTTPQL Query Language

HTTPQL is the query language we use in Caido to let you filtering requests and responses.

Caido uses HTTPQL for filtering requests throughout the application. Common query examples:

  • req.ext.eq:".js" - Filter by file extension
  • resp.code.eq:200 - Filter by status code
  • req.method.eq:"GET" - Filter by HTTP method
  • req.ext.eq:".php" AND resp.code.eq:200 - Combine filters with boolean operators

Understanding HTTPQL is important for plugins that query or filter traffic.

WebSocket Support

Caido supports WebSocket traffic inspection and manipulation, allowing real-time analysis of WebSocket streams.

Plugin Integration Points

Plugins can extend virtually any part of Caido:

  • Add custom pages to the navigation
  • Inject UI components into existing pages via slots
  • Register commands in the command palette
  • Add context menu items to requests/responses
  • Process traffic automatically via intercept callbacks
  • Create custom automation workflows
  • Generate security findings programmatically
  • Access and query the project database
  • Integrate with external tools and APIs

Reference: See https://docs.caido.io/llms.txt for complete Caido platform documentation

Plugin Types

Caido supports three types of plugins, each with its own SDK:

  1. Frontend Plugins (@caido/sdk-frontend) - UI components, custom pages, commands, context menus
  2. Backend Plugins (@caido/sdk-backend) - Server-side logic, HTTP operations, data processing, findings
  3. Workflow Plugins (@caido/sdk-workflow) - JavaScript nodes for automation sequences

Plugins can combine frontend and backend components in a single package. In most cases, we're going to focus on frontend and backend plugins.

Quick Start: Creating a New Plugin

IMPORTANT: Always start a new Caido plugin project using the official starter CLI:

pnpm create @caido-community/plugin

This is the standard and recommended way to initialize any new Caido plugin project.

Prerequisites

  • Node.js 18+ or 20+
  • pnpm package manager

Initialize a New Plugin

Use the official starter CLI to scaffold a new plugin:

pnpm create @caido-community/plugin

This creates a template with:

  • caido.config.ts - Plugin configuration
  • manifest.json - Auto-generated metadata
  • Frontend and/or backend starter code
  • Build configuration

Reference: https://developer.caido.io/guides/ (Getting Started section)

Install Dependencies

pnpm install

Build the Plugin

pnpm build

This produces dist/plugin_package.zip ready for installation in Caido.

Project Structure

A typical Caido plugin follows this structure:

my-plugin/
├── caido.config.ts          # Plugin configuration
├── manifest.json            # Auto-generated from config
├── package.json
├── pnpm-workspace.yaml      # If using monorepo
├── packages/
│   ├── frontend/            # Frontend plugin code
│   │   ├── src/
│   │   │   ├── index.ts     # Entry point
│   │   │   └── types.ts     # TypeScript types
│   │   └── style.css        # Optional styles
│   └── backend/             # Backend plugin code
│       ├── src/
│       │   └── index.ts     # Entry point
│       └── assets/          # Backend-accessible files
└── dist/                    # Build output
  • packages/backend → Backend plugin code - handles server-side logic, data processing, and API endpoints
  • packages/frontend → Frontend plugin code - handles UI components, user interactions, and calls to backend using sdk.backend.functionName

Example: See https://github.com/caido-community/plugin-demo for official demo structure

Configuration Files

caido.config.ts

The primary configuration file that defines your plugin package. Uses defineConfig from @caido-community/dev.

Reference: https://developer.caido.io/reference/config

Common Configuration:

import { defineConfig } from '@caido-community/dev';

export default defineConfig({
  id: 'my-plugin',                    // Unique ID: lowercase, hyphens/underscores
  name: 'My Plugin',                  // Display name
  description: 'Plugin description',
  version: '1.0.0',                   // Semantic versioning
  author: {
    name: 'Your Name',
    email: 'you@example.com',         // Optional
    url: 'https://yoursite.com'       // Optional
  },
  plugins: [
    // Frontend plugin config
    {
      kind: 'frontend',
      id: 'my-plugin-frontend',
      name: 'My Plugin Frontend',
      root: './packages/frontend',
      backend: {
        id: 'my-plugin-backend'       // Link to backend plugin
      }
    },
    // Backend plugin config
    {
      kind: 'backend',
      id: 'my-plugin-backend',
      name: 'My Plugin Backend',
      root: './packages/backend',
      assets: ['assets/**/*']         // (optional) Glob patterns for assets
    }
  ]
});

manifest.json

Auto-generated from caido.config.ts during build. Defines plugin metadata and installation structure.

Reference: https://developer.caido.io/reference/manifest

Key Fields:

  • id - Unique identifier matching ^[a-z][a-z0-9]+(?:[_-][a-z0-9]+)*$
  • version - MAJOR.MINOR.PATCH format
  • plugins - Array of frontend/backend/workflow definitions
  • author - Creator information (name, email, url)
  • links - Sponsor/funding URLs

Note: Manually edit only if not using caido.config.ts

Frontend Plugin Development

Frontend plugins create UI components, custom pages, commands, and context menus using Vue.js and the Caido frontend SDK. The Caido Frontend SDK is used for creating UI components, pages, and handling user interactions in Caido plugins.

SDK Reference: https://developer.caido.io/reference/sdks/frontend/

Entry Point

Frontend plugins are initialized via packages/frontend/src/index.ts:

Basic Frontend Plugin Structure

import { Caido } from "@caido/sdk-frontend";
import { API, BackendEvents } from "backend";

// Define SDK type with backend API
export type FrontendSDK = Caido<API, BackendEvents>;

// Plugin initialization
export const init = (sdk: FrontendSDK) => {
  // Create pages and UI
  createPage(sdk);

  // Register sidebar items
  sdk.sidebar.registerItem("My Plugin", "/my-plugin-page", {
    icon: "fas fa-rocket"
  });

  // Register commands
  sdk.commands.register("my-command", {
    name: "My Custom Command",
    run: () => sdk.backend.myCustomFunction("Hello"),
  });
};

UI Components

The frontend SDK provides styled components that match Caido's design system:

HTTP Editors

const requestEditor = caido.ui.httpRequestEditor({
  id: 'my-request-editor',
  options: { readOnly: false }
});

const responseEditor = caido.ui.httpResponseEditor({
  id: 'my-response-editor',
  options: { readOnly: true }
});

Registering Custom Pages

Add custom pages to Caido's navigation:

sdk.navigation.addPage("/my_plugin", {
  body: root
});

Navigation:

// Navigate programmatically
sdk.navigation.goTo('/my-plugin');

// Listen for page changes
sdk.navigation.onPageChange((path) => {
  console.log('Navigated to:', path);
});

SDK Type Definitions

For plugins WITHOUT backend, this is fine:

export type FrontendSDK = Caido<Record<string, never>, Record<string, never>>;

For plugins WITH backend:

import { Caido } from "@caido/sdk-frontend";
import { API, BackendEvents } from "backend";

export type FrontendSDK = Caido<API, BackendEvents>;

Command Pattern

Commands provide a unified way to register actions that can be triggered from:

  • Command palette (Ctrl/Cmd+Shift+P)
  • Context menus (right-click)
  • UI buttons
  • Keyboard shortcuts

Commands is a frontend-only concept.

// Define command IDs as constants
const Commands = {
  processData: "my-plugin.process-data",
  exportResults: "my-plugin.export-results",
} as const;

// Register commands
sdk.commands.register(Commands.processData, {
  name: "Process Data",
  run: async () => {
    const result = await sdk.backend.processData();
    sdk.window.showToast(`Processed ${result.count} items`, {
      variant: "success"
    });
  },
  group: "My Plugin",
});

// Add to command palette
sdk.commandPalette.register(Commands.processData);

// Add to context menus
sdk.menu.registerItem({
  type: "Request",
  commandId: Commands.processData,
  leadingIcon: "fas fa-cog",
});

Context Menus

Add commands to context menus in various Caido views:

// Request context menu
sdk.menu.registerItem({
  type: 'Request',
  commandId: 'my-plugin:process-request',
  leadingIcon: 'fas fa-cog'
});

// Response context menu
sdk.menu.registerItem({
  type: 'Response',
  commandId: 'my-plugin:process-response'
});

// Request row context menu (in tables)
sdk.menu.registerItem({
  type: 'RequestRow',
  commandId: 'my-plugin:analyze-request'
});

// Settings menu
sdk.menu.registerItem({
  type: 'Settings',
  commandId: 'my-plugin:open-settings'
});

Data Persistence (Frontend Storage)

Store plugin configuration and data:

// Define storage type
type MyStorage = {
  notes: Array<{
    content: string;
    timestamp: string;
  }>;
  settings: {
    enabled: boolean;
  };
};

// Get data
const data = await sdk.storage.get<MyStorage>();

// Set data
await sdk.storage.set<MyStorage>({
  notes: [...],
  settings: { enabled: true }
});

// Listen for changes
sdk.storage.onChange<MyStorage>((newData) => {
  console.log('Storage updated:', newData);
});

Example: See https://developer.caido.io/tutorials/notebook for a complete storage example

Backend Communication

Call backend functions and subscribe to events:

// Call backend function
const result = await sdk.backend.myBackendFunction('param');

// Subscribe to backend events
sdk.backend.onEvent("my_plugin:request_received", (data) => {
  console.log(data)
});

Error Handling

When calling backend APIs from the frontend, handle Result types gracefully:

// Frontend usage - no try/catch needed
const handleProcess = async () => {
  const result = await sdk.backend.processData(inputValue);

  if (result.kind === "Error") {
    sdk.window.showToast(result.error, { variant: "error" });
    return;
  }

  // Handle successful result
  const data = result.value;
  sdk.window.showToast("Processing completed!", { variant: "success" });
};

Environment Variables

Access Caido environment variables:

const apiKey = await sdk.env.getVar('API_KEY');

Types:

/**
 * Utilities to interact with the environment.
 * @category Environment
 */
export type EnvironmentSDK = {
    /**
     * Get the value of an environment variable.
     * @param name The name of the environment variable.
     * @returns The value of the environment variable.
     */
    getVar: (name: string) => string | undefined;
    /**
     * Get all environment variables available in the global environment and the selected environment.
     * @returns All environment variables.
     */
    getVars: () => EnvironmentVariable[];
};

export type EnvironmentVariable = {
    /**
     * The name of the environment variable.
     */
    name: string;
    /**
     * The value of the environment variable.
     */
    value: string;
    /**
     * Whether the environment variable is a secret.
     */
    isSecret: boolean;
};

Page-Specific SDKs

Caido provides specialized SDKs for extending specific pages:

HTTP History:

sdk.httpHistory.setQuery("resp.raw.cont:\"hello\"")

Search:

sdk.search.setQuery("resp.raw.cont:\"hello\"")

Replay:

const session = await sdk.replay.createSession({
  name: 'My Session',
  requests: [requestId1, requestId2]
});

/**
  * Create a session.
  * @param sessionId The ID of the request to add.
  * @param collectionId The ID of the collection to add the request.
  */
createSession: (source: RequestSource, collectionId?: ID) => Promise<void>;

/**
 * @category Replay
 *
 * @remarks
 * This type is a discriminated union with two possible shapes:
 * - A raw request, containing the raw HTTP request string and connection information.
 * - A reference to an existing request ID.
 *
 * @example
 * // Using a raw request
 * const source: RequestSource = {
 *   type: "Raw",
 *   raw: "GET /api/data HTTP/1.1",
 *   connectionInfo: { ... }
 * };
 * // Using an ID
 * const source: RequestSource = {
 *   type: "ID",
 *   id: "request-123"
 * };
 */

export type RequestSource = {
    type: "Raw";
    raw: string;
    connectionInfo: SendRequestOptions["connectionInfo"];
} | {
    type: "ID";
    id: string;
};

/**
 * The connection information to use for the request.
 */
connectionInfo: {
    /**
     * The host to use for the request.
     */
    host: string;
    /**
     * The port to use for the request.
     */
    port: number;
    /**
     * Whether the request is TLS.
     */
    isTLS: boolean;
    /**
     * The SNI to use for the request.
     * If not provided, the SNI will be inferred from the host.
     */
    SNI?: string;
};

Findings:

const finding = await sdk.findings.createFinding(requestId, {
  title: 'XSS Vulnerability',
  description: 'Reflected XSS found',
  reporter: 'My Plugin'
});

Automate, Intercept, Sitemap: See SDK docs for specialized methods

Toasts

Toasts:

sdk.window.showToast("Hello", {
  variant: 'success',  // "success" | "error" | "warning" | "info";
  duration: 3000
});

Active Editor Access

Get content from the currently focused editor:

const editor = sdk.window.getActiveEditor();
if (editor) {
  const content = editor.getContent();
  const selection = editor.getSelection();
}

Slots and Extensions

UI slots allow adding buttons, commands, or custom components to predefined locations in Caido's interface:

import { FooterSlot } from "@caido/sdk-frontend";

// Add a button to the footer
sdk.ui.addToSlot(FooterSlot.FooterSlotPrimary, {
  component: MyCustomButton,
  props: { label: "My Action" }
});

Available slot locations include footer areas and replay toolbars. Pass slot identifiers to addToSlot() to specify placement.

Custom Request View Modes

Custom request view modes display requests in alternative formats across multiple pages (HTTP History, Replay, Search, etc.):

// Register a custom view mode
sdk.ui.addRequestViewMode({
  id: "my-custom-view",
  name: "Custom View",
  component: MyViewComponent
});

The Vue component receives sdk, request, and optionally requestDraft props.

Command Palette Custom Views

The command palette supports pushing custom Vue components for multi-step wizards and custom search interfaces:

// Push a custom view to the command palette
sdk.commandPalette.pushView({
  component: MyCustomSearchComponent
});

Components automatically receive the SDK as a prop and can emit events and update state reactively.

Keyboard Shortcuts

Register keyboard shortcuts for commands:

sdk.shortcuts.register("my-plugin.action", {
  key: "ctrl+shift+a",
  mac: "cmd+shift+a"
});

Complete Frontend SDK Reference: https://developer.caido.io/reference/sdks/frontend/

Backend Plugin Development

Backend plugins handle server-side logic, HTTP operations, data processing, findings generation, and more. They run in a QuickJS runtime with Node.js-compatible APIs.

SDK Reference: https://developer.caido.io/reference/sdks/backend/

Entry Point

Backend plugins are initialized via packages/backend/src/index.ts:

Basic Backend Plugin Structure

  import { SDK, DefineAPI } from "caido:plugin";

  // Define your API functions
  function myCustomFunction(sdk: SDK, param: string) {
    sdk.console.log(`Called with: ${param}`);
    return `Processed: ${param}`;
  }

  // Export the API type definition
  export type API = DefineAPI<{
    myCustomFunction: typeof myCustomFunction;
  }>;

  // Plugin initialization
  export function init(sdk: SDK<API>) {
    // Register API endpoints
    sdk.api.register("myCustomFunction", myCustomFunction);
  }

SDK Type Definitions

Backend SDK with events:

  import { DefineEvents, SDK } from "caido:plugin";

  export type BackendEvents = DefineEvents<{
    "data-updated": { message: string };
    "status-changed": { status: "active" | "inactive" };
  }>;

  export type CaidoBackendSDK = SDK<never, BackendEvents>;

Best Practices

When building API endpoints in the backend and calling them from the frontend, use Result types to handle errors gracefully without throwing exceptions:

  // Define the Result type
  export type Result<T> =
    | { kind: "Error"; error: string }
    | { kind: "Ok"; value: T };

  // Backend API function returning Result
  function processData(sdk: SDK, input: string): Result<ProcessedData> {
    try {
      // Your processing logic here
      const processed = doSomeProcessing(input);
      return { kind: "Ok", value: processed };
    } catch (error) {
      return { kind: "Error", error: error.message };
    }
  }

  // Frontend usage - no try/catch needed
  const handleProcess = async () => {
    const result = await sdk.backend.processData(inputValue);

    if (result.kind === "Error") {
      sdk.window.showToast(result.error, { variant: "error" });
      return;
    }

    // Handle successful result
    const data = result.value;
    sdk.window.showToast("Processing completed!", { variant: "success" });
  };

Registering Multiple API Endpoints

  // Define multiple API functions
  function getData(sdk: SDK, id: string): Result<Data> {
    // Implementation
  }

  function saveData(sdk: SDK, data: Data): Result<void> {
    // Implementation
  }

  function deleteData(sdk: SDK, id: string): Result<boolean> {
    // Implementation
  }

  // Export Caido Backend API
  export type API = DefineAPI<{
    getData: typeof getData;
    saveData: typeof saveData;
    deleteData: typeof deleteData;
  }>;

  // Register all endpoints
  export function init(sdk: SDK<API>) {
    sdk.api.register("getData", getData);
    sdk.api.register("saveData", saveData);
    sdk.api.register("deleteData", deleteData);
  }

RPC and Frontend Communication

Send Events to Frontend:

sdk.api.send('eventName', eventData);

Working with Requests and Responses

Creating and Sending Requests

import { RequestSpec } from "caido:utils";
import { type Request, type Response } from "caido:utils";

// Create a new request
const spec = new RequestSpec("https://api.example.com/data");
spec.setMethod("POST");
spec.setHeader("Content-Type", "application/json");
spec.setBody(JSON.stringify({ key: "value" }));

// Send the request
const result = await sdk.requests.send(spec);
if (result.response) {
  const statusCode = result.response.getCode();
  const responseBody = result.response.getBody()?.toText();
}

Get Saved Requests by ID:

const request = await sdk.requests.get(requestId);

Check Request Scope:

const inScope = await sdk.requests.inScope(requestUrl);

Test HTTPQL Filters:

const matches = await sdk.requests.matches(request, 'ext:php AND status:200');

Request/Response Data Models

Request:

const method = request.getMethod();
const url = request.getUrl();
const path = request.getPath();
const query = request.getQuery();
const headers = request.getHeaders();
const body = request.getBody();

// Convert body
const bodyText = body?.toText();      // Returns string (unprintable → �)
const bodyJson = body?.toJson();      // Parse as JSON
const bodyRaw = body?.toRaw();        // Get raw bytes

// Get specific header (case-insensitive)
const contentType = request.getHeader('content-type');

// Convert to mutable spec
const spec = request.toSpec();
spec.setHeader('X-Custom', 'value');

Response:

const code = response.getCode();
const statusLine = response.getStatusLine();
const headers = response.getHeaders();
const body = response.getBody();

Intercept Callbacks

Intercept and modify requests/responses in real-time:

sdk.events.onInterceptRequest(async (sdk, request) => {
  // Modify request
  const spec = request.toSpec();
  spec.setHeader('X-Intercepted', 'true');
  return spec;
});

sdk.events.onInterceptResponse(async (sdk, response) => {
  // Modify response
  const spec = response.toSpec();
  // Modify as needed
  return spec;
});

Important Caido SDK Types

export type Request = {
  getId(): ID;
  getHost(): string;
  getPort(): number;
  getTls(): boolean;
  getMethod(): string;
  getPath(): string;
  getQuery(): string;
  getUrl(): string;
  getHeaders(): Record<string, Array<string>>;
  getHeader(name: string): Array<string> | undefined;
  getBody(): Body | undefined;
  getRaw(): RequestRaw;
  getCreatedAt(): Date;
  toSpec(): RequestSpec;
  toSpecRaw(): RequestSpecRaw;
};

export type Response = {
  getId(): ID;
  getCode(): number;
  getHeaders(): Record<string, Array<string>>;
  getHeader(name: string): Array<string> | undefined;
  getBody(): Body | undefined;
  getRaw(): ResponseRaw;
  getRoundtripTime(): number;
  getCreatedAt(): Date;
};

For Body and Raw you can use methods like getBody()?.toText() to extract text content.

These types can be imported by:

import { type Request, type Response } from "caido:utils";

Data Storage

SQLite Database:

const db = sdk.meta.db();

// Execute queries with connection pooling
await db.execute('CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, content TEXT)');
const rows = await db.query('SELECT * FROM notes');

Plugin Data Directory:

const dataPath = sdk.meta.path();  // Plugin-specific writable directory
const assetsPath = sdk.meta.assetsPath();  // Read-only assets from package

Project and Scope

Current Project:

const project = sdk.projects.getCurrent();
const projectName = project?.getName();
const projectPath = project?.getPath();

Scope Management:

const scopes = await sdk.scope.getAll();

  export type Scope = {
    /**
     * The unique Caido {@link ID} of the scope.
     */
    readonly id: ID;
    /**
     * The name of the scope.
     */
    readonly name: string;
    /**
     * The allowlist of the scope.
     */
    readonly allowlist: string[];
    /**
     * The denylist of the scope.
     */
    readonly denylist: string[];
  };

  /**
   * The SDK for the Scope service.
   * @category Scope
   */
  export type ScopeSDK = {
    /**
     * Get all the scopes.
     * @returns An array of {@link Scope}
     */
    getAll(): Promise<Scope[]>;
  };

GraphQL API

Execute GraphQL queries against Caido's internal API:

const result = await sdk.graphql.execute(`
  query {
    requests(limit: 10) {
      nodes {
        id
        method
        url
      }
    }
  }
`);

Process Management

Spawn external processes (e.g., security tools):

import { spawn } from 'child_process';

const proc = spawn('nmap', ['-sV', 'target.com']);

proc.stdout.on('data', (data) => {
  sdk.console.log(data.toString());
});

proc.on('close', (code) => {
  sdk.console.log(`Process exited with code ${code}`);
});

Event Handlers

Project Change:

sdk.events.onProjectChange((sdk, project) => {
  sdk.console.log('Project changed:', project?.name);
});

Console Logging

sdk.console.log('Info message');
sdk.console.debug('Debug message');
sdk.console.warn('Warning message');
sdk.console.error('Error message');

Complete Backend SDK Reference: https://developer.caido.io/reference/sdks/backend/

Backend Runtime Modules

Backend plugins run in a QuickJS environment with Node.js-compatible modules:

Available Modules: https://developer.caido.io/reference/modules/

HTTP Module (caido:http)

Provides Fetch API implementations:

import { fetch, Request, Response, Headers } from "caido:http";

// Make HTTP requests
const response = await fetch("https://api.example.com/data", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ key: "value" })
});

const data = await response.json();

// Work with Headers
const headers = new Headers();
headers.set("Authorization", "Bearer token");
headers.append("Accept", "application/json");

Response methods: text(), json(), arrayBuffer(), blob(), bytes()

FileSystem Module (llrt/fs)

import { readFile, writeFile, mkdir, stat, unlink } from "llrt/fs";

// Read file
const content = await readFile("/path/to/file.txt", "utf-8");

// Write file
await writeFile("/path/to/output.txt", "content");

// Create directory
await mkdir("/path/to/dir", { recursive: true });

// Check file exists
const stats = await stat("/path/to/file");

Constants: F_OK, R_OK, W_OK, X_OK for access checks

Child Process Module

import { spawn } from "child_process";

// Note: exec() is unavailable - use spawn with shell: true instead
const proc = spawn("nmap", ["-sV", "target.com"], { shell: true });

proc.stdout.on("data", (data) => {
  sdk.console.log(data.toString());
});

proc.on("close", (code) => {
  sdk.console.log(`Exited with code ${code}`);
});

Limitations: Stream pipe() is not supported.

Crypto Module

import { createHash, createHmac, randomBytes } from "crypto";

// Hashing
const hash = createHash("sha256").update("data").digest("hex");

// HMAC
const hmac = createHmac("sha256", "secret").update("data").digest("hex");

// Random bytes
const bytes = randomBytes(16);

Buffer Module

import { Buffer } from "buffer";

const buf = Buffer.from("hello", "utf-8");
const base64 = buf.toString("base64");
const hex = buf.toString("hex");

// Concatenate buffers
const combined = Buffer.concat([buf1, buf2]);

Path Module

import { join, resolve, dirname, basename, extname } from "path";

const fullPath = join("/base", "dir", "file.txt");
const dir = dirname("/path/to/file.txt");  // "/path/to"
const file = basename("/path/to/file.txt"); // "file.txt"
const ext = extname("file.txt");            // ".txt"

Process Module

import process from "process";

const cwd = process.cwd();
const apiKey = process.env.API_KEY;

OS Module

import os from "os";

const platform = os.platform();  // "darwin", "linux", "win32"
const home = os.homedir();
const tmp = os.tmpdir();

SQLite Module

import { Database } from "sqlite";

const db = new Database("/path/to/db.sqlite");
const stmt = db.prepare("SELECT * FROM users WHERE id = ?");
const user = stmt.get(userId);

// Transactions
db.transaction(() => {
  db.run("INSERT INTO users (name) VALUES (?)", "Alice");
  db.run("INSERT INTO users (name) VALUES (?)", "Bob");
});

Timers Module

// Available globally, no import needed
const timeoutId = setTimeout(() => {
  sdk.console.log("Delayed execution");
}, 1000);

const intervalId = setInterval(() => {
  sdk.console.log("Repeated execution");
}, 5000);

clearTimeout(timeoutId);
clearInterval(intervalId);

Abort Module

const controller = new AbortController();
const signal = controller.signal;

// Use with fetch
fetch(url, { signal }).catch(err => {
  if (err.name === "AbortError") {
    sdk.console.log("Request was cancelled");
  }
});

// Cancel the request
controller.abort();

Global Modules (no import needed):

  • console - Logging
  • timers - setTimeout, setInterval
  • abort - AbortController, AbortSignal
  • dom-events - EventTarget, EventListener

Note: Some modules use caido: prefix (e.g., caido:http, caido:plugin, caido:utils)

Working with Assets

Static assets can be bundled with plugins and accessed at runtime. Configure assets in caido.config.ts:

// caido.config.ts
{
  kind: 'backend',
  id: 'my-plugin-backend',
  root: './packages/backend',
  assets: ['./assets/**/*']  // Glob patterns for assets
}

Accessing Assets at Runtime

// Get an asset file
const asset = await sdk.assets.get("path/to/file.txt");

// Convert to different formats
const text = asset.asString();           // Text content
const json = asset.asJson<ConfigType>(); // Typed JSON
const buffer = asset.asArrayBuffer();    // Binary data
const stream = asset.asReadableStream(); // Stream chunks

Assets are read-only and bundled with the plugin package. Use sdk.meta.assetsPath() to get the assets directory path.

Handling Binary Data

JavaScript's UTF-8 encoding can corrupt raw binary bytes. Use these patterns to preserve exact byte sequences:

// Get raw bytes (preserves binary data)
const rawBytes = request.getPath({ raw: true });  // Returns Uint8Array

// Manipulate as needed
const modified = new Uint8Array([...rawBytes, 0x00]);

// Set back (preserves exact bytes)
spec.setPath(modified);

// For body data
const bodyRaw = request.getBody()?.toRaw();  // Get raw bytes

Important: Use .toText() only when you need string representation. Use .toRaw() when preserving exact bytes is critical.

Workflow SDK

For JavaScript nodes within Caido's workflow system.

SDK Reference: https://developer.caido.io/reference/sdks/workflow/

Key Capabilities:

  • Query and send HTTP requests
  • Create security findings
  • Execute GraphQL queries
  • Access environment variables
  • Manage scope configurations
  • Work with workflow inputs/outputs

Workflow Plugins in manifest.json:

{
  "kind": "workflow",
  "id": "my-workflow",
  "definition": "workflow-definition.json"
}

Community Workflows: https://github.com/caido-community/workflows

Development Workflow

Hot Reload with Devtools

For rapid iteration without rebuilding:

  1. Install Devtools from the Caido Community Store

  2. Run Watch Mode:

pnpm watch
  1. Connect to Dev Server in Devtools within Caido

Changes are automatically reloaded in real-time.

Reference: https://developer.caido.io/guides/ (Development Workflow section)

Building and Testing

Build:

pnpm build

Output: dist/plugin_package.zip

Install in Caido:

  1. Open Caido
  2. Navigate to Plugins
  3. Click "Install from file"
  4. Select plugin_package.zip

Pre-Commit Checks

IMPORTANT: Always run these commands before committing your code:

pnpm lint
pnpm typecheck

These commands ensure code quality and catch type errors before they make it into version control. Make it a habit to run both commands before every commit.

Debugging

Use sdk.console methods for logging:

  • Backend: Logs appear in Caido's backend console
  • Frontend: Logs appear in browser DevTools

Distribution

Publishing to Community Store

  1. Setup Repository: Follow guidelines at https://developer.caido.io/guides/distribution/repository

  2. Submit Plugin: Follow process at https://developer.caido.io/guides/distribution/store

  3. Plugin Requirements:

    • Valid manifest.json with all required fields
    • Proper versioning (semantic versioning)
    • Author information
    • Optional: Sponsor link

Plugin Signing

Caido supports plugin signing for security. Refer to SDK documentation for signing requirements.

Developer Policy

When publishing plugins to the Caido store, adhere to these requirements:

Prohibited:

  • Obfuscated code
  • Malware or malicious functionality
  • Advertisements
  • Client-side telemetry without disclosure
  • Auto-updating without user consent
  • Loading internet assets without disclosure

Required Disclosures:

  • Required payments or account creation
  • External service dependencies
  • Server-side telemetry (with privacy policy link)
  • Closed-source code status

Licensing:

  • All plugins must include a LICENSE file
  • Respect original licenses of dependencies
  • Provide proper attribution for third-party code

Popular Plugin Examples

Learn from existing community plugins to understand implementation patterns:

General Reference Plugins

  • plugin-demo (Official Demo): https://github.com/caido-community/plugin-demo

    • Demonstrates frontend architecture with Vue.js and TypeScript
    • Monorepo structure with pnpm workspaces
    • PrimeVue component integration
    • Use when: Building frontend-heavy plugins with UI components
  • Notebook (Tutorial Example): https://developer.caido.io/tutorials/notebook

    • Note-taking plugin with persistent storage
    • Command palette integration
    • Context menu registration
    • Storage synchronization patterns
    • Use when: Implementing data persistence and UI integration

Specialized Security Plugins

Utilize these as references for adding specific features, so you will know how to implement similar functionality.

Workflow and Automation

Development Tools

Common Patterns and Best Practices

Plugin Architecture Patterns

  1. Frontend-Only Plugins: UI enhancements, visual tools, reporting interfaces
  2. Backend-Only Plugins: HTTP processing, external tool integration, background tasks
  3. Full-Stack Plugins: Complex features requiring both UI and server-side logic

TypeScript Type Safety

Always define types for backend API and events:

// Frontend
type API = {
  functionName: (param: ParamType) => Promise<ReturnType>;
};

type Events = {
  eventName: (data: EventDataType) => void;
};

const caido: Caido<API, Events>;

Error Handling

try {
  const result = await sdk.requests.send({ request: spec });
  // Process result
} catch (error) {
  sdk.console.error('Request failed:', error);
  sdk.api.send('error', { message: error.message });
}

Performance Considerations

  • Use pagination for large request queries
  • Implement timeouts for HTTP requests
  • Debounce UI events that trigger backend calls
  • Cache frequently accessed data in storage

Security Best Practices

  • Validate all user input
  • Use secret: true for sensitive environment variables
  • Implement proper error handling without leaking sensitive data
  • Use deduplication keys for findings to avoid duplicates
  • Follow principle of least privilege for external integrations

Troubleshooting

Common Issues

Plugin Not Loading:

  • Check manifest.json syntax
  • Verify plugin ID format (lowercase, hyphens/underscores only)
  • Ensure version follows semantic versioning
  • Check console for errors

Hot Reload Not Working:

  • Verify Devtools is installed
  • Ensure pnpm watch is running
  • Check connection URL in Devtools matches watch server

Backend/Frontend Communication Failing:

  • Verify API function registration matches frontend calls
  • Check type definitions match between frontend and backend
  • Ensure backend plugin ID is correctly referenced in frontend config

Build Failures:

  • Run pnpm install to ensure dependencies are up-to-date
  • Check TypeScript errors with pnpm tsc
  • Verify caido.config.ts syntax

Getting Help

AI-Assisted Development

Cursor IDE Integration

Cursor IDE supports loading Caido documentation directly:

  1. Open Cursor settings
  2. Navigate to "Add New Custom Docs"
  3. Add URL: https://developer.caido.io/

This enables Cursor's AI to reference Caido SDK documentation when assisting with plugin development.

LLM Documentation Endpoints

Caido provides optimized documentation for AI/LLM consumption:

Custom GPT

A custom GPT trained on Caido resources is available for high-accuracy answers to development questions. Check the Caido Discord or documentation for access.

Quick Reference Links

Official Documentation:

SDK References (Always Check for Latest APIs):

Configuration Files:

Guides (18 Frontend Guides Available):

Community Resources:

Development Tools:


Note: Caido's plugin ecosystem is actively developed. Always reference the official documentation links above for the most up-to-date API information, as SDKs and capabilities may evolve over time.

TypeScript Guidelines

  • Use TypeScript for all files.
  • NEVER use any type.
  • Use undefined over null.
  • Try to keep things in one function unless composable or reusable.
  • Prefer single word variable names where possible.
  • DO NOT do unnecessary destructuring of variables.
  • AVOID else statements where possible.
  • AVOID try / catch where possible.
  • AVOID using interfaces where possible.

UI Style Guidelines

PrimeVue

  • Prefer to use PrimeVue compontents where possible
  • A custom PrimeVue theme is configured with dark mode as default, handling most color-related styles for us.

General Theme

  • Dark Mode is default — all UI elements follow a dark, low-contrast background with light text for high readability.
  • For text / background colors, prefer to use ...-surface-... f.e. border-surface-700.
  • Caido uses bg-surface-800 as the main app background, bg-surface-700 is the background used for Card component.
  • Follow minimalistic color palette. AVOID using too much colors where possible.

Layout & Components

  • Often use PrimeVue Splitter and SplitterPanel for vertical or horizontal layout.
  • Prefer to use Card PrimeVue components a lot, if needed add h-full to them via pt params.
  • Remember to use <template #content> and other named slots (like #header, #footer) in Card and other PrimeVue components that support them.

Example:

<Card
  class="h-full"
  :pt="{
    body: { class: 'h-full p-0' },
    content: { class: 'h-full flex flex-col' },
  }"
>
  <template #content>
     ...
  </template #content>
</Card>

Enviroment

  • Keep in mind that we are building a plugin that's inside a Caido web app, we can modify frontend by adding sidebar pages using Caido Frontend SDK.
  • Our plugin content is rendered within a dedicated window/panel that Caido provides for our sidebar page.
  • The plugin UI should integrate seamlessly with Caido's existing interface and theming.

Data Representation

  • Prefer DataTable component for displaying structured data:
    • Caido often uses stripedRows, use it where possible
    • Actions column at the end (e.g. “Install” buttons).
  • Empty states use friendly, minimal messages with icons.

Icons

  • Always use fas fa-[...] for icons. We don't support any other icon libraries.

Caido SDK API Validation

Valid API Usage

Always use documented Caido SDK APIs directly without runtime checks or validation.

  • Good

    sdk.window.showToast("Message", { variant: "error" });
    sdk.backend.myFunction();
    sdk.commands.register("my-command", { name: "Command", run: () => {} });
    
  • Bad (NEVER do this)

    if ("showToast" in sdk.window && typeof sdk.window.showToast === "function") {
      sdk.window.showToast("Message");
    }
    

Rules

  1. Never use runtime API existence checks (typeof, in operator)
  2. Never assume undocumented SDK methods exist
  3. If unsure about an API, ask for clarification rather than guessing

The SDK is typed and guaranteed to have the documented methods available.

Linter

We have a built-in ESLint linter configured at the root folder. After making any significant change, always run the linter with pnpm lint and fix all potential issues.

Most common mistakes that lead to linter errors

Lint Rule: Unexpected nullable string value in conditional. Please handle nullish or empty cases explicitly

To prevent this, when comparing strings, instead of writing:

if (!str) {}

do this:

if (str !== undefined) {}

Components

  1. Use Primevue components for all UI components.
  2. Use <script setup lang="ts"> for all components.

Structure

Use the following layout for every component to keep imports and growth consistent:

ComponentName/
  ├─ index.ts           # Re-export (single public entry)
  ├─ Container.vue      # Main component implementation
  ├─ useForm.ts         # Optional: composable when logic grows (e.g. forms)
  └─ DependentComponent.vue

ComponentName/index.ts

export { default as ComponentName } from "./Container.vue";

When a child piece becomes complex or needs its own hook, use the same pattern as the parent:

ComponentName/
  └─ DependentComponent/
       ├─ index.ts
       └─ Container.vue
Install via CLI
npx skills add https://github.com/caido-community/caido-plugin-skill --skill caido-plugin-dev
Repository Details
star Stars 21
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator
caido-community
caido-community Explore all skills →