ProofmergeA senior engineer reviews what your AI wrote.

Launch Security Check

Security review of AI-built and AI-powered software
Prepared forDemo Client (sample data)
Applicationsample-app
Scan date2026-11-11
Review statusReviewed by Demo reviewer (illustrative decisions on planted sample data), 2026-11-11
Findings70 (15 critical, 42 high)
0of 100
Grade
F
Not ready to launch. Critical issues expose data, credentials or infrastructure.
Reviewed. Every finding below was confirmed by Demo reviewer (illustrative decisions on planted sample data) (2026-11-11). 1 automated finding(s) were dismissed as false positives and are listed in the appendix.

1. Executive summary

We scanned sample-app (19 files, 396 lines) with LaunchCheck built-in checks and Gitleaks. The scan produced 70 findings across 27 distinct issue types: 15 critical, 42 high, 10 medium and 3 low.

Assessment: grade F, score 0/100. Not ready to launch. Critical issues expose data, credentials or infrastructure.

Every finding in this report was reviewed by Demo reviewer (illustrative decisions on planted sample data); 1 automated finding(s) were dismissed as false positives.

Highest risks

  1. Hardcoded API key or provider secret (7 places) - Anyone who can read the code - a contractor, a leaked zip, a public repo, an AI tool with repo access - can use this key.
  2. LLM output passed to a dangerous sink (3 places) - Model output is untrusted text: a prompt-injected document or user can make the model write anything.
  3. Secret exposed to the browser through a public env prefix (2 places) - Variables with these prefixes are copied into the JavaScript that every visitor downloads.
  4. Table created without Row Level Security (2 places) - Supabase exposes every table in the public schema through an auto-generated API.
  5. Private key committed to the repository (1 place) - A private key proves identity.

Recommended order of work

  1. Rotate every exposed credential and remove secrets from the repository. Assume they are already compromised.
  2. Close access-control gaps: database row-level rules and authentication on every API route.
  3. Remove injection paths and stop executing model output; restrict agent tools and MCP servers.
  4. Harden configuration (CORS, debug, rate limits) and lock dependency versions.

2. Severity breakdown

15critical
42high
10medium
3low

By area

LLM and agents20
Secrets16
Access control9
Configuration8
Injection8
Dependencies5
Supabase4

By OWASP category

A01:2025 Broken Access Control13
A02:2025 Security Misconfiguration8
A03:2025 Software Supply Chain Failures5
A04:2025 Cryptographic Failures1
A05:2025 Injection11
A06:2025 Insecure Design3
A07:2025 Authentication Failures15
LLM01:2025 Prompt Injection3
LLM02:2025 Sensitive Information Disclosure5
LLM03:2025 Supply Chain2
LLM05:2025 Improper Output Handling3
LLM06:2025 Excessive Agency9
LLM10:2025 Unbounded Consumption5

OWASP Top 10:2025 (web) and OWASP Top 10 for LLM Applications 2025.

3. Findings

