detached 26d49b5

docs: add security hardening regression matrix

commit
26d49b56c59c53515ea5351d2649c7f184965693
author
brice · · 2026-04-25 08:30 UTC
parent
ec8c5b9
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +222 -0
A docs/security-hardening-regression-matrix.md +222 -0
@@ -0,0 +1,222 @@
1+# Security Hardening Regression Matrix
2+
3+This matrix lists the behaviors that should be tested while implementing the security-hardening fixes from the audit. It is intentionally focused on regressions: normal Gordon workflows must continue to work while exploit paths are blocked.
4+
5+## Registry uploads
6+
7+### Normal behavior to preserve
8+- Docker push of a small image succeeds.
9+- Docker push with multiple layers succeeds when every layer is below the configured cumulative blob limit.
10+- Chunked blob upload succeeds when the cumulative size remains below the new cumulative limit setting, tentatively named `max_blob_size` unless implementation chooses a clearer config name.
11+- A final `PUT` with a valid digest finalizes the blob and returns OCI-compatible headers.
12+- Existing `max_blob_chunk_size` behavior still rejects a single oversized chunk; this remains distinct from the cumulative per-blob/layer limit.
13+
14+### Security behavior to verify
15+- Repeated `PATCH` chunks cannot exceed the cumulative upload limit.
16+- `Content-Length` larger than the remaining allowed size is rejected before writing when possible.
17+- A rejected upload does not leave unbounded stale data on disk.
18+- Cancel/cleanup paths remove upload temp files and upload locks.
19+- Error response remains Docker Registry compatible (`413` / `SIZE_INVALID`).
20+
21+### Suggested tests
22+- Unit tests for filesystem blob append under and over cumulative size.
23+- Handler tests for `PATCH` and final `PUT` over limit.
24+- Integration/manual: push image with a large layer and confirm failure is clean.
25+
26+## Deploy errors and logs
27+
28+### Normal behavior to preserve
29+- Successful deploy response still includes status, domain, and container ID.
30+- Failed deploy still returns useful high-level error/cause/hint.
31+- Operators with the new logs permission can still retrieve logs through `/admin/logs`.
32+- CLI deploy displays helpful failure information without leaking raw secrets.
33+
34+### Security behavior to verify
35+- A token with only `admin:config:write` cannot receive raw container logs in `/admin/deploy` error responses.
36+- Logs returned through log endpoints are redacted for common secret patterns.
37+- `admin:status:read` no longer grants log read access.
38+- `admin:*:*` remains compatible and can access logs.
39+
40+### Suggested tests
41+- Handler tests for deploy error response with and without `logs:read`.
42+- Scope tests for `logs:read`, wildcard, and old `status:read` behavior.
43+- Redactor table tests for `PASSWORD=`, `TOKEN=`, `SECRET=`, `API_KEY=`, `DATABASE_URL=`, and simple JSON fields.
44+
45+## Admin scopes and compatibility
46+
47+### Normal behavior to preserve
48+- Existing `admin:*:*` tokens continue to work everywhere.
49+- Existing `routes`, `config`, `secrets`, and `status` scopes keep their intended behavior.
50+- Token exchange through `/auth/token` still works for admin scopes.
51+
52+### Security behavior to verify
53+- New sensitive resources such as `logs` and possibly `volumes` are parsed and enforced.
54+- Unknown or malformed scopes remain ignored/denied, not accidentally allowed.
55+
56+### Suggested tests
57+- Domain auth scope parsing tests.
58+- Admin middleware/handler tests for each sensitive endpoint.
59+
60+## Auto-route env-file extraction
61+
62+### Normal behavior to preserve
63+- Valid `gordon.env-file` imports default env values for a newly created auto-route.
64+- Existing env values still override image-provided defaults.
65+- Secret reference rejection remains intact.
66+- Auto-route without env-file remains unchanged.
67+
68+### Security behavior to verify
69+- Oversized env files are rejected before full memory read.
70+- Disallowed paths (`/proc`, `/sys`, `/dev`, `/run`, `/var/run`, `/`, traversal) are rejected.
71+- Failed env-file import does not prevent route/deploy behavior beyond the intended warning path unless explicitly designed.
72+
73+### Suggested tests
74+- Runtime extraction tests using a controlled reader larger than the limit.
75+- Auto-route usecase tests for allowed/disallowed paths.
76+- Tests confirming `${pass:...}` / `${sops:...}` refs remain rejected.
77+
78+## Domain canonicalization and proxy routing
79+
80+### Normal behavior to preserve
81+- Existing lowercase route domains continue to resolve.
82+- Registry domain still routes to registry.
83+- External routes still resolve and retain SSRF protections.
84+- Hostnames with expected public domains work through proxy.
85+
86+### Security behavior to verify
87+- `App.Example.com` and `app.example.com` map to the same canonical route.
88+- Duplicate case-variant routes cannot be created.
89+- Host with invalid port/trailing dot/case is handled consistently.
90+- HTTP-to-HTTPS redirect does not reflect arbitrary unconfigured Host values.
91+
92+### Suggested tests
93+- Config service tests for add/update/list canonical route domains.
94+- Auto-route tests for case variants.
95+- Proxy handler tests for Host normalization.
96+- Middleware tests for redirect target host validation.
97+
98+## Volumes
99+
100+### Normal behavior to preserve
101+- Listing volumes still shows Gordon-managed volumes.
102+- Dry-run prune still reports what would be removed.
103+- Actual prune still removes unused Gordon-managed volumes.
104+- In-use Gordon volumes are not removed.
105+
106+### Security behavior to verify
107+- Unused non-Gordon Docker volumes are not removed.
108+- Volumes without explicit Gordon ownership metadata, preferably `gordon.managed=true`, are ignored; prefix-only matching is unsafe except as a constrained legacy fallback with dedicated tests.
109+- Any new `volumes:read/write` scope is enforced correctly.
110+
111+### Suggested tests
112+- Volume service tests with mixed Gordon/non-Gordon, used/unused volumes.
113+- Admin handler tests for permissions.
114+- Manual: create an unrelated Docker volume, run Gordon prune dry-run/real, confirm it remains.
115+
116+## Backups
117+
118+### Normal behavior to preserve
119+- Manual backup completes and appears in list.
120+- Scheduled backup path format remains readable/listable.
121+- Backup detect still finds supported DB attachments.
122+- Existing backups can still be listed after filename format changes.
123+
124+### Security behavior to verify
125+- Two backups for same domain/db/schedule in the same second do not share tmp/final filenames.
126+- Concurrent backups do not truncate each other.
127+- Store uses unique tmp files and atomic rename.
128+- Path traversal protections remain intact.
129+
130+### Suggested tests
131+- Filesystem storage tests with identical timestamps.
132+- Concurrent store test with two readers and same domain/db/schedule.
133+- List tests for old and new filename formats if compatibility is needed.
134+
135+## Secrets and pass migration
136+
137+### Normal behavior to preserve
138+- `pass` backend still migrates existing env keys correctly.
139+- Attachment env migration still works.
140+- Unsafe backend still loads valid files under `dataDir/secrets` for development.
141+- Existing env file parsing remains compatible.
142+
143+### Security behavior to verify
144+- Unsafe backend rejects `../` and absolute `auth.token_secret` paths.
145+- Migration does not leave plaintext `.env.migrated` by default.
146+- If an explicit keep/quarantine option exists, files are stored with restrictive permissions and loud warnings.
147+
148+### Suggested tests
149+- `loadSecret` path traversal tests.
150+- Migration tests verifying plaintext source file removal/quarantine.
151+- Permission tests where practical (`0600` files, `0700` dirs).
152+
153+## Config snapshot
154+
155+### Normal behavior to preserve
156+- `/admin/config` still returns enough information for CLI/admin use.
157+- Routes and network isolation status remain visible to authorized callers.
158+
159+### Security behavior to verify
160+- `data_dir` is hidden by default or only available under a deliberately stronger detailed mode.
161+- External route targets are hidden/redacted by default or gated.
162+- `config:read` behavior remains documented.
163+
164+### Suggested tests
165+- Admin config response tests for default and detailed modes.
166+- CLI remote config tests if output shape changes.
167+
168+## External image registries
169+
170+### Normal behavior to preserve
171+- Images in the Gordon registry deploy normally.
172+- Explicitly allowlisted external registries deploy if configured.
173+- Attachments still deploy from allowed sources.
174+
175+### Security behavior to verify
176+- `localhost`, loopback, private IP, link-local, metadata IP, and DNS resolving to private ranges are rejected unless a deliberate unsafe override exists.
177+- Non-allowlisted external registries are rejected before Docker pull.
178+- Error messages do not leak sensitive network details.
179+
180+### Suggested tests
181+- Image reference validation table tests.
182+- Config tests for `allowed_registries`.
183+- Container service tests ensuring rejected images do not call `PullImage`.
184+
185+## Docker networks and runtime hardening
186+
187+### Normal behavior to preserve
188+- Default/compat profile keeps common apps and attachments working.
189+- Attachments remain reachable by service alias where intended.
190+- Network groups still work when configured.
191+
192+### Security behavior to verify
193+- `internal` network option is applied when configured.
194+- Strict profile applies readonly rootfs/user/caps as designed.
195+- `no-new-privileges:true` and cap dropping are preserved.
196+- Compat profile behavior is documented and tested.
197+
198+### Suggested tests
199+- Docker adapter config construction tests.
200+- Container service tests for network option propagation.
201+- Manual smoke: deploy app + postgres attachment in compat profile.
202+- Manual smoke: deploy simple read-only app in strict profile.
203+
204+## Install, Dockerfile, and supply-chain
205+
206+### Normal behavior to preserve
207+- `install.sh` installs the binary on Linux/macOS.
208+- GitHub composite action installs Gordon in CI.
209+- Dockerfile builds successfully.
210+
211+### Security behavior to verify
212+- Tar extraction only extracts `gordon`.
213+- Installed binary must be a regular file, not symlink/directory.
214+- Permissions are explicit (`0755`).
215+- Checksums/signatures are verified consistently.
216+- Gitleaks output is clean or only contains allowlisted examples.
217+
218+### Suggested tests
219+- Shellcheck/manual review for install script.
220+- Action script dry-run if practical.
221+- Docker build smoke after version alignment.
222+- Gitleaks on `git archive HEAD`, not local `.worktrees`.
@@ -0,0 +1,222 @@
1+# Security Hardening Regression Matrix
2+
3+This matrix lists the behaviors that should be tested while implementing the security-hardening fixes from the audit. It is intentionally focused on regressions: normal Gordon workflows must continue to work while exploit paths are blocked.
4+
5+## Registry uploads
6+
7+### Normal behavior to preserve
8+- Docker push of a small image succeeds.
9+- Docker push with multiple layers succeeds when every layer is below the configured cumulative blob limit.
10+- Chunked blob upload succeeds when the cumulative size remains below the new cumulative limit setting, tentatively named `max_blob_size` unless implementation chooses a clearer config name.
11+- A final `PUT` with a valid digest finalizes the blob and returns OCI-compatible headers.
12+- Existing `max_blob_chunk_size` behavior still rejects a single oversized chunk; this remains distinct from the cumulative per-blob/layer limit.
13+
14+### Security behavior to verify
15+- Repeated `PATCH` chunks cannot exceed the cumulative upload limit.
16+- `Content-Length` larger than the remaining allowed size is rejected before writing when possible.
17+- A rejected upload does not leave unbounded stale data on disk.
18+- Cancel/cleanup paths remove upload temp files and upload locks.
19+- Error response remains Docker Registry compatible (`413` / `SIZE_INVALID`).
20+
21+### Suggested tests
22+- Unit tests for filesystem blob append under and over cumulative size.
23+- Handler tests for `PATCH` and final `PUT` over limit.
24+- Integration/manual: push image with a large layer and confirm failure is clean.
25+
26+## Deploy errors and logs
27+
28+### Normal behavior to preserve
29+- Successful deploy response still includes status, domain, and container ID.
30+- Failed deploy still returns useful high-level error/cause/hint.
31+- Operators with the new logs permission can still retrieve logs through `/admin/logs`.
32+- CLI deploy displays helpful failure information without leaking raw secrets.
33+
34+### Security behavior to verify
35+- A token with only `admin:config:write` cannot receive raw container logs in `/admin/deploy` error responses.
36+- Logs returned through log endpoints are redacted for common secret patterns.
37+- `admin:status:read` no longer grants log read access.
38+- `admin:*:*` remains compatible and can access logs.
39+
40+### Suggested tests
41+- Handler tests for deploy error response with and without `logs:read`.
42+- Scope tests for `logs:read`, wildcard, and old `status:read` behavior.
43+- Redactor table tests for `PASSWORD=`, `TOKEN=`, `SECRET=`, `API_KEY=`, `DATABASE_URL=`, and simple JSON fields.
44+
45+## Admin scopes and compatibility
46+
47+### Normal behavior to preserve
48+- Existing `admin:*:*` tokens continue to work everywhere.
49+- Existing `routes`, `config`, `secrets`, and `status` scopes keep their intended behavior.
50+- Token exchange through `/auth/token` still works for admin scopes.
51+
52+### Security behavior to verify
53+- New sensitive resources such as `logs` and possibly `volumes` are parsed and enforced.
54+- Unknown or malformed scopes remain ignored/denied, not accidentally allowed.
55+
56+### Suggested tests
57+- Domain auth scope parsing tests.
58+- Admin middleware/handler tests for each sensitive endpoint.
59+
60+## Auto-route env-file extraction
61+
62+### Normal behavior to preserve
63+- Valid `gordon.env-file` imports default env values for a newly created auto-route.
64+- Existing env values still override image-provided defaults.
65+- Secret reference rejection remains intact.
66+- Auto-route without env-file remains unchanged.
67+
68+### Security behavior to verify
69+- Oversized env files are rejected before full memory read.
70+- Disallowed paths (`/proc`, `/sys`, `/dev`, `/run`, `/var/run`, `/`, traversal) are rejected.
71+- Failed env-file import does not prevent route/deploy behavior beyond the intended warning path unless explicitly designed.
72+
73+### Suggested tests
74+- Runtime extraction tests using a controlled reader larger than the limit.
75+- Auto-route usecase tests for allowed/disallowed paths.
76+- Tests confirming `${pass:...}` / `${sops:...}` refs remain rejected.
77+
78+## Domain canonicalization and proxy routing
79+
80+### Normal behavior to preserve
81+- Existing lowercase route domains continue to resolve.
82+- Registry domain still routes to registry.
83+- External routes still resolve and retain SSRF protections.
84+- Hostnames with expected public domains work through proxy.
85+
86+### Security behavior to verify
87+- `App.Example.com` and `app.example.com` map to the same canonical route.
88+- Duplicate case-variant routes cannot be created.
89+- Host with invalid port/trailing dot/case is handled consistently.
90+- HTTP-to-HTTPS redirect does not reflect arbitrary unconfigured Host values.
91+
92+### Suggested tests
93+- Config service tests for add/update/list canonical route domains.
94+- Auto-route tests for case variants.
95+- Proxy handler tests for Host normalization.
96+- Middleware tests for redirect target host validation.
97+
98+## Volumes
99+
100+### Normal behavior to preserve
101+- Listing volumes still shows Gordon-managed volumes.
102+- Dry-run prune still reports what would be removed.
103+- Actual prune still removes unused Gordon-managed volumes.
104+- In-use Gordon volumes are not removed.
105+
106+### Security behavior to verify
107+- Unused non-Gordon Docker volumes are not removed.
108+- Volumes without explicit Gordon ownership metadata, preferably `gordon.managed=true`, are ignored; prefix-only matching is unsafe except as a constrained legacy fallback with dedicated tests.
109+- Any new `volumes:read/write` scope is enforced correctly.
110+
111+### Suggested tests
112+- Volume service tests with mixed Gordon/non-Gordon, used/unused volumes.
113+- Admin handler tests for permissions.
114+- Manual: create an unrelated Docker volume, run Gordon prune dry-run/real, confirm it remains.
115+
116+## Backups
117+
118+### Normal behavior to preserve
119+- Manual backup completes and appears in list.
120+- Scheduled backup path format remains readable/listable.
121+- Backup detect still finds supported DB attachments.
122+- Existing backups can still be listed after filename format changes.
123+
124+### Security behavior to verify
125+- Two backups for same domain/db/schedule in the same second do not share tmp/final filenames.
126+- Concurrent backups do not truncate each other.
127+- Store uses unique tmp files and atomic rename.
128+- Path traversal protections remain intact.
129+
130+### Suggested tests
131+- Filesystem storage tests with identical timestamps.
132+- Concurrent store test with two readers and same domain/db/schedule.
133+- List tests for old and new filename formats if compatibility is needed.
134+
135+## Secrets and pass migration
136+
137+### Normal behavior to preserve
138+- `pass` backend still migrates existing env keys correctly.
139+- Attachment env migration still works.
140+- Unsafe backend still loads valid files under `dataDir/secrets` for development.
141+- Existing env file parsing remains compatible.
142+
143+### Security behavior to verify
144+- Unsafe backend rejects `../` and absolute `auth.token_secret` paths.
145+- Migration does not leave plaintext `.env.migrated` by default.
146+- If an explicit keep/quarantine option exists, files are stored with restrictive permissions and loud warnings.
147+
148+### Suggested tests
149+- `loadSecret` path traversal tests.
150+- Migration tests verifying plaintext source file removal/quarantine.
151+- Permission tests where practical (`0600` files, `0700` dirs).
152+
153+## Config snapshot
154+
155+### Normal behavior to preserve
156+- `/admin/config` still returns enough information for CLI/admin use.
157+- Routes and network isolation status remain visible to authorized callers.
158+
159+### Security behavior to verify
160+- `data_dir` is hidden by default or only available under a deliberately stronger detailed mode.
161+- External route targets are hidden/redacted by default or gated.
162+- `config:read` behavior remains documented.
163+
164+### Suggested tests
165+- Admin config response tests for default and detailed modes.
166+- CLI remote config tests if output shape changes.
167+
168+## External image registries
169+
170+### Normal behavior to preserve
171+- Images in the Gordon registry deploy normally.
172+- Explicitly allowlisted external registries deploy if configured.
173+- Attachments still deploy from allowed sources.
174+
175+### Security behavior to verify
176+- `localhost`, loopback, private IP, link-local, metadata IP, and DNS resolving to private ranges are rejected unless a deliberate unsafe override exists.
177+- Non-allowlisted external registries are rejected before Docker pull.
178+- Error messages do not leak sensitive network details.
179+
180+### Suggested tests
181+- Image reference validation table tests.
182+- Config tests for `allowed_registries`.
183+- Container service tests ensuring rejected images do not call `PullImage`.
184+
185+## Docker networks and runtime hardening
186+
187+### Normal behavior to preserve
188+- Default/compat profile keeps common apps and attachments working.
189+- Attachments remain reachable by service alias where intended.
190+- Network groups still work when configured.
191+
192+### Security behavior to verify
193+- `internal` network option is applied when configured.
194+- Strict profile applies readonly rootfs/user/caps as designed.
195+- `no-new-privileges:true` and cap dropping are preserved.
196+- Compat profile behavior is documented and tested.
197+
198+### Suggested tests
199+- Docker adapter config construction tests.
200+- Container service tests for network option propagation.
201+- Manual smoke: deploy app + postgres attachment in compat profile.
202+- Manual smoke: deploy simple read-only app in strict profile.
203+
204+## Install, Dockerfile, and supply-chain
205+
206+### Normal behavior to preserve
207+- `install.sh` installs the binary on Linux/macOS.
208+- GitHub composite action installs Gordon in CI.
209+- Dockerfile builds successfully.
210+
211+### Security behavior to verify
212+- Tar extraction only extracts `gordon`.
213+- Installed binary must be a regular file, not symlink/directory.
214+- Permissions are explicit (`0755`).
215+- Checksums/signatures are verified consistently.
216+- Gitleaks output is clean or only contains allowlisted examples.
217+
218+### Suggested tests
219+- Shellcheck/manual review for install script.
220+- Action script dry-run if practical.
221+- Docker build smoke after version alignment.
222+- Gitleaks on `git archive HEAD`, not local `.worktrees`.

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close