惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Application and Cybersecurity Blog
Application and Cybersecurity Blog
月光博客
月光博客
Y
Y Combinator Blog
P
Proofpoint News Feed
Forbes - Security
Forbes - Security
美团技术团队
博客园 - Franky
Attack and Defense Labs
Attack and Defense Labs
T
Tor Project blog
T
The Blog of Author Tim Ferriss
C
CERT Recently Published Vulnerability Notes
U
Unit 42
人人都是产品经理
人人都是产品经理
V2EX - 技术
V2EX - 技术
L
Lohrmann on Cybersecurity
罗磊的独立博客
博客园 - 聂微东
C
Cybersecurity and Infrastructure Security Agency CISA
N
News and Events Feed by Topic
大猫的无限游戏
大猫的无限游戏
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
H
Help Net Security
Security Archives - TechRepublic
Security Archives - TechRepublic
Microsoft Azure Blog
Microsoft Azure Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
W
WeLiveSecurity
P
Privacy International News Feed
爱范儿
爱范儿
J
Java Code Geeks
Blog — PlanetScale
Blog — PlanetScale
The Cloudflare Blog
T
Threat Research - Cisco Blogs
云风的 BLOG
云风的 BLOG
F
Full Disclosure
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Hugging Face - Blog
Hugging Face - Blog
T
Tenable Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Hacker News: Ask HN
Hacker News: Ask HN
TaoSecurity Blog
TaoSecurity Blog
B
Blog RSS Feed
Google Online Security Blog
Google Online Security Blog
D
Docker
Martin Fowler
Martin Fowler
I
Intezer
阮一峰的网络日志
阮一峰的网络日志
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Security Affairs
T
Tailwind CSS Blog
IT之家
IT之家

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
I built a SaaS in a month with Claude. Here is what actually happened.
Edwin Shibu · 2026-05-11 · via DEV Community

I built VerrixAI solo over about a month. It is a tool that takes a contract or lease, runs it through Claude, and gives you back a plain-English breakdown with risk flags. I am not a lawyer. I wanted something I could hand to my parents when they ask me what their lease actually says.

This post is the honest version. The parts I got wrong, the bugs that took days to find, the security audit I almost forgot to do, and the numbers behind the unit economics. It is long because the interesting stuff is in the details.

What the product actually does

You drop in a PDF, DOCX, or TXT file, or paste text. The document content is extracted in your browser using pdf.js and JSZip. Only the extracted text is sent to the server, capped at 60,000 characters. The original file never leaves the browser.

The server passes the text to Claude with a tight system prompt and gets back four things:

  1. A short summary of what the document is
  2. Risk flags graded HIGH, MEDIUM, or LOW with a short explanation each
  3. The obligations and clauses worth knowing about
  4. A plain-language rewrite

There is a hard cap of 8 risks per response, with a carveout that all HIGH-severity risks are always included up to a ceiling of 15. Anything dropped is counted in a separate field so the UI can say "3 lower-severity risks omitted."

The stack

Plain HTML, CSS, and JS. No framework. Six static HTML files.

Vercel for hosting, edge functions, and cron. Supabase for auth and Postgres. Anthropic Claude Sonnet 4.6 for the analysis itself. Stripe for subscriptions. Resend for transactional email. Hostinger for the domain and DNS.

Seven API endpoints under /api/: analyse, create-checkout, change-plan, cancel-subscription, stripe-webhook, delete-account, and a "want to know more" form handler.

I made the no-framework choice deliberately. Frameworks are great when there is a team. For a solo build where I needed to ship a thing rather than learn a thing, plain HTML let me move faster and debug in DevTools
without source maps.

The cost math

This is the part that matters most for the unit economics.

A typical scan sends about 470 tokens of system prompt plus up to about 15,000 input tokens from the user document, and gets back up to 4,000 output tokens. At Sonnet 4.6 pricing that works out to roughly A$0.07 per scan in worst case, less for shorter documents.

Plans are:

  • Free: 3 scans total
  • Starter: A$5/month for 50 scans/month
  • Pro: A$9/month for 100 scans/month
  • Pro 2: A$15/month for 250 scans/month

Margin is comfortable on every paid tier even at 100 percent quota usage. The Free tier is a real cost (about A$0.21 per free user worst case) and I accepted that because it is the only way I would have tried the product myself.

I considered scan top-ups for heavy Pro 2 users approaching the margin cliff but parked it as a post-launch feature. Pre-launch I had no data on whether anyone would actually hit the quota.

Vibe coding with Claude

I built most of this by talking to Claude. I gave it the spec, asked for components, pushed back when the code was wrong, and shipped what worked. I have a network admin background so I can read code and break it intelligently, but I am not a frontend developer.

What that actually felt like in practice was not "Claude wrote the app." It was more like having a very fast pair programmer who occasionally generates plausible nonsense, and your job is to be the person who notices.

The places where I had to be the noticing person:

Claude does not know what it does not know. Several times Claude generated code calling APIs that did not exist, or with parameter names that were almost right but off by one word. The fix is to test everything end to end before assuming it works. I caught these because I deployed to a Vercel preview branch and clicked through every flow. The bug usually surfaced within five minutes.

Claude pattern-matches to the most common solution, not the right one. When I asked for a payment integration, the first draft used sub.current_period_end. That field moved in the Stripe API version 2025-03-31 update. The new path is sub.items.data[0].current_period_end. Claude knew about both but defaulted to the old one. Every Stripe read in the app now uses a fallback chain.

Claude will happily silence errors. Several try/catch blocks in early drafts were swallowing real failures. I had to be deliberate about logging and about where failures should propagate versus where they should be caught.

The cumulative point is: AI-assisted coding works, but only if you treat the output as a first draft and not a delivery. The work is in the testing and the noticing, not in the typing.

Bugs and war stories

The navigator.locks deadlock

This one took me three sessions to fully understand. Supabase JS client uses a navigator.locks based mechanism for serialising calls in the same tab. On certain async patterns, the HTTP call would complete (network tab showed 200 OK), but the JS promise wrapper would never resolve. The await sat there forever.

I hit this six times across different code paths. The pattern that emerged:

  1. Wrap any critical Supabase JS call in a race against a timeout
  2. On timeout, read the access token from localStorage and call the REST API directly via fetch

For state transitions like sign-in and sign-out, the cleanest workaround was a full page reload after the auth event. That re-initialises the Supabase JS client from scratch and clears whatever was wedged.

The cause is a known issue with Supabase JS 2.39 and above. I am pinned to 2.38.4 because of it. Anthropic told me the same thing in a different conversation. Specifically:

npm install @supabase/supabase-js@2.38.4

Enter fullscreen mode Exit fullscreen mode

Not 2.38.x, not @latest, exactly 2.38.4. Upgrading silently breaks sessions on a subset of browsers and you will not see it in dev.

The sign-out token still being valid

Related to the deadlock work. When you call signOut, Supabase JS clears its in-memory state and the cookie, but the sb-{projectref}-auth-token key in localStorage can survive long enough for the next page load to read it and silently sign the user back in.

The fix is:

// Clear sb-* localStorage keys BEFORE redirect or reload
Object.keys(localStorage)
  .filter(k => k.startsWith('sb-'))
  .forEach(k => localStorage.removeItem(k));
window.location.href = '/';

Enter fullscreen mode Exit fullscreen mode

If you do not clear before the redirect, the next page boots up, reads the still-valid token, and the user is signed in again. This bit me twice before I worked out what was happening.

Quota drift across surfaces

The original quotas were Free 5, Pro 300, Pro 2 700. I changed them to 3, 50, 100, 250 after the cost analysis.

