GUIDE

How to Secure Your SupaForge Credentials

Environment variables, CI secrets, least privilege, MFA - protect your connection strings and tokens.

The SupaForge CLI is open source and on npm: npm install -g @akal/supaforge. A hosted dashboard is on the way - to hear when it opens.

What Credentials Does SupaForge Need?

SupaForge connects to the environments it compares. For each one it needs:

  • Database connection strings - PostgreSQL URIs with username, password, host, and database name.
  • A Supabase personal access token (optional) - for the checks that use the Management API, such as auth and Edge Functions. On self-hosted Supabase, the service-role key plays this part.

These are the most sensitive values in your workflow. A leaked connection string can give full read and write access to your database, and a leaked access token reaches every project the account can.

Rule 1 - Never Commit Credentials

Never put connection strings in your config file if it's committed to Git. Use environment variables instead:

supaforge.config.json (safe)
{
  "environments": {
    "staging": {
      "dbUrl": "$SUPAFORGE_STAGING_URL",
      "projectRef": "STAGING_REF",
      "accessToken": "$SUPABASE_ACCESS_TOKEN"
    },
    "production": {
      "dbUrl": "$SUPAFORGE_PROD_URL",
      "projectRef": "PROD_REF",
      "accessToken": "$SUPABASE_ACCESS_TOKEN"
    }
  },
  "source": "staging",
  "target": "production"
}
.env (gitignored)
SUPAFORGE_STAGING_URL=postgresql://user:[email protected]_REF.supabase.co:5432/postgres
SUPAFORGE_PROD_URL=postgresql://user:[email protected]_REF.supabase.co:5432/postgres
SUPABASE_ACCESS_TOKEN=your-personal-access-token

$VAR and ${VAR} in dbUrl and accessToken are read from the environment, and SupaForge loads .env files itself. Add .env to your .gitignore, and use .env.example with placeholder values to document the required variables.

Rule 2 - Use CI Secrets

In CI/CD pipelines, store credentials as encrypted secrets - not in workflow files:

  • GitHub Actions - Repository secrets or environment secrets.
  • GitLab CI - CI/CD variables with "masked" and "protected" enabled.
  • Vercel / Netlify - Environment variables in the project settings.
GitHub Actions example
- name: Run scan
  env:
    SUPAFORGE_STAGING_URL: ${{ secrets.SUPAFORGE_STAGING_URL }}
    SUPAFORGE_PROD_URL: ${{ secrets.SUPAFORGE_PROD_URL }}
    SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
  run: npx @akal/supaforge diff --ci

Rule 3 - Encrypt at Rest

If you're building a hosted service on top of SupaForge (like a team dashboard), encrypt credentials before storing them in your database. Use AES-256-GCM with a unique initialisation vector (IV) per row:

Encryption pattern (Node.js)
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';

const ALGORITHM = 'aes-256-gcm';

function encrypt(plaintext: string, key: Buffer): string {
  const iv = randomBytes(12);
  const cipher = createCipheriv(ALGORITHM, key, iv);
  const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
  const tag = cipher.getAuthTag();
  return Buffer.concat([iv, tag, encrypted]).toString('base64');
}

function decrypt(ciphertext: string, key: Buffer): string {
  const data = Buffer.from(ciphertext, 'base64');
  const iv = data.subarray(0, 12);
  const tag = data.subarray(12, 28);
  const encrypted = data.subarray(28);
  const decipher = createDecipheriv(ALGORITHM, key, iv);
  decipher.setAuthTag(tag);
  return decipher.update(encrypted) + decipher.final('utf8');
}

Store the encryption key in an environment variable (ENCRYPTION_KEY), not in your codebase. The key should be a 64-character hex string (32 bytes).

Rule 4 - Principle of Least Privilege

  • Diff with a role that can read, not write. Reporting drift only reads, though the role must be able to see everything it compares - Supabase's own schemas included. To make sure a production URL can never write, add ?options=-c%20default_transaction_read_only%3Don to it: definitions that read differently but mean the same are still recognised, as long as the other connection - your local or staging database - can create temporary objects.
  • Keep write access for applying. --apply needs a role that owns what it changes, and --prove also needs to create a temporary database. Use a separate connection string for those runs.
  • Rotate credentials regularly. Supabase lets you reset the database password from the Dashboard - do this on a schedule.

Rule 5 - Enable MFA on Supabase

A compromised Supabase account can expose all connection strings. Enable MFA on every team member's Supabase account:

  1. Go to Account → Security in the Supabase Dashboard.
  2. Enable TOTP (authenticator app) - this is the most reliable factor.
  3. Consider enforcing MFA at the organisation level if available.

Checklist

  • ✅ Connection strings in environment variables, not committed files.
  • ✅ CI/CD uses encrypted secrets.
  • ✅ Credentials encrypted at rest if stored in a database.
  • ✅ Read-only roles for diffs, write roles only for applying.
  • ✅ MFA enabled on all Supabase accounts.
  • ✅ Database passwords rotated on a schedule.

Summary

Credential security is non-negotiable. Use environment variables, CI secrets, AES-256-GCM encryption for storage, least-privilege roles, and MFA to protect your Supabase environments.

Ready to try SupaForge?

Install the CLI and run your first diff in a minute. Nothing is changed until you add --apply.

npm install -g @akal/supaforge
View on GitHub →