Kelp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
No arguments | |||
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": true
} |
| prompts | {
"listChanged": true
} |
| resources | {
"listChanged": true
} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| scan_pathA | 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. |
| scan_snippetA | 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. |
| list_rulesA | 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. |
| explain_findingA | 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. |
| explain_ruleA | 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. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
| review-repo | Ask the assistant to run Kelp over a project and summarize the critical and high findings with concrete remediation. |
| harden-file | Ask the assistant to scan one file, then rewrite it in place to remove any critical or high finding. |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
| rules-catalog | Every detection rule Kelp knows about, as JSON. |
| stripe-secret-live | A live Stripe secret key can charge cards, refund customers, and read your live payment data. Once it is public on GitHub it is compromised, even after removal, because the git object is still fetchable. |
| stripe-secret-test | Test-mode keys cannot charge real cards but still expose your test data and give attackers a working handle into your Stripe account structure. |
| stripe-webhook-secret | A leaked whsec_ lets anyone forge signed webhook payloads and bypass the receiving service's authenticity check, which means arbitrary state changes can be triggered by an attacker. |
| aws-access-key-id | Even alone, an AWS access key id narrows the search space. Combined with the very common paired secret key leak, this becomes a full account takeover vector. |
| github-token | A leaked GitHub token can read private repos, push code, and open PRs as the token owner. On a CI account, that is a supply-chain compromise. |
| github-pat-fine | Fine-grained PATs are still bearer tokens. Their narrower scope reduces blast radius but does not remove it, especially if the token has repo write. |
| anthropic-key | An Anthropic API key can spend the account's balance and read message history. Costs can escalate to hundreds of dollars in hours if abused. |
| openai-project-key | sk-proj-... project keys have organization-wide blast radius on the OpenAI side and can be used to spend the balance. |
| openai-key | A leaked OpenAI key can spend account balance and access model usage history. |
| google-api-key | Google / Firebase API keys are usually origin-restricted on the client, but an unrestricted key or one used server-side can incur billing and expose services. |
| slack-token | Slack tokens can read messages, post as bots, and depending on scope, invite users or exfiltrate DM history. |
| private-key-block | A PEM-formatted private key in the repo means whichever service uses that keypair is now impersonable by any reader of the repo. |
| supabase-service-role | The service_role key bypasses RLS and gives read plus write to every table in the project. If it ships to the browser or leaks to GitHub, the database is fully compromised. |
| jwt-exposed | A JWT in source code is either a session token that should never be committed, or a signing artifact whose secret is likely near. |
| high-entropy-string | The value looks like a credential by shape and entropy but does not match a known provider pattern. Likely still a secret, treat as suspicious until proven otherwise. |
| rls_disabled | The table lives in the `public` schema (reachable through PostgREST) and does not have RLS enabled. Any caller with the anon key can read and write every row. |
| permissive_policy | The table has an ownership column and a policy that uses `USING (true)` or `WITH CHECK (true)` for a client role. RLS is enabled but the policy grants access to every row. |
| owner_not_scoped | The table has an obvious owner column (user_id, owner_id, tenant_id, etc.) but none of the client-facing policies check `auth.uid() = <owner_column>`. Rows are probably not tenant-scoped. |
| rls_no_policies | RLS is on but the table has zero policies for anon or authenticated. Postgres defaults to deny, so the API refuses the table entirely. |
| fk_leak_to_unprotected | An RLS-protected parent table has a foreign key to a target table that has RLS disabled. A PostgREST caller can embed the target through the FK (e.g. ?select=parent(*,child(*))) and read every row of the target even though the parent is protected. |
| command_scope_gap | A table has RLS enabled, a SELECT policy that scopes reads to the owner, but no policy for INSERT/UPDATE/DELETE while a GRANT still lets a client role write. Reads are safe, writes are wide open. |
| storage_public_bucket | The bucket is created with `public = true`. Every object in it is served over Supabase's CDN without authentication. Anyone who guesses or captures a path (from HTML, an error log, a shared link) can download the file. Common accidental leak of user-uploaded documents, private avatars, or attachments. |
| storage_policy_missing_user_scope | A policy on `storage.objects` filters on `bucket_id = 'X'` but never checks `auth.uid()`, `owner`, or `auth.jwt()`. Every authenticated caller can read or write every other user's files in that bucket. Common mistake when copying tutorials that stop at 'restrict to bucket'. |
| storage_policy_permissive | A policy on `storage.objects` uses `USING (true)` or `WITH CHECK (true)` for a client-facing role. Any authenticated caller reaches every file in the entire storage.objects table, ignoring bucket and owner. |
| view_bypasses_rls | A `CREATE VIEW` over an RLS-protected base table without `WITH (security_invoker = true)` runs with the view owner's policies (usually postgres), which bypasses RLS on the base table entirely. |
| supabase-config-verify-jwt-false | verify_jwt = false in supabase/config.toml disables the built-in JWT verification for that function, meaning it accepts any unauthenticated caller. Usually a mistake, sometimes a public-by-design function that then must gate on a shared secret in the handler. |
| route_handler_no_auth | An exported route handler (app/**/route.ts or a legacy pages/api/** default export) has no recognized authentication call anywhere in the file: no getUser/getSession, no requireUser helper, no verified webhook signature. Mutations (POST/PUT/PATCH/DELETE) reachable without a session let any caller write on behalf of anyone; an unauthenticated GET that touches a backend leaks other users' data. This is a heuristic: auth enforced only in middleware or an imported wrapper will read as a false positive, so treat it as a lead to confirm, not proof. |
| server_action_no_auth | A "use server" action reads formData (or touches a backend) with no recognized authentication call. Server actions are public POST endpoints the framework exposes to any caller, not just the form they were written for, so an unauthenticated action lets anyone invoke the write with a crafted request. Heuristic, same caveat as route handlers. |
| firebase_rule_public | A Firestore or Storage security rule uses `allow ...: if true`, so the collection or bucket path is open to the entire internet with no authentication. Critical when it grants a write (anyone can overwrite or delete every document in the path), high when it is a public read (anyone can dump the collection). This is the Firebase equivalent of a `USING (true)` RLS policy. |
| firebase_rule_unauthenticated_write | A Firestore/Storage rule allows a write (write/create/update/delete) with a condition that never references `request.auth`, so an unauthenticated caller can write as long as the non-auth constraints pass. Payload validation is not authentication. |
| firebase_rule_write_no_owner | A rule requires the caller to be signed in but never binds the write to the document owner, so any authenticated user can overwrite anyone else's data. The Firebase version of an RLS policy that checks the JWT exists but not `auth.uid()`. Heuristic: rules that delegate to a user-defined `function()` helper are treated as guarded and not flagged. |
| client_exposed_secret | A var named with a public prefix (NEXT_PUBLIC_, VITE_, REACT_APP_, EXPO_PUBLIC_, and friends) is inlined into the client bundle by the build tool, so its value ships to every visitor. When the name is a backend secret (SERVICE_ROLE, SECRET_KEY, PRIVATE_KEY, PASSWORD), that secret is readable by anyone. A Supabase service_role key exposed this way bypasses Row Level Security entirely: full read/write on the whole database from the browser. Anon and publishable keys are public by design and are not flagged. |
| open_redirect | A redirect sink (next/navigation redirect, NextResponse.redirect, res.redirect, location assignment) is fed a target that comes from the request (a query param or other user input) with no allowlist or relative-path check. An attacker sends a link to the trusted domain that bounces the victim to an attacker-controlled site; in an OAuth/auth callback it can carry the code or token off-site. High severity in an auth/callback file. Heuristic: files with a relative-path guard, an origin comparison, or a named allowlist/validator are not flagged. |
| cors_reflect_credentials | The response echoes the request's Origin header into Access-Control-Allow-Origin and sets Access-Control-Allow-Credentials: true. Any website the victim visits can make credentialed cross-origin requests and read the response, so it can steal authenticated data. The real, exploitable CORS bug. |
| cors_wildcard_credentials | Access-Control-Allow-Origin is '*' while Access-Control-Allow-Credentials is true. Browsers reject this exact combination, so it usually gets 'fixed' by reflecting the Origin, which is the exploitable case. A loud misconfiguration worth fixing before it becomes origin reflection. |
| cors_wildcard | Access-Control-Allow-Origin is '*', so any website can read this endpoint's responses cross-origin. Fine for a genuinely public, unauthenticated API; a smell on anything that returns user-specific data. |
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.