feat: hash analytics IPs and add data-retention cron #153
No reviewers
Labels
No labels
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
javascript
question
severity: high
severity: low
severity: medium
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
isyuricunha/website!153
Loading…
Reference in a new issue
No description provided.
Delete branch "feat/ip-privacy-hashing-and-retention-v2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
analytics_eventsare now stored as a salted SHA-256 hash (hashIpForAnalytics, salt viaANALYTICS_IP_HASH_SALT) instead of plaintext. Confirmed this is the only write site for that column./api/cron/data-retention(30 3 * * *invercel.json) prunes old rows fromanalytics_events(180d),performance_metrics(90d),login_attempts(90d),ai_chat_feedback(365d), and resolvedsecurity_events/error_logs(180d). Unresolved security events and error logs are never deleted, regardless of age — checked against theresolvedcolumn in the schema.security_events,login_attempts,ip_access_control,error_logsintentionally keep plaintext IPs — they back the real manual IP-block admin feature and need to stay readable.ANALYTICS_IP_HASH_SALTdocumented in.env.exampleand declared inturbo.json's env allowlist.Lint fix included in this PR
While working on this,
apps/web'slint-stagedpre-commit hook flagged 93 pre-existing errors inmonitoring.tsthat had nothing to do with my 2-line change. Root cause: inlinez.object({...})schemas passed directly into.input(...)pushunicorn/max-nested-callsover its limit, plus severalunicorn/no-for-eachandunicorn/no-computed-property-existence-checkviolations.Fixed properly instead of bypassing the hook:
.forEach()loops tofor...of.key in objectexistence checks withObject.hasOwn(object, key)(theinoperator also matches inherited properties, whichno-computed-property-existence-checkflags).get(e.g.getPerformanceMetricsSchema→performanceMetricsQuerySchema) to satisfyunicorn/no-non-function-verb-prefix.monitoring.tsnow lints clean with zero errors — down from 93. No behavior change from these fixes; verified with the full test suite before and after.Related, separately-scoped finding (not fixed here):
apps/web'slintscript itself (pnpm -w exec eslint . --max-warnings 0) was resolving paths incorrectly and only linting 13 root config files instead ofapps/web/src— meaningpnpm turbo lint(what CI runs) was never actually checking this app's code. That's tracked separately in #152 and affects ~1000 more pre-existing findings across the rest ofapps/web, out of scope for this PR.Testing
apps/web/src/tests/lib/ip-hash.test.ts(new) — hash determinism, salt sensitivity, null handling.apps/web/src/tests/api/cron-data-retention-route.test.ts(new) — auth check, lock acquisition/release, per-table deletion counts, resolved-only filtering for security events/error logs.tsc --noEmit: clean.eslint(bare, not through the broken script) on every changed file: 0 errors.lint-stagedpre-commit hook — no--no-verify.Vercel cron notes
Project is on the Hobby plan (confirmed via an existing commit
8ba8aa42that already adjusted a cron for this). Hobby allows at most 1 execution/day per cron and doesn't guarantee the exact minute within the scheduled hour — both fine for this use case since retention doesn't need minute-level precision. Cron only fires against production, never preview deployments, and an in-flight cron run is not interrupted by a new deploy.Supersedes #151, which had the same functional change but two commits made with
--no-verifyto bypass the pre-existing lint failures instead of fixing them.The latest updates on your projects. Learn more about Vercel for GitHub.