mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
fix(cli): sanitize errors and diagnostics at their choke points
From the narrow Codex confirmation: provider, KSEI, and manifest text still reached the terminal through error messages (a Yahoo error description, a manifest's `kind`), idx-core diagnostics, the ownership heading's ticker code and Bing date, the technical heading and comparison symbol, and printed paths. Sanitize where every error (emit_error) and every diagnostic (runtime) is printed instead of at each caller, plus the remaining direct prints. terminal_safe moves to idx_core::text so the runtime can use it; the CLI re-exports it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
This commit is contained in:
parent
75b8819d95
commit
927d4224e9
8 changed files with 67 additions and 49 deletions
|
|
@ -15,6 +15,7 @@ pub mod investor;
|
|||
pub mod market;
|
||||
#[cfg(feature = "ownership")]
|
||||
pub mod ownership;
|
||||
pub mod text;
|
||||
|
||||
pub use client::{CachePolicy, Fetched, Idx};
|
||||
|
||||
|
|
@ -47,7 +48,10 @@ pub mod runtime {
|
|||
let line = serde_json::json!({ "level": level, "message": message.to_string() });
|
||||
eprintln!("{line}");
|
||||
} else {
|
||||
eprintln!("{level}: {message}");
|
||||
eprintln!(
|
||||
"{level}: {}",
|
||||
crate::text::terminal_safe(&message.to_string())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
44
crates/idx-core/src/text.rs
Normal file
44
crates/idx-core/src/text.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
//! Text from providers, KSEI files, and snapshot manifests is untrusted.
|
||||
//! Before it reaches a terminal, control characters (which can start escape
|
||||
//! sequences that clear the screen, rewrite output, or set the window title)
|
||||
//! and bidirectional overrides (which can reorder what is displayed) are
|
||||
//! replaced.
|
||||
|
||||
/// `text` with control characters other than newline, and bidirectional
|
||||
/// override characters, replaced by U+FFFD.
|
||||
pub fn terminal_safe(text: &str) -> String {
|
||||
text.chars()
|
||||
.map(|ch| {
|
||||
let bidi = matches!(ch, '\u{202A}'..='\u{202E}' | '\u{2066}'..='\u{2069}');
|
||||
if (ch.is_control() && ch != '\n') || bidi {
|
||||
'\u{FFFD}'
|
||||
} else {
|
||||
ch
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::terminal_safe;
|
||||
|
||||
#[test]
|
||||
fn escape_sequences_and_bidi_overrides_are_neutralized() {
|
||||
assert_eq!(
|
||||
terminal_safe("\u{1b}[2J\u{1b}[Hfake"),
|
||||
"\u{FFFD}[2J\u{FFFD}[Hfake"
|
||||
);
|
||||
assert_eq!(
|
||||
terminal_safe("\u{1b}]0;title\u{7}x"),
|
||||
"\u{FFFD}]0;title\u{FFFD}x"
|
||||
);
|
||||
assert_eq!(terminal_safe("a\u{9b}31mb"), "a\u{FFFD}31mb");
|
||||
assert_eq!(terminal_safe("ab\u{202E}cd"), "ab\u{FFFD}cd");
|
||||
assert_eq!(terminal_safe("line one\nline two"), "line one\nline two");
|
||||
assert_eq!(
|
||||
terminal_safe("Saham Grup Djarum’s 14,81%"),
|
||||
"Saham Grup Djarum’s 14,81%"
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue