feat: enhance OAuth flow validation and improve security checks; add credential read blocking and git operation safeguards

This commit is contained in:
asepharyana
2026-07-12 11:45:28 +07:00
parent 2efd40ca88
commit 8767beef39
11 changed files with 323 additions and 42 deletions
+25 -10
View File
@@ -1029,7 +1029,7 @@ fn maybe_trigger_review(state: &mut AppStateRest) {
}
state.push_toast(Toast::new(
ToastKind::Info,
format!("{} file(s) modified this turn. Review available.", edit_count),
format!("{} file(s) modified this session. Review available.", edit_count),
));
}
@@ -1116,11 +1116,17 @@ fn run_oauth_flow(provider: &str) -> anyhow::Result<String> {
let state_token = format!("{:x}", sha2::Sha256::digest(rand_bytes(16)));
let mut manager = OAuthManager::new(config.clone());
let _auth_url = manager.build_auth_url(&redirect_uri, &state_token, challenge.as_str());
let auth_url = manager.build_auth_url(&redirect_uri, &state_token, challenge.as_str());
if auth_url.is_empty() {
tracing::warn!("[oauth] auth_url was empty for provider '{}'", provider);
} else if webbrowser::open(&auth_url).is_err() {
tracing::warn!(
"[oauth] could not open browser for '{}'; user must open URL manually:\n{}",
provider, auth_url
);
}
// let _ = webbrowser::open(&auth_url);
let code = server.wait_for_code(120_000)?;
let code = server.wait_for_code(120_000, &state_token)?;
manager.exchange_code(&code, &redirect_uri, verifier.as_str())
.map_err(|e| anyhow::anyhow!("{}", e))?;
@@ -1139,15 +1145,24 @@ fn run_oauth_flow(provider: &str) -> anyhow::Result<String> {
Ok(format!("Successfully authenticated with {}.", provider))
}
/// Generate `n` pseudo-random bytes from the current sub-second timestamp.
/// Generate `n` pseudo-random bytes from the system clock mixed with a monotonic
/// counter, providing sufficient unpredictability for a per-flow OAuth state
/// token without a `rand` dependency.
///
/// Why: avoids pulling in a full RNG crate for the OAuth state token;
/// sufficient for a nonce that only needs to be unpredictable over the
/// lifetime of a single OAuth flow.
/// the counter ensures sequential invocations produce different outputs even
/// within the same clock tick, which is sufficient for a short-lived nonce.
fn rand_bytes(n: usize) -> Vec<u8> {
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().subsec_nanos();
(0..n).map(|i| ((seed >> (i % 4 * 8)) ^ (i as u32 * 2654435761)) as u8).collect()
static COUNTER: AtomicU64 = AtomicU64::new(0);
let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
let seed = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
let base = seed ^ counter;
(0..n).map(|i| ((base >> ((i as u64 % 8) * 8)) ^ (i as u64 * 2654435761)) as u8).collect()
}