The Developer Security Checklist I Wish Someone Handed Me on Day One
Every few months someone on my team asks me to look over their shoulder while they set up a new laptop, and every few months I notice the same thing: smart engineers who can debug a race condition in a distributed system are still using one SSH key across four employers, have never signed a commit, and store secrets in a Notes app. This isn't a competence problem. Security setup is just poorly taught — it's the stuff you're supposed to already know, so nobody ever explains it properly. This is the walkthrough I actually give people.
SSH keys: stop using RSA, stop reusing keys across contexts
If you generated your SSH key more than a few years ago, it's probably RSA-2048 or even RSA-1024, and you've probably used the same key for GitHub, your personal VPS, and your work servers. Both of those are worth fixing. Ed25519 is faster, has smaller keys, and is the current recommendation from basically every security team that publishes guidance, including GitHub's own docs.
ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/id_ed25519_github
I keep a separate key per context — one for GitHub, one for work infra, one for personal servers — because if a key does leak, you want to know exactly what blast radius you're dealing with, and you want to be able to revoke one thing without rotating everything. Your ~/.ssh/config makes this painless instead of annoying:
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github
IdentitiesOnly yes
Host prod-bastion
HostName bastion.internal.example.com
User rupraj
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
ForwardAgent no
That ForwardAgent no line matters more than people think. Agent forwarding is convenient — it lets a remote box use your local key to hop further — but if that remote box is compromised, an attacker can use your forwarded agent to authenticate as you elsewhere, without ever seeing the private key. Unless you specifically need it for a jump host, leave it off. If you do need it, use ProxyJump instead, which routes the connection through the bastion without exposing your agent to it:
Host prod-db
HostName db.internal.example.com
User rupraj
IdentityFile ~/.ssh/id_ed25519_work
ProxyJump prod-bastion
Always set a passphrase on your private key. Yes, it's mildly annoying. Add it to your OS keychain once (ssh-add --apple-use-keychain ~/.ssh/id_ed25519_github on macOS, or through keychain/gnome-keyring on Linux) and you'll rarely type it again, but if your laptop is ever stolen, an unencrypted private key is a fully usable credential sitting in plaintext on disk.
GPG signing: proving commits actually came from you
Anyone can git config user.email to whatever they want and author a commit that looks like it came from you. GPG signing doesn't stop that entirely, but it does mean GitHub, GitLab, and anyone verifying the signature can confirm the commit was signed by a key you control — which matters a lot more than people assume once you've had a supply-chain scare or a compliance audit ask "how do you know this commit is legitimate."
gpg --full-generate-key
choose (9) ECC (sign and encrypt), curve 25519, no expiration or a sane 1-2yr expiry
gpg --list-secret-keys --keyid-format=long
copy the key ID after sec ed25519/
gpg --armor --export YOUR_KEY_ID
paste this into GitHub -> Settings -> SSH and GPG keys -> New GPG key
Then wire it into git:
git config --global user.signingkey YOUR_KEY_ID
git config --global commit.gpgsign true
git config --global tag.gpgsign true
Every commit now gets signed automatically, and you'll see a "Verified" badge on GitHub. The first time I turned this on for a team, we found two commits within a week that had spoofed author emails from a misconfigured CI bot — nothing malicious, but it was a good reminder that "who authored this" and "who actually ran git commit" aren't the same question unless you're verifying signatures. If you use GitHub Actions or CI to make commits (changelog bots, version bumps), sign those too using an action like crazy-max/ghaction-import-gpg so automated commits carry the same trust signal.
Hardware keys: the thing that actually stops account takeover
Passwords and even TOTP codes can be phished. A YubiKey using FIDO2/WebAuthn can't be, because the browser cryptographically binds the authentication to the actual origin — a fake login page simply can't complete the handshake even if it perfectly mimics the real one. I resisted buying one for years thinking it was overkill for an individual developer, until a coworker's Google account got hit by a fairly convincing OAuth-consent phishing attempt that would have sailed straight through SMS 2FA. It didn't work because his account was hardware-key-only.
Set one up on GitHub, your password manager, and your identity provider (Google, Okta, whatever your org uses) at minimum:
You can also generate SSH keys directly on a YubiKey so the private
key material never touches disk at all:
ssh-keygen -t ed25519-sk -O resident -O verify-required -f ~/.ssh/id_ed25519_sk
That -sk suffix means "security key" — the private key stays on the hardware token, and every use requires a physical touch. Even with full disk access to your laptop, nobody can use that key without the physical device in hand. Buy two — a primary and a backup registered everywhere the primary is, stored somewhere separate — because losing your only hardware key to a service with no other recovery path is a genuinely bad afternoon.
Password managers: the boring foundation under everything else
I'm not going to pretend there's one right answer between 1Password, Bitwarden, and the rest — pick one, pay for it if you can, and actually use it for everything, including internal service credentials, database passwords, and API tokens, not just website logins. The habit that matters most isn't the tool, it's never typing a password into anything your manager didn't autofill. That single behavior is what stops most phishing, because a manager tied to a specific origin simply won't offer to fill credentials on a lookalike domain, where a human skimming a URL might not notice the swapped letter.
1Password's CLI is genuinely useful for keeping secrets out of .env files entirely:
op run --env-file=.env.template -- npm run dev
Your .env.template holds references like DATABASE_URL=op://work/prod-db/connection-string instead of the actual secret, so the real value never sits on disk unencrypted and never accidentally gets committed.
Putting it together
None of this is exotic. It's an Ed25519 key per context, a locked-down SSH config, signed commits, a hardware key on your most important accounts, and a password manager you actually trust with everything, not just website logins. The whole setup takes maybe two hours once, and in my experience it's the two hours most likely to matter on the one day you really need it to.
Rolling this out beyond just yourself
The harder version of this problem is getting a whole team to actually do it, not just you. Individual discipline is easy to preach and hard to enforce, so the setups that actually stick are the ones baked into onboarding rather than left as a wiki page nobody reads. On the last team I helped set this up for, we added a first-day checklist item that blocked repo access until a new hire had generated an Ed25519 key, enabled GPG signing, and registered a hardware key, with a thirty-minute pairing session to walk through it rather than a document they were expected to follow alone. That pairing session mattered more than the checklist itself, because it's where people actually ask the questions they'd otherwise silently skip past, like whether it's safe to reuse a key from a previous employer, which the honest answer is no, generate a new one, always.
We also set branch protection rules requiring signed commits on our most sensitive repositories, which sounds heavy-handed until you remember that an unsigned commit slipping through review is indistinguishable from a legitimate one without that enforcement. GitHub makes this a checkbox under branch protection settings, and pairing it with required status checks meant nobody could accidentally merge an unsigned commit even with good intentions and a busy afternoon. None of this is about distrust of your teammates. It's about making the secure path the default path, so good security hygiene doesn't depend on everyone remembering to care every single day.
Related Posts
Sponsor Our Newsletter
Reach thousands of developers who are actively evaluating AI tools, MCP servers, and dev infrastructure. Our weekly newsletter goes to engaged technical decision-makers.
All sponsored content is clearly labeled per our editorial policy.