Skip to content

Conversation

@sbansal1999
Copy link
Contributor

@sbansal1999 sbansal1999 commented Jan 4, 2026

Description

Anonymize user id for outgoing links data.

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Summary by CodeRabbit

  • New Features

    • Enhanced link tracking: richer property parsing and explicit timestamp capture for outbound links.
  • Bug Fixes

    • Improved anonymization of user identifiers using a rotating daily salt and stricter handling to reduce re-identification risk.
  • Behavior

    • Outbound link processing flow updated to ensure parsed, timestamped, and anonymized data is consistently returned.

✏️ Tip: You can customize this high-level summary in your review settings.


Note

Strengthens privacy for outgoing link events by salting anonymous_id with a daily salt and avoiding salting empty IDs.

  • In event-service.ts and routes/basket.ts, derive anonymous_id from anonymousId only after retrieving the daily salt and apply saltAnonymousId conditionally when the ID exists
  • processOutgoingLinkData is now async to fetch the daily salt; outgoing link event construction updated accordingly

Written by Cursor Bugbot for commit b886d1b. This will update automatically on new commits. Configure here.

@vercel
Copy link

vercel bot commented Jan 4, 2026

@sbansal1999 is attempting to deploy a commit to the Databuddy OSS Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 4, 2026

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

processOutgoingLinkData was changed to async and now awaits a daily salt via getDailySalt; the anonymous identifier is salted (replacing prior inline sanitization). The function also parses properties with parseProperties and includes a computed timestamp in the returned CustomOutgoingLink.

Changes

Cohort / File(s) Summary
Async outgoing link processing
apps/basket/src/routes/basket.ts
Converted processOutgoingLinkData to async and updated signature to return Promise<CustomOutgoingLink>. Obtains daily salt via getDailySalt and uses it to produce a salted anonymousId (replacing previous inline sanitization). Adds properties parsed with parseProperties and includes a precomputed timestamp in the returned object.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: anonymizing the user ID in outgoing links data. It directly aligns with the primary modification of adding daily salt retrieval and applying it to the anonymousId field.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 4, 2026

Greptile Summary

Applied anonymization to outgoing link events using daily rotating salt, matching the pattern already implemented for track events in PR #260.

  • Enhanced insertOutgoingLink() in event-service.ts to fetch daily salt and anonymize anonymous_id before sending to Kafka
  • Made processOutgoingLinkData() async in basket.ts to support salt fetching and anonymization
  • Added truthy check before calling saltAnonymousId() to prevent hashing empty strings (consistent with track event implementation)
  • Uses SHA-256 hashing with daily rotating salt stored in Redis to reduce re-identification risk while maintaining session correlation within each day

Confidence Score: 5/5

  • This PR is safe to merge with minimal risk
  • The implementation follows the exact same pattern as the track events anonymization from PR feat: anonymize user id for batch events processing. #260, with proper empty checks before salting and consistent use of daily rotating salts for privacy protection
  • No files require special attention

Important Files Changed

Filename Overview
apps/basket/src/lib/event-service.ts Added anonymization for outgoing link anonymous IDs using daily salt, consistent with track event implementation
apps/basket/src/routes/basket.ts Made processOutgoingLinkData async to support anonymization, added empty check before salting, consistent with track event pattern

Sequence Diagram

sequenceDiagram
    participant Client
    participant basket.ts
    participant event-service.ts
    participant security.ts
    participant Redis
    participant Kafka

    Client->>basket.ts: POST /batch (outgoing_link)
    basket.ts->>basket.ts: processOutgoingLinkData()
    basket.ts->>security.ts: getDailySalt()
    security.ts->>Redis: get salt:currentDay
    alt Salt exists
        Redis-->>security.ts: return cached salt
    else Salt missing
        security.ts->>security.ts: generate new salt
        security.ts->>Redis: setex salt:currentDay
        Redis-->>security.ts: OK
    end
    security.ts-->>basket.ts: return salt
    basket.ts->>basket.ts: sanitizeString(anonymousId)
    alt anonymousId is truthy
        basket.ts->>security.ts: saltAnonymousId(anonymousId, salt)
        security.ts->>security.ts: sha256(anonymousId + salt)
        security.ts-->>basket.ts: hashed anonymousId
    end
    basket.ts->>basket.ts: build CustomOutgoingLink object
    basket.ts->>event-service.ts: insertOutgoingLinksBatch()
    event-service.ts->>Kafka: sendEventBatch("analytics-outgoing-links")
    Kafka-->>event-service.ts: success
    event-service.ts-->>basket.ts: success
    basket.ts-->>Client: 200 OK

    Note over Client,Kafka: Single event path (POST / or /px.jpg)
    Client->>event-service.ts: insertOutgoingLink()
    event-service.ts->>security.ts: checkDuplicate()
    security.ts->>Redis: set dedup:outgoing_link:eventId
    Redis-->>security.ts: result
    security.ts-->>event-service.ts: isDuplicate
    alt not duplicate
        event-service.ts->>security.ts: getDailySalt()
        security.ts-->>event-service.ts: salt
        event-service.ts->>event-service.ts: sanitizeString(anonymousId)
        alt anonymousId is truthy
            event-service.ts->>security.ts: saltAnonymousId(anonymousId, salt)
            security.ts-->>event-service.ts: hashed anonymousId
        end
        event-service.ts->>Kafka: sendEvent("analytics-outgoing-links")
        Kafka-->>event-service.ts: success
    end
    event-service.ts-->>Client: success
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (4)

  1. apps/basket/src/routes/basket.ts, line 145 (link)

    logic: missing imports for getDailySalt and saltAnonymousId - this will cause runtime error

  2. apps/basket/src/routes/basket.ts, line 151 (link)

    logic: missing imports for saltAnonymousId - this will cause runtime error

  3. apps/basket/src/routes/basket.ts, line 1-37 (link)

    syntax: add missing imports from @lib/security

  4. apps/basket/src/routes/basket.ts, line 139-163 (link)

    logic: inconsistent anonymization - processOutgoingLinkData (used by /batch endpoint) anonymizes user IDs, but insertOutgoingLink in event-service.ts (used by /px.jpg and / endpoints) doesn't anonymize. this creates a data inconsistency where some outgoing link events have anonymized IDs and others don't. should the anonymization logic be moved to insertOutgoingLink in event-service.ts to ensure all endpoints anonymize consistently?

1 file reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/basket/src/routes/basket.ts (1)

139-163: Inconsistent anonymization across event processing paths creates data correlation issues.

The salting of anonymous_id is applied inconsistently across different event ingestion routes:

  • insertTrackEvent (single event): applies salting
  • processTrackEventData (batch): does not apply salting
  • insertOutgoingLink (single event): does not apply salting
  • processOutgoingLinkData (batch): applies salting

This means the same user tracked via different routes will have different anonymous_id values—some salted, some not—breaking cross-event correlation and analytics capabilities.

Fix: Apply salting consistently across all anonymous_id handling, regardless of ingestion route (single vs. batch) and event type. Alternatively, if different handling is intentional, document the rationale in code comments.

📜 Review details

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8522d08 and 0c293c2.

📒 Files selected for processing (1)
  • apps/basket/src/routes/basket.ts
🧰 Additional context used
📓 Path-based instructions (9)
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

**/*.{ts,tsx,js,jsx,vue}: Never block paste in <input> or <textarea> elements
Enter submits focused text input; in <textarea>, ⌘/Ctrl+Enter submits; Enter adds newline
Compatible with password managers and 2FA; allow pasting one-time codes

Files:

  • apps/basket/src/routes/basket.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

**/*.{ts,tsx,js,jsx}: Trim values to handle text expansion and trailing spaces in form submissions
URL reflects state (deep-link filters/tabs/pagination/expanded panels); prefer libraries like nuqs
Back/Forward buttons restore scroll position
Delay first tooltip in a group; subsequent peers have no delay
Use locale-aware formatting for dates, times, numbers, and currency
Batch layout reads/writes; avoid unnecessary reflows/repaints
Virtualize large lists using libraries like virtua

**/*.{ts,tsx,js,jsx}: Don't use accessKey attribute on any HTML element.
Don't set aria-hidden="true" on focusable elements.
Don't add ARIA roles, states, and properties to elements that don't support them.
Don't use distracting elements like <marquee> or <blink>.
Only use the scope prop on <th> elements.
Don't assign non-interactive ARIA roles to interactive HTML elements.
Make sure label elements have text content and are associated with an input.
Don't assign interactive ARIA roles to non-interactive HTML elements.
Don't assign tabIndex to non-interactive HTML elements.
Don't use positive integers for tabIndex property.
Don't include "image", "picture", or "photo" in img alt prop.
Don't use explicit role property that's the same as the implicit/default role.
Make static elements with click handlers use a valid role attribute.
Always include a title element for SVG elements.
Give all elements requiring alt text meaningful information for screen readers.
Make sure anchors have content that's accessible to screen readers.
Assign tabIndex to non-interactive HTML elements with aria-activedescendant.
Include all required ARIA attributes for elements with ARIA roles.
Make sure ARIA properties are valid for the element's supported roles.
Always include a type attribute for button elements.
Make elements with interactive roles and handlers focusable.
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden).
Always include...

Files:

  • apps/basket/src/routes/basket.ts
**/*.{ts,tsx,js,jsx,css,scss,sass,less}

📄 CodeRabbit inference engine (.cursor/rules/guidelines.mdc)

**/*.{ts,tsx,js,jsx,css,scss,sass,less}: During drag operations, disable text selection and set inert on dragged element and containers
Animations must be interruptible and input-driven; avoid autoplay

Files:

  • apps/basket/src/routes/basket.ts
**/*.{ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{ts,tsx,jsx}: Use semantic elements instead of role attributes in JSX.
Don't use unnecessary fragments.
Don't pass children as props.
Don't use the return value of React.render.
Make sure all dependencies are correctly specified in React hooks.
Make sure all React hooks are called from the top level of component functions.
Don't forget key props in iterators and collection literals.
Don't destructure props inside JSX components in Solid projects.
Don't define React components inside other components.
Don't use event handlers on non-interactive elements.
Don't assign to React component props.
Don't use both children and dangerouslySetInnerHTML props on the same element.
Don't use dangerous JSX props.
Don't use Array index in keys.
Don't insert comments as text nodes.
Don't assign JSX properties multiple times.
Don't add extra closing tags for components without children.
Use <>...</> instead of <Fragment>...</Fragment>.
Watch out for possible "wrong" semicolons inside JSX elements.
Make sure void (self-closing) elements don't have children.
Don't use target="_blank" without rel="noopener".
Don't use <img> elements in Next.js projects.
Don't use <head> elements in Next.js projects.

Files:

  • apps/basket/src/routes/basket.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't return a value from a function with the return type 'void'.
Don't use the TypeScript directive @ts-ignore.
Don't use TypeScript enums.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use TypeScript namespaces.
Don't use non-null assertions with the ! postfix operator.
Don't use parameter properties in class constructors.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use either T[] or Array<T> consistently.
Initialize each enum member value explicitly.
Use export type for types.
Use import type for types.
Make sure all enum members are literal values.
Don't use TypeScript const enum.
Don't declare empty interfaces.
Don't let variables evolve into any type through reassignments.
Don't use the any type.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use implicit any type on variable declarations.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Use consistent accessibility modifiers on class properties and methods.
Use function types instead of object types with call signatures.
Don't use void type outside of generic or return types.

**/*.{ts,tsx}: Do NOT use types 'any', 'unknown' or 'never'. Use proper explicit types
Suffix functions with 'Action' in types, like 'type Test = { testAction }'

Files:

  • apps/basket/src/routes/basket.ts
!(**/pages/_document.{ts,tsx,jsx})**/*.{ts,tsx,jsx}

📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)

Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/basket/src/routes/basket.ts
**/*.{ts,tsx,html,css}

📄 CodeRabbit inference engine (.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc)

Use bun build <file.html|file.ts|file.css> instead of webpack or esbuild for bundling

Files:

  • apps/basket/src/routes/basket.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/01-MUST-DO.mdc)

**/*.{js,jsx,ts,tsx}: Split off components, utils, and reusable code to ensure better loading speed and less complexity
Use lower-case-like-this naming convention for variables, functions, and identifiers
NEVER add placeholders, mock data, or anything similar to production code
Use Dayjs for date handling, NEVER use date-fns. Use Tanstack query for hooks, NEVER use SWR
Use json.stringify() when adding debugging code
Never use barrel exports or create index files

Files:

  • apps/basket/src/routes/basket.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (.cursor/rules/01-MUST-DO.mdc)

Handle complex data transformations independently of React. Keep modules decoupled from React for improved modularity and testability

Files:

  • apps/basket/src/routes/basket.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review

@sbansal1999
Copy link
Contributor Author

@greptile review this PR

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 152 to 156
let anonymousId = sanitizeString(
linkData.anonymousId,
VALIDATION_LIMITS.SHORT_STRING_MAX_LENGTH
);
anonymousId = saltAnonymousId(anonymousId, salt);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing empty check before salting anonymous ID. The insertTrackEvent function in event-service.ts (lines 64-70) checks if anonymousId is truthy before calling saltAnonymousId(). Without this check, an empty string will be hashed with the salt, creating a hash of just the salt, which could lead to data quality issues.

Suggested change
let anonymousId = sanitizeString(
linkData.anonymousId,
VALIDATION_LIMITS.SHORT_STRING_MAX_LENGTH
);
anonymousId = saltAnonymousId(anonymousId, salt);
let anonymousId = sanitizeString(
linkData.anonymousId,
VALIDATION_LIMITS.SHORT_STRING_MAX_LENGTH
);
if (anonymousId) {
anonymousId = saltAnonymousId(anonymousId, salt);
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/basket/src/routes/basket.ts
Line: 152:156

Comment:
Missing empty check before salting anonymous ID. The `insertTrackEvent` function in `event-service.ts` (lines 64-70) checks if `anonymousId` is truthy before calling `saltAnonymousId()`. Without this check, an empty string will be hashed with the salt, creating a hash of just the salt, which could lead to data quality issues.

```suggestion
	let anonymousId = sanitizeString(
		linkData.anonymousId,
		VALIDATION_LIMITS.SHORT_STRING_MAX_LENGTH
	);
	if (anonymousId) {
		anonymousId = saltAnonymousId(anonymousId, salt);
	}
```

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 10, 2026

Additional Comments (2)

apps/basket/src/routes/basket.ts
Missing empty check before salting anonymous ID in processTrackEventData. The insertTrackEvent function in event-service.ts (lines 64-70) checks if anonymousId is truthy before calling saltAnonymousId(). This function should follow the same pattern for consistency.

		let anonymousId = sanitizeString(
			trackData.anonymousId,
			VALIDATION_LIMITS.SHORT_STRING_MAX_LENGTH
		);
		if (anonymousId) {
			anonymousId = saltAnonymousId(anonymousId, salt);
		}
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/basket/src/routes/basket.ts
Line: 70:74

Comment:
Missing empty check before salting anonymous ID in `processTrackEventData`. The `insertTrackEvent` function in `event-service.ts` (lines 64-70) checks if `anonymousId` is truthy before calling `saltAnonymousId()`. This function should follow the same pattern for consistency.

```suggestion
		let anonymousId = sanitizeString(
			trackData.anonymousId,
			VALIDATION_LIMITS.SHORT_STRING_MAX_LENGTH
		);
		if (anonymousId) {
			anonymousId = saltAnonymousId(anonymousId, salt);
		}
```

How can I resolve this? If you propose a fix, please make it concise.

apps/basket/src/routes/basket.ts
Critical inconsistency: This PR only anonymizes user IDs for outgoing links coming through the /batch endpoint. However, outgoing link events from the /px.jpg endpoint (line 198) and / endpoint (line 443) call insertOutgoingLink() directly, which does NOT anonymize the anonymous_id (see event-service.ts lines 207-210).

This creates a data inconsistency where:

  • Events from /batch → anonymized IDs (hashed with daily salt)
  • Events from /px.jpg and / → raw, non-anonymized IDs

This will make it impossible to correlate events properly in analytics and violates the stated goal of anonymizing user IDs for outgoing links.

Recommendation: The anonymization logic should be moved into insertOutgoingLink() in event-service.ts to ensure all endpoints handle user IDs consistently, similar to how insertTrackEvent() already handles anonymization.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/basket/src/routes/basket.ts
Line: 144:168

Comment:
Critical inconsistency: This PR only anonymizes user IDs for outgoing links coming through the `/batch` endpoint. However, outgoing link events from the `/px.jpg` endpoint (line 198) and `/` endpoint (line 443) call `insertOutgoingLink()` directly, which does NOT anonymize the `anonymous_id` (see `event-service.ts` lines 207-210). 

This creates a data inconsistency where:
- Events from `/batch` → anonymized IDs (hashed with daily salt)
- Events from `/px.jpg` and `/` → raw, non-anonymized IDs

This will make it impossible to correlate events properly in analytics and violates the stated goal of anonymizing user IDs for outgoing links.

**Recommendation**: The anonymization logic should be moved into `insertOutgoingLink()` in `event-service.ts` to ensure all endpoints handle user IDs consistently, similar to how `insertTrackEvent()` already handles anonymization.

How can I resolve this? If you propose a fix, please make it concise.

@izadoesdev
Copy link
Member

check greptile's comments if possible @sbansal1999

@sbansal1999
Copy link
Contributor Author

@greptile review this PR

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional Comments (1)

  1. apps/basket/src/routes/basket.ts, line 146-172 (link)

    logic: Incomplete anonymization: insertOutgoingLink in event-service.ts:183-226 doesn't anonymize anonymousId (it only sanitizes on lines 207-210). This creates inconsistent behavior where:

    • /batch endpoint → uses processOutgoingLinkData → anonymizes ✓
    • / and /px.jpg endpoints → use insertOutgoingLink → doesn't anonymize ✗

    This means outgoing link data sent via single events still has raw anonymousId values, defeating the purpose of this PR.

    // In event-service.ts insertOutgoingLink, add:
    const salt = await getDailySalt();
    let anonymousId = sanitizeString(
      linkData.anonymousId,
      VALIDATION_LIMITS.SHORT_STRING_MAX_LENGTH
    );
    if (anonymousId) {
      anonymousId = saltAnonymousId(anonymousId, salt);
    }

1 file reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@sbansal1999
Copy link
Contributor Author

@greptile review this PR

@sbansal1999
Copy link
Contributor Author

@izadoesdev this can be merged now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants