Table of Contents
A signed application isn’t automatically a trusted one. I’ve reviewed enough broken release pipelines to know that teams often treat code signing as a checkbox, get the certificate, wire it into the build script, move on. Then a customer reports a SmartScreen warning, or a cert quietly expires mid-release, and nobody can say why.
Most of these problems trace back to a small set of repeatable mistakes. None of them require an attacker or a system failure. They come from ordinary gaps in how private keys, certificates, and signing workflows get handled day to day. Here’s where things usually go wrong, and what actually fixes each one.
Storing Private Keys on Local Machines
This is the mistake I see most often on smaller teams. A developer generates a certificate, exports the private key, and it ends up sitting in a folder on their laptop, sometimes unencrypted, sometimes backed up to a personal cloud drive without anyone realizing it.
The problem isn’t laziness. It’s that local storage is the path of least resistance when you’re trying to ship a build by Friday. But a private key on a laptop is only as secure as that laptop. If the device is lost, sold, or compromised, whoever has it can sign code as your organization.
The fix: move private keys into a hardware security module (HSM) or a cloud-based signing service that never exposes the raw key to any individual machine. Access should be tied to named accounts, not shared folders, and nobody outside the signing process should be able to export the key at all.
Letting Certificates Expire Mid-Release
Code signing certificates typically run one to three years. That’s a long enough window that renewal falls off people’s radar, especially on teams where the person who originally set up signing has since moved to a different project or left the company.
When a certificate lapses, any software signed after that date shows as unsigned or untrusted, even if nothing else about the release changed. For products with staggered rollout schedules or slow-moving enterprise customers, this can surface weeks after the actual expiration, which makes it harder to trace.
The fix: track certificate expiration the same way you’d track a domain renewal or an SSL cert — on a calendar, with alerts well before the deadline, owned by a specific person or team. Pair this with timestamping, covered next, so expiration doesn’t retroactively invalidate work you’ve already shipped.
Skipping Timestamping
Timestamping is the part of code signing that gets skipped most often, usually because it’s an extra step in the build process and teams don’t fully understand what it buys them. Here’s the short version: a timestamp records the exact moment your software was signed, independent of the certificate’s validity period.
Without it, the validity of a signature is tied entirely to whether the certificate is still active. Once the cert expires, every piece of software signed with it starts showing as untrusted, even versions that shipped years earlier and have been running fine on customer machines the whole time.
The fix: enable timestamping as a default step in your build pipeline, not an optional flag someone remembers to add. Most signing tools support it natively. Once it’s timestamped, a signature stays valid on its original signing date regardless of what happens to the certificate later.
Shipping With Self-Signed Certificates
Self-signed certificates have a real place in development, they’re free, fast to generate, and fine for internal testing where nobody outside your team is installing the build. The trouble starts when a self-signed cert makes it into a public release by accident.
Operating systems don’t trust self-signed certificates by default, because there’s no third-party verification behind them. A user installing your software sees a security warning, sometimes phrased in language alarming enough to make them abandon the install entirely. That’s lost trust and lost conversions in one step.
The fix: keep test-signing and release-signing completely separate. Test certificates can stay self-signed or come from an internal CA, but they should chain to a different root than your production certificate. That separation makes it structurally harder for a test build to slip out signed with the wrong certificate.
Reusing One Key Across Multiple Products or Teams
Using a single code signing key across every product line looks efficient on paper, one certificate, one renewal date, less to manage. It also means that if that one key is ever compromised, every product signed with it is compromised at the same time.
I’ve talked to teams who only realized how widely a key was used after a security review, well after it had been shared across three or four unrelated products for convenience. At that point, revoking it means re-signing everything, all at once, under time pressure.
The fix: segment signing keys by product line or business unit, and rotate them on a regular schedule rather than treating a certificate as a “set it and forget it” asset. This limits how much damage a single compromised key can do and makes revocation manageable if it ever comes to that.
Committing Certificate Files to Version Control
Certificate files (.pfx, .p12) occasionally end up committed to a Git repository, usually by accident, a developer testing locally, forgetting to add the file to .gitignore, and pushing it along with everything else. Once it’s in the commit history, it’s there permanently unless someone actively scrubs it.
Anyone with repository access, including former contractors or employees whose access wasn’t fully revoked, can pull that history and extract the certificate. If the private key is password-protected, that buys time, not safety, since offline brute-forcing is only a matter of resources.
The fix: add certificate file extensions to .gitignore from day one, and run secrets-scanning tools in CI to catch anything that slips through. If a certificate does end up in a repo, treat it as compromised and reissue, rather than assuming the password held.
No Access Controls or Audit Trail on Signing
On a lot of teams, whoever has access to the build server can sign a release. There’s no distinction between someone authorized to ship code and someone who simply has the right permissions on the machine where signing happens to run.
Without an audit trail, you also lose the ability to answer basic questions after the fact: who signed this build, when, and was it supposed to happen. That gap becomes a real problem the moment something unexpected ships, and there’s no record to work from.
The fix: apply role-based access control specifically to the signing step, separate from general build server permissions, and log every signing event, who initiated it, what was signed, and when. This doesn’t need to be complicated, but it does need to exist before you need it.
Frequently Asked Questions
What are the most common mistakes companies make with code signing certificates? The recurring ones are storing private keys on local machines, letting certificates expire without renewal tracking, skipping timestamping, and reusing a single key across multiple products. Most trace back to treating signing as a one-time setup rather than an ongoing process.
Why did my Windows app get flagged as untrusted even though it’s signed? This usually means the certificate has expired, the software was signed with a self-signed or untrusted certificate, or timestamping wasn’t applied before the certificate lapsed. Windows SmartScreen checks certificate validity at install time, not just whether a signature is present.
Is it safe to store a code signing private key on a developer’s laptop? No. A key stored locally is only as secure as that individual device, and it’s vulnerable if the laptop is lost, stolen, or compromised. Private keys should live in a hardware security module or a managed signing service instead.
What happens if a code signing certificate expires before software timestamping? Without a timestamp, the signature’s validity is tied entirely to the certificate. Once it expires, previously signed software starts showing as untrusted, even builds that shipped and ran fine long before the expiration date.
Should I use a self-signed certificate for production software releases? No. Self-signed certificates aren’t recognized by operating systems as trusted by default, which triggers security warnings for end users. They’re appropriate for internal testing, not for anything distributed publicly.
How do I prevent code signing private keys from being leaked in a Git repo? Add certificate file extensions to .gitignore before you start working with them, and run automated secrets-scanning in your CI pipeline. If a key does get committed, treat it as compromised and reissue rather than relying on its password.
What’s the difference between test signing and release signing certificates? Test-signing certificates are typically self-signed or issued by an internal CA and used only within development environments. Release-signing certificates come from a trusted public CA and should chain to a completely different root than test certificates.
How often should a code signing key be rotated? There’s no universal number, but a regular rotation schedule — commonly annually, or tied to certificate renewal — limits how much exposure a single compromised key creates. The right cadence depends on how many products and teams share that key.
What security risks come from reusing one code signing certificate across multiple products? If that certificate’s key is ever compromised, every product signed with it is exposed at the same time, and revoking it means re-signing everything simultaneously. Segmenting keys by product line contains the damage if one is ever compromised.
How can I tell if a code signing certificate was issued by a trusted certificate authority? Check the certificate details in the file properties or signature panel — a trusted CA will be listed as the issuer, and the chain will resolve to a recognized root certificate without warnings. Self-signed or internally issued certificates won’t show this chain.
Building Signing Into the Pipeline, Not Bolting It On
Every mistake here comes back to the same root cause: code signing gets treated as a setup task instead of a process that needs ongoing attention — renewal tracking, key segmentation, access control, audit logs. None of it is complicated on its own, but it adds up if nobody owns it.
The teams that avoid these problems aren’t necessarily more careful. They’ve just built signing into their release process as a standing responsibility, with clear ownership over keys, certificates, and access, rather than something that gets configured once and left alone.
Need a Code Signing Certificate?
Compare Comodo, Sectigo, and DigiCert code signing options starting at $226.10/yr.