Long-Lived Credentials Are the Original Sin of CI/CD

A trick for building any pipeline one question at a time, why the build is the test at proof-of-concept stage, and the mistake almost every pipeline starts with.

September 4, 20264 min read3 / 4

Every pipeline I've ever built started the same confusing way: staring at an empty workflow file, not sure what the first step should even be.

The fix turned out to be smaller than I expected. Stop thinking about the whole pipeline. Ask one question at a time, and let each answer produce the next question.

Build the pipeline by asking, not designing

Try it on a plain Node project. What do you need first? Your source code, so the first step is checking it out. What do you need next? A JavaScript runtime, so the next step sets up Node. What comes after that? Your dependencies, so npm ci. And after that? The actual build.

YAML
steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npm run build

Four steps. Every Node project needs some version of this chain, whether it uses these exact actions or not. You don't have to design a pipeline from a blank page. You have to answer "what do I need before this step can run" until you run out of questions.

That chain of four is the same build job I wrote out fully in the proof-of-concept pipeline post, if you want the deploy half of it too.

The temptation, especially early on, is to imagine every edge case before writing a single line. Skip that. Get one working pipeline, watch the check go green, and only then start adding the parts that make it more robust.

The build is the test, for now

There's a version of this same restraint that applies to testing. If you're shipping a static site and you don't have a real test suite yet, that's fine. At proof-of-concept stage, a successful build is the test.

That sounds like an excuse until you think about what a build failure actually catches: a broken import, a missing dependency, a syntax error that would have crashed the page anyway. None of that requires a dedicated test suite to catch. It just requires the build to run.

Add real tests the moment the project needs them, which is usually the moment more than one person is relying on the pipeline being right. Until then, a green build is an honest signal, not a shortcut you have to apologize for.

Why a long-lived credential is the mistake, even when you know better

Here's the one place I'd stop being casual, even at proof-of-concept stage.

To deploy that build to S3, the pipeline needs AWS credentials. The fast path is an IAM user's access key, pasted straight into a GitHub repo secret as a long-lived credential with no expiration. It works. It's also, in my experience, the single most common mistake in CI/CD, common enough that I'd call it the original sin of the whole discipline.

A static access key doesn't expire on its own. Pasted into a repo secret, it authenticates forever, whether it's a week old or four years old. I've sat in security audits where a key that old turned up still valid, and nobody left on the team could say who created it.

That's the real danger. Not that the key might leak, but that it works exactly the same on day one and year four, so nothing ever forces you to look at it again. Anyone with access to that repository can see it, and if they can see it, they can use it, in the pipeline or outside of it.

A static IAM key doesn't get riskier over time, it just sits there working, which is the problem ExpandA static IAM key doesn't get riskier over time, it just sits there working, which is the problem

A static key in a repo secret is still a real, deliberate tradeoff for shipping fast at this stage. Just don't mistake "it's fine for now" for "it's fine." Build toward short-lived, per-run credentials the moment the project moves past proof-of-concept.

It's the same instinct behind rejecting a bad token before it ever reaches a Lambda function: the earlier a credential can be limited or checked, the less damage it can do if it ever ends up somewhere it shouldn't.

The Essentials

  1. Build a pipeline one question at a time. What do you need before this step can run? Answer that repeatedly and the pipeline designs itself.
  2. Every Node pipeline needs the same core chain. Checkout, runtime setup, install, build, regardless of which exact actions you use.
  3. A successful build is an acceptable test at proof-of-concept stage. It catches broken imports and syntax errors without needing a real test suite yet.
  4. The danger isn't that a static key might leak, it's that nothing ever forces you to notice it. No expiration means no natural checkpoint to question whether a four-year-old key should still exist.
  5. A static key at proof-of-concept stage is a deliberate tradeoff, not a free pass. Ship fast with it if you must, but build toward short-lived, per-run credentials the moment the project moves past stage one.

Further Reading and Watching