Skip to content

fix: classification banner working for SPAs#2710

Open
joelmccoy wants to merge 3 commits into
mainfrom
fix-classification-banner
Open

fix: classification banner working for SPAs#2710
joelmccoy wants to merge 3 commits into
mainfrom
fix-classification-banner

Conversation

@joelmccoy

@joelmccoy joelmccoy commented May 29, 2026

Copy link
Copy Markdown
Contributor

Description

The classification banner was noticed to block traffic to Grafana in uds-identity-config. See: defenseunicorns/uds-identity-config#870. In our envoy filter response_handle:body() buffers the entire body and blocks until end-of-stream and on Grafana 13's streamed shell (no Content-Length) this hung.

This PR:

  • Fixes the classification-banner EnvoyFilter so it injects without hanging: buffer responses with a Content-Length, stream-inject (bodyChunks) those without one, and skip non-200s (redirects).
  • Pins the banner font (Arial, Helvetica, sans-serif, 16px) so it's identical on every app instead of inheriting each app's font.
  • Reserves viewport space so the banner doesn't overlap apps whose nav is position: fixed (SPAs).
  • Enables the banner on sso/portal/grafana in the k3d-standard bundle and adds a Playwright test to confirm working classification banner.

NOTE: People will see the SAMPLE BANNER classification banner for demo bundle deploys. If we feel this is weird I would be open to changing this. I also set this to SAMPLE BANNER instead of UNCLASSIFIED as I didn't want to assume classification for where people are deploying the demo bundle locally.

image

Related Issue

Fixes CORE-590

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Other (security config, docs update, etc)

Steps to Validate

  • uds run and check the banners on portal, sso, and grafana

Checklist before merging

Copilot AI review requested due to automatic review settings May 29, 2026 20:56
@joelmccoy
joelmccoy requested a review from a team as a code owner May 29, 2026 20:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the Istio classification-banner EnvoyFilter to avoid hanging on streamed HTML responses (notably Grafana 13’s streamed shell), standardizes banner font styling, reserves viewport space to prevent overlap in SPAs, and enables/validates the banner in the k3d-standard demo bundle via a new Playwright test.

Changes:

  • Update the classification-banner Lua filter to inject safely for both bounded (Content-Length) and streamed (chunked/no Content-Length) HTML responses, and skip non-200 responses.
  • Adjust injected banner styling and add client-side logic to reserve space so fixed-position SPA chrome doesn’t overlap the banner.
  • Enable the banner on demo bundle hosts (sso/portal/grafana) and add a Playwright test to verify banner injection.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
test/playwright/classification-banner.test.ts Adds an end-to-end Playwright check that the banner is injected on sso/grafana (and portal when present).
src/istio/common/chart/templates/classification-banner.yaml Updates Envoy Lua injection logic to avoid blocking on streamed responses and improves banner layout/styling behavior.
bundles/k3d-standard/uds-bundle.yaml Enables the banner on selected demo hosts and sets demo text to UNKNOWN.

Comment thread src/istio/common/chart/templates/classification-banner.yaml
Comment thread src/istio/common/chart/templates/classification-banner.yaml Outdated
@greptile-apps

greptile-apps Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the classification banner EnvoyFilter so it no longer hangs the gateway on streamed HTML responses (e.g. Grafana 13) by dispatching to either a buffered path (Content-Length present) or a chunk-by-chunk path (bodyChunks), and skips non-200 responses entirely. The JS placement script is rewritten to move banners to <html> and reserve viewport space via a transparent border on <body>, which correctly offsets both normal-flow content and position: fixed SPA chrome without the old paddingTop approach.

  • Streamed injection: partial <head>/<body> tags that straddle chunk boundaries are carried forward in a pending variable and merged with the next chunk, so neither injection point is ever missed.
  • transform: translateZ(0) on <body>: intentionally makes body the containing block for its fixed-positioned descendants, so the 24px border pushes SPA navbars clear of the banner; banners themselves are re-parented to <html> to stay viewport-anchored.
  • SAMPLE BANNER: added as a demo-safe placeholder in the color map, schema pattern, classificationKey, and the k3d-standard bundle; documented in the how-to guide.

Confidence Score: 5/5

Safe to merge. The buffered and streamed injection paths are logically correct for all valid HTML responses, and the 200-only guard eliminates the original hang on bodyless redirects.

All changed paths are well-tested (Playwright covers SSO, Grafana, and portal) and the streaming logic correctly handles cross-chunk tag boundaries. The one edge case — a malformed response that ends mid-tag without ever emitting a body tag — would silently drop a few bytes of already-broken content, but cannot affect valid production responses.

