Call plain HTTP critical only when Pulse is publicly reachable

The Security Overview tagged the HTTPS tile red and Critical on every
install, directly above a hardening card that calls HTTPS Recommended on
a private network and says local HTTP is acceptable. The same flag also
counted plain HTTP as a missing critical control in the posture score, so
a typical LAN install read 55 percent.

The tile now uses the hardening card's rule: critical once the instance
is publicly reachable, recommended otherwise. A private install with a
password and protected exports reads 79 percent, still Moderate, and a
public one still shows HTTPS as Critical.
This commit is contained in:
rcourtman
2026-10-05 07:03:03 +01:00
parent fdcffc6411
commit 85bf454ba3
6 changed files with 74 additions and 2 deletions
@@ -859,6 +859,10 @@ tokens, and path-normalization variants.
placeholders on the shared `SettingsLoadingSkeleton` primitive. This
subsystem owns the security/privacy posture semantics; frontend-primitives
owns skeleton animation, fill tokens, and placeholder shell consistency.
Posture severity is one rule across the Security Overview: the HTTPS tile
counts as critical, in its label and in the posture score, only when the
instance is publicly reachable, matching the hardening step that calls
HTTPS critical for public access and recommended on a private network.
6a. Keep API token refresh/loading indicators on the shared `LoadingSpinner`
primitive. Security/privacy owns the token-management trust copy and
refresh semantics; frontend-primitives owns spinner shell, tone, and
@@ -0,0 +1,33 @@
{
"version": 1,
"base_sha": "fdcffc64114bd9d7ff7a92324391a09702c3c26a",
"verified_at": "2026-10-05T06:03:02Z",
"result": "passed",
"changed_paths": [
"frontend-modern/src/utils/securityScorePresentation.ts"
],
"content_sha256": {
"frontend-modern/src/utils/securityScorePresentation.ts": "8a49852f591edd3e75bf4a2b1288a6ba71bf54259feffddbc0b3659d06ce6e0c"
},
"routes": [
"/settings/security-overview"
],
"viewports": [
{
"width": 1440,
"height": 1000
},
{
"width": 390,
"height": 1000
}
],
"states": [
"Private-network instance at 1440: the HTTPS tile is neutral with no Critical tag, matching the Plan HTTPS before live use card below it marked Recommended. Before, the tile was red and tagged Critical above that same Recommended card",
"Posture score reads 79 percent Moderate (password and export protection on, plain HTTP on a private network), where before plain HTTP pulled it to 55 percent",
"Phone at 390: the same neutral HTTPS tile and 79 percent Moderate header"
],
"interactions": [
"Compared the posture grid with the hardening steps on the same page"
]
}
@@ -47,4 +47,32 @@ describe('SecurityPostureSummary', () => {
expect(screen.getByText('Weak')).toBeInTheDocument();
});
it('only calls plain HTTP critical once the instance is publicly reachable', () => {
const status = {
hasAuthentication: true,
ssoEnabled: false,
hasProxyAuth: false,
apiTokenConfigured: false,
exportProtected: true,
unprotectedExportAllowed: false,
hasHTTPS: false,
hasAuditLogging: false,
requiresAuth: true,
};
const { unmount } = render(() => (
<SecurityPostureSummary status={{ ...status, publicAccess: false, isPrivateNetwork: true }} />
));
// Private network: HTTPS is a recommended step, so no Critical tag and the
// critical share of the score is complete (0.7 + 2 of 7 x 0.3 = 79).
expect(screen.queryByText('Critical')).toBeNull();
expect(screen.getByText('79%')).toBeInTheDocument();
unmount();
render(() => (
<SecurityPostureSummary status={{ ...status, publicAccess: true, isPrivateNetwork: false }} />
));
expect(screen.getByText('Critical')).toBeInTheDocument();
expect(screen.getByText('55%')).toBeInTheDocument();
});
});
@@ -271,11 +271,12 @@ describe('securityScorePresentation — branch coverage (batch 0718)', () => {
description: 'Token + passphrase required',
critical: true,
});
// Private network (publicAccess false): HTTPS is recommended, not critical.
expect(byKey.https).toMatchObject({
label: 'HTTPS',
enabled: false,
description: 'HTTP only',
critical: true,
critical: false,
});
expect(byKey.audit).toMatchObject({
label: 'Audit log',
@@ -271,6 +271,8 @@ describe('securityScorePresentation', () => {
key: 'export',
description: 'Token + passphrase required',
}),
// Publicly reachable, so HTTPS counts as critical.
expect.objectContaining({ key: 'https', critical: true }),
]),
);
@@ -394,7 +394,11 @@ export function getSecurityPostureItems(status: SecurityPostureStatus): Security
label: 'HTTPS',
enabled: Boolean(status.hasHTTPS),
description: status.hasHTTPS ? 'Encrypted' : 'HTTP only',
critical: true,
// Same rule as the hardening steps below the grid: plain HTTP is
// critical once the instance is publicly reachable, and a recommended
// step on a private network. Marking it critical there put a red
// Critical tile above a Recommended card for the same control.
critical: Boolean(status.publicAccess),
},
{
key: 'audit',