What happens when an API key or private key is committed in SQL?
API key, access token or private key in a string literal
Critical: a working credential committed to the repository; rotate it, git history keeps it.
What happens
Vendors give their secrets recognisable prefixes precisely so that leaks can be caught: sk_live_ (Stripe), AKIA (AWS), ghp_ (GitHub), xoxb- (Slack), -----BEGIN PRIVATE KEY-----. One of those in an INSERT, an UPDATE or a column DEFAULT is a working credential committed to the repository. The rule matches only published formats, never entropy, a uuid, a bcrypt hash or a base64 blob is not a token, and the rule stays silent on all of them.
Why it is dangerous on a populated table
Table size does not change this one: a committed credential is live from the first row, and git history keeps it after the fix.
Fires on
INSERT INTO settings (key, value) VALUES ('stripe_secret', 'sk_live_51H8zK2eZvKYlo2CabcDEFghij');The safe pattern
Never seed a live token through a migration. Store a reference (the secret's name in your vault) or a placeholder the deploy substitutes, and have the application read the value at runtime. Rotate the token that was already committed, git history keeps it.
INSERT INTO settings (key, value) VALUES ('stripe_secret', 'vault:stripe/live/secret_key');Fixtures
The rule ships with these files and the test suite runs them on every change: the first set must fire, the second must stay silent.
Fires (6)
UPDATE integrations SET config = '{"access_key_id": "AKIAIOSFODNN7EXAMPLE"}' WHERE name = 'exports';INSERT INTO integrations (name, token) VALUES ('github', 'ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef012345');INSERT INTO service_accounts (name, bearer) VALUES ('ingest', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJpbmdlc3QiLCJyb2xlIjoic2VydmljZSJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c');INSERT INTO signing_keys (kid, pem) VALUES ('2026-09', '-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu
KUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm
-----END RSA PRIVATE KEY-----');INSERT INTO settings (key, value) VALUES ('stripe_secret', 'sk_test_51H8zK2eZvKYlo2CabcDEFghijKLMNopqrSTUv');-- Two vendor tokens inside one JSON literal: both must be masked, not just the first.
INSERT INTO integrations (name, config)
VALUES ('billing', '{"stripe":"sk_live_51H8zK2eZvKYlo2CabcDEFghij","slack":"xoxb-1234567890-abcdefghijklmnop"}');Stays silent (4)
INSERT INTO settings (key, value) VALUES ('provider', 'sk_live'), ('region_code', 'AKIA'), ('note', 'rotate the ghp_ token quarterly');INSERT INTO settings (key, value) VALUES ('stripe_publishable', 'pk_live_51H8zK2eZvKYlo2CabcDEFghijKLMNopqrSTUv');INSERT INTO api_clients (id, secret_hash, logo) VALUES ('6f1c2b3a-4d5e-4f60-8a7b-9c0d1e2f3a4b', '$2b$12$C6UzMDM.H6dfI/f/IKcEeO9rOsTbnR6Q3oFf9zGjHpMqU9ZAwK9Tu', 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==');INSERT INTO settings (key, value) VALUES ('stripe_secret', 'vault:stripe/live/secret_key');How to check locally
This rule runs locally in the free CLI, or with the full corpus through the hosted service. No install, nothing leaves your machine:
npx bolvrk check migration.sql