Why developers run security scans on every code change
This guide is for non-technical clients who want to understand why a software team checks every code change for security issues before release. You will see where these scans fit in delivery, what they catch, what they miss, and how to judge whether the time and cost are worth it for your product.
TL;DR — Your developers run security scans on every change because the cheapest time to catch a security problem is before it reaches production. In practice, the most important takeaway is this: small automated checks in the delivery pipeline stop risky code, vulnerable packages, and leaked secrets from quietly shipping. Reading time: ~7 min
What it is and where it sits
"Security scans on every change" means automated checks that run whenever a developer opens a pull request (a proposed code change) or pushes a commit (a saved change in version control). These checks look for common security problems before the code is merged and deployed.
In a typical software delivery setup, the scan lives in the CI/CD pipeline (the automated build, test, and deploy process). It usually runs alongside unit tests and code quality checks.
What talks to it:
- The source control system, such as GitHub, GitLab, or Bitbucket, triggers the scan when code changes.
- The CI system downloads the code and runs scanning tools.
- The scanner may also read package files like
package-lock.json,requirements.txt, orpom.xmlto inspect third-party dependencies (outside libraries your app uses). - The results are sent back to the pull request as pass/fail checks, comments, or a report in the CI dashboard.
What it replaces:
- It does not replace a full security review, penetration test (an authorized attack simulation), or secure architecture work.
- It does replace a lot of manual, inconsistent checking like "someone should remember to look for hardcoded passwords" or "we'll review package vulnerabilities before launch."
Where it lives in the request/data flow:
- It is not in the live customer request path like a firewall or web server.
- It sits earlier, in the software change path, before the code ever reaches production.
View as text
[Developer] ---- 1. push / open PR ----> [Git Repository] [Git Repository] ---- 2. trigger pipeline ----> [CI/CD] [CI/CD] ---- 3. run tests + scans ----> [Security Scanners] [Security Scanners] ---- 4. findings ----> [CI/CD] [CI/CD] ---- 5. pass/fail status ----> [Pull Request] [CI/CD] | v [Deploy to staging/production]
The important architecture point is this: these scans are a gate. They sit between "developer changed code" and "customers can use it." That is why agencies insist on them even for small changes. A one-line change can still introduce a secret key, a vulnerable package, or a dangerous database query.
How it actually works
There are several kinds of scans, but the mechanism is straightforward:
- A code change appears.
- The pipeline starts automatically.
- Different scanners inspect different things.
- The pipeline compares findings to a policy.
- The change either passes, fails, or requires review.
Common scan types you will hear about:
- SAST (static application security testing): reads source code without running it, looking for risky patterns.
- Dependency scanning: checks third-party packages against known vulnerability databases.
- Secret scanning: looks for API keys, passwords, tokens, and certificates accidentally committed to the repo.
- IaC scanning (infrastructure as code): checks cloud config files like Terraform or Kubernetes manifests for unsafe settings.
One realistic end-to-end example
Imagine your team maintains a customer portal written in Node.js. A developer adds a feature to export invoices and also installs a CSV library from the package registry.
Step 1: The developer opens a pull request.
- In the repository UI, they click New pull request or Create merge request depending on the platform.
- That action triggers the CI pipeline automatically.
Step 2: The CI system checks out the branch and installs dependencies.
- It runs the normal build and tests.
- Then it runs security jobs.
Step 3: Dependency scanning inspects package-lock.json.
- It sees the new CSV library and its sub-dependencies.
- One sub-dependency has a known high-severity vulnerability: a regular expression denial of service (a crafted input that can consume excessive CPU).
- The scanner compares package versions against public advisories.
Step 4: Secret scanning inspects changed files.
- It notices a line in a config file that looks like a live API token copied in during testing.
- This is a common real-world mistake.
Step 5: SAST inspects the new export endpoint.
- It sees user input being inserted into a SQL query string directly instead of using parameterized queries (values passed separately so the database treats them as data, not executable SQL).
- That can lead to SQL injection.
Step 6: The pipeline posts results back to the pull request.
- Status check: failed.
- Comment or report: lists the vulnerable package, the leaked token pattern, and the risky query line.
Step 7: The developer fixes the issues.
- They upgrade the package to a safe version.
- They delete the token, rotate it in the provider dashboard, and move the value into the app's secret store or environment variable settings.
- They rewrite the query using placeholders.
Step 8: The pipeline reruns automatically.
- This time the scans pass.
- The pull request can be reviewed and merged.
Without these scans, all three issues could have shipped. One would create a performance/security risk, one would expose credentials, and one could become a direct data breach path. The agency is not scanning every change because they distrust developers; they are scanning because humans are busy and modern software includes too many moving parts to inspect manually every time.
When to use it (and when not to)
For agency-built software in active development, the default answer is: use it. The real decision is how strict to be and which scans should block a merge.
| Scenario | Recommendation |
|---|---|
| Customer-facing web app with logins, payments, or personal data | Run security scans on every pull request and block merges on high-severity findings |
| Internal business app with a small user base but internet exposure | Run on every change; at minimum block secrets and critical dependency issues |
| API used by mobile apps or partners | Run on every change; add dependency, secret, and IaC scanning |
| Static marketing site with no backend and no user data | You may use lighter checks; dependency and secret scanning are still worthwhile |
| Prototype or throwaway proof of concept that will never hold real data | You probably do not need a full scanning stack, but secret scanning is still cheap insurance |
| Legacy app with thousands of existing findings | Start with scans on every change, but fail only on new critical/high issues until the backlog is reduced |
You probably don't need a heavy, blocking setup if:
- The code is not deployed anywhere public.
- It contains no real customer data, credentials, or business-critical logic.
- The repository is effectively archived and not changing.
Even then, one exception is worth calling out: secret scanning is almost always worth it. Accidentally committing a cloud key or payment token can create immediate financial and security damage.
Trade-offs
Security scanning is useful, but it is not free.
| Benefit | What it costs |
|---|---|
| Catches issues before release, when fixes are cheapest | Longer pipeline times; developers wait a bit more for feedback |
| Creates consistent enforcement instead of relying on memory | Initial setup effort to tune rules and avoid noisy alerts |
| Finds vulnerable third-party packages automatically | Ongoing maintenance when advisories change or safe upgrades require code work |
| Prevents secret leaks from being merged | Occasional false positives on test data or example strings |
| Gives customers and auditors evidence of secure delivery practices | Process overhead: someone must review findings, document exceptions, and maintain policy |
| Reduces the chance of emergency security fixes in production | Some teams become over-reliant on tools and neglect architecture review or manual testing |
Two trade-offs matter most in practice:
Noise vs. protection
If the scanner flags everything, people start ignoring it. Good teams tune the rules so the pipeline blocks the issues that truly matter: leaked secrets, known exploitable dependencies, and clear high-confidence code risks.
Speed vs. safety
Running more scans catches more problems, but also slows delivery. The usual compromise is:
- fast scans on every pull request,
- deeper scans nightly or before release,
- manual review for unusual or high-risk changes.
In practice
Below are two examples a team could adapt today. These are generic CI examples, not tied to one vendor's paid features.
Example 1: Secret scanning in a CI job with Gitleaks
name: security-checks
on:
pull_request:
push:
branches: [main]
jobs:
secret-scan:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
What it does: this runs secret scanning on every pull request and on pushes to main. If the scanner finds something that looks like a real token or password, the job fails and the change cannot pass required checks.
Gotcha: scanners often find old leaked secrets already in Git history. Fixing that may require both removing the secret from current files and rotating the credential in the provider dashboard, because deleting it from code does not make the old key safe again.
Example 2: Dependency scanning with npm audit in CI
name: dependency-audit
on:
pull_request:
jobs:
audit:
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Audit production dependencies
run: npm audit --omit=dev --audit-level=high
What it does: this installs the exact locked package versions and fails the job if a high-severity or critical vulnerability is found in production dependencies.
Gotcha: npm audit can report issues in transitive packages (packages of packages) that your team did not choose directly. The fix is often to upgrade the parent package, not edit the nested package manually.
Optional local command before opening a pull request
If developers want to catch issues before the CI system does, they can run the same checks locally.
npm audit --omit=dev --audit-level=high
gitleaks dir .
What it does: the first command checks Node.js dependencies for serious known vulnerabilities; the second scans the current directory for likely secrets.
Gotcha: local results can differ from CI if the developer has uncommitted files or a different lockfile state. The CI result is the one that matters for merge approval.
⚠️ If a scan finds a real secret such as a cloud access key, do not just delete the line and move on. First rotate or revoke the credential in your provider's dashboard (for example, in your cloud or payment provider's API key settings), then remove it from code, then rerun the pipeline. A leaked key may already have been copied.
Further reading
- OWASP Top 10
- OWASP ASVS
- NIST Secure Software Development Framework (SSDF)
- GitHub Docs: "About status checks"
- The "Supply chain security" section of Google's SRE and software delivery guidance
This article was written by an AI system and published pending human review. Verify anything you intend to act on.
Have a project in mind?
Get an instant AI price estimate for it, or talk directly to our team.
One email a month on what we learn building with AI