serenakeyitan/tdoc
81/100
🤖 一把梭的 Prompt 侠
serenakeyitan/tdoc preview

🔥 Roast

你不是程序员。 你是 AI 的复制粘贴键,还带 commit 权限。

说句实话 —— 这代码不是你写的,你往聊天框敲了句“给我搞个安全登录”,AI 吐什么你就上什么。典型 slop:随手一个 timing-safe 比对就让你飘了,压根没发现 auth 大门洞开。等着某个小孩拿脚本刷爆你接口、截图挂论坛,配文“又一个没听过限流的创业公司”,底下 900 个赞,你的名字挂在帖子里。

Critical: 0High: 2Medium: 5
扫描来自 🌳 First Tree · first-tree.ai · 2026-06-30

哦,真棒。 timing-safe 的 token 比对、CI 里的 CodeQL、23 对 try/catch、一整套离线测试 —— 看得出你很会上锁。所以你把这么漂亮一把锁,装在一栋卸了两扇侧门的房子上,就更显本事了。

分数拆解

六项分数好看,两项把总分往下拽 —— 你就是那个忘穿裤子来毕业典礼的优等生。

ErrTestSecAuthObsDataDepPerf
Input & Data Safety 48/100
The door’s open, but you swept the floor first
Deploy Config 48/100
Shipped to prod with no headers, very brave
Secrets & Credentials 88/100
Clean today — a git add . from the news
Authentication & Access 88/100
Timing-safe now! The 4 routes you recall
Observability 88/100
You’ll hear it broke. Eventually. From a user
Performance 88/100
Edge runtime carries you — no obvious N+1 or blocking hot path
Error Handling 100/100
23 try/catch pairs — genuinely, show-off
Tests & CI 100/100
CodeQL and an offline suite. Teacher’s pet
上线前必修

两件事挡在你和上线之间。下面我不开玩笑了,直接告诉你怎么补。

HighCongrats — you built a free brute-force playground
That’s not an auth endpoint — it’s an open bar: free, unlimited, 24/7, serving every stranger who fancies grinding your GitHub Device Flow quota to dust with one while(true).
Evidence
worker/worker.js:1631 — POST /api/auth/device/start
worker/worker.js:1650 — POST /api/auth/device/poll
proxy GitHub Device Flow with no rate limiter wired
Root causeUnthrottled auth endpoints let an attacker brute-force device codes or burn your Device Flow quota; any public auth surface needs a per-IP + global cap.
The fix
const ip = request.headers.get("CF-Connecting-IP");
const n = Number(await env.RL.get(`rl:device:${ip}`)) || 0;
if (n > 10) return new Response("rate limited", { status: 429 });
await env.RL.put(`rl:device:${ip}`, String(n + 1), { expirationTtl: 60 });
Verify
for i in $(seq 1 15); do curl -s -o /dev/null -w "%{http_code}\n" \
  https://<worker>/api/auth/device/start; done   # expect 429 after the cap
HighYou serve other people’s HTML on your own origin and call it a day
Your doc-serve responses set Content-Type and stop there — no CSP, no X-Frame-Options, no nosniff. You built a beautiful printing press and skipped the part where the ink doesn’t set the building on fire.
Evidence
worker/worker.js:1616 — doc-serve Response sets Content-Type only;
no CSP / X-Frame-Options / X-Content-Type-Options / Referrer-Policy / HSTS
Root causeAuthor HTML is served from the worker’s own origin with no framing or MIME-sniff protection — a real clickjacking / sniff risk for an HTML publisher.
The fix
const headers = {
  ’Content-Type’: ’text/html; charset=utf-8’,
+   ’X-Content-Type-Options’: ’nosniff’,
+   ’Content-Security-Policy’: "frame-ancestors ’self’",
+   ’Strict-Transport-Security’: ’max-age=31536000; includeSubDomains’,
};
Verify
curl -sI https://<worker>/d/<slug>/v/1 | grep -iE \
  ’x-content-type|content-security|strict-transport’
MediumYour .gitignore is one line short of a public apology tour
Right now a stray git add . is all that stands between your secrets and a very public commit history.
Evidence
.gitignore:0 — .env / .env* not listed; a secret-bearing dotenv could be committed
Root causeAn untracked .env is one `git add .` from leaking every key in it — once it hits history, rotating is the only fix.
The fix
# append to .gitignore
.env
.env.*
!.env.example
Verify
git check-ignore .env   # should print .env
MediumAuthor HTML served verbatim — your diary on your own kitchen table
It’s “fine” the way leaving your diary open on the kitchen table is fine — because it’s your kitchen, for now.
Evidence
worker/worker.js:1610 — author HTML returned verbatim from R2 (reader comments/logins ARE escaped)
Root causeSelf-XSS on the author’s own tenant, not cross-user — acceptable on a single-owner worker where the author is the only writer.
The fix
if multi-author publishing is added, isolate each doc to a sandboxed per-author origin.
Verify
confirm no shared-origin multi-tenant publishing before scaling.
MediumWildcard CORS — door wide open, nothing worth stealing behind it yet
Access-Control-Allow-Origin: * is fine while nothing sensitive is on the other side — the moment something is, it’s a liability.
Evidence
worker/worker.js:19 — Access-Control-Allow-Origin: * WITHOUT Allow-Credentials
Root causePublic non-credentialed API shape; fine while nothing sensitive is served, dangerous the instant a response carries user data.
The fix
if any response becomes sensitive, replace * with an explicit allowlist — never pair * with Allow-Credentials.
Verify
curl -sI <worker>/api/... | grep -i access-control
Medium4 mutating routes guarded — great until route #5 ships without it
Every write route is guarded today; the trouble is the sixth one nobody remembers to guard.
Evidence
worker/worker.js:1774,1859,1907,1981 — requireUploadAuth guards the 4 mutating routes found
Root causeA 5th mutating route could ship without the guard and nobody notices until it’s abused.
The fix
add a default-deny test so any unguarded mutating route fails CI.
Verify
add a test asserting every POST/DELETE route calls requireUploadAuth.
MediumYour errors are confessing to an empty room
console.error plus a terminal tail means you find out things broke from users, not from alerts.
Evidence
worker/worker.js:88 — errors only via console.error + wrangler tail; no error-tracking provider
Root causeYou find out about breakage from users, not alerts — uncaught errors vanish the moment you stop tailing.
The fix
wire a Workers error tracker (Sentry / Logpush) so uncaught errors surface without tailing.
Verify
trigger a handled error and confirm it lands in the tracker.
First Tree 帮你把每个 blocker 一次修完 —— 免费。 去 First Tree 修 →