<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Mario</title>
    <description>The latest articles on DEV Community by Mario (@getceron).</description>
    <link>https://dev.to/getceron</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4117107%2F830b1fab-1c3c-4002-b4f5-20a34f4f2b16.png</url>
      <title>DEV Community: Mario</title>
      <link>https://dev.to/getceron</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/getceron"/>
    <language>en</language>
    <item>
      <title>How much does a security audit actually cost? A transparent breakdown</title>
      <dc:creator>Mario</dc:creator>
      <pubDate>Wed, 09 Sep 2026 08:56:35 +0000</pubDate>
      <link>https://dev.to/getceron/how-much-does-a-security-audit-actually-cost-a-transparent-breakdown-2g1j</link>
      <guid>https://dev.to/getceron/how-much-does-a-security-audit-actually-cost-a-transparent-breakdown-2g1j</guid>
      <description>&lt;p&gt;Mario Luck, founder of Ceron. I charge from $1,500 for a fixed-fee security audit, which makes me something of an outlier, so let me show you the entire pricing landscape: what things really cost, why, and how to tell when a number is honest. Every figure below is defensible. If a vendor quotes you outside these ranges, this article tells you what question to ask.&lt;/p&gt;

&lt;p&gt;The short answer&lt;br&gt;
If you just want numbers, here they are. The ranges below are what I have seen across the market for a typical small to mid-size SaaS company: one web app, one or two cloud accounts, under 50 employees.&lt;/p&gt;

&lt;p&gt;Automated scanner subscriptions run $50 to $500 per month. You get headers, known CVEs, and common injection patterns, checked continuously. You do not get business logic testing, because a subscription scanner cannot understand what your product does.&lt;/p&gt;

&lt;p&gt;AI-driven autonomous scanning platforms run $1,000 to $10,000 or more per year. Coverage is still scanner-class, but the tooling and triage are better. Prices vary wildly because the category is young and vendor maturity varies even more.&lt;/p&gt;

&lt;p&gt;Compliance-focused pentests, the kind your SOC 2 or ISO 27001 auditor requires, run $8,000 to $25,000. What you are buying is a report that satisfies your auditor. Testing depth varies inversely with price pressure, and at the low end it can be thin.&lt;/p&gt;

&lt;p&gt;A traditional external penetration test from a consultancy runs $10,000 to $30,000. Manual testing, days to weeks of work, one point in time. Add internal or authenticated scope and you are looking at $15,000 to $60,000 or more, because each access level multiplies the testable surface.&lt;/p&gt;

&lt;p&gt;A fixed-fee audit from a solo practitioner runs $1,500 to $8,000. One operator, defined scope, verified findings. This is the category I compete in, and I will come back to why it exists.&lt;/p&gt;

&lt;p&gt;A red team engagement runs $25,000 to $100,000 or more. Objective-based adversary simulation. This is not what a 20 person startup needs, and I will say why later.&lt;/p&gt;

&lt;p&gt;Two caveats before you use any of these numbers. First, geography and brand move prices: a Big Four consultancy will quote 3x to 10x a boutique for comparable technical work, and sometimes that buys you a logo your enterprise customer's procurement team recognizes, which is a legitimate reason to pay it. Second, everything above is point-in-time testing except the scanner subscriptions. If your product changes monthly, a $20,000 annual pentest is testing a product that no longer exists for eleven months of the year.&lt;/p&gt;

