Cursor: Apply local changes for cloud agent

This commit is contained in:
2026-02-22 22:29:52 +01:00
parent 6b8b61c6ae
commit 41c57a1181
21 changed files with 616 additions and 142 deletions

View File

@@ -24,6 +24,7 @@ futures = { workspace = true }
# Server utilities
dashmap = { workspace = true }
governor = { workspace = true }
sha2 = { workspace = true }
tracing = { workspace = true }
tracing-subscriber = { workspace = true }

View File

@@ -10,8 +10,12 @@ use crate::error_codes::*;
pub const SESSION_TTL_SECS: u64 = 24 * 60 * 60; // 24 hours
pub const PENDING_LOGIN_TTL_SECS: u64 = 300; // 5 minutes
pub const RATE_LIMIT_WINDOW_SECS: u64 = 60;
pub const RATE_LIMIT_MAX_ENQUEUES: u32 = 100;
/// Maximum enqueues per second per token before GCRA rate limiting kicks in.
pub const RATE_LIMIT_MAX_PER_SEC: std::num::NonZeroU32 =
std::num::NonZeroU32::new(100).expect("RATE_LIMIT_MAX_PER_SEC must be non-zero");
/// Keyed GCRA rate limiter backed by DashMap (one bucket per session token).
pub type RateLimiter = governor::DefaultKeyedRateLimiter<Vec<u8>>;
#[derive(Clone, Debug)]
pub struct AuthConfig {
@@ -47,11 +51,6 @@ pub struct PendingLogin {
pub created_at: u64,
}
pub struct RateEntry {
pub count: u32,
pub window_start: u64,
}
#[derive(Clone)]
pub struct AuthContext {
pub token: Vec<u8>,
@@ -65,32 +64,14 @@ pub fn current_timestamp() -> u64 {
.as_secs()
}
pub fn check_rate_limit(
rate_limits: &DashMap<Vec<u8>, RateEntry>,
token: &[u8],
) -> Result<(), capnp::Error> {
let now = current_timestamp();
let mut entry = rate_limits.entry(token.to_vec()).or_insert(RateEntry {
count: 0,
window_start: now,
});
if now - entry.window_start >= RATE_LIMIT_WINDOW_SECS {
entry.count = 1;
entry.window_start = now;
} else {
entry.count += 1;
if entry.count > RATE_LIMIT_MAX_ENQUEUES {
return Err(crate::error_codes::coded_error(
E014_RATE_LIMITED,
format!(
"rate limit exceeded: {} enqueues in {}s window",
RATE_LIMIT_MAX_ENQUEUES, RATE_LIMIT_WINDOW_SECS
),
));
}
}
Ok(())
/// Check the GCRA rate limit for a token. Returns an error if the token has exceeded the quota.
pub fn check_rate_limit(limiter: &RateLimiter, token: &[u8]) -> Result<(), capnp::Error> {
limiter.check_key(&token.to_vec()).map_err(|_| {
crate::error_codes::coded_error(
E014_RATE_LIMITED,
format!("rate limit exceeded: max {} enqueues/s", RATE_LIMIT_MAX_PER_SEC),
)
})
}
pub fn validate_auth(

View File

@@ -23,7 +23,7 @@ mod sql_store;
mod tls;
mod storage;
use auth::{AuthConfig, PendingLogin, RateEntry, SessionInfo};
use auth::{AuthConfig, PendingLogin, RateLimiter, SessionInfo, RATE_LIMIT_MAX_PER_SEC};
use config::{
load_config, merge_config, validate_production_config, DEFAULT_DATA_DIR, DEFAULT_DB_PATH,
DEFAULT_LISTEN, DEFAULT_STORE_BACKEND, DEFAULT_TLS_CERT, DEFAULT_TLS_KEY,
@@ -215,13 +215,15 @@ async fn main() -> anyhow::Result<()> {
let pending_logins: Arc<DashMap<String, PendingLogin>> = Arc::new(DashMap::new());
let sessions: Arc<DashMap<Vec<u8>, SessionInfo>> = Arc::new(DashMap::new());
let rate_limits: Arc<DashMap<Vec<u8>, RateEntry>> = Arc::new(DashMap::new());
let rate_limiter: Arc<RateLimiter> = Arc::new(governor::RateLimiter::keyed(
governor::Quota::per_second(RATE_LIMIT_MAX_PER_SEC),
));
// Background cleanup task (expire sessions, pending logins, rate limits, and stale messages).
// Background cleanup task (expire sessions, pending logins, and stale messages).
// Governor's DashMapStateStore handles rate-limit cleanup automatically.
spawn_cleanup_task(
Arc::clone(&sessions),
Arc::clone(&pending_logins),
Arc::clone(&rate_limits),
Arc::clone(&store),
);
@@ -260,7 +262,7 @@ async fn main() -> anyhow::Result<()> {
let opaque_setup = Arc::clone(&opaque_setup);
let pending_logins = Arc::clone(&pending_logins);
let sessions = Arc::clone(&sessions);
let rate_limits = Arc::clone(&rate_limits);
let rate_limiter = Arc::clone(&rate_limiter);
let sealed_sender = effective.sealed_sender;
tokio::task::spawn_local(async move {
@@ -272,7 +274,7 @@ async fn main() -> anyhow::Result<()> {
opaque_setup,
pending_logins,
sessions,
rate_limits,
rate_limiter,
sealed_sender,
)
.await

View File

@@ -84,7 +84,7 @@ impl NodeServiceImpl {
));
}
if let Err(e) = check_rate_limit(&self.rate_limits, &auth_ctx.token) {
if let Err(e) = check_rate_limit(&self.rate_limiter, &auth_ctx.token) {
// Audit: rate limit hit — do not log token or identity.
tracing::warn!("rate_limit_hit");
metrics::record_rate_limit_hit_total();

View File

@@ -10,8 +10,7 @@ use tokio::sync::Notify;
use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
use crate::auth::{
current_timestamp, AuthConfig, PendingLogin, RateEntry, SessionInfo,
PENDING_LOGIN_TTL_SECS, RATE_LIMIT_WINDOW_SECS,
current_timestamp, AuthConfig, PendingLogin, RateLimiter, SessionInfo, PENDING_LOGIN_TTL_SECS,
};
use crate::storage::Store;
@@ -143,7 +142,7 @@ pub struct NodeServiceImpl {
pub opaque_setup: Arc<ServerSetup<OpaqueSuite>>,
pub pending_logins: Arc<DashMap<String, PendingLogin>>,
pub sessions: Arc<DashMap<Vec<u8>, SessionInfo>>,
pub rate_limits: Arc<DashMap<Vec<u8>, RateEntry>>,
pub rate_limiter: Arc<RateLimiter>,
/// When true, enqueue does not require identity-bound session (Sealed Sender).
pub sealed_sender: bool,
}
@@ -156,7 +155,7 @@ impl NodeServiceImpl {
opaque_setup: Arc<ServerSetup<OpaqueSuite>>,
pending_logins: Arc<DashMap<String, PendingLogin>>,
sessions: Arc<DashMap<Vec<u8>, SessionInfo>>,
rate_limits: Arc<DashMap<Vec<u8>, RateEntry>>,
rate_limiter: Arc<RateLimiter>,
sealed_sender: bool,
) -> Self {
Self {
@@ -166,7 +165,7 @@ impl NodeServiceImpl {
opaque_setup,
pending_logins,
sessions,
rate_limits,
rate_limiter,
sealed_sender,
}
}
@@ -180,7 +179,7 @@ pub async fn handle_node_connection(
opaque_setup: Arc<ServerSetup<OpaqueSuite>>,
pending_logins: Arc<DashMap<String, PendingLogin>>,
sessions: Arc<DashMap<Vec<u8>, SessionInfo>>,
rate_limits: Arc<DashMap<Vec<u8>, RateEntry>>,
rate_limiter: Arc<RateLimiter>,
sealed_sender: bool,
) -> Result<(), anyhow::Error> {
let connection = connecting.await?;
@@ -207,7 +206,7 @@ pub async fn handle_node_connection(
opaque_setup,
pending_logins,
sessions,
rate_limits,
rate_limiter,
sealed_sender,
));
@@ -221,7 +220,6 @@ const MESSAGE_TTL_SECS: u64 = 7 * 24 * 60 * 60; // 7 days
pub fn spawn_cleanup_task(
sessions: Arc<DashMap<Vec<u8>, SessionInfo>>,
pending_logins: Arc<DashMap<String, PendingLogin>>,
rate_limits: Arc<DashMap<Vec<u8>, RateEntry>>,
store: Arc<dyn Store>,
) {
tokio::spawn(async move {
@@ -232,7 +230,7 @@ pub fn spawn_cleanup_task(
sessions.retain(|_, info| info.expires_at > now);
pending_logins.retain(|_, pl| now - pl.created_at < PENDING_LOGIN_TTL_SECS);
rate_limits.retain(|_, entry| now - entry.window_start < RATE_LIMIT_WINDOW_SECS * 2);
// Rate limit cleanup is handled automatically by governor's DashMapStateStore.
match store.gc_expired_messages(MESSAGE_TTL_SECS) {
Ok(n) if n > 0 => {

View File

@@ -346,8 +346,12 @@ impl Store for FileBackedStore {
channel_id: channel_id.to_vec(),
recipient_key: recipient_key.to_vec(),
};
let seq = *inner.next_seq.entry(key.clone()).or_insert(0);
*inner.next_seq.get_mut(&key).unwrap() = seq + 1;
let seq = {
let entry = inner.next_seq.entry(key.clone()).or_insert(0);
let s = *entry;
*entry = s + 1;
s
};
inner.map.entry(key).or_default().push_back(SeqEntry { seq, data: payload });
self.flush_delivery_map(&self.ds_path, &*inner)?;
Ok(seq)