Training · Intermediate

Closing Vulnerabilities for Good

Test-driven security: every vulnerability becomes a test, even when a coding agent helps to find and fix it

Find vulnerabilities through capture the flag, pin them down as a PHPUnit test, and close them for good. A coding agent helps you find and fix them, and the test decides whether the hole is really closed.

  • Scope 4 sessions of 2 hours
  • Format Live via Zoom
  • Language German
  • Price per person €799

What it is about

In many projects, security is a one-off audit: checked once, fixed once, quickly forgotten. This training starts from a different thesis: a security vulnerability is a missing test.

You work in Spatium, the booking software of a fictitious coworking space with two locations: room search, reservations, day passes, vouchers, invoices, and an administration area for the front desk. A small team wrote it in plain PHP, without a framework, feature by feature and each with tests that show that it works. There are no tests showing that it works only that way. Its vulnerabilities look like real development mistakes, forgotten escaping, string concatenation in SQL, a missing authorisation check, and no framework hides them. The Common Weakness Enumeration (CWE) provides the structure: every class of vulnerability becomes a station, and at each station you go through the same cycle.

  • Capture the flag. You find and exploit the vulnerability by hand, to really understand the attack vector.
  • Reproduce. You express the security requirement as a PHPUnit test that is red against the vulnerable code.
  • Fix and secure. You change the code until the test is green, and the test stays in your test suite as regression protection.

The “flag” here is not proof of a break-in, but a green test that keeps the same vulnerability from coming back, even when a coding agent would build it in again during the next refactoring.

From the second session on, a coding agent helps you. It searches code for suspicious places, drafts the red test, and proposes the fix. That saves time, but an agent also reports vulnerabilities that are not there, misses real ones, and sometimes repairs only the one example instead of the cause. You learn where it helps and where it does not, and how to check its result: you have understood the attack yourself, and the test is the referee.

How you work

Every session follows the same rhythm. First you see the cycle demonstrated at one station, from the exploit through the red test to the fix, together with the principle behind the solution. Then you go through it yourself at the other stations of the session, and at the end you discuss which test level you chose and what a scanner, static analysis, or the agent would have found. Because you can solve each station independently of the others, you move on to the next one when you finish early.

The agent only joins in the second session, on purpose: if you have not traced the attack yourself, you can judge neither the agent’s test nor its fix. From then on there is a check for each step of the cycle. Is the candidate the agent found really a vulnerability? Is its test red for the right reason, because of the vulnerability and not because of a mistake in the test? Does its fix remove the cause, does it withstand a variation of the attack, and has the test stayed unchanged? The agent works only inside the container of the exercise project.

Between the first and the second session you set up your agent in the container of the exercise project.

Programme

  1. Session 1: The cycle and injection

    How does an exploit become a test that stays?

    • Security as a property rather than an audit, and the CWE as a checklist for code that touches the database, templates, the shell, the file system, or an authorisation decision
    • The cycle: capture the flag, reproduce (red), fix and secure (green)
    • SQL injection, cross-site scripting, and OS command injection (CWE-89, CWE-79, CWE-78): untrusted input leaves its data context
    • The principle “separate data and code”: parameterised queries, automatic output escaping, argument escaping
    • Why the first pass happens without a coding agent

    Result: Your first three security tests, written without an agent and each one proof of its vulnerability, and the principle “separate data and code” behind all three fixes

  2. Session 2: File system, test levels, and the agent

    Where does the vulnerability sit, and how do you know that the agent has really closed it?

    • Path traversal (CWE-22) and unrestricted file upload (CWE-434)
    • The right test level: a unit test where the vulnerability sits in one class, a functional test at the application boundary
    • The agent in the cycle: having it draft the red test from your exploit and propose the fix, and the three checks
    • Typical mistakes of agents: a test that is red for the wrong reason, a fix that only fends off the example, such as a list of forbidden strings instead of a check against the permitted directory, and an “adjusted” test

    Result: Security tests on two levels with a justified choice of level, drafted and fixed by the agent and checked by you against a variation of the attack

  3. Session 3: Authorisation

    Where is the rule “Bob must not cancel Alice’s reservation” written down, apart from the test?

    • IDOR (CWE-639), missing authorisation (CWE-862), and improper authorisation (CWE-285)
    • The break in the pattern: broken authorisation is a logic error rather than a problem of input validation, and the test encodes a business rule that no tool can guess
    • Testing both directions: permitted access keeps working, forbidden access is rejected
    • The agent as a finder: it delivers candidates, such as every action that uses an ID from the request, but only someone who knows the rule can tell whether it is missing there
    • The agent as a fixer: when it closes an IDOR vulnerability, it often locks down too much or checks the wrong thing, and the counter-test for permitted access catches that

    Result: Pairs of tests for permitted and forbidden access that specify the business rule, and every candidate of the agent confirmed or rejected by an exploit

  4. Session 4: Trust boundaries and transfer

    How does the collection become lasting regression protection in your own project?

    • Cross-site request forgery (CWE-352), insecure deserialisation (CWE-502), and SSRF (CWE-918)
    • The security tests as a test suite of their own (phpunit --testsuite security): on every commit, the build checks again whether any of the vulnerabilities is present
    • The security test suite as a guardrail for agents, which build in the same classes of mistakes as humans: an instruction and a hook prevent an agent from changing the tests
    • What tests can do and what they cannot: they complement scanners, static analysis, agents, and audits, but do not replace them
    • Introducing it in the team: a team that practises TDD already has everything test-driven security needs except the habit

    Result: A security test suite that runs on every build and that no agent may change, and a checked list of the CWE classes you secure next in your own code

Who it is for

  • Experienced PHP developers who want security to be a fixed part of their automated test suite
  • Teams who have so far covered security mainly through scanners and audits
  • Teams in which the same vulnerability came back after a refactoring
  • Teams who use coding agents and want to make sure that their code does not open vulnerabilities

Prerequisites

  • Solid PHP knowledge and experience with object-oriented code
  • You have written tests with PHPUnit before
  • A local environment with PHP and Composer, or Podman or Docker for the container that comes with the training
  • From the second session on, a coding agent of your choice, such as Claude Code, Copilot Agent Mode, Cursor, or Junie, that may write files and run commands in the project; you cover the cost and usage quota of your agent yourself
  • If you are not allowed to use an agent at your company, you work together with another participant
  • Curiosity about the attacker’s perspective; prior knowledge of security is not required

What you take away

  • You recognise vulnerabilities because you have traced the attack vector yourself
  • You translate security requirements into automated tests instead of relying on one-off audits
  • You choose the right test level for each vulnerability and can justify your choice
  • You test authorisation in both directions: permitted access keeps working, forbidden access is rejected
  • You use a coding agent to find and fix vulnerabilities and check its result with the test
  • You keep every fixed vulnerability closed for good with a green test, even when an agent changes the code later
  • You take away a collection of security tests that you can adapt to your project

Your trainer

Sebastian Bergmann answering a participant's question at the laptop
Photo: Filip Kovačević

Sebastian Bergmann is the creator of PHPUnit, the de facto standard for testing PHP code. Since 2001 he has been helping teams build maintainable and well-tested software as a consultant and trainer, since 2009 as a co-founder of thePHP.cc.

The training builds on his talk Test-Driven Security, which he gave in 2026 at the International PHP Conference, Web Summer Camp, and Laravel Live Denmark, and on his article on the topic. In the sessions he shows you on real vulnerable code how an exploit becomes a test that stays.