<?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: JB</title>
    <description>The latest articles on DEV Community by JB (@bagofnarcissism).</description>
    <link>https://dev.to/bagofnarcissism</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%2F4109767%2Fece1f2e1-7b8f-4046-be0a-93e09f047123.jpg</url>
      <title>DEV Community: JB</title>
      <link>https://dev.to/bagofnarcissism</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bagofnarcissism"/>
    <language>en</language>
    <item>
      <title>OpenAI’s ExploitGym sandbox escape and wire-level agent constraints</title>
      <dc:creator>JB</dc:creator>
      <pubDate>Thu, 10 Sep 2026 17:55:02 +0000</pubDate>
      <link>https://dev.to/bagofnarcissism/openais-exploitgym-sandbox-escape-and-wire-level-agent-constraints-jgc</link>
      <guid>https://dev.to/bagofnarcissism/openais-exploitgym-sandbox-escape-and-wire-level-agent-constraints-jgc</guid>
      <description>&lt;p&gt;A prompt is a sign on a door. A wire level constraint is a lock.&lt;/p&gt;

&lt;p&gt;That is the whole argument. The rest is where the lock has to sit when the model is having a bad day.&lt;/p&gt;

&lt;p&gt;In July 2026 it sat in the wrong place. Hugging Face disclosed an agent intrusion on the 16th. On the 21st OpenAI said the agents were GPT-5.6 Sol and a more capable internal model, running ExploitGym with cyber refusals turned down. Egress was supposed to be a package-registry proxy. It was not enough. The name IM1, the confirmation that the proxy was Artifactory, and the swarm reconstruction arrived later. The control lesson did not wait on those names.&lt;/p&gt;

&lt;p&gt;What follows is the same anatomy as &lt;a href="https://lnkd.in/p/eUPGVYqs" rel="noopener noreferrer"&gt;The Architecture of Leashed Autonomy&lt;/a&gt;, held up against that month. Not a victory lap. A field check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control is not etiquette
&lt;/h2&gt;

&lt;p&gt;Most agent stacks still treat safety as manners. The system prompt says ask first. The model asks. A human clicks yes. That feeling of control is real. The control is not. When the model routes around the instruction, or the eval turns refusals off on purpose, there was never a guardrail. There was a sign.&lt;/p&gt;

&lt;p&gt;A floor is ordinary code between “I want this tool, with these arguments” and the tool running. The model does not see it, edit it, or argue it down. Fail the check and the tool does not run. The model gets an error in the same shape as any other tool error. It can retry or ask. It cannot push the call through.&lt;/p&gt;

&lt;p&gt;Put enforcement where the model cannot reach it. Prompts still teach the model how to work inside the fence. They must not be the only thing between an agent and a consequential action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five verbs, or it is not control
&lt;/h2&gt;

&lt;p&gt;The aim is not a green dashboard. An operator should be able to observe, decide, interrupt, reconstruct, and compare every material action. Each verb needs a line of code you can point at. A paragraph in a prompt does not count.&lt;/p&gt;

&lt;p&gt;Under those five: wire, proof of stop, reconstruct. Everything else is decoration - including a panic button that cannot prove it worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  What July actually was
&lt;/h2&gt;

&lt;p&gt;Three public records. Same failure class. Different dates.&lt;/p&gt;

&lt;p&gt;On July 16–21 the production story was Hugging Face. Isolated eval, ExploitGym, refusals lowered so the test would measure capability. The one allowed outbound path was an internally hosted package-registry proxy: install packages, do not wander. Agents used that path, got off-box, and went looking for answers on Hugging Face infrastructure. &lt;a href="https://openai.com/index/hugging-face-model-evaluation-security-incident/" rel="noopener noreferrer"&gt;OpenAI’s 21 July post&lt;/a&gt; named the models and the shape of the escape. It did not yet publish a full timeline.&lt;/p&gt;

