fix(cli): sanitize every provider or user value printed outside tables

From the second Codex re-gate: the empty-sentiment line printed MSN's
symbol raw, so a hostile response could clear the screen. Sweep every
print in the table and ownership renderers that interpolates data: the
sentiment and analysis headings, stale-quote notes, sync reasons, flow
period, search query, alias mapping, and DOT graph output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3syn2fN2LNo5pvTSqc6eh
This commit is contained in:
hermes 2026-10-17 05:30:58 +00:00
commit 75b8819d95
2 changed files with 43 additions and 13 deletions

View file

@ -250,7 +250,7 @@ fn handle_sync(investor: &Investor, args: &SyncArgs, config: &IdxConfig) -> Resu
snapshot::OwnershipSyncAction::NoChange
| snapshot::OwnershipSyncAction::SkippedNewer
| snapshot::OwnershipSyncAction::SkippedDiverged => {
println!("{}", result.reason);
println!("{}", terminal_safe(&result.reason));
}
}
@ -419,7 +419,10 @@ fn handle_search(
}
if entities.is_empty() {
println!("No entities found for query: {}", args.query);
println!(
"No entities found for query: {}",
terminal_safe(&args.query)
);
return Ok(());
}
@ -526,7 +529,10 @@ fn handle_flow(investor: &Investor, args: &FlowArgs, config: &IdxConfig) -> Resu
&Option::<idx_core::ownership::types::InstitutionalFlow>::None,
);
}
println!("No institutional flow data available for {symbol}.");
println!(
"No institutional flow data available for {}.",
terminal_safe(&symbol)
);
return Ok(());
};
@ -534,7 +540,12 @@ fn handle_flow(investor: &Investor, args: &FlowArgs, config: &IdxConfig) -> Resu
return json::print_json(&flow);
}
println!("{} {} ({})", "Flow:".bold(), symbol.bold(), flow.period);
println!(
"{} {} ({})",
"Flow:".bold(),
terminal_safe(&symbol).bold(),
terminal_safe(&flow.period)
);
print_flow_section("TOP BUYERS", &flow.top_buyers);
print_flow_section("TOP SELLERS", &flow.top_sellers);
print_flow_section("NEW POSITIONS", &flow.new_positions);
@ -591,7 +602,10 @@ fn handle_graph(investor: &Investor, args: &GraphArgs, config: &IdxConfig) -> Re
Ok(())
}
"dot" => {
print!("{}", graph::format_graph_dot(&nodes, &edges));
print!(
"{}",
terminal_safe(&graph::format_graph_dot(&nodes, &edges))
);
Ok(())
}
other => Err(IdxError::InvalidInput(format!(
@ -704,7 +718,11 @@ fn handle_resolve(
"entity": entity,
}));
}
println!("Mapped alias '{alias}' -> '{entity}'.");
println!(
"Mapped alias '{}' -> '{}'.",
terminal_safe(alias),
terminal_safe(entity)
);
Ok(())
}
ResolveCommand::Merge { keep, merge } => {

View file

@ -83,7 +83,7 @@ pub fn print_quotes(quotes: &[Quote], no_color: bool) -> Result<(), IdxError> {
println!("{table}");
for note in stale_quote_notes(quotes, chrono::Utc::now()) {
println!("{note}");
println!("{}", terminal_safe(&note));
}
Ok(())
}
@ -107,7 +107,10 @@ fn stale_quote_notes(quotes: &[Quote], now: chrono::DateTime<chrono::Utc>) -> Ve
}
pub fn print_history(symbol: &str, history: &[Ohlc]) -> Result<(), IdxError> {
println!("{}", format!("History for {symbol}").bold());
println!(
"{}",
format!("History for {}", terminal_safe(symbol)).bold()
);
let mut table = Table::new();
table
.load_preset(UTF8_FULL)
@ -199,7 +202,10 @@ pub fn print_technical(report: &TechnicalReport, no_color: bool) -> Result<(), I
}
pub fn print_growth(symbol: &str, report: &GrowthReport, no_color: bool) -> Result<(), IdxError> {
println!("{}", format!("Growth Analysis for {symbol}").bold());
println!(
"{}",
format!("Growth Analysis for {}", terminal_safe(symbol)).bold()
);
let mut table = Table::new();
table
@ -232,7 +238,10 @@ pub fn print_valuation(
report: &ValuationReport,
no_color: bool,
) -> Result<(), IdxError> {
println!("{}", format!("Valuation Analysis for {symbol}").bold());
println!(
"{}",
format!("Valuation Analysis for {}", terminal_safe(symbol)).bold()
);
let mut table = Table::new();
table
@ -281,7 +290,10 @@ pub fn print_valuation(
}
pub fn print_risk(symbol: &str, report: &RiskReport, no_color: bool) -> Result<(), IdxError> {
println!("{}", format!("Risk Analysis for {symbol}").bold());
println!(
"{}",
format!("Risk Analysis for {}", terminal_safe(symbol)).bold()
);
let mut table = Table::new();
table
@ -317,7 +329,7 @@ pub fn print_risk(symbol: &str, report: &RiskReport, no_color: bool) -> Result<(
pub fn print_fundamental(report: &FundamentalReport, no_color: bool) -> Result<(), IdxError> {
println!(
"{}",
format!("Fundamental Analysis for {}", report.symbol).bold()
format!("Fundamental Analysis for {}", terminal_safe(&report.symbol)).bold()
);
println!();
print_growth(&report.symbol, &report.growth, no_color)?;
@ -794,7 +806,7 @@ pub fn print_earnings(report: &EarningsReport) -> Result<(), IdxError> {
pub fn print_sentiment(data: &SentimentData) -> Result<(), IdxError> {
if data.statistics.is_empty() {
println!("No sentiment votes for {}.", data.symbol);
println!("No sentiment votes for {}.", terminal_safe(&data.symbol));
return Ok(());
}
let mut table = Table::new();