Files
arah2/.planning/phases/02-bot-skeleton-initdata-security/02-VERIFICATION.md
2026-10-23 22:37:55 +08:00

12 KiB
Raw Permalink Blame History

phase, verified, status, score, overrides_applied, re_verification
phase verified status score overrides_applied re_verification
02-bot-skeleton-initdata-security 2026-10-22T21:05:00Z passed 4/4 must-haves verified 0
previous_status previous_score gaps_closed gaps_remaining regressions
none n/a

Phase 2: Bot Skeleton + initData Security Verification Report

Phase Goal: Users can reach araharah through Telegram with a working command surface, and the backend's identity + privacy architecture is provably sound from the first deploy — the initData validator is the first backend artifact. Verified: 2026-10-22T21:05Z Status: passed Re-verification: No — initial verification (codebase + live deployment)

Goal Achievement

Observable Truths

# Truth (ROADMAP SC) Status Evidence
1 SC1/ONBD-01: /start gives a concise explanation plus an always-available command menu (/help, /privacy) that answers correctly ✓ VERIFIED Handlers bot.command("start"/"mula", …) reply strings.startIntro (one clinical sentence, text-only, no keyboard — CS4 grep gate green); live Telegram read-backs: getMyCommands (default) and getMyCommands?language_code=ms both return exactly help + privacy, getChatMenuButton → commands; user real-client walkthrough a–e all green and APPROVED (02-03-APPROVAL.md)
2 SC2/PRIV-01: /privacy gives an honest visible statement of what araharah stores / never stores ✓ VERIFIED privacy.ts builds privacyStored + privacyNotStored from strings.ts; copy names exactly user id, chat id, language, ~15-min TTL — matching sessions.ts SessionClaim {user_id, chat_id, issued_at, language_code} and SESSION_TTL_SECONDS=900; no 5th claim field in code; user observed block as written (approval check c)
3 SC3: tampered, stale, and replayed initData rejected with CI test vectors; known-good HMAC passes; only validated identities get a one-time token ✓ VERIFIED npm test -w bot → 34 passed, 0 failed (T1–T7 security vectors incl. tamper/stale/899/901 boundary/order/malformed + RFC-4231 pin; WK1–WK5; sessions SM1/SM2/SR1–SR3; privacy PG1/PG2). Mint path is only after ok:true in webhook.ts (single mintSession call). Replay closes via D-07 rotate-on-new (CONTEXT decision; re-mint cannot yield an extra token — accepted locked decision, WR-03 invariant documented honestly)
4 SC4/PRIV-02: storage spot-check shows TTL-only KV, zero persistent coordinates or profiles ✓ VERIFIED Live wrangler kv key list --namespace-id 59f58a54… → [] (empty = TTL-expired privacy-by-construction); every kv.put in sessions.ts carries expirationTtl: SESSION_TTL_SECONDS on the call line (PG1 grep test green); wrangler.jsonc has exactly ONE kv binding (chat_sessions), no D1/R2/DO anywhere; claim key-set equals D-08 enforced by SM1

Score: 4/4 truths verified

Post-Review Hardening (WR-01/02/03) — Live Verification

Check Status Evidence
Deployed version includes all 3 fix commits ✓ Commits 8995498/3ea1fdd/af691ae at 12:17–12:21 UTC; HEAD = af691ae; latest active deployment b6ba6e58-37dd-4aa1… created 12:22:03 UTC (after final commit — wrangler deployments list)
WR-01 live (4 KB body cap) ✓ POST /api/session with 5 KB body → 413; tiny garbage → 401 {"error":"malformed"}
WR-02 fail-closed secret guard ✓ Code verified at webhook.ts:50-53 (constant 500 before grammY accept-all path); WK5 test green; not live-testable read-only (would require rotating secrets — timing evidence + 413 smoke confirm post-fix bundle deployed)
WR-03 honest invariant ✓ sessions.ts header documents best-effort rotate + reconciliation sweep; SR3 asserts the honest KV-limits outcome; IN-01b dead branch removed

Deferred Items (Step 9b filter of review findings)

# Item Addressed In Evidence
1 Stale wrangler.jsonc comment (line 2: "filled in plan 03") contradicting the live id on line 9 (REVIEW IN-02, INFO) Phase 7 02-REVIEW-FIX.md: "Out of scope (Phase 7 audit): IN-01(a), IN-02, IN-03, IN-04, IN-05" — doc-comment hygiene, zero functional impact

Required Artifacts

Artifact Expected Status Details
bot/src/security/initdata.ts validateInitData + rejection classes, no grammy dep ✓ VERIFIED 215 lines; SubtleCrypto HMAC, constant-time hex compare, "WebAppData" salt; consumed by webhook.ts
bot/src/kv/sessions.ts TTL-only KV layer, rotate-on-new ✓ VERIFIED 122 lines; 2 puts, both with expirationTtl; read helpers noted as Phase 4 API (IN-01c)
bot/src/webhook.ts Worker entry: webhook route + /api/session gate ✓ VERIFIED Two routes only + 404; WR-01 cap; WR-02 fail-closed; D-14 category-only logs (4 console lines, all category-only)
bot/src/i18n/strings.ts + bot/src/bot/{bot.ts,commands/*} Copy source of truth, en+ms, text-only handlers ✓ VERIFIED WIRED: commands import strings; bot.ts registers 5 commands; live menu read-back matches helpMenu
bot/src/config.ts Env contract ✓ VERIFIED BOT_TOKEN/WEBHOOK_SECRET/chat_sessions; REQUIRED comment added in WR-02 fix
bot/test/{harness,runner}.mjs Zero-dep D-12 harness ✓ VERIFIED test: "node test/runner.mjs" — ran clean, 34/0, devDependencies carry zero test libs
bot/test/{initdata,sessions,privacy,commands,webhook}.test.mjs Vector suites ✓ VERIFIED 34 cases green incl. T1–T7, WK1–WK5 (webhook suite added by review fix 8995498)
bot/scripts/{register-commands,set-webhook}.mjs Menu registration + setWebhook w/ secret_token ✓ VERIFIED Descriptions derived from helpMenu (no drift); secrets read from .dev.vars, never echoed
bot/wrangler.jsonc main src/webhook.ts + chat_sessions binding (live id) ✓ VERIFIED id 59f58a54… live (matches APPROVAL record + deployment)
.dev.vars hygiene Gitignored, untracked ✓ VERIFIED git check-ignore ok; git ls-files shows only .dev.vars.example
From To Via Status Details
webhook.ts initdata.ts validateInitData call → 401/413 on rejection, mint on ok ✓ WIRED Lines 99–113; live 401 smoke confirms
webhook.ts sessions.ts mintSession only after ok:true ✓ WIRED Single mint path; WK1 asserts exactly two 900s KV writes
webhook.ts bot.ts / grammy webhookCallback(bot,"cloudflare-mod",undefined,undefined,env.WEBHOOK_SECRET) ✓ WIRED Live unsigned/wrong-secret webhook → 401 both
commands/*.ts strings.ts copy imported, no inline copy ✓ WIRED start/help/privacy all import strings; reply builders pure
register-commands.mjs strings.ts menu descriptions derived from helpMenu ✓ WIRED Live getMyCommands text matches strings exactly
sessions.ts KVNamespace.put every put carries expirationTtl ✓ WIRED PG1/PG2/SM2 green; live KV list empty post-TTL

Data-Flow Trace (Level 4)

Artifact Data Variable Source Produces Real Data Status
webhook.ts handleSession initData string HTTP request body Yes — validated against real bot-token HMAC ✓ FLOWING
privacy command locale/strings ctx.from.language_code → pickLocale Yes (live user confirmed) ✓ FLOWING

Behavioral Spot-Checks

Behavior Command Result Status
Full test suite npm test -w bot [runner] 34 passed, 0 failed, exit 0 ✓ PASS
Typecheck npm run bot:typecheck exit 0 ✓ PASS
Webhook auth (unsigned / wrong header) live POST /telegram/webhook 401 / 401 ✓ PASS
/api/session garbage live POST 401 {"error":"malformed"} ✓ PASS
/api/session 5 KB body (WR-01) live POST 413 ✓ PASS
GET /api/session live 405 ✓ PASS
Unknown route live 404 ✓ PASS
Command menu (default + ms) live getMyCommands ×2 exactly /help + /privacy in both languages ✓ PASS
Menu button live getChatMenuButton type commands ✓ PASS
Webhook armed live getWebhookInfo url /telegram/webhook, pending_update_count 0, allowed_updates:["message"] ✓ PASS
KV TTL-only spot-check live wrangler kv key list [] (empty = all sessions expired ≤900s) ✓ PASS
Deployed = HEAD (post-fix bundle) wrangler deployments list + git log b6ba6e58 (12:22:03Z) created after af691ae (12:21:11Z); current deployments list shows b6ba6e58 active at 100% ✓ PASS

Requirements Coverage

Requirement Source Plan Description Status Evidence
ONBD-01 02-01, 02-03 Open bot, /start gives concise explanation + always-available command menu ✓ SATISFIED Handlers + strings verified; live menu lists exactly /help /privacy both scopes; user-approved walkthrough (SC1)
PRIV-01 02-01, 02-03 Privacy promise visible in-product: /privacy command ✓ SATISFIED (phase half) /privacy + /privasi live-observed; noted: "trip ended, data deleted" messaging half completes in Phase 6 per REQUIREMENTS.md:127
PRIV-02 02-01, 02-02 No persistent per-user history/profile — TTL-only ephemeral state ✓ SATISFIED Live KV []; TTL-only on all write paths (PG1/PG2/SM tests); one binding in wrangler.jsonc; no other storage code in bot/src

All phase requirement IDs (ONBD-01, PRIV-01, PRIV-02) appear in plan frontmatter and are mapped to Phase 2 in REQUIREMENTS.md:97-99 — no orphaned requirements. Roadmap Phase 2 defines exactly 4 success criteria (device-matrix criterion belongs to Phase 3's SC5) — all 4 covered above.

Anti-Patterns Found

File Line Pattern Severity Impact
bot/wrangler.jsonc 2 Stale "KV_ID_PLACEHOLDER" comment vs live id on line 9 ℹ️ Info Doc-only, deferred to Phase 7 (REVIEW IN-02)
bot/src/security/initdata.ts 53-60 ParsedInitData exported but unused ℹ️ Info Type-only export, deliberate; documented in REVIEW IN-01(a)
— (everything else) — TODO/FIXME/placeholder/console-echo of secrets/empty implementations: full grep of src/, scripts/, test/ returned zero — —

The getWebhookInfo last_error timestamp (11:18:20 UTC) predates the final BOT_TOKEN secret re-upload (11:18:25 UTC deployment from the walkthrough remediation) and the user's subsequent live /start approval; pending_update_count is 0. Known resolved history, not a finding.

Human Verification Required

None outstanding. The human-only gates of this phase were already resolved and are recorded as evidence:

  • Real-client walkthrough a–e (D-16 blocking gate): user performed 2026-10-22 and said "it works! Menu or / has /help and /privacy! approved!" — 02-03-APPROVAL.md (cross-checked live via Telegram API read-backs performed during this verification).
  • Secret provisioning (BotFather token, wrangler secrets): resolved during deploy chain; runtime behavior live-verified by smokes.

Gaps Summary

No gaps. All four roadmap success criteria verified against code, tests, and the live deployment; the code-review's 3 warnings are fixed and the fix bundle is what is live (timing + WR-01 live smoke). The one un-replicable human gate (real Telegram client) is user-approved with recorded evidence.


Verified: 2026-10-22T21:05Z Verifier: OpenCode (gsd-verifier) Verification basis: local code at af691ae + live deployment b6ba6e58; all Cloudflare/Telegram state touched read-only.