Scanner
Vulnerability scanner for Caido
Install / Use
/learn @caido-community/ScannerREADME
Scanner
A web vulnerability scanner plugin for Caido.
About Scanner
Scanner is a vulnerability detection plugin that brings automated security testing capabilities to Caido. Scanner provides a user-friendly interface for identifying common web application vulnerabilities.
<video src="docs/demo.mp4" controls preload></video>
🚀 Getting Started
Installation [Recommended]
- Open Caido, navigate to the
Pluginssidebar page and then to theCommunity Storetab - Find
Scannerand click Install - Done! 🎉
Installation [Manual]
- Go to the Scanner Releases tab and download the latest
plugin_package.zipfile - In your Caido instance, navigate to the
Pluginspage, clickInstalland select the downloadedplugin_package.zipfile - Done! 🎉
💚 Community
Join our Discord community and connect with other Caido users! Share your ideas, ask questions, and get involved in discussions around Caido and security testing.
🧑💻 Developer Documentation
Scanner Structure
The project is organized into distinct packages: backend, frontend, engine, and shared. The engine package is responsible for all the logic related to executing checks. All checks are located within the packages/backend/src/checks/ directory.
Check Definition
To define your own check, use the defineCheck function as shown below. The check metadata contains static information about the check, some fields, such as id, are always required, while others are used for filtering.
import { defineCheck, Severity } from "engine";
import { keyStrategy } from "../../utils/key";
export const exampleCheck = defineCheck(({ step }) => {
return {
metadata: {
id: "example-check",
name: "Example Check",
description:
"This is an example check",
type: "passive",
tags: ["example"],
severities: [Severity.INFO],
aggressivity: {
minRequests: 0,
maxRequests: 0,
},
},
initState: () => ({}),
dedupeKey: keyStrategy().withHost().withPort().withPath().build(),
when: (context) => {
return (
context.response !== undefined && context.response.getCode() === 200
);
},
};
});
Check Metadata
The CheckMetadata type is a crucial part of defining a check, as it contains all the static information about the check. Here's a breakdown of its components:
- id: A unique identifier for the check. This is required and ensures that each check can be distinctly referenced.
- name: A human-readable name for the check, which is displayed in the UI.
- description: A detailed explanation of what the check does and the vulnerabilities it detects. This helps users understand the check's functionality and scope.
- tags: An array of tags used for categorization and filtering. Tags help in organizing checks and making them easily searchable.
- aggressivity: This defines the request limits for the check. It uses the
CheckAggressivitytype, which specifiesminRequestsandmaxRequests. If the request count is dynamic, useInfinityformaxRequests. - type: Indicates whether the check is
passiveoractive. This helps in determining how the check interacts with the target. Usepassiveif the scan is silent enough to run in the background without causing noise, andactiveif the scan requires more noticeable interaction with the target. - severities: An array of possible severity levels that the check can report. This is used for filtering, and the engine will throw an error if a finding is returned with a severity not included in this array.
- dependsOn (optional): An array of check IDs that must run before this check. This ensures that dependencies are resolved before execution.
- minAggressivity (optional): The minimum scan aggressivity level required for this check to run. This allows checks to be gated by the scan's aggressivity level.
- skipIfFoundBy (optional): An array of check IDs. If any of these checks have found findings during the scan, this check will be skipped.
export type CheckAggressivity = {
minRequests: number;
maxRequests: number | "Infinity";
};
export type CheckType = "passive" | "active";
export type CheckMetadata = {
/** Unique identifier for the check */
id: string;
/** Human-readable name displayed in the UI */
name: string;
/** Detailed description of what the check does and what vulnerabilities it detects */
description: string;
/** Array of tags used for categorization and filtering */
tags: string[];
/** Defines the request limits for this check. Please use Infinity if it's dynamic. */
aggressivity: CheckAggressivity;
/** Whether this is a passive or active check */
type: CheckType;
/**
* Array of possible severity levels this check can report.
* This is used for filtering.
* Engine will throw an error if you return a finding with a severity that is not in this array.
**/
severities: Severity[];
/** Optional: Array of check IDs that must run before this check */
dependsOn?: string[];
/** Optional: Minimum scan aggressivity level required for this check to run */
minAggressivity?: ScanAggressivity;
/** Optional: array of check IDs - if any of these check IDs have found any findings during the scan, skip this check */
skipIfFoundBy?: string[];
};
Steps
Steps let you break your check into smaller parts. Each step should be simple and quick. End a step with done(...) or go to the next step with continueWith({ state, nextStep: '...' }).
import { continueWith, defineCheck, done, Severity } from "engine";
export default defineCheck<{
responseBody: string | undefined;
}>(({ step }) => {
step("check200", async (state, context) => {
if (
context.target.response !== undefined &&
context.target.response.getCode() === 200
) {
const responseBody = context.target.response.getBody()?.toText();
return continueWith({
state: {
responseBody,
},
nextStep: "reportFinding",
});
}
return done({ state });
});
step("reportFinding", async (state, context) => {
const finding = {
name: "HTTP 200 OK",
description: `Target responded with 200 OK. Response body: ${state.responseBody}`,
severity: Severity.INFO,
correlation: {
requestID: context.target.request.getId(),
locations: [],
},
};
return done({ state, findings: [finding] });
});
return {
metadata: {
id: "example-check",
name: "Example Check",
description: "This is an example check",
type: "passive",
tags: ["example"],
severities: [Severity.INFO],
aggressivity: {
minRequests: 0,
maxRequests: 0,
},
},
dedupeKey: (context) =>
context.request.getHost() +
context.request.getPort() +
context.request.getPath(),
when: (context) => context.response !== undefined,
initState: () => ({
responseBody: undefined,
}),
};
});
State allows you to pass data from one step to another within your check. When you define a state for your check, you must also provide an initState function in your return statement that sets the initial value for your state. Context gives you access to the Caido Backend SDK, the scanner engine runtime SDK (which will be explained later), as well as the target request and response.
Checks Engine
The checks engine is built around a step-based execution model that allows responsive and interruptible scans. Each check is composed of sequential steps that can pass data through state, send requests, and produce findings.
Steps
Steps are the fundamental building blocks of a check in the engine. Steps are executed sequentially, and each execution of a step is called a "tick".
Keeping each tick short is important. After every tick, the engine checks if the scan has been aborted. If a step were to perform multiple long-running actions (like sending several requests in a loop), the user would have to wait for all of them to finish. By designing checks so that each step only sends one request or performs a small piece of logic, we ensure that the engine remains responsive and can quickly react to user actions or scan interruptions.
When you need to perform multiple actions (such as sending several requests), structure your check so that each action happens in its own step. This way, each tick is short, and the engine can efficiently manage scan flow and user interactions.
A common pattern in checks is to create loops by reusing the same step in nextStep. This allows you to perform iterative operations like testing multiple paths or parameters. Once your loop condition is met, you can either return done() to complete the check or continue() to proceed to subsequent steps.
Context
Context provides access to:
- The Caido Backend SDK via the
sdkfield - The request and response of the scan target via the
targetfield - The Runtime SDK via the
runtimefield - The scan configuration via the
configfield
Runtime SDK
The runtime SDK is a set of utilities specifically scoped to the current scan.
/** Runtime SDK for accessing utilities scoped to the current scan. */
runtime: {
/** Utilities for parsing HTML. */
html: {
/** Parse the
Related Skills
node-connect
353.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
111.7kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
353.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
353.3kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
