-
-
Notifications
You must be signed in to change notification settings - Fork 11.1k
Added routing support to profile tabs in ActivityPub #25090
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughAdds a profile preview hover card and integrates it into feed and suggested profiles: new ProfilePreviewHoverCard component, APAvatar no longer sets title, and hover-card UI from Shade is exported. Updates routing and profile UI to support profile/:handle/:tab, normalizes tab/handle via actualHandle/activeTab props, and converts ProfilePage to controlled tabs using navigation. Extends useAccountForUser with an optional enabled flag and changes a suggested_profiles cache key. Adds a 'preview' feature flag and hardens openLinksInNewTab against javascript:/data:/vbscript: URLs. Bumps admin-x-activitypub version and adds Radix hover-card dependency. Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/admin-x-activitypub/src/views/Profile/Profile.tsx (1)
143-182
: Fix likes-tab handling for other profilesWhen you visit a path like
/profile/alice/likes
, the normalization above setsactiveTab
to'likes'
even thoughProfilePage
hides that tab wheneveractualHandle
is defined. The tab list then renders without a matching trigger/content, leaving the main pane empty (no posts, no likes, no messaging). Please fall back to'posts'
(or redirect) whenever a non-current-user profile requestslikes
, so we never publish a state that can’t render.Apply this diff to constrain
likes
to the current user:- } else if (params.tab) { - // We have both handle and tab - if (validTabs.includes(params.tab)) { - activeTab = params.tab; - } - // activeTab stays 'posts' if tab is invalid - } + } else if (params.tab) { + // We have both handle and tab + if (params.tab === 'likes' && params.handle) { + // Likes are only available for the current user; fall back to posts + activeTab = 'posts'; + } else if (validTabs.includes(params.tab)) { + activeTab = params.tab; + } + // activeTab stays 'posts' if tab is invalid + }
🧹 Nitpick comments (1)
apps/admin-x-activitypub/src/utils/content-formatters.ts (1)
103-110
: Consider removing or replacing sanitized links.The security check correctly blocks dangerous protocols (javascript:, data:, vbscript:), but removing only the
href
attribute leaves a non-functional anchor element that still receivestarget="_blank"
andrel="noopener noreferrer"
. This creates a visual link that doesn't navigate anywhere.Consider either:
- Removing the entire anchor element and replacing it with plain text
- Replacing the link with a warning message
- Skipping the target/rel attributes if href was removed
Apply this diff to skip adding attributes to sanitized links:
// Add target="_blank" and rel attributes to each link for (let i = 0; i < links.length; i++) { const href = links[i].getAttribute('href') || ''; // Block javascript:, data:, and other dangerous protocols if (href.match(/^\s*(javascript|data|vbscript):/i)) { links[i].removeAttribute('href'); + continue; } links[i].setAttribute('target', '_blank'); links[i].setAttribute('rel', 'noopener noreferrer'); }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
📒 Files selected for processing (14)
apps/admin-x-activitypub/package.json
(1 hunks)apps/admin-x-activitypub/src/components/feed/FeedItem.tsx
(3 hunks)apps/admin-x-activitypub/src/components/global/APAvatar.tsx
(1 hunks)apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx
(1 hunks)apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts
(3 hunks)apps/admin-x-activitypub/src/lib/feature-flags.tsx
(1 hunks)apps/admin-x-activitypub/src/routes.tsx
(1 hunks)apps/admin-x-activitypub/src/utils/content-formatters.ts
(1 hunks)apps/admin-x-activitypub/src/views/Feed/components/SuggestedProfiles.tsx
(2 hunks)apps/admin-x-activitypub/src/views/Profile/Profile.tsx
(2 hunks)apps/admin-x-activitypub/src/views/Profile/components/ProfilePage.tsx
(6 hunks)apps/shade/package.json
(1 hunks)apps/shade/src/components/ui/hover-card.tsx
(1 hunks)apps/shade/src/index.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
apps/shade/src/components/ui/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/shade/AGENTS.md)
apps/shade/src/components/ui/**/*.{ts,tsx}
: Place new UI components under src/components/ui
Do not rename ShadCN-generated files purely for casing; keep kebab-case filenames while component identifiers use PascalCase
Files:
apps/shade/src/components/ui/hover-card.tsx
apps/shade/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/shade/AGENTS.md)
Use camelCase for functions and variables
Files:
apps/shade/src/components/ui/hover-card.tsx
apps/shade/src/index.ts
apps/shade/src/components/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/shade/AGENTS.md)
apps/shade/src/components/**/*.{ts,tsx}
: Use CVA (class-variance-authority) for variants where useful
Prefer composable components and compound subcomponents (e.g., Header.Title, Header.Meta, Header.Actions) over heavy prop configs; attach parts as static properties and export as named exports
Files:
apps/shade/src/components/ui/hover-card.tsx
apps/shade/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/shade/AGENTS.md)
Use the @ alias for internal imports (e.g., @/lib/utils)
Files:
apps/shade/src/components/ui/hover-card.tsx
apps/shade/src/index.ts
apps/shade/src/index.ts
📄 CodeRabbit inference engine (apps/shade/AGENTS.md)
Export new UI components from src/index.ts
Files:
apps/shade/src/index.ts
🧠 Learnings (3)
📚 Learning: 2025-09-11T11:56:37.466Z
Learnt from: CR
PR: TryGhost/Ghost#0
File: apps/shade/AGENTS.md:0-0
Timestamp: 2025-09-11T11:56:37.466Z
Learning: Applies to apps/shade/src/components/**/*.{tsx} : Expose sensible Radix/HTML props and always include className merged via cn(...)
Applied to files:
apps/shade/src/components/ui/hover-card.tsx
📚 Learning: 2025-09-11T11:56:37.466Z
Learnt from: CR
PR: TryGhost/Ghost#0
File: apps/shade/AGENTS.md:0-0
Timestamp: 2025-09-11T11:56:37.466Z
Learning: Applies to apps/shade/src/index.ts : Export new UI components from src/index.ts
Applied to files:
apps/shade/src/index.ts
📚 Learning: 2025-09-11T11:56:37.466Z
Learnt from: CR
PR: TryGhost/Ghost#0
File: apps/shade/AGENTS.md:0-0
Timestamp: 2025-09-11T11:56:37.466Z
Learning: Applies to apps/shade/src/components/ui/**/*.{ts,tsx} : Place new UI components under src/components/ui
Applied to files:
apps/shade/src/index.ts
🧬 Code graph analysis (4)
apps/admin-x-activitypub/src/views/Profile/Profile.tsx (1)
apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (1)
useAccountForUser
(1619-1631)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx (4)
apps/admin-x-framework/src/api/activitypub.ts (1)
ActorProperties
(29-63)apps/admin-x-activitypub/src/lib/feature-flags.tsx (1)
useFeatureFlags
(84-90)apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (1)
useAccountForUser
(1619-1631)apps/admin-x-activitypub/src/utils/content-formatters.ts (2)
openLinksInNewTab
(93-113)stripHtml
(1-52)
apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (1)
apps/admin-x-activitypub/src/api/activitypub.ts (1)
Account
(9-30)
apps/admin-x-activitypub/src/components/feed/FeedItem.tsx (2)
apps/admin-x-activitypub/src/utils/handle-profile-click.ts (1)
handleProfileClick
(5-12)apps/admin-x-activitypub/src/utils/render-timestamp.tsx (1)
renderTimestamp
(15-40)
🪛 ast-grep (0.39.5)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx
[warning] 170-170: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx
[error] 171-171: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
⏰ 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). (3)
- GitHub Check: Setup
- GitHub Check: Build & Push
- GitHub Check: Setup
🔇 Additional comments (8)
apps/shade/package.json (1)
78-78
: LGTM!The dependency addition for Radix UI's hover-card component is appropriate and follows the existing pattern of using caret versioning for Radix UI packages.
apps/shade/src/index.ts (1)
17-17
: LGTM!The export for the new hover-card UI component is correctly placed alphabetically and follows the established pattern for exporting UI components from the index.
Based on learnings.
apps/admin-x-activitypub/src/components/global/APAvatar.tsx (1)
190-190
: LGTM!The removal of the title attribute is appropriate since profile information is now provided through the ProfilePreviewHoverCard component, which offers a richer and more accessible user experience than browser-native tooltips.
apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (2)
1619-1624
: LGTM!The optional
enabled
flag addition touseAccountForUser
provides fine-grained control over query execution while maintaining backward compatibility through the default value. This aligns well with the ProfilePreviewHoverCard use case where account data may need to be fetched conditionally based on user interaction.
1117-1117
: LGTM!The cache key updates from
'suggested_profiles_json'
to'suggested_profiles'
align with the query key definition at line 78 and improve consistency across the codebase. Usingexact: false
correctly matches all suggested profile queries regardless of theirhandle
andlimit
parameters.Also applies to: 1281-1281
apps/shade/src/components/ui/hover-card.tsx (1)
1-27
: LGTM!The HoverCard component implementation follows best practices:
- Uses forwardRef pattern for proper ref forwarding
- Exposes sensible Radix UI props with appropriate defaults
- Merges className via the cn utility as per coding guidelines
- Sets displayName for better debugging experience
- Provides comprehensive styling with animations and proper positioning
Based on coding guidelines.
apps/admin-x-activitypub/src/routes.tsx (1)
83-87
: LGTM!The new route for
profile/:handle/:tab
correctly extends the profile routing to support tab-based navigation. The placement after the existing profile routes and the reuse of the same Profile component and pageTitle is appropriate and maintains consistency.apps/admin-x-activitypub/src/views/Feed/components/SuggestedProfiles.tsx (1)
141-150
: LGTM!The ProfilePreviewHoverCard integration enhances the user experience by providing rich profile previews on hover. The wrapping preserves existing click navigation and only applies when profiles are loaded, maintaining the loading state skeleton behavior.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #25090 +/- ##
==========================================
- Coverage 71.50% 71.50% -0.01%
==========================================
Files 1531 1531
Lines 115939 115939
Branches 13985 13986 +1
==========================================
- Hits 82907 82903 -4
+ Misses 32044 32028 -16
- Partials 988 1008 +20
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
This component will be used for the profile preview card when hovering
As we now display profile preview when avatars are hovered, we do not need to display the user info in generic title attribute
- added "enabled" option to useAccountForUser() hook, so that it supports on demand requests which is used for profile preview card - updated follow/unfollow caches, so that they are updated correctly when the follow button is clicked from the profile preview card
- this component uses HoverCard component from shadcn under the hood - the children of the component will be used as a trigger - when the trigger is hovered, it first makes a request for fetching account data, and then displays the data in the preview - if the data is not ready yet, the preview card will display skeleton loader
This was recommended by coderabbit. As the amount of change was small, including the fix right in the PR instead of creating a new issue
cf856ab
to
06f2fd4
Compare
if (!params.tab && params.handle && validTabs.includes(params.handle)) { | ||
// This means the URL is /profile/likes (current user with tab) | ||
activeTab = params.handle; | ||
actualHandle = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Tab Routing Fails for Direct Navigation
The tab routing logic has a couple of issues. The validTabs
array is missing 'posts', which means direct URL navigation to the posts tab won't work. Also, if a user's handle matches a tab name (like 'likes'), the system incorrectly interprets it as a tab for the current user, making those profiles inaccessible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
apps/admin-x-activitypub/src/views/Profile/Profile.tsx (1)
147-152
: Routing conflict persists: tab names shadow user handles.This logic still misinterprets a user handle that matches a tab name (e.g., a user named 'likes') as a tab for the current user, preventing access to that user's profile.
🧹 Nitpick comments (3)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx (2)
32-38
: Consider simplifying handle construction logic.The fallback logic for constructing
targetHandle
has multiple paths. While functional, the try-catch block could be simplified to improve readability.Consider this refactor:
- let targetHandle = actor?.handle; - if (!targetHandle && actor?.preferredUsername && actor?.id) { - try { - targetHandle = `@${actor.preferredUsername}@${new URL(actor.id).hostname.replace(/^www\./, '')}`; - } catch { - targetHandle = null; - } - } + const targetHandle = actor?.handle || ( + actor?.preferredUsername && actor?.id + ? (() => { + try { + return `@${actor.preferredUsername}@${new URL(actor.id).hostname.replace(/^www\./, '')}`; + } catch { + return null; + } + })() + : null + );
52-60
: Refetch logic may cause unnecessary network calls.The
useEffect
triggers a refetch whenevershouldFetch
becomes true and data is incomplete. However, this could cause repeated fetches if the query keeps failing or returning incomplete data.Consider adding a retry limit or debouncing the refetch:
+ const [retryCount, setRetryCount] = useState(0); + const MAX_RETRIES = 3; + useEffect(() => { - if (!shouldFetch || !targetHandle) { + if (!shouldFetch || !targetHandle || retryCount >= MAX_RETRIES) { return; } if (!hasCompleteAccountData && !accountQuery.isFetching && !accountQuery.isLoading) { accountQuery.refetch({cancelRefetch: false}); + setRetryCount(prev => prev + 1); } - }, [accountQuery, accountQuery.isFetching, accountQuery.isLoading, accountQuery.refetch, hasCompleteAccountData, shouldFetch, targetHandle]); + }, [accountQuery, accountQuery.isFetching, accountQuery.isLoading, accountQuery.refetch, hasCompleteAccountData, shouldFetch, targetHandle, retryCount]); + + // Reset retry count when hover ends + useEffect(() => { + if (!shouldFetch) { + setRetryCount(0); + } + }, [shouldFetch]);apps/admin-x-activitypub/src/views/Profile/components/ProfilePage.tsx (1)
52-54
: Optimize currentUser query with conditional fetching.The
currentAccountQuery
always executes, even when viewing the current user's profile (actualHandle is undefined). This wastes a network request since we already know it's the current user.Apply this diff to enable the query conditionally:
- const currentAccountQuery = useAccountForUser('index', 'me'); - const {data: currentUser} = actualHandle ? currentAccountQuery : {data: undefined}; + const currentAccountQuery = useAccountForUser('index', 'me', {enabled: !!actualHandle}); + const {data: currentUser} = currentAccountQuery; const isCurrentUser = actualHandle === currentUser?.handle || !actualHandle;
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
yarn.lock
is excluded by!**/yarn.lock
,!**/*.lock
📒 Files selected for processing (14)
apps/admin-x-activitypub/package.json
(1 hunks)apps/admin-x-activitypub/src/components/feed/FeedItem.tsx
(3 hunks)apps/admin-x-activitypub/src/components/global/APAvatar.tsx
(1 hunks)apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx
(1 hunks)apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts
(3 hunks)apps/admin-x-activitypub/src/lib/feature-flags.tsx
(1 hunks)apps/admin-x-activitypub/src/routes.tsx
(1 hunks)apps/admin-x-activitypub/src/utils/content-formatters.ts
(1 hunks)apps/admin-x-activitypub/src/views/Feed/components/SuggestedProfiles.tsx
(2 hunks)apps/admin-x-activitypub/src/views/Profile/Profile.tsx
(2 hunks)apps/admin-x-activitypub/src/views/Profile/components/ProfilePage.tsx
(6 hunks)apps/shade/package.json
(1 hunks)apps/shade/src/components/ui/hover-card.tsx
(1 hunks)apps/shade/src/index.ts
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- apps/admin-x-activitypub/src/components/feed/FeedItem.tsx
- apps/shade/src/components/ui/hover-card.tsx
- apps/admin-x-activitypub/package.json
- apps/admin-x-activitypub/src/lib/feature-flags.tsx
🧰 Additional context used
📓 Path-based instructions (3)
apps/shade/src/index.ts
📄 CodeRabbit inference engine (apps/shade/AGENTS.md)
Export new UI components from src/index.ts
Files:
apps/shade/src/index.ts
apps/shade/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/shade/AGENTS.md)
Use camelCase for functions and variables
Files:
apps/shade/src/index.ts
apps/shade/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/shade/AGENTS.md)
Use the @ alias for internal imports (e.g., @/lib/utils)
Files:
apps/shade/src/index.ts
🧠 Learnings (2)
📚 Learning: 2025-09-11T11:56:37.466Z
Learnt from: CR
PR: TryGhost/Ghost#0
File: apps/shade/AGENTS.md:0-0
Timestamp: 2025-09-11T11:56:37.466Z
Learning: Applies to apps/shade/src/index.ts : Export new UI components from src/index.ts
Applied to files:
apps/shade/src/index.ts
📚 Learning: 2025-09-11T11:56:37.466Z
Learnt from: CR
PR: TryGhost/Ghost#0
File: apps/shade/AGENTS.md:0-0
Timestamp: 2025-09-11T11:56:37.466Z
Learning: Applies to apps/shade/src/components/ui/**/*.{ts,tsx} : Place new UI components under src/components/ui
Applied to files:
apps/shade/src/index.ts
🧬 Code graph analysis (4)
apps/admin-x-activitypub/src/views/Profile/components/ProfilePage.tsx (1)
apps/shade/src/components/ui/tabs.tsx (4)
Tabs
(296-296)TabsList
(297-297)TabsTrigger
(298-298)TabsContent
(300-300)
apps/admin-x-activitypub/src/views/Profile/Profile.tsx (1)
apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (1)
useAccountForUser
(1619-1631)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx (5)
apps/admin-x-framework/src/api/activitypub.ts (1)
ActorProperties
(29-63)apps/admin-x-activitypub/src/lib/feature-flags.tsx (1)
useFeatureFlags
(84-90)apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (1)
useAccountForUser
(1619-1631)apps/admin-x-activitypub/src/utils/content-formatters.ts (2)
openLinksInNewTab
(93-113)stripHtml
(1-52)apps/admin-x-activitypub/src/utils/handle-profile-click.ts (1)
handleProfileClick
(5-12)
apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (1)
apps/admin-x-activitypub/src/api/activitypub.ts (1)
Account
(9-30)
🪛 ast-grep (0.39.5)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx
[warning] 170-170: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx
[error] 171-171: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
⏰ 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). (3)
- GitHub Check: Setup
- GitHub Check: Setup
- GitHub Check: Build & Push
🔇 Additional comments (13)
apps/admin-x-activitypub/src/hooks/use-activity-pub-queries.ts (2)
1619-1624
: LGTM! Good pattern for conditional fetching.The addition of an optional
enabled
flag touseAccountForUser
allows callers to control when queries execute. This is correctly implemented and follows React Query best practices.
1117-1117
: Cache key migration complete: no remainingsuggested_profiles_json
references detected.apps/shade/package.json (1)
78-78
: LGTM! Dependency addition aligns with new hover card feature.The
@radix-ui/react-hover-card
dependency is required for the new ProfilePreviewHoverCard component. The version constraint is consistent with other Radix UI dependencies in the project.apps/admin-x-activitypub/src/routes.tsx (1)
83-87
: LGTM! Route addition enables tab-based navigation.The new route
profile/:handle/:tab
correctly extends profile routing to support URL-based tab selection. The route is properly positioned after the less specificprofile/:handle
route.apps/shade/src/index.ts (1)
17-17
: LGTM! Export follows project conventions.The hover-card component export is correctly placed in alphabetical order and follows the established pattern for UI component exports.
Based on coding guidelines.
apps/admin-x-activitypub/src/utils/content-formatters.ts (1)
103-107
: LGTM! Security enhancement prevents XSS attacks.The addition of protocol sanitization blocks dangerous URI schemes (
javascript:
,data:
,vbscript:
) before applyingtarget="_blank"
. This is a critical security improvement that prevents XSS attacks through malicious href attributes.apps/admin-x-activitypub/src/views/Feed/components/SuggestedProfiles.tsx (1)
141-150
: LGTM! ProfilePreviewHoverCard integration enhances UX.The APAvatar is now wrapped with ProfilePreviewHoverCard, enabling hover-based profile previews for suggested profiles. The
align='center'
prop is appropriate for this centered layout.apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx (1)
95-177
: LGTM! Component implements hover preview effectively.The ProfilePreviewHoverCard component is well-structured and implements the hover preview functionality correctly. Key strengths:
- Feature flag gating prevents unintended usage
- Conditional fetching optimizes performance
- Proper error handling for avatar load failures
- Click event propagation is properly managed
apps/admin-x-activitypub/src/views/Profile/Profile.tsx (3)
161-161
: LGTM: Correct account lookup.Using
actualHandle || 'me'
correctly handles both viewing other users and the current user.
178-181
: LGTM: Consistent handle propagation to tabs.The actualHandle is correctly passed to PostsTab, FollowingTab, and FollowersTab, while LikesTab appropriately receives no handle since likes are always for the current user.
185-186
: LGTM: New props correctly wired.activeTab and actualHandle are properly passed to ProfilePage for controlled tab navigation.
apps/admin-x-activitypub/src/views/Profile/components/ProfilePage.tsx (2)
253-258
: LGTM: Well-implemented controlled tabs with navigation.The controlled tabs correctly:
- Use a key to force re-render when switching profiles
- Navigate to the appropriate URL on tab change
- Use
replace: true
to prevent history pollution- Handle the 'posts' tab by navigating to the base path
261-287
: LGTM: Correct conditional rendering of likes tab.The likes tab is appropriately hidden when viewing other users' profiles, as likes are only available for the current user.
) : bio ? ( | ||
<div dangerouslySetInnerHTML={{__html: bio}} className='leading-tight dark:text-gray-300' /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Verify HTML sanitization is sufficient for XSS prevention.
The bio content is rendered using dangerouslySetInnerHTML
. While openLinksInNewTab
and stripHtml
are applied, these utilities should be verified to ensure complete XSS protection.
The content pipeline is:
stripHtml(displayData.bio, ['a'])
- strips all HTML except<a>
tagsopenLinksInNewTab()
- removes dangerous protocols and adds target="_blank"
Run the following verification to confirm the sanitization is comprehensive:
Consider using a dedicated HTML sanitization library like DOMPurify for enhanced security:
import DOMPurify from 'dompurify';
const bio = displayData?.bio
? DOMPurify.sanitize(openLinksInNewTab(stripHtml(displayData.bio, ['a'])), {
ALLOWED_TAGS: ['a'],
ALLOWED_ATTR: ['href', 'target', 'rel']
})
: undefined;
🏁 Script executed:
#!/bin/bash
# Verify the sanitization functions handle all edge cases
rg -A 10 "export (const|function) (stripHtml|openLinksInNewTab)" apps/admin-x-activitypub/src/utils/content-formatters.ts
Length of output: 981
🏁 Script executed:
#!/bin/bash
rg -n "export function stripHtml" -C30 apps/admin-x-activitypub/src/utils/content-formatters.ts
rg -n "export const openLinksInNewTab" -C30 apps/admin-x-activitypub/src/utils/content-formatters.ts
Length of output: 3714
Prevent XSS by using a robust HTML sanitizer
Current stripHtml
+ openLinksInNewTab
preserve all original <a>
attributes (e.g. onclick
, style
) and only sanitize href
. Inline event handlers and other unsafe attributes remain, allowing XSS. Replace the regex‐based pipeline with a proven library (e.g. DOMPurify) configured to allow only <a>
with href
, target
, rel
.
🧰 Tools
🪛 ast-grep (0.39.5)
[warning] 170-170: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html
(react-unsafe-html-injection)
🪛 Biome (2.1.2)
[error] 171-171: Avoid passing content using the dangerouslySetInnerHTML prop.
Setting content using code can expose users to cross-site scripting (XSS) attacks
(lint/security/noDangerouslySetInnerHtml)
🤖 Prompt for AI Agents
In apps/admin-x-activitypub/src/components/global/ProfilePreviewHoverCard.tsx
around lines 170-171, the current pipeline using stripHtml + openLinksInNewTab
leaves unsafe attributes (e.g. onclick, style) on tags and allows XSS; replace
that with a trusted sanitizer such as DOMPurify: import and run
DOMPurify.sanitize(bio, {ALLOWED_TAGS:
['a','b','i','strong','em','p','br','ul','ol','li'], ALLOWED_ATTR:
['href','target','rel'], FORCE_BODY: true, ADD_ATTR: []}) (or similar config)
and then transform all <a> to ensure target='_blank' and rel='noopener
noreferrer' (you can use DOMPurify hooks or a post-process step) before
injecting via dangerouslySetInnerHTML; ensure the sanitizer is applied wherever
bio is rendered and remove the regex-based openLinksInNewTab/stripHtml logic.
no issues