&lt;p&gt;Where the money actually goes&lt;br&gt;
Prices in security testing are not arbitrary. They decompose into four cost centers, and knowing them lets you audit any quote.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Sales and overhead (20 to 40 percent at consultancies). A firm with account executives, proposal writers, and a marketing budget loads all of it into your engagement. This is the biggest single reason identical technical work costs $8,000 from a solo practitioner and $25,000 from a consultancy. You are not paying for better testing. You are paying for the machine that sold it to you. Sometimes worth it (enterprise procurement, compliance optics), often not.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Testing hours (the real product). Manual security testing by someone competent runs $100 to $250 per hour in Western markets. A serious external pentest of one web app is roughly 40 to 80 hours of focused work: reconnaissance, mapping, auth testing, business logic probing, exploitation attempts, verification, false-positive elimination. Do the arithmetic and the $10,000 to $20,000 range explains itself. Quotes dramatically below that range mean fewer hours, cheaper hours, or automated output with a human cover sheet.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verification and reporting (where cheap engagements quietly fail). Finding something suspicious is 20 percent of the work. Proving it is exploitable, ruling out false positives, writing evidence an engineer can act on, and ordering fixes by real business impact is the other 80 percent. This is the labor that gets cut first under price pressure, and it is the labor you are actually buying. A report full of unverified "potential issues" transfers the cost back to your engineering team, which is more expensive than the audit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tooling and infrastructure (small but real). Proxies, scanners, cloud accounts for testing, lab environments. A few hundred dollars per engagement. Anyone itemizing this heavily is padding.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why the same audit costs $1,500 from me and $15,000 from a firm&lt;br&gt;
Honest answer, since this article is useless without it. Three structural differences:&lt;/p&gt;

&lt;p&gt;AI methodology. I run frontier AI models with custom harnesses against your attack surface to investigate configuration, permissions, and exposed services, and I verify every candidate finding manually before it enters a report. The models do breadth; I do judgment. This compresses reconnaissance and hypothesis generation, which is a large slice of traditional testing hours. It does not replace the practitioner. An AI that reports a "critical vulnerability" without evidence has done nothing; verification is the job, and verification is human.&lt;br&gt;
Fixed fee, &lt;strong&gt;no findings no fee&lt;/strong&gt;. I quote one price before testing, and you pay &lt;strong&gt;only&lt;/strong&gt; if I find a verified, actionable vulnerability in scope. This transfers risk from you to me, which means I only take engagements where I genuinely expect to find something, and I test accordingly. A firm billing hourly cannot offer this without abandoning its business model.&lt;br&gt;
The catch, stated plainly: a solo practitioner is one person. If you need a team on site for two weeks, a red team, or a brand your Fortune 500 customer's procurement department recognizes, you need a firm, and you should pay firm prices. The $1,500 to $8,000 band serves startups and SMBs whose alternative is often no testing at all.&lt;/p&gt;

&lt;p&gt;What drives your specific number&lt;br&gt;
Any vendor quoting before understanding these four variables is quoting a template:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Assets. How many applications, APIs, domains, and services? One marketing site plus one app plus one API is a very different engagement from twelve microservices. Roughly, each additional authenticated application adds 30 to 100 percent to manual testing effort, depending on complexity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Environments. One cloud account or four? A single AWS account with clean IAM is a day of configuration review. Multi-account, multi-cloud, Kubernetes in the mix: a week or more.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Access depth. Unauthenticated external testing is the cheapest and finds the least. Authenticated testing with two or more test accounts at different privilege levels is where the serious findings live (broken access control, the most exploited API vulnerability class, is invisible without it). Internal network testing costs more again. Each access level you authorize multiplies the testable surface.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Complexity and integrations. SSO, multi-tenancy, payment flows, third-party integrations, legacy components. A multi-tenant SaaS with Stripe billing and OAuth has more trust boundaries than a brochure site, and trust boundaries are where vulnerabilities concentrate.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A useful sanity rule: price tracks your attack surface, not your headcount. A 5 person team running 30 microservices needs more testing than a 200 person company with one monolith. Any quote that scales with employee count is pricing you as a logo, not as an environment.&lt;/p&gt;

