メインコンテンツまでスキップ

セキュリティの仕組み

このガイドでは、シークレットがどのように保護されるか、異なるアクセス方法がどのように機能するか、多層防御モデルについて説明します。

アーキテクチャ概要

┌─────────────────────────────────────────────────────────────────────┐
│ secretctl アーキテクチャ │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 人間オペレーター AI エージェント │
│ ═══════════════ ═════════════ │
│ CLI / デスクトップアプリ MCP サーバー │
│ │ │ │
│ │ フルアクセス │ 制限付き(AI安全設計) │
│ │ │ │
│ └──────────┬────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Vault │ │
│ │ (SQLite) │ │
│ │ │ │
│ │ AES-256-GCM │ │
│ │ 暗号化 │ │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ 監査ログ │ │
│ │ HMAC チェーン│ │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘

鍵階層

secretctl は防御を深めるために3層の鍵階層を使用します:

┌─────────────────────────────────────────────────────────┐
│ ユーザー入力 │
│ マスターパスワード │
└─────────────────────┬───────────────────────────────────┘

▼ Argon2id (memory: 64MB, time: 3, threads: 4)
┌─────────────────────────────────────────────────────────┐
│ マスターキー (256ビット) │
│ ※ メモリのみ、保存されない │
└─────────────────────┬───────────────────────────────────┘

▼ AES-256-GCM 暗号化
┌─────────────────────────────────────────────────────────┐
│ データ暗号化キー (DEK) (256ビット) │
│ ※ 暗号化して保存 │
└─────────────────────┬───────────────────────────────────┘

▼ AES-256-GCM 暗号化
┌─────────────────────────────────────────────────────────┐
│ 暗号化されたシークレット │
│ ※ SQLite に保存 │
└─────────────────────────────────────────────────────────┘

なぜこの設計か?

レイヤー目的
マスターパスワードユーザー認証、保存されない
マスターキーアンロックごとに新たに導出、DEK を保護
DEK実際の暗号化キー、パスワードローテーションを可能に
暗号化されたシークレット保存時に保護されたデータ

Vault 構造

Vault ディレクトリには以下が含まれます:

~/.secretctl/
├── vault.db # SQLite DB(値はアプリで暗号化)
├── vault.salt # Argon2 salt (128ビット)
└── vault.meta # メタデータ(バージョン、作成日時)

ファイル権限

すべての機密ファイルは厳格な権限を使用:

ファイル権限理由
vault.db0600暗号化されたシークレットを含む
vault.salt0600鍵導出に必要
vault.meta0600メタデータ保護
~/.secretctl/0700ディレクトリアクセス制御

アクセス制御モデル

CLI vs MCP: 信頼レベル

secretctl はアクセス方法に基づいて機能を制限します:

┌─────────────────────────────────────────────────────────────────────┐
│ secretctl アクセスモデル │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ CLI (人間オペレーター) MCP サーバー (AI エージェント) │
│ ════════════════════ ═════════════════════ │
│ 信頼レベル: 完全 信頼レベル: 制限付き │
│ │
│ ✅ secretctl get KEY ❌ secret_get (未実装) │
│ → 平文 OK → AI に平文なし │
│ │
│ ✅ secretctl set KEY ❌ secret_set (未実装) │
│ → stdin で保存 → AI は値を設定できない │
│ │
│ ✅ secretctl list ✅ secret_list │
│ → 完全なキーリスト → キー名のみ │
│ │
│ ✅ secretctl run -- cmd ✅ secret_run │
│ → 環境変数注入 → マスクされた出力のみ │
│ │
│ ✅ secretctl delete KEY ❌ (未実装) │
│ → 削除 OK → AI は削除できない │
│ │
│ ✅ secretctl export ❌ (未実装) │
│ → エクスポート OK → AI はエクスポートできない │
│ │
└─────────────────────────────────────────────────────────────────────┘

設計の理由

アクセスオペレーター信頼制限
CLI人間直接完全なし(全機能)
デスクトップ人間直接完全なし(全機能)
MCPAI エージェント制限付き平文の get/set/delete なし

なぜ違いがあるのか?

  • CLI ユーザーはシークレットを所有する人間であり、見る権利がある
  • AI エージェントは実際の値を知る必要のないプロキシ
  • AI はシークレット自体ではなく、シークレットを使用した結果だけが必要

AI安全設計: 露出なしのアクセス

AI 連携のコアセキュリティモデル:

┌─────────────────────────────────────────────────────────────────────┐
│ AI安全設計: 露出なしのアクセス │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ❌ 従来の MCP 実装: │
│ AI → MCP サーバー → 平文シークレット → AI → 使用 │
│ 問題: AI がシークレットを「知っている」 │
│ │
│ ✅ secretctl AI安全設計: │
│ AI → 「このシークレットでこのコマンドを実行」 │
│ → シークレットは直接注入(AI は見ない) │
│ → 結果のみが AI に返る │
│ │
└─────────────────────────────────────────────────────────────────────┘

MCP ツールの能力

能力説明AI レスポンス
reference_onlyキー存在確認{"exists": true, "created_at": "..."}
env_inject環境変数注入{"exit_code": 0, "stdout": "..."}
masked_returnマスク値表示{"masked_value": "sk-****7890"}

注意: full(平文返却)は廃止されました。MCP ツールは平文値を返しません。

secret_run のセキュリティレイヤー

secret_run コマンドは多層防御を使用します:

AI → secret_run(keys, command)


