DevSecOps is often described as a pipeline: static analysis on every commit, dependency and container scanning, infrastructure checks, signed artifacts. Those gates matter, and an earlier post covers them. But a scanner can only find problems in code that already exists. The most expensive security issues we see in reviews are not coding mistakes; they are design decisions: an export that includes every customer's data, a password reset flow that leaks whether an account exists, an admin action without audit.

Those are cheapest to fix while a feature is still a ticket. This post describes the front half of a secure software development lifecycle (SDLC) as we run it with product teams: what happens before the first commit, and how to keep it light enough that teams actually do it.

The SDLC stages and where security fits

Stage Security activity Output
Plan Security requirements, data classification Requirements on the ticket
Design Abuse cases, design review, threat model for risky features Notes, decisions, tickets
Build Secure coding guidelines, review checklist Code
Test Security acceptance tests, scanners Passing tests and gates
Release Pipeline gates, sign-off for high-risk changes Deployed artifact
Operate Monitoring, incident response, vulnerability management Alerts, fixes

This post is about the first two rows.

Plan: security requirements on every relevant ticket

Most features need no special security work. Some do, and the trick is noticing which. We use a short set of trigger questions on the ticket template:

  • Does this feature create, read or export personal or payment data?
  • Does it add a new way to authenticate or change account recovery?
  • Does it add roles, permissions or admin actions?
  • Does it accept files, URLs or rich text from users?
  • Does it integrate a new third party that receives our data?
  • Does it change who can see what across tenants or customers?

If any answer is yes, the ticket gets a "security" label and security requirements written into it. They are concrete, testable statements, not principles:

Security requirements
- Only users with the "billing_admin" role can trigger the export.
- The export contains only the requester's tenant data.
- The download link expires after 24 hours and works once.
- The export action is recorded in the audit log with user, tenant and row count.

A requirement like "the export must be secure" is useless; nobody can test it.

Design: abuse cases

User stories describe what a legitimate user wants. Abuse cases describe what a malicious or careless user might try with the same feature. Writing three to five of them for a labelled ticket takes ten minutes:

Abuse case Expected behaviour
A support agent changes the tenant id in the export request Request rejected, attempt logged
A user shares the download link publicly Link expires, works once, requires login
An attacker triggers thousands of exports Rate limited per user and tenant
A removed employee uses an old session to export Session invalidated at offboarding
Export includes columns added later, such as tax ids Export uses an explicit column allow-list

Abuse cases turn into security acceptance tests later, and they often reveal requirements nobody had written down, like the last row.

Design: a lightweight review

For labelled features, the developer writes a short design note, one page at most, and a security champion or the security team reviews it. The note covers:

  • data flows: where data comes from, where it is stored, where it goes;
  • trust boundaries: browser, API, background jobs, third parties;
  • authentication and authorization decisions;
  • how the abuse cases are handled;
  • logging and monitoring.

The review is a 30-minute conversation, not an approval board. Its output is a list of decisions and tickets. For high-risk features (payment flows, authentication, cross-tenant data), we do a full threat model with STRIDE, which a follow-up post in this series walks through.

Security champions

The security team cannot review every feature. Each product team has a security champion: a developer with some extra training and a direct line to the security team. Champions review most labelled tickets themselves and escalate the risky ones. This scales, and it spreads security knowledge into teams.

Build and test: security acceptance criteria

Security requirements and abuse cases become acceptance criteria on the ticket and tests in the code:

def test_export_rejects_other_tenant(client, billing_admin_tenant_a):
    resp = client.post("/exports", json={"tenant_id": TENANT_B}, user=billing_admin_tenant_a)
    assert resp.status_code == 403
    assert AuditLog.objects.filter(action="export_denied", user=billing_admin_tenant_a).exists()

def test_export_link_works_once(client, billing_admin_tenant_a):
    link = create_export(client, billing_admin_tenant_a)
    assert client.get(link).status_code == 200
    assert client.get(link).status_code == 410

These tests are as important as functional ones. They are the evidence that the requirement holds, and they keep holding after refactoring.

Data classification as the starting point

Most trigger questions depend on knowing what data a feature touches. We keep a simple classification on our data model: each table and important field is tagged public, internal, confidential or restricted, in code next to the schema. Tickets that touch confidential or restricted fields are labelled automatically when a developer links the affected models. The classification also drives logging redaction and export rules, so it pays for itself several times.

Keeping it light

Teams resist processes that feel like bureaucracy. What made this work:

  • Only for labelled tickets. Most tickets need nothing extra.
  • Templates, not documents. Requirements, abuse cases and the design note are sections in the ticket.
  • Timeboxed reviews. Thirty minutes, scheduled within two days of request.
  • Visible value. We share examples where a design review prevented a real problem, anonymised, in the engineering newsletter.

Training

Champions get a half-day workshop on abuse cases and design review, and everyone else a short session on the trigger questions. Most of the skill is learned by doing: the first few reviews are run together with the security team, then champions run them alone.

Measuring

Two numbers tell us whether the front half of the SDLC works:

  • share of security findings from penetration tests and bug reports that a design review could have caught, which should fall over time;
  • median time from "security" label to completed review, which should stay under a few days.

In the first year, design-level findings in our annual penetration test dropped from seven to two.

Common mistakes

Mistake Effect Do this instead
Security only in the pipeline Design flaws reach production Requirements and abuse cases at planning
Vague requirements ("secure", "compliant") Nothing to test Concrete, testable statements
Every feature reviewed by security Bottleneck, rubber stamps Trigger questions plus champions
Reviews as approval gates Teams route around them Short conversations producing tickets
Abuse cases not turned into tests Protections regress silently Security acceptance tests

Key takeaways

  • The cheapest security fixes happen while a feature is a ticket.
  • Use trigger questions to find tickets that need security requirements.
  • Write abuse cases and turn them into security acceptance tests.
  • Keep design reviews short, frequent and handled mostly by security champions.
  • Save full threat models for the riskiest features.

References