# Securing CodeHero

For CodeHero 5.7.0.

This guide has three levels: **Dev**, **Production** and **Fortress**.
Each level includes the level before it.
The levels are lists of steps for you.
They are not switches in the panel.
CodeHero gives the same protections at every level.
The levels differ in what you do: who can reach the server, what data you put on it, and how you get back in.

Before any step that closes a port, turns on 2FA or changes SSH, read [Do not lock yourself out](#do-not-lock-yourself-out).

---

## Facts for every level

- One owner per installation.
  Never share the panel's login.
- The panel can do everything on the server.
  It runs as the user `claude`, with `sudo` without a password, because it manages the server.
  Treat the panel's password and 2FA like the server's root password.
- CodeHero installs no firewall and never changes one.
  You choose it: your cloud's firewall or your router's is the easiest.
- CodeHero never changes SSH.
  SSH is your own decision.
  Some clouds have no web console to get back in.
- Use two domain names: one for the panel (for example `panel.example.com`), one for the previews (for example
  `apps.example.com`).
  A browser sends a site's cookies to every port of the same name.
  With two names, a project page never receives your panel's login.
  Set both in **Settings → Domains**.
- Apply your firewall rules to IPv4 and to IPv6, if your server has IPv6.
- The **Production** profile in **Settings → Performance** tunes speed and memory.
  It is not a security level.
- Coming in 5.7.1 (not in 5.7.0):
  - project modes: `production` (the AI only reads), `maintenance` (every change asks you), `developer`;
  - a project lock: a locked project cannot be deleted or restored over, and its container cannot be destroyed, until
    you unlock it;
  - «Allow access from» for the AI agents and tickets too: which other projects' files, tickets, messages and knowledge
    a project's tickets may reach.

| Level | For | Data | The panel from the internet |
|---|---|---|---|
| **Dev** | Development and tests | Test data only | Only your own IPs |
| **Production** | Your customers' live apps, a government app | Real data | Only through the CodeHero VPN |
| **Fortress** | The least exposure | Real data | Only through the CodeHero VPN; the previews too |

---

## Level 1 — Dev

For development and tests, with test data.
Do not put real customer data or real production passwords on a Dev server.

### What you do

| # | Step | Where | Why |
|---|---|---|---|
| 1 | Save the passwords the installer showed at its end in your password manager. | The end of the install. Later: `/etc/codehero/credentials.conf` (root only). | You need them outside the server. |
| 2 | IF the server came from a version before 5.7.0 and you still sign in with `admin123`, THEN set your own password. | The page «Set your password» opens by itself after that sign-in. It asks for the one-time setup code the upgrade showed in a box (console: `sudo cat /etc/codehero/panel-setup-code`). | Older versions published that password. The code proves that it is you. |
| 3 | Set the panel's name and the previews' name. | **Settings → Domains** | The panel's login never reaches a project page. |
| 4 | Get a real certificate for both names. | **Settings → Certificates** → **Import Let's Encrypt** | The browser checks that it talks to your server. |
| 5 | Read [Do not lock yourself out](#do-not-lock-yourself-out). Then turn on 2FA. | **Settings → Security** → **Enable 2FA** | A stolen password alone does not open the panel. |
| 6 | Allow `9453` and `9454` only from your own IPs. | Your cloud's firewall, or your router | Strangers do not reach the sign-in page. |
| 7 | Give each project a container. | Project → **Container Isolation (LXC)** → **Create Container** | A project's code runs in its own hardened container. |
| 8 | Press **Apply security** on each container made before 5.7.0. | Project → Container tab | The upgrade does not harden old containers. |
| 9 | Use test data in tickets, previews and screenshots. | Your tickets | Ticket text, code and screenshots go to the ticket's AI provider. |
| 10 | Put passwords and keys in the project's Secrets panel. | Project → **🔐 Secrets** | Never in a ticket's text or in the chat. |
| 11 | Install updates. | **Updates** (the server, its containers, the AI tools) · **Settings → 📦 Manual Update** (CodeHero) | Known faults get fixed. |
| 12 | Make a backup and download it to another machine. | Project → **Backup & Restore** → **Backup History** → **Download** | A copy on the same server is lost with the server. |

### What CodeHero already does

| Protection | Version |
|---|---|
| Every new install generates the panel's and the databases' passwords. There is no default password. | 5.7.0 |
| The upgrade replaces the old published database passwords with new random ones, only where they are still in use. A password you chose is never changed. | 5.7.0 |
| A sign-in with the old panel password `admin123` opens only «Set your password». It asks for a one-time setup code that only the upgrade's output and the server's root show (not asked after 2FA). | 5.7.0 |
| A new panel password ends every other sign-in, its open terminals and «Remember this device». The upgrade to 5.7.0 ends every sign-in from before. | 5.7.0 |
| Wrong passwords: 5 for one account from one address lock that account at that address for 30 minutes. 20 from one address lock that address. Other addresses are not locked. | 5.7.0 |
| 2FA with an authenticator app (TOTP). 5 wrong codes lock. The code page is valid for 5 minutes. | 2FA before 5.7.0; the limits 5.7.0 |
| The panel answers only over HTTPS. With a domain and a real certificate the browser keeps to HTTPS (HSTS). | 5.7.0 |
| nginx removes the panel's login cookie from every request to a project. A change (POST, PUT, DELETE) is taken only from the panel's own pages. | 5.7.0 |
| phpMyAdmin opens only from a signed-in panel. | before 5.7.0 |
| The server's MySQL answers only on the server itself and to its containers. An upgraded server got it unless a database account needed another host; then the panel shows a notice and you decide. | before 5.7.0 |
| A preview needs its project's preview key, on every request — this server's own requests too. nginx checks it for the path it really serves. A ticket's tools send only their own project's key. | 5.7.0 |
| New containers are hardened: AppArmor without nesting, a seccomp filter, the dangerous capabilities dropped, `/proc/sys` and `/sys` read only, no device of the server. | 5.7.0 |
| The AI's file reads stay inside its project. Its commands are checked part by part. A search, copy or archive of the whole server or of every project's folder is refused. | 5.7.0 |
| A ticket's browser and web tools reach only its own project's preview and container, checked on every redirect. The browser trusts only this server's preview certificate; every other certificate is checked normally. | 5.7.0 |
| The containers of different projects cannot reach each other, unless a project allows another on its Container tab («Allow access from»). Each container can use only its own address: it cannot pretend to be another container or the server. | 5.7.0 |
| The Logs menu's diagnostic ZIP hides every secret value and key shape it knows (`[hidden]`). If a secret file could not be read, its `manifest.json` says `"sanitized": "partial"` and names the file. | 5.7.0 |
| Your registered secrets are kept out of what the AI sees. A ticket cannot read the platform's provider keys. | before 5.7.0 |
| Every CodeHero release is signed. **📦 Manual Update** installs only a signed release, and never an older version. | 5.7.0 |
| ZIP backups, snapshots and container images. A safety copy before every restore. | before 5.7.0 |

Limits:
- The secrets filter cannot see a value shorter than 8 characters, or a value inside a screenshot.
- The previews port (`9867`) is open to whoever your firewall lets in. The preview key protects each project.

### What a reviewer will still ask

| Question | The honest answer |
|---|---|
| Can you sign in when the phone with the authenticator is lost? | There are no backup codes. Keep the 2FA «Manual entry code» offline, or add it to a second authenticator. The last resort is the console (see [2FA](#2fa)). |
| Is there a copy outside the server? | Only the copies you download. Backups to external storage (S3, SFTP) are planned, not built. |
| Who can reach the panel? | Whoever your firewall lets in. CodeHero does not filter the internet for you. |
| Can one project harm another? | Containers are hardened, and the containers of different projects cannot reach each other unless you allow it. They still share the server's kernel. There are no memory or CPU limits per container yet. A project without a container runs its AI commands as `claude` on the server. |

---

## Level 2 — Production

For your customers' live apps, and for a government app.
A government app also needs the organisation's own risk decision and acceptance.
This list alone does not give that acceptance.

### What you do — everything of Dev, and:

| # | Step | Where | Why |
|---|---|---|---|
| 1 | Write down which data each project holds and which AI providers may see it. | Your own records | You know what leaves the server. A local LLM on your own device keeps the text in your network. |
| 2 | Add two VPN devices: your laptop and a spare computer. | **Devices** → **+ Device** → connection **CodeHero VPN** | One lost laptop is not your only way in. |
| 3 | Keep «What it reaches» on «The server only» for each device. | **Devices** → the device → **Manage** | A device that is broken into reaches only the server itself. |
| 4 | Close `9453` and `9454` to the internet, in [the safe order](#close-the-panels-ports-behind-the-vpn--the-safe-order). | **Devices** → **Use the VPN to close your ports**, then your cloud's firewall | Only your VPN devices reach the panel and phpMyAdmin. |
| 5 | Decide who must see the previews (`9867`). | Your cloud's firewall | Allow your customers' IPs where you can. Otherwise show only test data in previews. |
| 6 | Check each container: «Applied», no nesting unless the project runs Docker, only the port forwards you need, and «Allow access from» ticked only for a project that must reach it. | Project → Container tab | Nesting, open ports and allowed neighbours weaken the separation. |
| 7 | Build on a staging copy with test data. Change the live project only after a review. | A second project | The live app is not the AI's test bench. |
| 8 | Set the live project's default execution mode to **Supervised**. | **Projects** → edit the project → **Default Execution Mode** | The AI asks you before every write. |
| 9 | Keep encrypted copies of the backups off the server. Test a restore as a new project. | **Backup History** → **Download** · **Projects** → **📦 Import** | You know the restore works and how long it takes. |
| 10 | Turn on automatic security updates and get alerts. | **Updates** → **Automatic updates** → **Critical + security** · **Settings** → Telegram | Fixes come in without waiting for you. |
| 11 | Install the WAF. Start in **Log only**, then choose **Block**. | **Packages** → **WAF Security Setup** · **Settings → Security** | Common attacks on the panel and the previews are refused. |
| 12 | Keep a record of changes, restores and incidents outside the server. | Your own records | A reviewer asks for proof. |

**Supervised** is an approval aid.
It is not the project mode or the lock that come in 5.7.1.

### What CodeHero already does — everything of Dev, and:

| Protection | Version |
|---|---|
| The CodeHero VPN: one key per device. **Revoke** stops a device at once. | 5.5.0 |
| «What it reaches» per VPN device, enforced by the server's own VPN rules. «The server only» is the default, for existing devices too. | 5.7.0 |
| **Use the VPN to close your ports** shows the steps and the hosts line. It changes no firewall. | 5.7.0 |
| **Apply security** hardens an old container: a copy of its settings, a restart, the checks, the old settings back on any failure (a time-out or a panel restart too). **Remove security** works only from the panel, on a stopped container too. An AI ticket cannot edit a container's LXC settings. | 5.7.0 |
| «Allow access from»: CodeHero's own rules on the containers' bridge. A tick lets one project's container open connections to another's; nothing ticked = isolated. | 5.7.0 |
| A container's MySQL / MariaDB `root` logs in only from inside the container and from the panel. | 5.7.0 |
| The Updates page reads Ubuntu's security notices and installs by importance, by hand or every day. | 5.4.0 |
| Updates refuse while an old unsigned CodeHero repository list is in a container. **Apply security** signs it. | 5.7.0 |
| The WAF (ModSecurity with the OWASP Core Rule Set), if you install it. | 5.4.0 |
| Imports and backups: names, links, owners and permissions from an archive are checked. | 5.7.0 |
| An **Approve** on a ticket allows that exact call only. A ticket cannot give itself device or service tools. A ticket's «confirmed» on a device counts only after your **Approve** of that call. | 5.7.0 |
| Database passwords never appear on the server's process list during installs, repairs, backups and restores. | 5.7.0 |
| Device events (who did what, never the content), the ticket history, the update history. | before 5.7.0 |

### What a reviewer will still ask

| Question | The honest answer |
|---|---|
| Can the AI change the live app without your approval? | In 5.7.0: **Supervised** asks before every write; **Semi-Autonomous** asks for risky steps. A mode where the AI only reads, and a project lock, come in 5.7.1. |
| Does the server enforce what a VPN device reaches? | Yes. The server's own VPN rules decide it. A device's own WireGuard settings cannot widen it. |
| Is there an audit trail the owner cannot change? | No. The events, the histories and the logs are on the server, and the owner can change them. Send the logs to an outside service if you need that. |
| Do the backups survive a break-in on the server? | Only the copies you keep elsewhere, with other credentials. |
| Was the customer's app tested for security? | A plan can include `[SECURITY]` tickets. That is not an independent assessment. |
| Who approves a change? | The owner alone. There is no second approver. |
| Where does the data go? | Ticket text, code and screenshots go to the ticket's AI provider. You choose the providers per project. A local LLM keeps the text on your device. |
| Can one container reach another? | Only when the receiving project allows it («Allow access from»). CodeHero's own rules on the containers' bridge enforce it. A port you forward to a container is reachable from everywhere it is open, other containers included. On a kernel that cannot track replies on the bridge, an allowed pair talks both ways. A container cannot use another container's address or the server's. |

---

## Level 3 — Fortress

For the least exposure.
It is still one server, with one owner who can do everything on it.

What Fortress cannot give:
- Separate administrators, or a second person who must approve a change.
- A separate kernel per project. Containers share the server's kernel.
- Service when the server is gone. There is one server; recovery is a restore on a new server.
- A certificate (ISO 27001, SOC 2) or a government approval. Those come from an auditor.

IF a reviewer requires one of these, THEN this installation alone cannot meet it.
Use separate machines, for example a build server and a production server.

### What you do — everything of Production, and:

| # | Step | Where | Why |
|---|---|---|---|
| 1 | Use one admin computer for the panel: disk encryption, its own browser profile, updates on. | Your computer | The panel is as safe as the computer that opens it. |
| 2 | Turn on strong 2FA on your cloud account, your DNS account and your email. | Those providers | These accounts can go around the panel. |
| 3 | Close `9453`, `9454` and `9867` to the internet. Customers see previews through the VPN, or on another server. | Your cloud's firewall | Only the VPN and your published sites face the internet. |
| 4 | Remove the devices you do not use. Choose «The whole VPN» only for a device that needs it. | **Devices** → the device → **Manage** → **Remove…** | A broken device reaches as little as possible. |
| 5 | Never use the admin computer as a build device. | **Devices** | A ticket can run commands on its device. |
| 6 | When 5.7.1 comes: the live project in `production` mode, and locked. | The project page (5.7.1) | Every change becomes deliberate. |
| 7 | Keep backup copies where the server cannot delete them: other credentials, versioning, or offline. Rehearse a rebuild on a new server. | Your own storage | A break-in cannot erase every copy. |
| 8 | Watch from outside: your sites, the certificates' expiry, and that the panel does not answer from the internet. | An outside monitoring service | The alarm comes even when the server is down. |
| 9 | Audit Ubuntu against the CIS benchmark on a copy of the server first. | A clone of the server | A stricter setting can break LXC, `sudo` or the VPN. |
| 10 | Order an independent security assessment. Keep its findings and fixes outside the server. | An outside assessor | Proof for a reviewer. |

### What CodeHero already does

Everything of Dev and Production.
There is no extra «Fortress mode» in the panel.

### What a reviewer will still ask

| Question | The honest answer |
|---|---|
| Is the control plane apart from the code the AI writes? | No. One server; the panel has `sudo`; the containers share the kernel. Use separate machines if this is required. |
| Is the sign-in phishing-resistant (hardware keys)? | No. The panel has a password and TOTP; the VPN adds a device key. WebAuthn / FIDO2 is not built. |
| Are logs and backups immutable? | Not by CodeHero. Use outside storage with its own retention. |
| Can the release key be revoked? | The panel trusts one public key built into CodeHero. There is no revocation list. A release with a new key installs from the console. |
| Are third-party packages checked? | The release signature covers CodeHero's own files. Ubuntu, PHP, Node.js and MySQL packages come from their own signed repositories. The AI tools come from their makers' installers. |
| Is there high availability? | No. One server. |

---

## Do not lock yourself out

### Keep a way in that does not need the panel

1. Find out how you reach the server's console today: SSH from your IP, or your cloud's web console.
2. IF your cloud has no web console, THEN keep SSH open for your own IP. That is your only way back.
3. Test that way before each change below.

The panel's web terminal is behind the panel's login.
It does not help when you are locked out of the panel.
The commands in this section are a last resort.
They run in that console.

### Close the panel's ports behind the VPN — the safe order

1. Save your cloud firewall's current rules. A screenshot is enough.
2. Set the two names and real certificates: **Settings → Domains**, **Settings → Certificates**.
3. Give the VPN a public address of its own: `vpn.example.com`, or the server's IP.
   Never the panel's name: the hosts line sends the panel's name into the VPN, and the VPN itself must be reachable
   from outside it.
4. Open `51820/udp` on your cloud's firewall.
   In **Devices**, on the VPN card: **Install VPN**, tick **VPN on**, **Save**.
5. Add your laptop: **+ Device** → connection **CodeHero VPN** → **Get the command** (or **Config file / QR**).
   Keep «What it reaches: The server only».
6. Add a spare computer the same way, as a device of its own.
7. Press **Use the VPN to close your ports**.
   Copy the hosts line it shows into the hosts file of each of these computers:
   - Windows: `C:\Windows\System32\drivers\etc\hosts` (open Notepad as administrator).
   - macOS and Linux: `/etc/hosts` (with `sudo`).
   - Example with the default VPN network: `10.77.0.1  panel.example.com apps.example.com`
8. With the VPN on, open `https://panel.example.com:9453`.
   Sign in again with your password and 2FA.
   The browser must show no certificate warning.
   Open phpMyAdmin too.
9. Do step 8 on the spare computer, from another network (for example a phone's hotspot).
10. Only now close `9453` and `9454` on your cloud's firewall.
    Close `9867` only if nobody outside the VPN must see the previews.
    Close a forwarded port only if only VPN devices use it.
11. Keep `51820/udp` open.
    Keep `80` and `443` open if you publish sites or use Let's Encrypt.
12. Test from outside with the VPN off: the panel must not open.
    Turn the VPN on: the panel must open.
13. IF something fails, THEN put the saved rules back on your cloud's firewall.

While `9453` is closed, a new device cannot join with **Get the command**: the command needs the panel before the VPN.
Give it a **Config file / QR**, or open `9453` for its IP while it joins.

### The hosts line

- It makes the panel's name lead to the server's VPN address (`10.77.0.1` with the default network).
  The certificate still matches the name.
- IF you take the line out, THEN the computer uses the public address again.
  That works only while the port is open.
- IF you change the VPN network or move CodeHero to another server, THEN change the line on every computer.
- The panel's name works only over HTTPS (HSTS, below). Through the line it stays HTTPS.

### 2FA

- There are no backup codes.
- When you turn on 2FA, the panel shows a QR code and a «Manual entry code».
  Save that code offline: in a password manager on another device, or on paper in a safe place.
  Or scan the QR code with a second authenticator at the same time.
- Test a new sign-in in a private browser window before you close the old one.
- Wrong passwords lock that account at that address for 30 minutes.
  Wait, or sign in from another address.
- A lost phone, last resort, in the console:
  1. `sudo /opt/codehero/scripts/manage-2fa.sh disable`
  2. Sign in with your password.
  3. **Settings → Security** → **Enable 2FA** (a new QR code).
- A lock, last resort: `sudo /opt/codehero/scripts/manage-2fa.sh unlock`.
  It also clears the running panel's locks.

### Passwords

- `/etc/codehero/credentials.conf` holds the generated passwords. Only root can read it:
  `sudo cat /etc/codehero/credentials.conf`.
- Change the panel's password in **Settings → Security** → **Change Admin Password**.
- A new panel password signs out every other browser and device, and ends «Remember this device».
  The browser that changed it stays signed in.
- A lost panel password, last resort: `sudo /opt/codehero/scripts/change-passwords.sh`.
  It accepts only 8 to 128 characters: letters, digits and `. _ @ % + = : , / ! ^ * -` (no spaces or quotes).
  A panel password has at most 72 characters.
- The setup code of «Set your password» (an upgraded server that still had `admin123`): the upgrade shows it in a box.
  Lost it: `sudo cat /etc/codehero/panel-setup-code`. It works once. With 2FA on, it is not asked.
- IF an upgrade stopped half-way while it changed the database passwords, THEN `/etc/codehero/db-rotation.journal`
  (root only) keeps both passwords. Run the same upgrade again: it finishes or undoes the change. Nothing to do by hand.
- Keep a copy of your passwords outside the server.

### The release key

- CodeHero signs every release with its own private key.
  You keep no key.
  The panel checks the signature with a public key built into CodeHero.
- IF **📦 Manual Update** refuses a ZIP, THEN do not force it.
  Download it again and compare its SHA-256 with the release notes: `sha256sum <zip>`.
- **📦 Manual Update** never installs an older version.
  A downgrade runs only from the console: `sudo ./upgrade.sh --allow-downgrade`.
  Never go below 5.7.0 with hardened containers: they need its settings files to start.
- IF CodeHero ever announces a new signing key, THEN that release installs from the console (`sudo ./upgrade.sh`),
  after you checked its SHA-256.

### DNS and certificates

- Let's Encrypt renews over port `80`.
  IF you close `80`, THEN the certificates stop renewing.
- HSTS: after a browser opened the panel's name with a real certificate, it uses only HTTPS for that name, on every
  port, for one year.
  - A container service forwarded in plain HTTP on that name will not open in that browser.
    Forward it with TLS, or give it another name.
  - IF the certificate expires or does not match, THEN the browser refuses that name and shows no way past.
    Renew the certificate.
    Meanwhile open the panel by an IP address: `https://<the server's IP>:9453`, or through the VPN
    `https://10.77.0.1:9453`. The browser warns about the certificate. HSTS does not apply to an IP address.
  - A browser forgets the rule only in its own settings (Chrome: `chrome://net-internals/#hsts` → delete the domain).
- Keep your DNS account safe: whoever controls your DNS can send your names to another server.

### SSH is your decision

- CodeHero never changes SSH and never adds a firewall rule.
- IF your cloud has no web console and you close SSH to the internet, THEN a stopped VPN leaves you with no way in.
  Keep your own IP allowed.
- IF you change SSH yourself (a new port, keys only), THEN keep the old session open, test a new login from a second
  window, and only then close the old way.

### Other traps

| Trap | The safe order |
|---|---|
| Revoking or removing the only VPN device | Test the spare device first. |
| A new VPN key for a device | The new config replaces the old one at once. Have the spare device ready. |
| Moving the VPN to another server | **Devices** → **Export / Import** first; keep its passphrase elsewhere. Test the new address before you close the old way. |
| **Apply security** on a container | Make a backup. Press it when a minute without that site suits you, not while one of its tickets runs. It goes back by itself if the container does not start or a service does not come back. |
| A container that does not start with the hardening | **Remove security** works on the stopped container. IF it does not start without the hardening either, THEN the hardening comes back: look for another cause. |
| Two projects that share a container service (a database, a cache) | After the upgrade to 5.7.0 the containers are isolated. Tick the client project in «Allow access from» on the serving project's Container tab. |
| A script or cron job that calls a preview on this server | From 5.7.0 it needs the project's preview key: the header `X-CodeHero-Preview-Key`, or `?key=`. Or call the production domain. Test it after the upgrade. |
| Sharing the Logs menu's diagnostic ZIP | IF its `manifest.json` says `"sanitized": "partial"`, THEN check the ZIP first: the named file's values may not be hidden. |
| A backup without the secrets | ZIP backups leave out `.secrets/` by default. Keep the secrets safe yourself, or tick **Include the secrets** for a move. |
| Disk encryption with a password at boot | A server with no console cannot be unlocked from far away. Check your cloud's way first. |

---

## Ports by level

| Port | What | Dev | Production | Fortress |
|---|---|---|---|---|
| `22/tcp` | SSH | Your IPs | Your IPs. The VPN only after it is tested and you have a console. | Your decision, with a tested way back in |
| `9453/tcp` | The CodeHero panel | Your IPs | Only through the VPN | Only through the VPN |
| `9454/tcp` | phpMyAdmin, behind the panel's login | Your IPs | Only through the VPN | Only through the VPN |
| `9867/tcp` | The project previews | Whoever must see them | Your customers' IPs where you can; else test data only | Only through the VPN |
| `80/tcp`, `443/tcp` | Your published sites and Let's Encrypt | Only if you publish sites | Open if you publish sites | Open if you publish sites |
| `51820/udp` | The CodeHero VPN | Only if the VPN is on | Open | Open |
| A port you forward to a container | That container's service | As the service needs | Only what the service needs; through the VPN if only devices use it | Through the VPN, or closed |
| Every other port | MySQL `3306`, the panel's application `5000`, the containers, the databases of the Services tab … | Closed | Closed | Closed |

- MySQL and the panel's application already listen only on the server itself. Closing them is a second layer.
- A VPN device always reaches the server's own ports (`9453`, `9454`, `9867`, the forwarded ports, SSH), whatever
  «What it reaches» says.
- A port you forward to a container is reachable from everywhere your firewall lets in, other containers included.
  «Allow access from» decides only the traffic between containers on the bridge.
- Before you close a port, keep a way in ([above](#keep-a-way-in-that-does-not-need-the-panel)).

---

## Recognised baselines

A map, not a certificate.

| Baseline | Dev | Production | Fortress |
|---|---|---|---|
| CIS Ubuntu Linux benchmark (the server) | Read the Level 1 Server profile | Audit against Level 1 Server | Test Level 2 Server settings on a copy first |
| OWASP ASVS (the customer's app) | Level 1 | Level 2 | Level 3 for a high-assurance app |
| NIST SP 800-53 | — | Choose the controls by the system's impact | Choose the controls by the system's impact |

For an ISO 27001, SOC 2 or government review, keep an evidence folder outside the server:
- the data each project holds, and the risk decisions;
- the port checks from outside;
- the approved changes and the update records;
- the restore tests and their times;
- the incidents and what you did;
- the assessment findings and their fixes.

The reviewer decides which requirements apply.

---

## Report a vulnerability

See [SECURITY.md](../SECURITY.md).
More in the user guide: §28 Security, §29 updates, §31 containers, §33 devices and the VPN, §34 ports
([USER_GUIDE.md](USER_GUIDE.md)).
