<!-- mobian-agent-page publisher="dailydev" canonical="https://daily.dev/posts/the-pull-request-was-fine-production-was-not--jabyosdyx" -->

---
title: The Pull Request Was Fine. Production Was Not. | daily.dev
description: A reflection on how application security risk often lives outside the code diff itself, in configuration: expired Node runtimes, mismatched npm versions, loose...
canonical: https://daily.dev/posts/the-pull-request-was-fine-production-was-not--jabyosdyx
twitter:card: summary_large_image
twitter:site: @dailydotdev
og:type: website
og:site_name: daily.dev
og:title: The Pull Request Was Fine. Production Was Not. | daily.dev
og:description: A reflection on how application security risk often lives outside the code diff itself, in configuration: expired Node runtimes, mismatched npm versions, loose...
og:url: https://daily.dev/posts/the-pull-request-was-fine-production-was-not--jabyosdyx
og:image: https://api.daily.dev/og/posts/jAbYoSDyX.png
og:image:alt: The Pull Request Was Fine. Production Was Not.
og:image:width: 1200
og:image:height: 630
og:locale: en
---

> ## Documentation Index
> Fetch the complete documentation index at: https://daily.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# The Pull Request Was Fine. Production Was Not.

**[Application Security Blog](https://daily.dev/sources/applicationsecurityblog)** · [@przemyslaw91](https://daily.dev/przemyslaw91) · 10 min read · 0 upvotes · 0 comments

## Summary

A reflection on how application security risk often lives outside the code diff itself, in configuration: expired Node runtimes, mismatched npm versions, loose CORS rules, and sensitive AWS values leaking into frontend environment variables. Code review only sees the diff, not runtime versions, CI/CD tooling, or production settings that drift over time. Draws on OWASP's Security Misconfiguration category to argue for treating configuration as part of the application, preferring allowlists over blacklists for things like environment variables and CORS origins, and using automated checks judiciously since noisy SAST output trains developers to ignore warnings. Ends with a question to readers about publishing cadence and format.

## Content

For a long time, I treated configuration as something around the application rather than part of it.

The code was the main thing. Configuration was what helped that code run somewhere else: environment variables, CI/CD settings, Node versions, CORS rules, deployment settings.

That view worked well enough when I was thinking mostly as a frontend developer.

But after spending more time on application security, I started noticing how often the code is not actually the part causing trouble.

I have worked on projects where nothing was obviously wrong with the implementation, but CI/CD would still refuse to let the application through. Sometimes the Node version had reached end of life. On older projects, a newer npm version could also introduce problems and we had to explicitly force an older version that the project still supported.

None of that means the code suddenly became vulnerable.

But it does show something important: the application does not exist in isolation from the environment running it.

CORS is another example I have seen many times. Locally everything works, then another environment behaves differently because the allowed origins are configured differently.

And recently I came across something more directly security-related: sensitive AWS-related values being used in environment configuration.

That one made the point much clearer.

A pull request can be completely fine while something around it creates the real risk.

## Everything looked good in review

Code review gives us a very specific view of an application.

We look at what changed, whether the logic makes sense, whether tests cover it and whether anything obviously dangerous has been introduced.

Most of the time, that is exactly what we should be doing.

But it also means we naturally focus on the diff.

A runtime version reaching end of life might not be visible there.

The npm version used by a pipeline might not be visible there either, depending on how the project is set up.

The production environment can have different variables from testing. Someone may have changed a setting outside the repository months ago.

The PR can still look perfectly healthy.

I have seen older projects hit this kind of problem quite a few times. You make a small code change, everything works locally, then the pipeline fails because the surrounding tooling has moved on while the project has not.

At first this feels like an annoying build problem.

From an AppSec point of view, though, it raises another question: if an application depends on an unsupported runtime or tooling nobody has reviewed for a long time, how confident are we about the environment around that application?

The build failure is sometimes the first visible sign that something has been neglected.

## Then production changes the rules

![418d306d-0079-4ccd-8067-77d430d2221d.png](https://media.daily.dev/image/upload/s--MsyPZo4H--/f_auto/v1786532553/ugc/content_44d07bba-f52d-4326-b0c7-fc5eaceb02bf?_a=BAMAMicg0)

Local development is forgiving.

We usually have a setup that has been made convenient enough to let us work.

An API might allow localhost.

A developer environment may expose more debugging information.

Some restrictions may be relaxed because otherwise everyone spends half the day fighting their own machine.

There is nothing automatically wrong with that.

Problems start when the same assumptions travel further than they should.

CORS is probably one of the easiest examples to recognise as a frontend developer.

You call an API, the browser blocks the request, and someone needs to update the allowed origin.

The fastest solution during development can easily become something too permissive.

That does not necessarily break anything.

Actually, that is part of the problem. A loose CORS configuration can make the application work perfectly well from a functional point of view.

The browser stops complaining. The ticket gets closed. Everyone moves on.

Security issues caused by configuration often look like this. The application behaves exactly as somebody configured it to behave.

There is no red error banner telling you the trust boundary is wider than it needs to be.

## Configuration is part of the application too

I have always understood the basic rules around `.env` files.

Do not commit sensitive values.

Keep configuration centralised.

Do not expose things publicly if they are meant to stay private.

That part was never particularly surprising.

What changed for me was thinking less about the file itself and more about where the value eventually goes.

Recently, I saw sensitive AWS-related information being placed in environment configuration.

The fact that it was inside `.env` was not really the important part.

The important part was whether that value stayed in a trusted server-side environment or eventually made its way into something delivered to a user.

Frontend projects make that distinction especially important.

A variable can look private because it lives outside the source code and gets injected during a build. But if the frontend needs that value at runtime, the finished JavaScript still has to reach the browser.

At that point it is not secret anymore.

This is one of those things that sounds obvious once somebody explains it, but the terminology makes it easy to miss.

Backend environment variable and frontend environment variable can look almost identical in a project.

Their security properties are completely different.

That is why I now treat configuration as part of the application itself, not just something used to deploy it.

## Small settings can create big problems

![ba43069e-0d8e-4a79-b3f5-108914a21ec5.png](https://media.daily.dev/image/upload/s--ME5Wr70C--/f_auto/v1786532569/ugc/content_4009654c-42e0-4f83-98cf-93f2487964f5?_a=BAMAMicg0)

Security Misconfiguration is not the most dramatic name in the OWASP Top 10.

It sounds like someone forgot to set a flag correctly.

In reality, configuration controls a surprising amount of application behaviour.

CORS decides which origins are allowed to interact with the application from a browser.

Debug settings can change how much internal information an error exposes.

Runtime versions decide which platform and security updates the application can receive.

Environment configuration decides which values exist in each environment and, sometimes, which values accidentally reach the client.

There are also things developers may barely interact with directly: default accounts, unnecessary services, server configuration, cloud permissions or features that were enabled once and never reviewed again.

What makes these issues difficult is that none of them need to make the application look broken.

A feature can behave correctly while the environment around it is more permissive than intended.

That makes misconfiguration easy to ignore during normal feature development because there is usually something more visible demanding attention.

## Code review only sees part of the picture

This is where I think automated checks can help, although I have become a bit cautious about describing automation as the answer to everything.

I am currently involved with an internal security package that is still being tested by developers.

Part of the work includes SAST checks.

The interesting lesson so far is that tools can find plenty of things without necessarily finding many useful things.

A lot of SAST results in normal projects are simply noise.

A rule matches something. You look at it. There is technically a pattern there, but the actual context makes it harmless or not worth blocking a build over.

That does not make SAST useless.

It means the rules need judgement.

I think configuration checks are the same.

If a security tool complains about everything slightly unusual, developers eventually stop treating the output as important. Worse, they may start looking for ways to bypass it because the tool gets in the way of normal work.

For me, the useful question is not just:

"Can we detect this automatically?"

It is also:

"Is this reliable enough that we should stop a developer because of it?"

There is a big difference between an informational warning and something that should actually block CI/CD.

The tool should help developers notice risks, not train them to ignore security output.

## I used to think in blacklists

![dc5aea12-d02f-464f-b50f-86281595ad08.png](https://media.daily.dev/image/upload/s--K1Jqr_H2--/f_auto/v1786532587/ugc/content_576d4c17-2e05-431f-b0ae-5463dd13a80a?_a=BAMAMicg0)

One part of my thinking has definitely changed while learning more about AppSec.

I used to prefer blacklists in a lot of situations because they are convenient.

If something is bad, block it.

Everything else continues to work.

As a developer, that is attractive because it creates less friction and you do not need to think about every valid case in advance.

The weakness becomes obvious when you ask what happens with something you did not know to blacklist.

An allowlist starts from the opposite direction.

Instead of trying to recognise everything dangerous, you define what you actually expect.

That makes more sense to me now in places where the allowed behaviour is reasonably small and predictable.

Environment variables are a good example.

Rather than trying to detect every suspicious key name that someone might introduce, it can be much cleaner to say which variables are expected in a frontend project.

If a new one appears outside that list, somebody should at least look at it.

The same idea works in other places too.

CORS origins are easier to reason about when you know which domains should be there.

Permissions are easier to review when you define what the application actually needs.

This does not mean allowlists are always the answer. Sometimes they are impractical.

But I no longer see the extra restriction as unnecessary inconvenience by default.

Sometimes making the allowed behaviour explicit is exactly what makes the system easier to understand.

## The same code can behave very differently

This is probably the part of A02 that matters most to me.

We tend to talk about releases as if the code defines the application.

Version X went to production.

Commit Y is deployed.

Technically that is true, but it is incomplete.

The same build can sit behind a different CORS policy.

It can run with different environment variables.

It can run on a different Node version.

It can expose different error information depending on production settings.

Someone can also change something manually after deployment.

At that point, comparing Git commits will not tell you the whole story.

That is why configuration drift can become such a difficult problem.

Development, testing and production slowly stop behaving like versions of the same application.

Usually nobody makes one huge change that causes this.

It is a collection of small fixes, exceptions and "we'll sort this later" decisions.

Most of them make sense individually.

Months later, nobody is completely sure why the environment looks the way it does.

## Security does not stop when the code is merged

![27ee174f-0289-47b5-a645-6203a5a83322.png](https://media.daily.dev/image/upload/s--WXdxMZ-t--/f_auto/v1786532756/ugc/content_b65e2872-3f59-4eaf-beb7-8087527b9469?_a=BAMAMicg0)

I do not think the solution is expecting every developer to become an expert in every part of infrastructure.

That would just replace one unrealistic expectation with another.

What seems much more useful is making important configuration predictable.

Projects should define the runtime versions they support.

Frontend projects should know which environment variables are expected.

Security-sensitive configuration should not depend entirely on somebody remembering what was agreed six months ago.

Checks that are reliable enough should run automatically.

And if somebody needs to make an exception, it should be obvious that an exception exists and why.

That is where I think tooling becomes genuinely useful.

Not when it tries to make every security decision for us, but when it removes the need to repeatedly remember the same basic rules.

Before spending more time on AppSec, I mostly asked whether the code itself was doing something unsafe.

I still ask that.

But now there is another question I find just as useful:

**What has to be true around this code for it to remain safe?**

Because sometimes the pull request really is fine.

The problem starts after that.

**Would you like to see articles like this more regularly?**
I am thinking about publishing the longer versions on Medium and sharing the links here. That would make it realistic for me to publish one or two articles a week.

Would that work for you, or would you rather see the full articles directly here?

## Similar posts on daily.dev

- [Not a Security Issue](https://daily.dev/posts/not-a-security-issue-uf7fwvv04) · Andrew Nesbitt · 0 upvotes · 0 comments
- [Why "It Worked on My Machine" Still Happens in 2026](https://daily.dev/posts/why-it-worked-on-my-machine-still-happens-in-2026-wu83ivzac) · freeCodeCamp · 49 upvotes · 2 comments
- [Continuous PR Security Review — ProjectDiscovery Blog](https://daily.dev/posts/continuous-pr-security-review-projectdiscovery-blog-t4wi10gzt) · ProjectDiscovery Blog · 0 upvotes · 0 comments
- [How to secure full-stack projects from NPM attacks](https://daily.dev/posts/how-to-secure-full-stack-projects-from-npm-attacks-p2w9ugiyl) · LogRocket · 0 upvotes · 0 comments
- [Preventing secret leakage in CI/CD environments](https://daily.dev/posts/preventing-secret-leakage-in-ci-cd-environments-tveiielvq) · Security Boulevard · 2 upvotes · 0 comments

---

Tags: [#security](https://daily.dev/tags/security), [#appsec](https://daily.dev/tags/appsec)

[View this post on daily.dev](https://daily.dev/posts/the-pull-request-was-fine-production-was-not--jabyosdyx)

```json
{"@context":"https://schema.org","@graph":[{"@type":"Organization","@id":"https://daily.dev/#organization","name":"daily.dev","url":"https://daily.dev","logo":{"@type":"ImageObject","url":"https://daily.dev/apple-touch-icon.png","width":180,"height":180},"sameAs":["https://twitter.com/dailydotdev","https://github.com/dailydotdev","https://www.linkedin.com/company/daily-dev-ltd"]},{"@type":"WebSite","@id":"https://daily.dev/#website","url":"https://daily.dev","name":"daily.dev","publisher":{"@id":"https://daily.dev/#organization"},"potentialAction":{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https://daily.dev/search?q={search_term_string}"},"query-input":"required name=search_term_string"}}]}
{"@context":"https://schema.org","@type":"DiscussionForumPosting","mainEntityOfPage":"https://daily.dev/posts/the-pull-request-was-fine-production-was-not--jabyosdyx","headline":"The Pull Request Was Fine. Production Was Not.","text":"A reflection on how application security risk often lives outside the code diff itself, in configuration: expired Node runtimes, mismatched npm versions, loose CORS rules, and sensitive AWS values leaking into frontend environment variables. Code review only sees the diff, not runtime versions, CI/CD tooling, or production settings that drift over time. Draws on OWASP's Security Misconfiguration category to argue for treating configuration as part of the application, preferring allowlists over blacklists for things like environment variables and CORS origins, and using automated checks judiciously since noisy SAST output trains developers to ignore warnings. Ends with a question to readers about publishing cadence and format.","url":"https://daily.dev/posts/the-pull-request-was-fine-production-was-not--jabyosdyx","datePublished":"2026-08-12T11:06:43.636Z","dateModified":"2026-08-12T11:07:12.009Z","author":{"@type":"Person","name":"Przemyslaw","url":"https://daily.dev/przemyslaw91","image":"https://media.daily.dev/image/upload/s--tcy6XTmy--/f_auto/v1778578633/avatars/avatar_zIjg9D2aXRpPyx4SnlnEv?_a=BAMAMiWQ0","interactionStatistic":{"@type":"InteractionCounter","interactionType":{"@type":"EndorseAction"},"userInteractionCount":520}},"image":"https://media.daily.dev/image/upload/s--lfh44gSb--/f_auto/v1786532804/posts/jAbYoSDyX?_a=BAMAMicg0","interactionStatistic":[{"@type":"InteractionCounter","interactionType":{"@type":"LikeAction"},"userInteractionCount":0},{"@type":"InteractionCounter","interactionType":{"@type":"CommentAction"},"userInteractionCount":0}],"isPartOf":{"@type":"WebPage","url":"https://daily.dev/squads/applicationsecurityblog","name":"Application Security Blog"}}
{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://daily.dev"},{"@type":"ListItem","position":2,"name":"Application Security Blog","item":"https://daily.dev/squads/applicationsecurityblog"},{"@type":"ListItem","position":3,"name":"The Pull Request Was Fine. Production Was Not."}]}
```

