ElevatedThe 'Link analytics and security features' clusters introduce password hashing, IP logging, and in-memory rate limiting, which together pose security and privacy risks that require careful review.
in scope- Password hashing strength and consistency across API and UI clusters must be verified to avoid weak or inconsistent protection.
- IP logging for click analytics introduces a privacy concern that needs to be aligned with data retention policies.
- In-memory rate limiting may not scale across multiple instances and could lead to inconsistent enforcement.
- Client-side password handling in the UI cluster increases the attack surface and must be validated for secure transmission.
original summary## Summary
- Add per-link click analytics with a `clicks` event table (referer, user-agent, IP, timestamp) and `GET /links/:id/clicks` for the last 30 days + top referrers.
- Optional **link expiration** and **password-protected links**, with a new `:slug/unlock` endpoint to exchange the password for the destination URL.
- Per-user **in-memory token-bucket rate limiter** on link creation, plus `DELETE /links/:id` for owned links.
- Dashboard shows expiry/password badges, copy-to-clipboard, and a delete control. New `LinkDetails` page with an inline-SVG sparkline and top-referrer list.
## Files changed
20 files, ~540 insertions / ~25 deletions. Mix of new feature files (`apps/api/src/routes/analytics.ts`, `apps/web/src/pages/LinkDetails.tsx`, shared `clickSummarySchema`, etc.) and modifications across the API, web, and shared packages.
architecture analysisRevised risk · high
The PR introduces password-protected links and click analytics, but password strength is weak across schema, API, and UI, and IP logging lacks anonymization. Rate limiting and expiration validation are inconsistent, creating security and correctness risks spanning the API, root config, and frontend.
Fragmented password policy across layersSecurity
Password strength requirements are inconsistent: shared schema allows 4-char passwords, API uses weak bcrypt (8 rounds), and UI stores plaintext. No central enforcement exists, weakening overall security.
spans: , , IP privacy not consistently protectedPrivacy
API logs raw IPs and database stores them without anonymization, while root config trusts all proxies, enabling IP spoofing. This exposes PII and undermines IP-based rate limiting.
spans: , Rate limiting lacks coordination and evictionPerformance
The in-memory rate limiter grows unbounded, anonymous users share a bucket, and response headers are missing. These issues span the API and root config, risking memory leaks and unfair throttling.
spans: , Expiration date validation missing across frontend and schemaCorrectness
Frontend allows past dates and crashes on invalid input, while the shared schema lacks future-date validation. This leads to inconsistent link expiration behavior and potential user errors.
spans: ,
7 blockers18 validated1 likely false positive