Claude Media
Claude Codeにkintoneプラグインのppkを読ませない設定と分担

Claude Codeにkintoneプラグインのppkを読ませない設定と分担

kintoneプラグインの署名鍵(.ppk)をClaude Codeに読ませない設定を示します。Read denyの書き方と、パッケージ化をだれが実行するかの分け方、効かない経路もまとめます。

kintoneプラグインの署名鍵である.ppkをClaude Codeに読ませないには、permissions.denyにRead(*.ppk)を書きます。この1行でReadツールとBashのcat系コマンドは止まります。ただし鍵を使うパッケージ化のコマンドは、ルールの外側で動きます。実行する担当を決めておく必要があります。

.ppkとは何で、どこに置かれるか

kintoneプラグインのパッケージ化に使うCLI@kintone/plugin-packerは、--ppk PPK_FILEで入力の秘密鍵ファイルを受け取ります。省略すると、鍵が自動生成されます。生成先は<Plugin ID>.ppkというファイル名で、PLUGIN_DIRと同じディレクトリです(--outを指定したときはその場所)。つまり何も指定せずに実行すると、プロジェクトの直下に鍵ができます。

webpackでビルドする構成では、@kintone/webpack-plugin-kintone-pluginのprivateKeyPathで鍵のパスを指定します。既定値は./private.ppkです。鍵が無いときはnpx @kintone/cli plugin keygen --output ./private.ppkで生成できます。

どちらの経路でも、鍵はリポジトリの作業ディレクトリ内に置かれやすい形です。Claude Codeはそのディレクトリを自由に読めるので、何も設定しなければprivate.ppkもReadツールの対象です。

Read denyを書く

プロジェクトの.claude/settings.jsonに次を置きます。

{
  "permissions": {
    "deny": [
      "Read(*.ppk)",
      "Edit(*.ppk)"
    ]
  }
}

ReadとEditのパスはgitignoreと同じ書式です。*.ppkのようにスラッシュを含まない書き方は、カレントディレクトリ以下のどの深さの.ppkにもマッチします。private.ppkでも<Plugin ID>.ppkでも1行で足ります。

Edit(*.ppk)は念のための行です。Readのdenyルールは、同じパスへのEditとWriteも止めます(Writeの検査はv2.1.228以降)。ただしNotebookEditは対象外なので、どのツールにも変更させたくないパスにはEditのdenyを足す、というのが公式の書き方です。鍵ファイルを上書きされると、プラグインの署名が別の鍵に変わる事故につながるため、書き込み側も塞いでおきます。

denyの評価は、deny、ask、allowの順で最初に当たったものが勝ちます。後からRead(./keys/**)のようなallowを足しても、denyを打ち消せません。

ディレクトリ一覧からも鍵の名前が消える

@でディレクトリをメンションすると、Claudeが受け取る一覧からは、Readのルールに合うファイルとディレクトリの名前が省かれます。この挙動はv2.1.290以降です。鍵の存在自体がプロンプトに出ないので、「このプロジェクトにはprivate.ppkがある」とモデルが知ることもありません。

どこまで効いて、どこから効かないか

Read/Editのdenyが及ぶ範囲は、公式の注意書きで限定されています。

経路denyが効くか
Read・Grep・Globなど組み込みのファイルツールdenyが効くか効く(Readルールは可能な範囲で適用)
cat head tail sed teeとリダイレクト(< file)denyが効くか効く
grep -r pattern .のようにファイルを名指ししないコマンドdenyが効くか効かない
PythonやNodeのスクリプトが自分でファイルを開く場合denyが効くか効かない

最後の行が、kintoneプラグインの開発で一番問題になります。kintone-plugin-packerも、webpackのプラグインも、鍵のファイルを自分のプロセスで開いて署名します。Claude CodeがBashでパッケージ化のコマンドを実行すれば、サブプロセスはRead(*.ppk)のdenyに関係なく鍵を読みます。

鍵の中身がClaudeのコンテキストに入るかどうかは別の話です。署名に使うだけで鍵を標準出力に出さないなら、中身は返ってきません。しかし、Claudeが「署名が通らない原因を調べる」と言って、Nodeの1行スクリプトで鍵をconsole.logすることは、denyでは止められません。止めたい場合は、次の2つのどちらかに寄せます。

パッケージ化はだれが実行するか

分担は、コマンドの実行を人に残すか、OSレベルで隔離するかで決まります。

方針設定向く場面
人が実行する設定パッケージ化コマンドをBashのdenyに入れる向く場面鍵を持つのが自分だけで、リリース時だけ署名する
隔離して任せる設定sandboxのfilesystem.denyReadで鍵を塞ぐ向く場面ビルドとアップロードも含めて自動で回したい

人が実行する場合

package.jsonのscriptsに"package": "kintone-plugin-packer --ppk private.ppk plugin"を置いているなら(plugin-packerのREADMEにある書き方です)、denyは次のように足します。

{
  "permissions": {
    "deny": [
      "Read(*.ppk)",
      "Edit(*.ppk)",
      "Bash(npm run package)",
      "Bash(kintone-plugin-packer *)"
    ]
  }
}

Claudeにはmanifest.jsonとsrc/の実装、リント、単体テストまでを任せます。署名つきのplugin.zipが必要になったら、人が自分のターミナルでnpm run packageを実行します。コード変更の確認だけなら、鍵を使わないnpm run lintや、webpackの開発ビルドで足ります。

Bashのdenyルールは、Claudeが書いたコマンド文字列に対して照合されます。同じプログラムを別の形で呼ぶ場合(絶対パスの指定やsh -cの中など)にはマッチしないため、境界にはなりません。公式もこの点を明記しています。「ふだんの呼び方を止める」ための柵だと考えます。

隔離して任せる場合

ビルドも含めて任せたいなら、sandboxを有効にし、鍵のパスをdenyReadに入れます。

{
  "permissions": {
    "deny": ["Read(*.ppk)", "Edit(*.ppk)"]
  },
  "sandbox": {
    "enabled": true,
    "filesystem": {
      "denyRead": ["./**/*.ppk"]
    }
  }
}

sandboxはBashで動くコマンドを包みます。サブプロセスがどう動いても、denyReadのパスは読めません。その代わり、署名のために鍵を読むkintone-plugin-packer自身も鍵を読めなくなります。鍵の読み込みだけを許すには、excludedCommandsでパッケージ化コマンドをsandbox外に出す設定が別に要ります。そうすると、そのコマンドの実行は結局サンドボックスの保護を外れるので、この方式の利点は薄れます。

また、sandboxのdenyReadはReadツールを止めません。Readツールを止めるのは、上のpermissions.denyのほうです。両方を書くのは重複ではなく、止める対象が違うためです。パスの書式も違い、sandboxは通常の絶対パスと~、permissionsは//と/で書きます。sandboxの./で始まるパスは、プロジェクト設定(.claude/settings.json)ならプロジェクトルート、ユーザー設定なら~/.claudeが起点です。./なしで書いても同じ扱いになります。~/はホーム、/は絶対パスです。鍵を守る設定をユーザー設定に置くと、./はプロジェクトではなく~/.claudeを指すので、プロジェクト設定に書くのが確実です。

Claudeに頼む作業の切り分け

鍵を読ませない構成にしたあとの、実際の依頼の分け方です。

  • 任せられる: manifest.jsonの編集、desktop.jsなどのカスタマイズ処理、設定画面のHTML、リントとテストの修正
  • 人が行う: 署名つきzipの生成、kintoneへのアップロード、鍵の生成と保管
  • 一緒に決める: privateKeyPathの置き場所と、CIで署名するときの鍵の渡し方

@kintone/create-pluginで雛形を作った場合、npm startが変更の監視とzip生成、@kintone/plugin-uploaderによる自動アップロードをまとめて行うと、READMEにあります。このコマンドをClaudeが実行すると、鍵とkintoneのアカウント情報の両方を扱うプロセスが走ります。雛形のpackage.jsonのscriptsで、どのコマンドが鍵を読むかを確かめたうえで、Bashのdenyに入れる対象を決めてください。

