| 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:
[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):
fnox reencrypt -p age
fnox reencrypt -p age -P production -f
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:
[secrets]
DATABASE_URL = { provider = "aws", value = "database-url", if_missing = "error" }
SENTRY_DSN = { provider = "aws", value = "sentry-dsn", if_missing = "ignore" }
Set a strict default at the top level for production:
if_missing = "error"
[secrets]
DATABASE_URL = { provider = "aws", value = "database-url" }
OPTIONAL_FEATURE = { default = "false", if_missing = "ignore" }
In CI/CD, control via environment variable:
- run: fnox exec --profile production -- ./deploy.sh
env:
FNOX_IF_MISSING: error
- 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:
- 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
Cloud Provider Secrets
Scanning Before Commit
Run fnox scan to detect any plaintext secrets accidentally added:
fnox scan
fnox doctor
fnox check
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 |