name: fnox-security description: 'Security best practices for fnox — key rotation, gitignore rules, CI/CD secret handling, access control, missing-secret enforcement, and avoiding common mistakes. Triggers on: "fnox security", "rotate fnox keys", "fnox gitignore", "fnox ci secrets", "fnox key rotation", "fnox access control", "fnox reencrypt", "secure fnox setup", "fnox best practices".'
Fnox Security Best Practices
When to Use This Skill
Use this skill when:
- Reviewing or improving the security posture of an fnox setup
- Setting up key rotation policies
- Configuring CI/CD to handle secrets safely
- Adding or removing team member access
- Enforcing strict missing-secret behavior in production
- Auditing fnox.toml before committing
When NOT to Use This Skill
- Initial provider setup — use
fnox-providers - fnox.toml structure or profiles — use
fnox-configuration - General cloud provider IAM setup outside fnox
The Golden Rules
- Never commit
fnox.local.toml— always gitignore it - Never store plaintext secrets — always encrypt before committing
- Use service accounts in CI/CD — not personal tokens
- Principle of least privilege — scope IAM roles and vault access tightly
- Rotate keys periodically — especially after team member departures
Gitignore Rules
Always add to .gitignore:
fnox.local.toml
.fnox.local.toml
Commit fnox.toml — encrypted values are safe. Never commit the local override file.
Provide a fnox.local.toml.example (committed) showing the structure without real values:
# fnox.local.toml.example — copy to fnox.local.toml and fill in your values
[secrets]
DATABASE_URL = { default = "postgresql://localhost/<your-db-name>" }
Key Rotation
Age Key Rotation
When rotating age keys (e.g., after a team member leaves):
# 1. Remove departing member from recipients in fnox.toml
# 2. Re-encrypt all secrets with remaining recipients
fnox reencrypt -p age
# Re-encrypt a specific profile
fnox reencrypt -p age -P production -f
# Preview without writing
fnox reencrypt -p age --dry-run
Remote Provider Rotation
For cloud providers (AWS SM, 1Password, etc.), rotate secrets directly in the provider. Fnox only stores references — no re-encryption needed for remote secrets.
Missing-Secret Enforcement
Use if_missing = "error" on required secrets to prevent silent failures:
# Required secrets must exist or the command fails
[secrets]
DATABASE_URL = { provider = "aws", value = "database-url", if_missing = "error" }
# Optional monitoring secrets (continue without them)
SENTRY_DSN = { provider = "aws", value = "sentry-dsn", if_missing = "ignore" }
Set a strict default at the top level for production:
if_missing = "error" # All secrets must resolve
[secrets]
DATABASE_URL = { provider = "aws", value = "database-url" }
OPTIONAL_FEATURE = { default = "false", if_missing = "ignore" } # Override
In CI/CD, control via environment variable:
# Fail fast on missing secrets in production deploys
- run: fnox exec --profile production -- ./deploy.sh
env:
FNOX_IF_MISSING: error
# Be lenient for forked PRs that lack secrets
- run: fnox exec -- npm test
env:
FNOX_IF_MISSING: ignore
CI/CD Security Patterns
Age in GitHub Actions
jobs:
test:
steps:
- uses: actions/checkout@v4
- uses: jdx/mise-action@v3
- name: Run tests
env:
FNOX_AGE_KEY: ${{ secrets.FNOX_AGE_KEY }}
run: fnox exec -- npm test
Never print secrets or pass them as arguments — fnox injects them as environment variables.
Remote Providers in CI
Prefer IAM roles over static credentials when possible:
# AWS: Use OIDC federation (no stored credentials)
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::ACCOUNT:role/github-actions
aws-region: us-east-1
- run: fnox exec --profile production -- ./deploy.sh
Access Control Checklist
Age-Encrypted Secrets
- Only active team members listed as recipients
- CI has its own dedicated age key
- Departed team members removed and secrets re-encrypted
- Age private keys stored securely (not in
.envcommitted to git)
Cloud Provider Secrets
- IAM roles scoped to minimum required paths (e.g.,
myapp/*not*) - Service accounts are per-environment (dev, staging, prod)
- Audit logs enabled and reviewed periodically
- Rotation policies configured for sensitive credentials
Scanning Before Commit
Run fnox scan to detect any plaintext secrets accidentally added:
fnox scan # Scan fnox.toml for unencrypted values
fnox doctor # Check overall configuration health
fnox check # Verify all secrets can be resolved
Common Mistakes
| Mistake | Risk | Fix |
|---|---|---|
Committing fnox.local.toml |
Personal overrides exposed | Add to .gitignore immediately |
Hardcoded default = "real-value" |
Plaintext secret in git | Use a provider, not default for real secrets |
| Single age key for whole team | Tight coupling, hard offboarding | Use per-person recipients |
| Same CI key across environments | Dev key can access prod | Separate CI keys per environment |
No if_missing = "error" on required secrets |
Silent failures in prod | Mark critical secrets as error |
| Personal tokens in CI | Token expiry breaks CI | Use service accounts / machine identities |