Security & SOC 2 Ready
If you're running Protocol in a production environment — especially one working toward SOC 2 readiness — this page covers what Protocol gives you, what you need to add, and how to lock everything down.
Automated Checks on Every Start
Every time you run protocol start, two sets of checks run automatically as part of the staged startup:
Security Audit
Six checks run against your codebase and server:
| Check | What it looks for |
|---|---|
| Malicious code | Scans PHP files for dangerous patterns — eval(, base64_decode(, shell_exec(, proc_open(, and other common backdoor signatures |
| File permissions | Verifies ~/.protocol/key is 0600 and ~/.protocol/ is 0700. Flags world-writable files in your config repo |
| Dependencies | Runs composer audit to check for known vulnerabilities in your PHP packages |
| Suspicious processes | Scans running processes for known bad actors — cryptominers (xmrig, kinsing), reverse shells (nc, ncat, socat) |
| Docker security | Parses your docker-compose.yml for risky settings — privileged: true, user: root, dangerous capability additions |
| Recent changes | Flags files modified in the last 24 hours that aren't tracked by git — on a production node, unexpected changes are suspicious |
If any check fails, the startup stage shows FAIL with the details. The deployment continues (security issues are warnings, not blockers) but the final summary reports issues.
Run it standalone anytime: protocol security:audit
SOC 2 Readiness Check
Seven checks validate your setup against SOC 2 Type II requirements:
| Check | What it verifies |
|---|---|
| Encrypted secrets | deployment.secrets is set to "encrypted" and an encryption key exists on this machine |
| Audit logging | The deployment log file exists at ~/.protocol/deployments.log and isn't world-readable |
| Deploy strategy | deployment.strategy is set to "release" — immutable tags with audit trails, not mutable branches |
| Git integrity | A remote is configured and HEAD is reachable from the remote — no orphaned or detached states |
| Reboot recovery | A @reboot crontab entry exists so Protocol restarts after server reboots |
| Key permissions | The encryption key has 0600 permissions and the Protocol directory has 0700 |
Run it standalone anytime: protocol soc2:check
What Protocol Already Handles
Protocol was built with security in mind. Here's what you get out of the box:
Encrypted Secrets
Your .env files are encrypted with AES-256-GCM before they touch git. The same encryption standard used by banks, governments, and every serious security system. The encryption key stays on your machines — only gibberish travels through repositories.
Audit Trail
Every deployment writes a timestamped log entry: what version was running before, what version is running now, when it happened, and whether it succeeded. View it with protocol deploy:log.
Immutable Deployments
Release mode deploys specific git tags. Tags are immutable — v1.2.0 always means the same code. You can't accidentally deploy "whatever's on master." And because tags don't change, rolling back means deploying a known-good version, not reverting commits.
Environment Isolation
Each environment (production, staging, dev) has its own branch in the config repo with its own secrets. A developer's laptop never touches production credentials. The environments share nothing except the encryption key.
SSH Key Management
protocol key:generate creates ed25519 SSH keys for deployment, so no passwords are used for git access.
SOC 2 Type II — What Auditors Want to See
SOC 2 Type II evaluates your controls over time. Here's how Protocol maps to the things auditors care about:
Access Controls (CC6)
What you can show them:
- Secrets are encrypted at rest in git — nobody can read
.envfiles without the key - Each environment is isolated in its own config branch
- SSH keys are used for all git operations — no shared passwords
- Encryption keys have strict file permissions (
0600) and live outside any repository
What you should add:
- Use GitHub branch protection to restrict who can push to the
productionconfig branch - Require pull request reviews before config changes reach production
- Use GitHub's CODEOWNERS file to require specific approvers for sensitive files
Change Management (CC7/CC8)
What you can show them:
- All code changes flow through git — every change has an author, timestamp, and commit message
- All config changes flow through git — same audit trail
- Deployments use explicit version tags — creating a release is a deliberate approval decision
- The audit log tracks every deployment with before/after versions
What you should add:
- Enable required status checks on your main branch (CI must pass before merge)
- Use GitHub's required reviewers feature on production branches
- Set up deployment notifications (Slack, email, PagerDuty) so the team knows when production changes
Availability (A1)
What you can show them:
protocol cron:addensures Protocol restarts after server rebootsprotocol statusgives a health overview of all running processes- Docker Compose handles container restart policies
What you should add:
- External monitoring (Uptime Robot, Datadog, etc.) that alerts when nodes go down
- Health check endpoints in your application that monitoring services can ping
- Load balancing across multiple nodes so one node going down doesn't take everything offline
Confidentiality (C1)
What you can show them:
- Secrets are encrypted with AES-256-GCM before being stored anywhere
- Decryption keys have strict filesystem permissions and never enter git
- Plaintext secrets are gitignored and deleted after encryption
- Each machine decrypts independently — secrets don't travel in plaintext over the network
What you should add:
- Document your key rotation schedule (how often you change the encryption key)
- Keep a backup of your encryption key in a password manager or vault
- Restrict which team members have access to the encryption key
The Hardening Checklist
Before running Protocol in a SOC 2 environment, go through this list:
Must Do
- Set
deployment.strategyto"release"inprotocol.json— branch mode has no audit trail - Set
deployment.secretsto"encrypted"— never store plaintext secrets in git - Enable GitHub branch protection on your main branch and production config branch
- Require pull request reviews before merging to production
- Set up
protocol cron:addon every node for reboot recovery - Keep your encryption key in a password manager as a backup
- Restrict
~/.protocol/keypermissions to0600(Protocol does this by default)
Should Do
- Set up deployment notifications (Slack webhook, email) when releases are pushed
- Set up external health monitoring for each production node
- Follow the Deployment SOPs for consistent operations
- Follow the Key Rotation Procedure on a quarterly schedule
- Review the Incident Response Runbook with your team
- Install Wazuh SIEM agent:
protocol siem:install --manager=your-wazuh-server - Forward audit logs to a centralized SIEM (Wazuh, CloudWatch, Datadog, Splunk)
- Run
protocol statuschecks as part of your monitoring
Nice to Have
- Run
protocol security:auditin your CI pipeline to catch issues before they reach production - Run
protocol soc2:checkin CI to enforce readiness gates - Use
security:trojansearchin your CI pipeline to scan for suspicious code patterns - Set up
security:changedfilesalerts to review files modified in the last 15 days - Use GitHub's CODEOWNERS feature for sensitive files (already added at
.github/CODEOWNERS)
How Secrets Stay Safe
Here's the full journey of a secret, from your keyboard to a running container:
Your Machine Git Production Node
───────────── ─── ───────────────
1. You edit .env
2. protocol config:init
→ Encrypt secrets
3. .env → AES-256-GCM
→ .env.enc 4. .env.enc committed
→ plaintext deleted and pushed
→ .gitignore updated 5. protocol start
→ pulls config repo
→ reads ~/.protocol/key
→ decrypts .env.enc
→ .env exists in memory
→ passed to Docker
→ containers run with secrets
→ audit log written
At no point does a plaintext secret travel through git. At no point is a plaintext secret stored in a shared location. The encrypted file is useless without the key, and the key never leaves the machines it's installed on.
Encryption Details
- Algorithm: AES-256-GCM (authenticated encryption — tamper-proof)
- Key size: 256-bit (64-character hex string)
- Nonce: Random 12 bytes per file (prevents identical plaintext from producing identical ciphertext)
- Output format:
base64(nonce + auth_tag + ciphertext) - Implementation: PHP's built-in
openssl_encrypt()— no external dependencies
The Deployment Audit Log
Every deployment action writes to ~/.protocol/deployments.log:
2024-01-15T10:30:01Z deploy repo=/opt/myapp from=v1.1.0 to=v1.2.0 status=success
2024-01-15T10:30:05Z config repo=/opt/myapp env=production files=3 status=success
2024-01-15T10:30:08Z docker repo=/opt/myapp image=registry/app:latest action=rebuild status=success
2024-03-01T14:22:00Z rollback repo=/opt/myapp from=v1.3.0 to=v1.2.0 status=success
What gets logged:
- Every deployment (version transitions)
- Every rollback
- Config changes
- Docker rebuilds
What you should do with it:
- Keep logs for at least 12 months (SOC 2 audits typically cover 6-12 months)
- Forward to a centralized, tamper-evident logging system
- Set up alerts on
status=failureentries
View your logs anytime:
protocol deploy:log
Key Rotation
When you need to change your encryption key (and you should, periodically):
# 1. Decrypt everything with the old key
protocol config:init # → choose "Decrypt secrets"
# 2. Generate a new key
protocol secrets:setup
# 3. Re-encrypt with the new key
protocol config:init # → choose "Encrypt secrets"
# 4. Push encrypted files
protocol config:save
# 5. Distribute the new key to all nodes
protocol secrets:key --scp=deploy@prod-server-1
protocol secrets:key --scp=deploy@prod-server-2
# 6. Restart nodes to pick up the new key
# (on each node)
protocol stop && protocol start
Quick Reference
| Question | Answer |
|---|---|
| What encryption does Protocol use? | AES-256-GCM (same as banks and governments) |
| Where is the key stored? | ~/.protocol/key on each machine, with 0600 permissions |
| Can I recover secrets without the key? | No. Keep a backup in a password manager. |
| Are secrets stored in git? | Only the encrypted versions. Plaintext is gitignored and deleted. |
| Does each environment use a different key? | No. Same key, different secrets (on different config branches). |
| How do I transfer the key to production? | protocol secrets:key --scp=user@host or protocol secrets:key --push (GitHub) |
| Where is the audit log? | ~/.protocol/deployments.log — view with protocol deploy:log |