Skip to content

[scanner] fix: extend LogString to strip ANSI escapes, null bytes, and Unicode line separators#20857

Merged
clubanderson merged 7 commits into
mainfrom
scanner/fix-20633
Jul 12, 2026
Merged

[scanner] fix: extend LogString to strip ANSI escapes, null bytes, and Unicode line separators#20857
clubanderson merged 7 commits into
mainfrom
scanner/fix-20633

Conversation

@clubanderson

Copy link
Copy Markdown
Collaborator

Part of #20633

Summary

pkg/sanitize.LogString previously only replaced ASCII CR/LF with . Attackers could still inject forged log lines via:

  • ANSI/VT100 escape sequences (CSI, OSC) — cursor movement, colour resets, window-title overwrite (CWE-117)
  • Unicode line/paragraph separators (U+2028, U+2029) — treated as newlines by many log aggregators and JavaScript engines
  • Null bytes (\x00) — truncate entries in C-based log pipelines
  • Other C0 control characters (backspace, bell, form-feed, DEL, etc.)

Changes

pkg/sanitize/prompt.go

  • Add ansiEscapeRe regexp to strip CSI, OSC, and 2-char ESC sequences before logging
  • Add logC0Re regexp to strip remaining C0/DEL control characters
  • Extend logInjectionReplacer to replace U+2028, U+2029 with and strip \x00

pkg/sanitize/prompt_test.go

  • Flip the former "documents current broken behavior" tests to require neutralization
  • Rename Preserves* tests to Strips* / Neutralizes* to reflect the hardened contract

Advisory finding addressed

pkg/sanitize LogString only handles CR/LF — misses ANSI, null bytes, Unicode separators (pkg/sanitize/prompt.go:LogString) — quality agent, Hive advisory digest 2026-07-11

Signed-off-by: Copilot 223556219+Copilot@users.noreply.github.com

…and Unicode line separators (part of hive advisory)

pkg/sanitize LogString previously only replaced ASCII CR/LF with the visible
placeholder ⏎. Attackers could still inject forged log lines using:
  - ANSI/VT100 terminal escape sequences (CSI, OSC) — cursor movement, color
    resets, window-title overwrite (CWE-117)
  - Unicode line/paragraph separators (U+2028, U+2029) — treated as newlines
    by many log aggregators and JavaScript engines
  - Null bytes (\x00) — truncate entries in C-based log pipelines
  - Other C0 control characters (backspace, bell, DEL, etc.)

Changes:
- Add ansiEscapeRe regexp to strip CSI, OSC, and 2-char ESC sequences
- Add logC0Re regexp to strip remaining C0/DEL control characters
- Extend logInjectionReplacer with U+2028, U+2029, and null byte entries
- Update tests: flip "documents current broken behavior" assertions to
  require neutralization; rename Preserves* tests to Strips*/Neutralizes*

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Copilot <copilot@github.com>
Copilot AI review requested due to automatic review settings July 11, 2026 10:15
@kubestellar-prow kubestellar-prow Bot added the dco-signoff: yes Indicates the PR's author has signed the DCO. label Jul 11, 2026
@kubestellar-prow

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign eeshaansa for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@netlify

netlify Bot commented Jul 11, 2026

Copy link
Copy Markdown

Deploy Preview for kubestellarconsole ready!

Name Link
🔨 Latest commit 648bff9
🔍 Latest deploy log https://app.netlify.com/projects/kubestellarconsole/deploys/6a536bf27c9de00008fd1f53
😎 Deploy Preview https://deploy-preview-20857.console-deploy-preview.kubestellar.io
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions

Copy link
Copy Markdown
Contributor

🐝 Hi @clubanderson! I'm kubestellar-hive[bot], an automation bot for this repo.

Trusted users — org members and contributors with write access — can mention @kubestellar-hive in a comment to trigger repo automation.
On issues, that mention queues an automated fix attempt. On pull requests, it records extra context for existing automation.
This is not an interactive Q&A bot, so mentions should be treated as requests for automation rather than a conversation.

Automation may take a moment to start, and follow-up happens through workflow activity rather than chat replies.

@github-actions github-actions Bot added the ai-generated Pull request generated by AI label Jul 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

👋 Hey @clubanderson — thanks for opening this PR!

🤖 This project is developed exclusively using AI coding assistants.

Please do not attempt to code anything for this project manually.
All contributions should be authored using an AI coding tool such as:

This ensures consistency in code style, architecture patterns, test coverage,
and commit quality across the entire codebase.


This is an automated message.

@kubestellar-prow kubestellar-prow Bot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Jul 11, 2026

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 hardens pkg/sanitize.LogString to better neutralize log-injection vectors (CWE-117) by stripping/normalizing additional control and escape sequences beyond ASCII CR/LF, and updates unit tests to enforce the stronger contract.

Changes:

  • Extend log sanitization to replace Unicode line/paragraph separators (U+2028/U+2029) with and strip null bytes.
  • Strip ANSI/VT100 escape sequences and additional C0/DEL control characters prior to replacement.
  • Update/rename log-sanitization tests from “documents current behavior” to “requires neutralization”.

Reviewed changes

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

File Description
pkg/sanitize/prompt.go Extends LogString sanitization to strip terminal escapes/control chars and normalize additional newline-like separators.
pkg/sanitize/prompt_test.go Tightens tests to assert the hardened LogString behavior against more injection vectors.

Comment thread pkg/sanitize/prompt.go
Comment on lines +71 to +74
// - CSI sequences: ESC [ <params> <final-byte>
// - OSC sequences: ESC ] <text> BEL
// - Two-char Fe/Fs: ESC <byte in 0x40–0x5F>
var ansiEscapeRe = regexp.MustCompile(`\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*\x07|[@-_])`)
Comment on lines +188 to +191
{"CSI clear screen", "normal\x1b[2Jtext", "normaltext"},
{"CSI color change", "normal\x1b[31mred text\x1b[0m", "normalred text"},
{"OSC title change", "normal\x1b]0;pwned\x07rest", "normalrest"},
{"cursor movement", "data\x1b[5Aoverwrite", "dataoverwrite"},

@clubanderson clubanderson left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[quality] This directly implements the hardening I was tracking in advisory bead 46bb0e2d-78b (referenced across #20808, #20828, #20840). Nice to see it land.

Implementation looks correct:

  • ansiEscapeRe = \x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*\x07|[@-_]) covers CSI, BEL-terminated OSC, and two-char Fe/Fs sequences.
  • logC0Re = [\x01-\x08\x0b\x0c\x0e-\x1a\x1c-\x1f\x7f] correctly excludes \x00 (handled by replacer), \x09 TAB (intentionally kept), \x0a/\x0d (handled), \x1b ESC (handled by ansi regex). No overlap, no gaps in the C0 range.
  • Replacer additions for U+2028 / U+2029 → ⏎ and \x00 → "" match the finding.

Tests properly flipped: the former TestLogString_*Passthrough / Preserves* cases that documented the gap are renamed to Neutralizes* / Strips* and now assert exact expected output (e.g. "normal\x1b[2Jtext" → "normaltext"). Coverage of surrounding-content preservation is retained.

Two small edge cases worth an optional follow-up (non-blocking):

  1. OSC terminated by ST (ESC \) instead of BEL — the OSC branch only matches BEL-terminated sequences. An ESC ] without BEL is caught by the two-char branch (since ] = 0x5D ∈ [@-_]), which strips the ESC ] prefix but leaves the OSC payload text visible as plain characters. Not a security regression (the ESC is gone, so no terminal control), but the payload text passes through. If you want tighter coverage, add an ST-terminated OSC alt: \][^\x07]*(?:\x07|\x1b\\).

  2. DCS (ESC P), APC (ESC _), PM (ESC ^) — same story. First byte after ESC is stripped by the two-char branch; payload text remains as plain characters. Again, no ESC = no terminal control, but tighter parsing would remove the whole sequence.

Both are minor and can be follow-ups. LGTM.

Bead will be closed once merged.


Filed by quality agent (ACMM L4/L6 — full mode)

clubanderson and others added 4 commits July 11, 2026 12:09
…ions

The logC0Re comment was incomplete: it did not document that \x00 (null
byte) is also excluded because the logInjectionReplacer already handles it,
and that \x09 (tab) is intentionally left through as safe. This clarifies
the full set of excluded bytes so reviewers can verify the ranges are correct.

All 19 sanitize package tests pass (TestLogString_Strips*, Neutralizes*,
Removes*, Preserves*, Basic, Empty, SingleElement, LongInput, UTF8).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Scanner Bot <scanner@kubestellar.io>
Signed-off-by: Andy Anderson <andy@clubanderson.com>
TestNewServer_ExplicitDevModeStartsWithoutOAuth (config_test.go) calls
NewServer(), which calls middleware.InitTokenRevocation(db) with a real
SQLiteStore. Its t.Cleanup calls server.Shutdown(), which closes the
store but left revokedTokens.store pointing to the now-closed DB.
TestAgentTokenEndpoint_Contract runs alphabetically after config_test.go
and fails closed (503) when IsRevokedChecked tries to query the closed DB.

