# Security ## Overview What the client does, who it trusts, and what stays the project's responsibility. ## The token The instance token is a 64-character hexadecimal value from 32 random bytes. It's the only secret between instance and server. - It's shown **once** when the instance is created. The server stores only the SHA-256 hash and cannot display it again. - It lives exclusively in `manage-client/config.php`. - This file does **not** belong in the repository and does **not** belong in the release package. - If compromise is suspected, in the Manage server go to **Instances** → "Rotate token". The old token becomes invalid immediately. ```gitignore manage-client/config.php data/manage/ ``` A compromised token allows: downloading releases and uploading backups — so, access to the application code and the ability to use up storage. It does **not** allow downloading backups or changing releases; both require logging into the Manage server. ## Transport Every request runs over HTTPS. The client uses PHP's default certificate verification; it is **not** disabled. A server with a self-signed certificate therefore doesn't work without a matching CA bundle on the system — that's intentional. Redirects are not followed (`follow_location => 0`). A redirected request fails instead of sending credentials to a different destination. ## Trust in the release package An update deploys someone else's code on the server. That's secured by: 1. **TLS** to the Manage server. 2. **Token required** for manifest and package — neither is publicly retrievable. 3. **SHA-256 check** of size and content against the manifest. On mismatch, the file is deleted and nothing is deployed. 4. **Path check** on every ZIP entry against escaping the target directory. 5. **Sanity check** via `MANAGE_UPDATE_SANITY_PATHS`. The limit of this model: the checksum comes from the same server as the package. Whoever takes over the Manage server can publish a package **and** the matching checksum. A signature backed by a public key held in the client deliberately doesn't exist — the Manage server has to be secured accordingly. ## Backups contain operational data A backup gets transferred to the Manage server, stored there, and can be downloaded by anyone who logs into the Manage server. It follows that: - **No credentials in the backup.** `config.php` with database passwords or API keys doesn't belong in `MANAGE_BACKUP_SOURCES`. - If the backup contains personal data — the rule for order or customer data — the same requirements apply to the Manage server as to the application itself: access control, transport encryption, deletion deadlines. Retention on the server is configurable. - The S3 target stores archives unencrypted in the bucket. The bucket must be private. ## Local directories These directories must not be reachable over the web: | Path | Content | |---|---| | `data/manage/backups/` | complete operational data | | `data/manage/updates/` | copies of the application files an update overwrote | | `data/manage/work/` | extracted packages during an update | | `manage-client/config.php` | instance token | The bundled `manage-client/.htaccess` locks `config.php`, `lib/` and `bin/`. For `data/`, the project's own `.htaccess` is responsible. On nginx, the matching `location` rules have to be set by hand — `.htaccess` has no effect there. This can be checked directly: ```bash curl -s -o /dev/null -w "%{http_code}\n" https://myproject.example.org/manage-client/config.php curl -s -o /dev/null -w "%{http_code}\n" https://myproject.example.org/data/manage/backups/ ``` Both must return `403` or `404`, never `200`. ## The UI `ui/panel.php` allows deploying updates and downloading backups — it's the application's most powerful page. Because of that: - The project must check its login **before** including it. - The panel brings an extra check on `$_SESSION["admin_logged_in"]`. - `MANAGE_PANEL_SKIP_AUTH_GUARD` disables only this extra check. Setting it without checking the login yourself publishes update and backup functions on the network. - All forms are CSRF-protected. - The download strictly validates the filename, so no arbitrary path can be served. Where possible, the page should be accessible only to administrators, not to every logged-in user. ## Command line `bin/manage-client.php` refuses to run over HTTP (a `PHP_SAPI` check) and is additionally locked via `.htaccess`. On the server, the file should still avoid living in the public directory tree where that can be helped. ## Logs The client log contains filenames, versions, HTTP status and error messages. Credentials are filtered out: from target configurations, the client only picks up an allowlist of non-sensitive keys; `access_key`, `secret_key`, `password` and the instance token never appear in the log. Response excerpts from failed uploads are truncated to 500 characters. ## Checklist before going live - [ ] `manage-client/config.php` is in `.gitignore` - [ ] `config.php` and `manage-client/config.php` are excluded from the release package - [ ] `data/manage/` is not reachable over the web (checked with `curl`) - [ ] `manage-client/config.php` is not reachable over the web (checked with `curl`) - [ ] The panel page requires an administrator login - [ ] `MANAGE_SERVER_URL` uses `https://` - [ ] No credentials are inside the backup - [ ] A backup has been downloaded once and its content checked ## Next - [08_PROTOCOL](08_PROTOCOL.md) – authentication in detail - [05_BACKUP_SOURCES](05_BACKUP_SOURCES.md) – what belongs in the archive