10 fixes after code review — security, reliability, and codebase cleanup
Version v0.16.1 undergoes a thorough code audit by app.devin.ai. As a result, we're shipping 10 fixes that strengthen public API security, improve monitoring pipeline reliability, and remove accumulated dead code. These are not visual changes — they reinforce the foundation cyberapis.pl runs on.
Security
Public API rate limiting
The /api/monitorings endpoint had no request limits or parameter validation. An attacker could send unlimited requests and scan data using arbitrarily long strings in the LIKE filter. We added throttle:60,1 (60 requests per minute) and max:255 validation on url and site_name parameters. The 61st request within a minute gets HTTP 429.
Proxy trust only in production
trustProxies(at: '*') trusted all IP addresses as proxies. Behind Cloudflare this is safe, but if the application ever ran outside Cloudflare (staging, dev, direct IPv6 access), X-Forwarded-For headers could be spoofed. We restricted trust to production environment only.
Gate-based authorization instead of inline logic
The /monitoring/{monitoring}/status endpoint had 10 lines of authorization logic directly in the route closure — owner checks, public role validation, locale handling. This logic is now encapsulated in a dedicated view-monitoring-status Gate in AppServiceProvider. The route shrunk to a single line: abort_unless(Gate::allows(...), 404). Cleaner, more testable, no scattered auth logic.
Reliability
Exception propagation from monitoring job
RunMonitoringCheck called Artisan::call('monitor:check-websites'), which caught exceptions internally and returned an exit code. The queue worker saw a successful job even when monitoring crashed — zero retries, zero alerts. The job now injects MonitoringService and MonitoringEmailService directly, and exceptions propagate naturally to the queue. Failed job = retry, logs, monitoring.
Chrome profile isolation for text checking
findTextWithBrowsershot() used a hardcoded /tmp/chrome-browsershot as the Chromium profile directory. Two concurrent checks with the same --user-data-dir caused conflicts ("browser is already running"). Each run now gets a unique /tmp/cyberapis-browsershot-{uniqid} directory with full cleanup afterward — exactly the same pattern as render probes.
Dynamic SHA256 digest for agent skills
AgentSkillsController had a hardcoded SHA256 hash of the SKILL.md file. Change the file = stale digest = AI clients reject the skill. The digest is now computed dynamically via hash_file('sha256', $path) — always matches the actual file content.
Sitemap generation deduplication
Post and DocsPage called Artisan::call('sitemap:generate') synchronously on every saved/deleted event. Bulk-importing 100 pages meant 100 separate sitemap rebuilds. A new RegenerateSitemap job with ShouldBeUnique and a 10-second delay queues only one generation regardless of how many saves occur. Subsequent dispatches are ignored until the job completes.
Code Cleanup
Removed duplicate / route
routes/web.php had two route definitions for / — the first with an anonymous function, the second via HomeController::index. Laravel used the second one; the first was dead code. Removed.
Backup files removed from repository
Six files (MonitoringService.php.#1, .gemini, .przedwdrozeniem-spatie-browsershot, MonitoringResource.php.#1, Post.php.back, PostController.php.back) were lingering in app/Services/, app/Filament/Resources/, app/Models/, and app/Http/Controllers/. Removed from the repo. Added patterns *.back, *.#*, *.gemini, *.przedwdrozeniem* to .gitignore.
Removed dead MonitorWebsite component
The Livewire MonitorWebsite component was an abandoned prototype — missing view, wrong model (MonitoringResult::first() instead of Monitoring), sync HTTP in mount(), zero authorization. Never rendered anywhere. Removed entirely.
All 10 fixes are now live in production. Next up: implementing recommendations from a full security audit.