diff --git a/docs/release-control/v6/internal/subsystems/security-privacy.md b/docs/release-control/v6/internal/subsystems/security-privacy.md
index 241e41af8d..beba19b3f5 100644
--- a/docs/release-control/v6/internal/subsystems/security-privacy.md
+++ b/docs/release-control/v6/internal/subsystems/security-privacy.md
@@ -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
diff --git a/frontend-modern/browser-verification/bc41850a607c7eb3.json b/frontend-modern/browser-verification/bc41850a607c7eb3.json
new file mode 100644
index 0000000000..cf8bd3ffdc
--- /dev/null
+++ b/frontend-modern/browser-verification/bc41850a607c7eb3.json
@@ -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"
+ ]
+}
diff --git a/frontend-modern/src/components/Settings/__tests__/SecurityPostureSummary.test.tsx b/frontend-modern/src/components/Settings/__tests__/SecurityPostureSummary.test.tsx
index 722244c02c..e8f5dbb6e3 100644
--- a/frontend-modern/src/components/Settings/__tests__/SecurityPostureSummary.test.tsx
+++ b/frontend-modern/src/components/Settings/__tests__/SecurityPostureSummary.test.tsx
@@ -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(() => (
+
+ ));
+ // 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(() => (
+
+ ));
+ expect(screen.getByText('Critical')).toBeInTheDocument();
+ expect(screen.getByText('55%')).toBeInTheDocument();
+ });
});
diff --git a/frontend-modern/src/utils/__tests__/securityScorePresentation.branchcov0718.test.ts b/frontend-modern/src/utils/__tests__/securityScorePresentation.branchcov0718.test.ts
index f9d844f4e4..61bd723374 100644
--- a/frontend-modern/src/utils/__tests__/securityScorePresentation.branchcov0718.test.ts
+++ b/frontend-modern/src/utils/__tests__/securityScorePresentation.branchcov0718.test.ts
@@ -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',
diff --git a/frontend-modern/src/utils/__tests__/securityScorePresentation.test.ts b/frontend-modern/src/utils/__tests__/securityScorePresentation.test.ts
index ade547f02e..b3b8325906 100644
--- a/frontend-modern/src/utils/__tests__/securityScorePresentation.test.ts
+++ b/frontend-modern/src/utils/__tests__/securityScorePresentation.test.ts
@@ -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 }),
]),
);
diff --git a/frontend-modern/src/utils/securityScorePresentation.ts b/frontend-modern/src/utils/securityScorePresentation.ts
index 67153323c5..4c19772017 100644
--- a/frontend-modern/src/utils/securityScorePresentation.ts
+++ b/frontend-modern/src/utils/securityScorePresentation.ts
@@ -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',