关于 sigil
sigil 是供 Claude Code 使用的本地密钥库与签名服务,让编码 Agent 请求 EVM 和 Solana 签名而不接触私钥。它通过策略检查和人工确认限制签名权限;项目仍处于预发布阶段,不建议用于真实资金。
主要功能
- 01私钥加密存储与会话内存隔离
- 02支持 EVM 与 Solana 签名
- 03按链、地址和金额检查策略
- 04ntfy 站外人工确认
- 05提供本地 JSON-RPC 签名代理
适用场景
- 01让编码 Agent 在不读取私钥时签署交易
- 02按允许的链和地址限制交易
- 03大额签名请求通过手机通知确认
MCP 相关
sigil-mcp 以 MCP 服务形式向 Claude Code 暴露签名工具。私钥留在本地进程中,客户端只接收不透明密钥句柄。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
4收录的工具列表 · 更新于 2 周前
此工具无需参数。
详细介绍
sigil
Claude 可以签名,但永远看不到密钥。
sigil 是一款本地签名工具,也是 Claude Code 集成,可让具备 Agent 能力的编码工具使用私钥,同时确保密钥材料绝不会进入模型的上下文窗口。
**状态:**预 Alpha。MCP 服务、CLI、解锁流程、ward 钩子、策略引擎(静态检查)、通过 ntfy 进行的带外确认、Solana 签名,以及 JSON-RPC 签名代理(Foundry/Hardhat)均已端到端运行。滚动窗口额度上限和 EIP-712 域名允许列表已加入策略引擎,但尚未发布。在 v0.1.0 发布前——届时将附上承诺的供应链证明——**请勿将其用于真实资金。**构建计划见跟踪问题。
它是什么
一个 MCP 服务进程、四个二进制文件(另有一个旧版 sigild 别名)、六个运行时依赖(全部固定版本,没有传递依赖):
sigil-mcp— 唯一运行的程序。Claude Code 会根据你的mcpServers配置为每个会话启动它;Claude 退出时它也会退出。它会将解锁后的密钥保存在进程内存中(在关闭、执行sigil lock或解锁失败时清零;计划使用 mlock 防止换出到交换空间)。静态存储的密钥使用 XChaCha20-Poly1305 和 Argon2id 派生密钥加密。它通过 stdio 使用自制的 MCP 线协议(约 200 行,不依赖 SDK)进行签名。Claude 永远看不到密钥材料,只能看到evm:executor这样的不透明句柄。sigil— 控制 CLI。提供init、status、portal new/add/list/qr/remove、policy show/init、unlock、lock。sigil-hook-pre/sigil-hook-post— Claude Code 钩子二进制文件,用于阻止读取常见密钥路径,并从工具输出中删去密钥格式的字符串。
sigil-mcp 启动时处于锁定状态:内存中的句柄表为空,未加载任何密钥。在你通过单独的终端运行 sigil unlock 并推送口令之前,签名方法会返回 DAEMON_LOCKED(-32003),并提示“run sigil unlock”。该 CLI 会连接到 sigil-mcp 启动时打开的每会话 Unix 套接字 ~/.sigil/control/<pid>.sock(权限为 0600),并将请求广播到所有此类套接字,因此一次 sigil unlock 就能解锁所有打开的窗口。解锁后,签名操作在本会话剩余时间内均可用;sigil lock 会清零句柄表,但不会终止进程。
当前提供的签名方法:EIP-191 personal_sign、EIP-1559 和传统交易、EIP-712 类型化数据,以及 Solana(ed25519)消息和交易签名——详见下文的 Solana 支持。
它不是什么
- 不能替代硬件钱包。如果你能使用 Ledger 或 YubiKey,就应该使用它们。
- 不是托管解决方案。它运行在你的笔记本电脑或 VPS 上,只能保护你免受一种特定风险:大模型 Agent 泄露密钥材料。
- 这是通过策略引擎对签名权限设定边界的初步实现,但并非完整方案。已发布的功能:静态检查(链 ID、目的地址允许列表、单笔交易价值上限、函数选择器允许列表、合约创建控制、personal_sign 和 EIP-712 的启用/禁用开关),以及通过 ntfy 进行的带外人工确认。滚动窗口额度上限和 EIP-712 域名允许列表已在 #3 跟踪,后续将逐步推出。
安装
npm install -g sigild
这会在你的 $PATH 中安装四个二进制文件:sigil、sigil-mcp、sigil-hook-pre、sigil-hook-post(另有 sigild,作为 sigil-mcp 的旧版别名)。(npm 上的包名因历史原因仍为 sigild;这些二进制文件不再包含守护进程。)
需要 Node 22+、macOS 或 Linux。(CLI 与会话之间的控制通道使用 Unix 域套接字;Windows 尚未经过测试,目前不受支持。)
快速开始
# 1. Wire sigil into Claude Code (project-scoped). Pass --user to do it globally.
sigil init
# 2a. Generate a fresh key inside sigil (no plaintext ever hits disk):
sigil portal new evm:bot
# → prompts for a passphrase, mints a fresh secp256k1 key, prints the
# address, writes ~/.sigil/keys/evm:bot.sigil + permissive policy.
#
# 2b. OR import an existing private key from a file:
# Accepts either 32 raw bytes or 64 hex chars (with optional 0x prefix).
sigil portal add evm:bot --key-file ./private.hex
# → same as above but seeded from the file. Source file is deleted by
# default (pass --no-remove-source to keep it).
#
# Either form: pass --strict to start with a locked-down policy template
# you fill in before any sign succeeds.
# 3. Open Claude Code. It spawns sigil-mcp automatically via your MCP config.
# sigil-mcp boots locked — the first sign attempt will return DAEMON_LOCKED.
# 4. In a separate terminal, push the passphrase to every running sigil-mcp.
sigil unlock
# → prompts once, decrypts every keyfile in ~/.sigil/keys/ into each open window
# 5. Use Claude Code. The sigil_* tools (EVM + Solana) will work for the rest of the session.
# Optional: re-lock without restarting Claude.
sigil lock
如果关闭 Claude Code,sigil-mcp 就会退出,其内存也会被清除。开启新会话后,需要再次运行 sigil unlock——磁盘上的加密密钥文件会保留。
CLI 参考
sigil init [--user]
Project scope: writes the ward hooks to <cwd>/.claude/settings.json
and the MCP server registration to <cwd>/.mcp.json.
--user: writes hooks to ~/.claude/settings.json and the MCP server
registration to ~/.claude.json. (Claude Code CLI reads MCP configs
from .mcp.json / ~/.claude.json — not from settings.json.)
Idempotent — preserves your unrelated settings, and on upgrade
migrates any stale mcpServers.sigil entry out of settings.json.
sigil portal new <handle> [--strict]
Generate a fresh secp256k1 key inside sigil, encrypt with your
passphrase, write it to ~/.sigil/keys/<handle>.sigil (mode 0600).
No plaintext key ever lands on disk. Use this when you want a clean
hot wallet for a bot (vs importing an existing key from a file).
Also writes ~/.sigil/policy/<handle>.toml — permissive by default,
or --strict for a locked-down template.
sigil portal add <handle> --key-file <path> [--no-remove-source] [--strict]
Import an existing private key. Encrypts it with your passphrase
and stores at ~/.sigil/keys/<handle>.sigil (mode 0600). Handle
format is <kind>:<name> where kind is "evm". The source key file
is deleted by default — pass --no-remove-source to keep it.
Also writes ~/.sigil/policy/<handle>.toml — permissive by default
(signs anything), or --strict for a locked-down template you fill
in before signs succeed.
sigil policy show <handle>
Print the current policy file for a portal. Validates schema; exits
1 if the file is missing or malformed.
sigil policy init <handle> [--strict]
Provision a policy file for an existing portal whose policy is
missing (e.g. a keyfile from an older sigil version, or one you
manually deleted). Refuses to overwrite — edit the file directly
or remove it first. Defaults to permissive; --strict writes the
locked-down template.
sigil rpc init <handle> --upstream <url> [--port <n>]
Enable the JSON-RPC signing proxy for a portal: generates a strong
auth token and appends the [rpc] block to ~/.sigil/config.toml
(refuses if one exists; preserves the rest of the file). Prints the
ready-to-paste authenticated endpoint and forge invocation. Restart
Claude Code sessions to pick it up.
sigil portal list
List the encrypted keyfiles on disk with their derived addresses
(EVM + Solana). Requires the passphrase.
sigil portal qr <handle>
Render a portal's address as a terminal QR code (for funding it from
a phone wallet). Requires the passphrase.
sigil portal remove <handle>
Delete a keyfile from disk.
sigil unlock
Prompt for the passphrase and push it to every running sigil-mcp at
once (one per Claude window). After unlock, sign calls succeed for the
rest of each session. Idempotent — sessions already unlocked are left
as-is. Fails if no sigil-mcp is running (start a Claude Code session
first).
sigil lock
Tell every running sigil-mcp to zeroize and clear its in-memory keys.
Re-unlock with sigil unlock — the sigil-mcp processes keep running.
sigil status
Report which sigil-mcp sessions are running (one entry per window,
with PID, unlocked flag, and loaded portals) and how many keyfiles
exist on disk. Does not require the passphrase.
设置 SIGIL_HOME 可覆盖 ~/.sigil。设置 SIGIL_CONTROL_DIR 可覆盖控制套接字目录。
多窗口行为
每个 Claude Code 窗口都会启动自己的 sigil-mcp,并在 ~/.sigil/control/<pid>.sock 绑定各自的控制套接字。它们共享磁盘上的密钥文件和审计日志,但各自维护独立的内存句柄表。
sigil unlock / lock / status 会向 ~/.sigil/control/ 中的所有套接字发送请求,因此只需运行一次 sigil unlock,就能将密钥加载到所有当前打开的窗口中——不必再猜 CLI 会连接到哪个进程。被强制终止的会话留下的套接字会在下次调用 CLI 时自动检测并清理。
每个窗口仍只会在自身生命周期内持有自己的解密密钥:关闭窗口会将该会话的密钥清零,而在你解锁之后打开的窗口会保持锁定状态(再次运行 sigil unlock 将其纳入)。密钥的生命周期绝不会超过使用它们的 Claude 会话——这是 #23 中刻意设计的特性。
OS 钥匙串集成(计划在 v0.3 推出)将让已完成设置的用户无需操作即可解锁。
升级 sigil 后,请重启每个 Claude 窗口的 sigil-mcp(重新连接 MCP 服务或重新打开窗口,然后再次运行 sigil unlock)。正在运行的 sigil-mcp 不会重新加载代码,不同版本的守护进程也不得共用 ~/.sigil/audit.log:锁协议和写入器的链检查均与版本相关。运行 ps -ax | grep sigil-mcp 可查看未退出的进程。
策略引擎
解锁门户后,对其密钥的签署权限就是真实有效的。为限制成功的提示注入所造成的影响范围,每个门户在 ~/.sigil/policy/<handle>.toml 中都有一个策略文件。有两种模式:
宽松模式(sigil portal add 的默认模式):没有规则。签署 Agent 要求签署的任何内容。密钥隔离保障仍然有效——你的密钥绝不会进入 Agent 的上下文——但攻击者可以诱使 Agent 发出任何请求,让已解锁的门户签署相应内容。适用场景:测试网机器人、演示流程,以及只关心上下文窗口保护的用户。
严格模式(使用 --strict 启用):检查每个签署请求。生成的模板:
mode = "strict"
chain_ids = [1] # allowed chain IDs
allow_to = [] # allowed destination addresses (lowercase 0x)
max_value_wei = "0" # per-tx cap, in wei, as decimal string
allowed_selectors = [] # 4-byte function selectors, e.g. "0xa9059cbb"
allow_contract_creation = false # deploys (to = null); when true, every
# deploy still requires a confirm tap
allow_message_signing = false # EIP-191 personal_sign (e.g. SIWE)
allow_typed_data = false # EIP-712 (Permit, OpenSea — can be financial)
typed_data_verifying_contracts = [] # when allowed: domain.verifyingContract allowlist
typed_data_primary_types = [] # when allowed: primaryType allowlist ("Permit", …)
# strict mode also requires domain.chainId ∈ chain_ids
# Optional rolling-window caps on tx.value (work in permissive mode too).
max_value_per_hour_wei = "100000000000000000" # 0.1 ETH / hour
max_value_per_day_wei = "1000000000000000000" # 1 ETH / day
# Optional: above this value, sigil pushes a notification to your phone and
# waits for an approve/deny tap before signing. See "Out-of-band confirm"
# below. Must be strictly less than max_value_wei.
require_confirm_above_wei = "10000000000000000" # 0.01 ETH
规则检查失败时,会向 Agent 返回 POLICY_DENIED(-32001)以及人类可读的原因(“交易被拒绝——金额 X 超过 max_value_wei Y”),并将拒绝记录追加到哈希链式审计日志中,与允许记录并列。拒绝记录在取证分析中更有价值——它们是提示注入的哨兵。
滚动窗口上限(max_value_per_hour_wei、max_value_per_day_wei 和 svm_max_lamports_per_hour/day)是两种模式下都会生效的唯一规则——“带额度的宽松模式机器人”是常见用例。它们只统计原生资产金额:EVM 的 tx.value,以及 Solana 中解码出的 System-Program 转账。ERC-20 / SPL 转账属于 calldata,而非原生资产金额,不会计入;require_confirm_above_wei 也无法检测到它们(代币转账的 value = 0)。使用严格模式的 allow_to + allowed_selectors 限制代币转移;对于 Solana,在严格模式下,sigil 无法解码为 System-Program 转账的任何操作都会转为确认流程;如果设置了 lamport 上限,则会直接拒绝。支出记录在每个门户各自的账本 ~/.sigil/state/<handle>.ledger 中(仅追加的 JSONL 文件,由与审计日志相同的锁在各窗口间共享,并会自动压缩),检查和记录是原子操作,因此两个窗口不会同时花掉额度的最后一笔。上限会在推送确认请求前检查一次(避免要求你批准本就会被拒绝的操作),并在签署前再次检查。sigil policy spend <handle> 会显示最近 1h/24h 的总额及对应上限。sigil portal remove 会随门户一并删除账本。
EIP-712 允许列表(严格模式下,且 allow_typed_data = true):当 typed_data_verifying_contracts 和 typed_data_primary_types 非空时,它们分别限制一个维度;同时,域必须包含一个 chainId,且其值必须属于 chain_ids(严格模式会拒绝不含链 ID 的域,因为无法对其进行检查)。类型化数据签名可以转移资金(Permit、Permit2、交易所订单),因此会像交易的目标地址一样受到处理。
仍待处理(见 #3):已解码 calldata 的参数检查。
带外确认
对于金额超过 require_confirm_above_wei 的签署请求,sigil 会向你控制的渠道(而非 Agent)推送通知,并等待人工明确确认后再签署。目前唯一支持的传输方式是 ntfy——无需设置,也无需账号。SMS 和 Telegram 传输方式已通过同一个 ConfirmTransport 接口接入,将在后续版本推出。
在 ~/.sigil/config.toml 中进行配置:
[confirm.ntfy]
topic = "your-unguessable-string-here" # the topic name IS the credential
# server = "https://ntfy.example.com" # optional, default https://ntfy.sh
[confirm]
# timeout_ms = 60000 # default 60s; timeout = deny
在手机上安装 ntfy 应用,订阅该主题后,每当超过阈值,你都会收到带有批准 / 拒绝按钮的推送。按钮会访问 sigil-mcp 内部监听的本地 127.0.0.1,并使用一次性、绑定请求的令牌——泄露或重放的令牌无法批准其他签署请求。超时、点击拒绝以及推送服务商故障都会默认拒绝。
如果任何策略文件设置了 require_confirm_above_wei,但未配置传输方式,sigil-mcp 会拒绝启动并给出明确错误,而不会静默地将所有需要确认的签署请求降级为拒绝。
JSON-RPC 签名代理(Foundry / Hardhat / 任意 web3 工具)
sigil 可以提供本地 JSON-RPC 端点,让任何 portal 都能直接充当工具所需的已解锁节点账户签名者——采用与 Clef 和 web3signer 相同的模式。合约字节码从 forge 直接传给 sigil;它绝不会经过 Agent 的上下文或 MCP 工具参数。
只需运行一条命令即可启用(生成令牌、写入配置块并打印 forge 调用命令):
sigil rpc init evm:bot --upstream https://sepolia.example/v3/KEY
……也可以手动添加到 ~/.sigil/config.toml:
[rpc]
portal = "evm:bot" # which portal signs
upstream = "https://sepolia.example/v3/KEY" # real node for everything else
token = "<openssl rand -hex 24>" # required — guards the endpoint
# port = 8547 # default 8547 (clear of anvil's 8545)
然后让任意工具连接到它,并将令牌作为 Basic 身份验证密码:
forge script script/Deploy.s.sol \
--rpc-url "http://sigil:<token>@127.0.0.1:8547" \
--unlocked --sender 0xYourPortalAddress --broadcast
代理使用 portal 密钥处理以下三种请求,其余请求(eth_call、eth_estimateGas、eth_getTransactionReceipt 等)则转发到上游:
eth_accounts→ portal 地址(锁定时为空)eth_signTransaction→ 如果缺少 nonce/gas/费用,则补齐后签名,并返回原始交易eth_sendTransaction→ 同样处理,然后通过上游广播并返回哈希
安全属性。监听器仅绑定到 127.0.0.1;每个请求都必须提供配置令牌(以恒定时间进行比较)和回环 Host 标头(防御 DNS 重绑定)。签名通过与 MCP 工具完全相同的守护进程流程执行——策略检查、带外确认门控和哈希链审计日志均适用,因此该接口只是增加了一种传输方式,并未增加权限。填充后的交易携带上游的链 ID,因此严格策略的 chain_ids 允许列表会将代理绑定到你配置的网络。此接口会拒绝消息/类型化数据方法(eth_sign、personal_sign、eth_signTypedData*)——请使用具有各自策略开关的 MCP 工具。严格策略设置 allow_contract_creation = true 后,即可通过确认门控部署 forge script:forge 提交交易,你的手机收到提醒,点击批准后交易便会签名。
当同时打开多个 Claude 窗口时,每个 sigil-mcp 都会尝试绑定端口;第一个成功,其他进程会记录日志并继续运行——任何一个会话的代理都能为整台机器提供服务。
启用代理后,sigil-mcp 会在 sigil_eth_sign_transaction 工具描述中公布端点(包括经过身份验证的 URL),让 Agent 自动发现它,并使用 forge --unlocked,而不是通过 MCP 工具转录字节码。这是有意为之:令牌用于保护其他本地软件,而非你的 Agent(参见 THREAT_MODEL.md)。
Solana(SVM)
每个 portal 还控制一个 Solana 地址,该地址由同一个密钥派生。EVM 使用 secp256k1;Solana 使用 ed25519——两者使用不同的椭圆曲线,因此无法共用公钥。但 portal 的原始 32 字节密钥也可作为 ed25519 种子,从而由一个密钥生成两个地址:
$ sigil portal list
evm:executor
evm: 0x1234…abcd
svm: 7vWxK…Qm9f # base58 ed25519 address, same key
这与 Phantom/Solflare 执行**“导入私钥”**时的派生方式完全一致,因此可以在其中恢复 Solana 地址(它不匹配助记词 / BIP44 账户,而是原始密钥账户)。
两个 MCP 工具:
sigil_svm_sign_message— 使用 ed25519 密钥对任意链下字节(例如 Sign-In With Solana)签名。输入为 base64;返回 base58 签名。sigil_svm_sign_transaction— 传入序列化的 Solana 交易消息(base64,legacy 或 v0);sigil 会对这些字节进行 ed25519 签名,并返回 base58 签名,供你组装进交易中。
策略。 Solana 将大部分语义隐藏在账户索引和链上状态之后,因此 sigil 只解码它能离线解析的内容:原生 SOL(System Program)转账,并通过 svm_allow_to(base58 收款方允许列表)和 svm_max_lamports(每笔交易上限)进行限制,与 EVM 完全相同。任何无法完整解码的内容——SPL 代币、程序调用、地址查找表账户——都会转交给带外确认门控,绝不会默默放行。自动放行必须满足所有条件:只有在每条指令都已解码并通过策略时,才会在无需人工确认的情况下签署交易。require_confirm_above_lamports 会添加一个金额阈值(严格模式下还会要求对无法解码的交易进行确认);在严格模式下,如果无法解码的交易没有配置确认传输方式,则会默认拒绝。
相关策略字段(~/.sigil/policy/<handle>.toml):allow_svm_message_signing、svm_allow_to、svm_max_lamports、require_confirm_above_lamports。
供应链安全态势
密钥管理库遭遇的致命问题是供应链被攻破,而不是代码遭到巧妙攻击。鉴于 2026 年的 npm 生态(Mini Shai-Hulud、Axios、pgserve、TanStack),sigil 承诺:
- 零安装脚本。 不使用
postinstall、preinstall、prepare。由 CI 强制执行:每个 PR 都会运行一项检查,如果解析后的依赖树中有任何包声明了这类脚本,就会失败。 - 六个运行时依赖,全部固定版本(不使用插入符号范围),且都没有传递依赖——整个
npm ls --omit dev依赖树恰好包含以下六个包:@noble/ciphers,用于 XChaCha20-Poly1305@noble/hashes,用于 Argon2id、keccak256、sha2/sha512、HMAC@noble/secp256k1,用于 ECDSA(EVM)@noble/ed25519,用于 EdDSA(Solana)@iarna/toml,用于解析每个 portal 的策略 TOML 文件qrcode-generator,用于渲染sigil portal qr
- 不使用 MCP SDK。 官方的
@modelcontextprotocol/sdk会引入 92 个传递依赖(ajv、hono、cors、cross-spawn 等),攻击面不可接受。我们直接用约 200 行代码实现 MCP 线协议。 - 不使用 Bun。 仅使用纯 Node。Mini Shai-Hulud 目前正将 Bun 武器化为规避手段;我们不会为这种模式提供任何掩护。
- 每次 npm 发布都附带来源证明。 从 v0.0.4 开始,发布版本由 GitHub Actions 工作流构建,通过 OIDC 受信任发布者身份验证,并使用 Sigstore 证明签名。不会使用长期有效的 npm 令牌;遭篡改或通过其他渠道发布的包无法通过签名验证。
- 每个 GitHub Release 都附带 CycloneDX SBOM。 在发布时枚举完整的传递依赖树。
- 安装脚本 CI 检查。 如果解析后的依赖树中有任何包声明了
preinstall/install/postinstall,每个 PR 都会失败。.npmrc已设置ignore-scripts=true,因此这些脚本实际上从未在我们的环境中运行;这项检查可以发现新增的传递依赖,以免没有我们的.npmrc的用户受到影响。 - 仍计划在 v0.1.0 中推出:
- 在 GitHub Releases 发布签名的独立二进制文件,供不愿使用 npm 的用户选择
- 通过 Dependabot 轮换固定的 Action SHA
验证发布版本
你可以确认 sigild 压缩包是在其声称来源的提交上由公开工作流构建的:
# Validates every package in your install tree:
npm audit signatures
# Inspect the attestation for a specific sigild version:
npm view sigild@<version> dist.attestations
# → shows the workflow filename, the commit SHA, and the Sigstore signing cert
证明会告诉你:此压缩包由 cdrn/sigil 的 .github/workflows/release.yml 在 main 分支上的特定提交中,于特定时间构建。它不能证明该提交没有恶意内容——要判断这一点,请阅读你信任的版本与准备升级到的版本之间的差异。但这意味着,窃取 npm 令牌的攻击者无法冒用我们的名义发布恶意 sigild;他们还必须攻破 GitHub 仓库并推送一个标签,而这会留下审计记录。
每个发布版本还会将 CycloneDX SBOM 作为 GitHub Release 资源发布,列出由 package-lock.json 锁定版本的安装依赖树中的每个包(直接依赖和传递依赖):
# Download + inspect the SBOM for a specific release:
gh release download v0.0.4 --repo cdrn/sigil --pattern '*.cdx.json'
# → produces sigild-v0.0.4.cdx.json — feed to syft/grype/etc. for vuln scan
威胁模型
请参阅 THREAT_MODEL.md。在信任它处理任何内容之前,请先阅读。
开发
git clone https://github.com/cdrn/sigil
cd sigil
npm install # respects .npmrc ignore-scripts=true
npm test # builds + runs 600+ tests; should finish in under 15s
有关按层提交 PR 的工作流,请参阅 CONTRIBUTING.md。
许可证
Apache License 2.0。请参阅 LICENSE。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。