<?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: Walden Wu</title>
    <description>The latest articles on DEV Community by Walden Wu (@waldenwuwei).</description>
    <link>https://dev.to/waldenwuwei</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%2F4128894%2Fc1d439b2-c45a-4bd9-a0f0-8e19c88f6d75.jpg</url>
      <title>DEV Community: Walden Wu</title>
      <link>https://dev.to/waldenwuwei</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/waldenwuwei"/>
    <language>en</language>
    <item>
      <title>I counted what my AI agents can actually touch: 16 MCP servers, 164 tools, 15 denials</title>
      <dc:creator>Walden Wu</dc:creator>
      <pubDate>Sat, 19 Sep 2026 13:12:41 +0000</pubDate>
      <link>https://dev.to/waldenwuwei/i-counted-what-my-ai-agents-can-actually-touch-16-mcp-servers-164-tools-15-denials-1lmj</link>
      <guid>https://dev.to/waldenwuwei/i-counted-what-my-ai-agents-can-actually-touch-16-mcp-servers-164-tools-15-denials-1lmj</guid>
      <description>&lt;p&gt;Every AI project eventually hits the same question, and it never comes from engineering:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What can your agent actually touch?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Usually it is a client, an auditor, or whoever signs the risk acceptance. And the honest answer is often "let me get back to you", because the answer is spread across a dozen config files and a tool list nobody ever wrote down.&lt;/p&gt;

&lt;p&gt;I run several agent harnesses on one laptop. So I measured it, on my own machine, and I am writing down the procedure — including the parts that did not work.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Enumerate, don't describe
&lt;/h2&gt;

&lt;p&gt;The install instructions are the source of truth, and almost all of them say &lt;code&gt;npx -y &amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;On my machine: &lt;strong&gt;16 MCP servers&lt;/strong&gt;, of which &lt;strong&gt;12 are launched through a package manager&lt;/strong&gt;, and &lt;strong&gt;all 12 are unpinned&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The split matters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;4 say &lt;code&gt;@latest&lt;/code&gt; outright&lt;/strong&gt; — &lt;code&gt;chrome-devtools-mcp@latest&lt;/code&gt;, &lt;code&gt;@playwright/mcp@latest&lt;/code&gt;, &lt;code&gt;@upstash/context7-mcp@latest&lt;/code&gt;, &lt;code&gt;superpowers-mcp@latest&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8 have no version string at all&lt;/strong&gt; — &lt;code&gt;firecrawl-mcp&lt;/code&gt;, &lt;code&gt;mcp-server-kubernetes&lt;/code&gt;, &lt;code&gt;gitee-mcp-server&lt;/code&gt;, &lt;code&gt;@edjl/docker-mcp&lt;/code&gt;, and so on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The second group surprised me more. &lt;code&gt;@latest&lt;/code&gt; at least reads like a moving target. A bare package name reads like a stable dependency, and it resolves exactly the same way: whatever landed on the registry this morning.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Ask the servers what they expose
&lt;/h2&gt;

&lt;p&gt;Listing tools is a separate step, because it starts processes. That distinction matters: a config parse executes nothing, an enumeration executes the servers.&lt;/p&gt;

&lt;p&gt;That step produced &lt;strong&gt;164 tools across 12 servers&lt;/strong&gt;. The distribution was not what I expected: chrome-devtools 29, firecrawl 25, playwright 25, kubernetes 23, gitee 20, filesystem 14, docker 10, memory 9.&lt;/p&gt;

&lt;p&gt;Kubernetes and Docker are what made the number real for me. A browser automation tool with 25 tools is one thing. &lt;code&gt;kubectl&lt;/code&gt; verbs are another.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Compile a policy, with reasons
&lt;/h2&gt;

&lt;p&gt;I compiled the inventory into a three-state policy: &lt;strong&gt;allow 43, approve 106, deny 15&lt;/strong&gt;, plus 24 that came back as "the capability cannot be inferred" — those get flagged for a human instead of decided.&lt;/p&gt;

&lt;p&gt;The 15 denials are the obvious ones: &lt;code&gt;kubectl_delete&lt;/code&gt;, &lt;code&gt;uninstall_helm_chart&lt;/code&gt;, &lt;code&gt;docker_remove&lt;/code&gt;, three &lt;code&gt;delete_*&lt;/code&gt; tools in the memory server, &lt;code&gt;node_repl js&lt;/code&gt;, &lt;code&gt;js_reset&lt;/code&gt;, &lt;code&gt;playwright browser_drop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The part that makes this worth writing about: the classifier is a keyword matcher, so it is wrong in both directions. It denied &lt;code&gt;resolve-library-id&lt;/code&gt; because its description matched the word "format". It denied &lt;code&gt;sequentialthinking&lt;/code&gt; because the description contained "clear".&lt;/p&gt;

&lt;p&gt;Both stay visible in the output. Every verdict prints the phrase it matched. A policy that hides its reasons cannot be corrected — including by the person who wrote it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Hand over something someone else can check
&lt;/h2&gt;

&lt;p&gt;The last step is the one people skip, because it feels like extra work: shipping a delivery that the recipient verifies &lt;strong&gt;without trusting the sender&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Hashes are recomputed on the reader's side — in a browser, or with a standard-library script. Not because I expect to be distrusted, but because "trust me" does not survive being asked a second time by someone who does not know you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I am not claiming
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Unpinned is not the same as compromised. The point is narrower: you cannot tell the difference after the fact, because the artifact you approved is not the artifact that runs next time.&lt;/li&gt;
&lt;li&gt;Name and description matching says nothing about implementation. It ranks what to review; it does not review it.&lt;/li&gt;
&lt;li&gt;Four of my 16 servers could not be enumerated at all (one exits on initialize, three are remote and time out). I still do not know what they expose. &lt;strong&gt;Unknown is not safe&lt;/strong&gt;, and nothing in my setup was telling me the difference.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Do it on your own machine
&lt;/h2&gt;

&lt;p&gt;Two numbers are enough to start: how many servers, how many pinned. If the second one is zero, you are in the same place I was this morning.&lt;/p&gt;

&lt;p&gt;I wrote the tool I used for this — read-only, and it executes nothing until you explicitly ask it to enumerate: &lt;a href="https://podcloud.dlszjr.com" rel="noopener noreferrer"&gt;https://podcloud.dlszjr.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>mcp</category>
      <category>tools</category>
    </item>
    <item>
      <title>Agent forensics in five minutes: what it did, and proof the log wasn't edited</title>
      <dc:creator>Walden Wu</dc:creator>
      <pubDate>Fri, 18 Sep 2026 02:42:40 +0000</pubDate>
      <link>https://dev.to/waldenwuwei/agent-forensics-in-five-minutes-what-it-did-and-proof-the-log-wasnt-edited-31kb</link>
      <guid>https://dev.to/waldenwuwei/agent-forensics-in-five-minutes-what-it-did-and-proof-the-log-wasnt-edited-31kb</guid>
      <description>&lt;p&gt;An agent incident ends one of two ways.&lt;/p&gt;

&lt;p&gt;Without an audit trail, you guess: what it did, when, with which arguments, under which policy, approved by whom. With a hash-chained audit, five minutes gets you a full timeline — and a way to show the timeline itself hasn't been edited since.&lt;/p&gt;

&lt;p&gt;I run agents on my own laptop (Claude Code, Cursor, OpenClaw, DSH), so I've had this happen. Here is the whole runbook, on real output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The script: 01:40, an agent tries to delete a file
&lt;/h2&gt;

&lt;p&gt;A real call from my machine — the policy stopped it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pod timeline &lt;span class="nt"&gt;--since&lt;/span&gt; 2h &lt;span class="nt"&gt;--tool&lt;/span&gt; delete_file
&lt;span class="go"&gt;
2026-09-01 01:40:08  [deny   ] delete_file        filesystem   agent=openclaw-main  args=cb1533f3… pol=0.1.0  reason=tool "delete_file" is denied on "filesystem"  blocked
~~~

Three seconds to read three things: **who** (openclaw-main), **what it wanted to do** (delete_file), **why it didn't happen** (policy deny, with the policy version in force at that moment — 0.1.0).

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="c"&gt;# Step 1 — replay the timeline (2 minutes)&lt;/span&gt;
&lt;span class="go"&gt;
~~~bash
&lt;/span&gt;&lt;span class="gp"&gt;pod timeline --since 2h                       #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;everything, last two hours
&lt;span class="gp"&gt;pod timeline --since 24h --tool write_file    #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;only writes
&lt;span class="gp"&gt;pod timeline --agent openclaw-main            #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;only one agent
&lt;span class="go"&gt;~~~

Every row carries: time / tool / server / agent / **argument hash** / decision / outcome / approver / **the policy version in force**. Arguments are stored as hashes only — during an incident that means you don't leak more than you have to, while still being able to prove the call happened.

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="c"&gt;# Step 2 — prove the log wasn't touched (1 minute)&lt;/span&gt;
&lt;span class="go"&gt;
~~~plaintext
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pod verify-audit
&lt;span class="go"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="c"&gt;# openclaw-main/filesystem.jsonl&lt;/span&gt;
&lt;span class="go"&gt;- status: hash chain intact
- entries: 8
- head hash: f7a56fb90350…
- tail hash: 06f31666c4e8…

conclusion: every record verifiable, none modified
~~~

Each record locks in the hash of the one before it. Edit any historical entry and verification fails immediately. That's a stronger claim than "the log shows it": it's "the log itself wasn't edited after the fact".

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="c"&gt;# Step 3 — package the evidence (2 minutes)&lt;/span&gt;
&lt;span class="go"&gt;
~~~console
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pod export-evidence
&lt;span class="go"&gt;evidence bundle: ~/.pod/evidence/pod-evidence-2026-09-01.json
  audit files: 1 | policy snapshots: 1
  top-level hash: 9f9e6ed35ed1c0b9…
  one-page report: ~/.pod/evidence/pod-evidence-2026-09-01.json.md

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pod verify-evidence &lt;span class="nt"&gt;--out&lt;/span&gt; ~/.pod/evidence/pod-evidence-2026-09-01.json
&lt;span class="go"&gt;✅ evidence bundle valid (top-level hash matches, nothing modified)
~~~

One file to hand over: the audit, **the policy snapshot that was in force**, the self-check result, and a top-level hash. Whoever receives it can verify the bundle themselves — no need to trust your machine.

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="c"&gt;# Optional: cross-agent evidence in the cloud&lt;/span&gt;
&lt;span class="go"&gt;
~~~bash
pod sync
~~~

The control plane lives in the same Apache-2.0 repo (`bash deploy/install.sh`). The data direction is one-way: only SHA-256 hashes go up, the audit text stays on your machine, and the cloud being down doesn't affect local enforcement.

&lt;/span&gt;&lt;span class="gp"&gt;#&lt;/span&gt;&lt;span class="c"&gt;# Why a hash chain instead of plain logs&lt;/span&gt;
&lt;span class="go"&gt;
| | Plain log | pod hash-chained audit |
|---|---|---|
| Records the call | yes | yes |
| Records raw arguments | yes (that's the leak surface) | no — hash only |
| Records the policy version in force | no | yes |
| Provable that nobody edited it | no — anyone can edit a log | yes — one edit breaks the chain |
| Portable, verifiable evidence bundle | no | yes |

Not every agent behaves. The audit's job is narrower and more useful: what it did, it can't deny.

Install (v0.3.2):

~~~bash
curl -fsSL https://gitee.com/suhuisoftwares/pod/raw/v0.3.2/scripts/install.sh | sh
pod init --template baseline
&lt;/span&gt;&lt;span class="gp"&gt;pod serve --agent &amp;lt;name&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--server&lt;/span&gt; &amp;lt;name&amp;gt; &lt;span class="nt"&gt;--policy&lt;/span&gt; ~/.pod/policies/baseline.json &lt;span class="nt"&gt;--command&lt;/span&gt; &amp;lt;cmd&amp;gt;
&lt;span class="go"&gt;~~~
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
      <category>mcp</category>
    </item>
    <item>
      <title>The OWASP Agentic AI Top 10, read as someone who actually runs agents on his laptop</title>
      <dc:creator>Walden Wu</dc:creator>
      <pubDate>Thu, 17 Sep 2026 11:49:18 +0000</pubDate>
      <link>https://dev.to/waldenwuwei/the-owasp-agentic-ai-top-10-read-as-someone-who-actually-runs-agents-on-his-laptop-3ob1</link>
      <guid>https://dev.to/waldenwuwei/the-owasp-agentic-ai-top-10-read-as-someone-who-actually-runs-agents-on-his-laptop-3ob1</guid>
      <description>&lt;p&gt;OWASP published a Top 10 for Agentic Applications in December 2025 — ten risks, numbered ASI01 through ASI10.&lt;/p&gt;

&lt;p&gt;Most write-ups I found target platform teams. I don't have one. I have a laptop, six agents (Claude Code, Cursor, OpenClaw, DSH and a couple of my own scripts), 14 MCP servers — and a habit of giving them shell access. So here is each item translated into what I actually changed.&lt;/p&gt;

&lt;h2&gt;
  
  
  ASI01 — Agent Goal Hijack
&lt;/h2&gt;

&lt;p&gt;Prompt injection, but the version that matters in practice is indirect: the instruction sits in a file, a web page or an issue your agent reads on the way to doing its job. The gate goes on the tool call, not the text: unauthorized tools rejected, reads of &lt;code&gt;~/.ssh&lt;/code&gt; and &lt;code&gt;.env&lt;/code&gt; denied, and a list of injection signal phrases that land in the audit log.&lt;/p&gt;

&lt;h2&gt;
  
  
  ASI02 — Tool Misuse
&lt;/h2&gt;

&lt;p&gt;The tool is fine; the arguments aren't. A delete that resolved to a root path, a write that landed on production. Judge by capability, not by tool name: the same filesystem tool is fine reading a project and not fine writing to &lt;code&gt;/etc&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  ASI03 — Identity &amp;amp; Privilege Abuse
&lt;/h2&gt;

&lt;p&gt;Every agent sharing one token means that when something happens, you can't say which agent did it. Each agent gets its own keypair, and delegations narrow hop by hop: a downstream agent only ever gets a subset of what the upstream one had.&lt;/p&gt;

&lt;h2&gt;
  
  
  ASI04 — Agentic Supply Chain
&lt;/h2&gt;

&lt;p&gt;This is the one that actually bit me. 12 of my 14 MCP servers were running unpinned — &lt;code&gt;npx -y pkg@latest&lt;/code&gt; means the next launch runs whatever someone uploaded last. Pin versions, check MCP server origins against an allowlist at startup, and audit lifecycle hooks: a plugin "update" is a great place to hang a command that runs on every session.&lt;/p&gt;

&lt;h2&gt;
  
  
  ASI05 — Unexpected Code Execution
&lt;/h2&gt;

&lt;p&gt;Anything that can execute code will eventually execute something you didn't intend. Mark exec capabilities separately, require approval, snapshot before execution so there is something to roll back to.&lt;/p&gt;

&lt;h2&gt;
  
  
  ASI06 — Memory &amp;amp; Context Poisoning
&lt;/h2&gt;

&lt;p&gt;Content written into &lt;code&gt;CLAUDE.md&lt;/code&gt;, &lt;code&gt;AGENTS.md&lt;/code&gt; or a memory directory doesn't affect this session — it affects every future one. Treat memory as untrusted input. The minimum bar is noticing when it changes, which means a baseline and drift detection.&lt;/p&gt;

&lt;h2&gt;
  
  
  ASI07 — Insecure Inter-Agent Communication
&lt;/h2&gt;

&lt;p&gt;The hard one. Cross-org A2A identity is a protocol problem and I don't pretend to solve it. What I can pin down locally: signed delegation chains, capability narrowing, one keypair per agent, so "who authorized whom" has an answer. I'd rather a security tool say plainly what it does not cover than quietly imply it covers everything.&lt;/p&gt;

&lt;h2&gt;
  
  
  ASI08 — Cascading Failures
&lt;/h2&gt;

&lt;p&gt;One poisoned agent talks to five downstream agents, and each of them trusts the message because it came from inside. Circuit breakers that take effect on the next call, plus anomaly detection for delegation bursts.&lt;/p&gt;

&lt;h2&gt;
  
  
  ASI09 — Human-Agent Trust Exploitation
&lt;/h2&gt;

&lt;p&gt;The agent writes a confident, plausible explanation for a destructive action and you click approve. "Read more carefully" is not a fix. Approval prompts should show the concrete action and its parameters, not the agent's own narrative; approvals should record who approved and why; a timeout should fail closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  ASI10 — Rogue Agents
&lt;/h2&gt;

&lt;p&gt;You cannot detect intent. Put the boundary on actions instead: what runs, what needs approval, what gets snapshotted — and keep records you can verify later, so "what was allowed at the time" is answerable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things I took away
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Nearly every item reduces to the same move: put the gate at the action, not at the prompt. You can't filter text well enough; you can decide whether a specific tool call with specific arguments runs.&lt;/li&gt;
&lt;li&gt;The other half is evidence. If you can't reconstruct what the agent did and which policy was in force, you're guessing.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I've been building &lt;a href="https://gitee.com/suhuisoftwares/pod" rel="noopener noreferrer"&gt;pod&lt;/a&gt; around those two ideas: &lt;code&gt;pod scan&lt;/code&gt; (read-only, finds unpinned servers and plaintext keys), &lt;code&gt;pod policy draft&lt;/code&gt; (compiles a least-privilege policy from what your agent actually did), &lt;code&gt;pod serve&lt;/code&gt; (enforces it), &lt;code&gt;pod verify-audit&lt;/code&gt; (tamper-evident evidence).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://gitee.com/suhuisoftwares/pod/raw/v0.3.2/scripts/install.sh | sh
pod scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The boundary, since it's a security tool: not a sandbox, doesn't see what your agents say to each other, won't stop someone who already has your shell. It sits in front of tool calls and keeps receipts.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
      <category>mcp</category>
    </item>
    <item>
      <title>I scanned my own laptop: 6 AI agents, 14 MCP servers, 12 unpinned, 5 plaintext keys</title>
      <dc:creator>Walden Wu</dc:creator>
      <pubDate>Thu, 17 Sep 2026 06:51:07 +0000</pubDate>
      <link>https://dev.to/waldenwuwei/i-scanned-my-own-laptop-6-ai-agents-14-mcp-servers-12-unpinned-5-plaintext-keys-1k81</link>
      <guid>https://dev.to/waldenwuwei/i-scanned-my-own-laptop-6-ai-agents-14-mcp-servers-12-unpinned-5-plaintext-keys-1k81</guid>
      <description>&lt;p&gt;I have six AI agents installed on one laptop: Claude Code, Cursor, Codex, OpenCode, OpenClaw and DeepSeek Harness. Between them they run 14 MCP servers.&lt;/p&gt;

&lt;p&gt;I wrote a read-only scanner to answer a question none of them could answer on their own: &lt;strong&gt;what can all of my agents touch, together?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It finished in 0.19 seconds. This is what came back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 1: 12 of the 14 MCP servers are unpinned
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;context7   -&amp;gt; npx @upstash/context7-mcp                (unpinned)
playwright -&amp;gt; npx @playwright/mcp@latest               (@latest)
github     -&amp;gt; npx @modelcontextprotocol/server-github  (unpinned)
... 12 in total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;npx -y &amp;lt;package&amp;gt;&lt;/code&gt; is a supply-chain decision. &lt;code&gt;@latest&lt;/code&gt; means the code that runs at startup is whatever was published to npm most recently — not the version you tested. My GitHub MCP server holds a personal access token and pulls &lt;code&gt;@latest&lt;/code&gt; on every start.&lt;/p&gt;

&lt;p&gt;This is not theoretical. 2025 already had real malicious MCP packages on npm harvesting &lt;code&gt;.env&lt;/code&gt; files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 2: five API keys sit in plaintext in agent config files
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/.dsh/mcp-manager.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GitHub token x2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/.codex/config.toml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenAI key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/.cursor/mcp.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GitHub token&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;~/.config/opencode/opencode.json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OpenAI key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The scanner masks values and never prints them. But the values are there, in plaintext, on disk.&lt;/p&gt;

&lt;p&gt;Any process — or any agent — that can read those files has my GitHub and OpenAI accounts. This is close to the default configuration on most developer machines right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding 3: six agents, no inventory
&lt;/h2&gt;

&lt;p&gt;Cursor knows its own permissions. OpenClaw knows its own. Nothing answers "what can all of my agents do together?" That is the shadow-agent problem, and it is why the scan report matters more than any single finding in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Pin versions.&lt;/strong&gt; &lt;code&gt;npx -y pkg@1.2.3&lt;/code&gt;, never &lt;code&gt;@latest&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move secrets to a keychain.&lt;/strong&gt; Tokens out of agent config files and into a system keychain or secret manager.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put a gate in front of tool calls.&lt;/strong&gt; A policy layer that denies sensitive paths, blocks secret output, and hash-chains every call for audit.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Run the scan yourself
&lt;/h2&gt;

&lt;p&gt;Read-only, offline, nothing uploaded, secrets masked in the report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://gitee.com/suhuisoftwares/pod/raw/v0.3.2/scripts/install.sh | sh
pod scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apache-2.0. Local-first.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://gitee.com/suhuisoftwares/pod" rel="noopener noreferrer"&gt;https://gitee.com/suhuisoftwares/pod&lt;/a&gt; (mirror: &lt;a href="https://github.com/suhui-organization/pod" rel="noopener noreferrer"&gt;https://github.com/suhui-organization/pod&lt;/a&gt;)&lt;br&gt;
Site: &lt;a href="https://podsec.vercel.app" rel="noopener noreferrer"&gt;https://podsec.vercel.app&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;The numbers come from a real scan of the author's machine (first scan 2026-09-01, re-confirmed 2026-09-12). No key values are published — run &lt;code&gt;pod scan&lt;/code&gt; to get your own.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
      <category>mcp</category>
    </item>
    <item>
      <title>I scanned 14 MCP servers on my laptop, then compiled a least-privilege policy from what my agent actually did</title>
      <dc:creator>Walden Wu</dc:creator>
      <pubDate>Thu, 17 Sep 2026 02:55:37 +0000</pubDate>
      <link>https://dev.to/waldenwuwei/i-scanned-14-mcp-servers-on-my-laptop-then-compiled-a-least-privilege-policy-from-what-my-agent-3de9</link>
      <guid>https://dev.to/waldenwuwei/i-scanned-14-mcp-servers-on-my-laptop-then-compiled-a-least-privilege-policy-from-what-my-agent-3de9</guid>
      <description>&lt;p&gt;I run six agent platforms on one laptop. Between them they have 14 MCP servers configured, 12 of them unpinned (&lt;code&gt;npx -y pkg@latest&lt;/code&gt;), and five plaintext API keys sitting in config files. I only knew that after writing a read-only scanner.&lt;/p&gt;

&lt;p&gt;That scanner turned into &lt;strong&gt;pod&lt;/strong&gt;. The piece I care about is the middle step that most existing tooling skips:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;record    run your agent through pod in record-only mode for a few days
compile   pod policy draft  -&amp;gt; least-privilege rules from real observed calls
enforce   pod serve         -&amp;gt; deny &amp;gt; approve &amp;gt; allow, fail-closed
prove     pod verify-audit  -&amp;gt; SHA-256 hash chain you can hand to someone else
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gateways give you a place to enforce rules you write by hand. Scanners tell you what is exposed. pod writes the rules from behavior — &lt;code&gt;read_file&lt;/code&gt; gets denied because it touched &lt;code&gt;.env&lt;/code&gt; exactly once out of 214 calls, and tools that never appeared in the corpus are simply not granted.&lt;/p&gt;

&lt;p&gt;Two things that make it different in practice:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The evidence is independently verifiable.&lt;/strong&gt; Every call goes into a hash chain; &lt;code&gt;pod export-evidence&lt;/code&gt; produces a bundle anyone can verify without trusting me or the host. Sensitive content is stored as hashes only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You own the rules.&lt;/strong&gt; Thresholds, regexes, severities and trusted sources live in &lt;code&gt;~/.pod/rules.json&lt;/code&gt;. Invalid rules fail closed instead of silently falling back to defaults.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is also a control-plane side most tools ignore: lifecycle hooks, frozen config, agent identities, delegation chains, memory-file drift. All of it lands in the same audit chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install (macOS/Linux, builds from source)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://gitee.com/suhuisoftwares/pod/raw/v0.3.2/scripts/install.sh | sh
pod scan        &lt;span class="c"&gt;# read-only, no network&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apache-2.0. Local-first: policies, audits and secrets stay on your machine; the optional cloud control plane is in the same repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest boundaries
&lt;/h2&gt;

&lt;p&gt;The threat model is explicit: no sandboxing, no A2A/mTLS protocol implementation, and no behavioral-drift-as-primary-defense. If you need those, this is not the tool.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://gitee.com/suhuisoftwares/pod" rel="noopener noreferrer"&gt;https://gitee.com/suhuisoftwares/pod&lt;/a&gt; (mirror: &lt;a href="https://github.com/suhui-organization/pod" rel="noopener noreferrer"&gt;https://github.com/suhui-organization/pod&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Website: &lt;a href="https://podsec.vercel.app" rel="noopener noreferrer"&gt;https://podsec.vercel.app&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I would especially like feedback on the compile step: whether the observed-behavior-to-policy mapping is too coarse for real workflows, and where it produces obviously wrong rules.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
      <category>mcp</category>
    </item>
  </channel>
</rss>