Fix: ShutdownTokenRevocation() now nils revokedTokens.store within its
existing lock, clearing the stale reference before the underlying store
is closed. Subsequent revocation checks see store==nil and return
(false, nil), letting authenticated requests through.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
github-actions Bot and others added 2 commits July 12, 2026 04:35
TestNewServer_ExplicitDevModeStartsWithoutOAuth calls NewServer(), which
calls InitUserValidation(db) with a real SQLiteStore. Its t.Cleanup calls
server.Shutdown(), which closes the store but left userValidationStore
pointing to the now-closed DB.

TestAgentTokenEndpoint_Contract runs after config_test.go and uses fresh
userIDs that are not in the cache, so ValidateUserActive hits the store
path and fails with a DB error on the closed SQLite handle, causing the
middleware to return 503 instead of 200.

Fix: add ShutdownUserValidation() (mirrors ShutdownTokenRevocation) and
call it from server.Shutdown() before store.Close().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
@clubanderson clubanderson merged commit f8374a2 into main Jul 12, 2026
27 of 28 checks passed
@kubestellar-prow kubestellar-prow Bot deleted the scanner/fix-20633 branch July 12, 2026 10:28
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution! Your PR has been merged.

Check out what's new:

Stay connected: Slack #kubestellar-dev | Multi-Cluster Survey

@github-actions

Copy link
Copy Markdown
Contributor

⏹️ Post-Merge Verification: cancelled

Commit: f8374a2f959314052ea55fbbc3e0b20aa1586fe6
Specs run: smoke.spec.ts
Report: https://github.com/kubestellar/console/actions/runs/29189176322

@github-actions

Copy link
Copy Markdown
Contributor

Post-merge build verification passed

Both Go and frontend builds compiled successfully against merge commit f8374a2f959314052ea55fbbc3e0b20aa1586fe6.

clubanderson added a commit that referenced this pull request Jul 12, 2026
* 🐛 fix: prevent TestAgentTokenEndpoint_Contract flake from stale token revocation store

TestNewServer_ExplicitDevModeStartsWithoutOAuth (config_test.go) initializes
middleware.TokenRevocation via NewServer, then t.Cleanup shuts it down (closing
the store). TestAgentTokenEndpoint_Contract runs later alphabetically and inherits
leftover global state: the middleware's initOnce has been used but the store is
nil, causing JWTAuth to fail closed when checking token revocation.

Fix: Call middleware.resetTokenRevocationForTest() in newAgentTokenTestServer to
reset initOnce and reinitialize with a fresh mock store, matching the pattern
already used in pkg/api/middleware/auth_test.go.

Fixes test ordering failure on PR #20857 and other PRs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: GitHub Copilot <copilot@github.com>

* 🐛 fix: export ResetTokenRevocationForTest so api package tests can call it

The PR introduced a call to middleware.resetTokenRevocationForTest() from
routes_api_core_test.go (package api), but resetTokenRevocationForTest was
unexported (lowercase). Go does not allow cross-package access to unexported
identifiers, causing a compilation failure.

Fix: rename to ResetTokenRevocationForTest (exported) and update all callers
in auth_test.go and auth_ws_revoke_test.go accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Scanner Bot <scanner@kubestellar.io>

* 🐛 fix: add missing middleware import in routes_api_core_test.go

The previous commit exported ResetTokenRevocationForTest but forgot to add
the middleware package import to routes_api_core_test.go (package api).
Without the import, Go cannot resolve middleware.ResetTokenRevocationForTest,
causing a build failure across all platforms.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: GitHub Copilot <copilot@github.com>

* 🐛 fix: init middleware globals in newRouteRegistrationServer

TestSetupRoutes_ProtectedEndpointsKeepAuthAndCSRFGuards panics when its
'admin-only github token route rejects viewer' subtest runs after
TestAgentTokenEndpoint_Contract: the global userValidationStore is still
set to the previous test's mockStore (set up for UUID-B), but the
registration test's request carries a JWT for a different UUID-A.
ValidateUserActive then calls GetUser(UUID-A) on a mock that only knows
UUID-B → panic.

Fix: add ResetTokenRevocationForTest / InitTokenRevocation / InitUserValidation
to newRouteRegistrationServer, mirroring the pattern already used in
newAgentTokenTestServer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Scanner Bot <scanner@kubestellar.io>

---------

Signed-off-by: GitHub Copilot <copilot@github.com>
Signed-off-by: Scanner Bot <scanner@kubestellar.io>
Co-authored-by: GitHub Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Scanner Bot <scanner@kubestellar.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-generated Pull request generated by AI dco-signoff: yes Indicates the PR's author has signed the DCO. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. tier/3-restricted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants