Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions .github/workflows/deploy-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ jobs:
STRIPE_PRO_MONTHLY_PRICE_ID: ${{ secrets.STRIPE_PRO_MONTHLY_PRICE_ID }}
STRIPE_PRO_YEARLY_PRICE_ID: ${{ secrets.STRIPE_PRO_YEARLY_PRICE_ID }}
SLACK_BILLING_WEBHOOK_URL: ${{ secrets.SLACK_BILLING_WEBHOOK_URL }}
SECRETS_ENCRYPTION_KEY: ${{ secrets.SECRETS_ENCRYPTION_KEY }}
run: |
vercel pull --yes --environment=preview --token=$VERCEL_TOKEN
vercel build --token=$VERCEL_TOKEN
Expand Down Expand Up @@ -294,7 +295,8 @@ jobs:
--env STRIPE_WEBHOOK_SECRET=$STRIPE_WEBHOOK_SECRET \
--env STRIPE_PRO_MONTHLY_PRICE_ID=$STRIPE_PRO_MONTHLY_PRICE_ID \
--env STRIPE_PRO_YEARLY_PRICE_ID=$STRIPE_PRO_YEARLY_PRICE_ID \
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL)
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL \
--env SECRETS_ENCRYPTION_KEY=$SECRETS_ENCRYPTION_KEY)
vercel alias $VERCEL_URL ${{ env.API_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN
echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT

Expand Down Expand Up @@ -379,6 +381,7 @@ jobs:
STRIPE_PRO_MONTHLY_PRICE_ID: ${{ secrets.STRIPE_PRO_MONTHLY_PRICE_ID }}
STRIPE_PRO_YEARLY_PRICE_ID: ${{ secrets.STRIPE_PRO_YEARLY_PRICE_ID }}
SLACK_BILLING_WEBHOOK_URL: ${{ secrets.SLACK_BILLING_WEBHOOK_URL }}
SECRETS_ENCRYPTION_KEY: ${{ secrets.SECRETS_ENCRYPTION_KEY }}
run: |
vercel pull --yes --environment=preview --token=$VERCEL_TOKEN
vercel build --token=$VERCEL_TOKEN
Expand All @@ -404,7 +407,8 @@ jobs:
--env STRIPE_WEBHOOK_SECRET=$STRIPE_WEBHOOK_SECRET \
--env STRIPE_PRO_MONTHLY_PRICE_ID=$STRIPE_PRO_MONTHLY_PRICE_ID \
--env STRIPE_PRO_YEARLY_PRICE_ID=$STRIPE_PRO_YEARLY_PRICE_ID \
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL)
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL \
--env SECRETS_ENCRYPTION_KEY=$SECRETS_ENCRYPTION_KEY)
vercel alias $VERCEL_URL ${{ env.WEB_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN
echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT

Expand Down Expand Up @@ -474,6 +478,7 @@ jobs:
STRIPE_PRO_MONTHLY_PRICE_ID: ${{ secrets.STRIPE_PRO_MONTHLY_PRICE_ID }}
STRIPE_PRO_YEARLY_PRICE_ID: ${{ secrets.STRIPE_PRO_YEARLY_PRICE_ID }}
SLACK_BILLING_WEBHOOK_URL: ${{ secrets.SLACK_BILLING_WEBHOOK_URL }}
SECRETS_ENCRYPTION_KEY: ${{ secrets.SECRETS_ENCRYPTION_KEY }}
run: |
vercel pull --yes --environment=preview --token=$VERCEL_TOKEN
vercel build --token=$VERCEL_TOKEN
Expand All @@ -496,7 +501,8 @@ jobs:
--env STRIPE_WEBHOOK_SECRET=$STRIPE_WEBHOOK_SECRET \
--env STRIPE_PRO_MONTHLY_PRICE_ID=$STRIPE_PRO_MONTHLY_PRICE_ID \
--env STRIPE_PRO_YEARLY_PRICE_ID=$STRIPE_PRO_YEARLY_PRICE_ID \
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL)
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL \
--env SECRETS_ENCRYPTION_KEY=$SECRETS_ENCRYPTION_KEY)
vercel alias $VERCEL_URL ${{ env.MARKETING_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN
echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT

Expand Down Expand Up @@ -581,6 +587,7 @@ jobs:
STRIPE_PRO_MONTHLY_PRICE_ID: ${{ secrets.STRIPE_PRO_MONTHLY_PRICE_ID }}
STRIPE_PRO_YEARLY_PRICE_ID: ${{ secrets.STRIPE_PRO_YEARLY_PRICE_ID }}
SLACK_BILLING_WEBHOOK_URL: ${{ secrets.SLACK_BILLING_WEBHOOK_URL }}
SECRETS_ENCRYPTION_KEY: ${{ secrets.SECRETS_ENCRYPTION_KEY }}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if the admin app references SECRETS_ENCRYPTION_KEY in its source
echo "=== Searching in admin app ==="
rg 'SECRETS_ENCRYPTION_KEY' --type=ts --type=js -g 'apps/admin/**' -l

echo ""
echo "=== Searching in packages ==="
rg 'SECRETS_ENCRYPTION_KEY' --type=ts --type=js -g 'packages/**' -l

echo ""
echo "=== Full search across codebase ==="
rg 'SECRETS_ENCRYPTION_KEY' --type=ts --type=js -l

Repository: superset-sh/superset

Length of output: 346


🏁 Script executed:

#!/bin/bash
# Check Admin app's dependencies and imports
echo "=== Admin app package.json ==="
cat apps/admin/package.json | grep -A 20 -B 5 '"dependencies"'

echo ""
echo "=== Admin app env.ts (if exists) ==="
if [ -f apps/admin/src/env.ts ]; then
  cat apps/admin/src/env.ts | head -50
else
  echo "No env.ts found in admin app"
fi

echo ""
echo "=== Admin app imports of `@superset-sh/trpc` ==="
rg '@superset-sh/trpc|packages/trpc' apps/admin/ --type=ts --type=js -l

echo ""
echo "=== Check deploy-preview.yml around line 590 ==="
sed -n '580,610p' .github/workflows/deploy-preview.yml

Repository: superset-sh/superset

Length of output: 4734


Remove SECRETS_ENCRYPTION_KEY from Admin app deployment environment.

The Admin app's env schema (apps/admin/src/env.ts) does not include SECRETS_ENCRYPTION_KEY, and no code in the Admin app references it. The key is only used by the API app's secrets management router. Passing unused secrets violates the least-privilege principle.

🤖 Prompt for AI Agents
In @.github/workflows/deploy-preview.yml at line 590, Remove the unused
SECRETS_ENCRYPTION_KEY environment variable from the Admin app deployment job in
the workflow: delete the line setting SECRETS_ENCRYPTION_KEY: ${{
secrets.SECRETS_ENCRYPTION_KEY }} from the Admin app's env block (the
deploy-preview/admin job), ensuring no code in apps/admin (e.g.,
apps/admin/src/env.ts) references it; verify by searching for
SECRETS_ENCRYPTION_KEY and confirm only the API app's secrets router uses it.

run: |
vercel pull --yes --environment=preview --token=$VERCEL_TOKEN
vercel build --token=$VERCEL_TOKEN
Expand All @@ -607,7 +614,8 @@ jobs:
--env STRIPE_WEBHOOK_SECRET=$STRIPE_WEBHOOK_SECRET \
--env STRIPE_PRO_MONTHLY_PRICE_ID=$STRIPE_PRO_MONTHLY_PRICE_ID \
--env STRIPE_PRO_YEARLY_PRICE_ID=$STRIPE_PRO_YEARLY_PRICE_ID \
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL)
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL \
--env SECRETS_ENCRYPTION_KEY=$SECRETS_ENCRYPTION_KEY)
vercel alias $VERCEL_URL ${{ env.ADMIN_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN
echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT

Expand Down Expand Up @@ -665,14 +673,16 @@ jobs:
NEXT_PUBLIC_SENTRY_DSN_DOCS: ${{ secrets.NEXT_PUBLIC_SENTRY_DSN_DOCS }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
SECRETS_ENCRYPTION_KEY: ${{ secrets.SECRETS_ENCRYPTION_KEY }}
run: |
vercel pull --yes --environment=preview --token=$VERCEL_TOKEN
vercel build --token=$VERCEL_TOKEN
VERCEL_URL=$(vercel deploy --prebuilt --token=$VERCEL_TOKEN \
--env NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY \
--env NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST \
--env NEXT_PUBLIC_SENTRY_DSN_DOCS=$NEXT_PUBLIC_SENTRY_DSN_DOCS \
--env NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT)
--env NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT \
--env SECRETS_ENCRYPTION_KEY=$SECRETS_ENCRYPTION_KEY)
Comment on lines +676 to +685
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

SECRETS_ENCRYPTION_KEY is unnecessarily propagated to Docs (and Marketing).

The Docs site is a static documentation deployment — it has no server-side secrets management logic. Passing SECRETS_ENCRYPTION_KEY here violates the principle of least privilege. The same applies to the Marketing deployment (Line 483/507).

Consider removing SECRETS_ENCRYPTION_KEY from deployments that don't use it (Docs, Marketing) to reduce the secret's exposure surface.

🤖 Prompt for AI Agents
In @.github/workflows/deploy-preview.yml around lines 678 - 687, Remove
SECRETS_ENCRYPTION_KEY from the vercel deploy invocations that build static
Docs/Marketing sites: locate the run block that sets VERCEL_URL via the vercel
deploy --prebuilt command (the step that constructs VERCEL_URL) and delete the
--env SECRETS_ENCRYPTION_KEY=$SECRETS_ENCRYPTION_KEY argument there (and the
equivalent argument in the Marketing deploy block). Leave SECRETS_ENCRYPTION_KEY
only on deployments that actually require it (do not remove the top-level
secrets definition), ensuring vercel deploy --prebuilt and VERCEL_URL remain
unchanged otherwise.

vercel alias $VERCEL_URL ${{ env.DOCS_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN
echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT

Expand Down
20 changes: 15 additions & 5 deletions .github/workflows/deploy-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ jobs:
STRIPE_PRO_MONTHLY_PRICE_ID: ${{ secrets.STRIPE_PRO_MONTHLY_PRICE_ID }}
STRIPE_PRO_YEARLY_PRICE_ID: ${{ secrets.STRIPE_PRO_YEARLY_PRICE_ID }}
SLACK_BILLING_WEBHOOK_URL: ${{ secrets.SLACK_BILLING_WEBHOOK_URL }}
SECRETS_ENCRYPTION_KEY: ${{ secrets.SECRETS_ENCRYPTION_KEY }}
run: |
vercel pull --yes --environment=production --token=$VERCEL_TOKEN
vercel build --prod --token=$VERCEL_TOKEN
Expand Down Expand Up @@ -154,7 +155,8 @@ jobs:
--env STRIPE_WEBHOOK_SECRET=$STRIPE_WEBHOOK_SECRET \
--env STRIPE_PRO_MONTHLY_PRICE_ID=$STRIPE_PRO_MONTHLY_PRICE_ID \
--env STRIPE_PRO_YEARLY_PRICE_ID=$STRIPE_PRO_YEARLY_PRICE_ID \
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL \
--env SECRETS_ENCRYPTION_KEY=$SECRETS_ENCRYPTION_KEY

deploy-web:
name: Deploy Web to Vercel
Expand Down Expand Up @@ -211,6 +213,7 @@ jobs:
STRIPE_PRO_MONTHLY_PRICE_ID: ${{ secrets.STRIPE_PRO_MONTHLY_PRICE_ID }}
STRIPE_PRO_YEARLY_PRICE_ID: ${{ secrets.STRIPE_PRO_YEARLY_PRICE_ID }}
SLACK_BILLING_WEBHOOK_URL: ${{ secrets.SLACK_BILLING_WEBHOOK_URL }}
SECRETS_ENCRYPTION_KEY: ${{ secrets.SECRETS_ENCRYPTION_KEY }}
run: |
vercel pull --yes --environment=production --token=$VERCEL_TOKEN
vercel build --prod --token=$VERCEL_TOKEN
Expand All @@ -236,7 +239,8 @@ jobs:
--env STRIPE_WEBHOOK_SECRET=$STRIPE_WEBHOOK_SECRET \
--env STRIPE_PRO_MONTHLY_PRICE_ID=$STRIPE_PRO_MONTHLY_PRICE_ID \
--env STRIPE_PRO_YEARLY_PRICE_ID=$STRIPE_PRO_YEARLY_PRICE_ID \
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL \
--env SECRETS_ENCRYPTION_KEY=$SECRETS_ENCRYPTION_KEY

deploy-marketing:
name: Deploy Marketing to Vercel
Expand Down Expand Up @@ -290,6 +294,7 @@ jobs:
STRIPE_PRO_MONTHLY_PRICE_ID: ${{ secrets.STRIPE_PRO_MONTHLY_PRICE_ID }}
STRIPE_PRO_YEARLY_PRICE_ID: ${{ secrets.STRIPE_PRO_YEARLY_PRICE_ID }}
SLACK_BILLING_WEBHOOK_URL: ${{ secrets.SLACK_BILLING_WEBHOOK_URL }}
SECRETS_ENCRYPTION_KEY: ${{ secrets.SECRETS_ENCRYPTION_KEY }}
run: |
vercel pull --yes --environment=production --token=$VERCEL_TOKEN
vercel build --prod --token=$VERCEL_TOKEN
Expand All @@ -312,7 +317,8 @@ jobs:
--env STRIPE_WEBHOOK_SECRET=$STRIPE_WEBHOOK_SECRET \
--env STRIPE_PRO_MONTHLY_PRICE_ID=$STRIPE_PRO_MONTHLY_PRICE_ID \
--env STRIPE_PRO_YEARLY_PRICE_ID=$STRIPE_PRO_YEARLY_PRICE_ID \
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL \
--env SECRETS_ENCRYPTION_KEY=$SECRETS_ENCRYPTION_KEY

deploy-admin:
name: Deploy Admin to Vercel
Expand Down Expand Up @@ -370,6 +376,7 @@ jobs:
STRIPE_PRO_MONTHLY_PRICE_ID: ${{ secrets.STRIPE_PRO_MONTHLY_PRICE_ID }}
STRIPE_PRO_YEARLY_PRICE_ID: ${{ secrets.STRIPE_PRO_YEARLY_PRICE_ID }}
SLACK_BILLING_WEBHOOK_URL: ${{ secrets.SLACK_BILLING_WEBHOOK_URL }}
SECRETS_ENCRYPTION_KEY: ${{ secrets.SECRETS_ENCRYPTION_KEY }}
run: |
vercel pull --yes --environment=production --token=$VERCEL_TOKEN
vercel build --prod --token=$VERCEL_TOKEN
Expand All @@ -396,7 +403,8 @@ jobs:
--env STRIPE_WEBHOOK_SECRET=$STRIPE_WEBHOOK_SECRET \
--env STRIPE_PRO_MONTHLY_PRICE_ID=$STRIPE_PRO_MONTHLY_PRICE_ID \
--env STRIPE_PRO_YEARLY_PRICE_ID=$STRIPE_PRO_YEARLY_PRICE_ID \
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL
--env SLACK_BILLING_WEBHOOK_URL=$SLACK_BILLING_WEBHOOK_URL \
--env SECRETS_ENCRYPTION_KEY=$SECRETS_ENCRYPTION_KEY

deploy-streams:
name: Deploy Streams to Fly.io
Expand Down Expand Up @@ -491,11 +499,13 @@ jobs:
NEXT_PUBLIC_SENTRY_DSN_DOCS: ${{ secrets.NEXT_PUBLIC_SENTRY_DSN_DOCS }}
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
NEXT_PUBLIC_SENTRY_ENVIRONMENT: ${{ vars.NEXT_PUBLIC_SENTRY_ENVIRONMENT }}
SECRETS_ENCRYPTION_KEY: ${{ secrets.SECRETS_ENCRYPTION_KEY }}
run: |
vercel pull --yes --environment=production --token=$VERCEL_TOKEN
vercel build --prod --token=$VERCEL_TOKEN
vercel deploy --prod --prebuilt --token=$VERCEL_TOKEN \
--env NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY \
--env NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST \
--env NEXT_PUBLIC_SENTRY_DSN_DOCS=$NEXT_PUBLIC_SENTRY_DSN_DOCS \
--env NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT
--env NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT \
--env SECRETS_ENCRYPTION_KEY=$SECRETS_ENCRYPTION_KEY
Comment on lines +502 to +511
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Same least-privilege concern: SECRETS_ENCRYPTION_KEY on Docs and Marketing in production.

Same issue as flagged in the preview workflow — Docs and Marketing don't need SECRETS_ENCRYPTION_KEY. Remove it from these deployments to minimize secret exposure.

🤖 Prompt for AI Agents
In @.github/workflows/deploy-production.yml around lines 504 - 513, The
SECRETS_ENCRYPTION_KEY is being passed into the production vercel deploy step
where Docs/Marketing don't need it; remove the --env
SECRETS_ENCRYPTION_KEY=$SECRETS_ENCRYPTION_KEY entry from the vercel deploy
invocation (the run block containing vercel pull/build/deploy) so the deploy
command no longer injects SECRETS_ENCRYPTION_KEY into the Docs/Marketing
production deployment.

8 changes: 4 additions & 4 deletions apps/api/src/app/api/electric/[...path]/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
invitations,
members,
organizations,
repositories,
projects,
subscriptions,
taskStatuses,
tasks,
Expand All @@ -18,7 +18,7 @@ import { QueryBuilder } from "drizzle-orm/pg-core";
export type AllowedTable =
| "tasks"
| "task_statuses"
| "repositories"
| "projects"
| "auth.members"
| "auth.organizations"
| "auth.users"
Expand Down Expand Up @@ -58,8 +58,8 @@ export async function buildWhereClause(
case "task_statuses":
return build(taskStatuses, taskStatuses.organizationId, organizationId);

case "repositories":
return build(repositories, repositories.organizationId, organizationId);
case "projects":
return build(projects, projects.organizationId, organizationId);

case "auth.members":
return build(members, members.organizationId, organizationId);
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const env = createEnv({
STRIPE_PRO_MONTHLY_PRICE_ID: z.string(),
STRIPE_PRO_YEARLY_PRICE_ID: z.string(),
SLACK_BILLING_WEBHOOK_URL: z.string().url(),
SECRETS_ENCRYPTION_KEY: z.string().min(1),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "crypto.ts" -o -name "crypto.js" | head -20

Repository: superset-sh/superset

Length of output: 97


🏁 Script executed:

rg "SECRETS_ENCRYPTION_KEY" -A 3 -B 3

Repository: superset-sh/superset

Length of output: 1498


🏁 Script executed:

rg "AES.*256.*GCM|createCipheriv|createDecipheriv" -A 2 -B 2 --type ts

Repository: superset-sh/superset

Length of output: 2732


🏁 Script executed:

python3 <<'EOF'
import base64

# 32 bytes as base64
test_bytes = b'x' * 32
encoded = base64.b64encode(test_bytes).decode('ascii')
print(f"32 bytes in base64: {len(encoded)} characters")
print(f"Example: {encoded}")
EOF

Repository: superset-sh/superset

Length of output: 151


SECRETS_ENCRYPTION_KEY validation is too permissive and suggested fix has incorrect length.

Since the key is base64-encoded and must decode to exactly 32 bytes, the minimum length should be 44 characters (32 bytes base64-encoded), not 32. A string like "abc" would currently pass min(1) validation but fail at runtime.

Additionally, the same issue exists in packages/trpc/src/env.ts.

Suggested fix
-		SECRETS_ENCRYPTION_KEY: z.string().min(1),
+		SECRETS_ENCRYPTION_KEY: z.string().min(44),

Apply the same fix to both apps/api/src/env.ts and packages/trpc/src/env.ts.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
SECRETS_ENCRYPTION_KEY: z.string().min(1),
SECRETS_ENCRYPTION_KEY: z.string().min(44),
🤖 Prompt for AI Agents
In `@apps/api/src/env.ts` at line 44, The SECRETS_ENCRYPTION_KEY zod validation is
too lax (z.string().min(1)) even though the key is base64 and must decode to
exactly 32 bytes; update the validation for SECRETS_ENCRYPTION_KEY to require
the correct base64 length by using z.string().min(44) (and consider adding a
max(44) or a custom refinement if you want to enforce exact length), and make
the identical change to the other env module that also defines
SECRETS_ENCRYPTION_KEY so both places validate the base64 32-byte key
consistently.

SENTRY_AUTH_TOKEN: z.string().optional(),
},
client: {
Expand Down
11 changes: 11 additions & 0 deletions apps/desktop/src/lib/trpc/routers/projects/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,17 @@ export const createProjectsRouter = (getWindow: () => BrowserWindow | null) => {
return { success: true, terminalWarning };
}),

linkToNeon: publicProcedure
.input(z.object({ id: z.string(), neonProjectId: z.string() }))
.mutation(({ input }) => {
localDb
.update(projects)
.set({ neonProjectId: input.neonProjectId })
.where(eq(projects.id, input.id))
.run();
return { success: true };
}),
Comment on lines +1004 to +1013
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing project existence check — silently succeeds for invalid IDs.

Every other mutation in this router that updates by id (e.g., update, close, refreshDefaultBranch) fetches the project first and throws NOT_FOUND / an error when it doesn't exist. This procedure skips that, so calling linkToNeon with a bogus id returns { success: true } without actually linking anything.

Also, z.string() on neonProjectId permits empty strings — consider adding .min(1).

Proposed fix
 		linkToNeon: publicProcedure
-			.input(z.object({ id: z.string(), neonProjectId: z.string() }))
+			.input(z.object({ id: z.string(), neonProjectId: z.string().min(1) }))
 			.mutation(({ input }) => {
+				const project = localDb
+					.select()
+					.from(projects)
+					.where(eq(projects.id, input.id))
+					.get();
+
+				if (!project) {
+					throw new TRPCError({
+						code: "NOT_FOUND",
+						message: `Project ${input.id} not found`,
+					});
+				}
+
 				localDb
 					.update(projects)
 					.set({ neonProjectId: input.neonProjectId })
 					.where(eq(projects.id, input.id))
 					.run();
 				return { success: true };
 			}),

As per coding guidelines, "Use appropriate TRPCError codes consistently: NOT_FOUND (resource doesn't exist)" and "Follow existing patterns in the codebase and match the codebase style rather than introducing novel patterns".

🤖 Prompt for AI Agents
In `@apps/desktop/src/lib/trpc/routers/projects/projects.ts` around lines 1004 -
1013, The linkToNeon mutation currently updates localDb without verifying the
project exists and allows empty neonProjectId; change the input schema to
neonProjectId: z.string().min(1) and, following the pattern used by
update/close/refreshDefaultBranch, first fetch the project by id (using the same
project lookup logic) and if not found throw a TRPCError with code 'NOT_FOUND';
only then perform the localDb.update(projects).set(...).where(...) and await the
.run() result before returning { success: true } so the procedure behaves
consistently with other id-based mutations.


getGitHubAvatar: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type {
SelectInvitation,
SelectMember,
SelectOrganization,
SelectRepository,
SelectProject,
SelectSubscription,
SelectTask,
SelectTaskStatus,
Expand Down Expand Up @@ -38,7 +38,7 @@ type ApiKeyDisplay = z.infer<typeof apiKeyDisplaySchema>;
interface OrgCollections {
tasks: Collection<SelectTask>;
taskStatuses: Collection<SelectTaskStatus>;
repositories: Collection<SelectRepository>;
projects: Collection<SelectProject>;
members: Collection<SelectMember>;
users: Collection<SelectUser>;
invitations: Collection<SelectInvitation>;
Expand Down Expand Up @@ -142,29 +142,19 @@ function createOrgCollections(organizationId: string): OrgCollections {
}),
);

const repositories = createCollection(
electricCollectionOptions<SelectRepository>({
id: `repositories-${organizationId}`,
const projects = createCollection(
electricCollectionOptions<SelectProject>({
id: `projects-${organizationId}`,
shapeOptions: {
url: electricUrl,
params: {
table: "repositories",
table: "projects",
organizationId,
},
headers,
columnMapper,
},
getKey: (item) => item.id,
onInsert: async ({ transaction }) => {
const item = transaction.mutations[0].modified;
const result = await apiClient.repository.create.mutate(item);
return { txid: result.txid };
},
onUpdate: async ({ transaction }) => {
const { modified } = transaction.mutations[0];
const result = await apiClient.repository.update.mutate(modified);
return { txid: result.txid };
},
}),
);

Expand Down Expand Up @@ -315,7 +305,7 @@ function createOrgCollections(organizationId: string): OrgCollections {
return {
tasks,
taskStatuses,
repositories,
projects,
members,
users,
invitations,
Expand Down
Loading
Loading