<?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: NET_DARK_BOI</title>
    <description>The latest articles on DEV Community by NET_DARK_BOI (@ookeolioli222).</description>
    <link>https://dev.to/ookeolioli222</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%2F4145312%2F54b385f3-9534-4920-b16c-662949f6ca95.png</url>
      <title>DEV Community: NET_DARK_BOI</title>
      <link>https://dev.to/ookeolioli222</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ookeolioli222"/>
    <language>en</language>
    <item>
      <title>Login Is Not Order Ownership: A Small Authorization Review Worksheet</title>
      <dc:creator>NET_DARK_BOI</dc:creator>
      <pubDate>Sat, 10 Oct 2026 00:30:30 +0000</pubDate>
      <link>https://dev.to/ookeolioli222/login-is-not-order-ownership-a-small-authorization-review-worksheet-4pjk</link>
      <guid>https://dev.to/ookeolioli222/login-is-not-order-ownership-a-small-authorization-review-worksheet-4pjk</guid>
      <description>&lt;p&gt;A successful login tells an application who is making a request. It does not, by itself, decide whether that person should see a particular order.&lt;/p&gt;

&lt;p&gt;That distinction is easy to agree with in a design meeting—and easy to lose when a feature is implemented. Here is a small review worksheet for an order-history feature, using fictional customers and no real customer data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a product rule
&lt;/h2&gt;

&lt;p&gt;Write one sentence before discussing routes or database queries:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A customer may read their own orders. Any support access requires a separate, explicitly approved permission.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is an illustrative rule, not a universal policy. Shared business accounts, delegated buyers and support teams may need different rules. The important part is to make those exceptions explicit instead of silently treating every signed-in account as equivalent.&lt;/p&gt;

&lt;p&gt;Authentication and authorization are separate decisions. OWASP recommends checking permissions on every request and denying access when no permission applies. Its &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html" rel="noopener noreferrer"&gt;Authorization Cheat Sheet&lt;/a&gt; is a useful reference for those principles.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make the expected decisions visible
&lt;/h2&gt;

&lt;p&gt;For the fictional feature above, a reviewer can use this table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Expected decision&lt;/th&gt;
&lt;th&gt;Evidence to retain&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Customer reads their own order&lt;/td&gt;
&lt;td&gt;Allow&lt;/td&gt;
&lt;td&gt;Only the intended order is returned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customer lacks permission for an order&lt;/td&gt;
&lt;td&gt;Deny&lt;/td&gt;
&lt;td&gt;No private order fields are returned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;No authenticated session for private history&lt;/td&gt;
&lt;td&gt;Deny&lt;/td&gt;
&lt;td&gt;No private history is returned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support account has the required permission&lt;/td&gt;
&lt;td&gt;Allow within its scope&lt;/td&gt;
&lt;td&gt;The reason for access is recorded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support permission has been removed&lt;/td&gt;
&lt;td&gt;Deny&lt;/td&gt;
&lt;td&gt;A previous permission does not keep granting access&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are proposed acceptance criteria, not results from a test run. An implementation needs its own automated checks and evidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three questions for the pull request
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Where does the identity come from?&lt;/strong&gt; The application should derive the acting user from its verified server-side authentication context. A customer-supplied owner field is input, not proof of ownership.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where is the permission decision enforced?&lt;/strong&gt; A hidden button is a user-interface choice. The service returning private data still needs to make the access decision. Review the place where the data becomes available to the caller.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when the decision cannot be made?&lt;/strong&gt; A missing permission record or policy-service error should not silently turn into permission. Define a safe failure behavior and a useful operational signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the response, not just its status
&lt;/h2&gt;

&lt;p&gt;Imagine a regression report that says “access denied” but includes the customer's shipping address in the response body. That would not meet the acceptance criteria above.&lt;/p&gt;

&lt;p&gt;For the denied cases, record whether private fields were absent. For the allowed case, record that the legitimate customer can still use order history. Both sides matter: a patch that disables the whole feature is not a successful product fix.&lt;/p&gt;

&lt;p&gt;Keep the evidence small and synthetic: the rule being checked, the expected decision, the observed decision and the application version. Avoid putting real customer records into test reports.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turn the worksheet into a team habit
&lt;/h2&gt;

&lt;p&gt;Add the decision table to the feature review, give each row an owner and keep the resulting regression checks alongside the implementation. When support access or account sharing changes later, update the rule and the table together.&lt;/p&gt;

&lt;p&gt;The useful question is not only “does this endpoint require login?” It is “what permits this person to perform this action on this record?”&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Product disclosure:&lt;/strong&gt; This post is from Breachloom. If you prefer an interactive introduction, our &lt;a href="https://breachloom.com/?lang=en&amp;amp;utm_source=devto&amp;amp;utm_medium=organic_social&amp;amp;utm_campaign=idor_oct2026#/try" rel="noopener noreferrer"&gt;browser-based security training&lt;/a&gt; includes an IDOR demo built around a fictional invoice-access scenario.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AI assistance was used to draft and edit this post. The worksheet is illustrative; no executed test results are claimed.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>testing</category>
    </item>
    <item>
      <title>Prompt injection isn't a payload problem — 3 questions decide if your AI feature is exploitable</title>
      <dc:creator>NET_DARK_BOI</dc:creator>
      <pubDate>Wed, 07 Oct 2026 22:57:00 +0000</pubDate>
      <link>https://dev.to/ookeolioli222/prompt-injection-isnt-a-payload-problem-3-questions-decide-if-your-ai-feature-is-exploitable-33kd</link>
      <guid>https://dev.to/ookeolioli222/prompt-injection-isnt-a-payload-problem-3-questions-decide-if-your-ai-feature-is-exploitable-33kd</guid>
      <description>&lt;p&gt;Prompt injection gets talked about as if it were a clever string you type to jailbreak a chatbot. That framing misses the actual problem, and it is why so many "fixes" do not work.&lt;/p&gt;

&lt;p&gt;The real issue is structural. An LLM feature puts two different things into one text stream: the instructions the application intends, and the data it processes. The model has no reliable way to tell an order it should follow from text it should merely process. That is the same root condition as SQL injection or command injection — instructions and data sharing a channel — except there is currently no complete escaping that makes natural-language data safe to concatenate with instructions.&lt;/p&gt;

&lt;p&gt;So the question is not "what payload breaks it." The question is whether a hostile instruction, hidden in data the model reads, can reach something that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lethal trifecta
&lt;/h2&gt;

&lt;p&gt;Simon Willison's framing is the most useful lens. An AI feature becomes dangerous when it combines three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;access to private data,&lt;/li&gt;
&lt;li&gt;exposure to untrusted content, and&lt;/li&gt;
&lt;li&gt;a way to communicate externally.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Any one of the three is survivable. All three together mean an attacker can plant an instruction in the untrusted content, have the model read private data, and have it sent out. Guardrails that catch 95% of phrasings do not save you, because the attacker only needs one that gets through.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three questions for any AI feature
&lt;/h2&gt;

&lt;p&gt;Instead of hunting for magic strings, map the feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Where does untrusted text enter the prompt?&lt;/strong&gt;&lt;br&gt;
Not just the user's message. Any retrieved document, web page, file, tool result, or earlier message the model reads is a potential injection point. A support bot summarising a ticket, an agent browsing a linked page, a RAG assistant quoting a document, an assistant reading an inbox — all ingest text written by someone else. This is &lt;em&gt;indirect&lt;/em&gt; prompt injection, and it is usually the higher-severity case, because the hostile instruction does not come from the person using the feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. What can the model actually do as a result?&lt;/strong&gt;&lt;br&gt;
A model that only writes a summary back to the same user can be misled, but the blast radius is that user's session. A model wired to tools — sending email, calling internal APIs, reading files, making purchases — can be steered into real actions. Severity is decided by the model's privileges, not the cleverness of the phrasing. Write down every tool it can call and whose authority it runs with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Can a hostile instruction reach a real action or a real exfiltration?&lt;/strong&gt;&lt;br&gt;
This is where the trifecta closes. The classic example is data exfiltration through a markdown image: a hidden instruction tells the assistant to encode something it can read — a recent message, a token — into the URL of an image, which the client then fetches automatically, sending the data to the attacker's server. No script execution required.&lt;/p&gt;

&lt;p&gt;If the answer to all three is yes, you have an exploitable feature, not a curiosity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why filtering the input does not fix it
&lt;/h2&gt;

&lt;p&gt;The instinct is to scan the input for "ignore previous instructions" and block it. It does not hold. Injections hide in Base64, emoji, other languages, text split across documents, or even inside images for a multimodal model. An instruction need not be human-readable to work. Blocklisting natural language is an unwinnable game.&lt;/p&gt;

&lt;p&gt;This is not theoretical. Prompt injection has been demonstrated against production AI features at Microsoft 365 Copilot, Slack AI, and GitLab Duo, among others — real systems wired to real data.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually helps
&lt;/h2&gt;

&lt;p&gt;Because the input cannot be fully sanitised, the durable controls sit around the model, not inside the prompt:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Least privilege for tools.&lt;/strong&gt; Give an agent only the functions a task needs, scoped to the current user, and prefer read-only. High-consequence actions — sending messages, moving money, changing settings — should require an explicit human confirmation that shows what will happen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat the model's output as untrusted.&lt;/strong&gt; If the response is rendered as HTML, it can carry stored XSS; if it is passed to a shell or a database, it carries that injection class. Encode output for its destination.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A deterministic check the model cannot talk past.&lt;/strong&gt; Authorization enforced outside the model is what keeps an injected instruction from becoming an injected action.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolate and label retrieved content,&lt;/strong&gt; limit how much untrusted text enters the context, and log tool calls so abuse is visible afterward. The "dual LLM" pattern — a quarantined model handles untrusted data, a privileged model never sees it — is worth reading about.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these "solve" injection. Together they shrink what a followed instruction can reach, which is the realistic goal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test it like a feature, not a vibe
&lt;/h2&gt;

&lt;p&gt;Open-source tools like Garak and Promptfoo probe for injection and unsafe output handling, and a guardrail classifier can sit in front as one more layer — never the only one. But the most useful test is the one you write for your own feature: place a benign marker in the kind of content the feature consumes, and see whether it comes back in the output. If it does, the data channel reached the instruction channel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the mechanics hands-on
&lt;/h2&gt;

&lt;p&gt;Reading about it only goes so far. These run in the browser:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Indirect injection from page content: &lt;a href="https://breachloom.com/ctf/llm-pi-indirect/" rel="noopener noreferrer"&gt;https://breachloom.com/ctf/llm-pi-indirect/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An agent that can send email, steered by injected text: &lt;a href="https://breachloom.com/ctf/llm-agency-email/" rel="noopener noreferrer"&gt;https://breachloom.com/ctf/llm-agency-email/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A poisoned MCP tool description: &lt;a href="https://breachloom.com/ctf/mcp-poison/" rel="noopener noreferrer"&gt;https://breachloom.com/ctf/mcp-poison/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The full chain, page → tools → disclosure: &lt;a href="https://breachloom.com/chains/ai-agent-pwn/" rel="noopener noreferrer"&gt;https://breachloom.com/chains/ai-agent-pwn/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Sources&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;OWASP GenAI — LLM01:2025 Prompt Injection: &lt;a href="https://genai.owasp.org/llmrisk/llm01-prompt-injection/" rel="noopener noreferrer"&gt;https://genai.owasp.org/llmrisk/llm01-prompt-injection/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Simon Willison — The lethal trifecta: &lt;a href="https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/" rel="noopener noreferrer"&gt;https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;What is the trickiest indirect-injection vector you have seen in a real feature?&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>The Password Reset Email Was Real. The Destination Wasn't.</title>
      <dc:creator>NET_DARK_BOI</dc:creator>
      <pubDate>Tue, 29 Sep 2026 20:34:44 +0000</pubDate>
      <link>https://dev.to/ookeolioli222/the-password-reset-email-was-real-the-destination-wasnt-568a</link>
      <guid>https://dev.to/ookeolioli222/the-password-reset-email-was-real-the-destination-wasnt-568a</guid>
      <description>&lt;p&gt;The application sends the password-reset email. Its own template. Its own delivery system.&lt;/p&gt;

&lt;p&gt;The link carries a genuine reset token.&lt;/p&gt;

&lt;p&gt;But the destination belongs to someone else.&lt;/p&gt;

&lt;p&gt;That is the unsettling part of password-reset poisoning: the application can assemble and deliver the dangerous message itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check was there. It never got its turn.
&lt;/h2&gt;

&lt;p&gt;Coolify's advisory describes a chain involving trusted forwarded headers, a host-validation cache bug, and a reset URL derived from the request. On an empty cache, validation was skipped; the code that would populate that cache sat behind the skipped path.&lt;/p&gt;

&lt;p&gt;The reported attack required the forged header to reach the application and the recipient to click the link. It was not an automatic compromise of every deployment.&lt;/p&gt;

&lt;p&gt;The advisory credits bugbunny.ai and lists &lt;code&gt;v4.0.0-beta.471&lt;/code&gt; as patched. This is an analysis of that disclosure, not a claim of discovering or reproducing the Coolify vulnerability. &lt;a href="https://github.com/coollabsio/coolify/security/advisories/GHSA-cgj8-7m5q-x5gv" rel="noopener noreferrer"&gt;Read the original advisory&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before looking at the fix, choose the destination
&lt;/h2&gt;

&lt;p&gt;Here is a separate, deliberately small JavaScript model. All domains and tokens are placeholders. It sends no requests or emails.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;buildLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/reset-password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;vulnerable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-forwarded-host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;buildLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`https://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now supply these inputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;app.example&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-forwarded-host&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;collector.example&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The token is &lt;code&gt;DEMO_ONLY&lt;/code&gt;. Which server receives it if someone follows the resulting URL?&lt;/p&gt;

&lt;p&gt;The answer comes from the first line inside &lt;code&gt;vulnerable&lt;/code&gt;. The forwarded value wins. The resulting link is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://collector.example/reset-password?token=DEMO_ONLY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how little is wrong with this URL as a string. It parses. It uses HTTPS. It has the expected path. The token is correctly encoded.&lt;/p&gt;

&lt;p&gt;The mistake happened before any of those checks could help: request metadata was allowed to choose the destination for a credential.&lt;/p&gt;

&lt;h2&gt;
  
  
  The obvious repair leaves a second question
&lt;/h2&gt;

&lt;p&gt;Suppose you delete support for &lt;code&gt;x-forwarded-host&lt;/code&gt; and use only &lt;code&gt;headers.host&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this model, the caller still controls &lt;code&gt;headers.host&lt;/code&gt;. You have removed one route to the wrong destination while leaving another.&lt;/p&gt;

&lt;p&gt;For this single-origin example, the repaired function takes its origin from deployment configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;APP_ORIGIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://app.example&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;repaired&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;buildLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;APP_ORIGIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This matches the principle in OWASP's reset-password guidance: do not derive reset destinations from an untrusted Host header; use a fixed destination or validate against trusted domains. &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Forgot_Password_Cheat_Sheet.html" rel="noopener noreferrer"&gt;OWASP guidance&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The constant here represents configuration controlled by the operator. It must not be another name for user input. A product supporting multiple customer domains needs an explicit, verified mapping rather than this single-origin shortcut.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the local experiment actually showed
&lt;/h2&gt;

&lt;p&gt;The offline model was run on Node.js 24.18.0. It produced these results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Input&lt;/th&gt;
&lt;th&gt;Original destination&lt;/th&gt;
&lt;th&gt;Repaired destination&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ordinary host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.example&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.example&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forged forwarded host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;collector.example&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.example&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forged Host&lt;/td&gt;
&lt;td&gt;&lt;code&gt;collector.example&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;app.example&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Twelve assertions passed. They checked the repaired origin, path and token across the three cases, confirmed the two vulnerable destinations, and checked a token containing reserved characters.&lt;/p&gt;

&lt;p&gt;Those results establish the behavior of these small functions. They do not establish how a deployed proxy rewrites headers, whether a framework rejects the request, or whether a complete reset flow is secure.&lt;/p&gt;

&lt;p&gt;My next integration test would inspect the link in a captured email after a request travels through the application's actual proxy stack. I would repeat it with a cold cache. That is a proposed next test, not a result reported here.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question I would bring to a code review
&lt;/h2&gt;

&lt;p&gt;When a test says “reset email sent,” what exactly did it verify?&lt;/p&gt;

&lt;p&gt;Delivery is one assertion. The destination is another. A test can prove the first while completely missing the second.&lt;/p&gt;

&lt;p&gt;I would ask the reviewer to trace the origin backwards: from the email template, through the URL builder, to whichever input selected it. That turns a vague question about whether the flow looks secure into a specific question about who controls one consequential value.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I am building &lt;a href="https://breachloom.com/?lang=en&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=reset_destination" rel="noopener noreferrer"&gt;Breachloom&lt;/a&gt;, a browser-based security practice platform. If you want to practice investigating failures and repairing vulnerable code, start with its free exercise. If that format suits you, consider PRO for continued practice. This article's model is a standalone example, not a claim that the Coolify case is available as a Breachloom mission.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What does your password-reset test assert about the actual link in the email?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>debugging</category>
      <category>beginners</category>
    </item>
    <item>
      <title>You're On Call. It's 02:13. Was This a Breach?</title>
      <dc:creator>NET_DARK_BOI</dc:creator>
      <pubDate>Tue, 29 Sep 2026 20:18:06 +0000</pubDate>
      <link>https://dev.to/ookeolioli222/youre-on-call-its-0213-was-this-a-breach-56fe</link>
      <guid>https://dev.to/ookeolioli222/youre-on-call-its-0213-was-this-a-breach-56fe</guid>
      <description>&lt;p&gt;At 02:13, a customer reports seeing another company's order.&lt;/p&gt;

&lt;p&gt;The application is healthy. No deployment failed. Your monitoring dashboard is green.&lt;/p&gt;

&lt;p&gt;You have the following five log entries. What can you conclude?&lt;/p&gt;

&lt;p&gt;This is a fictional investigation exercise. All accounts, orders, times and events below are invented.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;02:10:04 INFO  login.success user=alice tenant=north request=r10
02:11:18 INFO  GET /api/orders/o-814 actor=alice status=200 request=r11
02:11:52 WARN  image.resize.failed asset=logo-6 request=r12
02:12:09 INFO  GET /api/orders/o-815 actor=alice status=200 request=r13
02:13:02 INFO  support.ticket.created category=wrong-order request=r14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You also have this simplified handler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assume authentication has already succeeded. Tenant authorization is the question under investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pause before declaring a breach
&lt;/h2&gt;

&lt;p&gt;An HTTP 200 does not identify the owner of an order. It does not show the response body. The log excerpt also does not tell us whether another layer applied an authorization policy.&lt;/p&gt;

&lt;p&gt;The customer report and handler justify investigation. They do not establish how many users or records were affected.&lt;/p&gt;

&lt;p&gt;My next questions would be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Which tenant owned &lt;code&gt;o-815&lt;/code&gt; at the time?&lt;/li&gt;
&lt;li&gt;What access was Alice entitled to at the time?&lt;/li&gt;
&lt;li&gt;What data did request &lt;code&gt;r13&lt;/code&gt; return?&lt;/li&gt;
&lt;li&gt;Was an authorization check applied elsewhere?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The image-resizing warning currently has no demonstrated connection to the report.&lt;/p&gt;

&lt;p&gt;Reveal the additional evidence&lt;/p&gt;

&lt;p&gt;In this fictional scenario, the investigator confirms that &lt;code&gt;o-815&lt;/code&gt; belonged to tenant &lt;code&gt;south&lt;/code&gt;. Alice had access only to tenant &lt;code&gt;north&lt;/code&gt;. A retained trace confirms that &lt;code&gt;r13&lt;/code&gt; returned the order items, and code review finds no intervening tenant authorization check.&lt;/p&gt;

&lt;p&gt;That supports one confirmed cross-tenant disclosure in this exercise. It does not establish bulk extraction, intent, or the total number of affected orders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repair the rule, then verify the behavior
&lt;/h2&gt;

&lt;p&gt;For this example, order retrieval should be constrained by both the requested identifier and the tenant the caller is authorized to access. That tenant context must come from trusted, verified membership information.&lt;/p&gt;

&lt;p&gt;Regression tests should cover an allowed request, a cross-tenant request and a nonexistent order. They should also verify the permitted response fields.&lt;/p&gt;

&lt;p&gt;Incident handling would additionally require preserving relevant evidence, assessing the exposure and coordinating containment under the organization's process. A code patch alone does not complete the investigation.&lt;/p&gt;

&lt;h2&gt;
  
  
  The skill this exercise tests
&lt;/h2&gt;

&lt;p&gt;It is easy to jump from “suspicious log” to “confirmed breach,” or from “we found one request” to “only one request happened.”&lt;/p&gt;

&lt;p&gt;The harder skill is keeping observations, hypotheses and conclusions separate, then selecting the evidence that resolves uncertainty.&lt;/p&gt;

&lt;p&gt;I am building &lt;a href="https://breachloom.com/?lang=en&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=oncall_case" rel="noopener noreferrer"&gt;Breachloom&lt;/a&gt;, a browser-based security practice platform with investigation and code-repair exercises. This article is a standalone fictional case, not a claim that this exact scenario is playable there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before opening the reveal, which evidence would you request first — and what would it establish?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>debugging</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>How Would You Prove an AI Security Fix Actually Works?</title>
      <dc:creator>NET_DARK_BOI</dc:creator>
      <pubDate>Tue, 29 Sep 2026 20:12:14 +0000</pubDate>
      <link>https://dev.to/ookeolioli222/how-would-you-prove-an-ai-security-fix-actually-works-1fm2</link>
      <guid>https://dev.to/ookeolioli222/how-would-you-prove-an-ai-security-fix-actually-works-1fm2</guid>
      <description>&lt;p&gt;An assistant proposes a security patch. Its explanation sounds reasonable. The original attack no longer works.&lt;/p&gt;

&lt;p&gt;Do you merge it?&lt;/p&gt;

&lt;p&gt;I want to evaluate that decision with a small experiment built around intentionally vulnerable exercises. This post describes the experiment design. It does not report completed runs or claim a failure rate for any model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three cases, three different failure modes
&lt;/h2&gt;

&lt;p&gt;I would begin with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Object authorization:&lt;/strong&gt; one user can read another user's invoice.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL injection:&lt;/strong&gt; input changes the structure of a query.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path traversal:&lt;/strong&gt; a download feature can access files outside its permitted directory.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each exercise needs an explicit contract. Who should have access? Which inputs are legitimate? What should happen when a resource does not exist?&lt;/p&gt;

&lt;p&gt;Without those details, the assistant and the reviewer may be solving different problems.&lt;/p&gt;

&lt;h2&gt;
  
  
  Freeze the tests before asking for a patch
&lt;/h2&gt;

&lt;p&gt;I would prepare three test groups:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Original reproduction&lt;/td&gt;
&lt;td&gt;Does the reported misuse still succeed?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Related cases&lt;/td&gt;
&lt;td&gt;Does the same weakness survive with different data or conditions?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legitimate behavior&lt;/td&gt;
&lt;td&gt;Can intended users still use the feature?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For a SQL query, an ordinary product name containing an apostrophe is a useful legitimate input. Rejecting it may hide a query-construction bug while breaking a valid requirement.&lt;/p&gt;

&lt;p&gt;For object authorization, use at least two users and more than one protected object. A hard-coded exception must not pass as a general fix.&lt;/p&gt;

&lt;p&gt;For file access, the tests must match the actual path-decoding, normalization and filesystem behavior. A toy string check cannot establish the safety of a production file server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Record what actually happened
&lt;/h2&gt;

&lt;p&gt;For every run, save:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The model identifier, date and relevant settings.&lt;/li&gt;
&lt;li&gt;The exact prompt, code and requirements supplied.&lt;/li&gt;
&lt;li&gt;The proposed patch and any human edits.&lt;/li&gt;
&lt;li&gt;Test results by group, including failures.&lt;/li&gt;
&lt;li&gt;Whether the assistant saw test feedback and received another attempt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep first-attempt results separate from results after feedback. If comparing models, use the same cases and access to tools, and report the limited scope. A few examples cannot support a broad ranking of model security competence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let the reader review before revealing the result
&lt;/h2&gt;

&lt;p&gt;The most interesting part would be showing a patch first and asking: “Approve or request changes?”&lt;/p&gt;

&lt;p&gt;Then reveal the test evidence. Include patches that succeed. If the assistant handles every case well, that is the result to publish.&lt;/p&gt;

&lt;p&gt;My hypothesis is that reviewing the contract and test coverage will teach more than counting whether a single demonstration stopped working. The experiment should be allowed to challenge that hypothesis too.&lt;/p&gt;

&lt;p&gt;I am building &lt;a href="https://breachloom.com/?lang=en&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ai_patch_review" rel="noopener noreferrer"&gt;Breachloom&lt;/a&gt;, which includes code-repair exercises. Those exercises motivate this proposed evaluation; no AI benchmark results are being claimed here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which legitimate behavior would you include to catch an over-restrictive “security fix”?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>testing</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The Checkout Works. Would You Let This Store Go Live?</title>
      <dc:creator>NET_DARK_BOI</dc:creator>
      <pubDate>Tue, 29 Sep 2026 20:11:31 +0000</pubDate>
      <link>https://dev.to/ookeolioli222/the-checkout-works-would-you-let-this-store-go-live-2760</link>
      <guid>https://dev.to/ookeolioli222/the-checkout-works-would-you-let-this-store-go-live-2760</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;“Checkout works. The tests are green. We launch tomorrow.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You are reviewing a fictional store called LoomMart. The product manager has one question: can we ship?&lt;/p&gt;

&lt;p&gt;There are three observations in your review notes. None causes a crash. Every screen looks normal.&lt;/p&gt;

&lt;p&gt;This is a tabletop exercise, not a report about a real company or an announcement of a playable store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observation one: the order number
&lt;/h2&gt;

&lt;p&gt;Alice signs in and opens her order. The browser requests &lt;code&gt;/api/orders/order-100&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the exercise, changing the identifier to &lt;code&gt;order-101&lt;/code&gt; returns an order whose owner is Bob. The API verifies the session but retrieves orders by ID alone.&lt;/p&gt;

&lt;p&gt;The frontend never links Alice to Bob's order. Does that help?&lt;/p&gt;

&lt;p&gt;Only with navigation. It does not enforce permission at the server. The API needs to apply the relevant ownership or membership policy before returning the object.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observation two: the coupon
&lt;/h2&gt;

&lt;p&gt;The coupon is intended for one redemption per account. The implementation does this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Read whether the coupon was used.
If unused, apply the discount.
Mark the coupon as used.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One request at a time looks fine. Two overlapping requests can both read “unused” before either records the redemption.&lt;/p&gt;

&lt;p&gt;That is a concurrency hypothesis to test in a controlled environment. The snippet alone does not prove how the real database behaves.&lt;/p&gt;

&lt;p&gt;You would want the redemption rule enforced atomically, using appropriate database constraints or transactional logic, and a test showing that overlapping requests cannot both succeed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observation three: the profile preview
&lt;/h2&gt;

&lt;p&gt;The store displays a customer's profile name as HTML. Plain names render correctly. Input intended to be text is being treated as markup.&lt;/p&gt;

&lt;p&gt;If the product only needs a plain-text name, use a text-rendering operation. If it intentionally supports rich HTML, the requirements and sanitization strategy become more complicated.&lt;/p&gt;

&lt;p&gt;“It looks fine with my name” is a happy-path test, not an answer to the trust question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your launch decision
&lt;/h2&gt;

&lt;p&gt;For each observation, write three sentences:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The rule the application should enforce.&lt;/li&gt;
&lt;li&gt;The evidence you have, and what still needs verification.&lt;/li&gt;
&lt;li&gt;The test that would demonstrate a successful repair.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Avoid reducing this to three vulnerability names. A team needs reproducible evidence and a repair it can verify.&lt;/p&gt;

&lt;p&gt;For the order issue, I would test both directions: Alice cannot read Bob's order, and Bob can still read it. For the coupon, I would check concurrent requests and subsequent retries. For the profile name, I would confirm that ordinary names and markup-looking text both behave as intended.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I like this format
&lt;/h2&gt;

&lt;p&gt;A working demo can conceal assumptions about ownership, timing and interpretation. A launch review makes those assumptions visible in a familiar product.&lt;/p&gt;

&lt;p&gt;I am building &lt;a href="https://breachloom.com/?lang=en&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=store_launch" rel="noopener noreferrer"&gt;Breachloom&lt;/a&gt;, where learners practice web security through browser simulations and code-repair tasks. The free IDOR case lets you explore the first kind of mistake. LoomMart itself is a proposed scenario, not an available Breachloom feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Which observation would you investigate first, and what would you ask the team before making the launch decision?&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>testing</category>
    </item>
    <item>
      <title>Three Security Fixes. Only One Survives the Tests.</title>
      <dc:creator>NET_DARK_BOI</dc:creator>
      <pubDate>Tue, 29 Sep 2026 20:05:49 +0000</pubDate>
      <link>https://dev.to/ookeolioli222/three-security-fixes-only-one-survives-the-tests-ki9</link>
      <guid>https://dev.to/ookeolioli222/three-security-fixes-only-one-survives-the-tests-ki9</guid>
      <description>&lt;p&gt;The bug report is short: Alice can read Bob's invoice.&lt;/p&gt;

&lt;p&gt;Three patches arrive. All three stop the exact request in the report. Which one would you approve?&lt;/p&gt;

&lt;p&gt;This is a fictional code-review exercise. In this application, each invoice belongs to one user. Authentication middleware provides a verified &lt;code&gt;req.user.id&lt;/code&gt;. There are no shared accounts or administrator exceptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The original endpoint
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/invoices/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requireLogin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alice changes the invoice ID in the URL. The server returns Bob's invoice because the lookup never checks ownership.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patch A: reject the reported ID
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;invoice-bob-17&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Patch B: shut down the endpoint
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;403&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Patch C: scope the lookup to the authenticated user
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findFirst&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ownerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before reading on: what tests would distinguish these patches?&lt;/p&gt;

&lt;p&gt;Reveal the review&lt;/p&gt;

&lt;p&gt;Patch A blocks one example. Another invoice belonging to Bob still passes through. It can also prevent Bob from opening his own reported invoice.&lt;/p&gt;

&lt;p&gt;Patch B blocks the attack by removing the feature. It fails the requirement that owners can read their invoices.&lt;/p&gt;

&lt;p&gt;Patch C expresses the stated policy: the requested invoice must also belong to the authenticated user. The owner comes from the verified session, not a user-supplied query parameter.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test matrix I would ask for
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Request&lt;/th&gt;
&lt;th&gt;Expected result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alice requests her own invoice&lt;/td&gt;
&lt;td&gt;200, expected fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice requests Bob's reported invoice&lt;/td&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice requests another invoice belonging to Bob&lt;/td&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bob requests his own invoice&lt;/td&gt;
&lt;td&gt;200, expected fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Alice requests a nonexistent invoice&lt;/td&gt;
&lt;td&gt;404&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anonymous caller requests an invoice&lt;/td&gt;
&lt;td&gt;Rejected by authentication&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are expected outcomes derived from the example, not results from an executed application. A real implementation also needs input validation and tests against its actual database and authentication stack.&lt;/p&gt;

&lt;p&gt;The detail I care about most is the second invoice belonging to Bob. It separates a general authorization rule from a patch tailored to the bug report.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a useful review exercise
&lt;/h2&gt;

&lt;p&gt;“The original exploit stopped working” is necessary evidence. It is incomplete evidence.&lt;/p&gt;

&lt;p&gt;A useful review checks the policy, a different instance of the same failure, and legitimate behavior. Otherwise, we can accidentally reward a fix that blocks the demo while leaving the vulnerability class intact.&lt;/p&gt;

&lt;p&gt;I am building &lt;a href="https://breachloom.com/?lang=en&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=three_fixes" rel="noopener noreferrer"&gt;Breachloom&lt;/a&gt;, a browser-based security practice platform with code-repair exercises. This kind of reasoning is what I want learners to practice. The platform also has a free IDOR case without an account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What additional test would you require before approving Patch C? State the assumption it tests.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>testing</category>
    </item>
    <item>
      <title>Is Cybersecurity a Lifeboat in the AI Era?</title>
      <dc:creator>NET_DARK_BOI</dc:creator>
      <pubDate>Tue, 29 Sep 2026 20:04:54 +0000</pubDate>
      <link>https://dev.to/ookeolioli222/is-cybersecurity-a-lifeboat-in-the-ai-era-kjh</link>
      <guid>https://dev.to/ookeolioli222/is-cybersecurity-a-lifeboat-in-the-ai-era-kjh</guid>
      <description>&lt;p&gt;Your coding assistant builds the endpoint. The tests pass. The demo works.&lt;/p&gt;

&lt;p&gt;Then one customer opens another customer's invoice.&lt;/p&gt;

&lt;p&gt;That gap — between software that works and software that deserves our trust — is why cybersecurity looks like an attractive career move in the AI era.&lt;/p&gt;

&lt;p&gt;But is it a lifeboat? Or are we mistaking a growing problem for a guaranteed job?&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you change careers, review this endpoint
&lt;/h2&gt;

&lt;p&gt;Here is a deliberately simplified, fictional example. Assume authentication middleware has already populated &lt;code&gt;req.user&lt;/code&gt; from a verified session:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;app&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/invoices/:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requireLogin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The developer can demonstrate all of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anonymous requests are rejected.&lt;/li&gt;
&lt;li&gt;An existing invoice returns successfully.&lt;/li&gt;
&lt;li&gt;A missing invoice returns 404.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would you approve it?&lt;/p&gt;

&lt;p&gt;The missing question is: &lt;strong&gt;does this invoice belong to the authenticated user?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A login check establishes identity. It does not establish permission to read every object.&lt;/p&gt;

&lt;p&gt;For an application where invoices belong to individual accounts, the query could enforce both conditions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;invoice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;invoice&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findFirst&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;ownerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is one illustrative authorization rule, not a universal fix. Shared accounts, organizations and delegated access need their own explicit policies.&lt;/p&gt;

&lt;p&gt;The useful test is not just “can Alice open her invoice?” It is also “can Alice open Bob's?” A complete response needs to check which fields the API returns, too.&lt;/p&gt;

&lt;p&gt;An AI assistant can identify this bug. It can also help write the fix and tests. The example is not proof that humans outperform AI. It shows the kind of question someone must ensure the development process actually asks.&lt;/p&gt;

&lt;h2&gt;
  
  
  More demand does not mean an easy entrance
&lt;/h2&gt;

&lt;p&gt;There is a real reason to consider the field. The US Bureau of Labor Statistics projects &lt;strong&gt;29% employment growth for information security analysts between 2024 and 2034&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That is a projection for one occupation in the United States. It is not a promise of a junior vacancy in your city, and it does not establish that AI is causing that growth.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.bls.gov/ooh/computer-and-information-technology/information-security-analysts.htm" rel="noopener noreferrer"&gt;Source: BLS Occupational Outlook Handbook&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is also a less comfortable part of the picture. ISC2's 2025 workforce study reports continuing budget constraints, hiring freezes and layoffs alongside skills needs. Its findings also describe AI changing the skills practitioners need.&lt;/p&gt;

&lt;p&gt;A shortage of particular skills and a difficult job search can exist at the same time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.isc2.org/Insights/2025/12/2025-ISC2-Cybersecurity-Workforce-Study" rel="noopener noreferrer"&gt;Source: ISC2 2025 Cybersecurity Workforce Study&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The work inside the lifeboat is changing too
&lt;/h2&gt;

&lt;p&gt;Security teams use software, and their work includes tasks that AI can assist with: summarizing logs, explaining unfamiliar code, drafting tests and preparing reports for review.&lt;/p&gt;

&lt;p&gt;Moving into security does not remove the need to adapt to automation.&lt;/p&gt;

&lt;p&gt;My view is that a better career direction is learning to &lt;strong&gt;verify claims about systems&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What can this user actually access?&lt;/li&gt;
&lt;li&gt;Which evidence supports the reported impact?&lt;/li&gt;
&lt;li&gt;Does the fix block the misuse while preserving legitimate behavior?&lt;/li&gt;
&lt;li&gt;What remains uncertain after the tests pass?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Those questions apply whether code was written by a person, generated by an assistant, or assembled from both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try the work before buying the career story
&lt;/h2&gt;

&lt;p&gt;If you are a developer considering AppSec, try one small investigation before committing to a long retraining plan:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take an intentionally vulnerable lab with a clear scope.&lt;/li&gt;
&lt;li&gt;Reproduce one authorization failure using two fictional accounts.&lt;/li&gt;
&lt;li&gt;Write the expected and observed behavior.&lt;/li&gt;
&lt;li&gt;Implement a repair.&lt;/li&gt;
&lt;li&gt;Test both unauthorized access and legitimate access.&lt;/li&gt;
&lt;li&gt;Ask someone else to reproduce the issue from your report.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Notice which parts you enjoy. Security work includes careful documentation, false leads and checking your own assumptions. The satisfying moment is not only finding a bug; it is being able to explain what happened and demonstrate that the repair works.&lt;/p&gt;

&lt;p&gt;This experiment will not tell you whether you are job-ready. It gives you a more useful starting point than a promise that a profession is “AI-proof.”&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I stand
&lt;/h2&gt;

&lt;p&gt;I would consider cybersecurity because I want to understand how systems fail and how to make them safer. I would be cautious about choosing it purely as an escape from AI.&lt;/p&gt;

&lt;p&gt;For developers, security knowledge can also deepen an existing career. You can begin by reviewing authorization rules and adding meaningful security tests without immediately changing your job title.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I am building &lt;a href="https://breachloom.com/?lang=en&amp;amp;utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ai_lifeboat" rel="noopener noreferrer"&gt;Breachloom&lt;/a&gt;, a browser-based web security practice platform. It includes a free IDOR case you can try without an account. That is one place to try this kind of investigation; &lt;a href="https://portswigger.net/web-security" rel="noopener noreferrer"&gt;PortSwigger Web Security Academy&lt;/a&gt; is another established free learning resource.&lt;/p&gt;

&lt;p&gt;If you work in security: &lt;strong&gt;which part of your work has AI genuinely reduced, and which part now requires more checking?&lt;/strong&gt; Concrete examples would be more useful than predictions that either nobody or everybody will be replaced.&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>ai</category>
      <category>career</category>
    </item>
    <item>
      <title>curl quit, HackerOne paused, Elastic pays $2 a report: the bug-bounty economy after AI slop</title>
      <dc:creator>NET_DARK_BOI</dc:creator>
      <pubDate>Sun, 27 Sep 2026 10:55:10 +0000</pubDate>
      <link>https://dev.to/ookeolioli222/curl-quit-hackerone-paused-elastic-pays-2-a-report-the-bug-bounty-economy-after-ai-slop-47bc</link>
      <guid>https://dev.to/ookeolioli222/curl-quit-hackerone-paused-elastic-pays-2-a-report-the-bug-bounty-economy-after-ai-slop-47bc</guid>
      <description>&lt;p&gt;In December 2024 Seth Larson, the Python Software Foundation's security developer-in-residence, described "a new era of slop security reports for open source": vulnerability reports generated by language models, referencing code that does not exist, flagging deliberate design choices as bugs. One report to urllib3 warned that the library was disabling SSLv2 — which it was, on purpose. His summary of the cost was not about money: "Security reports that waste maintainers' time result in confusion, stress, frustration, and to top it off a sense of isolation due to the secretive nature of security reports" (&lt;a href="https://sethmlarson.dev/slop-security-reports" rel="noopener noreferrer"&gt;Larson, Dec 2024&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Eighteen months later, the thing he described has repriced an entire market. Here is the timeline, with the numbers traced to their sources.&lt;/p&gt;

&lt;h2&gt;
  
  
  The timeline
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;January 2026 — curl ends its bounty.&lt;/strong&gt; After seven years and 87 confirmed vulnerabilities, curl's HackerOne programme closed on 31 January. Daniel Stenberg's stated goal: "remove the incentive for people to submit crap and non-well researched reports to us. AI generated or not." In one sixteen-hour stretch that month the project received seven reports; none was a vulnerability (&lt;a href="https://www.bleepingcomputer.com/news/security/curl-ending-bug-bounty-program-after-flood-of-ai-slop-reports/" rel="noopener noreferrer"&gt;BleepingComputer&lt;/a&gt;, &lt;a href="https://www.theregister.com/security/2026/01/21/curl-shutters-bug-bounty-program-to-stop-ai-slop/5063039" rel="noopener noreferrer"&gt;The Register&lt;/a&gt;, &lt;a href="https://redmonk.com/kholterhoff/2026/05/05/ai-slop-vulnerability-treadmill/" rel="noopener noreferrer"&gt;RedMonk&lt;/a&gt;). Reports were still welcome; the payment was what stopped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;27 March 2026 — HackerOne's Internet Bug Bounty pauses.&lt;/strong&gt; The IBB, which has paid for fixes in open-source projects since 2013, stopped accepting new submissions. The reason given was not fraud but arithmetic: AI-assisted discovery had outrun the maintainers' capacity to fix, and bounties fund discovery, not remediation (&lt;a href="https://gigazine.net/gsc_news/en/20260407-hackerone-internet-bug-bounty-program-pause/" rel="noopener noreferrer"&gt;GIGAZINE&lt;/a&gt;, &lt;a href="https://www.darkreading.com/application-security/ai-led-remediation-crisis-prompts-hackerone-pause-bug-bounties" rel="noopener noreferrer"&gt;Dark Reading&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;21 April 2026 — HackerOne starts selling validation.&lt;/strong&gt; Launching &lt;em&gt;h1 Validation&lt;/em&gt;, the platform published its own numbers: submissions up 76% year over year, a record 46,947 in March, about 25% of findings confirmed exploitable, critical-and-high severity up to 32% of the total, and remediation capacity improved by only 19% (&lt;a href="https://www.hackerone.com/press-release/hackerone-introduces-h1-validation-help-enterprises-manage-surge-ai-discovered" rel="noopener noreferrer"&gt;HackerOne press release&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6 May 2026 — "Finding Fast, Fixing Slow".&lt;/strong&gt; HackerOne's product leadership spelled out the asymmetry. Mean time to remediate an individual issue fell by roughly 80%. The number of vulnerabilities actually resolved per month fell by about 46%. The backlog of validated-but-unresolved findings grew more than 21×; unresolved criticals grew 25×; the resolution rate for criticals dropped from over 83% to under 40% (&lt;a href="https://www.hackerone.com/blog/finding-fast-fixing-slow-rising-exposure-debt" rel="noopener noreferrer"&gt;HackerOne, May 2026&lt;/a&gt;). Teams are fixing faster and falling further behind.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4 August 2026 — Elastic publishes the cost of a report.&lt;/strong&gt; Elastic's programme received more than 1,390 reports in the first half of 2026 — more than 2024 and 2025 combined, against a historical 600–850 per year. Roughly 70% are rejected at analysis. Their AI-assisted triage now costs about $0.50–1.15 per report to analyse and $0.80–4.90 to reproduce when reproduction is needed, around $2 on average. The stated cause: "LLMs made it trivially cheap to generate vulnerability reports" (&lt;a href="https://www.elastic.co/security-labs/ai-vulnerability-triage-bug-bounty-hackerone" rel="noopener noreferrer"&gt;Elastic Security Labs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For scale: NIST published roughly 50,000 CVEs in 2025, up 22% on the year, and a small company like Screenly reported 331 submissions in under six months, of which 39 were real (&lt;a href="https://redmonk.com/kholterhoff/2026/05/05/ai-slop-vulnerability-treadmill/" rel="noopener noreferrer"&gt;RedMonk&lt;/a&gt;). The OpenSSF's vulnerability-disclosure working group now has an open work item to write best practices for maintainers facing AI-generated reports (&lt;a href="https://github.com/ossf/wg-vulnerability-disclosures/issues/178" rel="noopener noreferrer"&gt;OpenSSF WG issue #178&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is sending all this
&lt;/h2&gt;

&lt;p&gt;The senders are not a separate population from the good researchers. They are the same people with the same tools.&lt;/p&gt;

&lt;p&gt;HackerOne's ninth Hacker-Powered Security Report (October 2025) found 70% of researchers using AI in their workflow; valid AI-related vulnerability reports were up 210% and valid prompt-injection reports up 540%; 1,121 programmes had AI in scope, a 270% increase (&lt;a href="https://www.hackerone.com/press-release/hackerone-report-finds-210-spike-ai-vulnerability-reports-amid-rise-ai-autonomy" rel="noopener noreferrer"&gt;HackerOne, Oct 2025&lt;/a&gt;). Bugcrowd's &lt;em&gt;Inside the Mind of a Hacker 2026&lt;/em&gt;, drawn from more than 2,000 participants, puts AI adoption among hackers at 82%, up from 64% in 2023, with hackers "automating the search for low hanging fruit vulnerabilities" to spend their time on the complex ones (&lt;a href="https://www.prnewswire.com/news-releases/bugcrowd-report-unveils-the-era-of-human-augmented-intelligence-as-ai-adoption-climbs-to-82-302670846.html" rel="noopener noreferrer"&gt;Bugcrowd, Jan 2026&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So the same tooling produces the valid quarter and the noisy three-quarters. The variable is not the model. It is whether a human verified the reproduction and identified the root cause before pressing submit — the two steps that cost time, and therefore the two steps a volume strategy skips.&lt;/p&gt;

&lt;h2&gt;
  
  
  The economics, in one paragraph
&lt;/h2&gt;

&lt;p&gt;A bounty pays for discovery. Nobody pays for triage, and nobody pays for the fix; those are absorbed by the programme and the maintainer. When discovery becomes nearly free, its price falls toward zero and the unpaid parts absorb the entire cost of the flood. That is why programmes pause (IBB) or close (curl) &lt;em&gt;while valid findings are rising&lt;/em&gt;, and why the platforms' newest products are validation services rather than bigger payouts. RedMonk's Kate Holterhoff put the condition for stability plainly: "Until assessment costs are reduced, fixes are prioritized over mere discoveries, and supply chain security receives executive commitment, the vulnerability management system will accelerate unsustainably" (&lt;a href="https://redmonk.com/kholterhoff/2026/05/05/ai-slop-vulnerability-treadmill/" rel="noopener noreferrer"&gt;RedMonk&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  What a report that survives triage looks like
&lt;/h2&gt;

&lt;p&gt;Elastic's cost model is the clearest guide, because it shows where the money goes: reports that cannot be reproduced are cheap to close and reports that make reproduction trivial are the ones that get paid. Read from the triage side, a report that gets through has five parts.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;An exact reproduction.&lt;/strong&gt; The request and response, the account and role used, the preconditions. Not "an attacker could" — "this request, from this user, returned this".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The observed effect on test data.&lt;/strong&gt; What was read, changed or executed, shown, not inferred. A timing difference is a hint, not an impact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The missing decision.&lt;/strong&gt; Which check is absent — ownership on the object, parameterisation at the query, an allow-list at the sink — and, where the code is public, which line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A proposed fix&lt;/strong&gt;, ideally with the regression test that would have caught it. This is the part maintainers can act on without re-deriving the bug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A scope and duplicate check&lt;/strong&gt; done &lt;em&gt;before&lt;/em&gt; submission, against the programme's policy and its public issue tracker.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Parts 3 and 4 are the ones that models do not reliably produce and most training does not teach, because most training scores the exploit and stops. They are also the only parts with any remaining market value.&lt;/p&gt;

&lt;p&gt;Breachloom's practice track is built to that shape: after each exploit there is a fix-the-code mission in which the server replays the attack against the learner's patch, and the bug-bounty path includes a report template with exactly those five sections:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fix the bug and have it verified: &lt;a href="https://breachloom.com/codeops/" rel="noopener noreferrer"&gt;https://breachloom.com/codeops/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The bug-bounty roadmap and report template: &lt;a href="https://breachloom.com/bug-bounty/" rel="noopener noreferrer"&gt;https://breachloom.com/bug-bounty/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The bounty market is not dying. It is repricing.
&lt;/h2&gt;

&lt;p&gt;Discovery is heading toward free. Verified reproduction, root cause and a working fix are what is left to pay for — and, judging by the backlog numbers, there has never been more of that work to do.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources:&lt;/em&gt; &lt;a href="https://sethmlarson.dev/slop-security-reports" rel="noopener noreferrer"&gt;Seth Larson — New era of slop security reports for open source (Dec 2024)&lt;/a&gt; · &lt;a href="https://www.bleepingcomputer.com/news/security/curl-ending-bug-bounty-program-after-flood-of-ai-slop-reports/" rel="noopener noreferrer"&gt;BleepingComputer — curl ending bug bounty program (Jan 2026)&lt;/a&gt; · &lt;a href="https://www.theregister.com/security/2026/01/21/curl-shutters-bug-bounty-program-to-stop-ai-slop/5063039" rel="noopener noreferrer"&gt;The Register — curl shutters bug bounty program (Jan 2026)&lt;/a&gt; · &lt;a href="https://gigazine.net/gsc_news/en/20260407-hackerone-internet-bug-bounty-program-pause/" rel="noopener noreferrer"&gt;GIGAZINE — Internet Bug Bounty pauses new submissions (Apr 2026)&lt;/a&gt; · &lt;a href="https://www.darkreading.com/application-security/ai-led-remediation-crisis-prompts-hackerone-pause-bug-bounties" rel="noopener noreferrer"&gt;Dark Reading — AI-led remediation crisis prompts HackerOne to pause bug bounties&lt;/a&gt; · &lt;a href="https://www.hackerone.com/press-release/hackerone-introduces-h1-validation-help-enterprises-manage-surge-ai-discovered" rel="noopener noreferrer"&gt;HackerOne — h1 Validation press release (Apr 2026)&lt;/a&gt; · &lt;a href="https://www.hackerone.com/blog/finding-fast-fixing-slow-rising-exposure-debt" rel="noopener noreferrer"&gt;HackerOne — Finding Fast, Fixing Slow (May 2026)&lt;/a&gt; · &lt;a href="https://www.elastic.co/security-labs/ai-vulnerability-triage-bug-bounty-hackerone" rel="noopener noreferrer"&gt;Elastic Security Labs — AI vulnerability triage (Aug 2026)&lt;/a&gt; · &lt;a href="https://redmonk.com/kholterhoff/2026/05/05/ai-slop-vulnerability-treadmill/" rel="noopener noreferrer"&gt;RedMonk — AI Slop &amp;amp; the Vulnerability Treadmill (May 2026)&lt;/a&gt; · &lt;a href="https://github.com/ossf/wg-vulnerability-disclosures/issues/178" rel="noopener noreferrer"&gt;OpenSSF — AI-SLOP best practices work item&lt;/a&gt; · &lt;a href="https://www.hackerone.com/press-release/hackerone-report-finds-210-spike-ai-vulnerability-reports-amid-rise-ai-autonomy" rel="noopener noreferrer"&gt;HackerOne — 9th Hacker-Powered Security Report (Oct 2025)&lt;/a&gt; · &lt;a href="https://www.prnewswire.com/news-releases/bugcrowd-report-unveils-the-era-of-human-augmented-intelligence-as-ai-adoption-climbs-to-82-302670846.html" rel="noopener noreferrer"&gt;Bugcrowd — Inside the Mind of a Hacker 2026 (Jan 2026)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Most security labs end at the flag. That is how the industry keeps producing script kiddies.</title>
      <dc:creator>NET_DARK_BOI</dc:creator>
      <pubDate>Sun, 27 Sep 2026 10:50:26 +0000</pubDate>
      <link>https://dev.to/ookeolioli222/most-security-labs-end-at-the-flag-that-is-how-the-industry-keeps-producing-script-kiddies-n72</link>
      <guid>https://dev.to/ookeolioli222/most-security-labs-end-at-the-flag-that-is-how-the-industry-keeps-producing-script-kiddies-n72</guid>
      <description>&lt;p&gt;"Script kiddie" is a twenty-five-year-old insult for someone who runs exploits they do not understand. It is usually said with contempt. It is more useful to read it as a product description — because it is exactly what most security training is designed to produce, and the design is visible in how the exercises are scored.&lt;/p&gt;

&lt;h2&gt;
  
  
  The exercise ends when the payload works
&lt;/h2&gt;

&lt;p&gt;Open any popular hands-on platform and look at what earns the green tick.&lt;/p&gt;

&lt;p&gt;PortSwigger's Web Security Academy — arguably the best free resource of its kind — marks a lab as &lt;em&gt;solved&lt;/em&gt; the moment the attack goal is reached: the admin panel opens, the other user's data appears, the alert fires (&lt;a href="https://portswigger.net/web-security" rel="noopener noreferrer"&gt;PortSwigger&lt;/a&gt;). Hack The Box machines and TryHackMe rooms are complete when the flag is submitted. Capture-the-flag competitions are complete when the flag is captured; it is in the name. The accompanying reading often has a section on prevention. The &lt;em&gt;graded act&lt;/em&gt; is the exploit. Nobody asks for the diff.&lt;/p&gt;

&lt;p&gt;That is not an accident and it is not malice. CTF grew out of offensive training for penetration testers, and for that audience "make the payload work" is the job. The problem is who ended up using the material: developers, students, career changers — people whose actual job will be to stop the payload, not to launch it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the numbers say about who learns to fix
&lt;/h2&gt;

&lt;p&gt;Three separate bodies of data describe the same gap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The people who write the software were never taught.&lt;/strong&gt; The Linux Foundation and OpenSSF surveyed about 400 development professionals in 2024. Nearly one in three said they were unfamiliar with secure software development practices. Sixty-nine percent said on-the-job experience was their main way of learning it, and the report estimates it takes about five years of that to reach a &lt;em&gt;minimum&lt;/em&gt; level of familiarity. The two most cited obstacles were lack of time (58%) and lack of awareness and training (50%) (&lt;a href="https://openssf.org/press-release/2024/07/17/the-linux-foundation-and-openssf-release-report-on-the-state-of-education-in-secure-software-development/" rel="noopener noreferrer"&gt;OpenSSF, 2024&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Training that grades the fix works — and almost nobody gets it.&lt;/strong&gt; Secure Code Warrior analysed nine years of data from 600 enterprise customers and found that organisations with more than 7,000 developers trained in secure-by-design practices introduced 47–53% fewer vulnerabilities; smaller organisations in the case studies ranged from 20% to 80%. The same analysis estimated that roughly 4% of developers worldwide apply CISA's secure-by-design practices (&lt;a href="https://cyberscoop.com/secure-by-design-return-investment-code-warrior/" rel="noopener noreferrer"&gt;CyberScoop, 2024&lt;/a&gt;). Researchers working with Siemens reached a similar conclusion from the other direction: citing a survey of more than 4,000 developers in which fewer than half could spot a security hole, Gasiba and colleagues proposed a different kind of challenge for industry — one built around &lt;em&gt;entering code&lt;/em&gt; that an automated coach checks, rather than around breaking in (&lt;a href="https://arxiv.org/abs/2101.02108" rel="noopener noreferrer"&gt;Gasiba et al., 2021&lt;/a&gt;). The idea is older still: the "Build It, Break It, Fix It" contest of 2016 added a third phase in which teams had to repair what other teams broke in their code (&lt;a href="https://arxiv.org/abs/1606.01881" rel="noopener noreferrer"&gt;Ruef et al., 2016&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bottleneck is the fix, not the finding.&lt;/strong&gt; Veracode's 2026 State of Software Security reports that 82% of organisations now carry security debt — up 11% in a year — that 60% carry debt classed as critical, and that nearly half of all applications (49%) have flaws that have sat unfixed for more than a year (&lt;a href="https://www.veracode.com/blog/security-debt-crisis/" rel="noopener noreferrer"&gt;Veracode, 2026&lt;/a&gt;). HackerOne's platform data for the year to March 2026 shows submissions up 76% while the number of vulnerabilities actually resolved each month fell by about 46%; the backlog of validated-but-unfixed findings grew more than twenty-fold (&lt;a href="https://www.hackerone.com/blog/finding-fast-fixing-slow-rising-exposure-debt" rel="noopener noreferrer"&gt;HackerOne, May 2026&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Put the three together. The industry is drowning in findings, starving for fixes, has evidence that fix-oriented training cuts vulnerabilities roughly in half — and trains people almost exclusively to find.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two pipelines, one queue
&lt;/h2&gt;

&lt;p&gt;The exploit pipeline rewards the payload. Its graduates can make &lt;code&gt;' OR '1'='1&lt;/code&gt; log them in and cannot say which line to change, because no exercise ever asked. The developer pipeline never showed them the payload at all; they learn security, if they learn it, from the incident five years in.&lt;/p&gt;

&lt;p&gt;The two groups then meet in the same place: a bug bounty queue. One side writes reports without a root cause. The other side cannot act on them. The report says "the payload does something". The maintainer needs "here is the decision that is missing and here is the change that makes it". Neither party was trained to produce that sentence, and the gap between them is where triage time, bounty budgets and maintainer goodwill go to die.&lt;/p&gt;

&lt;p&gt;AI did not create this. It automated the first pipeline. A model can generate a plausible payload and a plausible paragraph about impact at almost no cost; what it cannot reliably generate is the verified root cause, which is the only part that was ever scarce. That is why curl ended its bounty over what its maintainer called "crap and non-well researched reports" (&lt;a href="https://www.theregister.com/security/2026/01/21/curl-shutters-bug-bounty-program-to-stop-ai-slop/5063039" rel="noopener noreferrer"&gt;The Register, Jan 2026&lt;/a&gt;), and why the platforms are now selling &lt;em&gt;validation&lt;/em&gt; as a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  What grading the fix actually changes
&lt;/h2&gt;

&lt;p&gt;A lab that ends at the flag teaches where the payload goes. A lab that ends at a passing test teaches where the &lt;em&gt;decision&lt;/em&gt; lives — and those are different pieces of knowledge.&lt;/p&gt;

&lt;p&gt;The format is simple and the research above describes it in several forms. First the learner exploits the bug, so the failure is concrete. Then they receive the vulnerable handler — the actual function, not a description of it. They change it. The server re-runs the original exploit and a small regression suite against their version. Green means the exploit no longer works &lt;em&gt;and&lt;/em&gt; the legitimate behaviour still does.&lt;/p&gt;

&lt;p&gt;What people learn from that second half is specific: that authorization is a decision about a person, an object and an action, taken next to the object; that a parameterised query is a boundary, not a string trick; that an allow-list belongs at the sink, not in the form. They also learn the thing every reviewer eventually learns — that the fix is often one line, and finding the right line is the whole skill.&lt;/p&gt;

&lt;p&gt;Breachloom was built around that split. Its 81 challenges end at the flag like everyone else's. Its 18 CodeOps missions do not: each hands over the vulnerable code and grades the repair, with the server replaying the attack. Four of them map onto the bugs above:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IDOR — add the ownership decision the invoice endpoint never made: &lt;a href="https://breachloom.com/codeops/owner-bind/" rel="noopener noreferrer"&gt;https://breachloom.com/codeops/owner-bind/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;SQL injection — turn a concatenated query into a parameterised one: &lt;a href="https://breachloom.com/codeops/query-lock/" rel="noopener noreferrer"&gt;https://breachloom.com/codeops/query-lock/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;XSS — fix the sink, not the input: &lt;a href="https://breachloom.com/codeops/dom-guard/" rel="noopener noreferrer"&gt;https://breachloom.com/codeops/dom-guard/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;JWT — verify the signature &lt;em&gt;and&lt;/em&gt; the claims that matter: &lt;a href="https://breachloom.com/codeops/jwt-verify/" rel="noopener noreferrer"&gt;https://breachloom.com/codeops/jwt-verify/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything runs in the browser against fictional applications; nothing to install.&lt;/p&gt;

&lt;h2&gt;
  
  
  The uncomfortable summary
&lt;/h2&gt;

&lt;p&gt;The training industry is very good at producing people who can make an exploit work. The data says the world needs people who can make it stop working, that such people are rare, and that teaching them is measurably possible. Until the green tick moves from the payload to the patch, "script kiddie" will keep being an accurate description of the graduate.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources:&lt;/em&gt; &lt;a href="https://openssf.org/press-release/2024/07/17/the-linux-foundation-and-openssf-release-report-on-the-state-of-education-in-secure-software-development/" rel="noopener noreferrer"&gt;OpenSSF / Linux Foundation — Secure Software Development Education 2024 Survey&lt;/a&gt; · &lt;a href="https://cyberscoop.com/secure-by-design-return-investment-code-warrior/" rel="noopener noreferrer"&gt;CyberScoop — Secure-by-design ROI, Secure Code Warrior data (Oct 2024)&lt;/a&gt; · &lt;a href="https://arxiv.org/abs/2101.02108" rel="noopener noreferrer"&gt;Gasiba, Lechner, Pinto-Albuquerque, Zouitni — Design of Secure Coding Challenges for Cybersecurity Education in the Industry (2021)&lt;/a&gt; · &lt;a href="https://arxiv.org/abs/1606.01881" rel="noopener noreferrer"&gt;Ruef et al. — Build It, Break It, Fix It (CCS 2016)&lt;/a&gt; · &lt;a href="https://www.veracode.com/blog/security-debt-crisis/" rel="noopener noreferrer"&gt;Veracode — 2026 State of Software Security&lt;/a&gt; · &lt;a href="https://www.hackerone.com/blog/finding-fast-fixing-slow-rising-exposure-debt" rel="noopener noreferrer"&gt;HackerOne — Finding Fast, Fixing Slow (May 2026)&lt;/a&gt; · &lt;a href="https://www.theregister.com/security/2026/01/21/curl-shutters-bug-bounty-program-to-stop-ai-slop/5063039" rel="noopener noreferrer"&gt;The Register — curl shutters bug bounty program (Jan 2026)&lt;/a&gt; · &lt;a href="https://portswigger.net/web-security" rel="noopener noreferrer"&gt;PortSwigger Web Security Academy&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>learning</category>
      <category>discuss</category>
    </item>
    <item>
      <title>One year of MCP security: nine in ten public servers have no auth, and the worst bug is from 1988</title>
      <dc:creator>NET_DARK_BOI</dc:creator>
      <pubDate>Sun, 27 Sep 2026 10:28:02 +0000</pubDate>
      <link>https://dev.to/ookeolioli222/one-year-of-mcp-security-nine-in-ten-public-servers-have-no-auth-and-the-worst-bug-is-from-1988-13e8</link>
      <guid>https://dev.to/ookeolioli222/one-year-of-mcp-security-nine-in-ten-public-servers-have-no-auth-and-the-worst-bug-is-from-1988-13e8</guid>
      <description>&lt;p&gt;The Model Context Protocol went from a November 2024 announcement to the default way AI agents reach tools, files and APIs. Within eighteen months it also became the most thoroughly attacked piece of AI infrastructure in existence — not because the protocol invented new bugs, but because it wired a component that cannot tell instructions from data to credentials that can do real damage.&lt;/p&gt;

&lt;p&gt;This is a summary of what the incidents, the CVEs and the security research actually say, with the numbers traced to their sources.&lt;/p&gt;

&lt;h2&gt;
  
  
  The numbers, before the stories
&lt;/h2&gt;

&lt;p&gt;A July 2026 study dynamically audited 414 internet-facing MCP servers out of 640 confirmed production instances. &lt;strong&gt;91.8% had no OAuth authentication.&lt;/strong&gt; The scan found 687 tool instances that expose shell execution without access controls, and 68 reportable vulnerabilities including SQL injection, SSRF against cloud metadata endpoints and path traversal (&lt;a href="https://arxiv.org/abs/2608.00150" rel="noopener noreferrer"&gt;Padilla, &lt;em&gt;Exposed by Design&lt;/em&gt;, arXiv, 2026&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The Cloud Security Alliance's May 2026 research note on what it calls the "MCP security crisis" puts the supply-chain side at roughly 200,000 vulnerable MCP instances behind more than 150 million package downloads, with at least seven confirmed high or critical CVEs by that point, and cites a July 2025 internet scan that found 1,862 publicly reachable servers with no authentication at all (&lt;a href="https://labs.cloudsecurityalliance.org/research/csa-research-note-mcp-security-crisis-20260504-csa-styled/" rel="noopener noreferrer"&gt;CSA, May 2026&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The earliest structured look, from March 2025, tested popular open-source servers and found &lt;strong&gt;43% with command injection, 30% with unrestricted URL fetching (SSRF) and 22% with path traversal&lt;/strong&gt;. The authors' diagnosis was blunt: the protocol "was designed primarily for functionality rather than security" (&lt;a href="https://equixly.com/blog/2025/03/29/mcp-server-new-security-nightmare/" rel="noopener noreferrer"&gt;Equixly, March 2025&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Command injection. SSRF. Path traversal. Missing authentication. None of these are AI vulnerabilities. They are the OWASP list from fifteen years ago, rediscovered inside servers that were written in a weekend to wrap an existing API.&lt;/p&gt;

&lt;h2&gt;
  
  
  What MCP is, in two paragraphs
&lt;/h2&gt;

&lt;p&gt;An MCP client (an IDE, a chat app, an agent runtime) connects to one or more MCP servers. On connect it asks each server for its tools via &lt;code&gt;tools/list&lt;/code&gt;. The server answers with names, descriptions and input schemas, and the client puts those descriptions into the model's context so the model can decide what to call. Servers can run locally as child processes or remotely over HTTP, where OAuth is supposed to handle identity.&lt;/p&gt;

&lt;p&gt;Two consequences follow. First, everything a server sends — tool descriptions included — is &lt;em&gt;input to the model&lt;/em&gt;. Second, whatever credentials the server holds are exercised on behalf of whoever, or whatever, managed to steer the model. Every incident below is one of those two facts meeting a real credential.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five incidents, in the order they taught something
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;April 2025 — attacks before the first tool call.&lt;/strong&gt; Trail of Bits showed that a malicious server can put a prompt-injection payload in a tool description or in its server instructions. Because descriptions enter the context at connection time, the model's behaviour is manipulated before any tool is invoked — which bypasses the human-in-the-loop approval that MCP clients put in front of tool &lt;em&gt;calls&lt;/em&gt;. They named it &lt;em&gt;line jumping&lt;/em&gt;; others call it tool poisoning (&lt;a href="https://blog.trailofbits.com/2025/04/21/jumping-the-line-how-mcp-servers-can-attack-you-before-you-ever-use-them/" rel="noopener noreferrer"&gt;Trail of Bits&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;May 2025 — the public issue that read the private repo.&lt;/strong&gt; Invariant Labs demonstrated a "toxic agent flow" against GitHub's official MCP server: a developer asks an agent to look at open issues in a public repository; one issue, written by a stranger, instructs the agent to read the developer's private repositories and post what it finds in a pull request on the public one. The agent complies. Nothing in the server was broken; the agent simply held a token that could reach private repos and followed text from someone who could not (&lt;a href="https://invariantlabs.ai/blog/mcp-github-vulnerability" rel="noopener noreferrer"&gt;Invariant Labs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;May–June 2025 — the cross-tenant leak with no attacker.&lt;/strong&gt; Asana launched its MCP server on 1 May 2025. A logic bug allowed data from one organisation's Asana instance to surface to MCP users in other organisations, within each user's own access scope, until Asana found it on 4 June. Roughly 1,000 customers were affected; the server was taken offline and restored on 17 June. There is no indication anyone exploited it — it was ordinary multi-tenant isolation failing in a brand-new code path (&lt;a href="https://www.bleepingcomputer.com/news/security/asana-warns-mcp-ai-feature-exposed-customer-data-to-other-orgs/" rel="noopener noreferrer"&gt;BleepingComputer&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;July 2025 — the support ticket that ran SQL.&lt;/strong&gt; General Analysis showed the cleanest version of the pattern. An attacker files a support ticket containing instructions addressed to the assistant. A developer later opens tickets from Cursor through the Supabase MCP server, which runs with the &lt;code&gt;service_role&lt;/code&gt; key — the one that bypasses row-level security. The assistant reads the ticket, treats the embedded text as a task, queries the &lt;code&gt;integration_tokens&lt;/code&gt; table and writes the OAuth secrets back into the ticket, where the attacker reads them (&lt;a href="https://generalanalysis.com/blog/supabase-mcp-blog" rel="noopener noreferrer"&gt;General Analysis&lt;/a&gt;). Simon Willison used it as the textbook case of his &lt;em&gt;lethal trifecta&lt;/em&gt;: private data, untrusted content and a channel out, in one agent (&lt;a href="https://simonwillison.net/2025/Jul/6/supabase-mcp-lethal-trifecta/" rel="noopener noreferrer"&gt;Willison&lt;/a&gt;). Supabase's response was mostly configuration: scope the server to a project, run it read-only, restrict the tool set (&lt;a href="https://supabase.com/blog/defense-in-depth-mcp" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;July 2025 — remote code execution on the developer's laptop.&lt;/strong&gt; JFrog disclosed CVE-2025-6514 (CVSS 9.6) in &lt;code&gt;mcp-remote&lt;/code&gt;, the npm bridge that clients use to reach remote servers. During OAuth discovery a malicious server returns a crafted &lt;code&gt;authorization_endpoint&lt;/code&gt;; the client hands that string to the &lt;code&gt;open&lt;/code&gt; package, and on Windows PowerShell's &lt;code&gt;$()&lt;/code&gt; subexpression turns it into arbitrary command execution. Versions 0.0.5 to 0.1.15 were affected, across more than 437,000 downloads (&lt;a href="https://jfrog.com/blog/2025-6514-critical-mcp-remote-rce-vulnerability/" rel="noopener noreferrer"&gt;JFrog&lt;/a&gt;). It was the first documented case of connecting to an untrusted MCP server leading to code execution on the client machine.&lt;/p&gt;

&lt;p&gt;By 2026 the disclosures had moved up the stack — from individual servers to the SDKs and the package supply chain — which is why the CSA note reads less like a bug list and more like an incident-response plan (&lt;a href="https://labs.cloudsecurityalliance.org/research/csa-research-note-mcp-security-crisis-20260504-csa-styled/" rel="noopener noreferrer"&gt;CSA&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that predates all of this
&lt;/h2&gt;

&lt;p&gt;In 1988 Norm Hardy described a compiler service that could write to the billing file because it charged for compilations, and that also let users name an output file for debug logs. Someone named the billing file. The compiler had the authority; the user did not; the compiler could not tell that the instruction came from someone who should not be giving it. Hardy called it the &lt;em&gt;confused deputy&lt;/em&gt; (&lt;a href="https://dl.acm.org/doi/10.1145/54289.871709" rel="noopener noreferrer"&gt;Hardy, 1988&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Every agent incident above is that compiler. The GitHub agent had the private-repo token and followed an issue. The Cursor assistant had &lt;code&gt;service_role&lt;/code&gt; and followed a ticket. The deputy has the credential; the requester does not; the deputy cannot attribute the instruction.&lt;/p&gt;

&lt;p&gt;The protocol's authors know this. The MCP project's own &lt;em&gt;Security Best Practices&lt;/em&gt; document contains a section literally titled "Confused Deputy Problem" — about proxy servers with a static client ID whose consent cookie lets an attacker skip the consent screen — and its rule is unambiguous: "MCP proxy servers &lt;strong&gt;MUST&lt;/strong&gt; implement per-client consent". The same document forbids &lt;em&gt;token passthrough&lt;/em&gt; ("MCP servers &lt;strong&gt;MUST NOT&lt;/strong&gt; accept any tokens that were not explicitly issued for the MCP server"), warns that OAuth discovery can be pointed at &lt;code&gt;169.254.169.254&lt;/code&gt;, and, on state handles passed back as tool arguments, says "MCP servers &lt;strong&gt;MUST NOT&lt;/strong&gt; treat possession of a state handle as authentication" (&lt;a href="https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices" rel="noopener noreferrer"&gt;MCP Security Best Practices&lt;/a&gt;). That last sentence describes insecure direct object reference — the first bug in every web-security course — written down again for agent builders.&lt;/p&gt;

&lt;p&gt;OWASP has now formalised the list twice. The &lt;strong&gt;OWASP MCP Top 10&lt;/strong&gt; (beta, led by Vandana Verma Sehgal) runs from MCP01 &lt;em&gt;Token Mismanagement &amp;amp; Secret Exposure&lt;/em&gt; through MCP03 &lt;em&gt;Tool Poisoning&lt;/em&gt;, MCP05 &lt;em&gt;Command Injection &amp;amp; Execution&lt;/em&gt;, MCP07 &lt;em&gt;Insufficient Authentication &amp;amp; Authorization&lt;/em&gt; and MCP09 &lt;em&gt;Shadow MCP Servers&lt;/em&gt;, to MCP10 &lt;em&gt;Context Injection &amp;amp; Over-Sharing&lt;/em&gt; (&lt;a href="https://owasp.org/www-project-mcp-top-10/" rel="noopener noreferrer"&gt;OWASP MCP Top 10&lt;/a&gt;). The broader &lt;strong&gt;OWASP Top 10 for Agentic Applications 2026&lt;/strong&gt;, released on 9 December 2025 by more than a hundred contributors, adds the agent-level view: goal hijack, tool misuse, identity and privilege abuse, memory poisoning, rogue agents (&lt;a href="https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/" rel="noopener noreferrer"&gt;OWASP GenAI Security Project&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Two documents, twenty categories, and most of them map onto vulnerabilities with CWE numbers from the 2000s.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the evidence says holds
&lt;/h2&gt;

&lt;p&gt;Reading the incident write-ups and the vendor responses side by side, the effective controls are unglamorous and consistent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authorization at the tool boundary, keyed to the verified principal.&lt;/strong&gt; The check is "may this user perform this action on this object", with the user derived from the token the server validated — never from an argument the model filled in. This is the fix for Asana's cross-tenant bug, for state-handle hijacking, and for the confused deputy in general.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Break the trifecta per session.&lt;/strong&gt; Invariant's mitigation for the GitHub flow was one repository per session; Supabase's was &lt;code&gt;read_only=true&lt;/code&gt; and project scoping. If a session ingests untrusted content, it should not simultaneously hold broad private access and an outbound channel. Least privilege here is not a policy document; it is a token that cannot reach the billing file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Descriptions and fetched content are data, not instructions.&lt;/strong&gt; Trail of Bits shipped &lt;code&gt;mcp-context-protector&lt;/code&gt; as a wrapper that pins tool descriptions and flags changes; the OWASP list has tool poisoning at number three for a reason. Any text a server sends must be treated as evidence for the model, never as policy over it (&lt;a href="https://blog.trailofbits.com/2025/07/28/we-built-the-security-layer-mcp-always-needed/" rel="noopener noreferrer"&gt;Trail of Bits&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do what the spec says about OAuth.&lt;/strong&gt; Per-client consent, audience validation, no token passthrough, HTTPS and private-range blocking for discovery URLs. The mcp-remote CVE lived entirely in the discovery step the spec now warns about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human confirmation for side effects.&lt;/strong&gt; Reading a ticket and sending its contents to an outside address are different permissions. An assistant that can draft the message should not be the component that decides to send it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inventory.&lt;/strong&gt; MCP09 exists because most organisations do not know which servers their developers have connected. The CSA's first recommendation is not a patch; it is a list.&lt;/p&gt;

&lt;h2&gt;
  
  
  The unexciting conclusion
&lt;/h2&gt;

&lt;p&gt;MCP did not create a new class of vulnerability. It created a new &lt;em&gt;distribution channel&lt;/em&gt; for four old ones — command injection, SSRF, missing authorization and the confused deputy — and attached each of them to a component that will follow any well-phrased paragraph. The fixes are the same ones the web learned in the 2000s, applied at the tool boundary instead of the HTTP handler.&lt;/p&gt;

&lt;p&gt;For readers who would rather see the deputy than read about it, Breachloom has three browser exercises and a chain built on exactly these patterns, all against fictional applications with nothing to install:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A tool description that gives orders: &lt;a href="https://breachloom.com/ctf/mcp-poison/" rel="noopener noreferrer"&gt;https://breachloom.com/ctf/mcp-poison/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;A page that re-tasks the assistant summarising it: &lt;a href="https://breachloom.com/ctf/llm-pi-indirect/" rel="noopener noreferrer"&gt;https://breachloom.com/ctf/llm-pi-indirect/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;An agent that may draft an email but must not send it: &lt;a href="https://breachloom.com/ctf/llm-agency-email/" rel="noopener noreferrer"&gt;https://breachloom.com/ctf/llm-agency-email/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The full chain, from injected text to account takeover: &lt;a href="https://breachloom.com/chains/ai-agent-pwn/" rel="noopener noreferrer"&gt;https://breachloom.com/chains/ai-agent-pwn/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hardy's paper is four pages long. Most MCP configurations would benefit from someone reading it and then counting how many billing files the agent can write to.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources:&lt;/em&gt; &lt;a href="https://arxiv.org/abs/2608.00150" rel="noopener noreferrer"&gt;Padilla — Exposed by Design: A Dynamic Security Assessment of Internet-Facing MCP Servers at Scale (arXiv, 2026)&lt;/a&gt; · &lt;a href="https://labs.cloudsecurityalliance.org/research/csa-research-note-mcp-security-crisis-20260504-csa-styled/" rel="noopener noreferrer"&gt;Cloud Security Alliance — MCP Security Crisis research note (May 2026)&lt;/a&gt; · &lt;a href="https://equixly.com/blog/2025/03/29/mcp-server-new-security-nightmare/" rel="noopener noreferrer"&gt;Equixly — MCP Servers: The New Security Nightmare (March 2025)&lt;/a&gt; · &lt;a href="https://blog.trailofbits.com/2025/04/21/jumping-the-line-how-mcp-servers-can-attack-you-before-you-ever-use-them/" rel="noopener noreferrer"&gt;Trail of Bits — Jumping the line (April 2025)&lt;/a&gt; · &lt;a href="https://blog.trailofbits.com/2025/07/28/we-built-the-security-layer-mcp-always-needed/" rel="noopener noreferrer"&gt;Trail of Bits — mcp-context-protector (July 2025)&lt;/a&gt; · &lt;a href="https://invariantlabs.ai/blog/mcp-github-vulnerability" rel="noopener noreferrer"&gt;Invariant Labs — GitHub MCP Exploited (May 2025)&lt;/a&gt; · &lt;a href="https://www.bleepingcomputer.com/news/security/asana-warns-mcp-ai-feature-exposed-customer-data-to-other-orgs/" rel="noopener noreferrer"&gt;BleepingComputer — Asana warns MCP AI feature exposed customer data (June 2025)&lt;/a&gt; · &lt;a href="https://generalanalysis.com/blog/supabase-mcp-blog" rel="noopener noreferrer"&gt;General Analysis — Supabase MCP: how prompt injection leaked private tables (July 2025)&lt;/a&gt; · &lt;a href="https://simonwillison.net/2025/Jul/6/supabase-mcp-lethal-trifecta/" rel="noopener noreferrer"&gt;Simon Willison — Supabase MCP can leak your entire SQL database (July 2025)&lt;/a&gt; · &lt;a href="https://supabase.com/blog/defense-in-depth-mcp" rel="noopener noreferrer"&gt;Supabase — Defense in depth for MCP servers&lt;/a&gt; · &lt;a href="https://jfrog.com/blog/2025-6514-critical-mcp-remote-rce-vulnerability/" rel="noopener noreferrer"&gt;JFrog — CVE-2025-6514 (July 2025)&lt;/a&gt; · &lt;a href="https://modelcontextprotocol.io/docs/tutorials/security/security_best_practices" rel="noopener noreferrer"&gt;Model Context Protocol — Security Best Practices&lt;/a&gt; · &lt;a href="https://owasp.org/www-project-mcp-top-10/" rel="noopener noreferrer"&gt;OWASP MCP Top 10&lt;/a&gt; · &lt;a href="https://genai.owasp.org/resource/owasp-top-10-for-agentic-applications-for-2026/" rel="noopener noreferrer"&gt;OWASP Top 10 for Agentic Applications 2026&lt;/a&gt; · &lt;a href="https://dl.acm.org/doi/10.1145/54289.871709" rel="noopener noreferrer"&gt;Norm Hardy — The Confused Deputy (1988)&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>mcp</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Breachloom audited its own 81 security challenges. Three could only be solved by guessing.</title>
      <dc:creator>NET_DARK_BOI</dc:creator>
      <pubDate>Sun, 27 Sep 2026 10:19:10 +0000</pubDate>
      <link>https://dev.to/ookeolioli222/i-audited-my-own-81-security-challenges-three-could-only-be-solved-by-guessing-12lm</link>
      <guid>https://dev.to/ookeolioli222/i-audited-my-own-81-security-challenges-three-could-only-be-solved-by-guessing-12lm</guid>
      <description>&lt;p&gt;It started on a phone screen, during a routine check of the product. The challenge was called &lt;em&gt;Leaked UUID&lt;/em&gt;. The player gets an invoice, &lt;code&gt;inv-9af3&lt;/code&gt;, and the goal is to read a different customer's invoice. The brief said the other reference "had appeared in a shared-link response". There was no shared-link response in the scene. There was nothing. The player was supposed to just… know it.&lt;/p&gt;

&lt;p&gt;The challenge had been live for weeks. Nobody had flagged it — because a challenge that can only be solved by guessing looks exactly like a hard challenge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three out of eighty-one
&lt;/h2&gt;

&lt;p&gt;That weekend every one of Breachloom's 81 challenges went through the same audit. An AI assistant did the tedious pass — every brief, every hint, every simulated server — answering one question per challenge: &lt;em&gt;where does the player get this value?&lt;/em&gt; If the answer was "from the brief" or "by guessing", the challenge was broken.&lt;/p&gt;

&lt;p&gt;Three were.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Leaked UUID.&lt;/strong&gt; The invoice the player may see is &lt;code&gt;inv-9af3&lt;/code&gt;. The one they are meant to reach is &lt;code&gt;inv-2b7c&lt;/code&gt;. Nothing in the scene ever mentioned &lt;code&gt;inv-2b7c&lt;/code&gt;. Fix: the API response for the player's &lt;em&gt;own&lt;/em&gt; invoice now carries a &lt;code&gt;sharedFrom: "inv-2b7c"&lt;/code&gt; field — the kind of over-sharing a real API produces when the database model is reused as the response model. The player has to notice it, then ask the question that matters: does the server check ownership once the reference is known?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hidden field.&lt;/strong&gt; A GraphQL API with a &lt;code&gt;passwordHash&lt;/code&gt; field the UI never asks for. The player had to request it by name, but introspection was blocked, so there was no way to &lt;em&gt;learn&lt;/em&gt; the name. A lock with the key left outside the room. Fix: the server now does what real GraphQL servers do — answer an unknown field with &lt;code&gt;Did you mean "passwordHash"?&lt;/code&gt;. Field suggestions leak schema even with introspection off; a whole tool, &lt;a href="https://github.com/nikitastupin/clairvoyance" rel="noopener noreferrer"&gt;Clairvoyance&lt;/a&gt;, is built on exactly that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The CEO's project.&lt;/strong&gt; Stage two of a multi-step chain needed a project ID that stage one never showed. Fix: the &lt;code&gt;viewer&lt;/code&gt; query in stage one now returns the list of projects the account can see — and the list leaks &lt;code&gt;prj-ceo&lt;/code&gt; while the detail endpoint still guards it. The oldest access-control bug there is: the list is generous, the detail is strict, and nobody compares them.&lt;/p&gt;

&lt;p&gt;Three out of eighty-one is not a scandal. It is what should be expected from any team that writes challenges without an adversarial pass. The uncomfortable part is how long they stayed live, and why: a guessing game and a hard puzzle produce the same telemetry.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule that came out of it
&lt;/h2&gt;

&lt;p&gt;Every value the player needs must be discoverable inside the scene, by a method that works on real systems.&lt;/p&gt;

&lt;p&gt;Not "in the brief". Not "in the hint". In the scene — a response header, a leaky list endpoint, an error message that says too much, a notification email. If the discovery step does not exist in the wild, it does not go into the challenge.&lt;/p&gt;

&lt;p&gt;That rule sounds obvious. It is not how most security training works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the flag stops
&lt;/h2&gt;

&lt;p&gt;Most labs end when the payload works. Paste &lt;code&gt;' OR '1'='1&lt;/code&gt;, the page shows admin, confetti. The reading material often has a "how to prevent this" section. The &lt;em&gt;exercise&lt;/em&gt; never asks the learner to write it.&lt;/p&gt;

&lt;p&gt;That is not malice; it is lineage. Capture-the-flag was born as offensive training. Gasiba and colleagues, who study security training in industry, cite a survey of more than 4,000 developers in which fewer than half could spot a security hole — and their answer was a new kind of challenge, built around &lt;em&gt;entering code&lt;/em&gt; with an automated coach checking it, rather than around breaking in (&lt;a href="https://arxiv.org/abs/2101.02108" rel="noopener noreferrer"&gt;Gasiba et al., 2021&lt;/a&gt;). The "Build It, Break It, Fix It" contest made the same point a decade ago by adding a phase in which teams fix what others broke in their code (&lt;a href="https://arxiv.org/abs/1606.01881" rel="noopener noreferrer"&gt;Ruef et al., 2016&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Meanwhile the people who ship the software mostly never get taught. The Linux Foundation and OpenSSF surveyed about 400 development professionals in 2024: nearly one in three said they were unfamiliar with secure development practices; 69% learn it on the job, and the report estimates it takes around five years of that to reach a &lt;em&gt;minimum&lt;/em&gt; level of familiarity (&lt;a href="https://openssf.org/press-release/2024/07/17/the-linux-foundation-and-openssf-release-report-on-the-state-of-education-in-secure-software-development/" rel="noopener noreferrer"&gt;OpenSSF, 2024&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So there are two pipelines. One produces people who can make a payload work and cannot say which line to change. The other produces developers who were never shown the payload. The word for the first group used to be &lt;em&gt;script kiddie&lt;/em&gt;. It was an insult. It is more accurately a description of a training design.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same failure, at scale
&lt;/h2&gt;

&lt;p&gt;What is happening to bug bounty programs has the same shape.&lt;/p&gt;

&lt;p&gt;curl shut its bounty at the end of January 2026. Daniel Stenberg's stated reason: "remove the incentive for people to submit crap and non-well researched reports to us. AI generated or not." In one sixteen-hour stretch that month the project received seven reports; none was a vulnerability (&lt;a href="https://www.bleepingcomputer.com/news/security/curl-ending-bug-bounty-program-after-flood-of-ai-slop-reports/" rel="noopener noreferrer"&gt;BleepingComputer&lt;/a&gt;, &lt;a href="https://www.theregister.com/security/2026/01/21/curl-shutters-bug-bounty-program-to-stop-ai-slop/5063039" rel="noopener noreferrer"&gt;The Register&lt;/a&gt;). Elastic's program received more than 1,390 reports in the first half of 2026 — more than 2024 and 2025 combined — and roughly 70% were closed at analysis; the triage team's summary was that LLMs "made it trivially cheap to generate vulnerability reports" (&lt;a href="https://www.elastic.co/security-labs/ai-vulnerability-triage-bug-bounty-hackerone" rel="noopener noreferrer"&gt;Elastic Security Labs&lt;/a&gt;). HackerOne's own numbers: submissions up 76% year over year to a record 46,947 in March 2026, about a quarter confirmed exploitable, and the backlog of &lt;em&gt;validated but unfixed&lt;/em&gt; bugs up more than twenty-fold, because — in HackerOne's words — discovery used to be the bottleneck and now remediation is (&lt;a href="https://www.hackerone.com/blog/finding-fast-fixing-slow-rising-exposure-debt" rel="noopener noreferrer"&gt;HackerOne&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;A report that says "this payload does something" without "here is the decision that is missing and here is the line that makes it" is a script-kiddie report. The industry has now automated writing them. And the one skill that would make the pipeline useful again — understanding the bug well enough to fix it — is the skill almost nobody trains for, because the exercise ends at the flag.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed at Breachloom
&lt;/h2&gt;

&lt;p&gt;Two things.&lt;/p&gt;

&lt;p&gt;Every challenge now has to pass the discoverability rule above. Seventy-eight honest challenges beat eighty-one with three guessing games in a security costume.&lt;/p&gt;

&lt;p&gt;And the part that matters more: after the exploit there is a second half. Eighteen missions hand the learner the vulnerable code; they make the fix, and the server runs its checks against their version. Not "explain the fix" — &lt;em&gt;ship&lt;/em&gt; it, and have it verified. The IDOR mission uses the invoice endpoint from the story above and asks for the decision that was missing. It is harder than the exploit. It is also the only half that would have got any of those 1,390 reports past triage.&lt;/p&gt;

&lt;p&gt;The three fixes are live:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The leaked-UUID invoice, now with a real leak: &lt;a href="https://breachloom.com/ctf/idor-uuid/" rel="noopener noreferrer"&gt;https://breachloom.com/ctf/idor-uuid/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The hidden GraphQL field, now discoverable the way it is in the wild: &lt;a href="https://breachloom.com/ctf/gql-fields/" rel="noopener noreferrer"&gt;https://breachloom.com/ctf/gql-fields/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The chain where the list leaks what the detail protects: &lt;a href="https://breachloom.com/chains/graphql-tenant/" rel="noopener noreferrer"&gt;https://breachloom.com/chains/graphql-tenant/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The fix-the-code half: &lt;a href="https://breachloom.com/codeops/owner-bind/" rel="noopener noreferrer"&gt;https://breachloom.com/codeops/owner-bind/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything runs in the browser against fictional apps. No VM. And anyone who finds a fourth challenge that can only be solved by guessing is invited to report it — that is the kind of bug report worth paying for.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Sources:&lt;/em&gt; &lt;a href="https://arxiv.org/abs/2101.02108" rel="noopener noreferrer"&gt;Gasiba, Lechner, Pinto-Albuquerque, Zouitni — Design of Secure Coding Challenges for Cybersecurity Education in the Industry (2021)&lt;/a&gt; · &lt;a href="https://arxiv.org/abs/1606.01881" rel="noopener noreferrer"&gt;Ruef et al. — Build It, Break It, Fix It (CCS 2016)&lt;/a&gt; · &lt;a href="https://openssf.org/press-release/2024/07/17/the-linux-foundation-and-openssf-release-report-on-the-state-of-education-in-secure-software-development/" rel="noopener noreferrer"&gt;OpenSSF / Linux Foundation — Secure Software Development Education 2024 Survey&lt;/a&gt; · &lt;a href="https://www.bleepingcomputer.com/news/security/curl-ending-bug-bounty-program-after-flood-of-ai-slop-reports/" rel="noopener noreferrer"&gt;BleepingComputer — curl ending bug bounty program&lt;/a&gt; · &lt;a href="https://www.theregister.com/security/2026/01/21/curl-shutters-bug-bounty-program-to-stop-ai-slop/5063039" rel="noopener noreferrer"&gt;The Register — Curl shutters bug bounty program&lt;/a&gt; · &lt;a href="https://www.elastic.co/security-labs/ai-vulnerability-triage-bug-bounty-hackerone" rel="noopener noreferrer"&gt;Elastic Security Labs — AI vulnerability triage&lt;/a&gt; · &lt;a href="https://www.hackerone.com/blog/finding-fast-fixing-slow-rising-exposure-debt" rel="noopener noreferrer"&gt;HackerOne — Finding Fast, Fixing Slow&lt;/a&gt; · &lt;a href="https://github.com/nikitastupin/clairvoyance" rel="noopener noreferrer"&gt;Clairvoyance&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>discuss</category>
      <category>ctf</category>
    </item>
  </channel>
</rss>
