v5.7.3 - Terminal Escape Sanitization, Console Hardening - #41
Merged
Conversation
Add Parse::TerminalSafe and route every path where server-stored values reach a terminal or log record through it.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stored values can no longer drive the operator's terminal
Parse Server returns whatever a tenant stored, and until now those bytes reached the operator's terminal intact. A row whose field carried an OSC 52 sequence could write an attacker-chosen payload into the system clipboard, and CSI or carriage-return sequences could clear the screen or overwrite lines already on it, so what was displayed was not what was stored. This release adds one canonical sanitizer and routes every terminal and log sink through it. It also stops
parse-console --urlfrom trusting the document it fetches.Changes
A single sanitizer for untrusted output
Parse::TerminalSafeneutralizes terminal control sequences in text that is about to reach a terminal, a log record, or an IRBinspectline.Parse::TerminalSafe.sanitize(str)escapes ESC, BEL, backspace, carriage return, the remaining C0 controls, DEL, the C1 controls (the 8-bit CSI/OSC/DCS introducers, which a filter looking only for0x1Bmisses), the zero-width characters, and the Unicode bidirectional marks, overrides, and isolates. Tabs and newlines are preserved.Parse::TerminalSafe.sanitize_line(str)escapes newlines and the Unicode line and paragraph separators as well, for text interpolated into a single log record.\e,, and friends instead of vanishing. Non-UTF-8 and invalid-encoding input is coerced first, so the sanitizer never raises on a binary response body, and the pattern is built from codepoint ranges so the file itself carries none of the bytes it defends against.Every terminal and log sink now renders through it
Parse::Agent::MCPClient::Result#to_sand#inspect, which run merely by evaluatingmcp.ask(...)in IRB), the request and response bodies and header values written byParse::Middleware::Loggingand by the separateParse.logging = trueprinter inParse::Middleware::BodyBuilder, the REST error text in logged error summaries and inParse::Client's warning path,Parse::Query's error and explain warnings, the webhook request, payload, response, handler-error, and afterSave-callback lines, and the event and handler-error lines emitted byParse.watch.Parse::Agent::MCPClientinterpolated the raw provider response body into the exception message, and a malformed success body raised aJSON::ParserErrorquoting the offending bytes verbatim. IRB prints both raw, so a hostile or compromised endpoint could still reach the terminal through the failure path. Both are escaped now, and the quoted body is capped.rake mcp:chatescapes the answer, the tool-call trace, the/historyand/compactoutput, and error messages before printing them.parse-console --urlno longer trusts the document it fetchesrequiredoes. OnlyPARSE_SERVER_URL,PARSE_SERVER_APPLICATION_ID,PARSE_APP_ID,PARSE_SERVER_REST_API_KEY,PARSE_API_KEY,PARSE_SERVER_MASTER_KEY, andPARSE_MASTER_KEYare copied now, and each value must be a string. Migration: a remote config that carried additional variables should set them in the shell instead.JSON.load, which honorsjson_classadditions and will instantiate arbitrary already-loaded classes from the document. It usesJSON.parsenow.URI#hostname, so an IPv6 loopback literal and an uppercase host both resolve correctly.Behavior Notes
Sanitization applies to rendering, never to storage.
result.text,object.title, and the parsed response body keep their exact bytes, so application code writing to a non-terminal surface (an HTTP response, a database, a file) is unaffected. Only the human-readable form built for a terminal or a log line is escaped.This addresses terminal output. It is not a Ruby code-execution issue: IRB does not evaluate program output as input, and stored values do not obtain an IRB binding. Turning escaped output into command execution additionally requires a terminal emulator with dangerous features or remote control enabled, or an operator pasting a poisoned clipboard.
Two related items remain the deploying application's responsibility, since they live outside the SDK: keeping internet-facing agents in a separate process and OS account from administrative consoles, and auditing application-defined
agent_methods and custom tools for shell, eval, and deserialization sinks. The SDK-side guard for the latter is unchanged inParse::Agent::Tools, which validates identifiers and blockseval,system,exec,spawn,send, andbinding.Code Example
Commit: 16d312a
Author: Adrian Curtin
Date: August 14, 2026