An open source project with one or two maintainers cannot review its way to security. Every pull request, base image and dependency update is a chance to ship something that should not ship, and nobody has the hours to inspect each one by hand. What a small team can do is stack automated checks in layers, so that each layer catches some of what the one before it missed.
This article describes nine such layers, the way Turbo EA, an open source enterprise architecture platform, runs them today. For each layer it names the tools, when they run and whether a finding blocks the change or only reports it. It then sets out the guardrails that keep the automation honest and the decisions that stay with people. Most of the tools are open source, and GitHub’s code scanning, secret scanning and Dependabot are free for public repositories.
Why an onion
Security people have long used the onion as a picture of defence in depth. No single layer is expected to stop everything, and an attacker has to get through all of them. For a software project, the picture works best when you read it as the path of a change.
A change begins at the centre, as a request to write some code. On its way out it crosses every ring: the rules built into the code base, the checks before a merge, the build that signs it, the scanners that inspect it, a test against the running application, the dependency updates around it, the protections inside the running product and, at the edge, the practices of the team that operates it. Each ring has its own trigger and its own tools, and none of them depends on a person remembering to run it.
- 1Security from the first request
- 2Rules built into the code
- 3Checks before every merge
- 4Signed releases
- 5Vulnerability scanning
- 6Automated penetration test
- 7Up-to-date dependencies
- 8Protection inside the application
- 9The operator’s part
Every layer should answer three questions in writing: what it stops, when it runs, and whether a finding blocks the change or only reports it.
The nine layers
Each layer below explains what it is there to stop, which tools run it in Turbo EA, and what happens when it finds something.
1Security from the first request
Turbo EA is written with an AI coding assistant, Claude Code, so the first place to put security is the request itself. Each change starts as a written brief that says who may use the feature, what data it touches and who must never see it, and the plan is reviewed before any code is written.
The assistant also reads a standing file of project rules at the start of every session. In Turbo EA that file, CLAUDE.md, requires permission checks on every endpoint, reads included, validated input everywhere, no hand-written SQL and encrypted credentials. A rule that lives in a file survives the next contributor, whether that contributor is a person or a model. A pull-request template then repeats the most important items as a checklist, so a skipped box stays visible to the reviewer.
2Rules built into the code
Rules on paper are a start, but the ones that matter most should fail a build when they are broken. Turbo EA turns its access model into tests. Every action that changes data checks the person’s permissions, and a single visibility rule decides which cards a role may see. A static test scans the API package for any module that returns cards without applying that rule, so a new endpoint cannot quietly forget it.
Before a change is even committed, pre-commit hooks format and lint the code and run Gitleaks over the staged changes to catch passwords and keys. Credentials that the application stores at runtime, such as single sign-on and email secrets, are encrypted in the database rather than kept in plain text.
3Checks before every merge
The pull request is the natural gate for an open source project, because every change has to pass through it. Turbo EA runs four kinds of checks there.
- GitHub code scanning with CodeQL analyses the Python and TypeScript code and the GitHub Actions workflows themselves, on pull requests and again every week. It runs in GitHub’s default setup, so there is no workflow file to maintain.
- Gitleaks scans the full commit range of every pull request and every push to the main branch for secrets. It is deliberately not limited to certain paths, so no change can skip it.
- pip-audit and npm audit check the production dependencies of the backend and the frontend against public vulnerability databases whenever a change touches them. A known vulnerability fails the check.
- The test suite and an API contract check confirm that the change still does what the code base promises.
A small GitHub Actions workflow also closes pull requests from accounts outside a short allowlist. For a project that accepts few outside contributions, this removes a whole class of hostile pull requests before any check runs.
4Signed releases
Users of a self-hosted product install whatever the project publishes, so the release itself needs proof of origin. Turbo EA publishes five container images and a Helm chart to GitHub Container Registry and signs each of them with Sigstore cosign. The signing is keyless: the GitHub Actions job receives a short-lived certificate tied to its own workflow and Git reference, and the signature is recorded in Sigstore’s public Rekor transparency log. There is no long-lived signing key to store, rotate or leak.
Right after signing, the job verifies the signature with the oldest cosign version that the documentation promises to support, using a strict certificate identity. Every image also carries a software bill of materials (SBOM) in SPDX format and a build provenance record, both generated by Docker BuildKit, so an operator can see what is inside and how it was built. Every GitHub Action in the pipeline is pinned to a full commit SHA, and the application containers run as a non-root user with all extra Linux capabilities dropped.
5Vulnerability scanning
An image that is clean today can carry a known vulnerability tomorrow, because new advisories are published every day. Turbo EA therefore scans its images when they are built and keeps scanning them afterwards.
At publish time, Trivy from Aqua Security and Docker Scout check each image against their own vulnerability databases. High and critical findings are uploaded to the GitHub Security tab, and a critical vulnerability that already has a fix fails the run. Every morning, a scheduled job scans the published images again. When it finds a high or critical vulnerability for which a fix exists, it starts a rebuild of all five images on its own. Every Monday the images are also rebuilt without cache, which pulls in the latest operating system patches even when no scanner has asked for them.
6Automated penetration test
Code and image scanners look at what was built. They do not look at the application while it runs, with its real web server, headers and cookies. Turbo EA closes that gap with a weekly automated penetration test.
Every Tuesday, a workflow starts the published images with the unchanged Docker Compose file in production mode, loads sample data and runs the OWASP ZAP baseline scan against it twice: once as an anonymous visitor and once signed in as an administrator. ZAP explores every page and API route it can reach and reports missing or weak security headers, unsafe cookie settings and information leaks. The scan is passive, so it observes and never changes data. Any alert that is not listed in a rules file with a written reason fails the run. The first runs already led to concrete fixes, such as hiding the web server version and adding a frame-ancestors directive to the Content Security Policy.
7Up-to-date dependencies
Most of the code in a modern application was written by someone else. Keeping it current has to run on a schedule, because nobody remembers to do it by hand.
Dependabot raises an alert and proposes a fix when a security advisory affects a dependency, and every week it proposes grouped updates for the pinned base images. Once a month it also refreshes the commit pins of the GitHub Actions in a single grouped pull request. Some components are invisible to the bots. In Turbo EA these are the embedded editors for diagrams, process models and data grids, which a scheduled script bumps once a month. Every update arrives as a pull request, so it passes through the merge checks and the scanners like any other change.
8Protection inside the application
Some protection has to ship inside the product, because it must hold in every installation without anyone configuring it. In Turbo EA, the session lives in a cookie that page scripts cannot read, passwords are stored as bcrypt hashes, and five failed sign-ins lock an account for 15 minutes. In production mode the backend refuses to start with the example secret key from its documentation.
Every page is served with a strict Content Security Policy and other browser security headers. Uploaded files are checked by their actual content rather than by the name or type the browser reports. Extensions only install with a valid vendor signature, and AI assistants connected through the Model Context Protocol preview every change before it is saved.
9The operator’s part
A self-hosted product is only as safe as the way it is run, so the outer ring belongs to the team that operates it. The documentation asks operators to verify the image signatures with cosign before deploying, to install a pinned release rather than the moving latest tag, to back up before every upgrade and to generate a long random secret key.
It also recommends keeping Turbo EA off the open internet altogether. The application needs no inbound access from outside, so the best practice is to let only approved corporate devices reach it, through a VPN or a zero-trust access gateway, and to sign people in through the company’s identity provider, where multi-factor authentication is enforced.
When each layer runs
Timing matters as much as coverage. A layer that only runs at release cannot catch a secret in a pull request, and a layer that only runs at merge cannot notice a vulnerability published next month. The matrix shows when each Turbo EA layer is at work, from the moment a change is written to the moment it runs in production.
| Layer | While writing | Before saving | In review | On merge | On release | Every day | Every week | In production |
|---|---|---|---|---|---|---|---|---|
| 1The brief | acts | |||||||
| 2Built-in rules | acts | acts | acts | |||||
| 3Merge checks | acts | acts | acts | |||||
| 4Signed releases | acts | acts | ||||||
| 5Scanning | acts | acts | acts | acts | ||||
| 6Pen test | acts | |||||||
| 7Updates | acts | |||||||
| 8In the app | acts | |||||||
| 9Your part | acts |
Those last two columns deserve attention. The daily scan and the weekly rebuild, penetration test and dependency updates protect a release after it has shipped. A project that only scans on merge stops looking at its releases the moment they leave the pipeline.
Guardrails that keep the automation honest
Automation that nobody trusts gets switched off. These eight guardrails keep Turbo EA’s pipeline useful rather than noisy.
1Observe broadly, gate narrowly
Send every finding to one place and block only on the subset you are sure about. Turbo EA’s scanners upload high and critical findings to the GitHub Security tab, but only a critical finding with an available fix fails the build. A gate that fires on every advisory teaches people to ignore it.
2Give every exception a reason and a review date
Accepting a finding is sometimes right, for example when a vulnerable function is never reached. Every accepted finding in Turbo EA’s allowlists carries a written reason, secret-scan exceptions match an exact value in an exact file rather than a whole directory, and the lists are re-read every quarter so that an exception disappears once a fix exists.
3Pin everything and let bots move the pins
Every GitHub Action is pinned to a commit SHA and every base image to a tag, so nothing changes underneath the build without a pull request. Dependabot then moves those pins on a schedule, and the merge checks judge each move.
4Close the loop without waiting for a person
When the daily scan finds a fixable high or critical vulnerability in a published image, it starts the rebuild itself. The fix can ship before anyone has read the alert.
5Verify the promise you make to users
Signing an image proves little if users cannot verify it. Turbo EA checks each signature with the oldest cosign version its documentation supports, and a test fails if the workflows and the documentation name different versions.
6Test the configuration, not only the code
Many security properties live in configuration files rather than in code. Turbo EA has tests that read the repository from disk and check that every web server location keeps the security headers, that upload limits agree between the web server and the application, and that the list of signed images matches the images the compose file can pull.
7Give the pipeline least privilege
The pipeline is an attack surface of its own. Each workflow job declares only the permissions it needs, and the workflow that inspects pull requests from outside accounts never checks out their code.
8Say plainly what blocks and what only reports
A check that looks like a gate but is not one creates false confidence. Turbo EA’s security pipeline document states which checks fail a change and which only report, and its security page describes written policies as practice rather than as enforced gates.
What automation leaves to people
Automation narrows the work that needs a person, but it does not remove it. Four things stay with people in Turbo EA, on purpose.
- Second factors. Turbo EA has no built-in multi-factor authentication. People sign in through the company’s identity provider, which is where a second factor belongs and is enforced.
- Attack-mode testing. The weekly penetration test observes and never sends harmful input. Active scanning needs a throwaway environment of its own, and a manual penetration test needs a person.
- The operator’s network. The project’s checks cover its published images in their default setup. Each operator’s servers, proxy and identity provider are theirs to secure and to scan.
- Judgement on plans and exceptions. A person still reads the plan before code is written, decides whether an advisory really affects the product and re-reads the exceptions every quarter.
A starter kit for your own project
An open source project on GitHub does not need all nine layers on day one. The table lists what Turbo EA uses per layer, when it runs and what a finding does. A sensible order is to start with the merge checks and signed releases, and then add the daily scan and the weekly penetration test.
| Layer | What Turbo EA uses | When it runs | What a finding does |
|---|---|---|---|
| 1Security from the first request | A project rules file for the AI assistant, a pull-request template | While a change is written | A person reviews the plan |
| 2Rules built into the code | Guard tests, pre-commit hooks, Gitleaks | Every commit and every CI run | A broken rule fails the test |
| 3Checks before every merge | GitHub CodeQL, Gitleaks, pip-audit, npm audit, the test suite | Every pull request, every push to main, weekly | A failing check marks the change as not ready |
| 4Signed releases | Sigstore cosign, BuildKit SBOM and provenance, SHA-pinned Actions | Every merge to main and every release | An unverifiable signature fails the publish |
| 5Vulnerability scanning | Trivy, Docker Scout, the GitHub Security tab | At publish, daily, weekly rebuild | A fixable critical finding fails the build |
| 6Automated penetration test | OWASP ZAP baseline against the published stack | Weekly and on demand | An unreviewed alert fails the run |
| 7Up-to-date dependencies | Dependabot, a scheduled bump script | Weekly and monthly | Opens pull requests; the merge checks decide |
| 8Protection inside the application | The application’s own secure defaults | Always, in every installation | On by default |
| 9The operator’s part | cosign verify, a VPN or zero-trust gateway, the identity provider | At deployment and in operation | The operator decides |
Turn on GitHub code scanning in its default setup, add Gitleaks to your pull-request workflow and pin every GitHub Action to a commit SHA. None of the three needs new infrastructure, and together they close some of the most common gaps in an open source pipeline.
Turbo EA’s security page shows the same onion interactively, with every safeguard per layer, and the security pipeline document in the repository describes each workflow in technical detail. Both are public, so you can copy whatever fits your project.
Comments
Which layer is missing from your own pipeline, or which one was hardest to automate? Share what you see.