{
  "schemaVersion": "1.0",
  "item": {
    "slug": "safe-encryption-skill",
    "name": "Safe Encryption",
    "source": "tencent",
    "type": "skill",
    "category": "开发工具",
    "sourceUrl": "https://clawhub.ai/grittygrease/safe-encryption-skill",
    "canonicalUrl": "https://clawhub.ai/grittygrease/safe-encryption-skill",
    "targetPlatform": "OpenClaw"
  },
  "install": {
    "downloadMode": "redirect",
    "downloadUrl": "/downloads/safe-encryption-skill",
    "sourceDownloadUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=safe-encryption-skill",
    "sourcePlatform": "tencent",
    "targetPlatform": "OpenClaw",
    "installMethod": "Manual import",
    "extraction": "Extract archive",
    "prerequisites": [
      "OpenClaw"
    ],
    "packageFormat": "ZIP package",
    "includedAssets": [
      "SKILL.md"
    ],
    "primaryDoc": "SKILL.md",
    "quickSetup": [
      "Download the package from Yavira.",
      "Extract the archive and review SKILL.md first.",
      "Import or place the package into your OpenClaw setup."
    ],
    "agentAssist": {
      "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
      "steps": [
        "Download the package from Yavira.",
        "Extract it into a folder your agent can access.",
        "Paste one of the prompts below and point your agent at the extracted folder."
      ],
      "prompts": [
        {
          "label": "New install",
          "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
        },
        {
          "label": "Upgrade existing",
          "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
        }
      ]
    },
    "sourceHealth": {
      "source": "tencent",
      "status": "healthy",
      "reason": "direct_download_ok",
      "recommendedAction": "download",
      "checkedAt": "2026-05-07T17:22:31.273Z",
      "expiresAt": "2026-05-14T17:22:31.273Z",
      "httpStatus": 200,
      "finalUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-annual-report",
      "contentType": "application/zip",
      "probeMethod": "head",
      "details": {
        "probeUrl": "https://wry-manatee-359.convex.site/api/v1/download?slug=afrexai-annual-report",
        "contentDisposition": "attachment; filename=\"afrexai-annual-report-1.0.0.zip\"",
        "redirectLocation": null,
        "bodySnippet": null
      },
      "scope": "source",
      "summary": "Source download looks usable.",
      "detail": "Yavira can redirect you to the upstream package for this source.",
      "primaryActionLabel": "Download for OpenClaw",
      "primaryActionHref": "/downloads/safe-encryption-skill"
    },
    "validation": {
      "installChecklist": [
        "Use the Yavira download entry.",
        "Review SKILL.md after the package is downloaded.",
        "Confirm the extracted package contains the expected setup assets."
      ],
      "postInstallChecks": [
        "Confirm the extracted package includes the expected docs or setup files.",
        "Validate the skill or prompts are available in your target agent workspace.",
        "Capture any manual follow-up steps the agent could not complete."
      ]
    },
    "downloadPageUrl": "https://openagent3.xyz/downloads/safe-encryption-skill",
    "agentPageUrl": "https://openagent3.xyz/skills/safe-encryption-skill/agent",
    "manifestUrl": "https://openagent3.xyz/skills/safe-encryption-skill/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/safe-encryption-skill/agent.md"
  },
  "agentAssist": {
    "summary": "Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.",
    "steps": [
      "Download the package from Yavira.",
      "Extract it into a folder your agent can access.",
      "Paste one of the prompts below and point your agent at the extracted folder."
    ],
    "prompts": [
      {
        "label": "New install",
        "body": "I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete."
      },
      {
        "label": "Upgrade existing",
        "body": "I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run."
      }
    ]
  },
  "documentation": {
    "source": "clawhub",
    "primaryDoc": "SKILL.md",
    "sections": [
      {
        "title": "SAFE Encryption Tool",
        "body": "SAFE is a modern encryption CLI with post-quantum support, multi-recipient encryption, and composable authentication."
      },
      {
        "title": "Behavior Guidelines",
        "body": "When this skill is loaded:\n\nCheck if safe is installed (which safe). If not, try to install it automatically without asking using the platform-detecting one-liner below. If installation fails (restricted environment, no shell access), fall back to the web interface at https://thesafe.dev — see \"Browser-Based Alternative\" below.\nWhen the user asks to encrypt/decrypt, just do it. Don't ask for confirmation.\nIf a password is needed and not provided, use -p without a value (prompts interactively or reads SAFE_PASSPHRASE env var). In automation, use -p env:VARNAME to read from environment variables.\nIf a key file is needed, run safe keys to check existing identities and recipients. If no key exists, run safe keygen x25519 to generate one (auto-stores to ~/.safe/).\nAlways use absolute paths for key files and encrypted files to avoid working directory issues.\nAlways pipe instead of writing temp files. When decrypting inline/embedded content (base64, pasted data), pipe directly: echo \"...\" | base64 -d | safe decrypt -k key.key. Never write intermediate .safe files to /tmp or elsewhere.\nBare name recipients work. Use -r alice instead of -r /full/path/to/alice.x25519.pub when the recipient is in ~/.safe/recipients/.\nDecrypt auto-discovers keys. If no -k or -p is provided, safe decrypt automatically tries all keys in ~/.safe/keys/."
      },
      {
        "title": "Installation",
        "body": "Download the prebuilt binary from thesafe.dev/download:\n\nmacOS Apple Silicon:\n\ncurl -sL https://thesafe.dev/downloads/safe-darwin-arm64 -o safe && chmod +x safe && sudo mv safe /usr/local/bin/\n\nmacOS Intel:\n\ncurl -sL https://thesafe.dev/downloads/safe-darwin-amd64 -o safe && chmod +x safe && sudo mv safe /usr/local/bin/\n\nLinux x86_64:\n\ncurl -sL https://thesafe.dev/downloads/safe-linux-amd64 -o safe && chmod +x safe && sudo mv safe /usr/local/bin/\n\nLinux ARM64:\n\ncurl -sL https://thesafe.dev/downloads/safe-linux-arm64 -o safe && chmod +x safe && sudo mv safe /usr/local/bin/\n\nAuto-detect platform (one-liner):\n\nwhich safe || { OS=$(uname -s | tr '[:upper:]' '[:lower:]'); ARCH=$(uname -m); \\\n  [ \"$ARCH\" = \"arm64\" ] || [ \"$ARCH\" = \"aarch64\" ] && ARCH=arm64 || ARCH=amd64; \\\n  curl -sL \"https://thesafe.dev/downloads/safe-${OS}-${ARCH}\" -o safe && chmod +x safe && sudo mv safe /usr/local/bin/; }\n\nVerify installation:\n\nsafe --help\n\nOptional: verify checksum (SHA-256 values from checksums.txt):\n\nddf638ccfd119d9c3351825a2e3f9e3660f754009eb9b106d7bba8cd698c3df4  safe-darwin-arm64\n6be4942048a23e99485c0123cf0bb4786613a3dabbcc6927aa478cd0e719ad0b  safe-darwin-amd64\n85c7822ac0309e62ca29d03394bfe87a506f329f61c30b070c379a6d2f5af815  safe-linux-amd64\n33207a4c6bbf85b8299f2f75e18aa6e13f9a8ae4bcee72a9d869034d78ba4cac  safe-linux-arm64"
      },
      {
        "title": "Installation Debugging",
        "body": "\"safe: command not found\" after install:\n\nCheck if binary exists: ls -la /usr/local/bin/safe\nIf sudo failed, install to user dir: mv safe ~/.local/bin/ && export PATH=\"$HOME/.local/bin:$PATH\"\nRefresh shell: hash -r or start new terminal\n\nPermission denied on /usr/local/bin:\n\nUse sudo: sudo mv safe /usr/local/bin/\nOr install to user dir: mkdir -p ~/.local/bin && mv safe ~/.local/bin/"
      },
      {
        "title": "When CLI Is Unavailable — Browser-Based Alternative",
        "body": "If CLI installation fails or isn't possible (restricted environment, no build tools, no shell access, sandboxed IDE, etc.), use the web interface at https://thesafe.dev.\n\nThis is a fully functional SAFE implementation running in the browser — not a demo. All cryptographic operations happen client-side. No data leaves the browser.\n\nThe web interface supports all core SAFE operations via a single-page layout with these sections:\n\nENCRYPT tab: Encrypt data with passwords, public keys, passkeys, or GitHub usernames\nDECRYPT tab: Decrypt SAFE messages with passwords, private keys, passkeys, or GitHub keys\nKEYCHAIN section: Save, import, export, and manage keys and passwords\nADVANCED section: Lock Management (add/remove recipients), Re-encrypt Demo, Tests\nLOG section: View operation log output\n\nManual workflow (no automation needed):\n\nUsers can interact with the web interface directly:\n\nEncrypt: Enter plaintext, add recipients (key, password, passkey, or GitHub username), click \"ENCRYPT\". Copy or download the output.\nDecrypt: Paste/upload/URL-load a SAFE message, add credentials (private key, password, passkey, or GitHub), click \"DECRYPT\". Copy or download the plaintext.\n\nGenerated keys are automatically saved in the KEYCHAIN section and can be reused across operations.\n\nAgent with MCP browser tools (Playwright, Puppeteer, etc.):\n\nIf you have access to browser automation tools (e.g., Playwright MCP server, Claude in Chrome, Puppeteer MCP), you can drive the web interface directly.\n\nKey behaviors to know:\n\nAfter encrypting, output auto-populates into the decrypt section's SAFE message input\nThe browser auto-matches saved credentials and pre-adds them to decrypt\nGenerated keys are auto-saved to the Credentials section (04)\nAlways take a snapshot (browser_snapshot) after each action to get updated element references\n\nARIA labels for automation:\n\nThe interface uses semantic ARIA roles throughout:\n\nElementARIA LabelRoleKEM type selector\"Select key encapsulation mechanism type\"comboboxGenerate button\"Generate new keypair with selected KEM type\"buttonPlaintext input\"Enter plaintext message to encrypt\"textboxAdd Step button\"Add encryption step to recipient path\"buttonStep type selector\"Select encryption step type\"comboboxPassword field (encrypt)\"Enter password for encryption step\"textboxConfirm step\"Confirm encryption step\"buttonEncrypt button\"Encrypt plaintext with configured settings and recipient path\"buttonEncrypted output\"Encrypted SAFE message output\"textboxSAFE message input\"Paste encrypted SAFE message to decrypt\"textboxAdd credential button (decrypt)\"Add credential to decryption attempt\"buttonAdd credential button (keychain)\"Add credential to keychain\"buttonAdd all keychain button\"Add all keychain entries as credentials\"buttonCredential type selector\"Select credential type\"comboboxNew Passkey menu item\"Create a new passkey\"menuitemPassword field (decrypt)\"Enter password for decryption\"textboxConfirm credential\"Confirm credential\"buttonDecrypt button\"Decrypt SAFE message using provided keychain\"buttonDecrypted output\"Decrypted plaintext message\"textboxCopy buttons\"Copy encrypted SAFE message to clipboard\" / \"Copy decrypted plaintext to clipboard\"buttonDownload buttons\"Download encrypted SAFE message as file\" / \"Download decrypted file\"buttonShare button (output)\"Share encrypted SAFE message via URL\" / \"Share decrypted output via URL\"buttonSend button (output)\"Send encrypted output over WebRTC\"button (encrypted output only)Clear button (output)\"Clear encrypted output\" / \"Clear decrypted output\"buttonShare button (keychain)\"Share public key via URL\"buttonLabel button (keychain)\"Rename key label\"buttonUse File toggles\"Use file instead of plaintext input\" / \"Use file instead of SAFE message input\"generic (clickable)Navigation linksNew (#keygen), Encrypt (#encrypt), Decrypt (#decrypt), Keychain (#keyring), Advanced (expandable)linkAdvanced sections#unlock, #reencrypt, #tests, #loglink (under Advanced dropdown)Sectionsrole=\"region\" with labels like \"01 / Key Generation\"regionLog output\"Activity log showing operations and their results\"log\n\nNote on Advanced navigation: The Advanced sections (#unlock, #reencrypt, #tests, #log) are accessed via an \"Advanced\" navigation item that expands to show these additional features.\n\nNote on terminology: The UI currently uses mixed terminology - Section 04 is labeled \"Keychain\" and the decrypt button references \"keychain\", but the decrypt section's credential management buttons still use \"Credentials\" in some ARIA labels (e.g., \"Add all keychain entries as credentials\"). Both terms refer to the same saved keys/passwords.\n\nKeychain shortcut buttons:\n\nEach saved key in Section 04 (Keychain) has quick action buttons:\n\nEnc: Adds the public key as an encryption recipient step (one click — skips the Add Step → select type → paste → OK workflow)\nDec: Adds the private key as a decrypt credential (one click — skips the Add → select type → paste → OK workflow)\nPUB: Shows/copies the public key\nPRIV: Shows/copies the private key\nShare: Generates a shareable URL for the public key\nLabel: Rename the key for easier identification\nDel: Removes the key from the keychain\n\nPrefer using Enc/Dec shortcuts over the manual Add Step flow when keys are saved in the keychain — it reduces 4 interactions to 1.\n\nFile upload:\n\nBoth encrypt and decrypt sections have a \"Use File\" toggle. Clicking it triggers a file chooser dialog. With MCP Playwright, use browser_file_upload to provide the file path. Note: file paths must be within the MCP server's allowed directories.\n\nExample: Encrypt with password (MCP Playwright)\n\n# 1. Navigate\nbrowser_navigate(url=\"https://thesafe.dev\")\nbrowser_snapshot()\n\n# 2. Type plaintext (use ref from snapshot for \"Enter plaintext message to encrypt\")\nbrowser_type(ref=<plaintext-ref>, text=\"secret data\")\n\n# 3. Add password step\nbrowser_click(ref=<add-step-button-ref>)      # \"Add encryption step to recipient path\"\nbrowser_snapshot()                              # Get refs for step config form\n\n# 4. Select Password type (default may be \"Public Key\")\nbrowser_select_option(ref=<step-type-ref>, values=[\"Password\"])  # \"Select encryption step type\"\nbrowser_snapshot()                              # Get password field ref\n\n# 5. Enter password\nbrowser_type(ref=<password-ref>, text=\"my-password\")  # \"Enter password for encryption step\"\n\n# 6. Confirm the step\nbrowser_click(ref=<ok-ref>)                    # \"Confirm encryption step\"\n\n# 7. Encrypt\nbrowser_click(ref=<encrypt-ref>)               # \"Encrypt plaintext with configured settings...\"\nbrowser_snapshot()                              # Output is in \"Encrypted SAFE message output\" textbox\n\n# Optional: Share or clear the output\n# browser_click(ref=<share-button-ref>)        # \"Share encrypted SAFE message via URL\"\n# browser_click(ref=<clear-button-ref>)        # \"Clear encrypted output\"\n\nExample: Encrypt with saved key (fastest path)\n\n# 1. Navigate\nbrowser_navigate(url=\"https://thesafe.dev\")\nbrowser_snapshot()\n\n# 2. Type plaintext\nbrowser_type(ref=<plaintext-ref>, text=\"secret data\")\n\n# 3. Click \"Enc\" on a saved key in Credentials section (one click adds recipient)\nbrowser_click(ref=<enc-button-ref>)\n\n# 4. Encrypt\nbrowser_click(ref=<encrypt-ref>)\nbrowser_snapshot()\n\nExample: Decrypt from cold (no auto-populated credentials)\n\n# 1. Paste SAFE message into decrypt input\nbrowser_type(ref=<safe-message-ref>, text=\"-----BEGIN SAFE UNLOCK-----\\n...\")\n\n# 2. Add credential\nbrowser_click(ref=<add-credential-ref>)        # \"Add credential to decryption attempt\"\nbrowser_snapshot()\n\n# 3. Select Password type (default is \"Private Key\")\nbrowser_select_option(ref=<credential-type-ref>, values=[\"Password\"])\nbrowser_snapshot()\n\n# 4. Enter password\nbrowser_type(ref=<password-ref>, text=\"my-password\")\n\n# 5. Confirm credential\nbrowser_click(ref=<confirm-ref>)               # \"Confirm credential\"\n\n# 6. Decrypt\nbrowser_click(ref=<decrypt-ref>)               # \"Decrypt SAFE message using provided credentials\"\nbrowser_snapshot()                              # Output is in \"Decrypted plaintext message\" textbox\n\nExample: Same-session encrypt→decrypt (auto-populated)\n\nAfter encrypting, the output auto-populates into the decrypt section. If the matching key is saved in credentials, it auto-adds the private key. Just click Decrypt — no manual credential entry needed.\n\nProgrammatic browser automation (standalone scripts):\n\nFor non-MCP environments, use Playwright or Puppeteer directly:\n\n# Example with Playwright (Python)\nfrom playwright.sync_api import sync_playwright\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch()\n    page = browser.new_page()\n    page.goto('https://thesafe.dev')\n\n    # Generate X25519 keypair\n    page.get_by_role(\"combobox\", name=\"Select key encapsulation mechanism type\").select_option(\"X25519\")\n    page.get_by_role(\"button\", name=\"Generate new keypair\").click()\n\n    # Encrypt with password\n    page.get_by_role(\"textbox\", name=\"Enter plaintext message to encrypt\").fill(\"secret message\")\n    page.get_by_role(\"button\", name=\"Add encryption step to recipient path\").click()\n    page.get_by_label(\"Select encryption step type\").select_option(\"Password\")\n    page.get_by_role(\"textbox\", name=\"Enter password for encryption step\").fill(\"mypassword\")\n    page.get_by_role(\"button\", name=\"Confirm encryption step\").click()\n    page.get_by_role(\"button\", name=\"Encrypt plaintext\").click()\n\n    # Read encrypted output\n    encrypted = page.get_by_label(\"Encrypted SAFE message output\").input_value()\n\n    # Decrypt (message and credentials auto-populate from encrypt)\n    page.get_by_role(\"button\", name=\"Decrypt SAFE message\").click()\n    decrypted = page.get_by_label(\"Decrypted plaintext message\").input_value()\n\n    print(f\"Decrypted: {decrypted}\")  # \"secret message\"\n    browser.close()\n\nMulti-Recipient Encryption:\n\nBoth the browser UI and CLI support encrypting for multiple recipients. Each recipient can decrypt the message independently using their own credential.\n\nBrowser workflow:\n\nConfigure first recipient in \"Recipient 1\" (password or public key)\nClick \"+ Add Recipient\" button\nConfigure second recipient in \"Recipient 2\"\nRepeat for additional recipients (no limit)\nClick \"Encrypt\" - message is encrypted once but decryptable by any recipient\n\nHow it works:\n\nEach recipient gets their own UNLOCK block in the SAFE message\nFile is encrypted once with a symmetric key\nSymmetric key is wrapped separately for each recipient\nAny recipient can decrypt using their credential (password or private key)\nRecipients cannot see who else has access\n\nCLI multi-recipient examples:\n\n# Encrypt for multiple recipients using -r flag multiple times\nsafe encrypt -i file.txt -o file.safe -r alice.pub -r bob.pub -r charlie.pub\n\n# Mix recipient types (password + keys)\nsafe encrypt -i file.txt -o file.safe -p mypassword -r alice.pub -r bob.pub\n\n# Encrypt for GitHub users (fetches public keys from GitHub)\nsafe encrypt -i file.txt -o file.safe -r github:grittygrease\n\n# Multiple GitHub users\nsafe encrypt -i file.txt -o file.safe -r github:alice -r github:bob\n\n# Encrypt for GitHub users and a password\nsafe encrypt -i file.txt -o file.safe -p teampassword -r github:alice -r github:bob\n\nGitHub username recipient (github:username):\n\nFetches SSH public keys from https://github.com/{username}.keys\nAutomatically converts p-256 and x25519 keys to SAFE format\nBoth key types are added as separate recipients if available\nRequires user to have public keys on their GitHub profile\nError if no keys found: github:username: no keys found\n\nExample output:\n\n$ safe encrypt -i test.txt -o test.safe -r github:grittygrease\n# Creates UNLOCK blocks for both p-256 and x25519 keys from GitHub\n\n$ safe info -i test.safe\nLOCK Blocks: 2\n  [0] hpke(kem=p-256,id=QyLFP/...)\n  [1] hpke(kem=x25519,id=r1VeL...)\n\nAgent-to-Agent Communication via GitHub Gist:\n\nAgents can securely exchange encrypted messages using GitHub Gist as a transport layer. This enables asynchronous, persistent communication between agents with different GitHub accounts.\n\nComplete Workflow:\n\nAgent A (Sender):\n\n# 1. Create message for Agent B\necho \"Task completed. Results attached.\" > message.txt\n\n# 2. Encrypt for Agent B's GitHub account\nsafe encrypt -i message.txt -o message.safe -r github:agentb-username\n\n# 3. Upload encrypted message to public Gist\ngh gist create message.safe --desc \"Encrypted message for agentb-username\" --public\n\n# Output: https://gist.github.com/agenta-username/{gist-id}\n\nAgent B (Receiver):\n\n# Method 1: Direct pipe (simplest, auto-discovers keys)\ncurl -sL https://gist.github.com/alice/{gist-id}/raw | safe decrypt\n\n# Method 2: Download, inspect, then decrypt\ncurl -sL https://gist.github.com/alice/{gist-id}/raw > received.safe\nsafe info -i received.safe  # Verify sender and encryption details\nsafe decrypt -i received.safe -o message.txt\n\n# Method 3: Explicit key (if auto-discovery doesn't work)\ncurl -sL https://gist.github.com/alice/{gist-id}/raw | safe decrypt -k ~/.safe/keys/bob.x25519.key\n\nSSH Key Auto-Discovery (SAFE CLI v2.3+):\n\nThe SAFE CLI automatically discovers and uses SSH private keys from ~/.ssh/:\n\n✅ Ed25519 keys → converted to X25519\n✅ P-256 ECDSA keys → used directly\n✅ Unencrypted keys only (passphrase-protected keys silently skipped)\n✅ Zero configuration - just works if your SSH keys match GitHub public keys\n\nAuto-Discovery Order:\n\n~/.safe/keys/*.key - Native SAFE format keys (checked first)\n~/.ssh/* - All SSH private keys in ~/.ssh/ directory\n\nEd25519 keys → converted to X25519\nP-256 ECDSA keys → used directly\n\nExample Auto-Discovery Output:\n\n$ curl -sL https://gist.github.com/.../raw | safe decrypt\nsafe: using SSH key ~/.ssh/id_ed25519\nsafe: trying 3 key(s) (2 native + 1 SSH)\n[decrypted message]\n\nKey Requirements:\n\nAgent B must have private keys that correspond to the public keys on their GitHub profile\nGitHub SSH keys must be added to https://github.com/{username}.keys\nPrivate keys can be in ~/.safe/keys/ (SAFE format) OR ~/.ssh/ (OpenSSH format)\nGist can be public (encrypted content is safe) or private for additional obscurity\n\nMulti-Agent Broadcast:\n\n# Encrypt for multiple agents\nsafe encrypt -i broadcast.txt -o broadcast.safe \\\n  -r github:agent1 \\\n  -r github:agent2 \\\n  -r github:agent3\n\n# Any of the three agents can decrypt independently\ngh gist create broadcast.safe --desc \"Team update\" --public\n\nAgent Identity Setup:\n\nTo enable decryption, agents need to set up their GitHub SSH keys and store private keys:\n\n# Option 1: Use existing SSH keys (simplest - zero setup!)\n# If you already have ~/.ssh/id_ed25519 or ~/.ssh/id_ecdsa uploaded to GitHub, you're done!\n# SAFE CLI auto-discovers SSH keys - no key generation needed\n\n# Option 2: Generate new SSH key and upload to GitHub\nssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N \"\" -C \"safe-agent-key\"\ngh ssh-key add ~/.ssh/id_ed25519.pub --title \"SAFE Agent Key\"\n# Done! SAFE CLI will auto-discover this key\n\n# Option 3: Generate SAFE-native keys (for advanced use cases)\nsafe keygen x25519 -o agent-id\n# Upload agent-id.x25519.pub to https://github.com/settings/keys (manual conversion needed)\nmv agent-id.x25519.key ~/.safe/keys/\n\n# Test encryption to self (works with any option above)\nsafe encrypt -i test.txt -o test.safe -r github:your-username\nsafe decrypt -i test.safe -o decrypted.txt\n# With SSH keys, decryption auto-discovers your keys from ~/.ssh/\n\nBrowser-Based Agent Workflow:\n\nAgents using thesafe.dev have full GitHub support for both encryption and decryption.\n\nEncrypt to a GitHub user (browser):\n\nGo to the ENCRYPT tab\nEnter your message\nClick ADD FACTOR → NEW FACTOR → select GITHUB\nEnter the GitHub username (e.g., smithclay)\nClick FETCH KEYS — the browser fetches public keys from https://github.com/{username}.keys\nClick ENCRYPT\nCopy the output and share via Gist\n\nDecrypt a GitHub-encrypted message (browser):\n\nGo to the DECRYPT tab\nLoad the encrypted message: paste text, use the FILE button to upload, or use the URL button to load directly from a Gist URL (e.g., paste the raw Gist URL)\nClick ADD → GITHUB → enter your GitHub username → click FETCH KEYS\n(This matches your public keys to the message's LOCK blocks)\nClick ADD → KEY → paste your SSH private key (from ~/.ssh/id_ed25519 or ~/.ssh/id_ecdsa)\n— or use the \"Import SSH private key from GitHub\" button if available\nClick DECRYPT\n\nRecommended approach for browser-based workflows:\n\nBoth CLI and browser support github:username for encryption equally well\nCLI is simpler for decryption — it auto-discovers SSH keys from ~/.ssh/; browser requires pasting the private key once\nURL button is convenient — load encrypted Gist content directly without curl\n\nSend encrypted output over WebRTC (browser):\n\nThe browser supports real-time peer-to-peer transfer of encrypted SAFE messages via WebRTC — no copy-paste required:\n\nSender:\n\nEncrypt your message as usual to produce the encrypted output\nClick Send in the output toolbar (next to Download, Share, Copy)\nA dialog appears: \"Share join URL, then wait for receiver...\"\nThe join URL is offered via the native OS share sheet (if available) or logged in the Log panel\nKeep the tab open — when the receiver connects, the dialog updates to \"Receiver connected. Starting transfer...\"\nTransfer completes automatically\n\nReceiver:\n\nOpen the join URL: https://thesafe.dev/?session=<id>&token=<token>\nThe page auto-connects, receives the encrypted message, and switches to the Decrypt tab with the message pre-loaded\nAdd credentials and click Decrypt as normal\n\nNotes:\n\nJoin URLs expire after 30 minutes\nMax transfer size: 100 MB\nSender must keep the tab open until the receiver connects\nShare (separate button) shares the file or text directly with no server involved; Send is the WebRTC real-time peer flow\n\nAgent Ping/Notification Workflow:\n\nYou can \"ping\" another agent using their GitHub username without needing their public key in advance:\n\n# Alice pings Bob (discovers keys automatically via github:username)\necho \"PING: Status update requested\" > ping.txt\nsafe encrypt -i ping.txt -o ping.safe -r github:bob\ngh gist create ping.safe --desc \"Ping from Alice\" --public\n\n# Bob discovers the ping and decrypts (SSH key auto-discovery!)\ncurl -sL https://gist.github.com/alice/{gist-id}/raw | safe decrypt\n# safe: using SSH key ~/.ssh/id_ed25519\n# safe: trying 1 key(s) (0 native + 1 SSH)\n# PING: Status update requested\n\n# Bob responds back to Alice\necho \"PONG: Status OK, task 75% complete\" > pong.txt\nsafe encrypt -i pong.txt -o pong.safe -r github:alice\ngh gist create pong.safe --desc \"Response to Alice\" --public\n\nKey Benefits:\n\n✅ No prior key exchange needed - github:username fetches public keys automatically\n✅ No key management needed - reuse existing SSH keys from GitHub\n✅ Works instantly if you already have SSH keys on GitHub\n✅ Works for any GitHub user with public SSH keys on their profile\n✅ Both agents can initiate communication\n✅ Asynchronous - sender doesn't need to wait for response\n✅ Persistent - messages remain in Gist until deleted\n\nDiscovery Methods:\n\nGitHub Gist notifications (if agent watches their own Gists)\nPeriodic polling of GitHub API for new Gists mentioning their username\nGitHub webhooks for real-time notifications\nRSS feeds for public Gists\n\nSecurity Notes:\n\nGist URLs are discoverable if public - use private Gists for sensitive coordination\nEncrypted content is safe even if Gist is public (only recipient has private key)\nGist history is immutable - deleted messages remain in Git history\nUse short-lived Gists and delete after confirmation for ephemeral communication\nMulti-recipient encryption prevents sender from knowing who decrypted the message"
      },
      {
        "title": "CLI vs Browser: Feature Comparison",
        "body": "FeatureCLI (SAFE v2.3+)Browser (thesafe.dev)github:username encryption✅ Yes✅ Yesgithub:username decryption✅ Auto (SSH key auto-discovery)✅ Yes (paste SSH key once)SSH key auto-discovery✅ Yes (~/.ssh/)❌ No (manual paste)Ed25519 SSH keys✅ Auto-converts to X25519✅ Manual pasteP-256 ECDSA SSH keys✅ Direct support✅ Manual pasteSAFE native keys✅ Yes (~/.safe/keys/)✅ Yes (import/export)Load from URL (e.g. Gist)✅ curl <url> | safe decrypt✅ URL button in DECRYPT tabReal-time peer transfer❌ No✅ Send button (WebRTC, 30-min join URL)Zero-setup decryption✅ If SSH keys on GitHub⚠️ Must paste private key once\n\nRecommendation:\n\nEncryption: Both CLI and browser support github:username equally well\nDecryption: CLI is easier (auto-discovers SSH keys); browser requires pasting your private key\nBest of both: Use browser for encryption, CLI for decryption when available\n\nKeychain management:\n\nThe Keychain section (04) supports:\n\nAdd Credential (dropdown menu with options):\n\nImport Key: Import an existing public or private key (PEM or base64)\nNew Passkey: Create a new WebAuthn passkey (requires browser/OS authenticator, prompts for label)\nNew Password: Add a new password for encryption/decryption\nExisting Passkey: Use an existing passkey from your authenticator\n\n\nExport: Export keychain as encrypted SAFE backup (password-protected .safe file containing private keys in PEM format)\nImport: Import a previously exported keychain backup (file upload + passphrase)\nClear All: Delete all keychain entries (shows a confirm dialog)\n\nPasskey Limitations for Automation:\n\nPasskey creation requires WebAuthn hardware interaction (biometric, security key, etc.)\nCannot be fully automated - requires user interaction with authenticator\nDialog prompts for \"Passkey label\" before creation\nBest suited for interactive sessions, not headless automation\n\nGenerated keys are automatically saved here. Each key shows its type and Key ID hints, with Enc/Dec/Share/PUB/PRIV/Label/Del action buttons.\n\nExport/Import workflow (for persisting keys across sessions):\n\nExport:\n\nClick \"Export credentials as encrypted backup\" → dialog appears\nEnter passphrase + confirm passphrase → click OK\nDownloads safe-credentials-backup.safe (a standard SAFE file, pwd-encrypted)\nThe export contains private keys only (PEM format). Public keys are derived on re-import.\n\nImport:\n\nClick \"Import\" → file chooser appears (use browser_file_upload with MCP)\nSelect the .safe backup file → passphrase dialog appears\nEnter passphrase → click OK\nKeys are restored with full functionality (Enc/Dec/PUB/PRIV buttons)\nDuplicates are automatically skipped\n\nThe exported .safe file is CLI-compatible: safe decrypt safe-credentials-backup.safe -p \"passphrase\" reveals the PEM keys."
      },
      {
        "title": "Agent Session Persistence (Browser Workflow)",
        "body": "Agents can maintain a persistent identity across browser sessions by saving and re-uploading the keychain backup:\n\nFirst browser session:\n\nNavigate to https://thesafe.dev\nGenerate keys (Section 01) or receive keys from other agents\nExport keychain: Click \"Export\" → enter passphrase → download safe-credentials-backup.safe\nSave the encrypted backup file to persistent storage (filesystem, database, cloud storage, etc.)\nStore the passphrase securely (environment variable, secure key store, etc.)\n\nNew browser session (different browser, incognito, new tab, etc.):\n\nNavigate to https://thesafe.dev\nImport keychain: Click \"Import\" → upload safe-credentials-backup.safe → enter passphrase\nAll keys are restored with full functionality (Enc/Dec/PUB/PRIV/Share/Label/Del)\nAgent can immediately encrypt/decrypt using saved keys\n\nBenefits:\n\nAgents maintain the same cryptographic identity across sessions\nNo need to regenerate keys or re-exchange public keys with other agents\nEncrypted communication history remains accessible\nWorks across different browsers, devices, and environments\n\nSecurity notes:\n\nThe backup file is encrypted with the passphrase (argon2id by default)\nFile is CLI-compatible: safe decrypt safe-credentials-backup.safe -p \"passphrase\"\nStore the passphrase separately from the backup file\nBackup contains only private keys in PEM format; public keys are derived on import\n\nExample workflow with MCP Playwright:\n\n# First session: Export keychain\nbrowser_click(ref=<export-button-ref>)  # \"Export keychain\"\n# Enter passphrase in dialog\nbrowser_type(ref=<passphrase-ref>, text=\"agent-secret-passphrase\")\nbrowser_type(ref=<confirm-passphrase-ref>, text=\"agent-secret-passphrase\")\nbrowser_click(ref=<ok-ref>)\n# File downloads as safe-credentials-backup.safe\n# Save this file to persistent storage\n\n# New session: Import keychain\nbrowser_navigate(url=\"https://thesafe.dev\")\nbrowser_click(ref=<import-button-ref>)  # \"Import keychain backup\"\nbrowser_file_upload(paths=[\"/path/to/safe-credentials-backup.safe\"])\n# Enter passphrase in dialog\nbrowser_type(ref=<passphrase-ref>, text=\"agent-secret-passphrase\")\nbrowser_click(ref=<ok-ref>)\n# All keys restored, agent can now encrypt/decrypt\n\nThis enables agents to maintain cryptographic identities across:\n\nBrowser restarts\nIncognito/private browsing sessions\nDifferent physical machines\nCloud-based agent deployments\n\nWhen to use the web interface:\n\nCLI can't be installed (no Go/Rust, restricted environment, sandboxed IDE)\nNo shell access (browser-only agent, web-based coding environment)\nOne-off encryption/decryption tasks\nTesting SAFE format without installing dependencies\nQuick key generation or format exploration\n\nWhen to prefer the CLI:\n\nProduction systems or automated pipelines\nBatch or high-volume operations (CLI is significantly faster)\nAir-gapped or offline environments\nScripting with shell pipes and file I/O"
      },
      {
        "title": "Key Storage Convention",
        "body": "Personal keys are stored in ~/.safe/ (similar to ~/.ssh/). The CLI manages this directory automatically:\n\nsafe keygen x25519                     # Generates keypair, auto-stores to ~/.safe/\nsafe keygen x25519 -n alice            # Named identity \"alice\"\nsafe keys                              # List all identities and recipients\n\nKey Discovery Order (SAFE CLI v2.3+):\n\n~/.safe/keys/*.key - SAFE-native keys (checked first)\n~/.ssh/* - All SSH private keys in ~/.ssh/ directory\n\nEd25519 keys → auto-converted to X25519\nP-256 ECDSA keys → used directly\n\nNote: You can use EITHER format - SSH keys from GitHub work with zero configuration!\n\nDirectory structure (auto-created by safe keygen):\n\n~/.safe/keys/ — Private keys (0700, never share). E.g., nick.x25519.key\n~/.safe/*.pub — Your own public keys (safe to share). E.g., nick.x25519.pub\n~/.safe/recipients/ — Other people's public keys (managed by safe keys add)\n\nOverride with SAFE_HOME env var. Fallback: ./.safe/ in current directory."
      },
      {
        "title": "Generate Keys",
        "body": "Key TypeCommandUse Casex25519safe keygen x25519Fast, default, widely supportedp-256safe keygen p-256FIPS complianceml-kem-768safe keygen ml-kem-768Post-quantum security (seed by default)\n\nBy default, keygen uses $USER as the identity name and stores keys in ~/.safe/. Override with -n name or -o path.\n\nsafe keygen x25519                     # ~/.safe/keys/$USER.x25519.key + ~/.safe/$USER.x25519.pub\nsafe keygen x25519 -n alice            # ~/.safe/keys/alice.x25519.key + ~/.safe/alice.x25519.pub\nsafe keygen ml-kem-768                 # Generates seed (compact format, default for ML-KEM)\nsafe keygen ml-kem-768 -no-seed        # Raw keypair instead of seed\nsafe keygen x25519 -o /tmp/throwaway   # Custom output path\nsafe keygen x25519 -force              # Overwrite existing files\n\nOutput: <name>.<type>.pub (share this) and <name>.<type>.key (keep secret). Public key is always written to ~/.safe/."
      },
      {
        "title": "Manage Keys",
        "body": "# List all identities and known recipients\nsafe keys\n\n# Import a recipient's public key\nsafe keys add alice.x25519.pub --name alice\n\n# Remove a recipient\nsafe keys remove alice\n\n# Derive public key from private key (by name or path)\nsafe pubkey alice                       # Looks up ~/.safe/keys/alice.*.key\nsafe pubkey /path/to/key.key            # Direct file path\n\n# View key details\nsafe keyinfo alice.x25519.pub\n\nBare names work as recipients after import: safe encrypt data.txt -r alice resolves from ~/.safe/recipients/. Also resolves system users: -r bob checks ~bob/.safe/*.pub."
      },
      {
        "title": "Encrypt",
        "body": "stdin is the default input, stdout is the default output. Positional argument sets input file.\n\n# Password-protect a file\nsafe encrypt secrets.txt -o secrets.safe -p \"strong-password\"\n\n# Encrypt to recipient (bare name or key file)\nsafe encrypt file.txt -o file.safe -r alice\n\n# Multiple recipients (OR - any one can decrypt)\nsafe encrypt file.txt -o file.safe -r alice -r bob\n\n# Two-factor: password AND key required (+ is AND separator)\nsafe encrypt file.txt -o file.safe -r \"pwd:secret + alice.pub\"\n\n# Pipe from stdin (default)\necho \"secret\" | safe encrypt -p \"pw\" > msg.safe\n\n# Password from environment variable\nsafe encrypt file.txt -o file.safe -p env:MY_PASSWORD\n\n# PBKDF2 instead of argon2id\nsafe encrypt file.txt -o file.safe -p \"pw\" --kdf pbkdf2"
      },
      {
        "title": "Decrypt",
        "body": "stdin is the default input, stdout is the default output. If no credentials are provided, keys from ~/.safe/keys/ are tried automatically.\n\n# With password\nsafe decrypt file.safe -p \"password\"\n\n# With private key\nsafe decrypt file.safe -k alice.x25519.key\n\n# Auto-discover keys (no -k needed if keys are in ~/.safe/keys/)\nsafe decrypt file.safe\n\n# age-compatible --identity flag\nsafe decrypt file.safe --identity alice.key\n\n# Two-factor (all credentials required)\nsafe decrypt file.safe -o file.txt -p \"secret\" -k alice.key\n\n# Write to file instead of stdout\nsafe decrypt file.safe -o plaintext.txt -p \"password\"\n\n# Password from environment variable\nsafe decrypt file.safe -p env:MY_PASSWORD"
      },
      {
        "title": "Info",
        "body": "Inspect a SAFE file's metadata without credentials:\n\nsafe info file.safe\n# Output:\n#   AEAD: aes-256-gcm\n#   Block Size: 65536\n#   Key Hash: spki-sha256-16\n#   Data size: 1048 bytes\n#   UNLOCK Blocks: 2\n#     [0] pwd(argon2id)\n#     [1] hpke(kem=x25519, id=r3YlsKxQHj1q1d/kKi5e3Q==)\n\n# From stdin\ncat file.safe | safe info"
      },
      {
        "title": "Piping (stdin/stdout)",
        "body": "stdin and stdout are the defaults — no -i - or -o - needed. All operations are binary-safe.\n\nDefault behavior: Always prefer piping over writing intermediate files to disk. This avoids leaving decrypted content on disk and is cleaner.\n\n# Decrypt base64-encoded content (PREFERRED - no temp file)\necho \"LS0tLS1CRUdJTi...\" | base64 -d | safe decrypt -k ~/.safe/keys/id.x25519.key\n\n# AVOID: Writing intermediate files\n# echo \"LS0tLS1CRUdJTi...\" | base64 -d > /tmp/file.safe && safe decrypt /tmp/file.safe ...\n\n# Basic stdin/stdout\necho \"secret\" | safe encrypt -p \"pw\" > encrypted.safe\ncat encrypted.safe | safe decrypt -p \"pw\"\n\n# Chain operations (re-encrypt with different key)\nsafe decrypt a.safe -p \"pw1\" | safe encrypt -o b.safe -p \"pw2\"\n\n# Encrypt with compression\ntar cz src/ | safe encrypt -o backup.safe -r alice\n\n# Decrypt and decompress\nsafe decrypt backup.safe -k team.key | tar xz\n\n# Decrypt remote file\ncurl -s https://example.com/data.safe | safe decrypt -k my.key\n\n# Pipe through compression then encrypt\nsafe encrypt -p \"pw\" < large.bin | gzip > encrypted.safe.gz\n\n# Decrypt gzipped safe file\ngunzip -c encrypted.safe.gz | safe decrypt -p \"pw\" > large.bin\n\nNote: -i - and -o - still work for explicit stdin/stdout but are no longer required."
      },
      {
        "title": "Protect API Keys / .env Files",
        "body": "safe encrypt .env -o .env.safe -p \"dev-password\"\nsafe encrypt credentials.json -o credentials.safe -r ops-team"
      },
      {
        "title": "Share Secrets with a Teammate",
        "body": "# They generate their key\nsafe keygen x25519 -n teammate\n\n# You import their public key\nsafe keys add teammate.x25519.pub --name teammate\n\n# You encrypt for them (bare name!)\nsafe encrypt api-keys.txt -o api-keys.safe -r teammate\n\n# They decrypt (auto-discovers keys from ~/.safe/keys/)\nsafe decrypt api-keys.safe -o api-keys.txt"
      },
      {
        "title": "Encrypt Backup Before Cloud Upload",
        "body": "tar czf backup.tar.gz ~/Documents\nsafe encrypt backup.tar.gz -o backup.safe -p \"backup-phrase\" -r recovery\n# Upload backup.safe to S3/GCS/Dropbox"
      },
      {
        "title": "Encrypt Entire Directories",
        "body": "# Encrypt a folder\ntar cz project/ | safe encrypt -o project.safe -r team\n\n# Decrypt and extract\nsafe decrypt project.safe -k team.key | tar xz"
      },
      {
        "title": "Git-Friendly Encrypted Secrets",
        "body": "# Encrypt secrets, commit the .safe file\nsafe encrypt .env.production -o .env.production.safe -r deploy\ngit add .env.production.safe  # Safe to commit\n\n# On deploy server (auto-discovers deploy key from ~/.safe/keys/)\nsafe decrypt .env.production.safe -o .env.production"
      },
      {
        "title": "Separation of Duties (Two People Required)",
        "body": "# Encrypt requiring BOTH Alice and Bob (+ is AND)\nsafe encrypt codes.txt -o codes.safe -r \"alice.pub + bob.pub\"\n\n# Decrypt (both must provide keys)\nsafe decrypt codes.safe -o codes.txt -k alice.key -k bob.key"
      },
      {
        "title": "Two-Factor Encryption (Password + Key)",
        "body": "# Encrypt: requires password AND key\nsafe encrypt secrets.txt -o secrets.safe -r \"pwd:mypassword + hardware.pub\"\n\n# Decrypt: must provide both\nsafe decrypt secrets.safe -o secrets.txt -p \"mypassword\" -k hardware.key"
      },
      {
        "title": "Team Encryption + Emergency Backup",
        "body": "safe encrypt secrets.txt -o secrets.safe \\\n  -r alice -r bob -r carol \\\n  -p \"emergency-recovery-phrase\""
      },
      {
        "title": "Post-Quantum Hybrid Protection",
        "body": "# Generate both classical and PQ keys\nsafe keygen x25519 -n alice\nsafe keygen ml-kem-768 -n alice\n\n# Encrypt with both (future-proof against quantum computers)\nsafe encrypt data.txt -o data.safe \\\n  -r \"pwd:phrase + alice.x25519.pub + alice.ml-kem-768.pub\""
      },
      {
        "title": "Temporary Decryption (No File on Disk)",
        "body": "# Use decrypted content without writing to disk\n./my-app --config <(safe decrypt config.safe -p \"pw\")\n\n# Compare two encrypted files\ndiff <(safe decrypt old.safe -p pw) <(safe decrypt new.safe -p pw)"
      },
      {
        "title": "Password Rotation",
        "body": "# Change password without re-encrypting data\nsafe unlock replace secrets.safe -p \"old-password\" \\\n  --index 0 --recipient \"pwd:new-password\""
      },
      {
        "title": "Key Rotation (Compromised Key)",
        "body": "# View current recipients\nsafe info secrets.safe\n\n# Remove compromised key, add new one\nsafe unlock remove secrets.safe -k admin.key --index 2\nsafe unlock add secrets.safe -k admin.key --recipient new-employee.pub"
      },
      {
        "title": "Composable Paths (AND vs OR Logic)",
        "body": "Encrypt WithDecrypt RequiresLogic-r alice -r bob-k alice.key OR -k bob.keyOR-r \"alice.pub + bob.pub\"-k alice.key AND -k bob.keyAND-r \"pwd:x + alice.pub\"-p x AND -k alice.keyAND-p backup -r alice-p backup OR -k alice.keyOR\n\nMultiple -r or -p flags = OR (any one works)\n+ within one -r = AND (all required)\n\nNote: -> is deprecated but still works. Use + for new code."
      },
      {
        "title": "Editing Encrypted Files",
        "body": "SAFE supports random-access editing without full re-encryption. Only modified chunks are re-encrypted - unchanged chunks are copied byte-for-byte."
      },
      {
        "title": "Data Input Options",
        "body": "OptionUseExample--data \"string\"Literal text on command line--data \"hello\"--data-file pathRead content from a file--data-file patch.bin"
      },
      {
        "title": "Read Bytes at Offset",
        "body": "Read a portion of an encrypted file without decrypting the whole thing:\n\n# Read first 100 bytes\nsafe read file.safe -p \"pw\" --offset 0 --length 100\n\n# Read bytes 500-600 to a file\nsafe read file.safe -o excerpt.txt -k key.key --offset 500 --length 100\n\n# Shorthand with -n for offset\nsafe read file.safe -p \"pw\" -n 1024 --length 256\n\n# age-compatible --identity flag\nsafe read file.safe --identity key.key -n 0 --length 100"
      },
      {
        "title": "Write Bytes at Offset (In-Place Edit)",
        "body": "Modify bytes at a specific position. In-place by default (no -o needed):\n\n# Overwrite bytes starting at offset 10\nsafe write file.safe -p \"pw\" -n 10 --data \"new content\"\n\n# Replace header from a file\nsafe write config.safe -p \"pw\" -n 0 --data-file header.bin\n\n# With --identity flag\nsafe write file.safe --identity key.key -n 0 --data \"UPDATED\"\n\nNote: Write only supports in-place modification. Output defaults to overwriting the input file."
      },
      {
        "title": "Append Data",
        "body": "Add data to the end of an encrypted file (in-place by default):\n\n# Append log entry\nsafe append log.safe -p \"pw\" --data \"$(date): Event occurred\\n\"\n\n# Append from file\nsafe append data.safe -k key.key --data-file new-records.csv\n\n# Append binary data\nsafe append archive.safe -p \"pw\" --data-file chunk.bin"
      },
      {
        "title": "In-Place Editing Workflow",
        "body": "# 1. Check current content\nsafe read config.safe -p \"pw\" -n 0 --length 50\n\n# 2. Make targeted edit\nsafe write config.safe -p \"pw\" -n 25 --data \"new_value\"\n\n# 3. Verify the change\nsafe read config.safe -p \"pw\" -n 0 --length 50"
      },
      {
        "title": "Managing Recipients (UNLOCK Blocks)",
        "body": "Modify who can decrypt without re-encrypting the data. These operations only change the UNLOCK blocks — the encrypted DATA remains identical.\n\n# View current recipients and their indexes\nsafe info file.safe\n# Shows: [0] pwd(argon2id)\n#        [1] hpke(kem=x25519, id=ABC123...)\n\n# Add new recipient (in-place by default)\nsafe unlock add file.safe -p \"current-pw\" -r alice.pub\nsafe unlock add file.safe -k admin.key -r \"pwd:backup-pass\"\n\n# Add composable recipient (password + key required)\nsafe unlock add file.safe -p \"pw\" -r \"pwd:secret + bob.pub\"\n\n# Remove recipient by index (in-place)\nsafe unlock remove file.safe -k admin.key --index 0\n\n# Replace recipient at index (in-place)\nsafe unlock replace file.safe -p \"old-pw\" --index 0 -r \"pwd:new-pw\"\n\n# Write to new file instead of in-place\nsafe unlock add file.safe -o new-file.safe -p \"pw\" -r alice.pub\n\n# With --identity flag\nsafe unlock add file.safe --identity admin.key -r bob.pub\n\nNote: You cannot remove the last UNLOCK block — the file would become undecryptable."
      },
      {
        "title": "AEAD (Content Encryption)",
        "body": "AlgorithmFlagUse CaseAES-256-GCM--aead aes-256-gcmDefault, hardware acceleratedChaCha20-Poly1305--aead chacha20-poly1305ARM, older CPUs without AES-NIAEGIS-256--aead aegis-256CLI only - Key-committing, highest security. Not available in browser UI."
      },
      {
        "title": "Key Types",
        "body": "TypeSecuritySize (pub/priv)x25519Classical, fast32B / 32Bp-256FIPS compliant65B / 32Bml-kem-768Post-quantum1184B / 2400B"
      },
      {
        "title": "Key ID Modes",
        "body": "Control how much key identity information is included in UNLOCK blocks:\n\nModeFlagBehaviorFull--key-id-mode fullDefault. Full key ID included — recipients can check if a message is for them without attempting decryptionHint--key-id-mode hint4-digit hint only — reduces metadata, recipients may need to try decryptionAnonymous--key-id-mode anonymousNo key ID — recipient must try all their keys. Maximum privacy\n\n# Encrypt with hint-only key ID\nsafe encrypt file.txt -o file.safe -r alice --key-id-mode hint\n\n# Encrypt with no key ID (anonymous recipient)\nsafe encrypt file.txt -o file.safe -r alice --key-id-mode anonymous\n\nUse hint or anonymous when you want to hide who can decrypt a message. The encrypted data is identical — only the metadata changes."
      },
      {
        "title": "Password KDF",
        "body": "AlgorithmFlagUse CaseArgon2id--kdf argon2idDefault. Memory-hard, GPU-resistant (64 MiB, 2 iterations)PBKDF2--kdf pbkdf2Constrained environments (600,000 iterations)\n\nsafe encrypt file.txt -o file.safe -p \"pw\" --kdf pbkdf2"
      },
      {
        "title": "Key Hash Algorithm",
        "body": "AlgorithmFlagUse CaseSPKI-SHA256-16--key-hash spki-sha256-16Default. 16-byte truncated SHA-256SPKI-TurboSHAKE256--key-hash spki-turboshake256Alternative hash function\n\nsafe encrypt file.txt -o file.safe -r alice --key-hash spki-turboshake256"
      },
      {
        "title": "Migration from GPG/PGP",
        "body": "# Decrypt old GPG file, re-encrypt with SAFE\ngpg -d old-secrets.gpg | safe encrypt -o secrets.safe -r newkey"
      },
      {
        "title": "Edge Cases & Tips",
        "body": "Empty files: Encrypting empty files works correctly and produces valid .safe output.\n\nBinary data: SAFE handles all byte values (0x00-0xFF) correctly. No text encoding issues.\n\nUnicode passwords: Passwords are UTF-8 encoded. Multi-byte characters work correctly.\n\nLarge files: Files are encrypted in 64KB chunks by default. Only modified chunks are re-encrypted during edits.\n\nBlock size options: Use --block-size flag (16384, 32768, or 65536 bytes) to tune for your use case."
      },
      {
        "title": "Troubleshooting",
        "body": "Decryption fails:\n\nCheck password/key is correct\nFor composable paths, ALL credentials must be provided (partial won't work)\nVerify file integrity: safe info file.safe\nWrong key type? Check with safe keyinfo mykey.key\nCheck available keys: safe keys\n\n\"safe: command not found\":\nRun installation steps above. Verify with which safe.\n\nComposable path errors:\n\npwd:secret + alice.pub requires BOTH -p secret AND -k alice.key to decrypt\nProviding only the password or only the key will fail\nOrder of -k flags doesn't matter, but all must be present"
      },
      {
        "title": "Security Notes",
        "body": ".key files are secret — never share them\n.pub files are safe to distribute\nComposable paths (+) provide defense-in-depth\nML-KEM-768 protects against future quantum computers\nArgon2id password hashing is memory-hard and GPU-resistant (64 MiB memory, 2 iterations)"
      },
      {
        "title": "Password Security",
        "body": "Warning: Passwords passed via -p are visible in shell history and process listings.\n\nMitigations:\n\n# 1. Prefix with space to skip history (bash/zsh with HISTCONTROL=ignorespace)\n safe encrypt file.txt -o file.safe -p \"secret\"\n\n# 2. Use a key file instead of password\nsafe encrypt file.txt -o file.safe -r alice\n\n# 3. Read password from environment variable (RECOMMENDED for automation)\nMY_PW=\"secret\" safe encrypt file.txt -o file.safe -p env:MY_PW\n\n# 4. Use SAFE_PASSPHRASE env var with bare -p\nexport SAFE_PASSPHRASE=\"secret\"\nsafe encrypt file.txt -o file.safe -p\n\n# 5. Read password from file (careful with permissions)\nsafe encrypt file.txt -o file.safe -p \"$(cat ~/.safe/.password)\"\n\n# 6. Clear history after use\nhistory -d $(history 1 | awk '{print $1}')\n\nenv:VARNAME security: Environment variables are visible to the current process and child processes. They're safer than command-line args (not in /proc/*/cmdline) but still visible via /proc/*/environ on Linux. For maximum security, use key-based encryption."
      },
      {
        "title": "Agent-to-Agent Encrypted Communication",
        "body": "Agents can exchange encrypted messages across any text-based channel: email, forums, GitHub issues, Slack, shared files, etc.\n\nThe key management system makes agent-to-agent communication simple:\n\nsafe keygen — generates identity keys (auto-stored in ~/.safe/)\nsafe keys add — imports other agents' public keys as named recipients\nsafe encrypt -r name — encrypts to any known recipient by name\nsafe decrypt — auto-discovers keys, no -k flag needed"
      },
      {
        "title": "First-Run Setup",
        "body": "On first use, check for existing keys and generate if missing:\n\n# Check if any agent identities exist\nsafe keys 2>/dev/null || true\n\n# Generate if missing\nif [ ! -f ~/.safe/keys/*.key ]; then\n    safe keygen x25519\n    echo \"Generated new agent identity\"\n    safe keys\nfi\n\nThis creates:\n\n~/.safe/keys/$USER.x25519.key — your private key (never share)\n~/.safe/$USER.x25519.pub — your public key (share freely)"
      },
      {
        "title": "Exchanging Keys Between Agents",
        "body": "Agents discover each other by sharing public keys. The safe keys add command imports keys into the recipients directory:\n\n# Agent A: Share your public key\ncat ~/.safe/$USER.x25519.pub\n# Copy this to the other agent (paste, file, AGENTS.md, etc.)\n\n# Agent B: Import Agent A's public key\nsafe keys add /path/to/agent-a.x25519.pub --name agent-a\n\n# Verify import\nsafe keys\n# Shows:\n#   Identities:\n#     nick         x25519         /Users/nick/.safe/keys/nick.x25519.key\n#   Recipients:\n#     agent-a      x25519         /Users/nick/.safe/recipients/agent-a.x25519.pub\n\n# Now Agent B can encrypt to Agent A by name\nsafe encrypt message.txt -o message.safe -r agent-a\n\nAdvertising your keys directory: Agents should make their ~/.safe/*.pub files discoverable. Common approaches:\n\nLocationHowAGENTS.md in repoInclude PEM block in markdown~/.safe/*.pub filesOther agents on same system: -r username resolves ~username/.safe/*.pubGitHub gistPost PEM key, recipient imports with curl | safe keys addForum/email signatureInclude PEM block or link to public key"
      },
      {
        "title": "Convention: Reply-To Keys",
        "body": "To enable replies, prepend your public key(s) to the message before encrypting:\n\n-----BEGIN X25519 PUBLIC KEY-----\nfBhEEEB+CepxNQIfPtxnIhWbDUyo+Z/W17cYKlCbsDg=\n-----END X25519 PUBLIC KEY-----\n\nHere is the secret data you requested...\n\nWhen an agent decrypts a message and finds PEM public key blocks at the top, it saves them with safe keys add and encrypts the reply to that name.\n\nMultiple reply-to keys: If the sender wants multiple agents/identities to decrypt the reply:\n\n-----BEGIN X25519 PUBLIC KEY-----\nfBhEEEB+CepxNQIfPtxnIhWbDUyo+Z/W17cYKlCbsDg=\n-----END X25519 PUBLIC KEY-----\n-----BEGIN ML-KEM-768 PUBLIC KEY-----\n<base64...>\n-----END ML-KEM-768 PUBLIC KEY-----\n\nMessage body here...\n\nThe receiving agent extracts all key blocks, imports them with safe keys add, and encrypts the reply to all of them."
      },
      {
        "title": "Workflow: Send a Message",
        "body": "# 1. Create message with your public key as reply address\nsafe pubkey $USER > message.txt\necho \"\" >> message.txt\necho \"Here are the API credentials you requested...\" >> message.txt\n\n# 2. Encrypt to recipient (bare name from recipients dir)\nsafe encrypt message.txt -o message.safe -r recipient\n\n# 3. Share message.safe via any channel (email, forum, git, shared folder, etc.)"
      },
      {
        "title": "Checking if a Message is For You",
        "body": "Before attempting to decrypt, check if your key ID matches any unlock block:\n\n# Get Key IDs from the encrypted file\nsafe info message.safe\n# Output includes:\n#   UNLOCK Blocks: 2\n#   [0] hpke(kem=x25519, id=1SB5W2LJ8/DNu8rn+vaGHA==)\n#   [1] hpke(kem=ml-kem-768, id=abc123...)\n\n# Get your key info\nsafe keyinfo ~/.safe/$USER.x25519.pub\n\n# Or just try to decrypt — auto-key discovery handles it\nsafe decrypt message.safe -o message.txt\n\nNote on key ID modes: If the sender used --key-id-mode hint, you'll see hint=XXXX instead of a full ID. If they used --key-id-mode anonymous, there will be no key ID at all — you'll need to try decrypting (auto-discovery handles this)."
      },
      {
        "title": "Workflow: Receive and Reply",
        "body": "# 1. Decrypt the message (auto-discovers keys from ~/.safe/keys/)\nsafe decrypt message.safe -o message.txt\n\n# 2. Extract reply-to keys and import them\n# Save PEM blocks to a temp file, then import:\ngrep -A2 'BEGIN.*PUBLIC KEY' message.txt > /tmp/sender.pub\nsafe keys add /tmp/sender.pub --name sender\n\n# 3. Create and encrypt reply\nsafe pubkey $USER > reply.txt\necho \"\" >> reply.txt\necho \"Thanks, here's my response...\" >> reply.txt\n\n# Encrypt to sender (bare name!)\nsafe encrypt reply.txt -o reply.safe -r sender"
      },
      {
        "title": "Publishing Your Public Key",
        "body": "Share your public key so others can send you encrypted messages:\n\nLocationUse CaseAGENTS.md in repoProject-specific agent identityGitHub profile / gistPersonal agent keyForum signatureCommunity communicationShared team folderInternal team useEmail signatureEmail-based exchange\n\nExample AGENTS.md:\n\n## Agent Keys\n\n### Deploy Agent\n\\`\\`\\`\n-----BEGIN X25519 PUBLIC KEY-----\nfBhEEEB+CepxNQIfPtxnIhWbDUyo+Z/W17cYKlCbsDg=\n-----END X25519 PUBLIC KEY-----\n\\`\\`\\`\n\nTo send encrypted data to this agent:\n\\`\\`\\`bash\n# Import the key\nsafe keys add deploy-agent.pub --name deploy\n\n# Encrypt\nsafe encrypt data.txt -o data.safe -r deploy\n\\`\\`\\`"
      },
      {
        "title": "Handling Multiple Identities",
        "body": "Agents may have different keys for different contexts:\n\nsafe keygen x25519 -n personal     # ~/.safe/keys/personal.x25519.key\nsafe keygen x25519 -n work         # ~/.safe/keys/work.x25519.key\nsafe keygen ml-kem-768 -n pq       # ~/.safe/keys/pq.ml-kem-768.key\n\n# List all\nsafe keys\n\nWhen sending, choose the appropriate reply-to key for the context. When receiving, auto-discovery tries all keys in ~/.safe/keys/."
      },
      {
        "title": "Error Handling",
        "body": "If decryption fails even though key ID matched:\n\nThe file may be corrupted — check with safe info file.safe\nFor composable paths, ALL required credentials must be provided\nReport the error clearly; don't silently fail"
      },
      {
        "title": "Checking All Identities",
        "body": "With auto-key discovery, this is usually unnecessary. But for manual checking:\n\n# List all your identities\nsafe keys\n\n# Check message recipients\nsafe info message.safe\n\n# Just try to decrypt — auto-discovers all keys\nsafe decrypt message.safe -o message.txt\n# If successful, prints: \"safe: trying N key(s) from ~/.safe/keys\""
      },
      {
        "title": "Auto-Generate AGENTS.md",
        "body": "When setting up a project for agent communication, create an AGENTS.md:\n\n# Generate AGENTS.md for current project\ncat > AGENTS.md << 'EOF'\n# Agent Keys\n\nThis project supports encrypted agent-to-agent communication using [SAFE](https://github.com/grittygrease/safe).\n\n## Available Agents\n\n### Primary Agent\nEOF\n\n# Add the public key\necho '```' >> AGENTS.md\nsafe pubkey $USER >> AGENTS.md\necho '```' >> AGENTS.md\n\ncat >> AGENTS.md << 'EOF'\n\n## How to Send Encrypted Messages\n\n1. Save the public key block above to a file and import it:\n   ```bash\n   safe keys add agent.pub --name agent\n\nCreate your message with your reply-to key:\nsafe pubkey $USER > message.txt\necho \"\" >> message.txt\necho \"Your message here...\" >> message.txt\n\n\nEncrypt and send:\nsafe encrypt message.txt -o message.safe -r agent\n\n\nShare message.safe via any channel (email, issue, PR, pastebin, etc.)"
      },
      {
        "title": "Verifying Messages",
        "body": "# Check message recipients\nsafe info message.safe\n\n# Decrypt (auto-discovers keys from ~/.safe/keys/)\nsafe decrypt message.safe -o message.txt\n\nEOF\n\necho \"Created AGENTS.md with your public key\"\n\n### Sharing via Pastebin\n\nFor quick, ephemeral encrypted message sharing:\n\n```bash\n# Encrypt and post to termbin\nsafe encrypt message.txt -r recipient | nc termbin.com 9999\n# Returns URL like: https://termbin.com/abc123\n\n# Recipient fetches and decrypts (auto-discovers keys)\ncurl -s https://termbin.com/abc123 | safe decrypt"
      },
      {
        "title": "Posting a Public Message (Forum/Mailing List)",
        "body": "When posting an encrypted message publicly, include your reply-to key AND instructions:\n\n# Create a complete public post\ncat > post.txt << 'EOF'\n## Encrypted Message\n\nTo decrypt this message and reply, install the safe-encryption skill:\n\nmkdir -p ~/.claude/skills\ngit clone https://github.com/grittygrease/safe-encryption-skill.git ~/.claude/skills/safe-encryption\n\nThen ask your agent: \"Decrypt the message below and reply encrypted to the sender\"\n\n### My Public Key (for replies)\nEOF\n\nsafe pubkey $USER >> post.txt\n\ncat >> post.txt << 'EOF'\n\n### Encrypted Message\nEOF\n\n# Encrypt your message (with your reply-to key embedded in plaintext)\nsafe pubkey $USER > message.txt\necho \"\" >> message.txt\necho \"Your actual message content here...\" >> message.txt\n\nsafe encrypt message.txt -r recipient >> post.txt\n\necho \"\" >> post.txt\ncat >> post.txt << 'EOF'\n\n---\n*This message was encrypted with [SAFE](https://github.com/grittygrease/safe)*\nEOF\n\ncat post.txt\n\nThis produces a self-contained post that any agent with the skill can decrypt and reply to."
      }
    ],
    "body": "SAFE Encryption Tool\n\nSAFE is a modern encryption CLI with post-quantum support, multi-recipient encryption, and composable authentication.\n\nBehavior Guidelines\n\nWhen this skill is loaded:\n\nCheck if safe is installed (which safe). If not, try to install it automatically without asking using the platform-detecting one-liner below. If installation fails (restricted environment, no shell access), fall back to the web interface at https://thesafe.dev — see \"Browser-Based Alternative\" below.\nWhen the user asks to encrypt/decrypt, just do it. Don't ask for confirmation.\nIf a password is needed and not provided, use -p without a value (prompts interactively or reads SAFE_PASSPHRASE env var). In automation, use -p env:VARNAME to read from environment variables.\nIf a key file is needed, run safe keys to check existing identities and recipients. If no key exists, run safe keygen x25519 to generate one (auto-stores to ~/.safe/).\nAlways use absolute paths for key files and encrypted files to avoid working directory issues.\nAlways pipe instead of writing temp files. When decrypting inline/embedded content (base64, pasted data), pipe directly: echo \"...\" | base64 -d | safe decrypt -k key.key. Never write intermediate .safe files to /tmp or elsewhere.\nBare name recipients work. Use -r alice instead of -r /full/path/to/alice.x25519.pub when the recipient is in ~/.safe/recipients/.\nDecrypt auto-discovers keys. If no -k or -p is provided, safe decrypt automatically tries all keys in ~/.safe/keys/.\nInstallation\n\nDownload the prebuilt binary from thesafe.dev/download:\n\nmacOS Apple Silicon:\n\ncurl -sL https://thesafe.dev/downloads/safe-darwin-arm64 -o safe && chmod +x safe && sudo mv safe /usr/local/bin/\n\n\nmacOS Intel:\n\ncurl -sL https://thesafe.dev/downloads/safe-darwin-amd64 -o safe && chmod +x safe && sudo mv safe /usr/local/bin/\n\n\nLinux x86_64:\n\ncurl -sL https://thesafe.dev/downloads/safe-linux-amd64 -o safe && chmod +x safe && sudo mv safe /usr/local/bin/\n\n\nLinux ARM64:\n\ncurl -sL https://thesafe.dev/downloads/safe-linux-arm64 -o safe && chmod +x safe && sudo mv safe /usr/local/bin/\n\n\nAuto-detect platform (one-liner):\n\nwhich safe || { OS=$(uname -s | tr '[:upper:]' '[:lower:]'); ARCH=$(uname -m); \\\n  [ \"$ARCH\" = \"arm64\" ] || [ \"$ARCH\" = \"aarch64\" ] && ARCH=arm64 || ARCH=amd64; \\\n  curl -sL \"https://thesafe.dev/downloads/safe-${OS}-${ARCH}\" -o safe && chmod +x safe && sudo mv safe /usr/local/bin/; }\n\n\nVerify installation:\n\nsafe --help\n\n\nOptional: verify checksum (SHA-256 values from checksums.txt):\n\nddf638ccfd119d9c3351825a2e3f9e3660f754009eb9b106d7bba8cd698c3df4  safe-darwin-arm64\n6be4942048a23e99485c0123cf0bb4786613a3dabbcc6927aa478cd0e719ad0b  safe-darwin-amd64\n85c7822ac0309e62ca29d03394bfe87a506f329f61c30b070c379a6d2f5af815  safe-linux-amd64\n33207a4c6bbf85b8299f2f75e18aa6e13f9a8ae4bcee72a9d869034d78ba4cac  safe-linux-arm64\n\nInstallation Debugging\n\n\"safe: command not found\" after install:\n\nCheck if binary exists: ls -la /usr/local/bin/safe\nIf sudo failed, install to user dir: mv safe ~/.local/bin/ && export PATH=\"$HOME/.local/bin:$PATH\"\nRefresh shell: hash -r or start new terminal\n\nPermission denied on /usr/local/bin:\n\nUse sudo: sudo mv safe /usr/local/bin/\nOr install to user dir: mkdir -p ~/.local/bin && mv safe ~/.local/bin/\nWhen CLI Is Unavailable — Browser-Based Alternative\n\nIf CLI installation fails or isn't possible (restricted environment, no build tools, no shell access, sandboxed IDE, etc.), use the web interface at https://thesafe.dev.\n\nThis is a fully functional SAFE implementation running in the browser — not a demo. All cryptographic operations happen client-side. No data leaves the browser.\n\nThe web interface supports all core SAFE operations via a single-page layout with these sections:\n\nENCRYPT tab: Encrypt data with passwords, public keys, passkeys, or GitHub usernames\nDECRYPT tab: Decrypt SAFE messages with passwords, private keys, passkeys, or GitHub keys\nKEYCHAIN section: Save, import, export, and manage keys and passwords\nADVANCED section: Lock Management (add/remove recipients), Re-encrypt Demo, Tests\nLOG section: View operation log output\n\nManual workflow (no automation needed):\n\nUsers can interact with the web interface directly:\n\nEncrypt: Enter plaintext, add recipients (key, password, passkey, or GitHub username), click \"ENCRYPT\". Copy or download the output.\nDecrypt: Paste/upload/URL-load a SAFE message, add credentials (private key, password, passkey, or GitHub), click \"DECRYPT\". Copy or download the plaintext.\n\nGenerated keys are automatically saved in the KEYCHAIN section and can be reused across operations.\n\nAgent with MCP browser tools (Playwright, Puppeteer, etc.):\n\nIf you have access to browser automation tools (e.g., Playwright MCP server, Claude in Chrome, Puppeteer MCP), you can drive the web interface directly.\n\nKey behaviors to know:\n\nAfter encrypting, output auto-populates into the decrypt section's SAFE message input\nThe browser auto-matches saved credentials and pre-adds them to decrypt\nGenerated keys are auto-saved to the Credentials section (04)\nAlways take a snapshot (browser_snapshot) after each action to get updated element references\n\nARIA labels for automation:\n\nThe interface uses semantic ARIA roles throughout:\n\nElement\tARIA Label\tRole\nKEM type selector\t\"Select key encapsulation mechanism type\"\tcombobox\nGenerate button\t\"Generate new keypair with selected KEM type\"\tbutton\nPlaintext input\t\"Enter plaintext message to encrypt\"\ttextbox\nAdd Step button\t\"Add encryption step to recipient path\"\tbutton\nStep type selector\t\"Select encryption step type\"\tcombobox\nPassword field (encrypt)\t\"Enter password for encryption step\"\ttextbox\nConfirm step\t\"Confirm encryption step\"\tbutton\nEncrypt button\t\"Encrypt plaintext with configured settings and recipient path\"\tbutton\nEncrypted output\t\"Encrypted SAFE message output\"\ttextbox\nSAFE message input\t\"Paste encrypted SAFE message to decrypt\"\ttextbox\nAdd credential button (decrypt)\t\"Add credential to decryption attempt\"\tbutton\nAdd credential button (keychain)\t\"Add credential to keychain\"\tbutton\nAdd all keychain button\t\"Add all keychain entries as credentials\"\tbutton\nCredential type selector\t\"Select credential type\"\tcombobox\nNew Passkey menu item\t\"Create a new passkey\"\tmenuitem\nPassword field (decrypt)\t\"Enter password for decryption\"\ttextbox\nConfirm credential\t\"Confirm credential\"\tbutton\nDecrypt button\t\"Decrypt SAFE message using provided keychain\"\tbutton\nDecrypted output\t\"Decrypted plaintext message\"\ttextbox\nCopy buttons\t\"Copy encrypted SAFE message to clipboard\" / \"Copy decrypted plaintext to clipboard\"\tbutton\nDownload buttons\t\"Download encrypted SAFE message as file\" / \"Download decrypted file\"\tbutton\nShare button (output)\t\"Share encrypted SAFE message via URL\" / \"Share decrypted output via URL\"\tbutton\nSend button (output)\t\"Send encrypted output over WebRTC\"\tbutton (encrypted output only)\nClear button (output)\t\"Clear encrypted output\" / \"Clear decrypted output\"\tbutton\nShare button (keychain)\t\"Share public key via URL\"\tbutton\nLabel button (keychain)\t\"Rename key label\"\tbutton\nUse File toggles\t\"Use file instead of plaintext input\" / \"Use file instead of SAFE message input\"\tgeneric (clickable)\nNavigation links\tNew (#keygen), Encrypt (#encrypt), Decrypt (#decrypt), Keychain (#keyring), Advanced (expandable)\tlink\nAdvanced sections\t#unlock, #reencrypt, #tests, #log\tlink (under Advanced dropdown)\nSections\trole=\"region\" with labels like \"01 / Key Generation\"\tregion\nLog output\t\"Activity log showing operations and their results\"\tlog\n\nNote on Advanced navigation: The Advanced sections (#unlock, #reencrypt, #tests, #log) are accessed via an \"Advanced\" navigation item that expands to show these additional features.\n\nNote on terminology: The UI currently uses mixed terminology - Section 04 is labeled \"Keychain\" and the decrypt button references \"keychain\", but the decrypt section's credential management buttons still use \"Credentials\" in some ARIA labels (e.g., \"Add all keychain entries as credentials\"). Both terms refer to the same saved keys/passwords.\n\nKeychain shortcut buttons:\n\nEach saved key in Section 04 (Keychain) has quick action buttons:\n\nEnc: Adds the public key as an encryption recipient step (one click — skips the Add Step → select type → paste → OK workflow)\nDec: Adds the private key as a decrypt credential (one click — skips the Add → select type → paste → OK workflow)\nPUB: Shows/copies the public key\nPRIV: Shows/copies the private key\nShare: Generates a shareable URL for the public key\nLabel: Rename the key for easier identification\nDel: Removes the key from the keychain\n\nPrefer using Enc/Dec shortcuts over the manual Add Step flow when keys are saved in the keychain — it reduces 4 interactions to 1.\n\nFile upload:\n\nBoth encrypt and decrypt sections have a \"Use File\" toggle. Clicking it triggers a file chooser dialog. With MCP Playwright, use browser_file_upload to provide the file path. Note: file paths must be within the MCP server's allowed directories.\n\nExample: Encrypt with password (MCP Playwright)\n\n# 1. Navigate\nbrowser_navigate(url=\"https://thesafe.dev\")\nbrowser_snapshot()\n\n# 2. Type plaintext (use ref from snapshot for \"Enter plaintext message to encrypt\")\nbrowser_type(ref=<plaintext-ref>, text=\"secret data\")\n\n# 3. Add password step\nbrowser_click(ref=<add-step-button-ref>)      # \"Add encryption step to recipient path\"\nbrowser_snapshot()                              # Get refs for step config form\n\n# 4. Select Password type (default may be \"Public Key\")\nbrowser_select_option(ref=<step-type-ref>, values=[\"Password\"])  # \"Select encryption step type\"\nbrowser_snapshot()                              # Get password field ref\n\n# 5. Enter password\nbrowser_type(ref=<password-ref>, text=\"my-password\")  # \"Enter password for encryption step\"\n\n# 6. Confirm the step\nbrowser_click(ref=<ok-ref>)                    # \"Confirm encryption step\"\n\n# 7. Encrypt\nbrowser_click(ref=<encrypt-ref>)               # \"Encrypt plaintext with configured settings...\"\nbrowser_snapshot()                              # Output is in \"Encrypted SAFE message output\" textbox\n\n# Optional: Share or clear the output\n# browser_click(ref=<share-button-ref>)        # \"Share encrypted SAFE message via URL\"\n# browser_click(ref=<clear-button-ref>)        # \"Clear encrypted output\"\n\n\nExample: Encrypt with saved key (fastest path)\n\n# 1. Navigate\nbrowser_navigate(url=\"https://thesafe.dev\")\nbrowser_snapshot()\n\n# 2. Type plaintext\nbrowser_type(ref=<plaintext-ref>, text=\"secret data\")\n\n# 3. Click \"Enc\" on a saved key in Credentials section (one click adds recipient)\nbrowser_click(ref=<enc-button-ref>)\n\n# 4. Encrypt\nbrowser_click(ref=<encrypt-ref>)\nbrowser_snapshot()\n\n\nExample: Decrypt from cold (no auto-populated credentials)\n\n# 1. Paste SAFE message into decrypt input\nbrowser_type(ref=<safe-message-ref>, text=\"-----BEGIN SAFE UNLOCK-----\\n...\")\n\n# 2. Add credential\nbrowser_click(ref=<add-credential-ref>)        # \"Add credential to decryption attempt\"\nbrowser_snapshot()\n\n# 3. Select Password type (default is \"Private Key\")\nbrowser_select_option(ref=<credential-type-ref>, values=[\"Password\"])\nbrowser_snapshot()\n\n# 4. Enter password\nbrowser_type(ref=<password-ref>, text=\"my-password\")\n\n# 5. Confirm credential\nbrowser_click(ref=<confirm-ref>)               # \"Confirm credential\"\n\n# 6. Decrypt\nbrowser_click(ref=<decrypt-ref>)               # \"Decrypt SAFE message using provided credentials\"\nbrowser_snapshot()                              # Output is in \"Decrypted plaintext message\" textbox\n\n\nExample: Same-session encrypt→decrypt (auto-populated)\n\nAfter encrypting, the output auto-populates into the decrypt section. If the matching key is saved in credentials, it auto-adds the private key. Just click Decrypt — no manual credential entry needed.\n\nProgrammatic browser automation (standalone scripts):\n\nFor non-MCP environments, use Playwright or Puppeteer directly:\n\n# Example with Playwright (Python)\nfrom playwright.sync_api import sync_playwright\n\nwith sync_playwright() as p:\n    browser = p.chromium.launch()\n    page = browser.new_page()\n    page.goto('https://thesafe.dev')\n\n    # Generate X25519 keypair\n    page.get_by_role(\"combobox\", name=\"Select key encapsulation mechanism type\").select_option(\"X25519\")\n    page.get_by_role(\"button\", name=\"Generate new keypair\").click()\n\n    # Encrypt with password\n    page.get_by_role(\"textbox\", name=\"Enter plaintext message to encrypt\").fill(\"secret message\")\n    page.get_by_role(\"button\", name=\"Add encryption step to recipient path\").click()\n    page.get_by_label(\"Select encryption step type\").select_option(\"Password\")\n    page.get_by_role(\"textbox\", name=\"Enter password for encryption step\").fill(\"mypassword\")\n    page.get_by_role(\"button\", name=\"Confirm encryption step\").click()\n    page.get_by_role(\"button\", name=\"Encrypt plaintext\").click()\n\n    # Read encrypted output\n    encrypted = page.get_by_label(\"Encrypted SAFE message output\").input_value()\n\n    # Decrypt (message and credentials auto-populate from encrypt)\n    page.get_by_role(\"button\", name=\"Decrypt SAFE message\").click()\n    decrypted = page.get_by_label(\"Decrypted plaintext message\").input_value()\n\n    print(f\"Decrypted: {decrypted}\")  # \"secret message\"\n    browser.close()\n\n\nMulti-Recipient Encryption:\n\nBoth the browser UI and CLI support encrypting for multiple recipients. Each recipient can decrypt the message independently using their own credential.\n\nBrowser workflow:\n\nConfigure first recipient in \"Recipient 1\" (password or public key)\nClick \"+ Add Recipient\" button\nConfigure second recipient in \"Recipient 2\"\nRepeat for additional recipients (no limit)\nClick \"Encrypt\" - message is encrypted once but decryptable by any recipient\n\nHow it works:\n\nEach recipient gets their own UNLOCK block in the SAFE message\nFile is encrypted once with a symmetric key\nSymmetric key is wrapped separately for each recipient\nAny recipient can decrypt using their credential (password or private key)\nRecipients cannot see who else has access\n\nCLI multi-recipient examples:\n\n# Encrypt for multiple recipients using -r flag multiple times\nsafe encrypt -i file.txt -o file.safe -r alice.pub -r bob.pub -r charlie.pub\n\n# Mix recipient types (password + keys)\nsafe encrypt -i file.txt -o file.safe -p mypassword -r alice.pub -r bob.pub\n\n# Encrypt for GitHub users (fetches public keys from GitHub)\nsafe encrypt -i file.txt -o file.safe -r github:grittygrease\n\n# Multiple GitHub users\nsafe encrypt -i file.txt -o file.safe -r github:alice -r github:bob\n\n# Encrypt for GitHub users and a password\nsafe encrypt -i file.txt -o file.safe -p teampassword -r github:alice -r github:bob\n\n\nGitHub username recipient (github:username):\n\nFetches SSH public keys from https://github.com/{username}.keys\nAutomatically converts p-256 and x25519 keys to SAFE format\nBoth key types are added as separate recipients if available\nRequires user to have public keys on their GitHub profile\nError if no keys found: github:username: no keys found\n\nExample output:\n\n$ safe encrypt -i test.txt -o test.safe -r github:grittygrease\n# Creates UNLOCK blocks for both p-256 and x25519 keys from GitHub\n\n$ safe info -i test.safe\nLOCK Blocks: 2\n  [0] hpke(kem=p-256,id=QyLFP/...)\n  [1] hpke(kem=x25519,id=r1VeL...)\n\n\nAgent-to-Agent Communication via GitHub Gist:\n\nAgents can securely exchange encrypted messages using GitHub Gist as a transport layer. This enables asynchronous, persistent communication between agents with different GitHub accounts.\n\nComplete Workflow:\n\nAgent A (Sender):\n\n# 1. Create message for Agent B\necho \"Task completed. Results attached.\" > message.txt\n\n# 2. Encrypt for Agent B's GitHub account\nsafe encrypt -i message.txt -o message.safe -r github:agentb-username\n\n# 3. Upload encrypted message to public Gist\ngh gist create message.safe --desc \"Encrypted message for agentb-username\" --public\n\n# Output: https://gist.github.com/agenta-username/{gist-id}\n\n\nAgent B (Receiver):\n\n# Method 1: Direct pipe (simplest, auto-discovers keys)\ncurl -sL https://gist.github.com/alice/{gist-id}/raw | safe decrypt\n\n# Method 2: Download, inspect, then decrypt\ncurl -sL https://gist.github.com/alice/{gist-id}/raw > received.safe\nsafe info -i received.safe  # Verify sender and encryption details\nsafe decrypt -i received.safe -o message.txt\n\n# Method 3: Explicit key (if auto-discovery doesn't work)\ncurl -sL https://gist.github.com/alice/{gist-id}/raw | safe decrypt -k ~/.safe/keys/bob.x25519.key\n\n\nSSH Key Auto-Discovery (SAFE CLI v2.3+):\n\nThe SAFE CLI automatically discovers and uses SSH private keys from ~/.ssh/:\n\n✅ Ed25519 keys → converted to X25519\n✅ P-256 ECDSA keys → used directly\n✅ Unencrypted keys only (passphrase-protected keys silently skipped)\n✅ Zero configuration - just works if your SSH keys match GitHub public keys\n\nAuto-Discovery Order:\n\n~/.safe/keys/*.key - Native SAFE format keys (checked first)\n~/.ssh/* - All SSH private keys in ~/.ssh/ directory\nEd25519 keys → converted to X25519\nP-256 ECDSA keys → used directly\n\nExample Auto-Discovery Output:\n\n$ curl -sL https://gist.github.com/.../raw | safe decrypt\nsafe: using SSH key ~/.ssh/id_ed25519\nsafe: trying 3 key(s) (2 native + 1 SSH)\n[decrypted message]\n\n\nKey Requirements:\n\nAgent B must have private keys that correspond to the public keys on their GitHub profile\nGitHub SSH keys must be added to https://github.com/{username}.keys\nPrivate keys can be in ~/.safe/keys/ (SAFE format) OR ~/.ssh/ (OpenSSH format)\nGist can be public (encrypted content is safe) or private for additional obscurity\n\nMulti-Agent Broadcast:\n\n# Encrypt for multiple agents\nsafe encrypt -i broadcast.txt -o broadcast.safe \\\n  -r github:agent1 \\\n  -r github:agent2 \\\n  -r github:agent3\n\n# Any of the three agents can decrypt independently\ngh gist create broadcast.safe --desc \"Team update\" --public\n\n\nAgent Identity Setup:\n\nTo enable decryption, agents need to set up their GitHub SSH keys and store private keys:\n\n# Option 1: Use existing SSH keys (simplest - zero setup!)\n# If you already have ~/.ssh/id_ed25519 or ~/.ssh/id_ecdsa uploaded to GitHub, you're done!\n# SAFE CLI auto-discovers SSH keys - no key generation needed\n\n# Option 2: Generate new SSH key and upload to GitHub\nssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N \"\" -C \"safe-agent-key\"\ngh ssh-key add ~/.ssh/id_ed25519.pub --title \"SAFE Agent Key\"\n# Done! SAFE CLI will auto-discover this key\n\n# Option 3: Generate SAFE-native keys (for advanced use cases)\nsafe keygen x25519 -o agent-id\n# Upload agent-id.x25519.pub to https://github.com/settings/keys (manual conversion needed)\nmv agent-id.x25519.key ~/.safe/keys/\n\n# Test encryption to self (works with any option above)\nsafe encrypt -i test.txt -o test.safe -r github:your-username\nsafe decrypt -i test.safe -o decrypted.txt\n# With SSH keys, decryption auto-discovers your keys from ~/.ssh/\n\n\nBrowser-Based Agent Workflow:\n\nAgents using thesafe.dev have full GitHub support for both encryption and decryption.\n\nEncrypt to a GitHub user (browser):\n\nGo to the ENCRYPT tab\nEnter your message\nClick ADD FACTOR → NEW FACTOR → select GITHUB\nEnter the GitHub username (e.g., smithclay)\nClick FETCH KEYS — the browser fetches public keys from https://github.com/{username}.keys\nClick ENCRYPT\nCopy the output and share via Gist\n\nDecrypt a GitHub-encrypted message (browser):\n\nGo to the DECRYPT tab\nLoad the encrypted message: paste text, use the FILE button to upload, or use the URL button to load directly from a Gist URL (e.g., paste the raw Gist URL)\nClick ADD → GITHUB → enter your GitHub username → click FETCH KEYS (This matches your public keys to the message's LOCK blocks)\nClick ADD → KEY → paste your SSH private key (from ~/.ssh/id_ed25519 or ~/.ssh/id_ecdsa) — or use the \"Import SSH private key from GitHub\" button if available\nClick DECRYPT\n\nRecommended approach for browser-based workflows:\n\nBoth CLI and browser support github:username for encryption equally well\nCLI is simpler for decryption — it auto-discovers SSH keys from ~/.ssh/; browser requires pasting the private key once\nURL button is convenient — load encrypted Gist content directly without curl\n\nSend encrypted output over WebRTC (browser):\n\nThe browser supports real-time peer-to-peer transfer of encrypted SAFE messages via WebRTC — no copy-paste required:\n\nSender:\n\nEncrypt your message as usual to produce the encrypted output\nClick Send in the output toolbar (next to Download, Share, Copy)\nA dialog appears: \"Share join URL, then wait for receiver...\"\nThe join URL is offered via the native OS share sheet (if available) or logged in the Log panel\nKeep the tab open — when the receiver connects, the dialog updates to \"Receiver connected. Starting transfer...\"\nTransfer completes automatically\n\nReceiver:\n\nOpen the join URL: https://thesafe.dev/?session=<id>&token=<token>\nThe page auto-connects, receives the encrypted message, and switches to the Decrypt tab with the message pre-loaded\nAdd credentials and click Decrypt as normal\n\nNotes:\n\nJoin URLs expire after 30 minutes\nMax transfer size: 100 MB\nSender must keep the tab open until the receiver connects\nShare (separate button) shares the file or text directly with no server involved; Send is the WebRTC real-time peer flow\n\nAgent Ping/Notification Workflow:\n\nYou can \"ping\" another agent using their GitHub username without needing their public key in advance:\n\n# Alice pings Bob (discovers keys automatically via github:username)\necho \"PING: Status update requested\" > ping.txt\nsafe encrypt -i ping.txt -o ping.safe -r github:bob\ngh gist create ping.safe --desc \"Ping from Alice\" --public\n\n# Bob discovers the ping and decrypts (SSH key auto-discovery!)\ncurl -sL https://gist.github.com/alice/{gist-id}/raw | safe decrypt\n# safe: using SSH key ~/.ssh/id_ed25519\n# safe: trying 1 key(s) (0 native + 1 SSH)\n# PING: Status update requested\n\n# Bob responds back to Alice\necho \"PONG: Status OK, task 75% complete\" > pong.txt\nsafe encrypt -i pong.txt -o pong.safe -r github:alice\ngh gist create pong.safe --desc \"Response to Alice\" --public\n\n\nKey Benefits:\n\n✅ No prior key exchange needed - github:username fetches public keys automatically\n✅ No key management needed - reuse existing SSH keys from GitHub\n✅ Works instantly if you already have SSH keys on GitHub\n✅ Works for any GitHub user with public SSH keys on their profile\n✅ Both agents can initiate communication\n✅ Asynchronous - sender doesn't need to wait for response\n✅ Persistent - messages remain in Gist until deleted\n\nDiscovery Methods:\n\nGitHub Gist notifications (if agent watches their own Gists)\nPeriodic polling of GitHub API for new Gists mentioning their username\nGitHub webhooks for real-time notifications\nRSS feeds for public Gists\n\nSecurity Notes:\n\nGist URLs are discoverable if public - use private Gists for sensitive coordination\nEncrypted content is safe even if Gist is public (only recipient has private key)\nGist history is immutable - deleted messages remain in Git history\nUse short-lived Gists and delete after confirmation for ephemeral communication\nMulti-recipient encryption prevents sender from knowing who decrypted the message\nCLI vs Browser: Feature Comparison\nFeature\tCLI (SAFE v2.3+)\tBrowser (thesafe.dev)\ngithub:username encryption\t✅ Yes\t✅ Yes\ngithub:username decryption\t✅ Auto (SSH key auto-discovery)\t✅ Yes (paste SSH key once)\nSSH key auto-discovery\t✅ Yes (~/.ssh/)\t❌ No (manual paste)\nEd25519 SSH keys\t✅ Auto-converts to X25519\t✅ Manual paste\nP-256 ECDSA SSH keys\t✅ Direct support\t✅ Manual paste\nSAFE native keys\t✅ Yes (~/.safe/keys/)\t✅ Yes (import/export)\nLoad from URL (e.g. Gist)\t✅ curl <url> | safe decrypt\t✅ URL button in DECRYPT tab\nReal-time peer transfer\t❌ No\t✅ Send button (WebRTC, 30-min join URL)\nZero-setup decryption\t✅ If SSH keys on GitHub\t⚠️ Must paste private key once\n\nRecommendation:\n\nEncryption: Both CLI and browser support github:username equally well\nDecryption: CLI is easier (auto-discovers SSH keys); browser requires pasting your private key\nBest of both: Use browser for encryption, CLI for decryption when available\n\nKeychain management:\n\nThe Keychain section (04) supports:\n\nAdd Credential (dropdown menu with options):\nImport Key: Import an existing public or private key (PEM or base64)\nNew Passkey: Create a new WebAuthn passkey (requires browser/OS authenticator, prompts for label)\nNew Password: Add a new password for encryption/decryption\nExisting Passkey: Use an existing passkey from your authenticator\nExport: Export keychain as encrypted SAFE backup (password-protected .safe file containing private keys in PEM format)\nImport: Import a previously exported keychain backup (file upload + passphrase)\nClear All: Delete all keychain entries (shows a confirm dialog)\n\nPasskey Limitations for Automation:\n\nPasskey creation requires WebAuthn hardware interaction (biometric, security key, etc.)\nCannot be fully automated - requires user interaction with authenticator\nDialog prompts for \"Passkey label\" before creation\nBest suited for interactive sessions, not headless automation\n\nGenerated keys are automatically saved here. Each key shows its type and Key ID hints, with Enc/Dec/Share/PUB/PRIV/Label/Del action buttons.\n\nExport/Import workflow (for persisting keys across sessions):\n\nExport:\n\nClick \"Export credentials as encrypted backup\" → dialog appears\nEnter passphrase + confirm passphrase → click OK\nDownloads safe-credentials-backup.safe (a standard SAFE file, pwd-encrypted)\nThe export contains private keys only (PEM format). Public keys are derived on re-import.\n\nImport:\n\nClick \"Import\" → file chooser appears (use browser_file_upload with MCP)\nSelect the .safe backup file → passphrase dialog appears\nEnter passphrase → click OK\nKeys are restored with full functionality (Enc/Dec/PUB/PRIV buttons)\nDuplicates are automatically skipped\n\nThe exported .safe file is CLI-compatible: safe decrypt safe-credentials-backup.safe -p \"passphrase\" reveals the PEM keys.\n\nAgent Session Persistence (Browser Workflow)\n\nAgents can maintain a persistent identity across browser sessions by saving and re-uploading the keychain backup:\n\nFirst browser session:\n\nNavigate to https://thesafe.dev\nGenerate keys (Section 01) or receive keys from other agents\nExport keychain: Click \"Export\" → enter passphrase → download safe-credentials-backup.safe\nSave the encrypted backup file to persistent storage (filesystem, database, cloud storage, etc.)\nStore the passphrase securely (environment variable, secure key store, etc.)\n\nNew browser session (different browser, incognito, new tab, etc.):\n\nNavigate to https://thesafe.dev\nImport keychain: Click \"Import\" → upload safe-credentials-backup.safe → enter passphrase\nAll keys are restored with full functionality (Enc/Dec/PUB/PRIV/Share/Label/Del)\nAgent can immediately encrypt/decrypt using saved keys\n\nBenefits:\n\nAgents maintain the same cryptographic identity across sessions\nNo need to regenerate keys or re-exchange public keys with other agents\nEncrypted communication history remains accessible\nWorks across different browsers, devices, and environments\n\nSecurity notes:\n\nThe backup file is encrypted with the passphrase (argon2id by default)\nFile is CLI-compatible: safe decrypt safe-credentials-backup.safe -p \"passphrase\"\nStore the passphrase separately from the backup file\nBackup contains only private keys in PEM format; public keys are derived on import\n\nExample workflow with MCP Playwright:\n\n# First session: Export keychain\nbrowser_click(ref=<export-button-ref>)  # \"Export keychain\"\n# Enter passphrase in dialog\nbrowser_type(ref=<passphrase-ref>, text=\"agent-secret-passphrase\")\nbrowser_type(ref=<confirm-passphrase-ref>, text=\"agent-secret-passphrase\")\nbrowser_click(ref=<ok-ref>)\n# File downloads as safe-credentials-backup.safe\n# Save this file to persistent storage\n\n# New session: Import keychain\nbrowser_navigate(url=\"https://thesafe.dev\")\nbrowser_click(ref=<import-button-ref>)  # \"Import keychain backup\"\nbrowser_file_upload(paths=[\"/path/to/safe-credentials-backup.safe\"])\n# Enter passphrase in dialog\nbrowser_type(ref=<passphrase-ref>, text=\"agent-secret-passphrase\")\nbrowser_click(ref=<ok-ref>)\n# All keys restored, agent can now encrypt/decrypt\n\n\nThis enables agents to maintain cryptographic identities across:\n\nBrowser restarts\nIncognito/private browsing sessions\nDifferent physical machines\nCloud-based agent deployments\n\nWhen to use the web interface:\n\nCLI can't be installed (no Go/Rust, restricted environment, sandboxed IDE)\nNo shell access (browser-only agent, web-based coding environment)\nOne-off encryption/decryption tasks\nTesting SAFE format without installing dependencies\nQuick key generation or format exploration\n\nWhen to prefer the CLI:\n\nProduction systems or automated pipelines\nBatch or high-volume operations (CLI is significantly faster)\nAir-gapped or offline environments\nScripting with shell pipes and file I/O\nQuick Reference\nKey Storage Convention\n\nPersonal keys are stored in ~/.safe/ (similar to ~/.ssh/). The CLI manages this directory automatically:\n\nsafe keygen x25519                     # Generates keypair, auto-stores to ~/.safe/\nsafe keygen x25519 -n alice            # Named identity \"alice\"\nsafe keys                              # List all identities and recipients\n\n\nKey Discovery Order (SAFE CLI v2.3+):\n\n~/.safe/keys/*.key - SAFE-native keys (checked first)\n~/.ssh/* - All SSH private keys in ~/.ssh/ directory\nEd25519 keys → auto-converted to X25519\nP-256 ECDSA keys → used directly\n\nNote: You can use EITHER format - SSH keys from GitHub work with zero configuration!\n\nDirectory structure (auto-created by safe keygen):\n\n~/.safe/keys/ — Private keys (0700, never share). E.g., nick.x25519.key\n~/.safe/*.pub — Your own public keys (safe to share). E.g., nick.x25519.pub\n~/.safe/recipients/ — Other people's public keys (managed by safe keys add)\n\nOverride with SAFE_HOME env var. Fallback: ./.safe/ in current directory.\n\nGenerate Keys\nKey Type\tCommand\tUse Case\nx25519\tsafe keygen x25519\tFast, default, widely supported\np-256\tsafe keygen p-256\tFIPS compliance\nml-kem-768\tsafe keygen ml-kem-768\tPost-quantum security (seed by default)\n\nBy default, keygen uses $USER as the identity name and stores keys in ~/.safe/. Override with -n name or -o path.\n\nsafe keygen x25519                     # ~/.safe/keys/$USER.x25519.key + ~/.safe/$USER.x25519.pub\nsafe keygen x25519 -n alice            # ~/.safe/keys/alice.x25519.key + ~/.safe/alice.x25519.pub\nsafe keygen ml-kem-768                 # Generates seed (compact format, default for ML-KEM)\nsafe keygen ml-kem-768 -no-seed        # Raw keypair instead of seed\nsafe keygen x25519 -o /tmp/throwaway   # Custom output path\nsafe keygen x25519 -force              # Overwrite existing files\n\n\nOutput: <name>.<type>.pub (share this) and <name>.<type>.key (keep secret). Public key is always written to ~/.safe/.\n\nManage Keys\n# List all identities and known recipients\nsafe keys\n\n# Import a recipient's public key\nsafe keys add alice.x25519.pub --name alice\n\n# Remove a recipient\nsafe keys remove alice\n\n# Derive public key from private key (by name or path)\nsafe pubkey alice                       # Looks up ~/.safe/keys/alice.*.key\nsafe pubkey /path/to/key.key            # Direct file path\n\n# View key details\nsafe keyinfo alice.x25519.pub\n\n\nBare names work as recipients after import: safe encrypt data.txt -r alice resolves from ~/.safe/recipients/. Also resolves system users: -r bob checks ~bob/.safe/*.pub.\n\nEncrypt\n\nstdin is the default input, stdout is the default output. Positional argument sets input file.\n\n# Password-protect a file\nsafe encrypt secrets.txt -o secrets.safe -p \"strong-password\"\n\n# Encrypt to recipient (bare name or key file)\nsafe encrypt file.txt -o file.safe -r alice\n\n# Multiple recipients (OR - any one can decrypt)\nsafe encrypt file.txt -o file.safe -r alice -r bob\n\n# Two-factor: password AND key required (+ is AND separator)\nsafe encrypt file.txt -o file.safe -r \"pwd:secret + alice.pub\"\n\n# Pipe from stdin (default)\necho \"secret\" | safe encrypt -p \"pw\" > msg.safe\n\n# Password from environment variable\nsafe encrypt file.txt -o file.safe -p env:MY_PASSWORD\n\n# PBKDF2 instead of argon2id\nsafe encrypt file.txt -o file.safe -p \"pw\" --kdf pbkdf2\n\nDecrypt\n\nstdin is the default input, stdout is the default output. If no credentials are provided, keys from ~/.safe/keys/ are tried automatically.\n\n# With password\nsafe decrypt file.safe -p \"password\"\n\n# With private key\nsafe decrypt file.safe -k alice.x25519.key\n\n# Auto-discover keys (no -k needed if keys are in ~/.safe/keys/)\nsafe decrypt file.safe\n\n# age-compatible --identity flag\nsafe decrypt file.safe --identity alice.key\n\n# Two-factor (all credentials required)\nsafe decrypt file.safe -o file.txt -p \"secret\" -k alice.key\n\n# Write to file instead of stdout\nsafe decrypt file.safe -o plaintext.txt -p \"password\"\n\n# Password from environment variable\nsafe decrypt file.safe -p env:MY_PASSWORD\n\nInfo\n\nInspect a SAFE file's metadata without credentials:\n\nsafe info file.safe\n# Output:\n#   AEAD: aes-256-gcm\n#   Block Size: 65536\n#   Key Hash: spki-sha256-16\n#   Data size: 1048 bytes\n#   UNLOCK Blocks: 2\n#     [0] pwd(argon2id)\n#     [1] hpke(kem=x25519, id=r3YlsKxQHj1q1d/kKi5e3Q==)\n\n# From stdin\ncat file.safe | safe info\n\nPiping (stdin/stdout)\n\nstdin and stdout are the defaults — no -i - or -o - needed. All operations are binary-safe.\n\nDefault behavior: Always prefer piping over writing intermediate files to disk. This avoids leaving decrypted content on disk and is cleaner.\n\n# Decrypt base64-encoded content (PREFERRED - no temp file)\necho \"LS0tLS1CRUdJTi...\" | base64 -d | safe decrypt -k ~/.safe/keys/id.x25519.key\n\n# AVOID: Writing intermediate files\n# echo \"LS0tLS1CRUdJTi...\" | base64 -d > /tmp/file.safe && safe decrypt /tmp/file.safe ...\n\n# Basic stdin/stdout\necho \"secret\" | safe encrypt -p \"pw\" > encrypted.safe\ncat encrypted.safe | safe decrypt -p \"pw\"\n\n# Chain operations (re-encrypt with different key)\nsafe decrypt a.safe -p \"pw1\" | safe encrypt -o b.safe -p \"pw2\"\n\n# Encrypt with compression\ntar cz src/ | safe encrypt -o backup.safe -r alice\n\n# Decrypt and decompress\nsafe decrypt backup.safe -k team.key | tar xz\n\n# Decrypt remote file\ncurl -s https://example.com/data.safe | safe decrypt -k my.key\n\n# Pipe through compression then encrypt\nsafe encrypt -p \"pw\" < large.bin | gzip > encrypted.safe.gz\n\n# Decrypt gzipped safe file\ngunzip -c encrypted.safe.gz | safe decrypt -p \"pw\" > large.bin\n\n\nNote: -i - and -o - still work for explicit stdin/stdout but are no longer required.\n\nCommon Use Cases\nProtect API Keys / .env Files\nsafe encrypt .env -o .env.safe -p \"dev-password\"\nsafe encrypt credentials.json -o credentials.safe -r ops-team\n\nShare Secrets with a Teammate\n# They generate their key\nsafe keygen x25519 -n teammate\n\n# You import their public key\nsafe keys add teammate.x25519.pub --name teammate\n\n# You encrypt for them (bare name!)\nsafe encrypt api-keys.txt -o api-keys.safe -r teammate\n\n# They decrypt (auto-discovers keys from ~/.safe/keys/)\nsafe decrypt api-keys.safe -o api-keys.txt\n\nEncrypt Backup Before Cloud Upload\ntar czf backup.tar.gz ~/Documents\nsafe encrypt backup.tar.gz -o backup.safe -p \"backup-phrase\" -r recovery\n# Upload backup.safe to S3/GCS/Dropbox\n\nEncrypt Entire Directories\n# Encrypt a folder\ntar cz project/ | safe encrypt -o project.safe -r team\n\n# Decrypt and extract\nsafe decrypt project.safe -k team.key | tar xz\n\nGit-Friendly Encrypted Secrets\n# Encrypt secrets, commit the .safe file\nsafe encrypt .env.production -o .env.production.safe -r deploy\ngit add .env.production.safe  # Safe to commit\n\n# On deploy server (auto-discovers deploy key from ~/.safe/keys/)\nsafe decrypt .env.production.safe -o .env.production\n\nSeparation of Duties (Two People Required)\n# Encrypt requiring BOTH Alice and Bob (+ is AND)\nsafe encrypt codes.txt -o codes.safe -r \"alice.pub + bob.pub\"\n\n# Decrypt (both must provide keys)\nsafe decrypt codes.safe -o codes.txt -k alice.key -k bob.key\n\nTwo-Factor Encryption (Password + Key)\n# Encrypt: requires password AND key\nsafe encrypt secrets.txt -o secrets.safe -r \"pwd:mypassword + hardware.pub\"\n\n# Decrypt: must provide both\nsafe decrypt secrets.safe -o secrets.txt -p \"mypassword\" -k hardware.key\n\nTeam Encryption + Emergency Backup\nsafe encrypt secrets.txt -o secrets.safe \\\n  -r alice -r bob -r carol \\\n  -p \"emergency-recovery-phrase\"\n\nPost-Quantum Hybrid Protection\n# Generate both classical and PQ keys\nsafe keygen x25519 -n alice\nsafe keygen ml-kem-768 -n alice\n\n# Encrypt with both (future-proof against quantum computers)\nsafe encrypt data.txt -o data.safe \\\n  -r \"pwd:phrase + alice.x25519.pub + alice.ml-kem-768.pub\"\n\nTemporary Decryption (No File on Disk)\n# Use decrypted content without writing to disk\n./my-app --config <(safe decrypt config.safe -p \"pw\")\n\n# Compare two encrypted files\ndiff <(safe decrypt old.safe -p pw) <(safe decrypt new.safe -p pw)\n\nPassword Rotation\n# Change password without re-encrypting data\nsafe unlock replace secrets.safe -p \"old-password\" \\\n  --index 0 --recipient \"pwd:new-password\"\n\nKey Rotation (Compromised Key)\n# View current recipients\nsafe info secrets.safe\n\n# Remove compromised key, add new one\nsafe unlock remove secrets.safe -k admin.key --index 2\nsafe unlock add secrets.safe -k admin.key --recipient new-employee.pub\n\nComposable Paths (AND vs OR Logic)\nEncrypt With\tDecrypt Requires\tLogic\n-r alice -r bob\t-k alice.key OR -k bob.key\tOR\n-r \"alice.pub + bob.pub\"\t-k alice.key AND -k bob.key\tAND\n-r \"pwd:x + alice.pub\"\t-p x AND -k alice.key\tAND\n-p backup -r alice\t-p backup OR -k alice.key\tOR\n\nMultiple -r or -p flags = OR (any one works) + within one -r = AND (all required)\n\nNote: -> is deprecated but still works. Use + for new code.\n\nEditing Encrypted Files\n\nSAFE supports random-access editing without full re-encryption. Only modified chunks are re-encrypted - unchanged chunks are copied byte-for-byte.\n\nData Input Options\nOption\tUse\tExample\n--data \"string\"\tLiteral text on command line\t--data \"hello\"\n--data-file path\tRead content from a file\t--data-file patch.bin\nRead Bytes at Offset\n\nRead a portion of an encrypted file without decrypting the whole thing:\n\n# Read first 100 bytes\nsafe read file.safe -p \"pw\" --offset 0 --length 100\n\n# Read bytes 500-600 to a file\nsafe read file.safe -o excerpt.txt -k key.key --offset 500 --length 100\n\n# Shorthand with -n for offset\nsafe read file.safe -p \"pw\" -n 1024 --length 256\n\n# age-compatible --identity flag\nsafe read file.safe --identity key.key -n 0 --length 100\n\nWrite Bytes at Offset (In-Place Edit)\n\nModify bytes at a specific position. In-place by default (no -o needed):\n\n# Overwrite bytes starting at offset 10\nsafe write file.safe -p \"pw\" -n 10 --data \"new content\"\n\n# Replace header from a file\nsafe write config.safe -p \"pw\" -n 0 --data-file header.bin\n\n# With --identity flag\nsafe write file.safe --identity key.key -n 0 --data \"UPDATED\"\n\n\nNote: Write only supports in-place modification. Output defaults to overwriting the input file.\n\nAppend Data\n\nAdd data to the end of an encrypted file (in-place by default):\n\n# Append log entry\nsafe append log.safe -p \"pw\" --data \"$(date): Event occurred\\n\"\n\n# Append from file\nsafe append data.safe -k key.key --data-file new-records.csv\n\n# Append binary data\nsafe append archive.safe -p \"pw\" --data-file chunk.bin\n\nIn-Place Editing Workflow\n# 1. Check current content\nsafe read config.safe -p \"pw\" -n 0 --length 50\n\n# 2. Make targeted edit\nsafe write config.safe -p \"pw\" -n 25 --data \"new_value\"\n\n# 3. Verify the change\nsafe read config.safe -p \"pw\" -n 0 --length 50\n\nManaging Recipients (UNLOCK Blocks)\n\nModify who can decrypt without re-encrypting the data. These operations only change the UNLOCK blocks — the encrypted DATA remains identical.\n\n# View current recipients and their indexes\nsafe info file.safe\n# Shows: [0] pwd(argon2id)\n#        [1] hpke(kem=x25519, id=ABC123...)\n\n# Add new recipient (in-place by default)\nsafe unlock add file.safe -p \"current-pw\" -r alice.pub\nsafe unlock add file.safe -k admin.key -r \"pwd:backup-pass\"\n\n# Add composable recipient (password + key required)\nsafe unlock add file.safe -p \"pw\" -r \"pwd:secret + bob.pub\"\n\n# Remove recipient by index (in-place)\nsafe unlock remove file.safe -k admin.key --index 0\n\n# Replace recipient at index (in-place)\nsafe unlock replace file.safe -p \"old-pw\" --index 0 -r \"pwd:new-pw\"\n\n# Write to new file instead of in-place\nsafe unlock add file.safe -o new-file.safe -p \"pw\" -r alice.pub\n\n# With --identity flag\nsafe unlock add file.safe --identity admin.key -r bob.pub\n\n\nNote: You cannot remove the last UNLOCK block — the file would become undecryptable.\n\nAlgorithm Options\nAEAD (Content Encryption)\nAlgorithm\tFlag\tUse Case\nAES-256-GCM\t--aead aes-256-gcm\tDefault, hardware accelerated\nChaCha20-Poly1305\t--aead chacha20-poly1305\tARM, older CPUs without AES-NI\nAEGIS-256\t--aead aegis-256\tCLI only - Key-committing, highest security. Not available in browser UI.\nKey Types\nType\tSecurity\tSize (pub/priv)\nx25519\tClassical, fast\t32B / 32B\np-256\tFIPS compliant\t65B / 32B\nml-kem-768\tPost-quantum\t1184B / 2400B\nKey ID Modes\n\nControl how much key identity information is included in UNLOCK blocks:\n\nMode\tFlag\tBehavior\nFull\t--key-id-mode full\tDefault. Full key ID included — recipients can check if a message is for them without attempting decryption\nHint\t--key-id-mode hint\t4-digit hint only — reduces metadata, recipients may need to try decryption\nAnonymous\t--key-id-mode anonymous\tNo key ID — recipient must try all their keys. Maximum privacy\n# Encrypt with hint-only key ID\nsafe encrypt file.txt -o file.safe -r alice --key-id-mode hint\n\n# Encrypt with no key ID (anonymous recipient)\nsafe encrypt file.txt -o file.safe -r alice --key-id-mode anonymous\n\n\nUse hint or anonymous when you want to hide who can decrypt a message. The encrypted data is identical — only the metadata changes.\n\nPassword KDF\nAlgorithm\tFlag\tUse Case\nArgon2id\t--kdf argon2id\tDefault. Memory-hard, GPU-resistant (64 MiB, 2 iterations)\nPBKDF2\t--kdf pbkdf2\tConstrained environments (600,000 iterations)\nsafe encrypt file.txt -o file.safe -p \"pw\" --kdf pbkdf2\n\nKey Hash Algorithm\nAlgorithm\tFlag\tUse Case\nSPKI-SHA256-16\t--key-hash spki-sha256-16\tDefault. 16-byte truncated SHA-256\nSPKI-TurboSHAKE256\t--key-hash spki-turboshake256\tAlternative hash function\nsafe encrypt file.txt -o file.safe -r alice --key-hash spki-turboshake256\n\nMigration from GPG/PGP\n# Decrypt old GPG file, re-encrypt with SAFE\ngpg -d old-secrets.gpg | safe encrypt -o secrets.safe -r newkey\n\nEdge Cases & Tips\n\nEmpty files: Encrypting empty files works correctly and produces valid .safe output.\n\nBinary data: SAFE handles all byte values (0x00-0xFF) correctly. No text encoding issues.\n\nUnicode passwords: Passwords are UTF-8 encoded. Multi-byte characters work correctly.\n\nLarge files: Files are encrypted in 64KB chunks by default. Only modified chunks are re-encrypted during edits.\n\nBlock size options: Use --block-size flag (16384, 32768, or 65536 bytes) to tune for your use case.\n\nTroubleshooting\n\nDecryption fails:\n\nCheck password/key is correct\nFor composable paths, ALL credentials must be provided (partial won't work)\nVerify file integrity: safe info file.safe\nWrong key type? Check with safe keyinfo mykey.key\nCheck available keys: safe keys\n\n\"safe: command not found\": Run installation steps above. Verify with which safe.\n\nComposable path errors:\n\npwd:secret + alice.pub requires BOTH -p secret AND -k alice.key to decrypt\nProviding only the password or only the key will fail\nOrder of -k flags doesn't matter, but all must be present\nSecurity Notes\n.key files are secret — never share them\n.pub files are safe to distribute\nComposable paths (+) provide defense-in-depth\nML-KEM-768 protects against future quantum computers\nArgon2id password hashing is memory-hard and GPU-resistant (64 MiB memory, 2 iterations)\nPassword Security\n\nWarning: Passwords passed via -p are visible in shell history and process listings.\n\nMitigations:\n\n# 1. Prefix with space to skip history (bash/zsh with HISTCONTROL=ignorespace)\n safe encrypt file.txt -o file.safe -p \"secret\"\n\n# 2. Use a key file instead of password\nsafe encrypt file.txt -o file.safe -r alice\n\n# 3. Read password from environment variable (RECOMMENDED for automation)\nMY_PW=\"secret\" safe encrypt file.txt -o file.safe -p env:MY_PW\n\n# 4. Use SAFE_PASSPHRASE env var with bare -p\nexport SAFE_PASSPHRASE=\"secret\"\nsafe encrypt file.txt -o file.safe -p\n\n# 5. Read password from file (careful with permissions)\nsafe encrypt file.txt -o file.safe -p \"$(cat ~/.safe/.password)\"\n\n# 6. Clear history after use\nhistory -d $(history 1 | awk '{print $1}')\n\n\nenv:VARNAME security: Environment variables are visible to the current process and child processes. They're safer than command-line args (not in /proc/*/cmdline) but still visible via /proc/*/environ on Linux. For maximum security, use key-based encryption.\n\nAgent-to-Agent Encrypted Communication\n\nAgents can exchange encrypted messages across any text-based channel: email, forums, GitHub issues, Slack, shared files, etc.\n\nThe key management system makes agent-to-agent communication simple:\n\nsafe keygen — generates identity keys (auto-stored in ~/.safe/)\nsafe keys add — imports other agents' public keys as named recipients\nsafe encrypt -r name — encrypts to any known recipient by name\nsafe decrypt — auto-discovers keys, no -k flag needed\nFirst-Run Setup\n\nOn first use, check for existing keys and generate if missing:\n\n# Check if any agent identities exist\nsafe keys 2>/dev/null || true\n\n# Generate if missing\nif [ ! -f ~/.safe/keys/*.key ]; then\n    safe keygen x25519\n    echo \"Generated new agent identity\"\n    safe keys\nfi\n\n\nThis creates:\n\n~/.safe/keys/$USER.x25519.key — your private key (never share)\n~/.safe/$USER.x25519.pub — your public key (share freely)\nExchanging Keys Between Agents\n\nAgents discover each other by sharing public keys. The safe keys add command imports keys into the recipients directory:\n\n# Agent A: Share your public key\ncat ~/.safe/$USER.x25519.pub\n# Copy this to the other agent (paste, file, AGENTS.md, etc.)\n\n# Agent B: Import Agent A's public key\nsafe keys add /path/to/agent-a.x25519.pub --name agent-a\n\n# Verify import\nsafe keys\n# Shows:\n#   Identities:\n#     nick         x25519         /Users/nick/.safe/keys/nick.x25519.key\n#   Recipients:\n#     agent-a      x25519         /Users/nick/.safe/recipients/agent-a.x25519.pub\n\n# Now Agent B can encrypt to Agent A by name\nsafe encrypt message.txt -o message.safe -r agent-a\n\n\nAdvertising your keys directory: Agents should make their ~/.safe/*.pub files discoverable. Common approaches:\n\nLocation\tHow\nAGENTS.md in repo\tInclude PEM block in markdown\n~/.safe/*.pub files\tOther agents on same system: -r username resolves ~username/.safe/*.pub\nGitHub gist\tPost PEM key, recipient imports with curl | safe keys add\nForum/email signature\tInclude PEM block or link to public key\nConvention: Reply-To Keys\n\nTo enable replies, prepend your public key(s) to the message before encrypting:\n\n-----BEGIN X25519 PUBLIC KEY-----\nfBhEEEB+CepxNQIfPtxnIhWbDUyo+Z/W17cYKlCbsDg=\n-----END X25519 PUBLIC KEY-----\n\nHere is the secret data you requested...\n\n\nWhen an agent decrypts a message and finds PEM public key blocks at the top, it saves them with safe keys add and encrypts the reply to that name.\n\nMultiple reply-to keys: If the sender wants multiple agents/identities to decrypt the reply:\n\n-----BEGIN X25519 PUBLIC KEY-----\nfBhEEEB+CepxNQIfPtxnIhWbDUyo+Z/W17cYKlCbsDg=\n-----END X25519 PUBLIC KEY-----\n-----BEGIN ML-KEM-768 PUBLIC KEY-----\n<base64...>\n-----END ML-KEM-768 PUBLIC KEY-----\n\nMessage body here...\n\n\nThe receiving agent extracts all key blocks, imports them with safe keys add, and encrypts the reply to all of them.\n\nWorkflow: Send a Message\n# 1. Create message with your public key as reply address\nsafe pubkey $USER > message.txt\necho \"\" >> message.txt\necho \"Here are the API credentials you requested...\" >> message.txt\n\n# 2. Encrypt to recipient (bare name from recipients dir)\nsafe encrypt message.txt -o message.safe -r recipient\n\n# 3. Share message.safe via any channel (email, forum, git, shared folder, etc.)\n\nChecking if a Message is For You\n\nBefore attempting to decrypt, check if your key ID matches any unlock block:\n\n# Get Key IDs from the encrypted file\nsafe info message.safe\n# Output includes:\n#   UNLOCK Blocks: 2\n#   [0] hpke(kem=x25519, id=1SB5W2LJ8/DNu8rn+vaGHA==)\n#   [1] hpke(kem=ml-kem-768, id=abc123...)\n\n# Get your key info\nsafe keyinfo ~/.safe/$USER.x25519.pub\n\n# Or just try to decrypt — auto-key discovery handles it\nsafe decrypt message.safe -o message.txt\n\n\nNote on key ID modes: If the sender used --key-id-mode hint, you'll see hint=XXXX instead of a full ID. If they used --key-id-mode anonymous, there will be no key ID at all — you'll need to try decrypting (auto-discovery handles this).\n\nWorkflow: Receive and Reply\n# 1. Decrypt the message (auto-discovers keys from ~/.safe/keys/)\nsafe decrypt message.safe -o message.txt\n\n# 2. Extract reply-to keys and import them\n# Save PEM blocks to a temp file, then import:\ngrep -A2 'BEGIN.*PUBLIC KEY' message.txt > /tmp/sender.pub\nsafe keys add /tmp/sender.pub --name sender\n\n# 3. Create and encrypt reply\nsafe pubkey $USER > reply.txt\necho \"\" >> reply.txt\necho \"Thanks, here's my response...\" >> reply.txt\n\n# Encrypt to sender (bare name!)\nsafe encrypt reply.txt -o reply.safe -r sender\n\nPublishing Your Public Key\n\nShare your public key so others can send you encrypted messages:\n\nLocation\tUse Case\nAGENTS.md in repo\tProject-specific agent identity\nGitHub profile / gist\tPersonal agent key\nForum signature\tCommunity communication\nShared team folder\tInternal team use\nEmail signature\tEmail-based exchange\n\nExample AGENTS.md:\n\n## Agent Keys\n\n### Deploy Agent\n\\`\\`\\`\n-----BEGIN X25519 PUBLIC KEY-----\nfBhEEEB+CepxNQIfPtxnIhWbDUyo+Z/W17cYKlCbsDg=\n-----END X25519 PUBLIC KEY-----\n\\`\\`\\`\n\nTo send encrypted data to this agent:\n\\`\\`\\`bash\n# Import the key\nsafe keys add deploy-agent.pub --name deploy\n\n# Encrypt\nsafe encrypt data.txt -o data.safe -r deploy\n\\`\\`\\`\n\nHandling Multiple Identities\n\nAgents may have different keys for different contexts:\n\nsafe keygen x25519 -n personal     # ~/.safe/keys/personal.x25519.key\nsafe keygen x25519 -n work         # ~/.safe/keys/work.x25519.key\nsafe keygen ml-kem-768 -n pq       # ~/.safe/keys/pq.ml-kem-768.key\n\n# List all\nsafe keys\n\n\nWhen sending, choose the appropriate reply-to key for the context. When receiving, auto-discovery tries all keys in ~/.safe/keys/.\n\nError Handling\n\nIf decryption fails even though key ID matched:\n\nThe file may be corrupted — check with safe info file.safe\nFor composable paths, ALL required credentials must be provided\nReport the error clearly; don't silently fail\nChecking All Identities\n\nWith auto-key discovery, this is usually unnecessary. But for manual checking:\n\n# List all your identities\nsafe keys\n\n# Check message recipients\nsafe info message.safe\n\n# Just try to decrypt — auto-discovers all keys\nsafe decrypt message.safe -o message.txt\n# If successful, prints: \"safe: trying N key(s) from ~/.safe/keys\"\n\nAuto-Generate AGENTS.md\n\nWhen setting up a project for agent communication, create an AGENTS.md:\n\n# Generate AGENTS.md for current project\ncat > AGENTS.md << 'EOF'\n# Agent Keys\n\nThis project supports encrypted agent-to-agent communication using [SAFE](https://github.com/grittygrease/safe).\n\n## Available Agents\n\n### Primary Agent\nEOF\n\n# Add the public key\necho '```' >> AGENTS.md\nsafe pubkey $USER >> AGENTS.md\necho '```' >> AGENTS.md\n\ncat >> AGENTS.md << 'EOF'\n\n## How to Send Encrypted Messages\n\n1. Save the public key block above to a file and import it:\n   ```bash\n   safe keys add agent.pub --name agent\n\nCreate your message with your reply-to key:\nsafe pubkey $USER > message.txt\necho \"\" >> message.txt\necho \"Your message here...\" >> message.txt\n\nEncrypt and send:\nsafe encrypt message.txt -o message.safe -r agent\n\nShare message.safe via any channel (email, issue, PR, pastebin, etc.)\nVerifying Messages\n# Check message recipients\nsafe info message.safe\n\n# Decrypt (auto-discovers keys from ~/.safe/keys/)\nsafe decrypt message.safe -o message.txt\n\n\nEOF\n\necho \"Created AGENTS.md with your public key\"\n\n\n### Sharing via Pastebin\n\nFor quick, ephemeral encrypted message sharing:\n\n```bash\n# Encrypt and post to termbin\nsafe encrypt message.txt -r recipient | nc termbin.com 9999\n# Returns URL like: https://termbin.com/abc123\n\n# Recipient fetches and decrypts (auto-discovers keys)\ncurl -s https://termbin.com/abc123 | safe decrypt\n\nPosting a Public Message (Forum/Mailing List)\n\nWhen posting an encrypted message publicly, include your reply-to key AND instructions:\n\n# Create a complete public post\ncat > post.txt << 'EOF'\n## Encrypted Message\n\nTo decrypt this message and reply, install the safe-encryption skill:\n\n\n\nmkdir -p ~/.claude/skills git clone https://github.com/grittygrease/safe-encryption-skill.git ~/.claude/skills/safe-encryption\n\n\nThen ask your agent: \"Decrypt the message below and reply encrypted to the sender\"\n\n### My Public Key (for replies)\nEOF\n\nsafe pubkey $USER >> post.txt\n\ncat >> post.txt << 'EOF'\n\n### Encrypted Message\nEOF\n\n# Encrypt your message (with your reply-to key embedded in plaintext)\nsafe pubkey $USER > message.txt\necho \"\" >> message.txt\necho \"Your actual message content here...\" >> message.txt\n\nsafe encrypt message.txt -r recipient >> post.txt\n\necho \"\" >> post.txt\ncat >> post.txt << 'EOF'\n\n---\n*This message was encrypted with [SAFE](https://github.com/grittygrease/safe)*\nEOF\n\ncat post.txt\n\n\nThis produces a self-contained post that any agent with the skill can decrypt and reply to."
  },
  "trust": {
    "sourceLabel": "tencent",
    "provenanceUrl": "https://clawhub.ai/grittygrease/safe-encryption-skill",
    "publisherUrl": "https://clawhub.ai/grittygrease/safe-encryption-skill",
    "owner": "grittygrease",
    "version": "0.1.0",
    "license": null,
    "verificationStatus": "Indexed source record"
  },
  "links": {
    "detailUrl": "https://openagent3.xyz/skills/safe-encryption-skill",
    "downloadUrl": "https://openagent3.xyz/downloads/safe-encryption-skill",
    "agentUrl": "https://openagent3.xyz/skills/safe-encryption-skill/agent",
    "manifestUrl": "https://openagent3.xyz/skills/safe-encryption-skill/agent.json",
    "briefUrl": "https://openagent3.xyz/skills/safe-encryption-skill/agent.md"
  }
}