-
Notifications
You must be signed in to change notification settings - Fork 609
fix: Pentest remediation #3885
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
fix: Pentest remediation #3885
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,8 +7,14 @@ export const getInvitationList = t.procedure | |||||||||||||||||||||||||||||||||||||||||||||||||||
| .use(requireUser) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| .use(requireOrgAdmin) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| .input(z.string()) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| .query(async ({ input: orgId }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| .query(async ({ ctx, input: orgId }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (orgId !== ctx.workspace?.orgId) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new TRPCError({ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| code: "BAD_REQUEST", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| message: "Invalid organization ID", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
9
to
+17
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Input shape likely breaks requireOrgAdmin; also use FORBIDDEN (403)
- .input(z.string())
- .query(async ({ ctx, input: orgId }) => {
+ .input(z.object({ orgId: z.string() }))
+ .query(async ({ ctx, input: { orgId } }) => {
try {
- if (orgId !== ctx.workspace?.orgId) {
+ if (orgId !== ctx.workspace?.orgId) {
throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Invalid organization ID",
+ code: "FORBIDDEN",
+ message: "Forbidden",
});
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return await authProvider.getInvitationList(orgId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error("Error retrieving organization member list:", error); | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,8 +13,14 @@ export const inviteMember = t.procedure | |
| role: z.enum(["basic_member", "admin"]), | ||
| }), | ||
| ) | ||
| .mutation(async ({ input }) => { | ||
| .mutation(async ({ ctx, input }) => { | ||
| try { | ||
| if (input.orgId !== ctx.workspace?.orgId) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same here |
||
| throw new TRPCError({ | ||
| code: "BAD_REQUEST", | ||
| message: "Invalid organization ID", | ||
| }); | ||
| } | ||
| return await authProvider.inviteMember({ | ||
| email: input.email, | ||
| role: input.role, | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -12,8 +12,14 @@ export const removeMembership = t.procedure | |||||||||||||||||||||||||||||||||||||||||||
| orgId: z.string(), // needed for the requireOrgAdmin middleware | ||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||
| .mutation(async ({ input }) => { | ||||||||||||||||||||||||||||||||||||||||||||
| .mutation(async ({ ctx, input }) => { | ||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||
| if (input.orgId !== ctx.workspace?.orgId) { | ||||||||||||||||||||||||||||||||||||||||||||
| throw new TRPCError({ | ||||||||||||||||||||||||||||||||||||||||||||
| code: "BAD_REQUEST", | ||||||||||||||||||||||||||||||||||||||||||||
| message: "Invalid organization ID", | ||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| return await authProvider.removeMembership(input.membershipId); | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+15
to
23
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure membershipId is scoped to the same org (potential auth bypass) An admin of org A could supply - .mutation(async ({ ctx, input }) => {
+ .mutation(async ({ ctx, input }) => {
try {
- if (input.orgId !== ctx.workspace?.orgId) {
+ if (input.orgId !== ctx.workspace?.orgId) {
throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Invalid organization ID",
+ code: "FORBIDDEN",
+ message: "Forbidden",
});
}
- return await authProvider.removeMembership(input.membershipId);
+ // Option A: enforce scoping in provider call
+ return await authProvider.removeMembership({
+ membershipId: input.membershipId,
+ orgId: ctx.workspace!.orgId,
+ });If the provider doesn’t accept an object, pre-fetch the membership and verify its org matches 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||||||||||||||||||||||||
| throw new TRPCError({ | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,8 +12,14 @@ export const revokeInvitation = t.procedure | |
| orgId: z.string(), // needed for the requireOrgAdmin middleware | ||
| }), | ||
| ) | ||
| .mutation(async ({ input }) => { | ||
| .mutation(async ({ ctx, input }) => { | ||
| try { | ||
| if (input.orgId !== ctx.workspace?.orgId) { | ||
| throw new TRPCError({ | ||
| code: "BAD_REQUEST", | ||
| message: "Invalid organization ID", | ||
| }); | ||
| } | ||
| return await authProvider.revokeOrgInvitation(input.invitationId); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainEnforce org scoping at the provider boundary. Passing only invitationId relies on upstream checks; ensure the provider validates the invitation belongs to the same org to prevent cross-org revocations. Ideally pass orgId too or have the provider verify via membership/ownership. 🏁 Script executed: #!/bin/bash
# Verify provider method enforces org scoping
rg -nP -C3 --type=ts --type=tsx '\brevokeOrgInvitation\s*\('
rg -nP -C3 --type=ts --type=tsx 'function\s+revokeOrgInvitation\b|revokeOrgInvitation:\s*\('Length of output: 213 🏁 Script executed: #!/bin/bash
set -e
# Search for all occurrences of revokeOrgInvitation across the codebase (excluding node_modules)
rg -n --color never --glob '!node_modules/*' 'revokeOrgInvitation' .
# Search for authProvider definition to inspect its methods
rg -n --color never --glob '!node_modules/*' 'authProvider' .Length of output: 3501 🏁 Script executed: #!/bin/bash
set -e
# Print base-provider revokeOrgInvitation signature
sed -n '240,260p' apps/dashboard/lib/auth/base-provider.ts
# Print local provider implementation
sed -n '320,360p' apps/dashboard/lib/auth/local.ts
# Print workos provider implementation
sed -n '520,560p' apps/dashboard/lib/auth/workos.tsLength of output: 3024 Enforce org scoping in revokeOrgInvitation: update the |
||
| } catch (error) { | ||
| throw new TRPCError({ | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,12 @@ export const changeWorkspaceName = t.procedure | |||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||
| .mutation(async ({ ctx, input }) => { | ||||||||||||||||||||||||||
| if (input.workspaceId !== ctx.workspace.id) { | ||||||||||||||||||||||||||
| throw new TRPCError({ | ||||||||||||||||||||||||||
| code: "BAD_REQUEST", | ||||||||||||||||||||||||||
| message: "Invalid workspace ID", | ||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+20
to
+25
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Return FORBIDDEN (403) instead of BAD_REQUEST (400) to avoid ID enumeration This is an auth failure, not an input error. Use a generic message. - if (input.workspaceId !== ctx.workspace.id) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Invalid workspace ID",
- });
- }
+ if (input.workspaceId !== ctx.workspace.id) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Forbidden",
+ });
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||
| await db | ||||||||||||||||||||||||||
| .transaction(async (tx) => { | ||||||||||||||||||||||||||
| await tx | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
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.
this should've never been passed from the frontend and still shouldn't, why don't we use ctx.workspace.orgId and remove the input altogether?