#SeverityFindingLocationConfidenceSourceStatus
1criticalLLM output passed to a dangerous sink
LLM002-0834971b
agent-service/main.py:56confirmed by reviewerlaunchcheckconfirmed
2criticalLLM output passed to a dangerous sink
LLM002-46fc0b91
agent-service/main.py:68confirmed by reviewerlaunchcheckconfirmed
3criticalHardcoded API key or provider secret
SEC001-ed715aa0
.env:4confirmed by reviewergitleaks, launchcheckconfirmed
4criticalHardcoded API key or provider secret
SEC001-5dbd24da
.env:5confirmed by reviewerlaunchcheckconfirmed
5criticalHardcoded API key or provider secret
SEC001-815d437c
.env:6confirmed by reviewerlaunchcheckconfirmed
6criticalHardcoded API key or provider secret
SEC001-44f9a518
.env:7confirmed by reviewerlaunchcheckconfirmed
7criticalHardcoded API key or provider secret
SEC001-5b1f5a03
.env:8confirmed by reviewerlaunchcheckconfirmed
8criticalHardcoded API key or provider secret
SEC001-926d29a2
agent-service/main.py:11confirmed by reviewerlaunchcheckconfirmed
9criticalHardcoded API key or provider secret
SEC001-7e075a0c
lib/stripe.ts:3confirmed by reviewerlaunchcheckconfirmed
10criticalPrivate key committed to the repository
SEC002-b32f4c47
agent-service/keys/jwt_signing.pem:1confirmed by reviewergitleaks, launchcheckconfirmed
11criticalSecret exposed to the browser through a public env prefix
SEC004-db028cea
.env:4confirmed by reviewerlaunchcheckconfirmed
12criticalSecret exposed to the browser through a public env prefix
SEC004-d4fe1e67
.env:5confirmed by reviewerlaunchcheckconfirmed
13criticalTable created without Row Level Security
SUPA001-071de26e
supabase/migrations/0001_init.sql:12confirmed by reviewerlaunchcheckconfirmed
14criticalTable created without Row Level Security
SUPA001-0a48b64a
supabase/migrations/0001_init.sql:19confirmed by reviewerlaunchcheckconfirmed
15criticalSupabase service-role key used in frontend code
SUPA003-5722a896
lib/supabaseClient.ts:6confirmed by reviewerlaunchcheckconfirmed
16highNext.js API route with no authentication check
AUTH001-ec7eccc5
app/api/admin/users/route.ts:6confirmed by reviewerlaunchcheckconfirmed
17highNext.js API route with no authentication check
AUTH001-22590ed4
app/api/chat/route.ts:6confirmed by reviewerlaunchcheckconfirmed
18highNext.js API route with no authentication check
AUTH001-636574b9
app/api/export/route.ts:4confirmed by reviewerlaunchcheckconfirmed
19highExpress route with no authentication middleware
AUTH002-2acf1372
services/admin-api/index.js:10confirmed by reviewerlaunchcheckconfirmed
20highExpress route with no authentication middleware
AUTH002-20f7a06e
services/admin-api/index.js:14confirmed by reviewerlaunchcheckconfirmed
21highFastAPI/Flask endpoint with no authentication check
AUTH003-e94c439d
agent-service/main.py:44confirmed by reviewerlaunchcheckconfirmed
22highFastAPI/Flask endpoint with no authentication check
AUTH003-10dda696
agent-service/main.py:60confirmed by reviewerlaunchcheckconfirmed
23highFastAPI/Flask endpoint with no authentication check
AUTH003-efb41921
agent-service/main.py:72confirmed by reviewerlaunchcheckconfirmed
24highFastAPI/Flask endpoint with no authentication check
AUTH003-14cf804a
agent-service/main.py:78confirmed by reviewerlaunchcheckconfirmed
25highPermissive CORS configuration
CFG001-e938a299
agent-service/main.py:17confirmed by reviewerlaunchcheckconfirmed
26highPermissive CORS configuration
CFG001-8f7c9e91
app/api/admin/users/route.ts:11confirmed by reviewerlaunchcheckconfirmed
27highPackage name on the known-bad / typosquat sample list
DEP003-bc9c4c1d
agent-service/requirements.txt:5confirmed by reviewerlaunchcheckconfirmed
28highPackage name on the known-bad / typosquat sample list
DEP003-2d448800
package.json:17confirmed by reviewerlaunchcheckconfirmed
29highSQL query built by string concatenation
INJ001-cbde9238
agent-service/main.py:74confirmed by reviewerlaunchcheckconfirmed
30highSQL query built by string concatenation
INJ001-6124f1ce
app/api/admin/users/route.ts:8confirmed by reviewerlaunchcheckconfirmed
31highSQL query built by string concatenation
INJ001-8f6ecf3e
app/api/admin/users/route.ts:19confirmed by reviewerlaunchcheckconfirmed
32highDynamic code execution (eval / exec)
INJ002-139ae6a1
app/api/export/route.ts:7confirmed by reviewerlaunchcheckconfirmed
33highShell command built from variable input
INJ004-8e5070dd
agent-service/main.py:27confirmed by reviewerlaunchcheckconfirmed
34highShell command built from variable input
INJ004-9baecebe
agent-service/main.py:80confirmed by reviewerlaunchcheckconfirmed
35highShell command built from variable input
INJ004-5e202ed0
app/api/export/route.ts:6confirmed by reviewerlaunchcheckconfirmed
36highUser input placed directly into the system prompt
LLM001-a488e80b
agent-service/main.py:47confirmed by reviewerlaunchcheckconfirmed
37highUser input placed directly into the system prompt
LLM001-b5fc5614
app/api/chat/route.ts:9confirmed by reviewerlaunchcheckconfirmed
38highUser input placed directly into the system prompt
LLM001-1205aa43
app/api/chat/route.ts:10confirmed by reviewerlaunchcheckconfirmed
39highLLM output passed to a dangerous sink
LLM002-8815d0b7
components/ChatWindow.tsx:21confirmed by reviewerlaunchcheckconfirmed
40highAgent tool with unrestricted shell, file or network access
LLM004-325d8342
agent-service/main.py:25confirmed by reviewerlaunchcheckconfirmed
41highAgent tool with unrestricted shell, file or network access
LLM004-b1af7026
agent-service/main.py:30confirmed by reviewerlaunchcheckconfirmed
42highAgent tool with unrestricted shell, file or network access
LLM004-a0a556dd
agent-service/main.py:36confirmed by reviewerlaunchcheckconfirmed
43highAgent tool with unrestricted shell, file or network access
LLM004-95c80ab1
agent-service/run_agent.sh:3confirmed by reviewerlaunchcheckconfirmed
44highPlaintext credential in MCP / agent configuration
MCP001-50ee263a
.mcp.json:18confirmed by reviewerlaunchcheckconfirmed
45highPlaintext credential in MCP / agent configuration
MCP001-24016329
.mcp.json:26confirmed by reviewerlaunchcheckconfirmed
46highPlaintext credential in MCP / agent configuration
MCP001-273c6cc0
.mcp.json:42confirmed by reviewerlaunchcheckconfirmed
47highOver-broad MCP server or agent permission
MCP002-dfe084cf
.mcp.json:3confirmed by reviewerlaunchcheckconfirmed
48highOver-broad MCP server or agent permission
MCP002-7a9e2b03
.mcp.json:29confirmed by reviewerlaunchcheckconfirmed
49highOver-broad MCP server or agent permission
MCP002-1e115d6c
.mcp.json:35confirmed by reviewerlaunchcheckconfirmed
50highOver-broad MCP server or agent permission
MCP002-10d1ff23
.mcp.json:40confirmed by reviewerlaunchcheckconfirmed
51highEnvironment file (.env) is in the repository and not git-ignored
SEC003-7761a88b
.env:1confirmed by reviewerlaunchcheckconfirmed
52highHardcoded password, token or signing secret
SEC005-67123f9e
.env:9confirmed by reviewerlaunchcheckconfirmed
53highHardcoded password, token or signing secret
SEC005-e913daa2
.env:10confirmed by reviewerlaunchcheckconfirmed
54highHardcoded password, token or signing secret
SEC005-e32d61fd
.env:11confirmed by reviewerlaunchcheckconfirmed
55highHardcoded password, token or signing secret
SEC005-315b9ae7
agent-service/main.py:12confirmed by reviewerlaunchcheckconfirmed
56highHardcoded password, token or signing secret
SEC005-483dc713
lib/stripe.ts:4confirmed by reviewerlaunchcheckconfirmed
57highRow Level Security policy allows everyone (USING true)
SUPA002-f69f4715
supabase/migrations/0001_init.sql:10confirmed by reviewerlaunchcheckconfirmed
58mediumPermissive CORS configuration
CFG001-2a6c3142
services/admin-api/index.js:5confirmed by reviewerlaunchcheckconfirmed
59mediumDebug mode enabled
CFG002-f25c368a
.env:12confirmed by reviewerlaunchcheckconfirmed
60mediumDebug mode enabled
CFG002-0a75e2b9
agent-service/main.py:14confirmed by reviewerlaunchcheckconfirmed
61mediumNo rate limiting on login or LLM endpoint
CFG003-f7c026dd
agent-service/main.py:44confirmed by reviewerlaunchcheckconfirmed
62mediumNo rate limiting on login or LLM endpoint
CFG003-a45f1629
app/api/chat/route.ts:6confirmed by reviewerlaunchcheckconfirmed
63mediumNo rate limiting on login or LLM endpoint
CFG003-dfd50c52
services/admin-api/index.js:19confirmed by reviewerlaunchcheckconfirmed
64mediumNo dependency lockfile
DEP001-43992ce2
package.json:1confirmed by reviewerlaunchcheckconfirmed
65mediumRaw HTML rendering (dangerouslySetInnerHTML / innerHTML)
INJ003-f0cc179f
components/NoteCard.tsx:5confirmed by reviewerlaunchcheckconfirmed
66mediumLLM call without an output-length or cost limit
LLM003-831a66a1
agent-service/main.py:48confirmed by reviewerlaunchcheckconfirmed
67mediumLLM call without an output-length or cost limit
LLM003-14f0356a
app/api/chat/route.ts:12confirmed by reviewerlaunchcheckconfirmed
68lowUnpinned dependency versions
DEP002-78a19d7b
agent-service/requirements.txt:1confirmed by reviewerlaunchcheckconfirmed
69lowUnpinned dependency versions
DEP002-dcbc8802
package.json:13confirmed by reviewerlaunchcheckconfirmed
70lowOver-broad MCP server or agent permission
MCP002-a261e9de
.mcp.json:7confirmed by reviewerlaunchcheckconfirmed

Confidence: confirmed-by-pattern - the matched text is itself the issue (for example a key in a file). needs-human-verification - the pattern indicates a likely issue whose impact depends on context a person must check.

4. Finding details

critical

Hardcoded API key or provider secret

SEC001 · 7 instances
A07:2025 Authentication Failures

Why it matters

Anyone who can read the code - a contractor, a leaked zip, a public repo, an AI tool with repo access - can use this key. For LLM and payment providers that means running up your bill or moving money; for cloud keys it can mean full account takeover.

How to fix

Revoke and rotate the key at the provider now (assume it is compromised). Load it from a server-side environment variable or a secret manager, and remove it from git history (git filter-repo) if the repository was ever shared.

Severity basis: A string matching a provider key format (or a decoded Supabase service-role JWT) is stored in a repository file.

.env:4 · SEC001-ed715aa0
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=eyJh****[174 chars]
Supabase service-role key (eyJh****[174 chars])
critical confirmed by reviewer source: gitleaks, launchcheck
Severity basis: A string matching a provider key format (or a decoded Supabase service-role JWT) is stored in a repository file. Corroborated by 2 independent sources (gitleaks, launchcheck).
.env:5 · SEC001-5dbd24da
NEXT_PUBLIC_OPENAI_API_KEY=sk-p****[48 chars]
OpenAI API key (sk-p****[48 chars])
critical confirmed by reviewer source: launchcheck
.env:6 · SEC001-815d437c
OPENAI_API_KEY=sk-p****[48 chars]
OpenAI API key (sk-p****[48 chars])
critical confirmed by reviewer source: launchcheck
.env:7 · SEC001-44f9a518
STRIPE_SECRET_KEY=sk_l****[41 chars]
Stripe live secret key (sk_l****[41 chars])
critical confirmed by reviewer source: launchcheck
.env:8 · SEC001-5b1f5a03
AWS_ACCESS_KEY_ID=AKIA****[20 chars]
AWS access key ID (AKIA****[20 chars])
critical confirmed by reviewer source: launchcheck
agent-service/main.py:11 · SEC001-926d29a2
ANTHROPIC_API_KEY = "sk-a****[39 chars]"
Anthropic API key (sk-a****[39 chars])
critical confirmed by reviewer source: launchcheck
lib/stripe.ts:3 · SEC001-7e075a0c
export const stripe = new Stripe("sk_l****[41 chars]");
Stripe live secret key (sk_l****[41 chars])
critical confirmed by reviewer source: launchcheck
critical

LLM output passed to a dangerous sink

LLM002 · 3 instances
A05:2025 InjectionLLM05:2025 Improper Output Handling

Why it matters

Model output is untrusted text: a prompt-injected document or user can make the model write anything. Running that text as code, SQL, a shell command or HTML hands the attacker your server, database or users' browsers.

How to fix

Never execute model output directly. Validate it against a strict schema, run generated SQL read-only with a limited role, run code only in an isolated sandbox, and render text as text.

Severity basis: A variable holding model output flows into eval/exec, a shell command, a raw SQL call or raw HTML. Rated high for HTML sinks.

agent-service/main.py:56 · LLM002-0834971b
exec(generated_code)
model output `generated_code` reaches code execution
critical confirmed by reviewer source: launchcheck
agent-service/main.py:68 · LLM002-46fc0b91
rows = db.execute(generated_sql).fetchall()
model output `generated_sql` reaches a raw SQL call
critical confirmed by reviewer source: launchcheck
components/ChatWindow.tsx:21 · LLM002-8815d0b7
<div dangerouslySetInnerHTML={{ __html: aiResponse }} />
model output `aiResponse` reaches raw HTML
high confirmed by reviewer source: launchcheck
critical

Secret exposed to the browser through a public env prefix

SEC004 · 2 instances
A02:2025 Security MisconfigurationLLM02:2025 Sensitive Information Disclosure

Why it matters

Variables with these prefixes are copied into the JavaScript that every visitor downloads. Naming a secret this way publishes it.

How to fix

Rename the variable without the public prefix, use it only in server code (API routes, server actions), rotate the secret, and have the browser call your own endpoint instead of the provider directly.

Severity basis: A variable whose name marks it as secret uses a prefix (NEXT_PUBLIC_, VITE_, ...) that the build tool inlines into client JavaScript.

.env:4 · SEC004-db028cea
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=eyJh****[174 chars]
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY (also referenced in lib/supabaseClient.ts)
critical confirmed by reviewer source: launchcheck
.env:5 · SEC004-d4fe1e67
NEXT_PUBLIC_OPENAI_API_KEY=sk-p****[48 chars]
NEXT_PUBLIC_OPENAI_API_KEY
critical confirmed by reviewer source: launchcheck
critical

Table created without Row Level Security

SUPA001 · 2 instances
A01:2025 Broken Access Control

Why it matters

Supabase exposes every table in the public schema through an auto-generated API. Without Row Level Security, anyone holding the public anon key (it ships in your frontend) can read and change every row of this table.

How to fix

Run `ALTER TABLE <table> ENABLE ROW LEVEL SECURITY;` and add policies that tie rows to `auth.uid()`. Test with the anon key that other users' rows are not returned.

Severity basis: A CREATE TABLE in the public schema has no matching ENABLE ROW LEVEL SECURITY in any scanned migration.

supabase/migrations/0001_init.sql:12 · SUPA001-071de26e
create table public.notes (
table `notes`
critical confirmed by reviewer source: launchcheck
supabase/migrations/0001_init.sql:19 · SUPA001-0a48b64a
create table public.payments (
table `payments`
critical confirmed by reviewer source: launchcheck
critical

Private key committed to the repository

SEC002 · 1 instance
A04:2025 Cryptographic Failures

Why it matters

A private key proves identity. With it an attacker can sign tokens your app trusts, decrypt data, or log in to servers.

How to fix

Generate a new key pair, replace the old public key everywhere, and keep the private key outside the repository (secret manager or deployment-platform secret).

Severity basis: A PEM private-key header is present in a repository file. Corroborated by 2 independent sources (gitleaks, launchcheck).

agent-service/keys/jwt_signing.pem:1 · SEC002-b32f4c47
-----BEGIN RSA PRIVATE KEY-----
critical confirmed by reviewer source: gitleaks, launchcheck
critical

Supabase service-role key used in frontend code

SUPA003 · 1 instance
A01:2025 Broken Access Control

Why it matters

The service-role key bypasses Row Level Security completely. If it reaches the browser, any visitor can read, change or delete the whole database.

How to fix

Use the anon key in the browser. Keep the service-role key in server-only code (API route, server action, edge function) and rotate it now.

Severity basis: Code that is bundled for the browser (a client component, or a module it imports) references the service-role key.

lib/supabaseClient.ts:6 · SUPA003-5722a896
process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY!
imported by client code components/ChatWindow.tsx
critical confirmed by reviewer source: launchcheck
high

Over-broad MCP server or agent permission

MCP002 · 5 instances
LLM06:2025 Excessive Agency

Why it matters

MCP servers act with your developer's or your server's privileges. A filesystem server on `/` or a shell server lets any prompt injection reach everything on the machine.

How to fix

Limit filesystem servers to the project folder, remove shell servers or wrap them in an allowlist, pin server package versions, and keep approval prompts on.

Severity basis: An MCP server is configured with whole-disk access, command execution, auto-approval, an unpinned package or an unencrypted remote URL. Rated medium for unpinned packages.

.mcp.json:3 · MCP002-dfe084cf
"filesystem": {
server `filesystem`: filesystem access rooted at `/`
high confirmed by reviewer source: launchcheck
.mcp.json:29 · MCP002-7a9e2b03
"shell": {
server `shell`: gives the model command execution on this machine
high confirmed by reviewer source: launchcheck
.mcp.json:35 · MCP002-1e115d6c
"autoApprove": [
server `shell`: tools auto-approved without a prompt (autoApprove)
high confirmed by reviewer source: launchcheck
.mcp.json:40 · MCP002-10d1ff23
"url": "http://mcp.notegenie-demo.internal:8080/sse",
server `analytics`: remote server over unencrypted http
high confirmed by reviewer source: launchcheck
.mcp.json:7 · MCP002-a261e9de
"@modelcontextprotocol/server-filesystem",
unpinned server packages run at their latest version on every start: filesystem (@modelcontextprotocol/server-filesystem), github (@modelcontextprotocol/server-github), shell (mcp-shell-server)
low confirmed by reviewer source: launchcheck
Severity basis: An MCP server is configured with whole-disk access, command execution, auto-approval, an unpinned package or an unencrypted remote URL. Rated medium for unpinned packages. Reviewer adjusted severity from medium to low.
Reviewer note: Developer-machine configuration only; pin versions when convenient.
high

Hardcoded password, token or signing secret

SEC005 · 5 instances
A07:2025 Authentication Failures

Why it matters

A signing secret or password in source code is known to everyone with code access and usually never rotated. A leaked JWT secret lets an attacker mint a valid login for any user.

How to fix

Move the value to a server-side environment variable or secret manager, rotate it, and use a long random value.

Severity basis: A literal value is assigned to a name that indicates a credential (secret, password, token, api key).

.env:9 · SEC005-67123f9e
AWS_SECRET_ACCESS_KEY=FAKE****[36 chars]
AWS_SECRET_ACCESS_KEY = FAKE****[36 chars]
high confirmed by reviewer source: launchcheck
.env:10 · SEC005-e913daa2
JWT_SECRET=FAKE****[36 chars]
JWT_SECRET = FAKE****[36 chars]
high confirmed by reviewer source: launchcheck
.env:11 · SEC005-e32d61fd
DATABASE_URL=postgres://notegenie:FAKE****[21 chars]@db.notegenie-demo.internal:5432/app
connection string with an embedded password (FAKE****[21 chars])
high confirmed by reviewer source: launchcheck
agent-service/main.py:12 · SEC005-315b9ae7
ADMIN_PASSWORD = "FAKE****[24 chars]"
ADMIN_PASSWORD = FAKE****[24 chars]
high confirmed by reviewer source: launchcheck
lib/stripe.ts:4 · SEC005-483dc713
export const jwtSecret = "FAKE****[36 chars]";
jwtSecret = FAKE****[36 chars]
high confirmed by reviewer source: launchcheck
high

FastAPI/Flask endpoint with no authentication check

AUTH003 · 4 instances
A01:2025 Broken Access Control

Why it matters

This endpoint answers anyone on the internet who finds its URL - no login needed. AI coding tools often generate the happy path and leave the access check out.

How to fix

FastAPI: add `Depends(get_current_user)` to the route or router. Flask: add `@login_required` or verify the token in a `before_request` hook.

Severity basis: A route handler was found with no session, token or API-key check in the handler or in shared middleware.

agent-service/main.py:44 · AUTH003-e94c439d
@app.post("/agent/run")
POST /agent/run -> agent_run()
high confirmed by reviewer source: launchcheck
agent-service/main.py:60 · AUTH003-10dda696
@app.post("/agent/report")
POST /agent/report -> agent_report()
high confirmed by reviewer source: launchcheck
agent-service/main.py:72 · AUTH003-efb41921
@app.get("/notes/search")
GET /notes/search -> search_notes()
high confirmed by reviewer source: launchcheck
agent-service/main.py:78 · AUTH003-14cf804a
@app.post("/admin/backup")
POST /admin/backup -> backup()
high confirmed by reviewer source: launchcheck
high

Agent tool with unrestricted shell, file or network access

LLM004 · 4 instances
LLM06:2025 Excessive Agency

Why it matters

Whatever the tool can do, a prompt-injected model can be talked into doing: reading secrets from disk, running commands, calling internal services.

How to fix

Give each tool the smallest capability that works: fixed commands, one working directory, an allowlist of hosts, and human approval for destructive actions.

Severity basis: A function exposed to the model as a tool runs commands, touches files or fetches URLs with no allowlist or path restriction, or an agent is started with its permission prompts disabled.

agent-service/main.py:25 · LLM004-325d8342
def run_shell(command: str) -> str:
tool `run_shell` runs shell commands with no allowlist
high confirmed by reviewer source: launchcheck
agent-service/main.py:30 · LLM004-b1af7026
def read_file(path: str) -> str:
tool `read_file` reads or writes arbitrary files with no allowlist
high confirmed by reviewer source: launchcheck
agent-service/main.py:36 · LLM004-a0a556dd
def fetch_url(url: str) -> str:
tool `fetch_url` fetches arbitrary URLs with no allowlist
high confirmed by reviewer source: launchcheck
agent-service/run_agent.sh:3 · LLM004-95c80ab1
claude -p "fix the failing tests and push" --dangerously-skip-permissions
`--dangerously-skip-permissions` removes the safety checks around agent actions
high confirmed by reviewer source: launchcheck
high

Next.js API route with no authentication check

AUTH001 · 3 instances
A01:2025 Broken Access Control

Why it matters

This endpoint answers anyone on the internet who finds its URL - no login needed. AI coding tools often generate the happy path and leave the access check out.

How to fix

Check the session at the top of every handler (e.g. `const { data: { user } } = await supabase.auth.getUser()`; return 401 when missing) or protect the path in `middleware.ts`. Then check the user is allowed to touch the specific record.

Severity basis: A route handler was found with no session, token or API-key check in the handler or in shared middleware.

app/api/admin/users/route.ts:6 · AUTH001-ec7eccc5
export async function GET(req: Request) {
handlers: DELETE, GET
high confirmed by reviewer source: launchcheck
app/api/chat/route.ts:6 · AUTH001-22590ed4
export async function POST(req: Request) {
handlers: POST
high confirmed by reviewer source: launchcheck
app/api/export/route.ts:4 · AUTH001-636574b9
export async function POST(req: Request) {
handlers: POST
high confirmed by reviewer source: launchcheck
high

Permissive CORS configuration

CFG001 · 3 instances
A02:2025 Security Misconfiguration

Why it matters

CORS decides which websites may call your API from a visitor's browser. `*` means every website. Combined with credentials, some frameworks echo the caller's origin, so a malicious page can act with a logged-in user's session.

How to fix

List the exact origins you serve (your production and preview domains). Never combine a wildcard origin with credentials.

Severity basis: CORS allows any origin (`*`). Rated high when credentials are also allowed in the same configuration.

agent-service/main.py:17 · CFG001-e938a299
allow_origins=["*"],
wildcard origin together with credentials
high confirmed by reviewer source: launchcheck
app/api/admin/users/route.ts:11 · CFG001-8f7c9e91
"Access-Control-Allow-Origin": "*",
wildcard origin together with credentials
high confirmed by reviewer source: launchcheck
services/admin-api/index.js:5 · CFG001-2a6c3142
app.use(cors());
wildcard origin (no credentials seen nearby)
medium confirmed by reviewer source: launchcheck
high

SQL query built by string concatenation

INJ001 · 3 instances
A05:2025 Injection

Why it matters

If any part of that string comes from a user, they can rewrite the query: read other customers' data, change it, or delete tables.

How to fix

Use parameterised queries (`$1` / `%s` placeholders with a separate values array) or the query builder of your ORM. Never paste values into SQL text.

Severity basis: A SQL statement is assembled with string interpolation or concatenation instead of bound parameters.

agent-service/main.py:74 · INJ001-cbde9238
cur = db.execute(f"SELECT id, title FROM notes WHERE owner = '{owner}' AND title LIKE '%{q}%'")
high confirmed by reviewer source: launchcheck
app/api/admin/users/route.ts:8 · INJ001-6124f1ce
const result = await pool.query(`SELECT id, email, plan FROM users WHERE email = '${email}'`);
high confirmed by reviewer source: launchcheck
app/api/admin/users/route.ts:19 · INJ001-8f6ecf3e
await pool.query("DELETE FROM users WHERE id = " + id);
high confirmed by reviewer source: launchcheck
high

Shell command built from variable input

INJ004 · 3 instances
A05:2025 Injection

Why it matters

If a user controls any part of the command, adding `; rm -rf /` or `| curl attacker` runs their command on your server.

How to fix

Call the program with an argument list and no shell (`subprocess.run([...])`, `execFile`), and validate the input against an allowlist.

Severity basis: A shell command string is built from a variable and run through a shell.

agent-service/main.py:27 · INJ004-8e5070dd
return subprocess.run(command, shell=True, capture_output=True, text=True).stdout
shell=True with a non-constant command
high confirmed by reviewer source: launchcheck
agent-service/main.py:80 · INJ004-9baecebe
os.system("tar czf /backups/" + payload["name"] + ".tgz /data")
high confirmed by reviewer source: launchcheck
app/api/export/route.ts:6 · INJ004-5e202ed0
const dump = execSync(`pg_dump --table=${table} $DATABASE_URL`).toString();
high confirmed by reviewer source: launchcheck
high

User input placed directly into the system prompt

LLM001 · 3 instances
LLM01:2025 Prompt Injection

Why it matters

The system prompt is the model's rulebook. Text a user types into it is read as rules, so a user can tell the model to ignore your instructions, reveal the prompt, or misuse its tools (prompt injection).

How to fix

Keep the system prompt constant. Pass user text only as a separate `user` message, treat it as untrusted data, and enforce permissions in code, not in the prompt.

Severity basis: A value that looks user-controlled is interpolated into the system prompt or instructions string.

agent-service/main.py:47 · LLM001-a488e80b
system_prompt = f"You are the NoteGenie agent. The user's instructions are: {user_message}"
interpolates `user_message`
high confirmed by reviewer source: launchcheck
app/api/chat/route.ts:9 · LLM001-b5fc5614
const systemPrompt = `You are NoteGenie, a helpful assistant for ${userName}.
interpolates `userName`
high confirmed by reviewer source: launchcheck
app/api/chat/route.ts:10 · LLM001-1205aa43
Follow the user's preferences: ${question}`;
interpolates `question`
high confirmed by reviewer source: launchcheck
high

Plaintext credential in MCP / agent configuration

MCP001 · 3 instances
A07:2025 Authentication FailuresLLM02:2025 Sensitive Information Disclosure

Why it matters

MCP config files are often committed and are read by every AI tool that opens the project. A token here is shared with all of them.

How to fix

Reference an environment variable (`${GITHUB_TOKEN}`) instead of the value, rotate the exposed credential, and scope the new one to the minimum.

Severity basis: An MCP or agent configuration file contains a literal token, password or connection string instead of an environment reference.

.mcp.json:18 · MCP001-50ee263a
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_****[37 chars]"
server `github`: GITHUB_PERSONAL_ACCESS_TOKEN holds a literal credential
high confirmed by reviewer source: launchcheck
.mcp.json:26 · MCP001-24016329
"postgresql://admin:FAKE****[26 chars]@db.notegenie-demo.internal:5432/production"
server `postgres`: argument holds a literal credential
high confirmed by reviewer source: launchcheck
.mcp.json:42 · MCP001-273c6cc0
"Authorization": "Bearer FAKE****[32 chars]"
server `analytics`: Authorization holds a literal credential
high confirmed by reviewer source: launchcheck
high

Express route with no authentication middleware

AUTH002 · 2 instances
A01:2025 Broken Access Control

Why it matters

This endpoint answers anyone on the internet who finds its URL - no login needed. AI coding tools often generate the happy path and leave the access check out.

How to fix

Add an authentication middleware (`app.use(requireAuth)` or per route) that verifies the session or token before the handler runs.

Severity basis: A route handler was found with no session, token or API-key check in the handler or in shared middleware.

services/admin-api/index.js:10 · AUTH002-2acf1372
app.get("/admin/users", (req, res) => {
GET /admin/users
high confirmed by reviewer source: launchcheck
services/admin-api/index.js:14 · AUTH002-20f7a06e
app.post("/admin/users/:id/plan", async (req, res) => {
POST /admin/users/:id/plan
high confirmed by reviewer source: launchcheck
high

Package name on the known-bad / typosquat sample list

DEP003 · 2 instances
A03:2025 Software Supply Chain FailuresLLM03:2025 Supply Chain

Why it matters

AI assistants sometimes invent or misspell package names; attackers register those names with malware inside. Installing one runs their code on your machine and in your build.

How to fix

Remove the package, install the intended one, and check machines and CI that installed it. Verify against a current advisory database (OSV, GitHub Advisories).

Severity basis: A declared dependency name matches the bundled SAMPLE list of publicly reported malicious or typosquatted package names.

agent-service/requirements.txt:5 · DEP003-bc9c4c1d
colourama
`colourama` (pypi): typosquat of colorama reported in 2018 [sample list]
high confirmed by reviewer source: launchcheck
package.json:17 · DEP003-2d448800
"crossenv": "^7.0.0",
`crossenv` (npm): typosquat reported in the 2017 npm 'crossenv' incident (intended: cross-env) [sample list]
high confirmed by reviewer source: launchcheck
high

Dynamic code execution (eval / exec)

INJ002 · 1 instance
A05:2025 Injection

Why it matters

eval and exec run text as code. If a user can influence that text, they run their own code on your server or in other users' browsers.

How to fix

Remove eval/exec. Parse the data instead (JSON.parse, ast.literal_eval, a maths-expression library) or use an explicit allowlist of operations.

Severity basis: eval, exec or new Function is called on a non-constant value.

app/api/export/route.ts:7 · INJ002-139ae6a1
const total = eval(formula);
high confirmed by reviewer source: launchcheck
high

Environment file (.env) is in the repository and not git-ignored

SEC003 · 1 instance
A02:2025 Security Misconfiguration

Why it matters

.env files hold the app's real credentials. If the file is committed, every clone, fork and backup of the repo carries them.

How to fix

Add `.env*` to .gitignore (keep `.env.example` with empty placeholders), remove the file from git, and rotate every secret it contained.

Severity basis: A non-example .env file exists in the tree and no .gitignore rule covers it.

.env:1 · SEC003-7761a88b
variables: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY, NEXT_PUBLIC_OPENAI_API_KEY, OPENAI_API_KEY, STRIPE_SECRET_KEY, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY ...
11 variables, 7 with credential-like names
high confirmed by reviewer source: launchcheck
high

Row Level Security policy allows everyone (USING true)

SUPA002 · 1 instance
A01:2025 Broken Access Control

Why it matters

A policy whose condition is simply `true` switches Row Level Security back off for that operation: every visitor passes the check.

How to fix

Replace `true` with a real ownership condition, e.g. `USING (auth.uid() = user_id)`. Keep `true` only for data that is intentionally public and read-only.

Severity basis: A CREATE POLICY statement uses USING (true) or WITH CHECK (true) and is not limited to the service role.

supabase/migrations/0001_init.sql:10 · SUPA002-f69f4715
for all using (true) with check (true);
policy "Profiles are editable by everyone" on `profiles`
high confirmed by reviewer source: launchcheck
medium

No rate limiting on login or LLM endpoint

CFG003 · 3 instances
A06:2025 Insecure DesignLLM10:2025 Unbounded Consumption

Why it matters

Without a limit, one script can guess passwords all night or call your LLM endpoint thousands of times - you pay the bill for every call.

How to fix

Add per-user and per-IP limits (e.g. @upstash/ratelimit, express-rate-limit, slowapi) and a spending cap at the LLM provider.

Severity basis: An authentication or LLM-calling endpoint was found and no rate-limiting library or middleware appears in the same service.

agent-service/main.py:44 · CFG003-f7c026dd
@app.post("/agent/run")
POST /agent/run
medium confirmed by reviewer source: launchcheck
app/api/chat/route.ts:6 · CFG003-a45f1629
export async function POST(req: Request) {
LLM endpoint /api/chat
medium confirmed by reviewer source: launchcheck
services/admin-api/index.js:19 · CFG003-dfd50c52
app.post("/login", (req, res) => {
POST /login
medium confirmed by reviewer source: launchcheck
medium

Debug mode enabled

CFG002 · 2 instances
A02:2025 Security Misconfiguration

Why it matters

Debug mode shows stack traces, configuration and sometimes an interactive console to whoever triggers an error.

How to fix

Turn debug off by default and enable it only from a local, uncommitted environment variable.

Severity basis: A debug flag is set to true in application code or a committed environment file.

.env:12 · CFG002-f25c368a
DEBUG=true
medium confirmed by reviewer source: launchcheck
agent-service/main.py:14 · CFG002-0a75e2b9
app = FastAPI(debug=True)
medium confirmed by reviewer source: launchcheck
medium

LLM call without an output-length or cost limit

LLM003 · 2 instances
LLM10:2025 Unbounded Consumption

Why it matters

Without a cap, a single request can produce the longest and most expensive answer the model allows; repeated, it becomes a bill you did not plan for.

How to fix

Set `max_tokens` / `max_output_tokens` on every call, cap input size, and set a monthly budget and alerts at the provider.

Severity basis: An LLM API call has no max-tokens style parameter in its arguments.

agent-service/main.py:48 · LLM003-831a66a1
response = client.chat.completions.create(
chat.completions.create(...) has no max-tokens parameter
medium confirmed by reviewer source: launchcheck
app/api/chat/route.ts:12 · LLM003-14f0356a
const completion = await openai.chat.completions.create({
chat.completions.create(...) has no max-tokens parameter
medium confirmed by reviewer source: launchcheck
medium

No dependency lockfile

DEP001 · 1 instance
A03:2025 Software Supply Chain Failures

Why it matters

Without a lockfile every install can pull different versions - including a freshly published malicious one. Builds are not reproducible.

How to fix

Commit the lockfile (package-lock.json, pnpm-lock.yaml, uv.lock, poetry.lock) and install with the frozen option in CI (`npm ci`).

Severity basis: A dependency manifest exists and no lockfile was found next to it or in a parent folder.

package.json:1 · DEP001-43992ce2
no package-lock.json / yarn.lock / pnpm-lock.yaml / bun.lock found
package.json with 11 dependencies and no lockfile
medium confirmed by reviewer source: launchcheck
medium

Raw HTML rendering (dangerouslySetInnerHTML / innerHTML)

INJ003 · 1 instance
A05:2025 Injection

Why it matters

Rendering raw HTML lets a stored value run script in other users' browsers (cross-site scripting): session theft and account takeover.

How to fix

Render as text. If HTML is required, sanitise it first with a maintained library (DOMPurify) and a strict allowlist.

Severity basis: A non-constant value is rendered as raw HTML.

components/NoteCard.tsx:5 · INJ003-f0cc179f
<div dangerouslySetInnerHTML={{ __html: note.body_html }} />
medium confirmed by reviewer source: launchcheck
low

Unpinned dependency versions

DEP002 · 2 instances
A03:2025 Software Supply Chain Failures

Why it matters

`latest` and `*` mean 'whatever was published most recently'. A compromised release is installed automatically.

How to fix

Pin exact versions (or rely on a committed lockfile) and update deliberately with a tool such as Dependabot or Renovate.

Severity basis: The manifest declares dependencies with `*`, `latest`, open ranges or no version at all.

agent-service/requirements.txt:1 · DEP002-78a19d7b
fastapi
fastapi, uvicorn>=0.30, openai, colourama
low confirmed by reviewer source: launchcheck
package.json:13 · DEP002-dcbc8802
"@supabase/supabase-js": "latest",
@supabase/supabase-js@latest, openai@*, stripe@>=14.0.0
low confirmed by reviewer source: launchcheck

5. What a human review adds

This document starts as the output of an automated first pass. In a paid engagement a senior engineer then works through it and the codebase. That review adds what tooling cannot:

6. Scope and limitations

In scope: a static review of the source tree sample-app as provided on 2026-11-11: 19 text files, 396 lines. Dependency folders, build output, lockfile contents, test fixtures and git-ignored .env files were skipped.

Scanners used

ScannerCoversStatusRaw findingsNote
LaunchCheck built-in checks
0.1.0
27 AI-app checks (standard library only, always available)ran70
Gitleaks
8.21.2
secrets in filesran3
Semgrep
static analysis rules for codefailed-could not download rules 'p/default' from the Semgrep registry (network blocked or offline); use --semgrep-config <local rules path> to run offline
Trivy (fs)
known dependency vulnerabilities, secrets, misconfigurationskipped-binary not found on PATH or in product/.tools/bin; adapter skipped

Findings from all scanners are normalised into one schema and de-duplicated (same rule class, file and line range); the Source column lists every scanner that reported a finding. A scanner shown as skipped or failed contributed nothing to this report.

Not covered

7. Method and scoring

Score. Start at 100. Each issue type with open findings costs its severity weight per instance, capped at two instances: critical 15, high 8, medium 3, low 1. Findings dismissed in review cost nothing. Grade: A 90+, B 80+, C 65+, D 50+, F below 50; an open critical finding caps the grade at D and an open high finding at B.

Severity is the default for the issue type, adjusted by the evidence found (stated under each finding) and by the reviewer. Secret values are masked everywhere in this report; only the first four characters are shown.

Checks run

IDCheckAreaResult
SEC001Hardcoded API key or provider secretSecrets7 findings
SEC002Private key committed to the repositorySecrets1 finding
SEC003Environment file (.env) is in the repository and not git-ignoredSecrets1 finding
SEC004Secret exposed to the browser through a public env prefixSecrets2 findings
SEC005Hardcoded password, token or signing secretSecrets5 findings
SUPA001Table created without Row Level SecuritySupabase2 findings
SUPA002Row Level Security policy allows everyone (USING true)Supabase1 finding
SUPA003Supabase service-role key used in frontend codeSupabase1 finding
AUTH001Next.js API route with no authentication checkAccess control3 findings
AUTH002Express route with no authentication middlewareAccess control2 findings
AUTH003FastAPI/Flask endpoint with no authentication checkAccess control4 findings
CFG001Permissive CORS configurationConfiguration3 findings
CFG002Debug mode enabledConfiguration2 findings
CFG003No rate limiting on login or LLM endpointConfiguration3 findings
INJ001SQL query built by string concatenationInjection3 findings
INJ002Dynamic code execution (eval / exec)Injection1 finding
INJ003Raw HTML rendering (dangerouslySetInnerHTML / innerHTML)Injection1 finding
INJ004Shell command built from variable inputInjection3 findings
LLM001User input placed directly into the system promptLLM and agents3 findings
LLM002LLM output passed to a dangerous sinkLLM and agents3 findings
LLM003LLM call without an output-length or cost limitLLM and agents2 findings
LLM004Agent tool with unrestricted shell, file or network accessLLM and agents4 findings
MCP001Plaintext credential in MCP / agent configurationLLM and agents3 findings
MCP002Over-broad MCP server or agent permissionLLM and agents5 findings
DEP001No dependency lockfileDependencies1 finding
DEP002Unpinned dependency versionsDependencies2 findings
DEP003Package name on the known-bad / typosquat sample listDependencies2 findings

Appendix: dismissed after review

FindingLocationReviewer note
Secret detected by external scanner
EXT-SECRET-7181c2cd
.env:3Supabase anon key: public by design, it ships in the browser bundle. Not a secret.