Skip to content

Security

The question: what can someone do here that they should not?

Security testing splits cleanly into two halves that are usually confused. One half is mechanical, cheap, and belongs in the pipeline on every build. The other is human, expensive, scoped, and belongs in a campaign with a report and a deadline.

Mixing them produces the familiar failure: a penetration test once a year, whose findings are mostly outdated dependencies that a scanner would have caught daily for free, and whose actual insights arrive too late to influence anything.

Runs on every build. Fails it, for the categories that are unambiguous.

Dependency scanning (SCA). Known CVEs in the libraries and base images shipped. Highest value per unit of effort in this entire section, because most real-world compromises come through a dependency nobody chose deliberately. Fails the build on critical and high, with a documented, expiring exception process — an exception that never expires is a policy that does not exist.

Static analysis (SAST). Injection, unsafe deserialisation, weak crypto, hard-coded credentials, path traversal. High false-positive rates, so it fails only on high-confidence rules and reports the rest. A tool that cries wolf gets turned off, and a tool that is off is worse than one that was never adopted.

Secret scanning. Credentials in the repository, in the history, in CI logs, in container layers. Pre-commit and pipeline: pre-commit prevents, the pipeline catches what came from a machine without the hook.

Container and IaC scanning. Base image CVEs, containers running as root, privileged flags, exposed ports, permissive security groups and buckets. Cheap, mechanical, and the source of a large share of real exposure.

Dynamic scanning (DAST). An automated crawl against a deployed candidate for missing headers, TLS configuration, obvious injection points. Finds shallow issues that the static tools miss because they only exist once assembled.

Automation finds known classes of flaw. It does not find broken logic, which is where the expensive breaches come from.

Authorisation logic. Can user A read user B’s order by changing an identifier? Can a customer call the admin endpoint? Does the check happen on every path, or only on the one the UI uses? This is consistently the most commonly found and most damaging category, and no scanner detects it — the request is perfectly well formed, and the server answers it correctly, for the wrong person.

Business logic abuse. Applying the same discount twice. A negative quantity. Racing two withdrawals. Skipping a step in a checkout flow and arriving at confirmation.

Chained weaknesses. Three findings that are each low severity and together allow account takeover. A scanner reports three lows; a person reports the chain.

Worth noting: the first category is testable by the team, in the ordinary suite. “User B cannot read user A’s order” is an integration or component test, it costs almost nothing, and writing it puts authorisation into the same place as every other rule — which is what shift-left accountability means here.

Doing this at design time is worth more than any of the above, and it takes an hour. Four questions on a whiteboard:

  1. What are we building? (a diagram, roughly)
  2. What can go wrong? (STRIDE, or just: who would want to attack this and how?)
  3. What are we doing about it?
  4. Did we do a good enough job?

The output is a short list of threats with mitigations, and each mitigation is a requirement with an owner and a test — which is quality-driven development applied to security.

A scoped penetration test per major release, or on any change to authentication, authorisation, payment or personal data handling. Scoped means: a stated target, stated rules of engagement, a stated environment, a report with severities, and an owner for each finding with a date. An unowned finding is a finding that will be inherited.

Pipeline checks against the build and the container. DAST against a deployed candidate. Human testing against a production-like environment with production-like data — masked, never real, because a penetration test against real personal data is itself a data exposure. See test data.

See security tooling.