← Work

ESF-Plus

A German state funding programme needed more than 700 organisations to send documents to a public authority securely. I built it on my own over three years. It ran a year without a security incident, and my employer then sold it.

EmployerISG Institut, for the ESF Plus programme in Baden-Württemberg
My roleSole engineer. Architecture, build, security model, operations
Not my scopeRequirements gathering from the funding bank. That was my employer's process, and I built against it.
BuiltThree years, on my own. Handed over 2025
StackPHP 8.2 · Laravel 12 · Livewire 3 · MySQL
OutcomeMy employer sold it for €500,000

The constraint that mattered most

Public funding money has to be auditable years later. Not "we have logs" auditable. Someone from the funding bank has to be able to ask, in 2029, who uploaded which document in 2024 and who looked at it. That requirement does not arrive as a ticket. It arrives as a compliance review, after the software is finished, and by then it is too late to retrofit.

DecisionI designed and built the audit trail first, before the upload feature that needed it.
AlternativeAdd logging later, where every other project puts it. Faster to a demo, and the normal choice.
What it costWeeks of visible progress at the start, when I had nothing to demonstrate and was being asked when the upload screen would work.
What it boughtThe compliance review found nothing to fix.

How it works

700+ ORGANISATIONSoutside the authorityPERMISSION CHECKwho are youwhat may you seewhat may you changeevery request, no exceptionDOCUMENT STORE22,000+ processedAUDIT RECORDappend only, written first
Fig. 01 — nothing reaches storage without passing the permission check, and the audit record is written before the file

The product

The sign-in page. A panel headed Wichtige Informationen explains where credentials come from and who issues them; the sign-in form sits to its right.
Fig. 02 — sign-in, with the access rules stated on the page itself.
The account administration table, reading 718 accounts in total. The organisation, contact-person and both e-mail columns are covered by black redaction bars.
Fig. 03 — one account per organisation, 718 of them. Names and addresses redacted.
The contact-data entry grid, shown empty, with columns for surname, first name, street, postcode, town, telephone and e-mail, and buttons to import, validate, upload and export.
Fig. 04 — the upload grid, empty. Validation runs server-side before a row is stored.

For the technical reader

How it is built, enforced and checked

Architecture

Server-side extraction, so a malformed upload never becomes the browser's problem. Role and ownership checks live in one layer that every request passes through. Email to case handlers is template-driven, so the authority can change wording without me.

Enforcement

The audit record is written before the document is stored, so an upload that fails halfway still leaves a trace. Permissions are checked in one place rather than at each screen, which is what makes "who saw this file" answerable at all.

Checked

PHPUnit around the permission model and the extraction path. A year in production with no security incident, on a system used daily by people outside the organisation.

What I would do differently

Livewire kept me to one language and one mental model, which is why one person could finish it. It also means the interface is chattier over the network than a proper client would be, and on a bad connection in a small organisation that shows. If I built it again for the same constraints I would keep the choice, and I would measure it earlier.

Happy to walk through any of these in detail, including the parts that went wrong.