diff --git a/.jules/sentinel.md b/.jules/sentinel.md index d16e3dd..3739f7e 100644 --- a/.jules/sentinel.md +++ b/.jules/sentinel.md @@ -60,3 +60,13 @@ **Vulnerability:** Missing `object-src 'none'` and `block-all-mixed-content` in CSP. **Learning:** Without `object-src 'none'`, attackers could potentially inject malicious `` or `` elements (e.g. Flash/Java) if an injection flaw exists. Without `block-all-mixed-content`, active network attacks could downgrade HTTPS connections by loading HTTP subresources. **Prevention:** Always include `object-src 'none'; block-all-mixed-content;` in the baseline Content-Security-Policy to proactively disable legacy plugins and enforce secure transport. + +## 2026-06-28 - [Fetch Timeout Security Enhancement] +**Vulnerability:** Missing timeout on network requests in `fetchWithProgress.js`. +**Learning:** `fetch` requests without a timeout can cause the application to hang indefinitely if the server is unresponsive, potentially leading to resource exhaustion or a poor user experience. +**Prevention:** Always implement a timeout (e.g., using `AbortController`) for network requests to ensure they fail gracefully. + +## 2026-06-28 - [Insecure Randomness Security Enhancement] +**Vulnerability:** Weak random number generation using `Math.random()`. +**Learning:** `Math.random()` is not cryptographically secure, which could lead to predictable outcomes in simulation generation or terrain seeding. +**Prevention:** Use `window.crypto.getRandomValues()` instead of `Math.random()` to ensure strong random number generation for security or unpredictability. diff --git a/src/simulation/fetchWithProgress.js b/src/simulation/fetchWithProgress.js index 280d9db..2a55471 100644 --- a/src/simulation/fetchWithProgress.js +++ b/src/simulation/fetchWithProgress.js @@ -50,12 +50,18 @@ export async function readResponseBodyWithProgress(response, onProgress) { * @param {(ratio: number) => void} [onProgress] * @returns {Promise} */ -export async function fetchUint8ArrayWithProgress(url, onProgress) { - const response = await fetch(url); - if (!response.ok) { - throw new Error(`Failed to fetch ${url}: ${response.status}`); +export async function fetchUint8ArrayWithProgress(url, onProgress, timeoutMs = 30000) { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), timeoutMs); + try { + const response = await fetch(url, { signal: controller.signal }); + if (!response.ok) { + throw new Error(`Failed to fetch ${url}: ${response.status}`); + } + return await readResponseBodyWithProgress(response, onProgress); + } finally { + clearTimeout(timeoutId); } - return readResponseBodyWithProgress(response, onProgress); } /** diff --git a/src/simulation/main.js b/src/simulation/main.js index 177843a..b858040 100644 --- a/src/simulation/main.js +++ b/src/simulation/main.js @@ -705,7 +705,10 @@ export class MuJoCoDemo { if (!this.simulation || !this.model) { return; } - const theta = Math.random() * Math.PI * 2; + const cryptoRandom = typeof window !== 'undefined' && window.crypto + ? window.crypto.getRandomValues(new Uint32Array(1))[0] / 4294967296 + : Math.random(); + const theta = cryptoRandom * Math.PI * 2; this._knockdownDirX = Math.cos(theta); this._knockdownDirY = Math.sin(theta); this._knockdownSubstepsRemaining = 14; diff --git a/src/views/Demo.vue b/src/views/Demo.vue index 32afe29..6ce8a86 100644 --- a/src/views/Demo.vue +++ b/src/views/Demo.vue @@ -1825,7 +1825,10 @@ export default { this.bfmSimTime = null; }, bfmTerrainRandomize() { - this.bfmTerrainSeed = Math.floor(Math.random() * 1e9) + 1; + const cryptoRandom = typeof window !== 'undefined' && window.crypto + ? window.crypto.getRandomValues(new Uint32Array(1))[0] / 4294967296 + : Math.random(); + this.bfmTerrainSeed = Math.floor(cryptoRandom * 1e9) + 1; }, bfmTerrainGenerate() { const cfg = {