KHAGAN AYDIN

How to Build an Effective Application Security Program

You are the first security person a company has ever hired. There is no program, no tooling, and a backlog of applications nobody has ever looked at with security in mind. Where do you start?

· 4 min read

Not with a scanner. The instinct is to buy a tool and start finding bugs, and that instinct will bury you in findings nobody fixes while the real holes stay open. Your first job is not to find vulnerabilities. It is to see the system and learn who owns each piece, because you cannot secure what you cannot see, and you cannot fix anything without the people who own the code.

Step 1: Find out what exists, and who owns it

Before any testing, build two maps.

The inventory: every application, API and service that is running. What it does, what data it touches, whether it faces the internet, and where its code lives. Most first security hires are surprised by how much nobody could list from memory. The app you did not know about is the one that breaks you.

The people: a security program is built through them, not around them. Talk to your manager, for what “secure enough” means here and what the business actually fears. Talk to development leads, for how code gets written, reviewed and shipped today. Talk to DevOps or operations, for how it is deployed and who holds the keys. Talk to product owners, for which applications are critical and what compliance they carry.

Instead of a hundred-question checklist, start with the few questions that find fires fast:

  • What is exposed to the internet, and what sits behind a login?
  • Where does our most sensitive data live, and who can reach it?
  • How does a change get from a developer’s laptop to production, and who can push without review?
  • When did anything here last get a security test?
  • Do we know every third party and token that has access? That last one is the quiet killer, and I wrote a whole piece on it: Your GitHub Is a Vault, Not a Graveyard.

The answers tell you where to point everything that follows.

Step 2: Threat model before you test

With the map in hand, threat model the important systems: what are we building, what can go wrong, what will we do about it. This is the cheapest work in the whole program, because it finds design flaws before they are built, and a design flaw caught by a scanner is one you already paid to build. The method is in What is Threat Modeling.

Step 3: Secure design and secure defaults

The highest-leverage move is not catching bad code. It is making the safe way the easy way: shared libraries that handle authentication, input validation and output encoding correctly, so individual developers are not reinventing them, and misimplementing them, on every feature. A program that depends on every developer remembering every rule will lose. A program that bakes the rules into the defaults mostly wins on its own.

Step 4: Find bugs, in the right order

Two kinds of testing, and they complement each other.

SAST, static analysis, reads the source code for dangerous patterns before it runs: injection, hardcoded secrets, unsafe configuration. Tools like SonarQube, Semgrep or Snyk. It is fast and runs on every commit, but it cannot see runtime behavior and it produces false positives, so tune it or it becomes noise everyone learns to ignore.

DAST, dynamic analysis, attacks the running application the way a real attacker would, and finds what only shows up live: broken authentication, injection that actually fires, session problems. Tools like OWASP ZAP or Burp Suite.

Step 5: Put the testing in the pipeline, automatically

A scan you have to remember to run is a scan that stops happening. The win is making security testing part of the assembly line, your CI/CD pipeline, so it runs on every change with nobody remembering to start it.

Here is a concrete example with OWASP ZAP, which is free and open source, and GitHub Actions. Create a workflow file at .github/workflows/zap.yml:

name: ZAP Baseline Scan
on: [push]

jobs:
  zap_scan:
    runs-on: ubuntu-latest
    steps:
      - name: Check out code
        uses: actions/checkout@v4

      - name: Run ZAP baseline scan
        uses: zaproxy/action-baseline@v0.12.0
        with:
          target: 'https://staging.yourapp.com'
          fail_action: true

Two choices make this worth doing. on: [push] runs it automatically on every change, not when someone remembers. And fail_action: true makes a new security issue break the build, the same way a failing test does, so it gets attention now instead of sitting in a report nobody opens. Point it at staging, not production, and start with the baseline scan, which is quick and low-noise, before moving to fuller scans.

Catching an issue here is cheap. The same issue found after release is an incident. Automation is how you move the catch to the cheap end, every time, without heroics.

Step 6: Run it through the developers’ own workflow

Findings that live in a security tool get ignored. Findings that arrive as tickets in the system developers already use, with a clear fix and a named owner, get fixed. Put security work in the same backlog as everything else, Jira or whatever the team runs, prioritized honestly next to features. Security that lives outside the developers’ workflow is security that competes with their real work, and loses.

The point

A security program is not a pile of tools. It is a repeatable loop: know what you have, decide what can go wrong, build safe defaults, test automatically, and fix through the team that owns the code. The tools are interchangeable. The loop is the program.

None of the pieces are new. Inventory, least privilege, testing, fixing through the owners, this has been the job for decades. What makes a program effective is not a clever tool. It is making the secure path the default path, so security is what happens when everyone does their normal job, not an extra thing they have to remember. Build that, and you stop being the person who finds problems. You become the person who made them hard to create.