No files require special attention; the streaming Lua in classification-banner.yaml is the most complex change but its logic is sound.

Important Files Changed

Filename Overview
src/istio/common/chart/templates/classification-banner.yaml Core fix: adds buffered vs. streaming dispatch, partial-tag carry-forward, 200-only guard, and JS that moves banners to html with a 24px transparent border on body to push both fixed and flow content clear of the banner. Logic is sound for valid HTML; one edge case where pending fragment is not flushed when injection never fires.
bundles/k3d-standard/uds-bundle.yaml Adds SAMPLE BANNER classification banner config for sso, portal, and grafana in the k3d-standard dev bundle. Straightforward values override, no issues.
src/istio/common/chart/values.schema.json Adds SAMPLE BANNER to the allowed classification text regex pattern. Change is consistent with the new enum entry in the Lua color map and classificationKey function.
test/playwright/classification-banner.test.ts New Playwright tests for SSO, Grafana, and portal banner visibility. Test helper correctly targets the server-side-injected classification-banner-top div, which is guaranteed to be in the DOM before page load completes.
docs/how-to-guides/platform-features/enable-classification-banner.mdx Adds SAMPLE BANNER row to the classification level table with a clear demo placeholder description. Correct and consistent with the code change.

Sequence Diagram

sequenceDiagram
    participant GW as Envoy Gateway
    participant LUA as Lua Filter
    participant UP as Upstream App

    GW->>UP: Forward request
    UP->>GW: Response headers
    GW->>LUA: envoy_on_response
    LUA->>LUA: "Check status=200, text/html, enabled host"
    alt Not a target response
        LUA-->>GW: Return - pass through
    else Content-Length present
        LUA->>LUA: buffer full body
        LUA->>LUA: inject_banner on full body
        LUA-->>GW: setBytes with banner injected
    else No Content-Length - streaming
        loop each bodyChunk
            LUA->>LUA: inject_banner on pending+chunk
            alt Banner found in result
                LUA-->>GW: "setBytes - injected=true"
            else Trailing partial tag
                LUA-->>GW: Emit safe prefix - carry partial in pending
            else No partial tag
                LUA-->>GW: Emit full chunk - clear pending
            end
        end
    end
Loading

Reviews (2): Last reviewed commit: "chore: swap to sample banner" | Re-trigger Greptile

Comment thread src/istio/common/chart/templates/classification-banner.yaml Outdated
Comment thread src/istio/common/chart/templates/classification-banner.yaml Outdated
Comment thread src/istio/common/chart/templates/classification-banner.yaml
chance-coleman
chance-coleman previously approved these changes Jun 1, 2026

@chance-coleman chance-coleman left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@briantwatson

Copy link
Copy Markdown
Contributor

If I'm tracking correctly, some apps that more aggressively rebuild pages (e.g. React, Vue) might appear behind the banner as the place() function only triggers on first load. Untested, but some sort of observer pattern for window Mutation might work:

  1. Declare observer (after var H = '24px';):
    var observer;
  2. Disconnect at top of place(), right after the if (!top && !bottom) { return; } guard:
    if (observer) { observer.disconnect(); }
  3. Reconnect at end of place(), before its closing }:
    if (window.MutationObserver) {
    if (!observer) { observer = new MutationObserver(function () { place(); }); }
    observer.observe(b, { attributes: true, attributeFilter: ['style'] });
    observer.observe(document.documentElement, { childList: true });
    }

Makes me think as the Classification banner use cases/needs become more complex, maybe adding a playwright test with a more complex or tailored app that offers more coverage in this sense would be helpful if we don't have one already.

slaskawi
slaskawi previously approved these changes Jun 10, 2026

@slaskawi slaskawi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@Tobyhb

Tobyhb commented Jun 10, 2026

Copy link
Copy Markdown

Could we change 'UNKNOWN' to either 'SAMPLE BANNER' or 'NOTIONAL'? Seeing 'UNKNOWN' implies that the environment's actual classification hasn't been determined, which can look like a compliance issue. 'SAMPLE BANNER' or 'NOTIONAL' makes it clear that it's a deliberate placeholder for the demo.

@joelmccoy
joelmccoy dismissed stale reviews from slaskawi and chance-coleman via 9154662 June 11, 2026 20:57
@joelmccoy

Copy link
Copy Markdown
Contributor Author

@greptileai review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants