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

@goplausible/ac2-plugin-codex@0.2.4

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

AI Security Review

scanned 2h ago · by lpm-firewall-ai

LPM treats this as warn-only first-party agent extension lifecycle risk. No confirmed malicious attack surface was found. The package is a high-privilege Codex agent extension for remote wallet chat, approvals, signing, and paid HTTP calls, so misuse risk exists after explicit installation/pairing.

Static reason
High-risk behavior combination matched malicious policy.
Trigger
User explicitly installs/enables the Codex plugin and runs/pairs the MCP server, then invokes AC2 wallet or x402 tools.
Impact
Remote wallet chat can drive a Codex thread and request approvals, signatures, or x402 payments, but inspected source keeps these behind plugin setup and wallet/user approval flows.
Mechanism
first-party Codex MCP wallet bridge with local state, child Codex app-server, WebRTC/Liquid Auth, and user-approved signing/payment tools
Rationale
Source inspection shows a legitimate but powerful first-party Codex wallet bridge; the risky primitives are package-aligned and user/plugin initiated, with no install-time hijack or concrete theft/exfiltration chain. Because it grants remote agent-extension and payment/signing capabilities after setup, a warn-level lifecycle/capability risk is more appropriate than a publish 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/*~/.codex/config.toml
Network endpoints5
facilitator.goplausible.xyzmainnet-api.4160.nodely.devtestnet-api.4160.nodely.devlocalhost:4001127.0.0.1

Decision evidence

public snapshot
AI called this Suspicious at 86.0% confidence as Dangerous Capability with medium false-positive risk.
Evidence for warning
  • dist/server.js is a bundled Codex MCP/plugin server that spawns `codex app-server` and relays wallet-originated chat into a Codex thread.
  • dist/server.js exposes wallet signing and x402 payment tools, including `ac2_sign` and `make_http_request_with_x402_ac2`.
  • dist/server.js starts a localhost tool proxy and writes state such as `tool-proxy.json`, `codex-thread.json`, pairing records, and attachments under the AC2 state dir.
  • README.md asks users to enable the plugin MCP server with `default_tools_approval_mode = "approve"`, expanding agent/plugin authority after explicit setup.
Evidence against
  • package.json has no preinstall/install/postinstall lifecycle hook; only `prepublishOnly` for publisher build steps.
  • .mcp.json registers a package-owned stdio MCP command via `npx -y @goplausible/ac2-plugin-codex@0.2.4`, not a hidden mutation of foreign agent config.
  • Wallet signing/payment paths request approval from the connected wallet and do not expose private keys in inspected source.
  • Network use is aligned with stated AC2/WebRTC wallet pairing, Bazaar/x402 discovery, user-specified HTTP targets, and Algorand RPC.
  • Scanner wallet-drain label maps to user-approved x402/transaction signing code, not hardcoded private-key theft or automatic fund transfer.
  • No credential harvesting, environment exfiltration, destructive install-time behavior, or remote payload download/execution was confirmed.
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 $he=Object.create;var y3=Object.defineProperty;var Che=Object.getOwnPropertyDescriptor;var Dhe=Object.getOwnPropertyNames;var Phe=Object.getPrototypeOf,Mhe=Object.prototype.has... L9: `:""},this._extScope=e,this._scope=new cc.Scope({parent:e}),this._nodes=[new dT]}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 _6e({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 tke(t,e){if(!t.appCallTrace||!t.disassembly)return"";let r=e;return e!==void 0?r=e:r={maxValueWidth:cC,topOfStackFirst:!1},bte(t.appCallTrace,t.disassembly,r)}function r... L108: `);return{content:[yn(a)],details:o}}}}var CMe=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 RL=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),Ks(n)}function NL(t,e){let r=e.slice(0,32),n=e.slice(32,64),i=e[6... L114: `)}var h3={type:"object",properties:{},additionalProperties:!1};function Ch(t){return{content:[{type:"text",text:t}]}}async function hhe(t){if(!t)return Ch("No active pairing invit... ... L116: `));if(e.active)return Ch(`Already connected (peer=${e.active.peerDid}). Run ac2_status for details.`);if(e.pendingInvitation&&!mhe(e.pendingInvitation))return hhe(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 _he(){let t=vhe();if(!(t&&t.pid!==process.pid))try{vLe(qL())}catch{}}function She(t,e){let r=vhe();if(!r)return Promise.resolve({content:[{type:"text",text:"The AC... 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 RL=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),Ks(n)}function NL(t,e){let r=e.slice(0,32),n=e.slice(32,64),i=e[6... L114: `)}var h3={type:"object",properties:{},additionalProperties:!1};function Ch(t){return{content:[{type:"text",text:t}]}}async function hhe(t){if(!t)return Ch("No active pairing invit... ... L120: `)}catch(o){process.stderr.write(`[ac2] could not write tool-proxy endpoint: ${String(o)} L121: `)}}),r}function _he(){let t=vhe();if(!(t&&t.pid!==process.pid))try{vLe(qL())}catch{}}function She(t,e){let r=vhe();if(!r)return Promise.resolve({content:[{type:"text",text:"The AC... 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 tke(t,e){if(!t.appCallTrace||!t.disassembly)return"";let r=e;return e!==void 0?r=e:r={maxValueWidth:cC,topOfStackFirst:!1},bte(t.appCallTrace,t.disassembly,r)}function r... L108: `);return{content:[yn(a)],details:o}}}}var CMe=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 RL=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),Ks(n)}function NL(t,e){let r=e.slice(0,32),n=e.slice(32,64),i=e[6... L114: `)}var h3={type:"object",properties:{},additionalProperties:!1};function Ch(t){return{content:[{type:"text",text:t}]}}async function hhe(t){if(!t)return Ch("No active pairing invit... ... L116: `));if(e.active)return Ch(`Already connected (peer=${e.active.peerDid}). Run ac2_status for details.`);if(e.pendingInvitation&&!mhe(e.pendingInvitation))return hhe(e.pendingInvitat... L117: `)}async function fLe(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 _6e({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 $he=Object.create;var y3=Object.defineProperty;var Che=Object.getOwnPropertyDescriptor;var Dhe=Object.getOwnPropertyNames;var Phe=Object.getPrototypeOf,Mhe=Object.prototype.has... L9: `:""},this._extScope=e,this._scope=new cc.Scope({parent:e}),this._nodes=[new dT]}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 _6e({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