registry  /  @goplausible/ac2-plugin-codex  /  0.2.0

@goplausible/ac2-plugin-codex@0.2.0

AC2 plugin for Codex — connects Codex to AC2 wallet peers over Liquid Auth (FIDO2) + WebRTC DataChannels.

AI Security Review

scanned 19h ago · by lpm-firewall-ai

Review flagged AI-agent configuration or capability changes. This remains warn-only unless evidence shows foreign-agent hijack through preinstall/install/postinstall, hidden persistence, exfiltration, remote code execution, or other concrete malicious behavior.

Static reason
High-risk behavior combination matched malicious policy.
Trigger
User installs/enables the Codex plugin or invokes the MCP server/bin, then pairs an AC2 wallet or uses x402 tools.
Impact
A paired wallet user can drive a separate Codex conversation and approve signing/payment/file/command requests; misuse depends on user pairing and approvals rather than hidden package behavior.
Mechanism
Codex MCP wallet bridge with WebRTC pairing, local app-server child process, tokenized loopback proxy, and user-approved wallet signing/payments.
Policy narrative
When run as the MCP server, the package can pair Codex with an AC2 wallet, relay wallet-originated chat into a spawned `codex app-server`, and expose signing/x402 payment tools. The reviewed behavior is explicit plugin functionality and appears gated by user pairing and wallet approval, with no npm install-time mutation of foreign agent surfaces.
Rationale
Static inspection shows substantial agent and wallet capabilities, including spawning Codex and paid HTTP tools, but they are documented, user-invoked plugin behavior with no install-time hook or hidden credential exfiltration. Because this is a high-impact agent extension/payment bridge, warn rather than block.
Evidence
package.jsondist/server.js.mcp.json.codex-plugin/plugin.jsonREADME.mdskills/ac2/SKILL.md~/.codex/ac2/tool-proxy.json~/.codex/ac2/codex-thread.json~/.codex/ac2/attachments/*
Network endpoints4
liquidauth.goplausible.xyz127.0.0.1lora.algokit.ioallo.info

Decision evidence

public snapshot
AI called this Suspicious at 83.0% confidence as Dangerous Capability with medium false-positive risk.
Evidence for warning
  • dist/server.js spawns `codex app-server` with `AC2_PASSIVE_CHILD=1` for wallet-originated chat bridge
  • dist/server.js exposes wallet signing, x402 paid HTTP, and remote approval relay tools over MCP/WebRTC
  • dist/server.js writes runtime state files such as `tool-proxy.json`, `codex-thread.json`, and attachments under the AC2 state dir
  • skills/ac2/SKILL.md instructs agents to use `make_http_request_with_x402_ac2` for paid HTTP resources using the connected wallet
Evidence against
  • package.json has no install/postinstall hook; only `prepublishOnly` build hook is publisher-side
  • No source evidence of private key or mnemonic harvesting; README and tool code describe wallet-held keys and user approval
  • The `.mcp.json` and `.codex-plugin/plugin.json` are packaged platform manifests, not lifecycle-written foreign control-surface mutations
  • Loopback proxy uses random token and blocks `ac2_pair`/`ac2_forget` from passive child forwarding
  • Risky network/payment behavior is exposed as documented user-invoked tools, not automatic import/install-time execution
Behavioral surface
Source
ChildProcessCryptoEnvironmentVarsEvalFilesystemNativeBindingsNetworkWebSocket
Supply chain
HighEntropyStringsMinifiedObfuscatedProtestwareTelemetryUrlStrings
ManifestNo manifest risk signals triggered.
scanned 1 file(s), 1.87 MB of source, external domains: 127.0.0.1, ac2.io, api.devnet.solana.com, api.mainnet-beta.solana.com, api.testnet.solana.com, developer.mozilla.org, example.x402.goplausible.xyz, facilitator.goplausible.xyz, github.com, json-schema.org, liquidauth.goplausible.xyz, mainnet-api.4160.nodely.dev, raw.githubusercontent.com, sola.na, testnet-api.4160.nodely.dev, viem.sh, www.w3.org

Source & flagged code

7 flagged · loading source
dist/server.jsView file
6const __filename = __ac2FileURLToPath(import.meta.url); L7: const __dirname = __ac2Dirname(__filename); L8: var The=Object.create;var w3=Object.defineProperty;var Rhe=Object.getOwnPropertyDescriptor;var Ohe=Object.getOwnPropertyNames;var Nhe=Object.getPrototypeOf,$he=Object.prototype.has... L9: `:""},this._extScope=e,this._scope=new sc.Scope({parent:e}),this._nodes=[new pT]}toString(){return this._root.render(this.opts)}name(e){return this._scope.name(e)}scopeName(e){retu... L10: || (${a} == "string" && ${i} && ${i} == +${i})`).assign(s,(0,Ir._)`+${i}`);return;case"integer":n.elseIf((0,Ir._)`${a} === "boolean" || ${i} === null L11: || (${a} === "string" && ${i} && ${i} == +${i} && !(${i} % 1))`).assign(s,(0,Ir._)`+${i}`);return;case"boolean":n.elseIf((0,Ir._)`${i} === "false" || ${i} === 0 || ${i} === null`).... L12: || ${a} === "boolean" || ${i} === null`).assign(s,(0,Ir._)`[${i}]`)}}}function y6e({gen:t,parentData:e,parentDataProperty:r},n){t.if((0,Ir._)`${e} !== undefined`,()=>t.assign((0,Ir... L13: missingProperty: ${n}, ... L18: ${new this._window.XMLSerializer().serializeToString(h)}`;return typeof Blob>"u"||this._options.jsdom?Buffer.from(b):new Blob([b],{type:w})}return
Critical
Wallet Drain

Source uses private key material to transfer cryptocurrency funds.

dist/server.jsView on unpkg · L6
106Trigger-reachable chain: manifest.bin -> dist/server.js L106: `)} L107: `}function KIe(t,e){if(!t.appCallTrace||!t.disassembly)return"";let r=e;return e!==void 0?r=e:r={maxValueWidth:uD,topOfStackFirst:!1},xte(t.appCallTrace,t.disassembly,r)}function Z... L108: `);return{content:[yn(a)],details:o}}}}var EMe=Be.Object({refresh:Be.Optional(Be.Boolean({description:"DEPRECATED / no-op (plugin v0.0.84+): every `ac2_capabilities` call now hits ... ... L112: `);return{content:[yn(d)],details:a}}}}var OL=Be.Object({message_base64:Be.String({minLength:1,description:"Base64 of the raw bytes that were signed (same bytes the signer received... L113: ${t.length}`,r=new TextEncoder().encode(e),n=new Uint8Array(r.length+t.length);return n.set(r,0),n.set(t,r.length),Hs(n)}function $L(t,e){let r=e.slice(0,32),n=e.slice(32,64),i=e[6... L114: `)}var y3={type:"object",properties:{},additionalProperties:!1};function $h(t){return{content:[{type:"text",text:t}]}}async function phe(t){if(!t)return $h("No active pairing invit... ... L116: `));if(e.active)return $h(`Already connected (peer=${e.active.peerDid}). Run ac2_status for details.`);if(e.pendingInvitation&&!dhe(e.pendingInvitation))return phe(e.pen…
Critical
Trigger Reachable Dangerous Capability

A package entrypoint or install-time lifecycle script reaches a source file with blocking dangerous behavior.

dist/server.jsView on unpkg · L106
120`)}catch(o){process.stderr.write(`[ac2] could not write tool-proxy endpoint: ${String(o)} L121: `)}}),r}function yhe(){try{uLe(HL())}catch{}}function vhe(t,e){let r=fLe();if(!r)return Promise.resolve({content:[{type:"text",text:"The AC2 wallet link host is not reachable (no t... L122: `))i.trim()&&process.stderr.write(`[ac2] app-server: ${i}
High
Child Process

Package source references child process execution.

dist/server.jsView on unpkg · L120
112`);return{content:[yn(d)],details:a}}}}var OL=Be.Object({message_base64:Be.String({minLength:1,description:"Base64 of the raw bytes that were signed (same bytes the signer received... L113: ${t.length}`,r=new TextEncoder().encode(e),n=new Uint8Array(r.length+t.length);return n.set(r,0),n.set(t,r.length),Hs(n)}function $L(t,e){let r=e.slice(0,32),n=e.slice(32,64),i=e[6... L114: `)}var y3={type:"object",properties:{},additionalProperties:!1};function $h(t){return{content:[{type:"text",text:t}]}}async function phe(t){if(!t)return $h("No active pairing invit... ... L120: `)}catch(o){process.stderr.write(`[ac2] could not write tool-proxy endpoint: ${String(o)} L121: `)}}),r}function yhe(){try{uLe(HL())}catch{}}function vhe(t,e){let r=fLe();if(!r)return Promise.resolve({content:[{type:"text",text:"The AC2 wallet link host is not reachable (no t... L122: `))i.trim()&&process.stderr.write(`[ac2] app-server: ${i}
High
Same File Env Network Execution

A single source file combines environment access, network access, and code or shell execution; review context before blocking.

dist/server.jsView on unpkg · L112
106`)} L107: `}function KIe(t,e){if(!t.appCallTrace||!t.disassembly)return"";let r=e;return e!==void 0?r=e:r={maxValueWidth:uD,topOfStackFirst:!1},xte(t.appCallTrace,t.disassembly,r)}function Z... L108: `);return{content:[yn(a)],details:o}}}}var EMe=Be.Object({refresh:Be.Optional(Be.Boolean({description:"DEPRECATED / no-op (plugin v0.0.84+): every `ac2_capabilities` call now hits ... ... L112: `);return{content:[yn(d)],details:a}}}}var OL=Be.Object({message_base64:Be.String({minLength:1,description:"Base64 of the raw bytes that were signed (same bytes the signer received... L113: ${t.length}`,r=new TextEncoder().encode(e),n=new Uint8Array(r.length+t.length);return n.set(r,0),n.set(t,r.length),Hs(n)}function $L(t,e){let r=e.slice(0,32),n=e.slice(32,64),i=e[6... L114: `)}var y3={type:"object",properties:{},additionalProperties:!1};function $h(t){return{content:[{type:"text",text:t}]}}async function phe(t){if(!t)return $h("No active pairing invit... ... L116: `));if(e.active)return $h(`Already connected (peer=${e.active.peerDid}). Run ac2_status for details.`);if(e.pendingInvitation&&!dhe(e.pendingInvitation))return phe(e.pendingInvitat... L117: `)}async function oLe(t,e){return e.
High
Command Output Exfiltration

Source combines command execution, command-output handling, and outbound requests; review data flow before blocking.

dist/server.jsView on unpkg · L106
11|| (${a} === "string" && ${i} && ${i} == +${i} && !(${i} % 1))`).assign(s,(0,Ir._)`+${i}`);return;case"boolean":n.elseIf((0,Ir._)`${i} === "false" || ${i} === 0 || ${i} === null`).... L12: || ${a} === "boolean" || ${i} === null`).assign(s,(0,Ir._)`[${i}]`)}}}function y6e({gen:t,parentData:e,parentDataProperty:r},n){t.if((0,Ir._)`${e} !== undefined`,()=>t.assign((0,Ir... L13: missingProperty: ${n},
Low
Eval

Package source references a known benign dynamic code generation pattern.

dist/server.jsView on unpkg · L11
6const __filename = __ac2FileURLToPath(import.meta.url); L7: const __dirname = __ac2Dirname(__filename); L8: var The=Object.create;var w3=Object.defineProperty;var Rhe=Object.getOwnPropertyDescriptor;var Ohe=Object.getOwnPropertyNames;var Nhe=Object.getPrototypeOf,$he=Object.prototype.has... L9: `:""},this._extScope=e,this._scope=new sc.Scope({parent:e}),this._nodes=[new pT]}toString(){return this._root.render(this.opts)}name(e){return this._scope.name(e)}scopeName(e){retu... L10: || (${a} == "string" && ${i} && ${i} == +${i})`).assign(s,(0,Ir._)`+${i}`);return;case"integer":n.elseIf((0,Ir._)`${a} === "boolean" || ${i} === null L11: || (${a} === "string" && ${i} && ${i} == +${i} && !(${i} % 1))`).assign(s,(0,Ir._)`+${i}`);return;case"boolean":n.elseIf((0,Ir._)`${i} === "false" || ${i} === 0 || ${i} === null`).... L12: || ${a} === "boolean" || ${i} === null`).assign(s,(0,Ir._)`[${i}]`)}}}function y6e({gen:t,parentData:e,parentDataProperty:r},n){t.if((0,Ir._)`${e} !== undefined`,()=>t.assign((0,Ir... L13: missingProperty: ${n}, ... L18: ${new this._window.XMLSerializer().serializeToString(h)}`;return typeof Blob>"u"||this._options.jsdom?Buffer.from(b):new Blob([b],{type:w})}return
Low
Weak Crypto

Package source references weak cryptographic algorithms.

dist/server.jsView on unpkg · L6

Findings

2 Critical4 High4 Medium8 Low
CriticalWallet Draindist/server.js
CriticalTrigger Reachable Dangerous Capabilitydist/server.js
HighChild Processdist/server.js
HighSame File Env Network Executiondist/server.js
HighCommand Output Exfiltrationdist/server.js
HighObfuscated
MediumNetwork
MediumEnvironment Vars
MediumProtestware
MediumStructural Risk Force Deep Review
LowNon Install Lifecycle Scripts
LowScripts Present
LowEvaldist/server.js
LowWeak Cryptodist/server.js
LowFilesystem
LowHigh Entropy Strings
LowTelemetry
LowUrl Strings