I did not appreciate how many surfaces reference quotas until I started looking. Stale numbers were in:

  • The Supabase confirm-signup email template
  • The launch invite HTML
  • knowmore.js (the "want to know more" form's pricing list)
  • account.html cancellation modal copy
  • account.html upgrade nudges
  • terms.html Section 5 plan listings
  • Two hardcoded 5 defaults inside account.html itself (one in HTML, one as a JS fallback)

It took three separate sweep passes to catch them all. The lesson: every customer-facing surface that mentions a quota number is a candidate for drift. When quotas change in code, you have to manually sweep all of them. There is no compiler that catches "the email template still says 5 scans."

Webhook idempotency, before I had it

Stripe retries webhook events on any non-2xx response. My first webhook handler had no idempotency check at all. The handler was idempotent in some places but not others. Specifically: on customer.subscription.updated, I was resetting scans_used to 0 to handle the monthly billing cycle reset. A retried event would re-run that handler, resetting scans to 0 again mid-month, giving the user free scans they had not paid for.

The fix is a small table:

create table public.processed_webhook_events (
  event_id text primary key,
  event_type text not null,
  processed_at timestamptz not null default now()
);

alter table public.processed_webhook_events enable row level security;
create policy "deny_all_webhook_events" on public.processed_webhook_events
  for all to public using (false);

Enter fullscreen mode Exit fullscreen mode

The webhook inserts the event_id at the start of processing. PostgREST returns 409 on the primary key conflict, which is the signal to return 200 immediately without re-running anything. Insert before processing, not after, so a crashed handler does not leave the door open for retries to do damage.

The security audit I almost forgot

A week before launch I did a full pass through the code looking for vulnerabilities. I found one launch blocker and three defensive issues.

The launch blocker. The /api/delete-account endpoint was deleting the Supabase user and the profile row, but doing nothing with the Stripe subscription. A paying customer who clicked "delete my account" would have had their account vanish from the app while continuing to be billed every month forever. They would only find out by checking their card statement, by which point it is a chargeback. That bug had been in production for weeks before launch. It would have been visible the first time a real paid customer deleted their account.

The fix was 15 lines. Fetch stripe_subscription_id from the profile before deleting it, call DELETE /v1/subscriptions/{id} with cancellation_details metadata for Stripe analytics, then proceed with the existing deletion. Cancel-immediately, no refund. The deletion confirmation modal now explicitly says "Your subscription will be cancelled immediately."

The deeper lesson: code reviews catch syntactic bugs. Architectural bugs need someone to ask "what happens when a paid user deletes their account" out loud. I did not ask that question for a month.

Constant-time HMAC compare. My Stripe webhook signature verification was using !== to compare the computed and expected HMACs. That is a timing side channel. In practice, hard to exploit over the public internet because network jitter dominates the timing difference. In principle, you use crypto.timingSafeEqual from node:crypto because it is the right answer and your future security auditor will not flag it. Three line fix.

Prompt injection boundary enforcement. My code wrapped user document content in --- DOCUMENT START --- / --- DOCUMENT END --- boundary markers, with a comment claiming this defended against prompt injection. It did, except for the case where the user's document itself contained the literal string --- DOCUMENT END --- followed by an instruction. The fix is one regex:

const safeContent = rawContent.replace(
  /\s*-{2,}\s*DOCUMENT\s+(START|END)\s*-{2,}\s*/gi,
  ' [boundary marker removed] '
);

Enter fullscreen mode Exit fullscreen mode

I tested it with 14 attack variants and 6 benign cases. All passed. As a bonus, when I tried a manual injection through the live product after deploying, the model identified the injection attempt itself and flagged it as "Possible Document Tampering or Fraud" with HIGH severity. Defence in depth working as intended.

Things I did not build, by choice

No admin panel. Every operation an admin would need can be done in Supabase Studio with SQL. Building an admin UI is a long tail of buttons, permission edge cases, and one more place for credentials to leak. I am not building it until I genuinely need it.

No real-time anything. No WebSockets, no Server-Sent Events, no subscriptions on the Supabase client. The product does not need it and every realtime feature is another deadlock candidate.

No SSR, no React, no Next.js. Plain HTML works. Adding a framework would have added two weeks of "learn the framework" plus ongoing version churn.

No mobile app. The web version is responsive. Mobile app is a post-traction decision.

What is on the post-launch list

Things I deliberately punted on:

  • Self-service subscription reactivation (currently you have to email me)
  • DMARC policy ramp from p=none to p=quarantine to p=reject
  • Prompt caching (my system prompt is below the 1,024 token Sonnet minimum so caching does nothing)
  • Replacing the frontend profile creation with a Supabase DB trigger (closes the USER_LIMIT DevTools tampering loophole)
  • Dunning handler for invoice.payment_failed

Each one is in the master doc with an explicit reason it can wait. The pattern I followed: if it is not a launch blocker, and the workaround is reasonable, document it and ship.

Numbers and reflections

Time spent: about a month of evenings and weekends. I have a day job as a network admin.

Stack cost pre-launch: under A$50 total. Vercel Pro for previews and cron. Supabase free tier. Resend free tier. Stripe takes its cut on transactions. Anthropic API usage on my own testing was about A$8.

Per-scan AI cost: ~A$0.07 worst case. Per-scan profit on Starter at 100 percent quota usage: A$1.50. On Pro 2: A$8.50.

Lines of code: about 6,600 across HTML, CSS, and JS in the main app, plus 1,500 in the seven API endpoints.

Things I would tell anyone considering a similar build:

  1. Use AI assistance, but do not trust it. Test everything end to end before you assume it works.
  2. The architecture decisions you make on day one will haunt you. Pick boring technology that you can debug at 2am.
  3. Whatever your auth provider is, expect to fight it. Mine took six instances of working around a deadlock pattern.
  4. Do the security audit before you launch, not after. There will be at least one bug you would lose sleep over if a paying customer found it first.
  5. Write down every decision and every workaround as you make it. By month two you will not remember why you pinned that exact version, and the cost of figuring it out again is high.

VerrixAI is live at verrixai.com. The free tier does not need a card. I am still polishing for the Product Hunt launch.

Feedback welcome, especially on the risk-flag output and anything that reads wrong for non-Australian contracts. I am most curious whether the plain-language rewrites are actually useful or whether they over-simplify the original document.