registry  /  @goplausible/ac2-plugin-claude  /  0.2.9

@goplausible/ac2-plugin-claude@0.2.9

AC2 channel plugin for Claude Code — connects Claude Code to AC2 wallet peers (e.g. Regent) over Liquid Auth (FIDO2) + WebRTC DataChannels.

AI Security Review

scanned 4d ago · by lpm-firewall-ai

No confirmed malicious attack surface was established. The package implements a declared Claude Code channel for wallet pairing, signing requests, local verification, and remote permission relay.

Static reason
High-risk behavior combination matched malicious policy.
Trigger
User installs/enables the Claude plugin or invokes its MCP/slash commands.
Impact
Can request wallet signatures and relay Claude permission prompts after pairing, but behavior is disclosed and user-mediated.
Mechanism
User-invoked wallet channel and local Claude hook bridge
Rationale
Static inspection shows high-risk primitives are aligned with the package's declared AC2 wallet/channel purpose and are user-invoked or local hook plumbing. I did not find install-time execution, credential harvesting, hidden exfiltration, direct wallet draining, or unconsented AI-agent control-surface mutation.
Evidence
package.jsondist/server.js.mcp.json.claude-plugin/plugin.jsonhooks/hooks.jsoncommands/setup.mdREADME.mdskills/ac2/SKILL.md~/.claude/channels/ac2/hook.port~/.claude/channels/ac2/attachments/~/.claude/settings.json
Network endpoints3
liquidauth.goplausible.xyzliquid://<liquid-auth-server>/?requestId=<id>127.0.0.1:<hook.port>/hook

Decision evidence

public snapshot
AI called this Clean at 84.0% confidence as Benign with medium false-positive risk.
Evidence for block
  • dist/server.js exposes wallet signing tools and remote permission relay, but only as declared channel functionality.
  • commands/setup.md instructs Claude to edit ~/.claude/settings.json when the user invokes setup.
  • hooks/hooks.json installs local hook commands that curl 127.0.0.1 hook.port for Claude events.
Evidence against
  • package.json has no install/postinstall hook; prepublishOnly is publish-time build/typecheck only.
  • .mcp.json runs npx @goplausible/ac2-plugin-claude@0.2.9 as a stdio MCP server, matching package purpose.
  • dist/server.js signing path calls connected wallet sendSigningRequest; no private key harvesting or direct fund transfer found.
  • dist/server.js verifier code parses signed transactions but does not broadcast them.
  • README.md and .claude-plugin/plugin.json clearly describe Liquid Auth/WebRTC wallet channel and remote approval behavior.
  • dist/server.js warns inbound wallet text is prompt-injection-prone and requires terminal-side decisions for pair/sign/approve.
Behavioral surface
Source
ChildProcessCryptoEnvironmentVarsEvalFilesystemNativeBindings
Supply chain
HighEntropyStringsMinifiedObfuscatedProtestwareUrlStrings
ManifestNo manifest risk signals triggered.
scanned 1 file(s), 1.66 MB of source, external domains: api.devnet.solana.com, api.mainnet-beta.solana.com, api.testnet.solana.com, developer.mozilla.org, github.com, json-schema.org, raw.githubusercontent.com, sola.na, viem.sh, www.w3.org

Source & flagged code

4 flagged · loading source
dist/server.jsView file
6const __filename = __ac2FileURLToPath(import.meta.url); L7: const __dirname = __ac2Dirname(__filename); L8: var dle=Object.create;var vv=Object.defineProperty;var lle=Object.getOwnPropertyDescriptor;var ple=Object.getOwnPropertyNames;var mle=Object.getPrototypeOf,fle=Object.prototype.has... L9: `:""},this._extScope=e,this._scope=new pc.Scope({parent:e}),this._nodes=[new G7]}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 Q1e({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(f)}`;return typeof Blob>"u"||this._options.jsdom?Buffer.from(S):new Blob([S],{type:v})}return
Critical
Wallet Drain

Source uses private key material to transfer cryptocurrency funds.

dist/server.jsView on unpkg · L6
6Trigger-reachable chain: manifest.bin -> dist/server.js L6: const __filename = __ac2FileURLToPath(import.meta.url); L7: const __dirname = __ac2Dirname(__filename); L8: var dle=Object.create;var vv=Object.defineProperty;var lle=Object.getOwnPropertyDescriptor;var ple=Object.getOwnPropertyNames;var mle=Object.getPrototypeOf,fle=Object.prototype.has... L9: `:""},this._extScope=e,this._scope=new pc.Scope({parent:e}),this._nodes=[new G7]}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 Q1e({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(f)}`;return typeof Blob>"u"||this._o…
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 · L6
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 Q1e({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 dle=Object.create;var vv=Object.defineProperty;var lle=Object.getOwnPropertyDescriptor;var ple=Object.getOwnPropertyNames;var mle=Object.getPrototypeOf,fle=Object.prototype.has... L9: `:""},this._extScope=e,this._scope=new pc.Scope({parent:e}),this._nodes=[new G7]}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 Q1e({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(f)}`;return typeof Blob>"u"||this._options.jsdom?Buffer.from(S):new Blob([S],{type:v})}return
Low
Weak Crypto

Package source references weak cryptographic algorithms.

dist/server.jsView on unpkg · L6

Findings

2 Critical1 High3 Medium7 Low
CriticalWallet Draindist/server.js
CriticalTrigger Reachable Dangerous Capabilitydist/server.js
HighObfuscated
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
LowUrl Strings