Verified, high-confidence fixes from a project-wide review:
- markdown.js: escape " and ' in the HTML-escape step. User-controlled
image/link URLs and alt text were interpolated into "..." attributes
without quote escaping, allowing attribute breakout and injected event
handlers (stored XSS, only mitigated by CSP). Flagged independently by
two reviewers.
- cron/create_recurring_tickets.php & cron/cleanup_ratelimit.php: a
mangled crontab example inside the docblock contained */ which closed
the comment early, causing a fatal parse error — both cron jobs never
ran. Rewrote the docblocks without a literal */.
- update_ticket.php: validate visibility BEFORE the core DB write so an
invalid payload can't leave the ticket updated while the request reports
failure (which also skipped the audit delta and stats cache invalidation).
- watch_ticket.php: GET watcher_count was capped at 6 (count of a LIMIT 6
list); use an unbounded COUNT(*) so it matches the POST path.
- notifications.php: "assigned to me" LIKE pattern lacked a trailing
delimiter, so user 12 also matched 120/123/etc.; anchor with }.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Consolidate all 20 API files to use centralized Database helper
- Add optimistic locking to ticket updates to prevent concurrent conflicts
- Add caching to StatsModel (60s TTL) for dashboard performance
- Add health check endpoint (api/health.php) for monitoring
- Improve rate limit cleanup with cron script and efficient DirectoryIterator
- Enable rate limit response headers (X-RateLimit-*)
- Add audit logging for workflow transitions
- Log Discord webhook failures instead of silencing
- Fix visibility check on export_tickets.php
- Add database migration system with performance indexes
- Fix cron recurring tickets to use assignTicket method
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>