fix(billing): resolve all audit findings — CSRF, float precision, TOCTOU, error sanitization
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

- Add CSRF token protection for mock payment (SHA256 + constant-time verify)
- Replace f64 currency conversion with pure integer string parsing (parse_yuan_to_cents)
- Move subscription check inside transaction to prevent TOCTOU race
- Rewrite increment_usage to use atomic SQL (account_id+period_start WHERE)
- Add trade_no format validation in payment callback
- Sanitize error messages to prevent sensitive data leakage
- Use i32::try_from for WeChat amount conversion (prevent truncation)
- Replace window.__ZCLAW_STATS_SYNC_INTERVAL__ with useRef pattern
- Replace eprintln/println with tracing macros in lifecycle
- Remove unused variable in scheduler
- Remove duplicate sha2 and unused hmac from Cargo.toml
This commit is contained in:
iven
2026-04-02 20:04:43 +08:00
parent 8898bb399e
commit da438ad868
7 changed files with 127 additions and 34 deletions

View File

@@ -94,7 +94,7 @@ pub async fn kernel_init(
// Config changed, need to reboot kernel
// Shutdown old kernel
if let Err(e) = kernel.shutdown().await {
eprintln!("[kernel_init] Warning: Failed to shutdown old kernel: {}", e);
tracing::warn!("[kernel_init] Failed to shutdown old kernel: {}", e);
}
*kernel_lock = None;
}
@@ -117,9 +117,9 @@ pub async fn kernel_init(
// Debug: print skills directory
if let Some(ref skills_dir) = config.skills_dir {
println!("[kernel_init] Skills directory: {} (exists: {})", skills_dir.display(), skills_dir.exists());
tracing::debug!("[kernel_init] Skills directory: {} (exists: {})", skills_dir.display(), skills_dir.exists());
} else {
println!("[kernel_init] No skills directory configured");
tracing::debug!("[kernel_init] No skills directory configured");
}
let base_url = config.llm.base_url.clone();

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect, useCallback, useRef } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import './index.css';
import { Sidebar, MainViewType } from './components/Sidebar';
@@ -54,6 +54,7 @@ function App() {
const [bootstrapStatus, setBootstrapStatus] = useState('Initializing...');
const [showOnboarding, setShowOnboarding] = useState(false);
const [showDetailDrawer, setShowDetailDrawer] = useState(false);
const statsSyncRef = useRef<ReturnType<typeof setInterval> | null>(null);
// Hand Approval state
const [pendingApprovalRun, setPendingApprovalRun] = useState<HandRun | null>(null);
@@ -253,9 +254,8 @@ function App() {
}
}, MEMORY_STATS_SYNC_INTERVAL);
// Store interval for cleanup
// @ts-expect-error - Global cleanup reference
window.__ZCLAW_STATS_SYNC_INTERVAL__ = statsSyncInterval;
// Store interval for cleanup via ref
statsSyncRef.current = statsSyncInterval;
} catch (err) {
log.warn('Failed to start heartbeat engine:', err);
// Non-critical, continue without heartbeat
@@ -334,10 +334,8 @@ function App() {
return () => {
mounted = false;
// Clean up periodic stats sync interval
// @ts-expect-error - Global cleanup reference
if (window.__ZCLAW_STATS_SYNC_INTERVAL__) {
// @ts-expect-error - Global cleanup reference
clearInterval(window.__ZCLAW_STATS_SYNC_INTERVAL__);
if (statsSyncRef.current) {
clearInterval(statsSyncRef.current);
}
};
}, [connect, onboardingNeeded, onboardingLoading, isLoggedIn]);