mirror of
https://github.com/0xrsydn/idx-cli.git
synced 2026-11-07 09:45:36 +00:00
fix: address final review (path traversal, sync races, MCP limits)
Findings from a Codex (gpt-6.1-sol, xhigh) review of v0.2.4..HEAD, each verified in the code before fixing: - high: ticker symbols reached cache file paths unchecked, so a symbol like `../../REPORT` read (and deleted, if unparseable) .json files outside the cache, reachable through MCP get_quotes. resolve_symbol now accepts only ticker characters, and cache keys are encoded as single safe path components. - high: sync decided before downloading and replaced the database without rechecking; an import that landed meanwhile was erased. It now rechecks the local state before installing, and idx-mcp runs ownership_sync exclusively with ownership reads. - replaces_sqlite_sha256 never downgrades history: the client requires the rebuild to keep at least the local release count, and the publisher lists a replaced build only then. - cache writes are atomic (temp file + rename), so concurrent readers never see, and delete, a partial entry. - idx-mcp caps symbols (20, deduplicated) and limits, and runs at most 4 tool calls at once. - each Yahoo client gets its own cookie jar, paired with its crumb. - ZIP entries (XLSX, KSEI archives) are read with a 256 MiB bound; non-ASCII archive dates and huge percentages are parse errors instead of panics or overflow. - agent-smoke --keep deletes every copied agent config, not only auth files. CLI goldens unchanged; the MCP tool golden changes only to state the new caps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5a3ce462cc
commit
60634d3aef
16 changed files with 310 additions and 59 deletions
|
|
@ -1,5 +1,5 @@
|
|||
use std::fs;
|
||||
use std::io::{Cursor, Read};
|
||||
use std::io::Cursor;
|
||||
use std::path::Path;
|
||||
|
||||
use chrono::NaiveDate;
|
||||
|
|
@ -177,11 +177,7 @@ fn extract_txt_from_zip(path: &Path) -> Result<String, IdxError> {
|
|||
continue;
|
||||
}
|
||||
|
||||
let mut output = String::new();
|
||||
file.read_to_string(&mut output).map_err(|e| {
|
||||
IdxError::ParseError(format!("failed to decode KSEI archive TXT entry: {e}"))
|
||||
})?;
|
||||
return Ok(output);
|
||||
return super::read_zip_entry_bounded(&mut file, "KSEI archive TXT entry");
|
||||
}
|
||||
|
||||
Err(IdxError::ParseError(
|
||||
|
|
@ -201,7 +197,8 @@ fn validate_header(header: &str) -> Result<(), IdxError> {
|
|||
|
||||
fn parse_archive_date(raw: &str) -> Result<NaiveDate, IdxError> {
|
||||
let trimmed = raw.trim();
|
||||
if trimmed.len() != 11 {
|
||||
// Fixed byte offsets below; non-ASCII input would split a character.
|
||||
if trimmed.len() != 11 || !trimmed.is_ascii() {
|
||||
return Err(IdxError::ParseError(format!(
|
||||
"invalid KSEI archive date `{trimmed}`"
|
||||
)));
|
||||
|
|
@ -357,4 +354,14 @@ mod tests {
|
|||
assert!(pdf_local_cp.total_shares <= archive_local_cp.total_shares);
|
||||
assert!(pdf_local_cp.percentage_bps <= archive_local_cp.percentage_bps);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_ascii_archive_dates_are_parse_errors_not_panics() {
|
||||
// 11 bytes, but byte offsets would split the 'é'.
|
||||
assert!(super::parse_archive_date("1éJan-2026").is_err());
|
||||
assert_eq!(
|
||||
super::parse_archive_date("27-FEB-2026").unwrap(),
|
||||
chrono::NaiveDate::from_ymd_opt(2026, 2, 27).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue