Kelp
Scans Supabase backends for security issues such as permissive RLS policies, hardcoded service_role JWTs, edge functions with verify_jwt disabled, and insecure auth/CORS configurations.
Kelp
Security scanner for vibe-coded apps. Finds the doors AI code generators leave open — hardcoded secrets, permissive RLS, unauthenticated edge functions — and gates them out of your pull requests.
Live app · Docs · CLI · MCP server · GitHub Action · Architecture
What it does
Kelp scans an app's backend surface — Supabase (managed backends included), edge functions, RLS policies, source tree — the way an attacker would. Every finding is evidence-gated: a reviewer re-runs the reproduction before it lands in your report, so what you see is what an attacker would actually get.
Four surfaces, one detection engine:
Surface | For | How you use it |
CLI | Local scans, CI shells, scripts |
|
MCP server | Claude Code, Claude Desktop, Cursor, any MCP client |
|
GitHub Action | Pull-request gating |
|
Hosted app kelp.build | Continuous scanning, dashboard, PR fixes | Connect a repo, sign in with GitHub |
Zero configuration in the common case. The Action reads the workflow's
GITHUB_TOKEN, the hosted app installs a GitHub App, the CLI walks the
filesystem, the MCP server speaks stdio JSON-RPC to the LLM client.
Related MCP server: guardvibe
Quickstart — CLI
npx @kelp-security/cli scan ./my-appkelp v0.1.0 · scanning ./my-app · 214 files walked
CRITICAL src/lib/db.ts:14 VITE_SERVICE_ROLE — Supabase service_role JWT
HIGH src/api/orders.ts hardcoded Stripe secret (sk_live_…)
MEDIUM supabase/config.toml verify_jwt=false on get-order
3 findings · 8s · report at ./kelp-report.jsonAdd --json for machine-readable output, --severity high to filter, or see
docs/CLI.md for the full reference.
Quickstart, MCP server
Kelp implements the Model Context Protocol so an LLM client (Claude Code, Claude Desktop, Cursor, or any MCP-compatible client) can call Kelp mid-conversation, while it is writing code, instead of scanning after the fact. Same static engine as the CLI, exposed as MCP tools, resources, and slash commands over stdio. Runs locally, offline. No file content ever leaves the machine.
Add to your client's MCP config (~/.claude.json, Claude Desktop config, or the
Cursor MCP settings):
{
"mcpServers": {
"kelp": {
"command": "npx",
"args": ["-y", "@kelp-security/cli", "mcp"]
}
}
}Restart the client, then ask the assistant to scan the repo, or use the
/kelp:review-repo slash command.
MCP tools: scan_path, scan_snippet, list_rules, explain_finding,
explain_rule. Resources: kelp://rules, kelp://rules/{ruleId}.
Prompts (slash commands): /kelp:review-repo, /kelp:harden-file.
Full guide, with per-client install snippets, in docs/MCP.md.
Quickstart — GitHub Action
Add .github/workflows/kelp-check.yml:
name: kelp/check
on:
pull_request:
branches: [main]
permissions:
contents: read
pull-requests: read
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: kelp-security/kelp-action@v1Kelp will run on every PR, comment the verdict on the PR, and fail the check when new critical or high findings are introduced against the base branch. See the action docs for inputs and required-status-check setup.
What Kelp checks today
Class | How | Output |
Secrets in source | Provider patterns (AWS/GCP/Stripe/Supabase/…) + entropy fallback | Masked preview + line + severity |
Supabase RLS | Reads schema + policies, flags tables open to | Proposed migration snippet |
Edge functions | Replays without a JWT to detect | Function name + reproduction curl |
CORS + auth flows | Reads config + auth callbacks for permissive defaults | Config diff |
BOLA (opt-in, hosted only) | Active test with two user-provided test accounts | Human-review only, never auto-fix |
New detections land in packages/core/src/scanners/. See docs/ADAPTERS.md for extending Kelp to other backends (Firebase, Convex, PocketBase — see the north-star issue).
Architecture
Monorepo (npm workspaces). Three surfaces share one engine:
apps/
├─ web/ Next.js — hosted app at kelp.build
└─ cli/ kelp binary — standalone Node CLI
packages/
├─ core/ detection engine (pure, no I/O)
├─ worker/ scan pipeline + integrations (GitHub, Supabase, queue)
└─ db/ SQL migrationsThe core is intentionally I/O-free: it takes SourceFile[] and returns
Finding[]. The CLI shells file-reads to it; the worker adds the GitHub App
plumbing; the web app adds auth, storage, and the reviewer loop.
Full breakdown: docs/ARCHITECTURE.md.
Evidence-gating
Kelp's most important invariant: the model never decides a finding is real. Every agent-produced lead requires a reproduction (probe + expected observable, or a source citation). The executor re-runs it and records the finding only if the observable holds. Autonomy in reasoning, zero fabrication.
Read the full principle at docs/EVIDENCE-GATING.md.
Contributing
PRs, bug reports, and new detection classes are welcome — see CONTRIBUTING.md for the dev setup and the review checklist. Security vulnerabilities go through SECURITY.md, not the public issue tracker.
Good first contributions:
A new secret provider pattern in
packages/core/src/scanners/secrets.ts.A new edge-function heuristic in
packages/core/src/agent/edge-functions.ts.Docs improvements — the tutorials in
docs/are always in flight.
License
MIT — see LICENSE.
Acknowledgements
Kelp exists because vibe-code tools ship a lot of the same footguns, and the people using them shouldn't need a security team to catch them. Built by @Mic52M — solo, in the open.
Available Tools
5 toolsexplain_findingExplain a specific finding and how to fix itARead-only
Given a ruleId (and optionally the finding's path + preview), returns the reasoning ('why this matters') and a concrete remediation you can turn into a code change or an instruction to the user.
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | The file path where the finding fired. Optional, used for context in the response. | |
| ruleId | Yes | The rule id from a scan finding, e.g. 'stripe-secret-live'. | |
| preview | No | The masked preview from the finding. Optional, used verbatim in the response. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations mark this as read-only and not open-world, so the bar is lower. The description adds useful behavioral detail by specifying that the output includes reasoning and remediation, and that the result can be turned into a code change or instruction. It does not contradict the annotations and offers more behavioral context than the minimal read-only hint.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single well-structured sentence. It front-loads the required input (ruleId), notes optional parameters, and states the two-part output. No filler or redundant phrasing; every clause contributes to understanding how to use the tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only tool with three parameters and no output schema, the description covers the main call details: required and optional inputs, and the nature of the return value. It does not address invalid ruleId behavior or how to choose between explain_finding and explain_rule, but these are relatively minor gaps given the tool's simple purpose.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The tool description only mentions that path and preview are optional, which is already fully documented in the schema. It does not add semantic meaning beyond what the parameter descriptions provide.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a clear action: given a ruleId, it returns reasoning and a concrete remediation. This is specific and understandable, but it does not explicitly differentiate from the sibling explain_rule, which likely covers general rule explanations. The title adds the 'specific finding' framing, but the description itself could be clearer about the distinction.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies the tool is used after a scan produces a finding, since it asks for a ruleId and optionally the finding's path and preview. However, it does not explicitly state when to prefer this tool over explain_rule or any other sibling, nor does it mention when not to use it. Usage guidance is present but remains implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
explain_ruleExplain a rule by idARead-only
Given a ruleId, returns the rule's title, class, severity, why-it-matters, and remediation pattern. Use before or after a scan to understand a rule without an active finding.
| Name | Required | Description | Default |
|---|---|---|---|
| ruleId | Yes | The rule id, e.g. 'supabase-service-role'. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already declares this is a read-only operation, so the description doesn't need to state that. The description adds context by noting that it works 'without an active finding', which is a behavioral trait beyond the annotation. However, it doesn't discuss error conditions (e.g., invalid ruleId) or response details beyond listing fields, so it falls short of richer behavioral disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two concise sentences with no filler. The first sentence front-loads the purpose and output; the second gives usage timing. Every word earns its place, and the structure is optimally scannable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given a single parameter, no output schema, and readOnlyHint annotations, the description covers the essential behavior (what it returns, when to use it). It doesn't mention error handling for invalid ruleIds, but that's a minor gap for such a simple tool. The listed output fields are explicit, making it self-contained enough for an agent to call correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema description for ruleId already provides an example and type. The description mentions ruleId in context and clarifies its purpose (to identify a rule), but adds no new semantic detail beyond what the schema offers. With 100% schema coverage, the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('returns'), a resource ('rule'), and enumerates the exact fields returned (title, class, severity, why-it-matters, remediation pattern). It distinguishes itself from siblings by clarifying that it works 'without an active finding', setting it apart from explain_finding which presumably requires a finding. This is unambiguous and clearly scoped.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly says 'Use before or after a scan to understand a rule without an active finding.' This gives clear timing context and implies that if you have an active finding, you should use explain_finding instead, though it doesn't name the sibling explicitly. It provides solid usage guidance without full exclusion criteria.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_rulesList Kelp detection rulesARead-only
Returns the catalog of rules Kelp can fire on. Use this to introspect coverage before scanning, or to answer 'does Kelp check X?' without running a scan.
| Name | Required | Description | Default |
|---|---|---|---|
| class | No | Optional filter to a single rule class. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The readOnlyHint annotation already flags this as a safe read operation, so the description does not need to repeat that. The description adds a small behavioral nuance by implying it is lightweight ('without running a scan'), but it does not describe the return format, pagination, or any error conditions. Given the annotation coverage, a 3 is appropriate – it adds minimal behavioral context beyond the annotation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two sentences with zero redundancy. The main function is stated first ('Returns the catalog...'), followed by the usage guidance. Every word contributes value. It is an exemplary model of concise, front-loaded documentation.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple listing tool with one optional parameter and no output schema, the description provides sufficient information: it states what is returned and when to use it. It does not describe the structure of the returned catalog, but for a list of rules that is probably self-evident. It is complete for an agent to decide when to invoke it, though a more detailed return format would push it to a 5.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The schema fully documents the single optional 'class' parameter with an enum and a description ('Optional filter to a single rule class.'). The description does not mention this parameter at all. Since schema coverage is 100%, the baseline for parameter semantics is 3 – the schema carries the weight, and the description adds no additional meaning beyond that.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a clear verb and resource: 'Returns the catalog of rules Kelp can fire on.' This precisely identifies what the tool does. It also distinguishes itself from the sibling scan/explain tools by framing it as an introspection tool for coverage checks, so an agent can tell it apart without reading other definitions.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit use cases: 'Use this to introspect coverage before scanning, or to answer 'does Kelp check X?' without running a scan.' This tells the agent when to call it. It does not explicitly name alternative tools or state when not to use it, but the context makes the intent clear given the sibling names (scan_path, scan_snippet, explain_rule, explain_finding). A slightly higher score would require naming the alternative explicitly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
scan_pathScan a repository or directoryARead-only
Runs Kelp's static engine over a filesystem path. Detects hardcoded secrets (Stripe, AWS, GitHub, OpenAI, Anthropic, Supabase service_role, private keys, and more), Supabase edge functions that skip JWT verification, and recons Supabase edge-fn deployables. Fast, offline, deterministic. Returns findings sorted by severity.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | Absolute or relative filesystem path to scan. Should be the repo root or a subdirectory of it. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, so the description adds valuable behavioral traits: 'fast, offline, deterministic' and 'returns findings sorted by severity.' These go beyond the annotation and help the agent anticipate behavior. No contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded: it starts with the action, then lists detection targets, and ends with performance and output traits. Every sentence earns its place; no fluff or repetition.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (one parameter, no output schema), the description covers the core behavior and mentions output sorting. It doesn't detail the finding structure, but sibling tools like explain_finding likely cover that. The description is sufficient for an agent to call the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% for the single parameter, so the schema fully documents the 'path' parameter. The description adds no extra parameter-level detail beyond confirming it's a filesystem path, which matches the schema. Baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb ('runs'), a concrete resource ('Kelp's static engine over a filesystem path'), and enumerates exactly what it detects (hardcoded secrets, Supabase edge functions, etc.). It clearly distinguishes from siblings by the 'path' scope, even though it doesn't name alternatives explicitly.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description makes it clear that this tool scans a filesystem path and what it finds, but it does not explicitly state when to prefer this over scan_snippet or when not to use it. The usage is implied but not contrasted with alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
scan_snippetScan a single code snippetARead-only
Runs the static engine on one in-memory string as if it were a file. Use this after generating code, before showing it to the user or committing it. Cheaper than scan_path when only a diff is at stake.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | The path this snippet would live at, e.g. src/api/orders.ts. Used to make client-side heuristics work (files under public/, src/components/, *.tsx, etc. are treated as shipped to the browser). | |
| content | Yes | The full source text of the file/snippet. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, and the description adds useful behavioral context beyond that: it operates on an in-memory string, simulates file-based scanning, and is cheaper than a full path scan. It does not describe the return shape, but the read-only, in-memory behavior is clearly conveyed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences with no filler: the first states the core behavior, the second gives the intended use case and cost comparison. All information is front-loaded and useful.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter, read-only tool with thorough schema descriptions, the description covers purpose, usage timing, and trade-offs versus scan_path. The only minor gap is that it never explicitly says what the scan returns, but the 'scan' semantics and sibling explain_finding make the outcome reasonably inferable.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, with detailed descriptions for both path and content, including why path matters for client-side heuristics. The description itself adds little parameter-level meaning beyond 'one in-memory string,' so it meets the baseline but does not exceed what the schema already provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: 'Runs the static engine on one in-memory string as if it were a file.' It clearly distinguishes itself from the sibling scan_path by operating on an in-memory string rather than a filesystem path.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit usage context: 'Use this after generating code, before showing it to the user or committing it.' It also names the alternative (scan_path) and the selection condition ('Cheaper than scan_path when only a diff is at stake'), so an agent knows when to pick this tool.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.1.0- First observed
explain_finding - First observed
explain_rule - First observed
list_rules - First observed
scan_path - First observed
scan_snippet
TDQS
Scored across 5 tools
Each tool has a distinct core purpose: scanning paths vs snippets, listing rules, and explaining rules vs findings. The pairings scan_path/scan_snippet and explain_rule/explain_finding have some surface similarity, but descriptions clarify their boundaries.
All tool names follow a consistent verb_noun pattern in snake_case: scan_path, scan_snippet, list_rules, explain_rule, explain_finding. The naming is predictable and makes the toolset easy to navigate.
Five tools is well-scoped for a security scanning server: two scan entry points, one rule catalog query, and two explanation helpers. Each tool serves a clear, non-redundant role in the workflow.
The toolset covers the full lifecycle expected of a static analysis scanner: discover rules, scan a path or snippet, retrieve rule details, and explain findings with remediation. There are no obvious missing operations for the stated domain.
Maintenance
Related MCP Connectors
Zero-config MCP security scanner for AI-generated apps. 25K+ vulnerability patterns.
Security scanner for MCP servers. Detect vulnerabilities, prompt injection, and tool poisoning.
Security, SEO and AI-visibility scanner for web apps · free scans and focused checks via MCP.
Scan any public GitHub MCP-server repo for security issues. 37 MCP-specific L1 rules, 8 languages.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceSecurity scanner for MCP servers and AI-generated code. Detects leaked API keys, PII, prompt injection, and MCP misconfigs with A-F security grades.MIT
- AlicenseAqualityAmaintenanceSecurity MCP server with 300+ rules for AI-generated code. Scans Next.js, Supabase, Clerk, Stripe, Prisma, Hono, GraphQL and 20+ modules. Zero config, runs locally.39331 npm5Apache 2.0
- AlicenseAqualityCmaintenanceMCP server that lets AI coding agents (Claude Code, Cursor, Cline) audit Supabase projects for security misconfigurations AND apply the fixes — without leaving the agent. Tools: audit_project, list_findings, preview_fix (BEGIN/ROLLBACK safety), apply_fix (with confirmation), apply_all_fixes (transactional bulk). Closes the audit-fix loop entirely in the agent — other Supabase scanners only report.52 npm1MIT
- AlicenseNot gradedqualityFmaintenancePredeploy security scanner for AI-generated code. 80+ vulnerability patterns across secrets, auth, injection, config, Supabase, and logging. Runs locally, code never leaves your machine. Optional x402 witnessed attestation.46 npmApache 2.0