14 KiB
ACL + Security Headers Hotfix Plan (Proxy Host Create/Edit)
1. Introduction
Overview
Hotfix request: Proxy Host form dropdown selections for Access Control List (ACL) and Security Headers are not being applied/persisted for new or edited hosts.
Reported behavior:
- Existing hosts with previously assigned ACL/Security Header profile retain old values.
- Users cannot reliably remove or change those values in UI.
- Newly created hosts cannot reliably apply ACL/Security Header profile.
Objective
Deliver an urgent but correct root-cause fix across frontend binding and backend persistence flow, with minimum user interruption and full validation gates.
2. Research Findings (Current Architecture + Touchpoints)
Frontend Entry Points
frontend/src/pages/ProxyHosts.tsxhandleSubmit(data)callsupdateHost(editingHost.uuid, data)orcreateHost(data).- Renders
ProxyHostFormmodal for create/edit flows.
frontend/src/components/ProxyHostForm.tsx- Local form state initializes
access_list_idandsecurity_header_profile_id. - ACL control uses
AccessListSelector. - Security Headers control uses
Selectwithsecurity_header_profile_idmapping. - Submission path:
handleSubmit->onSubmit(payloadWithoutUptime).
- Local form state initializes
frontend/src/components/AccessListSelector.tsx- Converts select values between
stringandnumber | null.
- Converts select values between
Frontend API/Hooks
frontend/src/hooks/useProxyHosts.tscreateHost->createProxyHost.updateHost->updateProxyHost.
frontend/src/api/proxyHosts.tscreateProxyHost(host: Partial<ProxyHost>)->POST /api/v1/proxy-hosts.updateProxyHost(uuid, host)->PUT /api/v1/proxy-hosts/:uuid.- Contract fields:
access_list_id,security_header_profile_id.
Backend Entry/Transformation/Persistence
- Route registration
backend/internal/api/routes/routes.go:proxyHostHandler.RegisterRoutes(protected).
- Handler
backend/internal/api/handlers/proxy_host_handler.goCreate(c)usesShouldBindJSON(&models.ProxyHost{}).Update(c)usesmap[string]anypartial update parsing.- Target fields:
payload["access_list_id"]->parseNullableUintField->host.AccessListIDpayload["security_header_profile_id"]-> typed conversion ->host.SecurityHeaderProfileID
- Service
backend/internal/services/proxyhost_service.goCreate(host)validates +db.Create(host).Update(host)validates +db.Model(...).Select("*").Updates(host).
- Model
backend/internal/models/proxy_host.goAccessListID *uint \json:"access_list_id"``SecurityHeaderProfileID *uint \json:"security_header_profile_id"``
Existing Tests Relevant to Incident
- Frontend unit regression coverage already exists:
frontend/src/components/__tests__/ProxyHostForm-dropdown-changes.test.tsx
- E2E regression spec exists:
tests/security-enforcement/acl-dropdown-regression.spec.ts
- Backend update and security-header tests exist:
backend/internal/api/handlers/proxy_host_handler_update_test.gobackend/internal/api/handlers/proxy_host_handler_security_headers_test.go
3. Root-Cause-First Trace
Trace Model (Mandatory)
- Entry Point:
- UI dropdown interactions in
ProxyHostFormandAccessListSelector.
- UI dropdown interactions in
- Transformation:
- Form state conversion (
string<->number | null) and payload construction inProxyHostForm. - API serialization via
frontend/src/api/proxyHosts.ts.
- Form state conversion (
- Persistence:
- Backend
Updateparser (proxy_host_handler.go) andProxyHostService.Updatepersistence.
- Backend
- Exit Point:
- Response body consumed by React Query invalidation/refetch in
useProxyHosts. - UI reflects updated values in table/form.
- Response body consumed by React Query invalidation/refetch in
Most Likely Failure Zones
- Frontend select binding/conversion drift (top candidate)
- Shared symptom across ACL and Security Headers points to form/select layer.
- Candidate files:
frontend/src/components/ProxyHostForm.tsxfrontend/src/components/AccessListSelector.tsxfrontend/src/components/ui/Select.tsx
- Payload mutation or stale form object behavior
- Ensure payload carries updated
access_list_id/security_header_profile_idvalues at submit time.
- Ensure payload carries updated
- Backend partial-update parser edge behavior
- Ensure
nil, numeric string, and number conversions are consistent between ACL and security header profile paths.
- Ensure
Investigation Decision
Root-cause verification will be instrumented through failing-first Playwright scenario and targeted handler tests before applying code changes.
4. EARS Requirements
- WHEN a user selects an ACL in the Proxy Host create/edit form, THE SYSTEM SHALL persist
access_list_idand return it in API response. - WHEN a user changes ACL from one value to another, THE SYSTEM SHALL replace prior
access_list_idwith the new value. - WHEN a user selects "No Access Control", THE SYSTEM SHALL persist
access_list_id = null. - WHEN a user selects a Security Headers profile in the Proxy Host create/edit form, THE SYSTEM SHALL persist
security_header_profile_idand return it in API response. - WHEN a user changes Security Headers profile from one value to another, THE SYSTEM SHALL replace prior
security_header_profile_idwith the new value. - WHEN a user selects "None" for Security Headers, THE SYSTEM SHALL persist
security_header_profile_id = null. - IF dropdown interaction fails to update internal form state, THEN THE SYSTEM SHALL prevent stale values from being persisted.
- WHILE updating Proxy Host settings, THE SYSTEM SHALL maintain existing behavior for unrelated fields and not regress certificate, DNS challenge, or uptime-linked updates.
Note: User-visible blocking error behavior is deferred unless required by confirmed root cause.
5. Technical Specification (Hotfix Scope)
API Contract (No Breaking Change)
POST /api/v1/proxy-hosts- Request fields include
access_list_id,security_header_profile_idas nullable numeric fields.
- Request fields include
PUT /api/v1/proxy-hosts/:uuid- Partial payload accepts nullable updates for both fields.
- Response must echo persisted values in snake_case:
access_list_idsecurity_header_profile_id
Data Model/DB
No schema migration expected. Existing nullable FK fields in backend/internal/models/proxy_host.go are sufficient.
Targeted Code Areas for Fix
- Frontend
frontend/src/components/ProxyHostForm.tsxfrontend/src/components/AccessListSelector.tsxfrontend/src/components/ui/Select.tsx(only if click/select propagation issue confirmed)frontend/src/api/proxyHosts.ts(only if serialization issue confirmed)
- Backend
backend/internal/api/handlers/proxy_host_handler.go(only if parsing/persistence mismatch confirmed)backend/internal/services/proxyhost_service.go(only if update write path proves incorrect)
6. Edge Cases
- Edit host with existing ACL/profile and switch to another value.
- Edit host with existing ACL/profile and clear to null.
- Create new host with ACL/profile set before first save.
- Submit with stringified numeric values (defensive compatibility).
- Submit with null values for both fields simultaneously.
- Missing/deleted profile or ACL IDs in backend (validation errors).
- Multiple rapid dropdown changes before save (last selection wins).
7. Risk Analysis
High Risk
- Silent stale-state submission from form controls.
- Regressing other Proxy Host settings due to broad payload mutation.
Medium Risk
- Partial-update parser divergence between ACL and security profile behavior.
- UI select portal/z-index interaction causing non-deterministic click handling.
Mitigations
- Reproduce with Playwright first and capture exact failing action path.
- Add/strengthen focused frontend tests around create/edit/clear flows.
- Add/strengthen backend tests for nullable + conversion paths.
- Keep hotfix minimal and avoid unrelated refactors.
8. Implementation Plan (Urgent, Minimal Interruption)
Phase 1: Reproduction + Guardrails (Playwright First)
- Execute targeted E2E spec for dropdown flow and create/edit persistence behavior.
- Capture exact failure step and confirm whether failure is click binding, payload value, or backend persistence.
- Add/adjust failing-first test if current suite does not capture observed production regression.
Phase 2: Frontend Fix
- Patch select binding/state mapping for ACL and Security Headers in
ProxyHostForm/AccessListSelector. - If needed, patch
ui/Selectinteraction layering. - Ensure payload contains correct final
access_list_idandsecurity_header_profile_idvalues at submit. - Extend
ProxyHostFormtests for create/edit/change/remove flows.
Phase 3: Backend Hardening (Conditional)
- Only if frontend payload is correct but persistence is wrong:
- Backend fix MUST use field-scoped partial-update semantics for
access_list_idandsecurity_header_profile_idonly (unless separately justified). - Ensure write path persists null transitions reliably.
- Backend fix MUST use field-scoped partial-update semantics for
- Add/adjust handler/service regression tests proving no unintended mutation of unrelated proxy host fields during these targeted updates.
Phase 4: Integration + Regression
- Run complete targeted Proxy Host UI flow tests.
- Validate list refresh and modal reopen reflect persisted values.
- Validate no regressions in bulk ACL / bulk security-header operations.
Phase 5: Documentation + Handoff
- Update changelog/release notes only for hotfix behavior.
- Keep architecture docs unchanged unless root cause requires architectural note.
- Handoff to Supervisor agent for review after plan approval and implementation.
9. Acceptance Criteria
- ACL dropdown selection persists on create and edit.
- Security Headers dropdown selection persists on create and edit.
- Clearing ACL persists
nulland is reflected after reload. - Clearing Security Headers persists
nulland is reflected after reload. - Existing hosts can change from one ACL/profile to another without stale value retention.
- New hosts can apply ACL/profile at creation time.
- No regressions in unrelated proxy host fields.
- All validation gates in Section 11 pass.
- API create response returns persisted
access_list_idandsecurity_header_profile_idmatching submitted values (includingnull). - API update response returns persisted
access_list_idandsecurity_header_profile_idaftervalue->value,value->null, andnull->valuetransitions. - Backend persistence verification confirms unrelated proxy host fields remain unchanged for targeted updates.
10. PR Slicing Strategy
Decision
Single PR (hotfix-first), with contingency split only if backend root cause is confirmed late.
Rationale
- Incident impact is immediate user-facing and concentrated in one feature path.
- Frontend + targeted backend/test changes are tightly coupled for verification.
- Single PR minimizes release coordination and user interruption.
Contingency (Only if split becomes necessary)
- PR-1: Frontend binding + tests
- Scope:
ProxyHostForm,AccessListSelector,ui/Select(if required), related tests. - Dependency: none.
- Acceptance: UI submit payload verified correct in unit + Playwright.
- Scope:
- PR-2: Backend parser/persistence + tests (conditional)
- Scope:
proxy_host_handler.go,proxyhost_service.go, handler/service tests. - Dependency: PR-1 merged or rebased for aligned contract.
- Acceptance: API update/create persist both nullable IDs correctly.
- Scope:
- PR-3: Regression hardening + docs
- Scope: extra regression coverage, release-note hotfix entry.
- Dependency: PR-1/PR-2.
- Acceptance: full DoD validation sequence passes.
11. Validation Plan (Mandatory Sequence)
- E2E environment prerequisite
- Determine rebuild necessity per testing policy: if application/runtime or Docker input changes are present, rebuild is required.
- If rebuild is required or the container is unhealthy, run
.github/skills/scripts/skill-runner.sh docker-rebuild-e2e. - Record container health outcome before executing tests.
- Playwright first
- Run targeted Proxy Host dropdown and create/edit persistence scenarios.
- Local patch coverage preflight
- Generate
test-results/local-patch-report.mdandtest-results/local-patch-report.json.
- Generate
- Unit and coverage
- Backend coverage run (threshold >= 85%).
- Frontend coverage run (threshold >= 85%).
- Type checks
- Frontend TypeScript check.
- Pre-commit
pre-commit run --all-fileswith zero blocking failures.
- Security scans
- CodeQL Go + JS (security-and-quality).
- Findings check gate.
- Trivy scan.
- Conditional GORM security scan if model/DB-layer changes are made.
- Build verification
- Backend build + frontend build pass.
12. File Review: .gitignore, codecov.yml, .dockerignore, Dockerfile
Assessment for this hotfix:
.gitignore: no required change for ACL/Security Headers hotfix.codecov.yml: no required change; current exclusions/thresholds are compatible..dockerignore: no required change unless new hotfix-only artifact paths are introduced.Dockerfile: no required change; incident is application logic/UI binding, not image build pipeline.
If implementation introduces new persistent test artifacts, update ignore files in the same PR.
13. Rollback and Contingency
- If hotfix causes regression in proxy host save flow, revert hotfix commit and redeploy prior stable build.
- If frontend-only fix is insufficient, activate conditional backend phase immediately.
- If validation gates fail on security/coverage, hold merge until fixed; no partial exception for this incident.
- Post-rollback smoke checks:
- Create host with ACL/profile.
- Edit to different ACL/profile values.
- Clear both values to
null. - Verify persisted values in API response and after UI reload.