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
|
|
@ -163,6 +163,19 @@ pub fn resolve_symbol(symbol: &str, exchange: &str) -> Result<String, IdxError>
|
|||
"ticker symbol cannot be empty".into(),
|
||||
));
|
||||
}
|
||||
// Tickers name cache files and provider URLs: letters, digits, and the
|
||||
// separators real symbols use (BBCA.JK, BRK-B, ^JKSE), nothing else.
|
||||
if trimmed.len() > 32
|
||||
|| trimmed.contains("..")
|
||||
|| !trimmed
|
||||
.chars()
|
||||
.all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '.' | '-' | '^' | '='))
|
||||
{
|
||||
return Err(IdxError::InvalidInput(format!(
|
||||
"invalid ticker symbol '{}'",
|
||||
symbol.trim()
|
||||
)));
|
||||
}
|
||||
if let Some((base, suffix)) = trimmed.rsplit_once('.')
|
||||
&& !base.is_empty()
|
||||
&& !suffix.is_empty()
|
||||
|
|
@ -301,6 +314,26 @@ mod tests {
|
|||
assert_eq!(resolve_symbol("BBCA", "JK").unwrap(), "BBCA.JK");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_symbols_that_are_not_tickers() {
|
||||
for bad in [
|
||||
"../../REPORT",
|
||||
"a/b",
|
||||
"a\\b",
|
||||
"BB CA",
|
||||
"BBCA..JK",
|
||||
"BBCA\u{0}",
|
||||
&"A".repeat(40),
|
||||
] {
|
||||
assert!(
|
||||
matches!(resolve_symbol(bad, "JK"), Err(IdxError::InvalidInput(_))),
|
||||
"{bad:?} accepted"
|
||||
);
|
||||
}
|
||||
assert_eq!(resolve_symbol("^jkse", "JK").unwrap(), "^JKSE.JK");
|
||||
assert_eq!(resolve_symbol("BRK-B.US", "JK").unwrap(), "BRK-B.US");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn selected_provider_exposes_expected_capabilities_for_msn() {
|
||||
let provider = build_selected_provider(ProviderKind::Msn, false, false);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue