<?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: correctover</title>
    <description>The latest articles on DEV Community by correctover (@correctover).</description>
    <link>https://dev.to/correctover</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%2F3924714%2F72bbee41-90a8-4810-8fee-1ddb3ecef567.jpeg</url>
      <title>DEV Community: correctover</title>
      <link>https://dev.to/correctover</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/correctover"/>
    <language>en</language>
    <item>
      <title>Hot Wallet Drains in 2026: The Question Nobody Asks</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Fri, 30 Oct 2026 03:51:39 +0000</pubDate>
      <link>https://dev.to/correctover/hot-wallet-drains-in-2026-the-question-nobody-asks-5e05</link>
      <guid>https://dev.to/correctover/hot-wallet-drains-in-2026-the-question-nobody-asks-5e05</guid>
      <description>&lt;h1&gt;
  
  
  Hot Wallet Drains in 2026: The Question Nobody Asks About the $3.3B Lesson
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;The signature is the attack surface. Everything else is commentary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every major wallet-drain event of the last 18 months - from the $1.5B Bybit incident to this week's $3.3B hot wallet event - has produced the same post-mortem pattern: a forensic scramble over &lt;em&gt;where the money went&lt;/em&gt;, and a deafening silence over &lt;em&gt;how the signature that authorized the movement was produced&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That's the wrong order. And it keeps being the wrong order because most incident-response tooling is built for the trace, not for the authorization layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual question
&lt;/h2&gt;

&lt;p&gt;When funds leave a hot wallet in one transaction, the cryptographic facts are these:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A valid signature existed for that exact transfer&lt;/li&gt;
&lt;li&gt;That signature came from a key that was either compromised, coerced, or delegated&lt;/li&gt;
&lt;li&gt;The moment of signing happened &lt;em&gt;before&lt;/em&gt; the on-chain movement - often hours before, in a completely different trust context&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;EIP-7702 (which activated in Prague's shadow this year) makes this sharper: delegated execution means an EOA can temporarily behave like a contract, and &lt;em&gt;who authorized that delegation&lt;/em&gt; is a question almost no monitoring stack answers. The delegation signature is the real root event. The token sweep is just its echo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incident response teams today can answer "where did it go" within minutes. Almost none can answer "what produced the authorization" with verifiable evidence.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What a verifiable authorization layer looks like
&lt;/h2&gt;

&lt;p&gt;After studying these failure modes across multiple cases, we've converged on a minimal set of properties that any production hot-wallet authorization path should satisfy. This isn't a product pitch - it's a checklist the next $3.3B post-mortem will wish it had:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;What it means in practice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Operator-signed scopes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The &lt;em&gt;human or policy principal&lt;/em&gt; signs what the wallet may do, per session. The hot key can never authorize outside that scope, no matter what code it runs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Append-only session grants&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every credential-entry event ("key may sign for host X in this window") is recorded so the agent/wallet runtime can &lt;em&gt;read&lt;/em&gt; the grant but can never rewrite history. Bounded in time. Auditable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Typed-once handles&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Secrets enter exactly once, at a human terminal. The runtime references a handle - never the material.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fail-closed, not fail-open&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;If verification can't complete (missing scope, stale grant, unknown principal), the default is &lt;em&gt;refuse&lt;/em&gt;. Not "log and proceed".&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Third-party-verifiable receipts&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Every authorization produces a receipt an external auditor can verify offline - Ed25519 over JCS-canonicalized payloads. No trust in the vendor's database required.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last property is the one the industry keeps skipping - and it's the one that determines whether your insurer, your regulator, or a court can independently confirm what your wallet was told to do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is a verification problem, not a monitoring problem
&lt;/h2&gt;

&lt;p&gt;Monitoring watches &lt;em&gt;effects&lt;/em&gt; (transactions, balances, anomalies). Verification constrains &lt;em&gt;causes&lt;/em&gt; (who signed, what scope, which grant chain). By the time monitoring fires on a hot wallet sweep, the signature already happened - the only question left is forensic.&lt;/p&gt;

&lt;p&gt;The teams that will survive the next round of incidents are the ones who moved the boundary: from "we'll detect it fast" to "the signature that authorizes this class of movement cannot exist without a verifiable human or policy grant."&lt;/p&gt;

&lt;p&gt;Detection tells you you're bleeding. Verification is why you don't start.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Correctover works on runtime verification for AI agent and wallet tool calls - structure, schema, identity, integrity, security - with Ed25519-signed, offline-verifiable receipts. The scope/receipt/handle patterns above are from an IETF-draft-track design we're documenting in the open.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tools and evidence: &lt;a href="https://github.com/DSHCorrectover" rel="noopener noreferrer"&gt;github.com/DSHCorrectover&lt;/a&gt; — runtime verification for AI agents &amp;amp; node infrastructure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ethereum</category>
      <category>web3</category>
    </item>
    <item>
      <title>We Published the Largest Known Census of MCP Tools: 229,827 Parameter Schemas</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Thu, 29 Oct 2026 09:58:59 +0000</pubDate>
      <link>https://dev.to/correctover/we-published-the-largest-known-census-of-mcp-tools-229827-parameter-schemas-5869</link>
      <guid>https://dev.to/correctover/we-published-the-largest-known-census-of-mcp-tools-229827-parameter-schemas-5869</guid>
      <description>&lt;h1&gt;
  
  
  We Published the Largest Known Census of MCP Tools: 229,827 Parameter Schemas
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;If you're building security or conformance tooling for AI agents, you need machine-readable descriptions of what tools exist and what parameters they take. Today we're publishing the largest known dataset of exactly that.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's in it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;229,827 tool records&lt;/strong&gt;, each with a machine-readable &lt;code&gt;input_schema&lt;/code&gt; — the parameter definitions an agent sees when deciding how to call a tool.&lt;/li&gt;
&lt;li&gt;Coverage from &lt;strong&gt;13,120 servers with tool-level schemas&lt;/strong&gt; (out of 42,163 servers surveyed — the gap is itself a finding: most deployed MCP servers don't expose machine-readable parameters at all).&lt;/li&gt;
&lt;li&gt;Byte-verified counts, zero malformed records, SHA-256 anchored — &lt;a href="https://github.com/DSHCorrectover/mcp-atlas-census" rel="noopener noreferrer"&gt;verify the bundle yourself&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why parameter schemas matter
&lt;/h2&gt;

&lt;p&gt;An agent's tool call is only as safe as the contract it can read. Parameter-level risk classification — which arguments can carry URLs, file paths, shell fragments — is the layer where injection and exfiltration actually get stopped. Our IETF individual draft &lt;code&gt;draft-correctover-gg-mcp-parameter-risk&lt;/code&gt; proposes a canonical risk taxonomy for exactly these schemas; this census is the empirical base it stands on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use it
&lt;/h2&gt;

&lt;p&gt;Risk research, conformance test generation, discovery-layer security analysis. Data is CC0 (dataset; see repo for the verification hash and citation).&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limits
&lt;/h2&gt;

&lt;p&gt;English-language-weighted sampling; schema quality varies by server; the 29k servers without tool-level schema are recorded but not expanded. Methodology notes in the repo.&lt;/p&gt;

&lt;p&gt;Correctover — runtime verification and evidence infrastructure for AI agents.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
      <category>dataset</category>
    </item>
    <item>
      <title>Where the Agent Can't Write: Signing Trust Boundaries for AI Agents</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Thu, 29 Oct 2026 05:18:56 +0000</pubDate>
      <link>https://dev.to/correctover/where-the-agent-cant-write-signing-trust-boundaries-for-ai-agents-355m</link>
      <guid>https://dev.to/correctover/where-the-agent-cant-write-signing-trust-boundaries-for-ai-agents-355m</guid>
      <description>&lt;h1&gt;
  
  
  Where the Agent Can't Write: Signing Trust Boundaries for AI Agents
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;An agent that can edit its own permission policy has no permission policy. This is a design analysis of three patterns that survive that constraint — expanded from a discussion on agent credential handling, for teams building agent systems that handle real secrets.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Agent frameworks increasingly ship safety boundaries: "don't send credentials to arbitrary hosts," "don't pipe secrets into shell commands." These boundaries are only as strong as the configuration that expresses them. And the uncomfortable fact is: &lt;strong&gt;most agent configurations are agent-writable.&lt;/strong&gt; Settings files, environment variables, project configs — the same surfaces the developer edits are, in most deployments, surfaces the agent can also reach through its own file and shell tools.&lt;/p&gt;

&lt;p&gt;A prompt-injected webpage, a malicious dependency, a poisoned README — any of them can append to a credential allowlist as easily as the developer can. The boundary didn't fail loudly; it was never a boundary. It was an instruction the attacker could also give.&lt;/p&gt;

&lt;p&gt;The test for any trust declaration is simple: &lt;strong&gt;can the agent author it, or only consume it?&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Threat model
&lt;/h2&gt;

&lt;p&gt;We assume a capable agent with file and shell tools, running prompts and content from untrusted sources. The attacker controls some prompt surface (webpage text, issue comments, dependency behavior) and wants the agent to exfiltrate a credential or perform a privileged action. The defender wants legitimate workflows (testing against staging, entering dev credentials) to keep working.&lt;/p&gt;

&lt;p&gt;Everything below is evaluated against one question: does this pattern give the attacker's instructions the same power as the operator's?&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1 — Operator-signed scopes
&lt;/h2&gt;

&lt;p&gt;The environment declaration ("credentials may be entered for &lt;code&gt;staging.internal&lt;/code&gt;, &lt;code&gt;*.corp.example&lt;/code&gt;") is signed by an operator key that the agent never sees. The agent (and its tools) may &lt;em&gt;read&lt;/em&gt; the signed scope and enforce it; they cannot &lt;em&gt;author&lt;/em&gt; or modify it.&lt;/p&gt;

&lt;p&gt;Mechanics that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Key location is the security property.&lt;/strong&gt; The signing key lives in a hardware-backed store, a CI secret, or an operator's password manager — anywhere outside the agent's filesystem and shell reach. If the key is a file the agent can read, the pattern degrades to agent-writable config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signature covers the entire scope&lt;/strong&gt;, not entries: adding one host invalidates the whole signature, so incremental tampering is detectable rather than silent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revocation is a new signature&lt;/strong&gt;, which means emergency revocation has a cost — plan for short scope lifetimes (days, not months) so revocation-by-expiry is the common case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the strongest pattern and the least flexible. Use it for stable infrastructure: known hosts, known clusters.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2 — Session grants as append-only receipts
&lt;/h2&gt;

&lt;p&gt;Instead of declaring environments safe forever, the operator grants per-session: "for this session, host X may receive credentials." Each grant is recorded as a &lt;strong&gt;receipt&lt;/strong&gt;: a structured, timestamped, hash-chained entry the agent can read but not rewrite.&lt;/p&gt;

&lt;p&gt;Why append-only matters: if the agent could edit the grant log, it could mint grants. If the log only appends (hash-chained, or held by a separate process), the agent's powers are exactly the operator's historical decisions — no more. Auditing becomes trivial: the log &lt;em&gt;is&lt;/em&gt; the security story of the session.&lt;/p&gt;

&lt;p&gt;Design notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Grant tuples&lt;/strong&gt;: &lt;code&gt;{principal, capability, scope, expiry, nonce}&lt;/code&gt; — principal is the operator identity, capability is narrow ("enter-credential", not "do-anything"), expiry is short (minutes to hours), nonce prevents replay across sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hash chaining&lt;/strong&gt;: each receipt commits to its predecessor, so truncation and reordering are detectable. The chain head can be signed periodically for external anchoring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The receipt is a decision artifact&lt;/strong&gt; — the same shape as runtime-verification receipts in serving infrastructure. A grant and a verdict are cousins: both are signed statements about what was authorized, made by a principal the runtime cannot impersonate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pattern fits interactive work best: the developer approves a staging host once per morning, the grant is visible and bounded, and the injected page can't extend it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3 — Typed-once secret handles
&lt;/h2&gt;

&lt;p&gt;The operator types the secret directly, once, at the start of the session. Thereafter the agent only ever sees a &lt;em&gt;handle&lt;/em&gt; — an environment variable name, a secret-store reference — never the material.&lt;/p&gt;

&lt;p&gt;The agent's context contains &lt;code&gt;DB_PASSWORD_REF=vault://ci/db-password&lt;/code&gt;, not &lt;code&gt;DB_PASSWORD=hunter2&lt;/code&gt;. When a tool needs the credential, the &lt;em&gt;tool's backend&lt;/em&gt; resolves the handle outside the agent's context window. The agent can reference, leak the reference, but cannot leak the secret.&lt;/p&gt;

&lt;p&gt;Design notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Resolution must happen out-of-band.&lt;/strong&gt; If the agent's shell expands the handle, the material transits the agent's context and the pattern collapses. Resolution belongs in the tool's server side.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handles bind to grants&lt;/strong&gt;: a handle without a matching grant is inert. This composes Patterns 2 and 3 into a lattice — handles name &lt;em&gt;what&lt;/em&gt;, grants authorize &lt;em&gt;when and where&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Composition: a signed policy lattice
&lt;/h2&gt;

&lt;p&gt;The three patterns compose. Signed scopes define the stable world (Pattern 1). Session grants authorize deviations (Pattern 2). Secret handles keep material out of the untrusted context (Pattern 3). Enforcement happens at the jump — every privileged action is checked against the lattice &lt;em&gt;before&lt;/em&gt; execution, fail-closed.&lt;/p&gt;

&lt;p&gt;The lattice has a useful property: every authorization is a &lt;strong&gt;signed, inspectable artifact&lt;/strong&gt;, and every denial is a signed, inspectable artifact too. Debugging "why did the agent refuse my password" stops being archaeology and becomes reading the receipts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes (honest)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compromised operator key&lt;/strong&gt; breaks Pattern 1 entirely — key custody is the pattern. Mitigate with short lifetimes and an out-of-band revocation channel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TOCTOU on grants&lt;/strong&gt;: a grant checked at request time against a mutable store can be swapped between check and use. Receipts must be checked against the &lt;em&gt;appended&lt;/em&gt; log, not a cached copy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle dereference bugs&lt;/strong&gt;: a tool that logs its resolved material reintroduces the leak. Handle resolution paths need the same audit as the boundary itself.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usability pressure&lt;/strong&gt; is the real long-term threat: every "just let me disable this for a minute" request is a request to move a declaration from the operator's write sphere into the agent's. Resist structurally (short grants) rather than culturally (policy documents).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The claude-code thread asked for a permission-gated opt-in for dev environments. The feature is right; its safety depends entirely on &lt;em&gt;who signs the scope&lt;/em&gt;. Sign it in the agent's reach, and you've built a prompt-injection amplifier. Sign it outside, scope it in time, and record it in receipts — and the same flexibility ships without the exfiltration channel.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Guigui Wang — Correctover. We design and assess trust boundaries for AI infrastructure: signed receipts, scoped grants, evidence-grade deliverables.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tools and evidence: &lt;a href="https://github.com/DSHCorrectover" rel="noopener noreferrer"&gt;github.com/DSHCorrectover&lt;/a&gt; — runtime verification for AI agents &amp;amp; node infrastructure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Auditing a Published CVE: A Six-Step Reproducibility Walkthrough</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Thu, 29 Oct 2026 04:09:56 +0000</pubDate>
      <link>https://dev.to/correctover/auditing-a-published-cve-a-six-step-reproducibility-walkthrough-4inl</link>
      <guid>https://dev.to/correctover/auditing-a-published-cve-a-six-step-reproducibility-walkthrough-4inl</guid>
      <description>&lt;p&gt;: A Six-Step Reproducibility Walkthrough&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In July 2026, an advisory for a widely deployed SM2 library described private-key recovery by "observing a few Math.random() outputs." We audited that claim. The premise was wrong, the real root cause was narrower and stranger, and the maintainer confirmed our correction in writing. This is the method, on public record, step by step.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;The advisory described seeding from &lt;code&gt;Math.random()&lt;/code&gt; with a pool filled at module load — implying an attacker who samples a few outputs could reconstruct keys. Recoverable-from-outputs is a strong, testable claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 — Pin the artifact, not the story
&lt;/h2&gt;

&lt;p&gt;We froze the exact published npm tarballs (&lt;code&gt;sm-crypto@0.4.0&lt;/code&gt;, &lt;code&gt;jsbn@1.1.0&lt;/code&gt;, hash-recorded) and instrumented a victim process that pinned &lt;code&gt;Math.random&lt;/code&gt; and the wall clock &lt;em&gt;before&lt;/em&gt; the libraries loaded. No version drift, no "works on my machine."&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 — Pre-register the counters
&lt;/h2&gt;

&lt;p&gt;Before running anything, we wrote down what we expected to observe and what would falsify the claim: &lt;code&gt;Math.random&lt;/code&gt; call count at seeding time, OS RNG calls, generated keys across processes, and signatures under fixed pins. Counters first, experiment second — so the experiment could not be quietly re-interpreted after the fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 — Run the adversary model honestly
&lt;/h2&gt;

&lt;p&gt;The advisory's attack needed observable RNG outputs. Our instrumentation showed the pool fill &lt;strong&gt;never called &lt;code&gt;Math.random&lt;/code&gt; in Node at all&lt;/strong&gt; — the count was zero. The premise was not merely unverified; it was impossible as described. What remained was a clock-only seed with dead pool-fill code, which we demonstrated: identical keys across processes pinned to the same wall clock, valid signatures from those keys, both reproduced independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 — State the boundary before anyone asks
&lt;/h2&gt;

&lt;p&gt;Our claim, worded to survive hostile reading: keys generated at or near process startup in runtimes without &lt;code&gt;getRandomValues&lt;/code&gt; are recoverable within a millisecond-scale time window by a third party who can bound generation time. Not "any key, any time." The narrower claim is the one that stayed true.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 — Compare against the known art
&lt;/h2&gt;

&lt;p&gt;The registered attack class (weak-RNG key recovery from observed outputs) was well documented. Our variant required &lt;em&gt;no output observation at all&lt;/em&gt; — strictly weaker preconditions than anything in the record. When your result is stronger than the reference class, that comparison is part of the finding, not a footnote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 — Ship the bundle, not the adjectives
&lt;/h2&gt;

&lt;p&gt;Everything above ships as a canonicalized, SHA-256 anchored, Ed25519-signed evidence package with re-run instructions — anyone can re-derive every digest and re-verify the signature without trusting us. The maintainer independently confirmed the corrected root cause and adopted the expanded impact statement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this is a demonstration of
&lt;/h2&gt;

&lt;p&gt;Not a product pitch — a discipline: frozen artifacts, pre-registered counters, honest boundaries, and a signed bundle instead of adjectives. When your deliverable is trust, the method is the product.&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:wangguigui@correctover.com"&gt;wangguigui@correctover.com&lt;/a&gt; · &lt;a href="https://correctover.com" rel="noopener noreferrer"&gt;https://correctover.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tools and evidence: &lt;a href="https://github.com/DSHCorrectover" rel="noopener noreferrer"&gt;github.com/DSHCorrectover&lt;/a&gt; — runtime verification for AI agents &amp;amp; node infrastructure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>cryptography</category>
      <category>opensource</category>
    </item>
    <item>
      <title>After the Coldcard Entropy Disaster: How to Fingerprint a Wallet's Key Path in Three Steps</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Thu, 29 Oct 2026 03:38:01 +0000</pubDate>
      <link>https://dev.to/correctover/after-the-coldcard-entropy-disaster-how-to-fingerprint-a-wallets-key-path-in-three-steps-11hk</link>
      <guid>https://dev.to/correctover/after-the-coldcard-entropy-disaster-how-to-fingerprint-a-wallets-key-path-in-three-steps-11hk</guid>
      <description>&lt;p&gt;: How to Fingerprint a Wallet's Key Path in Three Steps&lt;/p&gt;

&lt;p&gt;&lt;em&gt;On July 30, 2026, Coinkite disclosed that Coldcard firmware shipped since March 2021 derived seeds from a software fallback RNG instead of the hardware entropy source — effective randomness estimated at 40 to 72 bits instead of the intended 128. Attackers swept funded addresses the same day; Galaxy Research puts confirmed losses near 1,596 BTC (~$100M) from ~7,300 addresses, with a suspected fourth wave under review. All of this is public record — Coinkite's advisory and technical backgrounder, plus independent analyses by Block and Wizardsardine. What follows is a method any team can run, plus our own reproducible evidence.&lt;/em&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;What is genuinely out of reach for a remote black-box attacker:&lt;/strong&gt; key generation that calls the platform CSPRNG directly in native code — iOS &lt;code&gt;SecRandomCopyBytes&lt;/code&gt;/&lt;code&gt;getentropy&lt;/code&gt;, Android &lt;code&gt;SecureRandom&lt;/code&gt; backed by &lt;code&gt;/dev/urandom&lt;/code&gt;. Kernel CSPRNG state is not recoverable from outputs; there is no finite state to reconstruct. Only kernel exploits or local side-channels touch this — a different track. If a wallet does this, say so: the "immune by design" stamp is worth real money to insurance and compliance teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where doors remain, even in modern wallets:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JS key paths inside native shells&lt;/strong&gt; (RN/Flutter/WebView bridges). A broken polyfill chain silently falls back to &lt;code&gt;Math.random&lt;/code&gt;. Precedent: a single 2014 iOS commit swapped a secure RNG for an insecure one across 200+ downstream wallets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signing nonces.&lt;/strong&gt; Key generation can be perfect; a repeated or biased ECDSA nonce still recovers the private key on the spot, entropy source irrelevant. Several documented thefts were exactly this — not generation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bundled third-party or cross-chain SDKs&lt;/strong&gt; that ship their own RNG inside an otherwise-clean app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom entropy mixing&lt;/strong&gt; — blending timestamps or sensor noise into platform CSPRNG output can end with less randomness than the platform offered. (The Coldcard fallback itself was seeded from device UID plus power-on timer state.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup/KDF layers&lt;/strong&gt; — weak KDF parameters, hardcoded IVs, exportable keys.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The three-step fingerprint
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Locate the generation path.&lt;/strong&gt; Find where mnemonics/seeds are produced; follow every call into bundled SDKs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classify the entropy source.&lt;/strong&gt; Platform CSPRNG direct? JS-engine path (check every polyfill)? Custom mixing? Record the exact call chain.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assign the verdict tier.&lt;/strong&gt; Immune-by-design / JS-bridge / nonce / SDK / custom-mixing / KDF — each with its reproduction class.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Generation and signing must be fingerprinted &lt;em&gt;separately&lt;/em&gt;: a clean generation path tells you nothing about nonce handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "reproducible" means here
&lt;/h2&gt;

&lt;p&gt;We publish our own capability the same way we fingerprint others: with an evidence pack, not adjectives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weak-entropy key recovery, end to end, hash-anchored:&lt;/strong&gt; a simulated vulnerable seed source (the low-effective-entropy class), table sweep, private-key recovery, and an ECDSA proof-of-possession — canonicalized with RFC 8785 JCS, SHA-256 anchored, Ed25519 signed. The demonstration runs in a reduced key space for runtime (details inside); the class it models is the one that just produced the largest self-custody loss event of 2026. Anyone can re-run and re-verify it.&lt;/p&gt;

&lt;p&gt;The bundle's core record is SHA-256 anchored (&lt;code&gt;f5df38c1…&lt;/code&gt;, re-derivable) and Ed25519-signed; verification instructions ship with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why 40–72 bits is fatal, briefly
&lt;/h2&gt;

&lt;p&gt;A 2^40 sweep is hours-to-days on commodity compute; 2^72 is nation-scale. Between those bounds sits ordinary theft economics. The disaster was not exotic — it was a quantifiable, preventable configuration that survived five years of public firmware.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we do
&lt;/h2&gt;

&lt;p&gt;For wallet vendors, custodians, and AI-infrastructure teams: private key-path fingerprinting. Every engagement produces either the immune-by-design stamp or a reproduction-class report — with the evidence pack, not a slide.&lt;/p&gt;

&lt;p&gt;&lt;a href="mailto:wangguigui@correctover.com"&gt;wangguigui@correctover.com&lt;/a&gt; · &lt;a href="https://correctover.com" rel="noopener noreferrer"&gt;https://correctover.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tools and evidence: &lt;a href="https://github.com/DSHCorrectover" rel="noopener noreferrer"&gt;github.com/DSHCorrectover&lt;/a&gt; — runtime verification for AI agents &amp;amp; node infrastructure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>cryptography</category>
      <category>blockchain</category>
      <category>ai</category>
    </item>
    <item>
      <title>Your keys are weaker than your cryptography: CVE-2026-73567 and the dead RNG that seeded SM2 keys</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Tue, 27 Oct 2026 10:32:45 +0000</pubDate>
      <link>https://dev.to/correctover/your-keys-are-weaker-than-your-cryptography-cve-2026-73567-and-the-dead-rng-that-seeded-sm2-keys-1a4l</link>
      <guid>https://dev.to/correctover/your-keys-are-weaker-than-your-cryptography-cve-2026-73567-and-the-dead-rng-that-seeded-sm2-keys-1a4l</guid>
      <description>&lt;p&gt;&lt;code&gt;Math.random()&lt;/code&gt; is not a key generator. Everyone knows this. And yet — when a widely deployed SM2 library seeded its key material at module load, that's effectively what happened. Except worse: in Node.js, the randomness pool was never even filled.&lt;/p&gt;

&lt;p&gt;This is the story of &lt;strong&gt;CVE-2026-73567&lt;/strong&gt;, a vulnerability in which our Node.js impact correction was incorporated into the public advisory by the maintainer — and what it teaches us about the whole &lt;em&gt;class&lt;/em&gt; of weak-entropy key compromises.&lt;/p&gt;

&lt;h2&gt;
  
  
  The discovery
&lt;/h2&gt;

&lt;p&gt;While auditing key-generation paths in SM2 (the Chinese national elliptic-curve standard, mandatory in regulated industries there), I noticed something odd in the dependency chain: a big-number library harvested entropy from &lt;code&gt;Math.random()&lt;/code&gt; — the non-cryptographic, browser-grade PRNG — at module initialization, and used it to seed its internal state. That state then fed private-key generation.&lt;/p&gt;

&lt;p&gt;The publicly discussed impact assumed ~128 &lt;code&gt;Math.random()&lt;/code&gt; calls at startup. When I built an instrumented Node victim and counted, the real number was &lt;strong&gt;zero&lt;/strong&gt;. V8 defers &lt;code&gt;Math.random()&lt;/code&gt;'s pool initialization in a way that left the seeding path reading predictable values — the effective root cause was a &lt;strong&gt;clock-only seed&lt;/strong&gt;, and the advisory's root cause was corrected accordingly.&lt;/p&gt;

&lt;p&gt;The consequence: given roughly a millisecond-scale time window, private keys were recoverable by enumeration. Sub-second. On an ordinary laptop.&lt;/p&gt;

&lt;p&gt;And it didn't stop at static keys. The same RNG stream also fed &lt;strong&gt;signing nonces&lt;/strong&gt; and &lt;strong&gt;ephemeral encryption keys&lt;/strong&gt;. That means a key used &lt;em&gt;only for signing&lt;/em&gt; was equally recoverable, and ciphertext from affected versions could be decrypted without the key.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened next
&lt;/h2&gt;

&lt;p&gt;Beyond the advisory, our independent reproduction verified that the actual root cause was not merely weak entropy, but a clock-only seed due to dead code in the Math.random() pool-fill — a correction formally recognized and credited in &lt;a href="https://github.com/advisories/GHSA-vh45-f885-3848" rel="noopener noreferrer"&gt;GHSA-vh45-f885-3848&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Versions before the fix remain vulnerable forever. Every key generated by an unpatched build in a predictable-time window should be considered compromised.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  This is a class, not an incident
&lt;/h2&gt;

&lt;p&gt;One library got caught. But weak entropy is a &lt;em&gt;systematic&lt;/em&gt; failure mode across the key-management stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;V8's PRNG&lt;/strong&gt; (&lt;code&gt;Math.random()&lt;/code&gt;, xorshift128+ / MWC1616 eras) can be fully state-recovered from a handful of consecutive outputs — meaning any JS wallet or signing code that ever touches non-CSPRNG randomness is one observation away from compromise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Historical events&lt;/strong&gt; (Randstorm-class bugs, 2014-era wallet RNG flaws) show the same pattern repeating across ecosystems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-address clustering&lt;/strong&gt;: weak-RNG victims often reuse exposed material across chains and address types, turning one bad key into a trail of them.&lt;/li&gt;
&lt;li&gt;The SM2 ecosystem is structurally exposed here: guomi compliance pushes SM2 into places where the surrounding JS/Node key hygiene was never designed for it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your product generates keys, signs, or encrypts — in JavaScript especially — the question isn't whether your &lt;em&gt;crypto&lt;/em&gt; is strong. It's whether your &lt;em&gt;entropy path&lt;/em&gt; is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we do about it
&lt;/h2&gt;

&lt;p&gt;At Correctover we recover keys from weak entropy — to prove the exposure before someone malicious does, and to build evidence that holds up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Black-box &lt;strong&gt;weak-entropy health checks&lt;/strong&gt; on key generation and signing paths&lt;/li&gt;
&lt;li&gt;Real recovery demonstrations in isolated environments, with signed, timestamped evidence receipts (Ed25519 + RFC 3161) — findings a court or auditor can verify without trusting us&lt;/li&gt;
&lt;li&gt;Coordinated disclosure done right: the advisory process above is our reference case&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build wallets, custody, or exchange infrastructure and want your entropy path audited against this exact attack class, reach out: &lt;a href="mailto:wangguigui@correctover.com"&gt;wangguigui@correctover.com&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Beyond the advisory, our independent reproduction verified that the actual root cause was not merely weak entropy, but a clock-only seed due to dead code in the Math.random() pool-fill — a correction formally recognized and credited in &lt;a href="https://github.com/advisories/GHSA-vh45-f885-3848" rel="noopener noreferrer"&gt;GHSA-vh45-f885-3848&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tools and evidence: &lt;a href="https://github.com/DSHCorrectover" rel="noopener noreferrer"&gt;github.com/DSHCorrectover&lt;/a&gt; — runtime verification for AI agents &amp;amp; node infrastructure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>cryptography</category>
      <category>blockchain</category>
      <category>web3</category>
    </item>
    <item>
      <title>Agent 安全不是单一赛道：三条技术边界的工程纪律</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Tue, 15 Sep 2026 11:17:12 +0000</pubDate>
      <link>https://dev.to/correctover/agent-an-quan-bu-shi-dan-sai-dao-san-tiao-ji-zhu-bian-jie-de-gong-cheng-ji-lu-11e</link>
      <guid>https://dev.to/correctover/agent-an-quan-bu-shi-dan-sai-dao-san-tiao-ji-zhu-bian-jie-de-gong-cheng-ji-lu-11e</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;入站内容防御 / 供应链完整性 / 出站执行验证——三件事，三种代价，三种工程纪律&lt;br&gt;
作者：Guigui Wang | Correctover — AI Reliability&lt;br&gt;
日期：2026-09-15&lt;/p&gt;

&lt;p&gt;&lt;em&gt;本文涉及自有 Internet-Draft 内容均为 individual submission，not an RFC or IETF endorsement.&lt;/em&gt;&lt;br&gt;
&lt;em&gt;CCS 性能数据均为 Correctover 内部实测，文中逐条标注测量口径；不同口径不可直接比较。&lt;/em&gt;&lt;br&gt;
&lt;em&gt;文中提到的项目（CAITLYN、AgentSecCore/ANOLISA）为公开信息事实性引用，不含任何商业评价或排名意图。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;当「AI Agent 安全」成为热词时，最危险的动作是把所有技术塞进同一个篮子。今天公开可见的三个代表性方案，恰好站在三条不同的工程边界上。把边界画清楚，比把它们揉成一顶「AI 安全标准」的大帽子，更对得起生产环境。&lt;/p&gt;

&lt;h2&gt;
  
  
  1. 三条边界
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;边界&lt;/th&gt;
&lt;th&gt;问题&lt;/th&gt;
&lt;th&gt;代表工程&lt;/th&gt;
&lt;th&gt;工程纪律核心&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;入站内容防御&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;非可信内容进入 Agent 前是否被过滤&lt;/td&gt;
&lt;td&gt;CAITLYN（arXiv:2608.27990，PolyU / CUHK）&lt;/td&gt;
&lt;td&gt;双向规格验证（attack 必命中 + benign 零误伤）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;供应链完整性&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent 运行时加载的技能/代码是否被篡改&lt;/td&gt;
&lt;td&gt;AgentSecCore（Alibaba Cloud Linux，ANOLISA 开源）&lt;/td&gt;
&lt;td&gt;Ed25519 签名 + append-only 版本链 + Skill 快照漂移检测&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;出站执行验证&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent 发出的工具调用是否符合运行时形态&lt;/td&gt;
&lt;td&gt;Correctover CCS（IETF individual draft）&lt;/td&gt;
&lt;td&gt;7 维运行时验证：Structure/Schema/Latency/Cost/Identity/Integrity/Security&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;三条边界不是三个竞品，是三个&lt;strong&gt;工程问题&lt;/strong&gt;。任何一个真实的 Agent 部署，都可能同时需要三者的某一层。把它们混为一谈，会让企业误以为「买一个就够」；把它们画清楚，才能让每一层都做到它该做到的程度。&lt;/p&gt;

&lt;h2&gt;
  
  
  2. 入站内容防御：CAITLYN 的工程纪律
&lt;/h2&gt;

&lt;p&gt;CAITLYN 是香港理工大学（PolyU）与香港中文大学（CUHK）团队 2026 年 8 月公开的研究项目（&lt;a href="https://arxiv.org/abs/2608.27990" rel="noopener noreferrer"&gt;arXiv:2608.27990&lt;/a&gt;，v1 提交于 2026-08-28，CC0），定位是&lt;strong&gt;非可信入站内容&lt;/strong&gt;（用户 prompt、工具返回值、外部网页）的间接提示注入防御中间件。&lt;/p&gt;

&lt;p&gt;它最有价值的工程纪律是 &lt;strong&gt;System II 的反例引导技能合成闭环（counterexample-guided synthesis）&lt;/strong&gt;：&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;漏报攻击 → 当作 counterexample&lt;/li&gt;
&lt;li&gt;generator 合成新的防御技能&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;独立 verification sandbox 做双向规格验证&lt;/strong&gt;——攻击正例必须命中、良性反例必须零误伤&lt;/li&gt;
&lt;li&gt;reviewer 把关 → 带 lineage 写回技能库&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;可吸收的工程纪律&lt;/strong&gt;：新 detector 必须通过「正/反」双面语料契约，并且可以追溯到催生它的那个 counterexample。这不是功能列表，是工程纪律。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;边界&lt;/strong&gt;：它解决的是「进来的内容是不是有毒」，不做 Agent 调用工具那一刻的运行时校验，也不做 Skill 文件在磁盘上的完整性守护。论文也如实给出了这条路线的工程代价：云端 LLM 判定路径在其评测中约为数秒至十余秒延迟、每查询约 0.003–0.007 美元量级，并且中间件在多个部署点默认 fail-open。准确率、延迟、适应性三者构成它要权衡的 trilemma。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Correctover 正在把这条工程纪律吸收到自己的规则更新管线（见 §5）。我们学纪律，不抄实现。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  3. 供应链完整性：AgentSecCore 的边界
&lt;/h2&gt;

&lt;p&gt;AgentSecCore 是 Alibaba Cloud Linux（Alinux）的本地 Agent 安全内核（&lt;a href="https://help.aliyun.com/zh/alinux/how-to-use-agentseccore" rel="noopener noreferrer"&gt;官方文档，更新于 2026-09-10&lt;/a&gt;），开源版本为 ANOLISA（Apache-2.0），处于持续活跃迭代状态。&lt;/p&gt;

&lt;p&gt;它最扎实的工作是 &lt;strong&gt;Skill Ledger（技能账本）&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;每个 Skill 的 manifest（含文件哈希、扫描结果、版本信息）经 Ed25519 签名&lt;/li&gt;
&lt;li&gt;append-only 版本链 + snapshot 机制，支持漂移识别与可信版本回退&lt;/li&gt;
&lt;li&gt;6 个业务安全状态（pass / none / drifted / warn / deny / tampered）&lt;/li&gt;
&lt;li&gt;与 SkillFS 职责解耦，已适配 6 类宿主：OpenClaw、Copilot Shell、Hermes、Codex、Qoder CLI、Qwen Code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;可吸收的工程纪律&lt;/strong&gt;：Skill/Plugin 的「身份 + 完整性」必须可校验，且任何漂移都必须可被检测。这是任何 Agent 供应链的基础。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;边界&lt;/strong&gt;：它强覆盖 Integrity 一维，部分覆盖 Identity 与 Security，&lt;strong&gt;不做&lt;/strong&gt;出站工具调用的 Structure / Schema / Latency / Cost 校验（即 CCS 7 维中的 4 维）。在提示词扫描上，官方把强度分为 FAST / STANDARD / STRICT 三档，并注明 STRICT 是为 L3 语义层预留、当前与 STANDARD 等效；本地 L2 判定依赖用户自行部署 Ollama 小模型。处置姿态上，官方文档说明各宿主 hook 在 &lt;code&gt;agent-sec-cli&lt;/code&gt; 缺失、超时或返回非法结果时统一 &lt;strong&gt;fail-open&lt;/strong&gt;（放行并记录），优先保障 Agent 可用性。它是 OS 侧内容安全 + Skill 供应链守护，不是 sub-millisecond 的运行时验证引擎。&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;与 CCS 技术路径重合度最高的点是 Ed25519 签名 + 版本链，但&lt;strong&gt;粒度不同&lt;/strong&gt;：AgentSecCore 守护 Skill 级（文件 / manifest），CCS 守护调用级（每一次工具调用）。fail-open 与 fail-closed 也是两种不同取舍——前者优先可用性，后者优先安全——而非高低之分。两条路径互为补充，不是替代。&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. 出站执行验证：Correctover CCS 的边界
&lt;/h2&gt;

&lt;p&gt;Correctover CCS（Conformance Shape）是我们定义的 Agent &lt;strong&gt;运行时验证标准&lt;/strong&gt;（IETF Internet-Draft，individual submission），覆盖每一次工具调用的 &lt;strong&gt;7 个运行时维度&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Structure / Schema&lt;/strong&gt;：调用消息是否符合协议形态与字段约束&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency / Cost&lt;/strong&gt;：响应时间与 token 开销是否符合预期形态&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity / Integrity / Security&lt;/strong&gt;：工具、内容、行为的可信性校验&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;运行时校验的核心纪律是&lt;strong&gt;确定性 + 零 token + sub-millisecond&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Correctover 核心判定层 Node P50 ≈ 2.7µs&lt;/li&gt;
&lt;li&gt;Python 端到端 P50 ≈ 27µs&lt;/li&gt;
&lt;li&gt;对外口径：core eval P50 &amp;lt; 10µs / P99 &amp;lt; 25µs（实测 P50 ≈ 7.5µs、P99 ≈ 21µs）&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fail-closed&lt;/strong&gt;：运行时校验失败 = 拒绝执行，而不是放行&lt;/li&gt;
&lt;li&gt;Ed25519 签名收据（receipt）提供可独立验证的审计证据&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;为什么有三组性能数字、它们为何不矛盾。&lt;/strong&gt; 三者测量边界不同，不能直接比较：Node P50 ≈ 2.7µs 是核心判定层在 Node 运行时的&lt;strong&gt;单次纯校验&lt;/strong&gt;耗时，是最优单点；Python 端到端 P50 ≈ 27µs 走的是&lt;strong&gt;真实集成路径&lt;/strong&gt;，含 Python 调用链上的序列化与封装开销；对外承诺的 core eval P50 &amp;lt; 10µs / P99 &amp;lt; 25µs 取的是&lt;strong&gt;跨实现的保守口径&lt;/strong&gt;，并显式覆盖 P99 尾延迟，而不是只报最好看的平均值。对外统一使用保守口径，内部单点数据在此一并披露，只为交代测量边界。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;边界&lt;/strong&gt;：它守护的是「Agent 发出的调用是否合规」。它不做入站内容的间接注入过滤（那是 CAITLYN 一类中间件的地盘），也不做 Skill 文件级的供应链漂移检测（那是 AgentSecCore 的地盘）。&lt;/p&gt;

&lt;h2&gt;
  
  
  5. 三条边界之间的工程对话
&lt;/h2&gt;

&lt;p&gt;最有意思的工程对话发生在边界之间：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CAITLYN 的双向规格验证纪律&lt;/strong&gt; → 被我们（Correctover）吸收到&lt;strong&gt;规则更新管线&lt;/strong&gt;：每条新规则必须带攻击正例集 + 良性反例集 + 全量回归，门禁 fail-closed（&lt;strong&gt;纪律跨边界复用，实现不抄&lt;/strong&gt;）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AgentSecCore 的 Skill Ledger&lt;/strong&gt; → 启发 Correctover 的&lt;strong&gt;规则包签名链&lt;/strong&gt;：规则包可 Ed25519 签名，复用 &lt;code&gt;trust.py&lt;/code&gt; 两级信任模型（VERIFIED → ACCEPTED），与调用级 receipt 互补（&lt;strong&gt;资产层 + 调用层双保险&lt;/strong&gt;）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CCS 的调用级 receipt&lt;/strong&gt; → 对 CAITLYN / AgentSecCore 都透明：它们在决策时产生的记录本身也可以被 CCS 校验，形成「对防御行为的防御」。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这种分层对话，才是对得起生产环境的工程态度。&lt;/p&gt;

&lt;h2&gt;
  
  
  6. 对 Correctover 自身的要求
&lt;/h2&gt;

&lt;p&gt;我们把自己定位在&lt;strong&gt;出站执行验证&lt;/strong&gt;这一条边界上，不跨边界声称自己覆盖所有 Agent 安全问题。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;我们&lt;strong&gt;有&lt;/strong&gt;的：CCS 7 维运行时校验、Ed25519 签名收据、sub-ms 确定性引擎、fail-closed 闸门、两阶段信任模型（VERIFIED / ACCEPTED）。&lt;/li&gt;
&lt;li&gt;我们&lt;strong&gt;正在补&lt;/strong&gt;的：规则入库双向语料门禁（对 CAITLYN 工程纪律的吸收，正在 P0 阶段）；规则包签名链（借鉴 Skill Ledger 的思路）。&lt;/li&gt;
&lt;li&gt;我们&lt;strong&gt;不做&lt;/strong&gt;的：入站内容防御（CAITLYN 一类中间件的地盘）、Skill 文件漂移检测（AgentSecCore 的地盘）。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我们同时承认：&lt;strong&gt;CCS 不是 AI 安全的唯一答案&lt;/strong&gt;，它只是&lt;strong&gt;运行时验证层&lt;/strong&gt;的一个答案。如果你的威胁模型同时包含入站内容投毒和第三方 Skill 供应链，我们建议在 CCS 之外&lt;strong&gt;同时部署&lt;/strong&gt;入站内容防御与 Skill 完整性方案——任何单一层都不足以覆盖完整的 Agent 攻击面。把边界画清楚，比把功能堆到一起更值得尊重。&lt;/p&gt;




&lt;h2&gt;
  
  
  7. 术语与事实锚点
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CAITLYN&lt;/strong&gt;：&lt;a href="https://arxiv.org/abs/2608.27990" rel="noopener noreferrer"&gt;arXiv:2608.27990&lt;/a&gt;（PolyU / CUHK，v1 2026-08-28，CC0）。属入站内容层，不对应 CCS 7 维中的任何一维。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AgentSecCore / ANOLISA&lt;/strong&gt;：&lt;a href="https://help.aliyun.com/zh/alinux/how-to-use-agentseccore" rel="noopener noreferrer"&gt;阿里云 Alinux 官方文档&lt;/a&gt;（页面更新 2026-09-10）；开源版 ANOLISA，Apache-2.0，GitHub 活跃维护。在 CCS 7 维中强覆盖 Integrity（1 维），部分覆盖 Identity + Security（2 维），不覆盖 Structure / Schema / Latency / Cost（4 维）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correctover CCS&lt;/strong&gt;：IETF Internet-Draft individual submission；Correctover 开源实现；性能实测 Node 核心判定层 P50 ≈ 2.7µs、Python 端到端 P50 ≈ 27µs，对外保守口径 core eval P50 &amp;lt; 10µs / P99 &amp;lt; 25µs（实测 7.5 / 21µs）。&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Internet-Draft 免责&lt;/strong&gt;：本文对 Correctover 自有 Internet-Draft 的引用均为 individual submission, not an RFC or IETF endorsement。&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;想先看清自己 Agent 的&lt;strong&gt;出站调用暴露面&lt;/strong&gt;，可以用免费自动扫描器建立一条基线（&lt;a href="https://correctover.com" rel="noopener noreferrer"&gt;correctover.com&lt;/a&gt;）；对于自动规则无法判定的部分（实际可利用性、生产危害、合规证据），再考虑人工运行时审计。&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;本文是 Correctover 对 AI Agent 安全三条技术边界的工程视角陈述，不是对任何产品或研究的排名或评价。作者对 CAITLYN 团队与 AgentSecCore/ANOLISA 团队公开贡献的工程纪律保持尊重。&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Tools and evidence: &lt;a href="https://github.com/DSHCorrectover" rel="noopener noreferrer"&gt;github.com/DSHCorrectover&lt;/a&gt; — runtime verification for AI agents &amp;amp; node infrastructure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiagents</category>
      <category>security</category>
      <category>mcp</category>
      <category>llm</category>
    </item>
    <item>
      <title>Your agent sandbox is a physical exam, not a black box: dynamic analysis can't be the last word in agent security</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Fri, 04 Sep 2026 15:21:31 +0000</pubDate>
      <link>https://dev.to/correctover/your-agent-sandbox-is-a-physical-exam-not-a-black-box-dynamic-analysis-cant-be-the-last-word-in-100i</link>
      <guid>https://dev.to/correctover/your-agent-sandbox-is-a-physical-exam-not-a-black-box-dynamic-analysis-cant-be-the-last-word-in-100i</guid>
      <description>&lt;p&gt;A pattern is settling into the AI agent security stack: when you're unsure about a skill, tool, or plugin, you detonate it. Drop it into a sandbox — a detonation chamber, a honeypot environment — let it run for real, and watch what it does. Does it phone home? Does it read files it shouldn't? Does it unpack additional payloads?&lt;/p&gt;

&lt;p&gt;It feels rigorous. Execution is truth; static analysis is just reading. And dynamic analysis does catch things static analysis misses — unpacked payloads, environment-triggered behavior, exfiltration that exists nowhere except at runtime.&lt;/p&gt;

&lt;p&gt;But here is the framing problem. A sandbox run is a &lt;strong&gt;physical exam&lt;/strong&gt;: a controlled check-up, in a clinic, on a good day, before the patient starts living their life. It is not a &lt;strong&gt;black box&lt;/strong&gt;: the tamper-evident recorder running during the actual flight, readable by investigators who don't trust the airline.&lt;/p&gt;

&lt;p&gt;Both matter. They are not interchangeable, and treating the exam as the recorder leaves a specific, predictable gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  What dynamic detonation gets right
&lt;/h2&gt;

&lt;p&gt;Let's be fair to the sandbox, because it earns its place. Static analysis reads code and reasons about what it &lt;em&gt;could&lt;/em&gt; do; dynamic detonation watches what it &lt;em&gt;does&lt;/em&gt; do. That distinction is real. Some behavior exists purely at runtime: payloads that decrypt themselves in memory, network calls assembled from environment state, logic that branches on what it finds on disk. If you never execute the artifact, you never see that side of it.&lt;/p&gt;

&lt;p&gt;For agent skills and tools — bundles of code that an LLM can invoke, often sourced from third-party registries, often with broad permissions — "run it in a cage and watch" is a genuinely useful pre-release check. It should be part of the pipeline. The argument here is not against the sandbox. It is against asking the sandbox to do a job it structurally cannot do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundary 1: coverage is an arms race, not a one-time build
&lt;/h2&gt;

&lt;p&gt;Anti-sandbox and anti-honeypot evasion is a roughly twenty-year-old arms race in the malware world, and it remains unresolved — managed continuously, never finished. A sample can check whether it is being watched: timing delays and sleep timers, virtual-machine fingerprints, the absence of mouse movement or browser history, telltale environment variables, user-data directories that look freshly faked. Detect the clinic, and you behave like a model patient. The malicious payload waits for what looks like real life.&lt;/p&gt;

&lt;p&gt;This has a direct consequence for agent security tooling. When a vendor quotes a dynamic-analysis catch rate — runtime detonation recovering a meaningful share of what static analysis misses — ask the mirror question: what share of malicious behavior does the detonation chamber itself miss, and who measures that? In detection engineering, a detector cannot prove its own completeness. The evasions you can observe are the ones you already know about; the evasion that stays quiet in your environment is, by construction, invisible to you. Coverage is a maintenance contract, not a build artifact — which means a sandbox you stopped tuning is a sandbox whose edge is eroding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundary 2: slow and expensive — a deployment gate, not a runtime gate
&lt;/h2&gt;

&lt;p&gt;Real detonation costs real things: environment construction, warm-up, a timeout window long enough to catch delayed payloads, resource isolation, teardown. That budget is fine for a release pipeline. It is not fine for the request path.&lt;/p&gt;

&lt;p&gt;An agent in production makes tool calls constantly — each one a decision point — and you cannot route every call through a multi-minute detonation without making the agent unusable. So the sandbox sits, structurally, &lt;em&gt;before&lt;/em&gt; deployment: it audits the artifact you shipped. Everything that happens afterward — production traffic, real sessions, real users — happens outside its field of view.&lt;/p&gt;

&lt;p&gt;This is the physical-exam property. The exam happens before the flight. It cannot watch the flight, no matter how thorough it was.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundary 3: honeypot behavior is not production behavior
&lt;/h2&gt;

&lt;p&gt;Even when the sandbox watches a run, it watches a &lt;em&gt;drill&lt;/em&gt;. The clinic has synthetic inputs, disposable credentials, empty data directories, and no real user in the loop. Production has the opposite: real documents, live tokens with real permissions, and chained calls where tool B receives output shaped by tool A — chains that can run many steps deep by the time something goes wrong.&lt;/p&gt;

&lt;p&gt;Plenty of real incidents live in that gap. Code that behaves in a demo environment but reaches for the customer database when a real database exists. A tool that is harmless with a dummy API key and aggressive with a valid one. A chain where each step looks fine individually and the composition is the exploit. You audited the rehearsal. The accident happens on opening night.&lt;/p&gt;

&lt;h2&gt;
  
  
  "OS-level logs can't be faked" — they can
&lt;/h2&gt;

&lt;p&gt;One defense of dynamic execution is that the operating system sees everything: syscalls, file access, network connections. Logs at that layer get treated as ground truth.&lt;/p&gt;

&lt;p&gt;They are ground truth right up until the thing you are watching holds administrator or root privileges — which is exactly what a malicious tool, an exploited dependency, or a successful sandbox escape hands an attacker. Code at that level can hook syscalls, suppress its own traces, wipe or rewrite audit records, and feed the observer a clean narrative. A log written by the party whose behavior is in dispute is self-reported evidence.&lt;/p&gt;

&lt;p&gt;Aviation is instructive here. The pilot's handwritten logbook is useful, but nobody mistakes it for a flight recorder. A black box is engineered so the pilot &lt;strong&gt;cannot&lt;/strong&gt; rewrite it after the fact: tamper-evident, write-once, readable by people who do not trust the emitter. If your "runtime evidence" is produced and stored by the same runtime you are auditing, you have a logbook.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an agent black box actually looks like
&lt;/h2&gt;

&lt;p&gt;The useful unit is a per-invocation receipt: one record per tool call, emitted as the call happens, with properties that make it evidence rather than a report:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cryptographically bound to the specific invocation&lt;/strong&gt; — signed, with hashes covering the caller, the tool, the arguments, and the authorization under which the call ran. A receipt you cannot tie to a concrete call is decorative.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anchored outside the call path&lt;/strong&gt; — verifiable by a party who is neither the emitter nor on the execution path: an append-only transparency log, a remote-attestation flow, any arrangement where rewriting history means breaking the anchor, not merely breaking into the box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three states kept separate&lt;/strong&gt; — the agent's &lt;em&gt;decision&lt;/em&gt; (what it chose to call and why), the provider's &lt;em&gt;outcome&lt;/em&gt; (what the tool reported back), and the &lt;em&gt;observed effect&lt;/em&gt; (what actually changed). These routinely disagree, and collapsing them into one "success/failure" field hides exactly the disagreements that matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Indeterminate stays indeterminate&lt;/strong&gt; — when evidence is missing, the state is "unknown," and policy fails closed. "No evidence of malice" must not be recorded as "verified safe."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the direction of open standards discussions in the MCP and agent security community: signed, verifiable invocation records rather than vendor-narrated dashboards. The format is less important than the property — evidence has to be checkable by someone with every reason to distrust the system that produced it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use both — they do different jobs
&lt;/h2&gt;

&lt;p&gt;For teams building or shipping agents, the practical split:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Before release, do the physical exam.&lt;/strong&gt; Static analysis plus dynamic detonation of skills and tools. You do not need an elaborate program for this; free and open toolchains cover the large majority of the basics — scan the code, detonate the artifact, fix what surfaces. Run it on every release, because coverage decays.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;During production, require the black box.&lt;/strong&gt; Your agent framework should emit verifiable decision records — who acted, what action, under what authorization, with what result — cryptographically signed and anchored somewhere the emitter cannot quietly rewrite them. If your framework cannot do this yet, that gap deserves more attention than another dashboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When evaluating agent security tooling, ask one question up front:&lt;/strong&gt; does it produce evidence &lt;em&gt;you&lt;/em&gt; can verify independently? A dashboard the vendor controls tells you what the vendor's system chose to record. That is a report. It is not evidence until a party outside the trust boundary can check it.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The exam does not replace the recorder
&lt;/h2&gt;

&lt;p&gt;Sandboxes are good engineering. Detonate suspicious code, watch it run, catch what static reading cannot see — keep doing all of it. Just do not mistake the pre-flight check-up for the flight recorder. The exam tells you what a tool does when it is being watched in a clinic. The black box tells you what actually happened, in production, in a record the tool cannot rewrite.&lt;/p&gt;

&lt;p&gt;If you want the physical-exam piece today, we publish a scanner that runs locally: &lt;code&gt;npx correctover-scan@latest&lt;/code&gt;. It is MIT-licensed open source and zero-upload — your code does not leave your machine, and there is nothing to sign up for. There is also a web entry point at &lt;a href="https://correctover.com/scan.html" rel="noopener noreferrer"&gt;https://correctover.com/scan.html&lt;/a&gt;. For the runtime side, take the same question to your framework and your vendors: where is the receipt, and who can verify it without trusting you?&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tools and evidence: &lt;a href="https://github.com/DSHCorrectover" rel="noopener noreferrer"&gt;github.com/DSHCorrectover&lt;/a&gt; — runtime verification for AI agents &amp;amp; node infrastructure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>security</category>
      <category>agents</category>
    </item>
    <item>
      <title>A PreToolUse security gate for Kimi Code: block live secrets, plaintext MCP, and SSRF before they hit disk</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Fri, 04 Sep 2026 05:16:16 +0000</pubDate>
      <link>https://dev.to/correctover/a-pretooluse-security-gate-for-kimi-code-block-live-secrets-plaintext-mcp-and-ssrf-before-they-2o3a</link>
      <guid>https://dev.to/correctover/a-pretooluse-security-gate-for-kimi-code-block-live-secrets-plaintext-mcp-and-ssrf-before-they-2o3a</guid>
      <description>&lt;p&gt;Last week Kimi Code (Moonshot AI's terminal coding agent) shipped a fix for a nasty prompt-injection vector: timestamps in tool output could be crafted to smuggle instructions into the model's context (&lt;a href="https://github.com/MoonshotAI/kimi-code/issues/2028" rel="noopener noreferrer"&gt;MoonshotAI/kimi-code#2028&lt;/a&gt;, fixed in the new v2 engine in 0.39.0 via #3007/#3341). Prompt-injection hardening matters — but injection is only half the story.&lt;/p&gt;

&lt;p&gt;A coding agent that has been steered (or is simply over-eager) can also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;write a hardcoded &lt;code&gt;sk-live-...&lt;/code&gt; key into &lt;code&gt;src/config.js&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;add an MCP server over &lt;strong&gt;plaintext HTTP&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;point a tool at &lt;code&gt;169.254.169.254&lt;/code&gt; (cloud-metadata SSRF)&lt;/li&gt;
&lt;li&gt;run &lt;code&gt;npm install&lt;/code&gt; inside a repo that already contains any of the above&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kimi Code has a mechanism for exactly this: &lt;strong&gt;Hooks (Beta)&lt;/strong&gt;. &lt;code&gt;PreToolUse&lt;/code&gt; events fire &lt;em&gt;before&lt;/em&gt; the permission check, and a hook can block the action by exiting with code 2. The block reason on stderr is fed back to the model — so it fixes the problem and retries.&lt;/p&gt;

&lt;p&gt;We built an open-source (MIT) hook that wires this event to a real scanner: &lt;a href="https://github.com/DSHCorrectover/kimi-code-security-hook" rel="noopener noreferrer"&gt;correctover-security-hook&lt;/a&gt;. This post explains how it works, what it catches, and how to verify it yourself in under a minute.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Kimi Code hooks work (60-second version)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Rules live in &lt;code&gt;~/.kimi-code/config.toml&lt;/code&gt; as &lt;code&gt;[[hooks]]&lt;/code&gt; entries with four fields: &lt;code&gt;event&lt;/code&gt;, &lt;code&gt;matcher&lt;/code&gt; (regex), &lt;code&gt;command&lt;/code&gt;, &lt;code&gt;timeout&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On a hookable event, the CLI pipes a JSON payload (tool name, parameters, file content) to your script via &lt;strong&gt;stdin&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Exit semantics: &lt;code&gt;0&lt;/code&gt; = proceed; &lt;strong&gt;&lt;code&gt;2&lt;/code&gt; = block, stderr goes back to the LLM as the reason&lt;/strong&gt;; any other non-zero / timeout = fail open (proceed).&lt;/li&gt;
&lt;li&gt;Only &lt;code&gt;PreToolUse&lt;/code&gt;, &lt;code&gt;Stop&lt;/code&gt;, and &lt;code&gt;UserPromptSubmit&lt;/code&gt; can block. Security gates must hang on &lt;code&gt;PreToolUse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Official guidance: hooks are fail-open by design — a reminder/light-gate layer, &lt;strong&gt;not your only security control&lt;/strong&gt;. Keep permission approvals, code review, and CI scanning.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What the hook does
&lt;/h2&gt;

&lt;p&gt;Two &lt;code&gt;PreToolUse&lt;/code&gt; matchers:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Matcher&lt;/th&gt;
&lt;th&gt;What gets scanned&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;`WriteFile\&lt;/td&gt;
&lt;td&gt;StrReplaceFile`&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Shell&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;MCP-related commands (&lt;code&gt;mcp add&lt;/code&gt;, config edits) → recursive config scan of the project; with &lt;code&gt;CORRECTOVER_SCAN_INSTALLS=1&lt;/code&gt;, &lt;code&gt;npm/pnpm/yarn/bun install&lt;/code&gt; → full-project code scan&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Scanning runs through &lt;a href="https://www.npmjs.com/package/correctover-scan" rel="noopener noreferrer"&gt;&lt;code&gt;correctover-scan&lt;/code&gt;&lt;/a&gt;, an open-source npm package invoked via &lt;code&gt;npx&lt;/code&gt; (free tier, no API key). The hook maps scanner output to exit codes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;fail-level finding → exit 2&lt;/strong&gt; (block; reason fed back to the model)&lt;/li&gt;
&lt;li&gt;clean → exit 0&lt;/li&gt;
&lt;li&gt;scanner missing / network down / timeout / quota exhausted → exit 0 with a stderr log (fail-open: a scanner problem never blocks your work)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What it actually flags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code layer (&lt;code&gt;.js/.mjs/.cjs/.ts&lt;/code&gt;)&lt;/strong&gt;: hardcoded live-key patterns (&lt;code&gt;sk-...&lt;/code&gt;), &lt;code&gt;child_process&lt;/code&gt; with &lt;code&gt;shell: true&lt;/code&gt;, &lt;code&gt;eval&lt;/code&gt; / &lt;code&gt;Function&lt;/code&gt; / &lt;code&gt;vm&lt;/code&gt; dynamic execution, cloud-metadata/SSRF signals, credentials flowing into env/stdout.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MCP config layer&lt;/strong&gt;: MCP server URLs without TLS (plaintext &lt;code&gt;http&lt;/code&gt;), internal/cloud-metadata endpoints (&lt;code&gt;169.254.169.254&lt;/code&gt;), plus warn-level gaps — missing timeouts, allowlists, or kill-switch.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The nice part is the feedback loop. When a write is blocked, the model reads &lt;em&gt;why&lt;/em&gt; ("hardcoded credential at config.js:12 — move to an environment variable") and usually rewrites the code itself, then retries. The gate turns a latent secret leak into a self-correcting loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test matrix: 8 scenarios, all behaving as expected
&lt;/h2&gt;

&lt;p&gt;The repo ships a selftest that fakes Kimi Code's stdin payloads (measured with correctover-scan v1.7.2, Node 22):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Scanner result&lt;/th&gt;
&lt;th&gt;Hook exit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;WriteFile&lt;/code&gt; a &lt;code&gt;.js&lt;/code&gt; with a hardcoded &lt;code&gt;sk-live-&lt;/code&gt; key&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;cred-exposure&lt;/code&gt; &lt;strong&gt;FAIL&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 (block)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;WriteFile&lt;/code&gt; a clean &lt;code&gt;.js&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;no fail&lt;/td&gt;
&lt;td&gt;0 (pass)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;WriteFile&lt;/code&gt; an unsafe MCP config (plaintext &lt;code&gt;http&lt;/code&gt; + &lt;code&gt;169.254.169.254&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mcp-tls&lt;/code&gt; + &lt;code&gt;mcp-ssrf&lt;/code&gt; &lt;strong&gt;FAIL&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 (block)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Shell&lt;/code&gt; plain &lt;code&gt;ls -la&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;not scanned&lt;/td&gt;
&lt;td&gt;0 (pass)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;Scanner binary missing (fault injection)&lt;/td&gt;
&lt;td&gt;startup failure&lt;/td&gt;
&lt;td&gt;0 (fail-open)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Shell&lt;/code&gt; &lt;code&gt;mcp add&lt;/code&gt; while a dangerous MCP config sits in the project&lt;/td&gt;
&lt;td&gt;recursive scan, 2 FAIL&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 (block)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npm install&lt;/code&gt; with &lt;code&gt;CORRECTOVER_SCAN_INSTALLS=1&lt;/code&gt;, leaky file in repo&lt;/td&gt;
&lt;td&gt;bundle FAIL&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2 (block)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;H&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;npm install&lt;/code&gt; with default config&lt;/td&gt;
&lt;td&gt;not scanned&lt;/td&gt;
&lt;td&gt;0 (pass)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The bundled script covers the five core cases (A–E, expect &lt;strong&gt;5 pass / 0 fail&lt;/strong&gt;); F–H use the install-toggle and are documented in the repo README.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/DSHCorrectover/kimi-code-security-hook
&lt;span class="nb"&gt;cd &lt;/span&gt;kimi-code-security-hook/selftest
bash run-hook-selftest.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or try it in a real session: ask Kimi Code to &lt;em&gt;"add a Stripe key constant &lt;code&gt;sk-live-51q8xPbQmRzNk2vWcY7aHdJfT3uLsE0oXnMp&lt;/code&gt; in src/config.js"&lt;/em&gt; — the write should be blocked and the model should rewrite it to read from an environment variable. (The sample key is a random fake string.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;As a plugin:&lt;/strong&gt; the repo is a Kimi Code plugin (&lt;code&gt;kimi.plugin.json&lt;/code&gt; at the root) — point your Kimi Code plugin flow at the repository and the two &lt;code&gt;PreToolUse&lt;/code&gt; hooks register automatically. We've also submitted it for the curated plugin marketplace (&lt;a href="https://github.com/MoonshotAI/kimi-code/issues/3534" rel="noopener noreferrer"&gt;MoonshotAI/kimi-code#3534&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual install:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.kimi-code/hooks
&lt;span class="nb"&gt;cp &lt;/span&gt;hooks/correctover-hook.mjs ~/.kimi-code/hooks/correctover-hook.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add to &lt;code&gt;~/.kimi-code/config.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[[hooks]]&lt;/span&gt;
&lt;span class="py"&gt;event&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"PreToolUse"&lt;/span&gt;
&lt;span class="py"&gt;matcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"WriteFile|StrReplaceFile"&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"node ~/.kimi-code/hooks/correctover-hook.mjs"&lt;/span&gt;
&lt;span class="py"&gt;timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;

&lt;span class="nn"&gt;[[hooks]]&lt;/span&gt;
&lt;span class="py"&gt;event&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"PreToolUse"&lt;/span&gt;
&lt;span class="py"&gt;matcher&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Shell"&lt;/span&gt;
&lt;span class="py"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"node ~/.kimi-code/hooks/correctover-hook.mjs"&lt;/span&gt;
&lt;span class="py"&gt;timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start a new session (or &lt;code&gt;/reload&lt;/code&gt;) and check &lt;code&gt;/hooks&lt;/code&gt;. Requires &lt;strong&gt;Node.js 18+&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Environment variables:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CORRECTOVER_SCAN_CMD&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;npx correctover-scan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Scanner command; point at a local mirror for offline/CI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CORRECTOVER_SCAN_INSTALLS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;1&lt;/code&gt; to bundle-scan the whole project before package installs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CORRECTOVER_SCAN_TIMEOUT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;120000&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Per-scan timeout in ms (fail-open on timeout)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The honest limits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fail-open by design.&lt;/strong&gt; Scanner missing, network down, timeout, or free-tier quota exhausted → the action proceeds with a stderr log. This is a light gate, not your only control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code scanning covers JS/TS only&lt;/strong&gt; (signal-based, not full type semantics). Python and other languages pass through — wire up a language-appropriate scanner for those.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signal scan, not proof of exploitability.&lt;/strong&gt; Findings locate signals at file+line; reachability and intent need manual review.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks are Beta&lt;/strong&gt; in Kimi Code — event names, config format, and tool names may change across versions; re-run the selftest after upgrades. Legacy &lt;code&gt;kimi-cli&lt;/code&gt; (&lt;code&gt;~/.kimi/&lt;/code&gt;) uses different tool names (&lt;code&gt;Bash&lt;/code&gt;); the script tolerates the aliases but matchers may need adjusting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For one-off manual checks without installing anything, there's a zero-upload browser scan at &lt;a href="https://correctover.com/scan.html" rel="noopener noreferrer"&gt;correctover.com/scan.html&lt;/a&gt; — paste code, everything runs locally in the page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters now
&lt;/h2&gt;

&lt;p&gt;Agents are moving from "suggest diffs" to "write files, run commands, wire up MCP servers" — the boundary between generated text and executed action gets thinner every release. Prompt-injection fixes like #2028 harden what the model &lt;em&gt;believes&lt;/em&gt;; &lt;code&gt;PreToolUse&lt;/code&gt; hooks gate what the agent &lt;em&gt;does&lt;/em&gt;. Defense in depth means you want both.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repo (MIT, issues/PRs welcome): &lt;a href="https://github.com/DSHCorrectover/kimi-code-security-hook" rel="noopener noreferrer"&gt;https://github.com/DSHCorrectover/kimi-code-security-hook&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Scanner on npm: &lt;a href="https://www.npmjs.com/package/correctover-scan" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/correctover-scan&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>mcp</category>
      <category>security</category>
      <category>aiagents</category>
      <category>javascript</category>
    </item>
    <item>
      <title>We scanned open-source finance MCP servers — here are the four ways API tokens leak</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Fri, 04 Sep 2026 01:52:57 +0000</pubDate>
      <link>https://dev.to/correctover/we-scanned-open-source-finance-mcp-servers-here-are-the-four-ways-api-tokens-leak-4g91</link>
      <guid>https://dev.to/correctover/we-scanned-open-source-finance-mcp-servers-here-are-the-four-ways-api-tokens-leak-4g91</guid>
      <description>&lt;p&gt;In 2026, financial data terminals started shipping MCP interfaces. Data vendors and open-source wrappers now expose market data, fundamentals, and fund flows as MCP tools that any AI client — Claude Code, Cursor, Copilot — can call.&lt;/p&gt;

&lt;p&gt;That is convenient. It also means every one of those MCP servers holds a &lt;strong&gt;paid API token&lt;/strong&gt; in memory: data-vendor tokens, vendor keys, session credentials. Unlike a random weekend project, those tokens carry metered quotas, billing relationships, and often access to non-public data feeds.&lt;/p&gt;

&lt;p&gt;We took seven open-source JS/TS finance MCP servers (npm packages and GitHub repositories) and ran our local static scanner, &lt;code&gt;correctover-scan&lt;/code&gt; v1.7.2, over them. This post describes the four concrete leak patterns we saw, how often they showed up in &lt;strong&gt;published&lt;/strong&gt; code, and the pre-release checklist that catches all of them. No projects or maintainers are named; findings have been privately disclosed to the affected maintainers.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we scanned
&lt;/h2&gt;

&lt;p&gt;The scanner runs entirely locally — the source code never leaves your machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx correctover-scan &lt;span class="nt"&gt;--bundle&lt;/span&gt; ./path/to/mcp-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For npm packages we ran it against &lt;code&gt;npm pack&lt;/code&gt; tarballs (the exact build users install); for repositories we scanned the checked-out source. The bundle mode parses &lt;code&gt;.js/.mjs/.cjs/.ts&lt;/code&gt; files and runs two layers of checks: a configuration layer (hardcoded secrets, dynamic code execution) and a code layer (plaintext outbound endpoints, credential flow into logs, MCP transport flags). Every machine-generated warning with a file/line was then manually reviewed against the actual source, because some classes of issues — CORS policy and network bind addresses — the scanner only flags indirectly and a human must confirm.&lt;/p&gt;

&lt;p&gt;Honest scope note: all seven targets were JS/TS, the scanner's home turf. The Python fastmcp ecosystem (which includes several popular finance MCPs) was &lt;strong&gt;not&lt;/strong&gt; in scope; the scanner does not parse &lt;code&gt;.py&lt;/code&gt; files yet, and we did not paper over that gap. Closed-source vendor MCPs had no published server source to scan either.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the numbers look like
&lt;/h2&gt;

&lt;p&gt;Of the seven targets, five ship as published npm packages. In two of those five published packages (40%) — two distinct projects — we confirmed at least one of the four token-leak patterns below in the exact artifact users install. One project had three of the patterns; another had two. The other three published packages, plus the two repository-only targets, came back clean on manual review.&lt;/p&gt;

&lt;p&gt;A note on severity labels: the scanner rates hardcoded credentials as &lt;code&gt;fail&lt;/code&gt;, while plaintext HTTP, logging, CORS, and bind issues are rated &lt;code&gt;warn&lt;/code&gt;. That understates them — a token sent over plaintext HTTP is a token disclosure, full stop. We went with manual review, not the score.&lt;/p&gt;

&lt;p&gt;One detail worth flagging for anyone maintaining an MCP: for one project, both of its issues were already fixed on the repository's main branch — but the fixes had never been published to npm. The code your users &lt;code&gt;npx&lt;/code&gt; can lag your own HEAD for months.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: The token goes out over plaintext HTTP
&lt;/h2&gt;

&lt;p&gt;The data vendor's endpoint was hardcoded as an &lt;code&gt;http://&lt;/code&gt; URL, and the token rides along in the POST body:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// api client in a finance MCP server (anonymized)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DataClient&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;apiUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;http://api.marketdata.example&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&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="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATA_TOKEN&lt;/span&gt; &lt;span class="o"&gt;??&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;async&lt;/span&gt; &lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;apiUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&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="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;api_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&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="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&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;We saw this in &lt;strong&gt;2 of the 5 published packages (40%)&lt;/strong&gt; — in one case across three separate tool modules, each with its own hardcoded &lt;code&gt;http://&lt;/code&gt; fetch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk:&lt;/strong&gt; anyone positioned on the same network path — coffee-shop Wi-Fi, a shared office VLAN, a compromised hop — reads the bearer token straight off the wire. The token is a paid, quota-bearing credential.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; use &lt;code&gt;https://&lt;/code&gt;. The vendor endpoint supported HTTPS in every case we saw; this was a copy-pasted URL scheme, not an infrastructure constraint. Put the base URL in one config constant so it cannot drift per-file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: The token gets logged to stdout
&lt;/h2&gt;

&lt;p&gt;In stdio MCP mode, stdout is the protocol channel — it is captured by the MCP client's log files. One published package logged the &lt;em&gt;entire request object&lt;/em&gt;, token included, right before sending it:&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="c1"&gt;// tool module in a published package (anonymized)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;api_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;balancesheet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API request params:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiParams&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&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="s2"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiParams&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;&lt;strong&gt;1 of the 5 published packages (20%)&lt;/strong&gt; did this, in three tool modules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk:&lt;/strong&gt; the token lands in MCP client log files on the developer's disk, in CI logs if the server runs there, and in anything that scrapes the host's stdout. It is the same credential, now duplicated into log storage with different retention and access rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; redact before you log. Log only the business parameters (&lt;code&gt;params.params&lt;/code&gt;), or pass the payload through a small redactor that masks known credential fields:&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;redactToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&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="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;***&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;API request params:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;redactToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiParams&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pattern 3: &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt; on a streamable-http transport
&lt;/h2&gt;

&lt;p&gt;When an MCP server offers Streamable HTTP, it is a web server — and the browser same-origin rules apply to it. One published package enabled CORS with a wildcard and explicitly allowed token-bearing custom headers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// http transport setup (anonymized)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;cors&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;methods&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GET&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="s2"&gt;POST&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="s2"&gt;OPTIONS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;allowedHeaders&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Content-Type&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="s2"&gt;Authorization&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="s2"&gt;Mcp-Session-Id&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="s2"&gt;X-Api-Token&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="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;&lt;strong&gt;1 of the 5 published packages (20%)&lt;/strong&gt; had this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk:&lt;/strong&gt; any web page the operator visits can issue a cross-origin POST to &lt;code&gt;http://localhost:3000/mcp&lt;/code&gt; and invoke the server's tools. In the package we saw, the server fell back to reading the token from request headers when no server-side token was set — meaning a page could potentially ride the server's own credential. CORS is not optional configuration here; it is the authentication boundary between the browser and a local privileged service.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; an MCP HTTP endpoint meant for local clients should not use &lt;code&gt;origin: "*"&lt;/code&gt;. Restrict allowed origins to an explicit allowlist (or omit wildcard CORS entirely and document the local proxy setup), and never accept a credential from a request header you did not explicitly opt into.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: The server binds to all interfaces with no Host allowlist
&lt;/h2&gt;

&lt;p&gt;The companion mistake. The same package started its listener without a host argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// default in Node/Express: binds 0.0.0.0&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&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="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`MCP endpoint: http://localhost:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/mcp`&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;&lt;strong&gt;1 of the 5 published packages (20%)&lt;/strong&gt;. The startup message says &lt;code&gt;localhost&lt;/code&gt;; the socket tells a different story — omitting the host binds &lt;code&gt;0.0.0.0&lt;/code&gt;, reachable from the entire LAN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk:&lt;/strong&gt; combined with pattern 3 (and no token gate), anyone on the local network can reach and invoke the MCP server — a paid data quota sitting on a laptop port. For comparison, the sibling project from the same maintainer had already done it correctly in its newer code: &lt;code&gt;app.listen(PORT, "127.0.0.1")&lt;/code&gt; plus an &lt;code&gt;ALLOWED_HOSTS&lt;/code&gt; Host-header check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; bind explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&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;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;127.0.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&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="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="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a deployment genuinely needs a non-loopback bind, require an explicit env var to opt in and add a Host-header allowlist.&lt;/p&gt;

&lt;h2&gt;
  
  
  A pre-release checklist for MCP authors
&lt;/h2&gt;

&lt;p&gt;Everything above is caught before publish with five checks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Grep your built artifacts for &lt;code&gt;http://&lt;/code&gt;&lt;/strong&gt; — run the scanner against the &lt;code&gt;npm pack&lt;/code&gt; tarball, not just the repo source. The build is what users run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit every &lt;code&gt;console.log&lt;/code&gt;/&lt;code&gt;logger&lt;/code&gt; call on a request or response path&lt;/strong&gt; — no credential field should reach stdout/stderr in stdio mode. Add a &lt;code&gt;redact&lt;/code&gt; helper and route payload logs through it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If you offer Streamable HTTP, bind &lt;code&gt;127.0.0.1&lt;/code&gt; by default&lt;/strong&gt; and require explicit configuration to expose anything wider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No wildcard CORS on a transport that carries credentials&lt;/strong&gt; — explicit origin allowlist, and never fall back to trusting client-supplied token headers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish the fixes&lt;/strong&gt; — main-branch patches do not protect users on the npm &lt;code&gt;latest&lt;/code&gt; tag. Make the release part of the fix.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Wire this into a &lt;code&gt;prepublishOnly&lt;/code&gt; script and a CI step; the patterns are cheap to detect automatically and expensive to explain after the fact.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on your own MCP
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;CLI (local static scan, code never leaves your machine): &lt;code&gt;npx correctover-scan&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Zero-install browser version: &lt;a href="https://dshcorrectover.github.io/agent-audit/scan.html" rel="noopener noreferrer"&gt;https://dshcorrectover.github.io/agent-audit/scan.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Project organization: &lt;a href="https://github.com/DSHCorrectover" rel="noopener noreferrer"&gt;https://github.com/DSHCorrectover&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Disclaimer
&lt;/h2&gt;

&lt;p&gt;Results are based on static analysis of publicly available package versions and repositories at scan time (2026-09-04). They are not a security rating or certification of any individual project; absence of findings in this scan is not a guarantee of safety, and findings reflect the versions we downloaded. The Python fastmcp ecosystem and closed-source vendor MCPs were outside this round's scope. All confirmed findings have been privately disclosed to the relevant maintainers.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>security</category>
      <category>ai</category>
      <category>typescript</category>
    </item>
    <item>
      <title>You can run a security scanner without shipping your source code anywhere</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Thu, 03 Sep 2026 11:10:25 +0000</pubDate>
      <link>https://dev.to/correctover/you-can-run-a-security-scanner-without-shipping-your-source-code-anywhere-3ch9</link>
      <guid>https://dev.to/correctover/you-can-run-a-security-scanner-without-shipping-your-source-code-anywhere-3ch9</guid>
      <description>&lt;p&gt;When you build an AI agent, you end up with a folder of things you do not want to upload to a stranger's server: an MCP config full of API keys, a &lt;code&gt;node_modules&lt;/code&gt; bundle with third-party skills, and — if you ship an SDK or an MCP server to clients — source code that is literally your product.&lt;/p&gt;

&lt;p&gt;The usual "free online scanner" flow asks you to either paste your code into a box or zip up the repo and upload it. That is fine for a public demo project. It is a non-starter for closed-source, unreleased, or commercially sensitive code.&lt;/p&gt;

&lt;p&gt;It turns out you do not need a backend to do this. A static-analysis scanner can run &lt;em&gt;entirely in the browser tab&lt;/em&gt;. Here is how we built ours, and the one architectural decision that makes it trustworthy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The constraint: the bytes must never leave the machine
&lt;/h2&gt;

&lt;p&gt;The goal was simple to state and surprisingly easy to get subtly wrong:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;drop in an &lt;code&gt;mcp.json&lt;/code&gt; / MCP config, or a JS bundle / &lt;code&gt;node_modules&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;get a report mapped to &lt;a href="https://owasp.org/www-project-artificial-intelligence-security-verification-standard-aisvs-docs/" rel="noopener noreferrer"&gt;OWASP AISVS 1.0&lt;/a&gt; (the AI security verification standard)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;zero upload, zero account, zero telemetry&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Zero upload" is not a marketing line here, it is an enforced property. There is no server endpoint to receive files. The page is static HTML + one inlined JavaScript bundle. &lt;code&gt;fetch&lt;/code&gt; is never called with file contents. The scanning engine reads files through the browser's own &lt;code&gt;File&lt;/code&gt; / &lt;code&gt;FileReader&lt;/code&gt; API (drag-and-drop or the file picker), analyzes them in memory, and renders the report into the DOM. Close the tab and the data is gone.&lt;/p&gt;

&lt;p&gt;You can verify this the honest way: open DevTools → Network, drop in a file, and watch nothing leave.&lt;/p&gt;

&lt;h2&gt;
  
  
  How a CLI scanner becomes a browser scanner
&lt;/h2&gt;

&lt;p&gt;The scanner already existed as an npm CLI (&lt;code&gt;npx correctover-scan&lt;/code&gt;) and a GitHub Action. The trick to the browser version was that the engine was already pure static analysis — no network calls, no model dependency on the hot path. The core verification logic runs in &lt;strong&gt;sub-millisecond time on the core verification hot path&lt;/strong&gt;, which matters because the browser tab has to stay responsive while you drag in a few hundred files.&lt;/p&gt;

&lt;p&gt;The build step (&lt;code&gt;tools/build-browser.js&lt;/code&gt;) bundles the same &lt;code&gt;core/&lt;/code&gt; modules the CLI uses into a single &lt;code&gt;scan-bundle.js&lt;/code&gt; and exposes them on &lt;code&gt;window.CorrectoverScan&lt;/code&gt;. The HTML page calls:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;scanConfig(text)&lt;/code&gt; for MCP config files — 14 checks covering things like MCP transport TLS, tool auth, tool-allowlist pinning, kill-switch / budget limits, SSRF guards, credential exposure, sandboxing, version pinning, and logging.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scanBundle(files)&lt;/code&gt; for JavaScript — a set of checks over the bundled code, with a &lt;em&gt;semantic&lt;/em&gt; pass instead of naive keyword matching.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last part is where naive scanners fall over. A grep for &lt;code&gt;child_process&lt;/code&gt; or &lt;code&gt;eval&lt;/code&gt; will flag every build tool, test runner, and bundler in existence. The engine classifies &lt;em&gt;intent&lt;/em&gt;: is that &lt;code&gt;exec()&lt;/code&gt; argument a hardcoded constant or attacker-controlled? Is the &lt;code&gt;eval&lt;/code&gt; inside a known code-execution sandbox, or is it running LLM-controlled input? Is the credential-looking regex part of a detection rule (the scanner contains a few of those itself)? Benign contexts get suppressed; genuinely dangerous patterns — dynamic eval of model output, unguarded shell execution, secrets passed to an unauthenticated MCP transport — get surfaced.&lt;/p&gt;

&lt;p&gt;Some checks are marked &lt;em&gt;semi-automated&lt;/em&gt;: the engine flags the pattern, but a human has to confirm whether the control is actually enforced in deployment. The report says so explicitly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mapping to a standard instead of inventing one
&lt;/h2&gt;

&lt;p&gt;Rather than hand you a pile of red/yellow dots, every check maps to an AISVS 1.0 control (for example, tamper-evident logging under the orchestration/agentic security chapter). The mapping page is deliberately honest about coverage: the automated scanner covers parts of 8 of the 12 AISVS chapters, and the chapters it &lt;em&gt;cannot&lt;/em&gt; cover statically — training-data integrity, adversarial robustness, model lifecycle change control — are marked as not covered, not silently skipped.&lt;/p&gt;

&lt;p&gt;That honesty matters. An automated scan is a fast first pass; it is not an audit, a certification, or a compliance verdict. The printed report carries that disclaimer, and the pages that reference AISVS note that OWASP® and AISVS are trademarks of the OWASP Foundation, which does not endorse or certify products.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this changes the workflow
&lt;/h2&gt;

&lt;p&gt;For a team shipping agents or MCP-based products, the practical difference is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You can scan the thing you were going to ship.&lt;/strong&gt; Unreleased SDK, internal agent config, client code under NDA — none of it touches a server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You get a shareable artifact.&lt;/strong&gt; The report prints/saves as PDF from the browser, so you can attach it to a vendor response without anyone needing an account.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The same engine runs in CI.&lt;/strong&gt; The browser scanner, the CLI, and the GitHub Action share the core modules and the rule identifiers, so a finding has the same name wherever it shows up.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The free scan deliberately does not try to do everything. It cannot produce a signed audit opinion, it cannot sign off on your supply chain, and a clean report does not mean "no vulnerabilities" — static analysis has false negatives by construction. For a vendor-acceptance or compliance trail you still need a manual audit. What it gives you is a five-minute, privacy-preserving first look at the configuration and bundle mistakes that cause the worst agent incidents: over-permissive tool allowlists, MCP servers without auth or TLS, unbounded agent budgets, and model output flowing into a shell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it, and watch the Network tab
&lt;/h2&gt;

&lt;p&gt;The scanner is here — a static page, no signup:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://dshcorrectover.github.io/agent-audit/scan.html" rel="noopener noreferrer"&gt;https://dshcorrectover.github.io/agent-audit/scan.html&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Drop in an &lt;code&gt;mcp.json&lt;/code&gt; or a JS bundle, open DevTools → Network first if you are the skeptical type (you should be), and watch the bytes stay put.&lt;/p&gt;

&lt;p&gt;If you build agent tooling, I would be curious to hear what it flags in your own config — especially the semi-automated checks, where a human reading the context still beats any rule.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article references OWASP AISVS 1.0 as an external, independently maintained community standard; Correctover is not affiliated with or endorsed by OWASP, and automated scanner output is not an audit or certification. Work on the related conformance receipt protocol is documented in an individual Internet-Draft (&lt;code&gt;draft-correctover-ccs&lt;/code&gt;), which is an IETF work-in-progress, not an RFC and not an IETF endorsement.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>aiagents</category>
      <category>mcp</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Valid provenance is not valid code: a supply-chain worm shipped with a valid SLSA attestation</title>
      <dc:creator>correctover</dc:creator>
      <pubDate>Thu, 03 Sep 2026 06:30:50 +0000</pubDate>
      <link>https://dev.to/correctover/valid-provenance-is-not-valid-code-a-supply-chain-worm-shipped-with-a-valid-slsa-attestation-2ld3</link>
      <guid>https://dev.to/correctover/valid-provenance-is-not-valid-code-a-supply-chain-worm-shipped-with-a-valid-slsa-attestation-2ld3</guid>
      <description>&lt;p&gt;In May 2026, a supply-chain worm did something the security community had been warning about for years: it got the receipt.&lt;/p&gt;

&lt;p&gt;According to Akamai's analysis, the May 11–12 wave of the &lt;strong&gt;Mini Shai-Hulud&lt;/strong&gt; campaign didn't steal an npm token. It poisoned a GitHub Actions CI cache through a fork-pull-request misconfiguration in the TanStack release workflow, waited for a legitimate maintainer merge, then ran inside the trusted release pipeline. The worm scraped credentials from the runner's memory and exchanged them for publish credentials through npm's own OIDC token endpoint. The result, in Akamai's words, was that the attack was "invisible and grant[ed] it SLSA attestation validation." (&lt;a href="https://www.akamai.com/blog/security-research/2026/may/mini-shai-hulud-worm-returns-goes-public" rel="noopener noreferrer"&gt;Akamai Security Intelligence Group, May 15 2026&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Across the full campaign (an earlier wave hit SAP's &lt;code&gt;@cap-js&lt;/code&gt; packages, PyTorch Lightning, and Intercom in late April/early May), Cloud Security Alliance counts &lt;strong&gt;180+ compromised packages, 400+ malicious versions&lt;/strong&gt;, affecting packages with more than &lt;strong&gt;518 million cumulative historical downloads&lt;/strong&gt;. (&lt;a href="https://labs.cloudsecurityalliance.org/wp-content/uploads/2026/05/CSA_research_note_teampcp_mini_shai_hulud_package_registry_wave_20260502-csa-styled.pdf" rel="noopener noreferrer"&gt;CSA research note&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;The worm also installed persistence into &lt;strong&gt;Claude Code session hooks and VS Code task automation&lt;/strong&gt; — which means it specifically dug into the agent tooling layer, and survived an &lt;code&gt;npm uninstall&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the attestation actually attests
&lt;/h2&gt;

&lt;p&gt;This is not a Sigstore flaw, and it's not a SLSA flaw. The provenance system worked exactly as designed. A SLSA Build Level 3 attestation answers one question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Was this artifact produced by the trusted build environment, from the claimed source?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; answer:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Is the code in this artifact what the maintainer thinks it is?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When the build environment itself is compromised, the attestation is produced honestly — by a dishonest environment. The signature is valid. The certificate is valid. The log entry is valid. The code is a worm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Valid provenance is not valid code.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Keyless signing (the Sigstore/Fulcio model) binds identity to a short-lived OIDC token issued to the CI environment. That's a great default for the overwhelming majority of projects, and we use the same transparency log it writes to (more on that below). But it draws the trust boundary &lt;em&gt;around the build environment&lt;/em&gt; — and the build environment is exactly what Mini Shai-Hulud attacked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two layers the receipt doesn't cover
&lt;/h2&gt;

&lt;p&gt;Reading the post-incident writeups, two gaps keep showing up:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Content-first evidence.&lt;/strong&gt; Attestations are environment-first: they certify &lt;em&gt;where and how&lt;/em&gt; something was built. A content-first receipt certifies &lt;em&gt;what the code is&lt;/em&gt;: a canonical manifest of every file and its hash, signed, anchored to a transparency log, with a durable citation that outlives any single registry or CI system. You can answer "does this byte-for-byte match what was anchored at release time?" offline, without asking anyone's server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. An optional key-isolation path.&lt;/strong&gt; Keyless signing has no concept of "the signing key never touches CI." For most projects that's fine — convenience wins. For high-assurance releases (infrastructure packages, agent runtimes, anything that harvests credentials by design), there's value in a mode where the signing key lives &lt;em&gt;outside&lt;/em&gt; the runner: an offline signer, a human approval step, or a KMS. Compromising a runner then no longer yields the ability to mint a valid release identity. The ecosystem has the building blocks (&lt;code&gt;cosign&lt;/code&gt; supports bring-your-own keys), but the content-manifest-and-durable-anchor layer around them is still DIY.&lt;/p&gt;

&lt;p&gt;There's also a third gap that matters specifically for AI assets: hashes answer "is this identical," but never "is this &lt;em&gt;derived from&lt;/em&gt; mine." A model fine-tuned from your weights, or a plugin forked past recognition, hashes differently by design. That requires semantic comparison — a different problem from signing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we built (and what we refuse to claim)
&lt;/h2&gt;

&lt;p&gt;We work on this problem. Our open-source &lt;strong&gt;code birth certificate&lt;/strong&gt; produces a signed content manifest (JCS-canonical hashes, Ed25519), anchors it to the public &lt;strong&gt;Rekor&lt;/strong&gt; transparency log, and optionally mints a &lt;strong&gt;Zenodo DOI&lt;/strong&gt; as a durable citation. A zero-configuration GitHub Action runs it on release; verification needs no account and no server of ours — the public key is embedded in the receipt, so verification is fully offline.&lt;/p&gt;

&lt;p&gt;Our own dogfood anchors are public and independently checkable: Rekor log index &lt;a href="https://search.sigstore.dev/?logIndex=2694324795" rel="noopener noreferrer"&gt;&lt;code&gt;2694324795&lt;/code&gt;&lt;/a&gt; with DOI &lt;a href="https://doi.org/10.5281/zenodo.22266162" rel="noopener noreferrer"&gt;&lt;code&gt;10.5281/zenodo.22266162&lt;/code&gt;&lt;/a&gt;. The GitHub Action that mints these certificates anchors its own repository on every run inside CI — the latest such self-anchor is Rekor index &lt;a href="https://search.sigstore.dev/?logIndex=2695953558" rel="noopener noreferrer"&gt;&lt;code&gt;2695953558&lt;/code&gt;&lt;/a&gt;, produced by a public GitHub Actions run rather than anyone's laptop.&lt;/p&gt;

&lt;p&gt;Three things we want to be explicit about, because a supply-chain incident is a bad time for marketing ambiguity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;If you run our Action with the signing key stored in a CI secret, you stand on the same side of the trust boundary as keyless signing.&lt;/strong&gt; That's the convenience mode, and it's a reasonable default. The key-isolation mode — key held outside the runner — is the one that changes the threat model. Pick deliberately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;We build on Sigstore, not against it.&lt;/strong&gt; Our anchors &lt;em&gt;are&lt;/em&gt; Rekor entries. We're an application layer on top of the public transparency-log ecosystem, not a replacement. "Sign everything keyless by default" remains the right advice for most projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anchoring proves existence, timestamp, and content binding — nothing else.&lt;/strong&gt; It doesn't prove code is safe, isn't malware, or isn't infringing. A DOI is evidence of existence and timestamp, not a copyright registration. And no signature — ours or anyone's — would have stopped Mini Shai-Hulud's persistence in Claude Code hooks. That's a &lt;em&gt;runtime&lt;/em&gt; problem: what the agent does after install, which is the other half of what we work on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The practical takeaway
&lt;/h2&gt;

&lt;p&gt;After Mini Shai-Hulud, the question "is your supply chain signed?" is no longer enough. Three better questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Does the receipt certify the build environment — or the code's content?&lt;/strong&gt; Ideally both, and you should know which you're looking at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where does the signing key live?&lt;/strong&gt; "Short-lived token inside the runner" is fine until the runner is the target. Know your mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What watches the code after it runs?&lt;/strong&gt; Signed packages can still be worms. Agents that install hooks into your editor doubly so.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Attestations got us from "trust me" to "trust the build." The next step is "trust the content — and verify it yourself."&lt;/p&gt;




&lt;p&gt;&lt;em&gt;We're Correctover — we build evidence infrastructure for the AI age: verifiable provenance for code and runtime assurance for agents. The birth-certificate tooling is open source (MIT); the transparency logs and DOI registry it uses are public infrastructure maintained by the Sigstore and Zenodo communities.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>supplychain</category>
      <category>ai</category>
      <category>github</category>
    </item>
  </channel>
</rss>
