T12-L04 · Security, privacy & governance · Level 4 Integrator · 22-minute read, plus 3 hours of doing
Real data is waiting
In a lab it's November. The assistant server that Lena set up for her group (the one from T11-L04) works well, and the department head wants two more groups on it, with their unpublished results. The institute's IT security team says yes, on one condition: "Show us your audit first." Lena is at a conference, so Tom, the PhD student who is the server's second admin, gets the job. He has never audited anything.
In a company, Lea's workflow sorts the shared quotes inbox every fifteen minutes (you met it in T12-L03). It runs on her own login. Now it's moving to the company's own n8n server, the automation tool installed on a machine IT runs, and it needs a login that isn't hers. IT's answer: "You'll get a service account. It will read one mailbox and nothing else, and we'll check that before it goes live."
Both services work. Neither has been checked by someone looking for the ways in.
You are at Level 4 Integrator. These services hold real records: unpublished results, customer mail, the keys that open them. A gap here isn't a bad answer. It's other people's data in the wrong hands.
After this you can
- Run a five-step audit of a self-hosted AI service: scan from outside, try it logged out, try to sign up, search the Git history for secrets, and list who is admin.
- Give a service the smallest access that does its job, and prove it by trying something it must not do.
- Keep secrets out of files and repositories, and replace one the same day it leaks.
- Limit where the service can send data, and keep logs that answer who did what, for as long as the data protection officer allows.
- Restore the service from backup on another machine, including the keys it needs to read its own data.
Before you start
- Books: T01-L01 for the four-quadrant method. T11-L04 for the server: Caddy in front, containers behind it, backups. T12-L03 for limits and a stop on an unattended workflow.
- Written permission from IT to scan and test the service, with the date and the addresses. Scanning a machine without permission can break your organisation's rules and the law.
- A contact in IT security, and the name of your data protection officer (DPO).
- A device outside the service's allowed network (a laptop on the guest Wi-Fi, or a phone on mobile data), and one inside it.
- A test account with no special rights, and a machine where you may restore a backup.
- About 3.5 hours: 22 minutes to read, about 3 hours of doing.
Last verified 23 September 2026. Nmap and gitleaks are free. Microsoft's RBAC for Applications, which limits an app to named mailboxes, has replaced Application Access Policies. n8n can block requests to internal addresses since version 2.12. Details for experts at the end of this book.
The idea in one page
A self-hosted AI service is locked when five things are true. Each one is something you can check from outside, not a setting you believe is on.
- Only the proxy is reachable. The one front door (Caddy, in T11-L04) answers. The chat app's own port, the model's port, the database and any admin page don't.
- Every connection is HTTPS, with a certificate the browser accepts without a warning.
- Every route needs a personal login. Logged out, you get nothing: no chats, no model list, no admin page. Each person has their own account, and nobody can sign themselves up.
- No secret lives in a file you share or in Git. A secret is anything that opens something: a password, an API key, a client secret, an encryption key. Secrets live in files only root can read, or in your organisation's password vault. Deleting one from the latest version of a Git repository doesn't remove it; Git keeps every earlier version.
- Outbound traffic goes only where it must. A service that reads valuable data and can also reach the whole internet can leak that data, for example when a document it reads contains instructions that it follows. List every place the service must reach, and have IT's firewall block the rest.
Two more ideas carry the Level 4 part.
Least privilege. A service account is a login that belongs to a system, not a person. It gets the smallest access that does the job: one mailbox, not all of them; read, not write. You prove it the only convincing way: you try something it must not do, and watch it fail.
A restore includes the keys. Some services encrypt their own stored credentials with a key kept outside the database. Back up the data without the key, and your backup is locked forever. A restore test on another machine is the only way to find out.
The check that ties it together is the five-step audit:
| Step | What you do | It passes when |
|---|---|---|
| 1. Scan from outside | Scan every port from a device outside, then from inside the allowed network | Only 443 (and 80, if used for certificates) is open |
| 2. Try it logged out | Open the address and its API in a private window | Only the login page appears; the API refuses |
| 3. Try to sign up | Look for a sign-up link and try it | No account can be created without an admin |
| 4. Search the Git history | Run a secret scanner over every commit | No secret, past or present |
| 5. List who is admin | Read the admin list in each tool | Only named, current people |
Each finding gets a line: what, how bad, the fix, who, the date, and the retest. IT security signs the result. The DPO signs the log retention. You fix what's yours and hand the rest to whoever owns it.
Worked example: two audits, four quadrants
The method comes from T01-L01. Here it is on Lena's server, then on Lea's workflow.
Lab: auditing the group's assistant server
Structure it. Tom: "Before two more groups put unpublished results into the assistant, show IT security that only the HTTPS address answers, only named people get in, no secret is exposed, and we can restore. A useful result: a signed audit sheet, with every finding fixed or dated."
Understand it. He asks IT security what they expect. They send him the five steps above and give him written permission to scan the server ai-lindqvist from two places: the group's network segment and the institute's guest Wi-Fi. They also tell him what they found on other groups' servers last year: forgotten admin accounts, and passwords in Git.
He reads the server record from T11-L04 and lists what should be true: port 443 from the group's segment only, SSH only from IT's admin network, sign-up off, university login, two admins. Then he checks each claim.
Choose tools.
- Nmap, the standard free network scanner, for step 1, because IT security uses it and can read its output. IT's own scan service would do as well.
- A private browser window and curl, the command-line tool that sends a single web request and shows the answer, for steps 2 and 3.
- gitleaks, a free tool that searches every commit of a Git repository for things that look like secrets, for step 4. Searching with Git's own log by hand is the alternative, but it only finds what you think to search for.
- Open WebUI's Admin Panel and the server's SSH configuration for step 5.
Build it: the five steps.
Step 1, scan from outside. From the guest Wi-Fi, he scans every TCP port, the numbered doors a server can answer on:
nmap -Pn -p- ai-lindqvist.bio.example.org
-p- means all 65,535 ports; -Pn tells Nmap not to give up if the server ignores its first ping. Nmap reports each port as open (something answers), closed or filtered (a firewall stops the probe). From the guest Wi-Fi, everything is filtered. From the group's segment, 443 is open, as it should be. So is 22, SSH. The record says SSH is open only from IT's admin network.
Step 2, try it logged out. In a private window the address shows the university login and nothing else. Then he asks the API, the address that programs use, for the model list, with no login:
curl -s -o /dev/null -w "%{http_code}\n" https://assistant.lindqvist.bio.example.org/api/models
The command prints only the status code of the answer. It prints 401, which means not logged in. That's a pass.
Step 3, try to sign up. There's no sign-up link. A colleague from another group, who has never used the assistant, logs in with her university account and arrives as pending, with no access until an admin approves her. Pass.
Step 4, search the Git history. Lena keeps the server's folder in a Git repository on the institute's GitLab, so changes are tracked. He clones it and runs:
gitleaks git --verbose ./assistant-config
It finds one secret: the SSO client secret, the password Open WebUI uses to talk to the university login, in the first commit. Lena removed the file a week later and added it to the ignore list, the .gitignore file that tells Git never to track it. But the old commit is still there, and the repository is readable by the whole group.
Step 5, list who is admin. The Admin Panel shows three admins: Lena, Tom, and admin-test, an account from the pilot that nobody remembers.
Back to Structure it. Tom had pictured the audit as a checklist that would come back clean. Two findings change that. The SSH finding isn't his to fix; it's IT's firewall. The secret in Git is worse than it looks: removing it from the history isn't enough, because anyone who cloned the repository already has it. "The goal isn't a clean sheet. It's every finding with an owner and a date, and any exposed secret replaced, not just hidden."
Build it: fix and retest.
| # | Finding | Risk | Fix | Who | Done | Retest |
|---|---|---|---|---|---|---|
| 1 | SSH (22) open to the whole group segment | Medium | Firewall rule: SSH only from IT admin network | IT | 14 Nov | Nmap from segment: 22 filtered |
| 2 | SSO client secret in first commit | High | IT issues a new client secret; old one revoked; new one only in the root-only env file; history rewritten without the file | IT, Tom | 13 Nov | gitleaks: no findings; login works |
| 3 | Unknown admin account admin-test | High | Account deleted; admin list reviewed monthly | Tom | 12 Nov | Two admins listed |
For finding 2 he asks IT to issue a new client secret, the way T12-L03 treated a credential: the secret changes, so the copy in the history opens nothing. He puts the new one in the env file, restarts Open WebUI, and checks that the university login still works. Only then does he clean the history, with git filter-repo, a tool that rewrites every commit without the file. He asks everyone in the group to delete their old copies and clone the repository again.
Build it: outbound, logs, restore. Three more checks complete the sheet.
For outbound traffic, he lists what the server must reach: the university login, and during an update the Ollama model library and the image registries, the sites Docker downloads images from. IT sets the firewall to allow those and block everything else from the server. In Open WebUI's Admin Panel settings he confirms web search is off and that only admins can add tools, the small programs that let the assistant fetch or run things.
For logs, Caddy's access log records each request with the time and address. With the account names in Open WebUI's own log, that answers who did what, when. The DPO sets 14 days. For Caddy, Tom adds a block inside the site's braces in the Caddyfile:
log {
output file /data/access.log {
roll_interval 24h
roll_keep_for 336h
}
}
Caddy then starts a new log file every day and deletes files older than 336 hours, which is 14 days.
For the restore, he takes last night's backup to a spare machine, restores it as in T11-L04 and logs in with the test account. It works because two things were saved: the volume, and the env file with Open WebUI's secret key from T11-L04. Without that key a restored server would log everyone out. The new client secret is in the institute's password vault too, so a rebuilt server could use it.
IT security signs the sheet on 15 November. The two new groups get accounts the following week.
What it taught him: the scan took five minutes and the fixes took three days, most of them waiting for the secret to be reissued. The unknown admin account was the finding that scared him, and it came from the one step that took no tools at all.
Company: a service account for one mailbox
Structure it. Lea: "The quotes workflow runs on the company's n8n server under a service account that can read the quotes mailbox and nothing else. IT can stop it with one switch. A useful result: IT signs the audit, and a test shows the account can't read any other mailbox."
Understand it. Stefan from IT explains how Microsoft 365 handles this. A workflow that runs without a person signed in uses an app registration, an identity for a program in Microsoft Entra, the company's account directory. Normally, giving such an app permission to read mail means it can read every mailbox in the company. Microsoft's answer is RBAC for Applications (RBAC stands for role-based access control), a feature of Exchange Online, Microsoft's hosted mail service, that grants the permission only for a named set of mailboxes. It has replaced the older Application Access Policies.
Stefan adds a warning from Microsoft's documentation: if the app also holds the same permission granted company-wide in Entra, the two add up, and the limit is gone.
Lea then looks at the n8n server itself, with Stefan watching. It runs in Docker behind Caddy, as in T11-L04. She finds two things. The n8n editor answers on its own port, 5678, from the office network, because someone published it "for setup". And the Compose file is in Git with a line reading N8N_ENCRYPTION_KEY= followed by a long string. n8n uses that key to encrypt every stored credential. With the key and a copy of the database, anyone can read them all.
Back to Structure it: "Three things before go-live. The mailbox scope is set in Exchange and not also in Entra. Only Caddy is published. And the encryption key is a secret: out of Git, into the vault, and part of every backup."
Choose tools.
- An app registration with RBAC for Applications, scoped to the quotes mailbox with a management scope, a saved filter that picks mailboxes by a property. Read permission only. Stefan sets it up; only Exchange administrators can.
- n8n's HTTP Request node, the workflow step that calls any web address, with an OAuth2 credential. OAuth2 is the standard way a program gets a short-lived token to call an API, and its client credentials grant is the variant where a program logs in as itself, with an ID and a secret. n8n's built-in Outlook node signs in as a person, so it doesn't fit.
- n8n's own audit, the
n8n auditcommand, which reports unused credentials and risky nodes, next to the five steps. - Nmap and gitleaks as in the lab.
Build it. Stefan creates the app registration, grants it read access to mail through RBAC for Applications, scoped to the quotes mailbox only, and checks in Entra that it has no company-wide mail permission. Lea creates the n8n credential with the app's ID and secret. Stefan gives her the two other values it asks for: the token address, https://login.microsoftonline.com/<tenant ID>/oauth2/v2.0/token, and the scope, https://graph.microsoft.com/.default. She rebuilds the trigger as a Schedule Trigger every fifteen minutes followed by an HTTP Request to Microsoft Graph, the API behind Microsoft 365, at https://graph.microsoft.com/v1.0/users/<quotes mailbox address>/messages.
Then the test that makes the rule true. In a copy of the workflow she points the same request at the sales director's mailbox. It fails with 403, access denied. Stefan runs Microsoft's own check as well, Test-ServicePrincipalAuthorization, a command in Exchange Online's PowerShell module, which shows the permission as in scope for the quotes mailbox and not in scope for the director's. Both results go into the audit sheet.
The five steps on the n8n server find the published editor port (step 1) and the encryption key in Git (step 4). Stefan removes the port line and the Caddy route becomes the only way in. The key is harder. It's already in the history, and n8n's main key is set once and not meant to change. So Stefan replaces what it protects: a new client secret for the app registration and a new model API key, entered fresh in n8n, with the old ones revoked. A copy of the key and the database now opens nothing that still works. The key itself goes into the company's password vault and a root-only env file on the server, never into Git.
Two checks remain. Outbound: the company firewall lets the n8n server reach Microsoft's login and mail services and the model API IT approved, and nothing else. Stefan also switches on n8n's SSRF protection. SSRF, server-side request forgery, means tricking a server into calling addresses inside your own network; the protection stops workflows from calling internal company addresses. Logs: n8n keeps each run's data, including the mail it read. The DPO agrees on 14 days, which is n8n's default, so Stefan leaves the pruning settings as they are and Lea keeps Redact production execution data switched on from T12-L03.
The restore test is where the plan nearly fails. Stefan restores last night's database backup on a spare machine, and n8n starts, but every credential shows an error. The backup had the database and not the key. With the key from the vault in the env file, the credentials open. The backup job now copies the env file too, into the same encrypted backup store.
The stop is one switch. If anything looks wrong, Stefan disables the app registration in Entra, and every request from the workflow fails at once. Murat, the sales team lead, can still unpublish the workflow in n8n, as in T12-L03. Both are written into the operating rule.
IT signs the audit, with the three findings fixed and retested.
What it taught her: she had thought least privilege was a setting you choose. It turned out to be a setting you prove, by reading a mailbox you shouldn't and watching it fail.
In both places the audit found something the builder was sure wasn't there. That's why it runs from the outside.
What goes wrong
The chat app is locked, the admin page isn't. Symptom: the assistant needs a login, but a database tool, a monitoring page or an editor answers on another port. Fix: scan every port, not just the ones you expect. Remove every published port except the proxy's.
A secret "deleted" from Git. Symptom: a password was removed in a later commit, and everyone assumes it's gone. Fix: treat it as leaked. Replace it the same day, then rewrite the history without it (git filter-repo) and add the file to .gitignore.
The scoped app that isn't scoped. Symptom: a service account limited to one mailbox can still read others. Fix: remove any company-wide grant of the same permission in Entra; Microsoft adds the two together. Then repeat the denied-read test.
A backup without its key. Symptom: the restored service starts, but its stored credentials can't be read. Fix: the encryption key goes into the vault and into the backup, and a restore on another machine proves it.
A shared admin account. Symptom: "admin@..." is used by three people, and nobody can say who changed a setting. Fix: every admin has a personal account. Delete shared and test accounts, and review the admin list monthly.
Outbound "needs the internet". Symptom: the server may reach anywhere, because nobody listed where it has to go. Fix: list every destination and its purpose, and have IT block the rest. Add destinations one at a time, when a task needs one.
An audit nobody repeats. Symptom: the service passed a year ago; since then, three updates and a new tool. Fix: rerun the five steps after every large change and at least twice a year. Updates can switch settings back on.
Do it yourself (3 hours)
- Structure it (15 min). Write one sentence: which service, what real data it will hold, and what "locked" must mean before that data goes in. Get IT's written permission to test.
- Understand it (30 min). Read the service's record. Write down every claim in it: which ports, who logs in, who is admin, where secrets live, where it sends data. Ask IT security what they want to see.
- Choose tools (15 min). Pick your scanner, secret finder and the device outside the network. Decide who can fix what: you, IT, the Exchange or directory admin.
- Build it: audit (60 min). Run the five steps. For each service account, try one thing it must not do, and record the result.
- Build it: fix (45 min, plus waiting). Fix what's yours. Replace any exposed secret. Hand the rest to whoever runs that part, with a date. Rerun the step that found each problem.
- Build it: outbound, logs and restore (15 min, plus the restore). List outbound destinations and send them to IT. Ask the DPO for log retention and set it. Restore a backup on another machine, keys included.
Exit check
Written finding: a dated audit sheet for one self-hosted AI service.
- The five steps, each with what you ran, from where, and the result.
- A findings table: each finding with risk, fix, who, date done and the retest result.
- One denied-access test of a service account or ordinary account, with the result.
- One restore test on another machine, noting where the keys came from.
- The outbound list and the log retention, with who agreed each.
It passes when every step has a result, no finding is open without a named person and a date, the denied-access test failed as it should, the restore worked, and IT has signed. If the service holds personal data, the DPO has signed the log retention.
Rule to remember
Check it from the outside, and prove every limit by trying to break it.
Details for experts
Checked on 23 September 2026. You don't need this to follow the book; it's for setting things up or answering a colleague.
| What | Details | What it means for you |
|---|---|---|
| Nmap port states | Open: a program accepts connections. Closed: the port answers but nothing listens. Filtered: a firewall or router stops the probe. -p- scans all TCP ports. | Only 443 (and 80, if used) should be open from where users are. |
| gitleaks | gitleaks git <repo> scans every commit using Git's log. MIT licence. | Run it before a repository is shared, and in your checks. |
| RBAC for Applications | Grants an Exchange Online app role, such as Application Mail.Read, with a resource scope: a management scope or an administrative unit (a group of accounts in Entra that can be managed separately). Commands, in Exchange Online PowerShell: New-ServicePrincipal (registers the app in Exchange; a service principal is the app's identity in one organisation), New-ManagementRoleAssignment, Test-ServicePrincipalAuthorization. It replaces Application Access Policies. Permissions granted in Entra add to it; remove them there. Changes can take 30 minutes to 2 hours to apply. | Ask your Exchange admin; test with a mailbox that must fail. |
| n8n encryption key | n8n creates a random key on first start and saves it in its data folder, or takes N8N_ENCRYPTION_KEY from the environment. This instance key doesn't change. Since key rotation arrived, it protects a data key that encrypts credentials; the data key can be rotated under Settings → Data Encryption Keys, a one-way change that needs a database backup first. | Keep the instance key in the vault and in the backup. If it leaks, replace the credentials it protects. |
| n8n security settings | n8n audit reports unused credentials and risky nodes. NODES_EXCLUDE blocks nodes; Execute Command is blocked by default. SSRF protection (N8N_SSRF_PROTECTION_ENABLED, from 2.12.0) blocks workflow requests to private, loopback and link-local addresses unless allowed. | Switch on SSRF protection; keep network firewalls as the main barrier. |
| n8n execution data | Pruning is on by default; runs older than 336 hours (14 days) are deleted, and at most 10,000 are kept. EXECUTIONS_DATA_MAX_AGE sets the age in hours. | Agree the period with the DPO before go-live. |
| n8n OAuth2 credential | The generic OAuth2 credential supports the client credentials grant, for programs that log in as themselves. | The way to use an app registration from an HTTP Request node. |
Further reading and tools
Next books
- T12-L05: keeping records, reviews and handover for AI systems a whole organisation depends on.
- T11-L04: the server this audit checks.
- T12-L03: limits and a stop for an unattended workflow.
- T07-L04: the same limits when an agent chooses its own actions.
- T14-L04: building your own connector, with the same least-privilege test.
Tools
- n8n: Taught. The workflow server, its audit and security settings.
- Open WebUI: Taught. The assistant being audited in the lab.
- Caddy: Taught. The only published port, and the access log.
- Docker: Taught. Where published ports are set.
- Git: Taught. Where the forgotten secrets were.
- Dokploy: Catalogued. A deployment panel some teams use; it's one more admin page to include in step 1.