← Back to home

Case study

We point the pipeline at ourselves first

Before we sell a security-first sprint, we run the exact same toolchain against our own production build — a payments-enabled booking marketplace. Here's what it found, what was already right, and what we fixed. Reports and commits included.

0
Critical issues
4 → 0
Container misconfigurations
0
Real secrets leaked
100%
Findings fixed or triaged

The build

A payments-enabled booking marketplace

Two-sided marketplace: OTP phone login, service scheduling, Razorpay payments, real-time booking updates, and an admin back office. Built on the sprint's default stack — Next.js and NestJS in TypeScript, PostgreSQL and Redis, Docker behind nginx. Built fast, then taken through the security pipeline. This case study is the "then."

  • Next.js
  • NestJS
  • TypeScript
  • PostgreSQL
  • Redis
  • TypeORM
  • Docker
  • nginx
  • GitHub Actions
  • Razorpay

The pipeline

Seven scanners, one command

The same suite that ships in every sprint — wired into GitHub Actions so it re-runs on every push, not just once.

SAST

Semgrep

Vulnerable code patterns — injection, unsafe auth logic — on every commit.

Secrets

Gitleaks

Scans the full git history for keys and credentials, not just the latest commit.

Dependencies

npm audit

Known-vulnerable packages across both frontend and backend.

Filesystem & IaC

Trivy fs + config

CVEs, secrets, and Dockerfile / compose misconfigurations.

Images

Trivy image

Scans the actual container images that would ship to production.

Running app (DAST)

OWASP ZAP

Probes the live staging app the way an attacker would.

Where things ran: Gitleaks, Trivy (filesystem + config), and npm audit ran directly against the code. Semgrep, the Trivy image scan, and the ZAP baseline run on the Linux CI runner — wired into the pipeline rather than run by hand, so they fire on every future push too.

What the scan found

Every finding, shown — not summarized away

  • High

    Containers ran as root — fixed

    Both the frontend and backend Docker images ran as root. If an attacker ever got code execution inside a container, root widened the blast radius. Now both drop to an unprivileged user; the images re-scan clean.

  • Low

    No container health checks — fixed

    Neither image told the orchestrator whether it was actually healthy. Both now probe a real endpoint, so a hung container gets restarted instead of quietly serving errors.

  • Medium

    Two transitive dependency CVEs — fixed

    Two frontend packages pulled in indirectly by the framework carried moderate advisories. Closed by pinning patched transitive versions — not the one-click "fix" the tooling suggests, which is actually a downgrade. Verified: the dependency audit now reports zero, and the app still builds and type-checks clean.

  • Triaged

    One flagged "secret" — a false positive

    The secrets scanner flagged a token in a file. A human checked it: a placeholder inside a framework's boilerplate README, not a credential. We allowlisted it with the reasoning written down — so the scan stays green and the judgment is auditable.

  • Clean

    No critical issues, no real secrets

    Backend dependencies: zero known vulnerabilities. No live credentials in the code or anywhere in the git history. The baseline was healthy before we touched it.

The fix, measured

Container hardening: 4 findings to zero

4

misconfigurations
(2 high, 2 low)

0

both images
scan clean

Verified by re-running the same scanner against the fixed Dockerfiles. The fix, the re-scan, and the write-up all live in the repository as a single reviewable change — found and closed in the same sitting.

What was already right

The controls a scanner can't add for you

01

Auth built to resist abuse

OTP login rate-limited to three attempts a minute, refresh tokens rotated and stored only as hashes — a database dump yields nothing usable.

02

Every input validated

A global validation layer strips unknown fields and enforces types on every request, so malformed or injected payloads never reach the logic.

03

Headers and CORS locked down

Security headers on by default; cross-origin access restricted to an explicit allowlist rather than left open.

04

Fail-closed in production

The app refuses to boot in production without its secrets configured — insecure defaults can't reach production by accident. And no raw SQL anywhere: the ORM parameterizes every query.

The honest part

What this is — and what it isn't

This is a static, dependency, and secrets baseline plus a running-app scan — not a penetration test. A baseline scan and a pentest catch different things; if a project needs the latter for compliance or funding due-diligence, we say so and refer it to a specialist. That honesty is the point.

"Security isn't an audit we ran once — it's a pipeline the app lives in."

Every finding above is now closed — the container hardening, the dependency CVEs, and the triaged false positive — and the whole pipeline runs green on every commit. But the point isn't a one-time clean report: the same scanners keep running, so new advisories and hardening get caught and tracked as the app evolves. Load-testing and production monitoring are part of the full sprint; this case study is scoped to the security pipeline.

Your turn

Want this run on your app?

The same pipeline, the same reports, the same honest triage — on a fully functioning app shipped in seven days. Book a free 30-minute scoping call and we'll tell you whether your idea fits.

Book a free scoping call