&lt;p&gt;How to read a quote: five questions&lt;br&gt;
How many testing hours does this include? If they cannot or will not say, the number is small.&lt;br&gt;
What is excluded? A scope section without an exclusions section is a scope section in hiding.&lt;br&gt;
Is this manual testing or automated scanning with a report wrapper? The tell: ask what happened the last time their tool flagged a false positive. Manual testers have stories. Wrapper vendors get defensive.&lt;br&gt;
Does the report include evidence and prioritized remediation, or a findings list? "Here are 34 issues, CVSS scored" without reproduction steps and fix guidance dumps the expensive work on your team.&lt;br&gt;
What happens if nothing is found? This question separates the confident from the rest. If the answer is "that has never happened," they are either extraordinary or inflating findings. If the answer is "you still pay," that is a legitimate business model, but know that you are buying effort, not outcomes.&lt;br&gt;
When each option is the right call&lt;br&gt;
A scanner subscription makes sense when you ship continuously and want cheap regression coverage on headers and known CVEs between real assessments. It is a smoke alarm, not a building inspection.&lt;/p&gt;

&lt;p&gt;A compliance pentest makes sense when your SOC 2 auditor requires one. Buy it to spec, but understand you are buying a certificate. If your budget allows only one real assessment a year, make it count, and question five above applies hardest here.&lt;/p&gt;

&lt;p&gt;A traditional pentest firm makes sense when you are enterprise-adjacent, procurement demands brand names, or you need a team for large scope. Real value, real prices.&lt;/p&gt;

&lt;p&gt;A fixed-fee practitioner makes sense when you are a startup or SMB that needs one honest, verified assessment of a defined attack surface without five figures of spend, and you want the vendor's incentives aligned with finding truth rather than billing hours.&lt;/p&gt;

&lt;p&gt;A red team makes sense when you have mature security, a detection function worth testing, and a budget to match. Almost nobody under 100 employees should be there yet.&lt;/p&gt;

&lt;p&gt;The number that matters most&lt;br&gt;
Whatever you spend, the audit is only as good as the fixes it produces. A $1,500 assessment whose High finding gets patched next sprint beats a $25,000 report that lives unread in a shared drive. Before you buy anything, ask yourself who on your team will own remediation, and pick a vendor whose report format that person can actually execute against: evidence, impact, and fix in priority order.&lt;/p&gt;

&lt;p&gt;That is the whole market in one paragraph. The numbers above are yours to use; if a quote you receive contradicts them, you now know which question to ask.&lt;/p&gt;

&lt;p&gt;I'm Mario. Ceron runs AI-assisted security audits of web apps, APIs, and cloud infrastructure: one-time engagements, fixed fee from $1,500, no findings no fee. Full pricing and terms are public at getceron.com, because after writing this article it would be embarrassing if they weren't.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>webdev</category>
      <category>testing</category>
    </item>
    <item>
      <title>Password reset doesn't log the attacker out. Test yours in 10 minutes.</title>
      <dc:creator>Mario</dc:creator>
      <pubDate>Wed, 09 Sep 2026 08:41:18 +0000</pubDate>
      <link>https://dev.to/getceron/password-reset-doesnt-log-the-attacker-out-test-yours-in-10-minutes-4b7</link>
      <guid>https://dev.to/getceron/password-reset-doesnt-log-the-attacker-out-test-yours-in-10-minutes-4b7</guid>
      <description>&lt;p&gt;Mario—I run AI-assisted security audits at Ceron. This is a workflow bug I keep finding in real SaaS products, so here's the full test recipe, what the major frameworks do by default, and the fix.&lt;/p&gt;

&lt;p&gt;A user suspects their account is compromised. Maybe they got a login alert from a city they've never been to. Maybe their laptop ran infostealer malware — those tools lift session cookies straight out of the browser's cookie database, no password needed. So they do the obvious thing: hit Forgot password, set a new one, and breathe a sigh of relief.&lt;/p&gt;

&lt;p&gt;The attacker doesn't notice a thing.&lt;/p&gt;

&lt;p&gt;Their stolen session cookie is still valid. The password reset authenticated the victim, issued the victim a new session — and left every other existing session untouched. The attacker keeps reading emails, exporting invoices, and changing settings with a session that a password reset was supposed to kill.&lt;/p&gt;

&lt;p&gt;This is CWE-613 (Insufficient Session Expiration) in its most consequential form. OWASP's session-management guidance is explicit that sessions should be invalidated when a password is changed or reset — and yet a large share of production apps don't do it, because whether they're vulnerable depends almost entirely on framework defaults and which session strategy was picked at the start of the project.&lt;/p&gt;

&lt;p&gt;Below is a 10-minute test you can run against your own app (or any app you're authorized to test — which, for your own product, you are).&lt;/p&gt;

&lt;h2&gt;
  
  
  The test
&lt;/h2&gt;

&lt;p&gt;You need two things: a test account on the app, and two separate browser sessions. The cleanest setup is one normal browser window (the "attacker") and one private/incognito window (the "victim"). You'll also want curl or any HTTP client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Attacker logs in and grabs the session.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Log into the test account in the attacker window. Then extract the session credential:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cookie-based apps: DevTools → Application → Cookies → copy the session cookie's name and value (this works even for HttpOnly cookies, which document.cookie can't see).&lt;/li&gt;
&lt;li&gt;JWT/token apps (common with NextAuth, SPAs, mobile backends): DevTools → Application → Local Storage (or Session Storage) → copy the access/refresh token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify the stolen credential works from outside the browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Cookie-based:&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://app.example.com/api/me &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Cookie: session=&amp;lt;PASTE_STOLEN_VALUE&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Token-based:&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://app.example.com/api/me &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &amp;lt;PASTE_TOKEN&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pick an endpoint that requires authentication and returns account data (/api/me, /api/account, /api/invoices). You should get a 200 with your user's data. That's your baseline: the attacker is in.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — Victim resets the password.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In the victim window, go through the real Forgot password flow: request the reset email, click the link, set a new password. Don't shortcut it by changing the password in account settings yet — the reset flow is the one that matters most, because it's the flow a compromised user is told to use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Attacker replays the stolen credential.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; https://app.example.com/api/me &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Cookie: session=&amp;lt;SAME_STOLEN_VALUE&amp;gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4 — Read the result.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Result&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;401 / 403 / redirect to login&lt;/td&gt;
&lt;td&gt;Sessions were invalidated on reset. You're clean.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;200 with account data&lt;/td&gt;
&lt;td&gt;The old session survived a password reset. Anyone holding that cookie is still logged in as you.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you got a 200, run two follow-ups before you fix anything, because the answers change the fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repeat the test using the authenticated Change password form in account settings instead of the email reset flow. Apps frequently handle one and not the other.&lt;/li&gt;
&lt;li&gt;Check the session's remaining lifetime. A 15-minute JWT that survived a reset is a nuisance; a 30-day cookie that survived is an open door.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the frameworks actually do
&lt;/h2&gt;

&lt;p&gt;This bug is rarely hand-written. It's usually inherited from a default. Here's what I see in the wild, by stack:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;Default behavior on password change/reset&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Django (database sessions)&lt;/td&gt;
&lt;td&gt;✅ Invalidates all sessions — the session auth hash no longer matches. Django even kills your current session, which is why update_session_auth_hash() exists. The rare framework that fails safe.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rails / Devise&lt;/td&gt;
&lt;td&gt;❌ The default cookie store is client-side and encrypted — there is nothing server-side to invalidate. Old cookies keep working until they expire.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Laravel&lt;/td&gt;
&lt;td&gt;❌ Doesn't invalidate on password change. Auth::logoutOtherDevices() exists but you have to wire it in yourself.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NextAuth (Auth.js)&lt;/td&gt;
&lt;td&gt;❌ Default JWT sessions live up to 30 days (maxAge) and are stateless — the server can't revoke what it doesn't store. Database-session mode can delete rows, but the reset flow doesn't do it for you.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Express + express-session&lt;/td&gt;
&lt;td&gt;⚠️ Sessions live in your store (Redis, Postgres, …), so they can be destroyed — but nothing does it automatically. Most apps never write the DELETE.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Supabase Auth&lt;/td&gt;
&lt;td&gt;✅ Mostly — updateUser() with a new password revokes other sessions by default (scope: 'global'). Verify nobody flipped the scope, and check what your RLS policies do with still-valid access tokens during their TTL.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two patterns jump out. First, client-side session strategies (signed cookies, JWTs) structurally cannot be revoked — if your sessions live in the browser, "invalidate on reset" requires bolting on server-side state you deliberately avoided. Second, the frameworks that get it right (Django) are the ones that made sessions a server-side, first-class object.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;If your sessions are server-side (database, Redis, cache): on successful password change and password reset, delete every session row for that user except the current one. In SQL terms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;sessions&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;-- $2 = current session, or drop the clause on the reset flow&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Put this inside the same transaction as the password update. On the reset flow specifically, delete all sessions including the current one — the person completing an email reset should get a fresh session, and there's no reason to trust the browser that's already there.&lt;/p&gt;

&lt;p&gt;If your sessions are stateless (JWTs, signed cookies), you need to reintroduce a tiny bit of state: a per-user session_version counter.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add session_version INT DEFAULT 0 to the users table.&lt;/li&gt;
&lt;li&gt;Embed the value in every issued token/cookie.&lt;/li&gt;
&lt;li&gt;On each authenticated request, compare the token's version to the database's. Mismatch → reject.&lt;/li&gt;
&lt;li&gt;On password change or reset: UPDATE users SET session_version = session_version + 1 WHERE id = $1.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One integer column, one comparison per request, and every credential issued before the reset becomes worthless — including the one in your account-settings page, your mobile app, and the attacker's cookie jar.&lt;/p&gt;

&lt;p&gt;While you're in there, two things the test usually surfaces as bonus findings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Remember me" tokens are a second session system with their own storage. They're revoked on password reset in a minority of the apps I test.&lt;/li&gt;
&lt;li&gt;API keys / personal access tokens issued to the user should have a documented policy on password reset. Killing them silently can break integrations; never killing them means the reset didn't actually reset access. Either is defensible — undecided is not.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Make it a regression test
&lt;/h2&gt;

&lt;p&gt;The fix decays the moment someone refactors auth. Pin it with a test that runs the exact attack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_password_reset_kills_other_sessions&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;attacker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;login_as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# session A
&lt;/span&gt;    &lt;span class="n"&gt;victim&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;login_as&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TEST_USER&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# session B
&lt;/span&gt;    &lt;span class="nf"&gt;complete_password_reset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;victim&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NEW_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# via the real reset flow
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;attacker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/me&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you can't script the email-reset flow in CI, at least test the authenticated change-password path automatically, and the reset flow manually per release. It's four clicks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why your scanner never caught this
&lt;/h2&gt;

&lt;p&gt;Automated scanners — the free dashboard kind, the $99/month kind, most of them — check for signatures: missing headers, known CVEs, SQL injection patterns. This bug has no signature. It's a stateful, multi-step, cross-session workflow property: log in as A, log in as B, drive B through an out-of-band email flow, then re-probe A. That's business-logic territory, and it's exactly where the interesting bugs live. It's also why this finding is a regular guest in my reports: not because it's exotic, but because nothing in a typical stack is designed to look for it.&lt;/p&gt;

&lt;p&gt;Run the test. It's ten minutes, and the answer is binary.&lt;/p&gt;




&lt;p&gt;I'm Mario. At Ceron I run AI-assisted security audits of web apps, APIs, and cloud infrastructure. One-time, fixed fee, no findings no fee. If you'd rather not find out what else your reset flow doesn't do, that's what I'm for.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
