Punktero SOPS Secrets
Decrypt, browse and encrypt SOPS-encrypted secrets directly in VS Code — without ever writing plaintext to disk.
Designed for Ansible inventories that use the punktero.sops.secret lookup plugin, but works with any YAML file.
Features
Key management
Manage recipient keys in .sops.yaml and automatically re-encrypt all matching SOPS files when keys change.
Add key — run SOPS: Add key from the command palette, right-click .sops.yaml, or click the $(add) Add key CodeLens inside the file:
- Pick an SSH public key from
~/.ssh/*.pub, or paste an age/SSH key manually
- Multi-select which
creation_rules the key should be added to (all pre-selected)
- Extension writes
.sops.yaml and runs sops updatekeys --yes on all matching SOPS files
Remove key — run SOPS: Remove key or click the $(trash) Remove CodeLens next to any key line:
- Pick the key to remove (or click directly via CodeLens)
- Key is removed from all rules where it appears
- Extension writes
.sops.yaml and re-encrypts matching files
The status bar $(key) SOPS button opens a quick-access menu with Add key / Remove key (or Initialize workspace when unconfigured).
Initialize workspace
Run SOPS: Initialize workspace from the command palette (or click the SOPS: Not configured status bar item) to set up a new SOPS-enabled workspace:
- Scans
~/.ssh/*.pub for existing Ed25519 and RSA keys — pick one, or paste an age public key manually
- Writes
.sops.yaml with a directory-specific creation rule and a default catch-all
- Creates the
.secrets/ folder
- Creates and encrypts an initial SOPS file (default:
.secrets/passwords.sops.yml)
SOPS Secrets panel
Opens automatically in the Explorer sidebar when you switch to an Ansible inventory YAML file.
- Hosts from the inventory are listed as expandable nodes
- Secrets shared via YAML anchors appear under a global node at the top
- Each secret shows the relevant part of its SOPS key path (host prefix is stripped —
some.key instead of inventory.vps-123.some.key)
Inside a *.sops.yml file the panel follows the cursor. Put the cursor on a key and the panel lists the secrets that belong to it:
| Cursor on |
Panel shows |
inventory: (top level) |
secret count only — a top-level key holds far too many secrets to list |
inventory.vps-684: |
all secrets of that host, nested groups expanded |
inventory.vps-684.postgres: |
only the secrets under postgres |
…postgres.password: (leaf) |
that single secret, labelled with its full key path |
Key names are read straight from the encrypted file (SOPS keeps them in plaintext) — nothing is decrypted until you reveal or copy a value.
Reveal & copy secrets
| Action |
Result |
| Double-click a secret |
Toggle show / hide — the decrypted value appears directly below the key in the tree |
| Double-click again |
Hide the value |
| Ctrl+C (panel focused) |
Copy the selected secret or value to clipboard |
| Right-click → Reveal secret |
Show the value below the key |
| Right-click → Hide secret |
Remove the revealed value |
| Right-click → Copy decrypted secret |
Copy to clipboard |
Copied secrets are automatically cleared from the clipboard after 30 seconds. On KDE/Linux the secret is set via x-kde-passwordManagerHint so Klipper never records it.
Sort keys
Large secrets files get hard to search because sops set always appends a new key at the end of its parent. SOPS: Sort keys (command palette, or right-click a *.sops.yml file) puts every mapping level into canonical order:
- natural for numbers —
d7 before d10 before d120, vps-99 before vps-112
- bytewise for everything else —
- before digits before _ before lowercase, uppercase before lowercase
Sorting goes through sops edit, so unchanged values keep their existing ciphertext: only mac and lastmodified change and the git diff shows nothing but moved lines. Verified on an 885-line file with 465 secrets — content identical, MAC valid, 0 values re-encrypted.
With punkteroSops.sortOnWrite (default on) the file is re-sorted after every Encrypt / Generate, so it stays ordered.
The identical order is implemented in the punktero.sops Ansible collection (module_utils/sops_sort.py, used by punktero.sops.set / punktero.sops.save and manage sops-sort) and enforced by audit_sops.py --mode order in pre-commit — extension, Ansible and CLI cannot drift apart.
Multi-file support
The extension searches all SOPS files matching the configured glob pattern (default: .secrets/*.sops.yml).
- If the same key exists in multiple files, all values are shown — one per file
- If a file cannot be decrypted (different age recipient), the entry shows Access denied instead of the value
- Access denied is only shown when the key actually exists in the inaccessible file (the extension reads the encrypted YAML structure without decrypting)
Hover over any lookup('punktero.sops.secret', '...') expression in any file to see the decrypted value in a popup.
The same works inside SOPS files: hover an ENC[...] value in a *.sops.yml file and the decrypted value appears. Only leaf keys show a popup — mapping keys stay quiet. Files you have no key for show Access denied instead of a value.
Missing secrets
Copying an inventory block from another host brings the lookup() expressions along, but not the secrets they point at — the new host ends up with lookups that resolve to nothing. The extension spots those keys and offers to generate them:
| Where |
What you see |
| CodeLens above the line |
🔑 Generate secret instead of 📋 Copy secret |
| Hover over the lookup |
not found in any SOPS file plus Generate secret / Generate to file… links |
| SOPS Secrets panel |
missing keys get a ⚠ icon and a missing label; the host node shows N missing |
Clicking any of them generates a random password (same 32-char generator as Generate secret), encrypts it under the key path the lookup already refers to and copies it to the secure clipboard. The lookup() expression is left untouched — it is already correct, only the secret was missing.
Generate all missing — the inline $(key) button on a host node (or Generate all missing secrets from its context menu) fills every gap of that host in one pass, after confirming the list. Each touched file is sorted once at the end rather than after every key.
Which keys are missing is determined by reading the encrypted files' key structure, which SOPS stores in plaintext — nothing is decrypted, and it works for files you have no key for. A lookup with file='…' is checked against that file alone and its secret is generated there.
Encrypt & generate secrets
Right-click in any editor to access the SOPS context menu:
| Condition |
Top-level shortcut |
SOPS submenu |
| Text selected |
SOPS: Encrypt text → default file |
Encrypt text / Encrypt text to file |
| No selection |
SOPS: Generate secret → default file |
Generate secret / Generate secret to file |
Encrypt text — encrypts the selected text into the SOPS file and replaces the selection with a lookup() expression.
Generate secret — generates a cryptographically random password (32 chars, letters + digits), encrypts it into the SOPS file, inserts a lookup() expression at the cursor position and copies the password to the secure clipboard.
For both actions the extension:
- Pre-fills the key path from the YAML context (e.g.
$.vps.name in an inventory, database.password in a generic config file)
- Optionally warns before overwriting an existing key
- After encryption, the clipboard clears automatically after 30 seconds
Requirements
sops CLI installed and available in PATH
- A
.sops.yaml configuration file in the workspace root (use SOPS: Initialize workspace to create one)
- Age or SSH keys configured for decryption
Configuration
| Setting |
Default |
Description |
punkteroSops.secretsFiles |
.secrets/*.sops.yml |
Glob pattern for SOPS files relative to workspace root |
punkteroSops.defaultSecretsFile |
.secrets/passwords.sops.yml |
Default target file for encrypt/generate commands |
punkteroSops.autoFillKeyPath |
true |
Pre-fill the key path from the YAML context |
punkteroSops.confirmOverwrite |
true |
Warn before overwriting an existing key in a SOPS file |
How it works
The extension calls sops -d <file> for each matching file and parses the decrypted YAML in memory. Nothing is written to disk. The decrypted data is cached for 30 seconds to avoid repeated subprocess calls on every hover or tree refresh.
Encryption uses sops set <file> <json-path> <value> to write individual keys without re-encrypting the whole file. The init wizard uses sops --encrypt --in-place to create the initial encrypted file.