From terraphim-engineering-skills
Secure secret management using 1Password CLI. Detect plaintext secrets in files and codebases, convert environment files to 1Password templates, inject secrets securely using op inject, and audit codebases for security compliance.
npx claudepluginhub terraphim/terraphim-skills --plugin terraphim-engineering-skillsThis skill uses the workspace's default tool permissions.
**Version:** 1.0.0
Guides 1Password CLI (op) integration for secret management in dev workflows with .op.env files, Makefile/Docker Compose/Kamal/CI patterns for infrastructure, deployments, local dev.
Guides secure secrets management using Vault, AWS Secrets Manager, Azure Key Vault, environment variables, rotation, scanning tools, and CI/CD security. For implementing storage, rotation, leak prevention, credentials review.
Manages passwords and API credentials using 1Password CLI: saves to vaults, queries items, reads secrets, injects into env vars for scripts and safe .env files.
Share bugs, ideas, or general feedback.
Version: 1.0.0 Author: Claude Code Purpose: Secure secret management using 1Password CLI
This skill provides comprehensive secret management using 1Password CLI (op). It helps you:
op injectop run --no-maskingCritical Mandate: This skill enforces the principle: never commit plaintext secrets, never overwrite .env files, always use 1Password CLI.
1Password CLI (op) installed and configured
# Check installation
which op && op --version
# Install if needed (macOS)
brew install 1password-cli
# Install if needed (Linux)
curl -sSfO https://downloads.1password.com/linux/tar/stable/x86_64/1password-cli-latest.tar.gz
tar -xf 1password-cli-latest.tar.gz
sudo mv op /usr/local/bin/
1Password Account signed in
# Sign in
op signin
# Verify session
op whoami
1Password Vault for storing secrets
# List vaults
op vault list
# Create vault if needed
op vault create "ProjectSecrets"
jq for JSON parsing (usually available)Purpose: Scan files for plaintext secrets and report findings without displaying values.
op CLI is installedUser: "Scan /path/to/.env for secrets"
Assistant Process:
1. Check op CLI: `which op`
2. Read pattern database: ~/.docs/1password-skill/secret-patterns.json
3. Read target file line by line
4. Apply regex patterns to each line
5. For each match:
- Record file:line:type:confidence
- NEVER store or display the actual secret value
6. Report findings
Example Output:
Found 3 secrets in .env:
Line 1: GITHUB_CLIENT_SECRET (github_oauth_secret, HIGH confidence)
Line 5: JWT_SHARED_KEY (jwt_secret, HIGH confidence)
Line 12: DATABASE_PASSWORD (database_password, HIGH confidence)
⚠️ Secret values are masked for security.
Recommendation: Convert to 1Password template (see Workflow 2)
Step 1: Validate op CLI
which op || echo "ERROR: 1Password CLI not installed"
op whoami || echo "ERROR: Not signed in to 1Password"
Step 2: Load Secret Patterns
cat ~/.docs/1password-skill/secret-patterns.json | jq -r '.patterns[]'
Step 3: Scan File For each line in target file:
Step 4: Report Results
✓ Secret values are NEVER displayed ✓ Secret values are NEVER logged ✓ Original files are NEVER modified ✓ All operations are read-only
Purpose: Convert environment files to 1Password templates with op:// references.
.env).env.template with op:// referencesUser: "Generate 1Password template from /path/to/.env"
Assistant Process:
1. Run secret detection (Workflow 1)
2. Create output file: /path/to/.env.template
3. For each line:
- If secret detected: Replace with op://Vault/Item/field
- If non-secret: Copy as-is
- If comment: Preserve
4. Generate migration guide
5. Show suggested 1Password vault structure
Example Output:
Created: /path/to/.env.template
Detected 3 secrets:
GITHUB_CLIENT_SECRET → op://Terraphim/GitHub-OAuth/client_secret
JWT_SHARED_KEY → op://Terraphim/JWT-Config/shared_key
DATABASE_PASSWORD → op://Terraphim/Database/password
Migration Guide:
1. Create 1Password items:
op item create --category=Login \
--title="GitHub-OAuth" \
--vault="Terraphim" \
client_secret=<paste-value-here>
op item create --category=Password \
--title="JWT-Config" \
--vault="Terraphim" \
shared_key=<paste-value-here>
op item create --category=Database \
--title="Database" \
--vault="Terraphim" \
password=<paste-value-here>
2. Verify items created:
op item list --vault="Terraphim"
3. Test injection (Workflow 3):
op inject -i .env.template
4. Add to .gitignore:
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
5. Commit template:
git add .env.template
git commit -m "Add 1Password template for secrets"
Input (.env):
# GitHub OAuth
GITHUB_CLIENT_ID=REDACTED-GITHUB-CLIENT-ID
GITHUB_CLIENT_SECRET=REDACTED-GITHUB-CLIENT-SECRET
# JWT Configuration
JWT_SHARED_KEY=REDACTED-JWT-SHARED-KEY
# Non-secret
PORT=8080
NODE_ENV=production
Output (.env.template):
# GitHub OAuth
GITHUB_CLIENT_ID=REDACTED-GITHUB-CLIENT-ID
GITHUB_CLIENT_SECRET=op://Terraphim/GitHub-OAuth/client_secret
# JWT Configuration
JWT_SHARED_KEY=op://Terraphim/JWT-Config/shared_key
# Non-secret
PORT=8080
NODE_ENV=production
Suggested 1Password Structure:
✓ Original .env file is NEVER modified ✓ Creates new .env.template file only ✓ Secret values shown in migration guide are for copy-paste only ✓ User must manually create 1Password items (not automated for security)
Purpose: Populate templates with secrets from 1Password using op inject.
.env.template)op inject to replace op:// references with actual values.gitignoreUser: "Inject secrets from .env.template to /tmp/app.env"
Assistant Process:
1. Validate op session: `op whoami`
2. Check template exists and has op:// refs
3. Execute: `op inject -i .env.template -o /tmp/app.env`
4. Verify output file created
5. Remind about .gitignore
Example Output:
✓ 1Password session active
✓ Template validated: .env.template
✓ Injecting secrets...
✓ Created: /tmp/app.env (3 secrets injected)
⚠️ IMPORTANT:
- Add /tmp/app.env to .gitignore
- Never commit files with injected secrets
- Use this file only for local development/testing
- For production, use Workflow 4 (op run) instead
Pattern 1: Local Development
op inject -i .env.template -o .env.local
# Add .env.local to .gitignore
# Application reads from .env.local
Pattern 2: CI/CD
op inject -i .env.template -o /tmp/secrets.env
source /tmp/secrets.env
# Run tests or deployment
rm /tmp/secrets.env # Clean up
Pattern 3: Docker
op inject -i app.env.template -o /tmp/app.env
docker run --env-file /tmp/app.env myapp:latest
Before injection:
After injection:
✓ NEVER overwrites existing .env files ✓ Output only to explicitly specified paths ✓ Validates op session before injection ✓ Fails gracefully if secrets missing
Purpose: Execute commands with secrets using op run --no-masking.
op run --no-masking -- <command>User: "Start Caddy with 1Password secrets"
Assistant Process:
1. Validate op session
2. Check service file or template exists
3. Execute: op run --no-masking -- systemctl start caddy-terraphim
4. Show command output
5. Preserve exit code
Example Output:
✓ 1Password session active
✓ Environment template found
▶ Running: op run --no-masking -- systemctl start caddy-terraphim
[Command output...]
✓ Command succeeded (exit code: 0)
Use Case 1: Systemd Service
op run --no-masking -- systemctl start myservice
op run --no-masking -- systemctl restart myservice
op run --no-masking -- systemctl reload myservice
Use Case 2: Docker Compose
op run --no-masking -- docker-compose up -d
op run --no-masking -- docker-compose restart api
Use Case 3: Shell Script
op run --no-masking -- ./deploy.sh
op run --no-masking -- ./run-tests.sh
Use Case 4: Direct Binary
op run --no-masking -- /usr/bin/myapp --config config.yaml
op run --no-masking -- python app.py
op run loads variables from:
.env file in current directory (if exists)--env-file flagop:// references are resolved automatically✓ Secrets never written to disk ✓ Secrets cleared from memory after process exits ✓ Original commands run with same permissions ✓ Exit codes preserved for error handling
Purpose: Scan entire codebase for plaintext secrets and generate compliance report.
User: "Audit /home/alex/caddy_terraphim for secrets"
Assistant Process:
1. Find files to scan:
- High priority: *.env, config.json, secrets.*, credentials.*
- Medium priority: docker-compose.yml, *.service, *.conf
- Low priority: *.js, *.py, *.sh (source code)
2. Scan each file (Workflow 1)
3. Generate report with:
- Executive summary
- Findings by file
- Findings by type
- Remediation steps
4. Export as markdown or JSON
Example Output:
═══════════════════════════════════════════════════════════
SECRET AUDIT REPORT
═══════════════════════════════════════════════════════════
Directory: /home/alex/caddy_terraphim
Scanned: 45 files
Duration: 2.3 seconds
EXECUTIVE SUMMARY
─────────────────────────────────────────────────────────
Critical Issues: 3
High Priority: 5
Medium Priority: 2
Total Secrets Found: 10
FINDINGS BY SEVERITY
─────────────────────────────────────────────────────────
CRITICAL (requires immediate action):
✗ caddy_complete.env:2
Type: github_oauth_secret
Context: GITHUB_CLIENT_SECRET=***...cf4
Risk: OAuth credentials in plaintext
Fix: Use Workflow 2 to convert to 1Password template
✗ caddy_complete.env:3
Type: jwt_secret
Context: JWT_SHARED_KEY=***...abd
Risk: JWT signing key exposed
Fix: Migrate to op://Terraphim/JWT-Config/shared_key
✗ github_runner.env:1
Type: token
Context: CI_TOKEN=***...xyz
Risk: CI/CD token in plaintext
Fix: Convert to 1Password reference
HIGH (should fix soon):
... (5 more findings)
FINDINGS BY FILE
─────────────────────────────────────────────────────────
caddy_complete.env: 3 secrets (CRITICAL)
github_runner.env: 1 secret (CRITICAL)
conf/Caddyfile_auth: 2 secrets (HIGH - bcrypt hashes, acceptable)
...
REMEDIATION PLAN
─────────────────────────────────────────────────────────
1. Immediate Actions (Critical):
- Convert caddy_complete.env to template:
Generate template: [see Workflow 2]
- Create 1Password items for 3 secrets
- Update systemd service to use op run
2. Short-term Actions (High):
- Audit bcrypt hashes in Caddyfile_auth
- Consider rotating exposed secrets
- Add .env files to .gitignore
3. Long-term Actions:
- Implement regular audits (monthly)
- Train team on 1Password usage
- Add pre-commit hooks for secret detection
FILES TO MIGRATE:
1. caddy_complete.env → caddy_complete.env.template
2. github_runner.env → github_runner.env.template
COMPLIANCE STATUS
─────────────────────────────────────────────────────────
✗ NOT COMPLIANT: 3 critical issues found
Requirement: Zero plaintext secrets in production configs
Current: 10 secrets found across 4 files
Target: 0 secrets (all using 1Password)
═══════════════════════════════════════════════════════════
Report generated: 2025-12-29 14:30:00 UTC
Next audit due: 2026-01-29
═══════════════════════════════════════════════════════════
Markdown (default):
JSON (for automation):
{
"audit_date": "2025-12-29T14:30:00Z",
"directory": "/home/alex/caddy_terraphim",
"files_scanned": 45,
"duration_seconds": 2.3,
"summary": {
"critical": 3,
"high": 5,
"medium": 2,
"total": 10
},
"findings": [
{
"file": "caddy_complete.env",
"line": 2,
"type": "github_oauth_secret",
"confidence": "high",
"severity": "critical",
"context": "GITHUB_CLIENT_SECRET=***...cf4",
"remediation": "Convert to op://Terraphim/GitHub-OAuth/client_secret"
}
],
"compliance": {
"status": "non_compliant",
"target": 0,
"current": 10
}
}
✓ Read-only operations (no file modifications) ✓ Secret values never displayed in reports ✓ Reports are safe to commit to version control ✓ Findings include remediation guidance
Purpose: Integrate 1Password with systemd services for secure secret management.
[Unit]
Description=My Service with 1Password
After=network.target
[Service]
Type=simple
ExecStartPre=/bin/sh -c 'op inject -i /path/to/service.env.template -o /tmp/service.env'
EnvironmentFile=/tmp/service.env
ExecStart=/usr/bin/myservice
ExecStopPost=/bin/rm -f /tmp/service.env
[Install]
WantedBy=multi-user.target
Pros:
Cons:
[Unit]
Description=My Service with 1Password
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/op run --no-masking -- /usr/bin/myservice
Restart=on-failure
[Install]
WantedBy=multi-user.target
Pros:
Cons:
Current (bigbox, insecure):
[Service]
WorkingDirectory=/home/alex/caddy_terraphim
EnvironmentFile=/home/alex/caddy_terraphim/caddy_complete.env
ExecStart=/home/alex/caddy_terraphim/caddy run --config /home/alex/caddy_terraphim/conf/Caddyfile_auth
After Migration (secure):
[Service]
WorkingDirectory=/home/alex/caddy_terraphim
ExecStart=/usr/bin/op run --no-masking -- /home/alex/caddy_terraphim/caddy run --config /home/alex/caddy_terraphim/conf/Caddyfile_auth
Steps:
caddy_complete.env to caddy_complete.env.template (Workflow 2)systemctl daemon-reloadsystemctl restart caddy-terraphimsystemctl status caddy-terraphimProblem: Systemd services run as different users, may not have op session.
Solution 1: Service Account
# Create service account token (lasts 30 days)
op signin --raw > /etc/1password/service-token
# Use in service
[Service]
Environment="OP_SERVICE_ACCOUNT_TOKEN=/etc/1password/service-token"
ExecStart=/usr/bin/op run --no-masking -- /usr/bin/myservice
Solution 2: Connect Server (Best for Production) Set up 1Password Connect server for machine-to-machine authentication.
# 1. Test op run manually first
op run --no-masking -- /usr/bin/myservice --test
# 2. Test service file syntax
systemd-analyze verify myservice.service
# 3. Start service
systemctl start myservice
# 4. Check status
systemctl status myservice
# 5. View logs
journalctl -u myservice -f
✓ Secrets never stored in plain text service files ✓ No temporary secret files on disk (Pattern B) ✓ Standard systemd security features work (ProtectSystem, PrivateTmp) ✓ Automatic secret cleanup on service stop
docker-compose.yml:
version: '3.8'
services:
app:
image: myapp:latest
env_file:
- app.env.template
Deployment:
# Method 1: Inject to temp file
op inject -i app.env.template -o /tmp/app.env
docker-compose --env-file /tmp/app.env up -d
rm /tmp/app.env
# Method 2: Use op run (simpler)
op run --no-masking -- docker-compose up -d
deploy.sh.template:
#!/bin/bash
API_KEY="op://Production/API-Keys/deploy_key"
DB_PASSWORD="op://Production/Database/password"
# Script logic here
curl -H "Authorization: Bearer $API_KEY" ...
Execution:
op run --no-masking -- bash deploy.sh.template
GitHub Actions:
- name: Deploy with 1Password
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
run: |
op run --no-masking -- ./deploy.sh
Structure:
.env.template # Template with op:// refs (commit this)
.env.dev.template # Dev-specific overrides (commit this)
.env.prod.template # Prod-specific overrides (commit this)
.env.local # Generated locally (never commit)
Usage:
# Development
op inject -i .env.dev.template -o .env.local
# Production
op run --no-masking -- ./start-production.sh
Symptoms:
bash: op: command not found
Solution:
# macOS
brew install 1password-cli
# Linux
curl -sSfO https://downloads.1password.com/linux/tar/stable/x86_64/1password-cli-latest.tar.gz
tar -xf 1password-cli-latest.tar.gz
sudo mv op /usr/local/bin/
# Verify
which op && op --version
Symptoms:
[ERROR] 2025/12/29 14:30:00 You are not currently signed in. Please run `op signin`
Solution:
op signin
# Follow prompts to sign in
op whoami # Verify
Symptoms:
[ERROR] 2025/12/29 14:30:00 Invalid session token
Solution:
op signin --force
# Or use biometric unlock if configured
Symptoms:
[ERROR] 2025/12/29 14:30:00 item "GitHub-OAuth" not found in vault "Terraphim"
Solution:
# List vaults
op vault list
# List items in vault
op item list --vault="Terraphim"
# Create missing item
op item create --category=Login \
--title="GitHub-OAuth" \
--vault="Terraphim" \
client_secret=<value>
Symptoms:
[ERROR] Unable to resolve op://Vault/Item/Field
Solution: Check reference format:
# Correct formats:
op://VaultName/ItemName/FieldName
op://VaultName/ItemName/section/FieldName
# Common mistakes:
op://Vault Name/Item Name/field # Spaces not escaped
op:\\VaultName\ItemName\Field # Wrong slashes
op://vaultname/itemname/Field # Case sensitivity issues
Symptoms:
systemctl status myservice
# Shows: op: command not found
Solution:
[Service]
Environment="PATH=/usr/local/bin:/usr/bin:/bin"
ExecStart=/usr/bin/op run --no-masking -- /usr/bin/myservice
Symptoms:
Permission denied: /tmp/secrets.env
Solution:
# Check file permissions
ls -la /tmp/secrets.env
# Fix permissions
chmod 600 /tmp/secrets.env
# Or inject to user-writable location
op inject -i template -o ~/.config/app/secrets.env
Always:
.env.template files with op:// references.gitignoreNever:
.env files with secretsAdd to .gitignore:
# Environment files with secrets
.env
.env.local
.env.*.local
*.env
# But allow templates
!.env.template
!.env.*.template
# 1Password service tokens
service-token
*.service-token
Run audits regularly:
# Weekly for critical systems
0 0 * * 0 cd /path/to/project && op run --no-masking -- ./audit-secrets.sh
# Monthly for development
0 0 1 * * cd /path/to/project && op run --no-masking -- ./audit-secrets.sh
1Password Vaults:
File Permissions:
# Injected files should be readable only by owner
chmod 600 .env.local
# Templates can be readable by group
chmod 640 .env.template
When to Rotate:
How to Rotate:
Set up alerts for:
Document for your team:
1Password handles backups, but you should:
Recommended workflow:
# 1. Clone repo
git clone repo
# 2. Get secrets template
git pull # Gets .env.template
# 3. Request access to 1Password vault
# (Team admin grants access)
# 4. Inject secrets for local development
op inject -i .env.template -o .env.local
# 5. Add .env.local to .gitignore (if not already)
echo ".env.local" >> .gitignore
# 6. Start development
op run --no-masking -- npm start
Recommended workflow:
# Never inject secrets on production servers
# Instead, use op run for all service starts
# 1. Deploy code
git pull
# 2. Update service to use op run
systemctl edit myservice
# Add: ExecStart=/usr/bin/op run --no-masking -- /usr/bin/myservice
# 3. Reload and restart
systemctl daemon-reload
systemctl restart myservice
# 4. Verify
systemctl status myservice
journalctl -u myservice -f
# Detection
op whoami && cat file.env # Manual scan
# Template Generation
# (Use Workflow 2 guidance)
# Injection
op inject -i template -o output
# Command Execution
op run --no-masking -- command
# Audit
# (Use Workflow 5 guidance)
# Systemd
systemctl edit myservice # Add op run to ExecStart
systemctl daemon-reload
systemctl restart myservice
Version History:
Maintainer: Claude Code License: MIT Documentation: This file