┌─────────────────────────────────────────────────┐
│ レイヤー 1: コマンド検証 │
│ - ブロックされたコマンドリスト (env, printenv, etc.) │
│ - 追加の AI 制限 │
├─────────────────────────────────────────────────┤
│ レイヤー 2: 実行環境 │
│ - 非 TTY モード強制 │
│ - 一時的な作業ディレクトリ │
│ - タイムアウト: 300秒 │
├─────────────────────────────────────────────────┤
│ レイヤー 3: 環境変数注入 │
│ - シークレットはプロセス環境にのみ存在 │
│ - 値は AI に渡されない │
├─────────────────────────────────────────────────┤
│ レイヤー 4: 出力サニタイズ │
│ - stdout/stderr でシークレットをスキャン │
│ - 検出された値をマスク │
│ - Base64 エンコード検出 │
└─────────────────────────────────────────────────┘


AI ← {"exit_code": 0, "stdout": "...", "stderr": "..."}
※ シークレット値は含まれない

ブロックされたコマンド

環境変数を露出する可能性のあるコマンド:

var blockedCommands = []string{
"env",
"printenv",
"set",
"export",
"declare",
"cat /proc/*/environ",
}

出力サニタイズ

// OutputSanitizer は stdout/stderr のシークレットをマスク
type OutputSanitizer struct {
secrets map[string]string // key -> value
}

func (s *OutputSanitizer) Sanitize(output string) string {
result := output
for key, value := range s.secrets {
// 平文検出
if strings.Contains(result, value) {
result = strings.ReplaceAll(result, value, "[REDACTED:"+key+"]")
}
// Base64 エンコード検出
encoded := base64.StdEncoding.EncodeToString([]byte(value))
if strings.Contains(result, encoded) {
result = strings.ReplaceAll(result, encoded, "[REDACTED:"+key+":base64]")
}
}
return result
}

マスキング形式

値はセキュリティのため固定長プレフィックスでマスク:

func maskValue(value string) string {
if len(value) <= 8 {
return "********"
}
// 末尾4文字のみ表示
return "********" + value[len(value)-4:]
}

// 例: "sk-proj-abc123xyz789" → "********z789"

固定長マスキングは値の長さの推測を防ぎます。

監査ログ

HMAC チェーン整合性

┌─────────────────────────────────────────────────────────────────────┐
│ 監査ログ HMAC チェーン │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ レコード 1 レコード 2 レコード 3 │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ data │ │ data │ │ data │ │
│ │ prev: Ø │──┐ │ prev: H1│──┐ │ prev: H2│ │
│ │ hash: H1│ │ │ hash: H2│ │ │ hash: H3│ │
│ └─────────┘ │ └─────────┘ │ └─────────┘ │
│ │ ▲ │ ▲ │
│ └─────────┘ └─────────┘ │
│ │
│ H = HMAC-SHA256(id || action || key_hash || ... || prev, key) │
│ key = HKDF-SHA256(master_key, "audit-log-v1") │
│ │
│ 改ざん検出: prev_hash 不一致 → チェーン破損 │
│ │
└─────────────────────────────────────────────────────────────────────┘

記録される内容

記録:

  • 操作タイプ (get, set, delete, list, run)
  • キーハッシュ(機密操作ではキー名自体ではなく)
  • ソース (cli, mcp, ui)
  • タイムスタンプ(ナノ秒精度)
  • 結果 (success, denied, error)

記録されない(セキュリティのため):

  • シークレット値(平文または暗号化)
  • マスターパスワード
  • 生のユーザー入力

検証

# 監査ログ整合性を検証
$ secretctl audit verify
15,234 レコードを検証
✓ チェーン整合性: OK
✓ ギャップは検出されず

# 改ざんが検出された場合
$ secretctl audit verify
✗ レコード id=01HQ5E7N8K... でチェーン破損
期待される prev: a3f2b1...
実際の prev: 7c8d4e...
アラート: 改ざんの可能性が検出されました

MCP ポリシーエンジン

ポリシー設定

# ~/.secretctl/mcp-policy.yaml
policies:
- name: "claude-desktop"
agent_id: "claude-desktop-*"
allowed_keys:
- "api/*" # api/ プレフィックスを許可
- "config/dev/*"
denied_keys:
- "*/prod/*" # prod シークレットを拒否
capabilities:
- env_inject # 環境変数注入
- reference_only # 存在確認
- masked_return # マスク表示

- name: "untrusted-agent"
agent_id: "*"
allowed_keys: [] # すべて拒否
capabilities:
- reference_only # 存在確認のみ

ポリシー評価順序

  1. 最初に denied_keys をチェック(拒否が優先)
  2. 許可のため allowed_keys をチェック
  3. 必要な capabilities を検証
  4. アクセス試行を監査にログ

セキュリティの利点

  1. AI に平文を露出しない

    • 最も重要な保護
    • 値は AI モデルログやトレーニングデータに含まれない
  2. プロンプトインジェクション耐性

    • 悪意のあるプロンプトで抽出するシークレット値がない
    • AI が侵害されてもシークレットは保護されたまま
  3. 多層防御

    • コマンド検証 + 環境分離 + 出力フィルタリング
    • 防御を深めるアプローチ
  4. 完全な監査可能性

    • すべてのコマンドが改ざん検出でログ
    • サニタイズがトリガーされるとアラート
  5. 最小権限

    • AI は必要最小限の情報のみ取得
    • キー名とマスク値のみ

次のステップ