Skip to content

Proxy Host ACL and Security Headers drop down hotfix#776

Merged
Wikid82 merged 16 commits intodevelopmentfrom
feature/beta-release
Feb 28, 2026
Merged

Proxy Host ACL and Security Headers drop down hotfix#776
Wikid82 merged 16 commits intodevelopmentfrom
feature/beta-release

Conversation

@Wikid82
Copy link
Owner

@Wikid82 Wikid82 commented Feb 27, 2026

No description provided.

@Wikid82 Wikid82 self-assigned this Feb 27, 2026
@Wikid82 Wikid82 added bug Something isn't working critical Must have for the release, blocks other work labels Feb 27, 2026
@Wikid82 Wikid82 added this to Charon Feb 27, 2026
@github-project-automation github-project-automation bot moved this to Backlog in Charon Feb 27, 2026
@Wikid82 Wikid82 moved this from Backlog to In Progress in Charon Feb 27, 2026
@github-advanced-security
Copy link

This pull request sets up GitHub code scanning for this repository. Once the scans have completed and the checks have passed, the analysis results for this pull request branch will appear on this overview. Once you merge this pull request, the 'Security' tab will show more code scanning analysis results (for example, for the default branch). Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results. For more information about GitHub code scanning, check out the documentation.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 27, 2026

✅ Supply Chain Verification Results

PASSED

📦 SBOM Summary

  • Components: 1671

🔍 Vulnerability Scan

Severity Count
🔴 Critical 0
🟠 High 0
🟡 Medium 10
🟢 Low 3
Total 13

📎 Artifacts

  • SBOM (CycloneDX JSON) and Grype results available in workflow artifacts

Generated by Supply Chain Verification workflow • View Details

@codecov
Copy link

codecov bot commented Feb 27, 2026

Codecov Report

❌ Patch coverage is 95.65217% with 10 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ackend/internal/api/handlers/proxy_host_handler.go 87.50% 5 Missing and 4 partials ⚠️
frontend/src/components/ProxyHostForm.tsx 98.93% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

actions-user and others added 11 commits February 28, 2026 03:34
- Add tests to normalize string numeric ACL IDs in AccessListSelector.
- Implement regression tests for ProxyHostForm to ensure numeric ACL values are submitted correctly.
- Introduce a recovery function for ACL lockout scenarios in auth setup.
- Create new tests for ACL creation and security header profiles to ensure dropdown coverage.
- Add regression tests for ACL and Security Headers dropdown behavior in ProxyHostForm.
- Establish a security shard setup to validate emergency token configurations and reset security states.
- Enhance emergency operations tests to ensure ACL selections persist across create/edit flows.
…n-major-updates

fix(deps): update module github.com/gin-gonic/gin to v1.12.0 (feature/beta-release)
@Wikid82 Wikid82 moved this from In Progress to In Review in Charon Feb 28, 2026
…onents

- Added new test suite for AccessListSelector to cover token normalization and emitted values.
- Updated existing tests for AccessListSelector to handle prefixed and numeric-string form values.
- Introduced tests for ProxyHostForm to validate DNS detection, including error handling and success scenarios.
- Enhanced ProxyHostForm tests to cover token normalization for security headers and ensure proper handling of existing host values.
- Implemented additional tests for ProxyHostForm to verify domain updates based on selected containers and prompt for new base domains.
@Wikid82 Wikid82 marked this pull request as ready for review February 28, 2026 22:32
Copilot AI review requested due to automatic review settings February 28, 2026 22:32
@Wikid82 Wikid82 merged commit 386fcd8 into development Feb 28, 2026
40 checks passed
@github-project-automation github-project-automation bot moved this from In Review to Done in Charon Feb 28, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This hotfix addresses a bug where ACL and Security Headers dropdown selections in the Proxy Host create/edit form were not being persisted after save or surviving a modal reopen. The root cause was in the frontend select binding (value coercion between string/number/null) and the backend's lack of UUID-to-ID resolution for these fields. The fix introduces a token-based selection system (id:N/uuid:X/none) consistently across the form components, adds UUID resolution on the backend for both fields, preloads AccessList associations, and adds comprehensive tests.

Changes:

  • Frontend: Token-based select value system in AccessListSelector.tsx and ProxyHostForm.tsx to correctly handle number | string | null form values across create/edit flows; z-index and pointer-events fix in Select.tsx
  • Backend: UUID-to-ID resolution in proxy_host_handler.go for both access_list_id and security_header_profile_id; AccessList preload added to proxyhost_service.go; minor dependency upgrades in go.mod/go.sum
  • Tests: Comprehensive frontend unit, backend handler, and Playwright E2E tests for ACL/security headers dropdown regression; restructured global/security-shard test setup

Reviewed changes

Copilot reviewed 28 out of 30 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
frontend/src/components/ui/Select.tsx Raises z-index to z-[70] and adds pointer-events-auto to fix dropdown click blocking
frontend/src/components/AccessListSelector.tsx Full rewrite to token-based value handling supporting `number
frontend/src/components/ProxyHostForm.tsx Extracts buildInitialFormData, adds normalization/token helpers, fixes security headers select binding
frontend/src/api/proxyHosts.ts Widens access_list_id and security_header_profile_id to `number
backend/internal/api/handlers/proxy_host_handler.go Adds resolveAccessListReference and resolveSecurityHeaderProfileReference with UUID-to-ID lookup
backend/internal/services/proxyhost_service.go Adds AccessList preload in GetByUUID and List
backend/go.mod / backend/go.sum Upgrades gin, sonic, go-yaml, ugorji codec; adds MongoDB driver as indirect dep
tests/global-setup.ts Removes emergency token validation and security reset from global setup
tests/security-shard.setup.ts New file: dedicated security shard setup with emergency reset and verification
tests/auth.setup.ts Adds recoverFromAclLockout helper for 403 recovery during auth setup
playwright.config.js Adds security-shard-setup project as dependency for security tests
tests/security-enforcement/acl-dropdown-regression.spec.ts New E2E regression spec for ACL/security headers persistence
tests/security-enforcement/acl-creation.spec.ts New E2E spec for ACL and security header profile baseline creation
tests/security/emergency-operations.spec.ts Adds helper functions and new ACL dropdown parity regression test
tests/proxy-host-dropdown-fix.spec.ts Moves old tests under .skip, rewrites with new helpers
frontend/src/components/__tests__/ProxyHostForm.test.tsx New unit tests for presets, security headers token resolution, domain handling
frontend/src/components/__tests__/ProxyHostForm-dropdown-changes.test.tsx New unit tests for null transitions, host switching, UUID-only payloads
frontend/src/components/__tests__/ProxyHostForm-token-coverage.test.tsx New file: focused token normalization branch coverage
frontend/src/components/__tests__/ProxyHostForm-uptime.test.tsx New test for sync error fallback toast
frontend/src/components/__tests__/ProxyHostForm-dns.test.tsx New DNS detection branch tests
frontend/src/components/__tests__/AccessListSelector.test.tsx Extensive new tests for token value handling
frontend/src/components/__tests__/AccessListSelector-token-coverage.test.tsx New file: token branch coverage with Select mock
backend/internal/api/handlers/proxy_host_handler_test.go New tests for resolveAccessListReference, resolveSecurityHeaderProfileReference, and create reference resolution
backend/internal/api/handlers/proxy_host_handler_update_test.go New tests for ACL and security header ID transition scenarios
docs/plans/current_spec.md Replaced with ACL+Security Headers hotfix specification
docs/issues/manual_test_acl_security_headers_dropdown_hotfix.md New manual test plan
CHANGELOG.md Adds hotfix entry
Files not reviewed (1)
  • frontend/package-lock.json: Language not supported

Comment on lines +209 to +245
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("access list not found")
}
return nil, fmt.Errorf("failed to resolve access list")
}

id := acl.ID
return &id, nil
}

func (h *ProxyHostHandler) resolveSecurityHeaderProfileReference(value any) (*uint, error) {
if value == nil {
return nil, nil
}

parsedID, _, parseErr := parseNullableUintField(value, "security_header_profile_id")
if parseErr == nil {
return parsedID, nil
}

uuidValue, isString := value.(string)
if !isString {
return nil, parseErr
}

trimmed := strings.TrimSpace(uuidValue)
if trimmed == "" {
return nil, nil
}

if _, err := uuid.Parse(trimmed); err != nil {
return nil, parseErr
}

var profile models.SecurityHeaderProfile
if err := h.db.Select("id").Where("uuid = ?", trimmed).First(&profile).Error; err != nil {
if err == gorm.ErrRecordNotFound {
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new resolveAccessListReference and resolveSecurityHeaderProfileReference functions use err == gorm.ErrRecordNotFound for error comparison, but the established codebase convention is to use errors.Is(err, gorm.ErrRecordNotFound) (as seen consistently in security_handler.go and settings_handler.go). Using direct equality (==) instead of errors.Is can fail to match wrapped errors, which means the "not found" branch may never be taken and the function would return a generic "failed to resolve" error instead of the more specific "not found" message.

Copilot uses AI. Check for mistakes.
Comment on lines +176 to +189
function normalizeSecurityHeaderReference(value: unknown): number | string | null | undefined {
const numericValue = normalizeNullableID(value)
if (numericValue !== undefined) {
return numericValue
}

if (typeof value !== 'string') {
return undefined
}

const trimmed = value.trim()
return trimmed === '' ? null : trimmed
}

Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The two functions normalizeAccessListReference and normalizeSecurityHeaderReference in ProxyHostForm.tsx have completely identical implementations. This is unnecessary code duplication — one shared utility function would suffice and reduce maintenance burden. If the intention is to allow divergence in the future, at minimum the functions are currently indistinguishable and should be consolidated.

Suggested change
function normalizeSecurityHeaderReference(value: unknown): number | string | null | undefined {
const numericValue = normalizeNullableID(value)
if (numericValue !== undefined) {
return numericValue
}
if (typeof value !== 'string') {
return undefined
}
const trimmed = value.trim()
return trimmed === '' ? null : trimmed
}
const normalizeSecurityHeaderReference = normalizeAccessListReference

Copilot uses AI. Check for mistakes.
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new transitive dependency go.mongodb.org/mongo-driver/v2 v2.5.0 appears in go.mod as an indirect dependency. MongoDB driver is unexpected for what appears to be a SQLite/GORM-based backend. This likely came in transitively via the gin v1.12.0 upgrade. The presence of a large, unneeded transitive dependency should be verified — if it's not actually needed, go mod tidy should be run to confirm its necessity or remove it. This significantly increases binary size and attack surface.

Suggested change
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect

Copilot uses AI. Check for mistakes.
Comment on lines +202 to +216
trimmed := strings.TrimSpace(uuidValue)
if trimmed == "" {
return nil, nil
}

var acl models.AccessList
if err := h.db.Select("id").Where("uuid = ?", trimmed).First(&acl).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("access list not found")
}
return nil, fmt.Errorf("failed to resolve access list")
}

id := acl.ID
return &id, nil
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The resolveAccessListReference function does not validate UUID format before attempting the database query (unlike resolveSecurityHeaderProfileReference which validates with uuid.Parse). This means any arbitrary non-numeric string (e.g. a malformed UUID or an SQL injection attempt) will be passed directly to the WHERE clause. While GORM uses parameterized queries preventing SQL injection, the asymmetry between the two resolve functions is a design inconsistency. If the intent is to accept only valid UUIDs as string references, UUID validation should be added here as well.

Copilot uses AI. Check for mistakes.
Comment on lines +126 to +127
access_list_id: host?.access_list?.uuid ?? host?.access_list_id,
security_header_profile_id: host?.security_header_profile?.uuid ?? host?.security_header_profile_id,
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The buildInitialFormData function uses host?.access_list?.uuid ?? host?.access_list_id to initialize the form. This means that when editing a host that has a non-null access_list_id but whose preloaded access_list object is absent (e.g., due to an ACL being deleted), the form will fall back to the raw access_list_id (which may be a numeric ID). However, the resolveSelectToken function later converts numeric IDs to id:N tokens, so the SelectItem values will not match unless the selector also builds id:N tokens. This should work correctly based on the current code, but it's a subtle dependency worth noting in code comments for future maintainers.

Copilot uses AI. Check for mistakes.
Comment on lines +259 to +262

useEffect(() => {
setFormData(buildInitialFormData(host))
}, [host?.uuid])
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useEffect that resets form data on host?.uuid change (line 260-262) could cause problems for newly created hosts that don't yet have a UUID. In create mode (host is undefined), host?.uuid is undefined, so the effect will only run on mount. However, if a host is being created and somehow the parent component passes a partial host object initially without a UUID and then adds one, the form state won't reset. More critically, if the same form component is reused for two different create operations in sequence where neither has a UUID, the form state won't reset. Depending on how the component is used, this edge case may or may not be an issue, but it warrants review.

Suggested change
useEffect(() => {
setFormData(buildInitialFormData(host))
}, [host?.uuid])
const hostToken = host ? getEntityToken(host) : null
useEffect(() => {
setFormData(buildInitialFormData(host))
}, [hostToken])

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +77
const statusContext = await playwrightRequest.newContext({
baseURL,
extraHTTPHeaders: {
'X-Emergency-Token': emergencyToken,
},
});

try {
const response = await statusContext.get('/api/v1/security/status', { timeout: 5000 });
expect(response.ok()).toBeTruthy();

const status = await response.json();
expect(status.acl?.enabled).toBeFalsy();
expect(status.waf?.enabled).toBeFalsy();
expect(status.rate_limit?.enabled).toBeFalsy();
} finally {
await statusContext.dispose();
}
Copy link

Copilot AI Feb 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In security-shard.setup.ts, the verifySecurityDisabled function passes the raw emergencyToken in extraHTTPHeaders for the status request. This exposes the emergency token in request headers for a non-emergency endpoint (/api/v1/security/status). While this appears to be a test setup file, tokens should not be sent to endpoints that don't require them. The status check should either use proper authentication (credentials like the username/password used for the reset) or omit the emergency token header.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working critical Must have for the release, blocks other work

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

3 participants