同じ分担の考え方は、Stripeの鍵ではwebhook開発の秘密鍵の守り方として整理しています。.env全般のdeny例はRead denyの書き方と効く範囲、暗号化したシークレットはSOPSの境界の切り方が参考になります。隔離の選択肢はサンドボックスの選び方にあります。

コマンド文字列で鍵の名前を止めるhook

Bashのdenyルールが「パッケージ化コマンドの名前」を見るのに対し、hookならコマンド文字列に.ppkが含まれるかを見られます。Nodeの1行スクリプトで鍵を読もうとする動きも、コマンドに鍵のパスが書かれていれば拾えます。PreToolUseのhookで、exit 2を返すとツール呼び出しがキャンセルされ、stderrの内容がClaudeに返ります。

#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')
 
if echo "$COMMAND" | grep -q "\.ppk"; then
  echo "Blocked: .ppk (kintone plugin private key) must not appear in commands" >&2
  exit 2
fi
 
exit 0

.claude/settings.jsonには、matcherをBashにして次のように登録します。

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/block-ppk.sh"
          }
        ]
      }
    ]
  }
}

この方式の弱点は、--ppkにシェル変数やglobを使うと、コマンド文字列に.ppkが現れないことです。npm run packageのように、鍵のパスがpackage.jsonの中にだけ書かれている場合も素通りします。鍵の名前を直接書いたコマンドを止める補助として使い、本命はReadのdenyとBashのdenyの組み合わせにします。jqが必要で、hookスクリプトには実行権限も要ります。

鍵を別の場所に置いたとき

--ppkやprivateKeyPathには任意のパスを渡せます。リポジトリの外、たとえば~/keys/に置く運用なら、プロジェクトのRead(*.ppk)は作業ディレクトリ以下にしか効きません。ホームの鍵まで塞ぐなら、ユーザー設定の~/.claude/settings.jsonに絶対パスで書きます。

{
  "permissions": {
    "deny": [
      "Read(//**/*.ppk)"
    ]
  }
}

//で始まるパターンはファイルシステムのルートが起点です。Read(//**/.env)が「ファイルシステム上のすべての.env」に当たるのと同じ形で、ディスク上のすべての.ppkが対象になります。複数のkintoneプラグインを別々のリポジトリで開発している場合は、リポジトリごとに書くよりもこの1行をユーザー設定に置くほうが漏れません。

鍵をリポジトリの外に出す運用では、package.jsonのscriptsに相対パス(../keys/private.ppk)を書くことになります。その相対パスは、Claudeがpackage.jsonを読めば見えます。鍵そのものの中身は読ませなくても、置き場所は知られる点を割り切っておきます。

設定したあとの確認

設定を書いたら、実際に止まるかを1回試します。Claude Codeを起動し直して、鍵を読ませる依頼を出します。

private.ppk の中身を表示して

Readツールが拒否されれば、denyが効いています。/permissionsを開くと、ルールとそれを定義した設定ファイルの対応が見えます。ダミーの鍵ファイル(dummy.ppk)で試すと、本物の鍵を危険にさらさずに済みます。

設定が反映されないときは、先頭スラッシュの罠を疑います。Read(/keys/*.ppk)のように単一のスラッシュで始めると、絶対パスではなく設定ファイルの置き場所が起点です。ユーザー設定に書く場合は//か~/で始めます。

まとめ

Read(*.ppk)のdenyは、Claudeが鍵を見つけて読む経路を塞ぎます。署名する側のプロセスには効かないため、パッケージ化コマンドを人が実行するのか、sandboxに閉じ込めるのかを先に決めると、設定が迷いません。個人開発で署名をリリース時にしか行わないなら、前者のほうが設定は単純です。

参照した一次ソース

この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。

  1. code.claude.com/docs/en/permissions
  2. code.claude.com/docs/en/sandboxing
  3. npmjs.com/package/@kintone/plugin-packer
  4. github.com/kintone/js-sdk/tree/main/packages/webpack-plugin-kintone-p…
この記事を共有:XはてブLinkedIn