Skip to content

Hard Constraints

These constraints are enforced by CI and code review. PRs that violate them are blocked.

deny.toml bans openssl, openssl-sys, and openssl-probe. Use rustls for all TLS.

If a new dependency transitively requires OpenSSL, it must be replaced or feature-flagged out.

Terminal window
# Check before adding a dep
cargo deny check

Never write a down migration. The policy:

  • Forward-only: each migration is permanent
  • Reverting a bad migration = code revert + new forward migration
  • Production has data in those tables; down migrations that DROP columns destroy data
Terminal window
# Create migration
sqlx migrate add <description>
# Verify (no down file)
ls migrations/ # should only show .sql files, not .down.sql

Every column holding a secret must be bytea + EncryptedField<T>.

Secret columns include: TOTP secrets, recovery codes, API key values, env var values with is_secret=true, DKIM private keys, backup repo passwords, database user passwords.

Plaintext secrets in the DB are a security bug and will be flagged in code review.

// ✅ Correct
pub totp_secret: Option<EncryptedField<String, TotpSecretFamily>>,
// ❌ Wrong
pub totp_secret: Option<String>,

New endpoints must be designed in proto/openapi.yaml first. tests/openapi_drift.rs fails CI if code diverges.

This prevents the spec from becoming documentation-only fiction.

Every state-changing handler writes a row to audit_log:

AuditLogRepo::new(&pool).append(NewAuditEntry {
actor: session.actor(),
action: "site.created",
resource_type: "site",
resource_id: site.id,
..Default::default()
}).await?;

Missing audit writes are a compliance bug.

rustls is configured with an explicit AEAD cipher list. No plaintext fallback, no TLS 1.2 downgrade.

// ❌ Crashes the worker thread
let data = some_option.unwrap();
// ✅ Returns 500 with request_id
let data = some_option.ok_or_else(|| ApiError::internal())?;

All binary crates have publish = false in Cargo.toml. Library crates are workspace-internal.