Address remaining review items: Synapse caching, cycle detection, cache/ratelimit/kanban
Security / PHP Security (semgrep) (push) Successful in 1m45s
Lint / Deploy (push) Successful in 3s
Lint / Notify on failure (push) Has been skipped
Lint / PHP (phpcs PSR-12) (push) Successful in 21s
Lint / JS (eslint) (push) Successful in 9s
Lint / PHP requirements (version + extensions) (push) Successful in 26s
Security / PHP Security (semgrep) (push) Successful in 1m45s
Lint / Deploy (push) Successful in 3s
Lint / Notify on failure (push) Has been skipped
Lint / PHP (phpcs PSR-12) (push) Successful in 21s
Lint / JS (eslint) (push) Successful in 9s
Lint / PHP requirements (version + extensions) (push) Successful in 26s
- SynapseHelper: memoize username->Matrix-ID lookups per-request (incl. negative
results) and add an overall time budget to resolveUsernames() plus a 2s connect
timeout, so notifying N watchers with a slow/unreachable Synapse can't stall the
request for N x 5s. (Chosen over async/queue per maintainer.)
- DependencyModel: fix cycle detection treating 'blocks' and 'blocked_by' as the
same edge direction. They are inverse relationships (single row each, no mirror
row), so the traversal now walks a unified precedence graph (blocks: ticket->
depends_on; blocked_by: depends_on->ticket) and wouldCreateCycle normalizes the
new edge's direction. Prevents both false-positive and missed cycles.
- CacheHelper: anchor prefix-delete to exact key boundaries (bare prefix or
prefix + '_' + md5) so delete('workflow') can't wipe a 'workflow_rules' cache.
- RateLimitMiddleware: hold an exclusive flock across the per-IP counter's
read-modify-write so concurrent requests can't both read N and write N+1
(undercounting past the limit). Fails open if the file can't be locked.
- dashboard.js: kanban status update now uses lt.api.post (per no-raw-fetch
convention) and reverts the card AND the optimistic column counts on failure
(the old raw-fetch catch left the card moved without reverting).
- BulkOperationsModel: document that bulk_status/bulk_close intentionally bypass
workflow transition validation (admin override, by design).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -84,28 +84,43 @@ class RateLimitMiddleware
|
||||
$ipHash = hash('sha256', $ip . '_' . $type);
|
||||
$filePath = self::getRateLimitDir() . '/' . $ipHash . '.json';
|
||||
|
||||
// Load existing rate data
|
||||
// Hold an exclusive lock across the whole read-modify-write so concurrent
|
||||
// requests from the same IP can't both read the same count and each write
|
||||
// count+1 (which would undercount and let the limit be exceeded).
|
||||
$fh = @fopen($filePath, 'c+');
|
||||
if ($fh === false) {
|
||||
// Can't open the counter file — fail open (don't block legitimate traffic).
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!flock($fh, LOCK_EX)) {
|
||||
fclose($fh);
|
||||
return true;
|
||||
}
|
||||
|
||||
$content = stream_get_contents($fh);
|
||||
$rateData = ['count' => 0, 'window_start' => $now];
|
||||
if (file_exists($filePath)) {
|
||||
$content = @file_get_contents($filePath);
|
||||
if ($content !== false) {
|
||||
$decoded = json_decode($content, true);
|
||||
if (is_array($decoded)) {
|
||||
$rateData = $decoded;
|
||||
}
|
||||
if ($content !== false && $content !== '') {
|
||||
$decoded = json_decode($content, true);
|
||||
if (is_array($decoded)) {
|
||||
$rateData = $decoded;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if window has expired
|
||||
if ($now - $rateData['window_start'] >= self::WINDOW_SECONDS) {
|
||||
// Reset when the window has expired
|
||||
if ($now - ($rateData['window_start'] ?? $now) >= self::WINDOW_SECONDS) {
|
||||
$rateData = ['count' => 0, 'window_start' => $now];
|
||||
}
|
||||
|
||||
// Increment count
|
||||
$rateData['count']++;
|
||||
|
||||
// Save updated data
|
||||
@file_put_contents($filePath, json_encode($rateData), LOCK_EX);
|
||||
// Rewrite the file in place while still holding the lock
|
||||
rewind($fh);
|
||||
ftruncate($fh, 0);
|
||||
fwrite($fh, json_encode($rateData));
|
||||
fflush($fh);
|
||||
flock($fh, LOCK_UN);
|
||||
fclose($fh);
|
||||
|
||||
// Check if over limit
|
||||
return $rateData['count'] <= $limit;
|
||||
|
||||
Reference in New Issue
Block a user