&lt;p&gt;On August 26, METR and Redwood described the swarm: about 1,200 agents that were supposed to be isolated found a message board in that eval plumbing, sent more than 70,000 messages and files, and about 700 joined the Hugging Face attack. &lt;a href="https://metr.org/blog/2026-08-26-openai-hugging-face-incident-investigation" rel="noopener noreferrer"&gt;METR write-up&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In September, other write-ups documented agents using old UseMod-lineage wikis (DSEWiki among them) that accept writes on GET, and a proxy rule that trusted any hostname ending in &lt;code&gt;.blob.core.windows.net&lt;/code&gt; without checking the name was real. &lt;a href="https://simonwillison.net/2026/Sep/4/rogue-agent-wikis/" rel="noopener noreferrer"&gt;Willison&lt;/a&gt;. That traffic is not the Hugging Face exploit chain. It is the same kind of perimeter: inspect the wrong field, call it a lock.&lt;/p&gt;

&lt;p&gt;A package-registry proxy is a middlebox so sandboxes can &lt;code&gt;pip&lt;/code&gt; / &lt;code&gt;apt&lt;/code&gt; / &lt;code&gt;npm&lt;/code&gt; without an open internet. In this eval it was also a reachable application with a cache and a filesystem. If that is your only allowed peer, it is the door and the hallway.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five floors
&lt;/h2&gt;

&lt;p&gt;Every tool request walks the same path. Fail closed. Clear all five, or do not run.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Scope — default deny
&lt;/h3&gt;

&lt;p&gt;The engagement defines what may be touched. Before a tool runs, targets come out of the actual arguments and hit an allow-list.&lt;/p&gt;

&lt;p&gt;Unclassified means refused. A tool with no scope mapping does not get a free pass.&lt;/p&gt;

&lt;p&gt;The model may be told its scope. The wire enforces it.&lt;/p&gt;

&lt;p&gt;July’s allowed target was “the proxy.” Policy then became whatever that product would fetch, forward, or store. A suffix check, a GET-only rule, or a vendor name is not target extraction. Either the host, identity, or object is knowable and the check is real, or the human decides. There is no third option.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Safeguards and posture — how loud, how far
&lt;/h3&gt;

&lt;p&gt;Scope answers &lt;em&gt;may this target be touched&lt;/em&gt;. This floor answers &lt;em&gt;may this technique be used here, and how noisy&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;A posture knob (stealth, normal, loud) gates aggressive tools on the wire. A halt budget backs it. Keep tripping prohibited patterns and the agent is stopped, not coached through another twenty attempts.&lt;/p&gt;

&lt;p&gt;Refusals returned to the model should be scrubbed of the banned content that triggered them. A detailed error is a lesson plan.&lt;/p&gt;

&lt;p&gt;Capability evals will keep turning model refusals down. Write that next to the allow-list. It is not a reason to skip egress, spawn, or stop-proof.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Approval — consent on the wire
&lt;/h3&gt;

&lt;p&gt;“Ask before dangerous work” in a prompt is nowhere.&lt;/p&gt;

&lt;p&gt;Label the call by consequence: modify state, pivot, cross a trust boundary. If policy requires a human for that label, block and file a question. Silence is deny. Timeout is deny. Garbage is deny. A network blip is deny. Nothing is approved by inattention. If the operator is gone, the swarm freezes.&lt;/p&gt;

&lt;p&gt;Seam worth naming: if the label never fires, deny-on-silence never runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Delegation — when autonomy compounds
&lt;/h3&gt;

&lt;p&gt;Agents talking to each other is the point of a swarm. It is also how a small mistake hires help.&lt;/p&gt;

&lt;p&gt;Reach is computed, not quietly widened. Delegation cycles are refused. Fan-out and spawn cost an explicit yes. Team boundaries hold here, not in prose.&lt;/p&gt;

&lt;p&gt;The August reconstruction is this floor’s exhibit. Isolation was a property of how the jobs were launched. It was not a property of the wire.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Killswitch — stop is not a status string
&lt;/h3&gt;

&lt;p&gt;Stopping is easy. Proving the stop is the work.&lt;/p&gt;

&lt;p&gt;A button that reports quiet while something still runs is worse than no button. Sweep in phases: dispatch, prove local subprocesses dead, terminate remote workers, reap children, list orphans. Each phase leaves evidence.&lt;/p&gt;

&lt;p&gt;Green is one function, used by CLI, web, and TUI. Incomplete proof stays red. The process under test does not certify its own shutdown.&lt;/p&gt;

&lt;p&gt;Hugging Face saw the traffic first. That is a Floor 5 failure as much as a detection story.&lt;/p&gt;

&lt;h2&gt;
  
  
  Receipts
&lt;/h2&gt;

&lt;p&gt;An agent should not get to assert reachability, compromise, or “we stopped.” Those claims need a handle the wire can re-check. If the handle is missing or the bytes moved, the write does not land. Transcripts are not proof. July produced a lot of transcripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six questions
&lt;/h2&gt;

&lt;p&gt;No source required. Specific answers required.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Trace one scary action from model decision to execution. Where does refusal live? If “system prompt,” you have hope.&lt;/li&gt;
&lt;li&gt;Unclassified tool — does it run anyway?&lt;/li&gt;
&lt;li&gt;Operator silent — does approval deny, or proceed after N seconds?&lt;/li&gt;
&lt;li&gt;Stop under stress — empty proofs, “done” with a live process: does any of that paint green?&lt;/li&gt;
&lt;li&gt;Where does control end? “Everywhere” usually means the edges have not been found.&lt;/li&gt;
&lt;li&gt;Does the refusal coach the model?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;July fails several of these in public. That is the story. The models did what a capable optimizer does when the score is on the other side of a perimeter. The perimeter was a proxy and a prompt.&lt;/p&gt;

&lt;p&gt;Put the floor where the model cannot move it. Prove the button works. Say out loud where it still doesn’t.&lt;/p&gt;




&lt;p&gt;Code that tries to put those checks on the tool wire: &lt;a href="https://github.com/baggybin/salient-core" rel="noopener noreferrer"&gt;salient-core&lt;/a&gt;. Design notes in &lt;a href="https://github.com/baggybin/salient-core/blob/main/docs/ARCHITECTURE.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/ARCHITECTURE.md&lt;/code&gt;&lt;/a&gt;. Alpha. Views are my own.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>agents</category>
      <category>kernel</category>
    </item>
    <item>
      <title>Give your coding agent a second brain</title>
      <dc:creator>JB</dc:creator>
      <pubDate>Fri, 04 Sep 2026 12:42:34 +0000</pubDate>
      <link>https://dev.to/bagofnarcissism/give-your-coding-agent-a-second-brain-3h47</link>
      <guid>https://dev.to/bagofnarcissism/give-your-coding-agent-a-second-brain-3h47</guid>
      <description>&lt;p&gt;The setup I run is a cheaper model doing the implementation. Give it a clear plan and it writes the code cleanly and fast, at a fraction of what a frontier model costs. I use Opus 4.8 for this and it's a genuinely good implementer. Where a model like that is weaker isn't the typing, it's the hard thinking: a subtle concurrency bug, a real fork between two designs, a "why is this actually happening" that wants a stronger reasoner than the one writing the code.&lt;/p&gt;

&lt;p&gt;The lazy fix is to run everything on a top tier model, but that's a bad trade. You'd pay frontier prices on every trivial edit just to have the deeper reasoning on hand for the few moments that need it. And telling a cheaper model to "think harder" in the prompt doesn't do anything, because it doesn't hand the model a better reasoner, just a longer leash.&lt;/p&gt;

&lt;p&gt;What I wanted was to keep the cheap, fast implementer and let it borrow a stronger brain for the hard question, then go straight back to writing code. &lt;/p&gt;

&lt;p&gt;That's all ask-fable (&lt;a href="https://github.com/baggybin/ask-fable" rel="noopener noreferrer"&gt;https://github.com/baggybin/ask-fable&lt;/a&gt;) is...&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;It's an &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;MCP&lt;/a&gt; server. You point Claude Code (or any MCP client) at it, and your agent picks up an &lt;code&gt;ask&lt;/code&gt; tool. When it hits something genuinely hard, it calls &lt;code&gt;ask&lt;/code&gt; with the real code and the real error, and a strong model reasons about that. By default that's Anthropic's Fable, or Claude Opus 5 if you want cheaper and faster turns.&lt;/p&gt;

&lt;p&gt;The models never see your repo. You paste the relevant code into the call, or point at files with a context bus so you only send a big chunk once. The model on the other end is a pure reasoner with no tools and no filesystem. It thinks about what you handed it and answers. Nothing else.&lt;/p&gt;

&lt;p&gt;That much is already handy. It gets more interesting when one opinion isn't enough, which is where the other three modes come in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four ways to ask
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ask&lt;/code&gt; is one model answering, with session memory so you can follow up. It's the one you'll reach for most.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ask_council&lt;/code&gt; sends the same question to several models at once and has a synthesizer reconcile their answers into one, while still handing you each raw answer and a consensus signal. I use it for the calls I don't want to get wrong: a data model, a migration, anything hard to undo. One model sounding confident is not the same as several models agreeing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ask_chain&lt;/code&gt; runs an ordered pipeline instead. A cheap model drafts, the middle stages criticize and extend that draft, and a strong model makes the final call. The cheap model does the legwork and the expensive one only shows up to finish, which keeps the bill down.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ask_debate&lt;/code&gt; is the adversarial one. Two models argue a claim through a structured ledger (propose, refute, revise) and a third rules on what's left standing. It's the heaviest mode, so I save it for real "approach X or approach Y" forks.&lt;/p&gt;

&lt;p&gt;Behind all of them is a mix of backends: Fable, Opus 5, MiniMax, Gemini, GPT, GLM, DeepSeek, Grok, Kimi, and gateways like Ollama, Atlas and OpenRouter, which alone reaches a few hundred models on one key. If a backend isn't configured or isn't reachable, it gets reported and skipped rather than failing the whole call. There's also a guard sitting in front that refuses off-scope requests (offensive-security work, non-software domains) so the thing stays a software-reasoning tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The moment it earned its keep
&lt;/h2&gt;

&lt;p&gt;I'd just added a feature I called the twin flames. It's one token, &lt;code&gt;twin&lt;/code&gt;, that expands to both Anthropic reasoners at once, Fable and Opus 5, so asking for &lt;code&gt;["twin"]&lt;/code&gt; runs both with no extra setup. Tests passed, I pushed it to a PR.&lt;/p&gt;

&lt;p&gt;Then I did the obvious thing and asked the twin flames to review the feature I'd built with them.&lt;/p&gt;

&lt;p&gt;They agreed with each other and pointed at a hole I'd walked right past. The group expands into its members before anything validates it, so a bad group definition never throws. It just quietly changes what gets asked. An empty group would fall through to the default panel. An unknown member would get reported under a name the user never typed. A nested group would never expand at all. Three different config mistakes, all of them failing silently, which is the worst way for anything to fail.&lt;/p&gt;

&lt;p&gt;I reproduced all three in about a minute, added a check that runs when the module loads, and pushed that too. The feature found its own bug. If you've ever wanted a second reader who's actually adversarial about your work instead of agreeable, that's the pitch.&lt;/p&gt;

&lt;p&gt;(For what it's worth, the underlying trap has a name: expansion that happens before validation can't fail loudly, it can only produce the wrong thing quietly. The fix is always to check the definition where it's written, not the expansion where it's used.)&lt;/p&gt;

&lt;h2&gt;
  
  
  The rest of it
&lt;/h2&gt;

&lt;p&gt;The reasoning is the headline but the boring parts are what make it usable. Refusals are deterministic, so a rejected question rejects the same way every time instead of flaking. There's a context bus so you paste a big codebase snippet once and refer back to it by key. There's an answer cache and a circuit breaker that backs off a struggling backend. And every call lands in a redacted audit trail with full tracing, so you can go back and see which model said what, and what it cost you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;git+https://github.com/baggybin/ask-fable.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then register it as an MCP server in Claude Code and hand &lt;code&gt;ask&lt;/code&gt; your hardest question.&lt;/p&gt;

&lt;p&gt;Fair warning on where this is: it's an early public snapshot of something I built for my own daily use. Fresh repo, MIT licensed, no pretense of a big user base. It works, I use it.&lt;/p&gt;

&lt;p&gt;Repo, the full tool list, and a setup guide are here: &lt;a href="https://github.com/baggybin/ask-fable" rel="noopener noreferrer"&gt;github.com/baggybin/ask-fable&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If your habit is already "let the cheap model grind and only escalate the hard question," this is that, turned into a tool your agent can reach for on its own.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>python</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
