Feat/api security updates - #34
Conversation
…ct rate limiting, and fix double OTP bug
… mass email queue
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds a Release feature (model, controllers, routes, frontend pages) with queued release emails; initializes email queue in app bootstrap and mounts releases route; splits Project API key into separate publishable and secret keys with origin checks and per-key regeneration; introduces project-scoped rate limiting and require-secret-key middleware; integrates these across routes and updates email queue and service. Changes
Sequence Diagram(s)sequenceDiagram
actor Admin
participant FE as Frontend App
participant API as Release API (/api/releases)
participant Ctrl as Release Controller
participant DB as MongoDB
participant Queue as BullMQ Queue
participant Worker as Email Worker
participant EmailSvc as Resend Email Service
Admin->>FE: submit release (version, title, content)
FE->>API: POST /api/releases (Bearer token)
API->>Ctrl: authorize & validate
Ctrl->>DB: save Release
Ctrl->>DB: query verified developers
loop per recipient
Ctrl->>Queue: add job (email, version, title, content)
end
Ctrl-->>FE: 201 Created (queuedCount)
Note over Queue,Worker: asynchronous processing (rate-limited)
Queue->>Worker: deliver job
Worker->>EmailSvc: sendReleaseEmail(email, details)
EmailSvc-->>Worker: success/failure
Worker-->>Queue: complete/fail
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the platform's security posture and introduces a robust system for managing and communicating platform updates. It provides developers with finer control over API key usage and access, protects projects from API abuse through rate limiting and CORS enforcement, and streamlines the process of announcing new features and bug fixes to the user base via an integrated changelog and throttled email system. Highlights
Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces significant security updates and a new release management system, including features like dual API keys, per-project CORS, rate limiting, and an email queue. However, the review identified several critical vulnerabilities: a CORS bypass, a stored XSS vulnerability in the new changelog feature, HTML injection in release emails, and a potential Denial of Service in the allowed domains configuration. Additionally, some hardcoded configuration values need to be moved to environment variables for improved security and maintainability.
| style={{ color: 'var(--color-text-muted)', lineHeight: '1.7', fontSize: '1.05rem' }} | ||
| dangerouslySetInnerHTML={{ __html: release.content.replace(/\n/g, '<br>') }} | ||
| /> |
There was a problem hiding this comment.
The Releases component uses dangerouslySetInnerHTML to render unsanitized release.content, creating a stored Cross-Site Scripting (XSS) vulnerability. An attacker with release creation privileges could inject malicious scripts. To remediate, sanitize the HTML content using a library like dompurify or a Markdown renderer that escapes HTML. Alternatively, render the content as plain text within a <pre> tag to prevent script execution while preserving formatting.
<pre style={{ color: 'var(--color-text-muted)', lineHeight: '1.7', fontSize: '1.05rem', whiteSpace: 'pre-wrap', fontFamily: 'inherit' }}>
{release.content}
</pre>| if (domain.startsWith('*.')) { | ||
| const baseDomain = domain.substring(2); | ||
| return originUrl.endsWith(baseDomain); | ||
| } |
There was a problem hiding this comment.
The wildcard matching logic for allowedDomains uses originUrl.endsWith(baseDomain). This is insecure because it doesn't ensure that the baseDomain is a proper suffix of the origin's hostname. For example, if *.example.com is allowed, an attacker can use https://attacker-example.com to bypass the check, as it also ends with example.com.
Remediation: Ensure the origin hostname either matches the base domain exactly or ends with a dot followed by the base domain.
| const Developer = require("../models/Developer"); | ||
| const { emailQueue } = require("../queues/emailQueue"); | ||
|
|
||
| const ADMIN_EMAIL = 'yashpouranik124@gmail.com'; |
There was a problem hiding this comment.
For security and maintainability, the admin email should not be hardcoded. This makes it difficult to change the admin user or run the application in different environments. It's recommended to move this to an environment variable.
| const ADMIN_EMAIL = 'yashpouranik124@gmail.com'; | |
| const ADMIN_EMAIL = process.env.ADMIN_EMAIL; |
| <div class="logo">urBackend</div> | ||
| <div class="badge">New Release ${version}</div> | ||
| <h1>${title}</h1> | ||
| <div class="content">${content}</div> |
There was a problem hiding this comment.
The sendReleaseEmail function directly embeds the content of a release into the HTML body of the email without any sanitization or escaping. This allows for the injection of malicious HTML or tracking pixels into emails sent to all verified developers.
Remediation: Sanitize the release content before embedding it into the email template.
| const { domains } = req.body; | ||
| if (!Array.isArray(domains)) { | ||
| return res.status(400).json({ error: "domains must be an array of strings." }); | ||
| } |
There was a problem hiding this comment.
The updateAllowedDomains function validates that domains is an array, but it does not verify that its elements are strings. If a non-string element (e.g., an object) is included in the array, it will cause the verifyApiKey middleware to throw an unhandled exception when calling .startsWith() on it, effectively disabling the API for that project (Denial of Service).
Remediation: Add a check to ensure all elements in the domains array are strings: if (!domains.every(d => typeof d === 'string')) { ... }.
| publishedBy: { | ||
| type: String, | ||
| default: 'yashpouranik124@gmail.com' | ||
| } |
There was a problem hiding this comment.
Hardcoding the default publishedBy email in the schema ties the data model to a specific user. A more flexible approach is to make this field required and set it dynamically in the controller based on the authenticated user who is creating the release. This ensures data integrity and correctly attributes the release to the publisher.
In release.controller.js, you would then set this field when creating the new release: const newRelease = new Release({ ..., publishedBy: req.user.email });
| publishedBy: { | |
| type: String, | |
| default: 'yashpouranik124@gmail.com' | |
| } | |
| publishedBy: { | |
| type: String, | |
| required: true | |
| } |
| content: '' | ||
| }); | ||
|
|
||
| const isAdmin = user?.email === 'yashpouranik124@gmail.com'; |
There was a problem hiding this comment.
The admin email is hardcoded on the frontend. While the primary authorization check is on the backend, this makes the UI logic rigid and hard to maintain. This value should be centralized in a configuration file (e.g., src/config.js) and imported where needed. This same issue is also present in frontend/src/pages/Releases.jsx.
| const isAdmin = user?.email === 'yashpouranik124@gmail.com'; | |
| const isAdmin = user?.email === 'yashpouranik124@gmail.com'; // TODO: Move to a config file |
There was a problem hiding this comment.
Actionable comments posted: 12
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
frontend/src/pages/CreateProject.jsx (1)
133-133:⚠️ Potential issue | 🟡 MinorText should use plural "keys" for consistency.
The button text still uses singular "key" but there are now two keys displayed (publishable and secret). This is inconsistent with the updated warning text at lines 97-99 that correctly uses "keys" (plural).
✏️ Suggested fix
- I have saved the key, Go to Dashboard + I have saved the keys, Go to Dashboard🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/CreateProject.jsx` at line 133, Update the button text in the CreateProject component that currently reads "I have saved the key, Go to Dashboard" to use plural "keys" for consistency with the warning copy; locate the JSX button/label in CreateProject.jsx (the element rendering that exact string) and change it to "I have saved the keys, Go to Dashboard" (or similar pluralized phrasing).
🧹 Nitpick comments (3)
frontend/src/pages/ProjectSettings.jsx (1)
926-938: Add lightweight client-side domain format validation before PATCH.Current add flow only checks duplicates; validating
*,*.domain.com, or fullhttp(s)URLs will reduce invalid allowlist updates.💡 Suggested refactor
const addDomain = () => { const domain = newDomain.trim(); if (!domain) return; + const isWildcard = domain === "*" || domain.startsWith("*."); + let isHttpUrl = false; + try { + const parsed = new URL(domain); + isHttpUrl = parsed.protocol === "http:" || parsed.protocol === "https:"; + } catch {} + + if (!isWildcard && !isHttpUrl) { + return toast.error("Use *, *.example.com, or a full URL like https://example.com"); + } + // basic validation if (domains.includes(domain)) { return toast.error("Domain already added"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/ProjectSettings.jsx` around lines 926 - 938, The addDomain handler currently only checks for duplicates but accepts invalid formats; update the addDomain function to perform lightweight client-side validation on newDomain (after trimming) before calling handleUpdate: allow a single "*" or a leading wildcard like "*.example.com" or a bare hostname/hostname with subdomains (no http:// or https://, no path, no ports), reject strings that start with "http://" or "https://" or contain "/" or ":" (except the "*" pattern) and call toast.error with a clear message for invalid formats; keep the existing duplicate check (domains.includes(domain)) and only build updated = [...domains, domain] and call handleUpdate(updated) when validation passes, then setNewDomain(""). Ensure the validation logic is implemented inside addDomain and references newDomain, domains, handleUpdate, setNewDomain.backend/models/Release.js (1)
18-21: Avoid hardcodedpublishedBydefault in the model.Persisting a fixed email here makes attribution inflexible. Prefer requiring
publishedByand setting it from authenticated user context during creation.Suggested change
publishedBy: { type: String, - default: 'yashpouranik124@gmail.com' + required: true, + trim: true }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/models/Release.js` around lines 18 - 21, The Release model currently hardcodes publishedBy default; remove the default and make the publishedBy schema field required (i.e., update the publishedBy field in the Release schema to type: String and required: true) and then ensure the release creation flow sets publishedBy from the authenticated user context (e.g., in your createRelease / release creation handler use req.user.email or equivalent to populate publishedBy before saving). This keeps the model flexible and ensures attribution comes from the authenticated user rather than a fixed value.backend/app.js (1)
89-89: Add request throttling/logging on the releases route for consistency.
/api/releasesis mounted without the request controls used on neighboring API groups. Add at least a limiter (and optionally logger) to reduce abuse and operational blind spots.Suggested change
-app.use('/api/releases', releaseRoute); +app.use('/api/releases', dashboardLimiter, logger, releaseRoute);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/app.js` at line 89, The /api/releases route is mounted without the request controls used elsewhere; add and apply the same rate-limiting (and optional request-logging) middleware when mounting releaseRoute. Create or import the existing limiter middleware (e.g., rateLimiter or express-rate-limit config) and optional requestLogger, then replace or change the mount to use them with releaseRoute (for example, app.use('/api/releases', rateLimiter, requestLogger, releaseRoute)) so the releases endpoints receive the same throttling/logging behavior as neighboring API groups; ensure you import the middleware and place the mount near the other API group mounts for consistency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@backend/controllers/project.controller.js`:
- Around line 703-710: The current code only checks that domains is an array but
can still contain non-string or empty entries; update the validation around the
domains variable to ensure every element is a non-empty string (e.g., typeof
entry === "string" && entry.trim().length > 0) before calling
Project.findOneAndUpdate, return res.status(400) with an explanatory error if
validation fails, and pass a sanitized array (trimmed values) into the { $set: {
allowedDomains: domains } } update so Project.findOneAndUpdate (using
req.params.projectId and req.user._id) only persists valid domain strings.
In `@backend/controllers/release.controller.js`:
- Line 5: Replace the hardcoded ADMIN_EMAIL constant and any direct equality
checks against it with a configurable authorization approach: remove the const
ADMIN_EMAIL and instead read an allowlist from an environment variable (e.g.,
process.env.ADMIN_EMAILS split by comma) and/or use role/claim-based checks on
the authenticated user (e.g., req.user.role === 'admin'); update the
authorization logic that currently compares emails (the occurrences that
reference ADMIN_EMAIL) to consult the allowlist and/or the user's role/claims,
and move the check into reusable middleware/helper (e.g., isAdmin(req)) so
rotation and testing are easier.
- Around line 21-33: The release `content` field is stored raw which allows
stored XSS; before constructing and saving the Release (where you reference
req.body.content and call new Release(...) / newRelease.save()), sanitize or
strip unsafe HTML on the server side (e.g., use a well‑maintained sanitizer like
sanitize-html or server DOMPurify) to allow only an approved subset of
tags/attributes or to remove scripts and event handlers, validate the sanitized
result is non-empty, and then save that sanitized content; alternatively ensure
the frontend always escapes/encodes content on render, but the immediate fix is
to sanitize req.body.content prior to new Release(...) and newRelease.save().
- Around line 40-47: The current emails.forEach loop calls
emailQueue.add('release-email', { email, version, title, content }) but doesn't
await the promises, so enqueue failures are missed; change the loop to collect
the promises (e.g., map over emails to call emailQueue.add(...)), await
Promise.all on that array inside the relevant controller function (the block
containing emails.forEach), and handle/rethrow or respond with an error if any
Promise rejects so the API only returns success when all jobs are enqueued.
In `@backend/middleware/verifyApiKey.js`:
- Around line 76-83: The wildcard origin check should compare hostnames, not the
full origin; replace use of originUrl = new URL(origin).origin with extracting
hostname via new URL(origin).hostname and then, in the allowedDomains.some
callback (the logic referencing allowedDomains and baseDomain), treat a
'*.example.com' entry as matching only when hostname === baseDomain or
hostname.endsWith('.' + baseDomain) to enforce the subdomain boundary; leave the
exact-match branch to compare hostname === domain for non-wildcard entries.
In `@backend/models/Project.js`:
- Around line 48-51: The Project schema in backend/models/Project.js is missing
the rateLimit field so per-project overrides read by
backend/middleware/projectRateLimiter.js (req.project.rateLimit) are never
persisted; add a rateLimit property to the Project model (e.g., rateLimit with
the appropriate type expected by projectRateLimiter—Number for a simple max or
an object/Schema.Types.Mixed if the middleware expects window/max pairs—and set
a sensible default/null) so saving a project stores the override and
req.project.rateLimit is populated when loaded.
In `@backend/queues/emailQueue.js`:
- Around line 18-22: Logs in the email queue are printing full recipient
addresses (PII); update the logging in the email processing code that calls
sendReleaseEmail (the console.log and console.error lines that reference the
email variable) to avoid raw emails by either logging a masked version (e.g.,
show only local-part initials and domain) or use the job ID/recipient hash
instead, and apply the same change to the other occurrence referenced around the
second log (line ~37) so no raw email addresses are written to logs.
- Around line 19-23: The worker currently awaits sendReleaseEmail(email, {
version, title, content }) but ignores its returned error object, so provider
failures can be treated as successes; update the handler in emailQueue.js to
inspect the result from sendReleaseEmail (e.g., returned value or { error }
payload), and if an error is present, log the error with context (email,
version) and rethrow or return a rejected promise so the job is marked failed;
ensure any existing try/catch around sendReleaseEmail uses the captured result
(not just thrown exceptions) to decide success vs failure.
In `@backend/utils/emailService.js`:
- Around line 52-57: The current email service logs failures and returns {data,
error} or {error} (the inner callback branch and the outer catch in
backend/utils/emailService.js) which lets callers treat failures as successes;
update both places to log the error and then throw the error (or rethrow a
wrapped Error with context) instead of returning an object—locate the
error-handling in the function that calls the resend client callback (the if
(error) console.error("[Resend Error]", error); return { data, error }; block)
and the enclosing catch block (console.error("[Email Service Error]", error);
return { error };) and replace the returns with throw error (or throw new
Error(`[EmailService] send failed: ${error.message || error}`)) so callers
receive a rejected exception.
In `@frontend/src/pages/ProjectDetails.jsx`:
- Around line 173-175: The two roll buttons share the boolean isRegenerating so
both spin together; change this to track which key is regenerating (e.g., use
state like regeneratingKey: null | 'publishable' | 'secret'), update
handleRegenerateKey(keyType) to set regeneratingKey to the keyType before async
work and clear it on finish/error, and update each button’s disabled and spinner
class logic to check regeneratingKey === 'publishable' or === 'secret' instead
of isRegenerating so only the button for the active key shows loading.
In `@frontend/src/pages/ProjectSettings.jsx`:
- Around line 1045-1063: The icon-only remove button lacks an accessible name;
update the JSX button that calls removeDomain(domain) (the element rendering <X
size={16} />) to include an aria-label (or aria-labelledby) such as
aria-label={`Remove domain ${domain}`} so screen readers get meaningful context;
you can also keep a title attribute for tooltip parity but ensure aria-label is
present and dynamic per domain.
In `@frontend/src/pages/Releases.jsx`:
- Around line 93-96: In Releases.jsx replace the div that uses
dangerouslySetInnerHTML with a plain-text rendering: remove
dangerouslySetInnerHTML and its .replace(/\n/g, '<br>') call and instead render
{release.content} as the div's child, keeping the existing style but add
whiteSpace: 'pre-wrap' (e.g., style={{ color: 'var(--color-text-muted)',
lineHeight: '1.7', fontSize: '1.05rem', whiteSpace: 'pre-wrap' }}). This
preserves newlines safely and eliminates the XSS risk from
dangerouslySetInnerHTML while retaining the visual formatting for the Releases
component.
---
Outside diff comments:
In `@frontend/src/pages/CreateProject.jsx`:
- Line 133: Update the button text in the CreateProject component that currently
reads "I have saved the key, Go to Dashboard" to use plural "keys" for
consistency with the warning copy; locate the JSX button/label in
CreateProject.jsx (the element rendering that exact string) and change it to "I
have saved the keys, Go to Dashboard" (or similar pluralized phrasing).
---
Nitpick comments:
In `@backend/app.js`:
- Line 89: The /api/releases route is mounted without the request controls used
elsewhere; add and apply the same rate-limiting (and optional request-logging)
middleware when mounting releaseRoute. Create or import the existing limiter
middleware (e.g., rateLimiter or express-rate-limit config) and optional
requestLogger, then replace or change the mount to use them with releaseRoute
(for example, app.use('/api/releases', rateLimiter, requestLogger,
releaseRoute)) so the releases endpoints receive the same throttling/logging
behavior as neighboring API groups; ensure you import the middleware and place
the mount near the other API group mounts for consistency.
In `@backend/models/Release.js`:
- Around line 18-21: The Release model currently hardcodes publishedBy default;
remove the default and make the publishedBy schema field required (i.e., update
the publishedBy field in the Release schema to type: String and required: true)
and then ensure the release creation flow sets publishedBy from the
authenticated user context (e.g., in your createRelease / release creation
handler use req.user.email or equivalent to populate publishedBy before saving).
This keeps the model flexible and ensures attribution comes from the
authenticated user rather than a fixed value.
In `@frontend/src/pages/ProjectSettings.jsx`:
- Around line 926-938: The addDomain handler currently only checks for
duplicates but accepts invalid formats; update the addDomain function to perform
lightweight client-side validation on newDomain (after trimming) before calling
handleUpdate: allow a single "*" or a leading wildcard like "*.example.com" or a
bare hostname/hostname with subdomains (no http:// or https://, no path, no
ports), reject strings that start with "http://" or "https://" or contain "/" or
":" (except the "*" pattern) and call toast.error with a clear message for
invalid formats; keep the existing duplicate check (domains.includes(domain))
and only build updated = [...domains, domain] and call handleUpdate(updated)
when validation passes, then setNewDomain(""). Ensure the validation logic is
implemented inside addDomain and references newDomain, domains, handleUpdate,
setNewDomain.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 93850855-87e2-4a61-ad2b-39e59c6fdfff
⛔ Files ignored due to path filters (1)
backend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (28)
backend/app.jsbackend/controllers/auth.controller.jsbackend/controllers/project.controller.jsbackend/controllers/release.controller.jsbackend/controllers/schema.controller.jsbackend/middleware/projectRateLimiter.jsbackend/middleware/requireSecretKey.jsbackend/middleware/verifyApiKey.jsbackend/models/Project.jsbackend/models/Release.jsbackend/package.jsonbackend/queues/emailQueue.jsbackend/routes/data.jsbackend/routes/projects.jsbackend/routes/releases.jsbackend/routes/schemas.jsbackend/routes/storage.jsbackend/utils/api.jsbackend/utils/emailService.jsfrontend/src/App.jsxfrontend/src/components/Layout/Sidebar.jsxfrontend/src/components/TryItPanel.jsxfrontend/src/pages/AdminCreateRelease.jsxfrontend/src/pages/CreateProject.jsxfrontend/src/pages/ProjectDetails.jsxfrontend/src/pages/ProjectSettings.jsxfrontend/src/pages/Releases.jsxfrontend/src/pages/Signup.jsx
| if (!Array.isArray(domains)) { | ||
| return res.status(400).json({ error: "domains must be an array of strings." }); | ||
| } | ||
|
|
||
| const project = await Project.findOneAndUpdate( | ||
| { _id: req.params.projectId, owner: req.user._id }, | ||
| { $set: { allowedDomains: domains } }, | ||
| { new: true } |
There was a problem hiding this comment.
Validate each domain entry, not only array shape.
At Line 703, the check only confirms domains is an array. Non-string or empty values can still be persisted and later break or weaken origin checks.
🔧 Proposed fix
module.exports.updateAllowedDomains = async (req, res) => {
try {
const { domains } = req.body;
- if (!Array.isArray(domains)) {
+ if (
+ !Array.isArray(domains) ||
+ domains.some((d) => typeof d !== "string" || !d.trim())
+ ) {
return res.status(400).json({ error: "domains must be an array of strings." });
}
+ const normalizedDomains = [...new Set(domains.map((d) => d.trim().toLowerCase()))];
const project = await Project.findOneAndUpdate(
{ _id: req.params.projectId, owner: req.user._id },
- { $set: { allowedDomains: domains } },
+ { $set: { allowedDomains: normalizedDomains } },
{ new: true }
);📝 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.
| if (!Array.isArray(domains)) { | |
| return res.status(400).json({ error: "domains must be an array of strings." }); | |
| } | |
| const project = await Project.findOneAndUpdate( | |
| { _id: req.params.projectId, owner: req.user._id }, | |
| { $set: { allowedDomains: domains } }, | |
| { new: true } | |
| if ( | |
| !Array.isArray(domains) || | |
| domains.some((d) => typeof d !== "string" || !d.trim()) | |
| ) { | |
| return res.status(400).json({ error: "domains must be an array of strings." }); | |
| } | |
| const normalizedDomains = [...new Set(domains.map((d) => d.trim().toLowerCase()))]; | |
| const project = await Project.findOneAndUpdate( | |
| { _id: req.params.projectId, owner: req.user._id }, | |
| { $set: { allowedDomains: normalizedDomains } }, | |
| { new: true } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/controllers/project.controller.js` around lines 703 - 710, The
current code only checks that domains is an array but can still contain
non-string or empty entries; update the validation around the domains variable
to ensure every element is a non-empty string (e.g., typeof entry === "string"
&& entry.trim().length > 0) before calling Project.findOneAndUpdate, return
res.status(400) with an explanatory error if validation fails, and pass a
sanitized array (trimmed values) into the { $set: { allowedDomains: domains } }
update so Project.findOneAndUpdate (using req.params.projectId and req.user._id)
only persists valid domain strings.
| const Developer = require("../models/Developer"); | ||
| const { emailQueue } = require("../queues/emailQueue"); | ||
|
|
||
| const ADMIN_EMAIL = 'yashpouranik124@gmail.com'; |
There was a problem hiding this comment.
Avoid hardcoding admin identity in controller logic.
Using a single inline email for authorization is brittle and hard to rotate. Move admin authorization to role/claim-based checks or env-configured allowlist.
Also applies to: 24-26
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/controllers/release.controller.js` at line 5, Replace the hardcoded
ADMIN_EMAIL constant and any direct equality checks against it with a
configurable authorization approach: remove the const ADMIN_EMAIL and instead
read an allowlist from an environment variable (e.g., process.env.ADMIN_EMAILS
split by comma) and/or use role/claim-based checks on the authenticated user
(e.g., req.user.role === 'admin'); update the authorization logic that currently
compares emails (the occurrences that reference ADMIN_EMAIL) to consult the
allowlist and/or the user's role/claims, and move the check into reusable
middleware/helper (e.g., isAdmin(req)) so rotation and testing are easier.
| const { version, title, content } = req.body; | ||
|
|
||
| // Verify Admin | ||
| if (req.user.email !== ADMIN_EMAIL) { | ||
| return res.status(403).json({ error: "Access denied. Admin only." }); | ||
| } | ||
|
|
||
| if (!version || !title || !content) { | ||
| return res.status(400).json({ error: "Missing version, title, or content" }); | ||
| } | ||
|
|
||
| const newRelease = new Release({ version, title, content }); | ||
| await newRelease.save(); |
There was a problem hiding this comment.
Stored XSS risk in release content pipeline.
content is accepted as-is and persisted without sanitization. Since it is later injected as HTML in the frontend, this enables stored script injection.
Suggested change
+const escapeHtml = (value = '') =>
+ value
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
exports.createRelease = async (req, res) => {
try {
- const { version, title, content } = req.body;
+ const { version, title, content } = req.body;
// Verify Admin
if (req.user.email !== ADMIN_EMAIL) {
return res.status(403).json({ error: "Access denied. Admin only." });
}
- if (!version || !title || !content) {
+ const safeVersion = String(version || '').trim();
+ const safeTitle = String(title || '').trim();
+ const safeContent = escapeHtml(String(content || '').trim());
+
+ if (!safeVersion || !safeTitle || !safeContent) {
return res.status(400).json({ error: "Missing version, title, or content" });
}
- const newRelease = new Release({ version, title, content });
+ const newRelease = new Release({
+ version: safeVersion,
+ title: safeTitle,
+ content: safeContent,
+ publishedBy: req.user.email
+ });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/controllers/release.controller.js` around lines 21 - 33, The release
`content` field is stored raw which allows stored XSS; before constructing and
saving the Release (where you reference req.body.content and call new
Release(...) / newRelease.save()), sanitize or strip unsafe HTML on the server
side (e.g., use a well‑maintained sanitizer like sanitize-html or server
DOMPurify) to allow only an approved subset of tags/attributes or to remove
scripts and event handlers, validate the sanitized result is non-empty, and then
save that sanitized content; alternatively ensure the frontend always
escapes/encodes content on render, but the immediate fix is to sanitize
req.body.content prior to new Release(...) and newRelease.save().
| const originUrl = new URL(origin).origin; | ||
| const isAllowed = allowedDomains.some(domain => { | ||
| if (domain.startsWith('*.')) { | ||
| const baseDomain = domain.substring(2); | ||
| return originUrl.endsWith(baseDomain); | ||
| } | ||
| return originUrl === domain; | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, locate and read the verifyApiKey.js file
fd -type f -name "verifyApiKey.js" | head -5Repository: yash-pouranik/urBackend
Length of output: 237
🏁 Script executed:
# Check lines 76-83 of the file
find . -name "verifyApiKey.js" -type f -exec sed -n '76,83p' {} +Repository: yash-pouranik/urBackend
Length of output: 492
🏁 Script executed:
node <<'EOF'
// Test URL.origin behavior to confirm the vulnerability
// Test case 1: Legitimate subdomain
const url1 = new URL('https://api.example.com/path');
console.log("url1.origin:", url1.origin);
console.log("url1.hostname:", url1.hostname);
// Test case 2: Sibling domain (evil)
const url2 = new URL('https://evil-example.com/path');
console.log("\nurl2.origin:", url2.origin);
console.log("url2.hostname:", url2.hostname);
// Test the vulnerable endsWith check
const baseDomain = 'example.com';
console.log("\nVulnerable check (originUrl.endsWith):");
console.log("url1.origin.endsWith('" + baseDomain + "'):", url1.origin.endsWith(baseDomain));
console.log("url2.origin.endsWith('" + baseDomain + "'):", url2.origin.endsWith(baseDomain));
// Test the proposed fix (hostname-based)
console.log("\nProposed fix (hostname-based):");
const isAllowedUrl1 = url1.hostname === baseDomain || url1.hostname.endsWith('.' + baseDomain);
const isAllowedUrl2 = url2.hostname === baseDomain || url2.hostname.endsWith('.' + baseDomain);
console.log("url1 allowed:", isAllowedUrl1);
console.log("url2 allowed:", isAllowedUrl2);
EOFRepository: yash-pouranik/urBackend
Length of output: 400
Wildcard origin matching bypasses subdomain boundary validation.
Line 80 uses originUrl.endsWith(baseDomain) which allows https://evil-example.com to pass when *.example.com is allowed. The .origin property includes the protocol, so suffix matching on the full origin string does not establish a proper subdomain boundary. For *.example.com, both https://api.example.com and https://evil-example.com satisfy .endsWith('example.com').
Extract the hostname separately and check for exact match or subdomain boundary with a leading dot:
Proposed fix
- const originUrl = new URL(origin).origin;
+ const parsedOrigin = new URL(origin);
+ const originUrl = parsedOrigin.origin;
+ const originHost = parsedOrigin.hostname.toLowerCase();
const isAllowed = allowedDomains.some(domain => {
if (domain.startsWith('*.')) {
- const baseDomain = domain.substring(2);
- return originUrl.endsWith(baseDomain);
+ const baseDomain = domain.substring(2).toLowerCase();
+ return originHost === baseDomain || originHost.endsWith(`.${baseDomain}`);
}
return originUrl === domain;
});📝 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.
| const originUrl = new URL(origin).origin; | |
| const isAllowed = allowedDomains.some(domain => { | |
| if (domain.startsWith('*.')) { | |
| const baseDomain = domain.substring(2); | |
| return originUrl.endsWith(baseDomain); | |
| } | |
| return originUrl === domain; | |
| }); | |
| const parsedOrigin = new URL(origin); | |
| const originUrl = parsedOrigin.origin; | |
| const originHost = parsedOrigin.hostname.toLowerCase(); | |
| const isAllowed = allowedDomains.some(domain => { | |
| if (domain.startsWith('*.')) { | |
| const baseDomain = domain.substring(2).toLowerCase(); | |
| return originHost === baseDomain || originHost.endsWith(`.${baseDomain}`); | |
| } | |
| return originUrl === domain; | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/middleware/verifyApiKey.js` around lines 76 - 83, The wildcard origin
check should compare hostnames, not the full origin; replace use of originUrl =
new URL(origin).origin with extracting hostname via new URL(origin).hostname and
then, in the allowedDomains.some callback (the logic referencing allowedDomains
and baseDomain), treat a '*.example.com' entry as matching only when hostname
=== baseDomain or hostname.endsWith('.' + baseDomain) to enforce the subdomain
boundary; leave the exact-match branch to compare hostname === domain for
non-wildcard entries.
| await sendReleaseEmail(email, { version, title, content }); | ||
| } catch (error) { | ||
| console.error(`[Queue] Failed to send email to ${email}:`, error); | ||
| throw error; | ||
| } |
There was a problem hiding this comment.
Provider send failures can be falsely marked as successful jobs.
sendReleaseEmail returns an error object on provider failure; this worker does not check it, so failed sends can be treated as completed jobs.
Suggested change
- await sendReleaseEmail(email, { version, title, content });
+ const result = await sendReleaseEmail(email, { version, title, content });
+ if (result?.error) {
+ throw new Error(`Release email send failed for ${email}`);
+ }📝 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.
| await sendReleaseEmail(email, { version, title, content }); | |
| } catch (error) { | |
| console.error(`[Queue] Failed to send email to ${email}:`, error); | |
| throw error; | |
| } | |
| const result = await sendReleaseEmail(email, { version, title, content }); | |
| if (result?.error) { | |
| throw new Error(`Release email send failed for ${email}`); | |
| } | |
| } catch (error) { | |
| console.error(`[Queue] Failed to send email to ${email}:`, error); | |
| throw error; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/queues/emailQueue.js` around lines 19 - 23, The worker currently
awaits sendReleaseEmail(email, { version, title, content }) but ignores its
returned error object, so provider failures can be treated as successes; update
the handler in emailQueue.js to inspect the result from sendReleaseEmail (e.g.,
returned value or { error } payload), and if an error is present, log the error
with context (email, version) and rethrow or return a rejected promise so the
job is marked failed; ensure any existing try/catch around sendReleaseEmail uses
the captured result (not just thrown exceptions) to decide success vs failure.
There was a problem hiding this comment.
♻️ Duplicate comments (1)
backend/routes/releases.js (1)
16-16:⚠️ Potential issue | 🟠 MajorAdd rate limiting to
POST /to protect write + email-queue side effects.Line 16 is authenticated but unthrottled. This leaves release creation and downstream email enqueuing vulnerable to burst abuse or accidental flooding.
🔧 Proposed patch
const getAllReleasesLimiter = RateLimit({ windowMs: 15 * 60 * 1000, max: 1000, }); + +const createReleaseLimiter = RateLimit({ + windowMs: 15 * 60 * 1000, + max: 10, +}); @@ -router.post('/', authorization, createRelease); +router.post('/', createReleaseLimiter, authorization, createRelease);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/routes/releases.js` at line 16, The POST / route is authenticated but unthrottled (router.post('/', authorization, createRelease)); add a rate-limiting middleware to protect the createRelease handler and downstream email-queue side effects by importing or creating an express rate limiter (e.g. express-rate-limit or existing shared limiter), configuring sensible window and max values (e.g. short window for per-IP/user write ops), and inserting that middleware into the route chain (e.g. router.post('/', authorization, rateLimiter, createRelease) or router.post('/', rateLimiter, authorization, createRelease) depending on whether you want to limit unauthenticated attempts) so bursts are throttled while legitimate users still can create releases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@backend/routes/releases.js`:
- Line 16: The POST / route is authenticated but unthrottled (router.post('/',
authorization, createRelease)); add a rate-limiting middleware to protect the
createRelease handler and downstream email-queue side effects by importing or
creating an express rate limiter (e.g. express-rate-limit or existing shared
limiter), configuring sensible window and max values (e.g. short window for
per-IP/user write ops), and inserting that middleware into the route chain (e.g.
router.post('/', authorization, rateLimiter, createRelease) or router.post('/',
rateLimiter, authorization, createRelease) depending on whether you want to
limit unauthenticated attempts) so bursts are throttled while legitimate users
still can create releases.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 90f4bf17-224d-48a1-abc3-7062c144477a
📒 Files selected for processing (1)
backend/routes/releases.js
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (2)
backend/controllers/release.controller.js (2)
21-37:⚠️ Potential issue | 🔴 CriticalSanitize
contentbefore saving to prevent stored XSS.At Line 21 and Line 35, user-controlled content is stored raw. If rendered as HTML later, this is a stored XSS path.
Suggested fix (server-side sanitization)
+const sanitizeHtml = require("sanitize-html"); @@ - const { version, title, content } = req.body; + const safeVersion = String(req.body.version || "").trim(); + const safeTitle = String(req.body.title || "").trim(); + const safeContent = sanitizeHtml(String(req.body.content || ""), { + allowedTags: ["p", "ul", "ol", "li", "strong", "em", "a", "code", "pre", "br"], + allowedAttributes: { a: ["href", "target", "rel"] } + }).trim(); @@ - if (!version || !title || !content) { + if (!safeVersion || !safeTitle || !safeContent) { return res.status(400).json({ error: "Missing version, title, or content" }); } @@ - version, - title, - content, + version: safeVersion, + title: safeTitle, + content: safeContent, publishedBy: req.user.email });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/controllers/release.controller.js` around lines 21 - 37, Sanitize the user-controlled content before saving to prevent stored XSS: validate and replace req.body.content with a server-side sanitized version (e.g., using a library like sanitize-html or DOMPurify for Node) before you construct newRelease; update the code around the extraction const { version, title, content } = req.body to sanitize the content variable and pass the sanitized value into new Release({ version, title, content: sanitizedContent, publishedBy: req.user.email }); also add the required import/dependency and unit tests to ensure dangerous HTML/scripts are stripped or escaped.
45-57:⚠️ Potential issue | 🟠 MajorAvoid fail-fast enqueue after persistence; handle partial queue results explicitly.
At Line 45,
Promise.allrejects on first failure, but some jobs may already be queued and the release is already saved. This creates partial-success ambiguity and retry duplication risk.Suggested resilient enqueue handling
- await Promise.all(emails.map(email => - emailQueue.add('release-email', { - email, - version, - title, - content - }) - )); + const enqueueResults = await Promise.allSettled( + emails.map((email) => + emailQueue.add("release-email", { + email, + version: newRelease.version, + title: newRelease.title, + content: newRelease.content, + }) + ) + ); + + const queued = enqueueResults.filter((r) => r.status === "fulfilled").length; + const failed = enqueueResults.length - queued;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/controllers/release.controller.js` around lines 45 - 57, The current use of Promise.all over emails.map with emailQueue.add can reject on the first enqueue error and obscure that the release was persisted and some jobs were queued; change to a resilient enqueue loop that collects per-email results (success/failure) by awaiting each emailQueue.add inside a try/catch (or using Promise.allSettled on the emails.map) so you can record failures without throwing, then respond with res.status(201).json including the total attempted, succeeded, and failed counts and a list or summary of failed email addresses; update code paths around Promise.all, emails.map, and emailQueue.add to return partial-results and avoid duplicative retries.
🧹 Nitpick comments (6)
backend/models/Release.js (1)
14-21: Add normalization and size guards for persisted release fields.
contentandpublishedByare required, but currently unbounded/untrimmed. This can allow oversized payloads and inconsistent author values in storage.Proposed schema hardening
const ReleaseSchema = new mongoose.Schema({ version: { type: String, required: true, - trim: true + trim: true, + maxlength: 64 }, title: { type: String, required: true, - trim: true + trim: true, + maxlength: 200 }, content: { type: String, - required: true + required: true, + trim: true, + maxlength: 20000 }, publishedBy: { type: String, - required: true + required: true, + trim: true, + lowercase: true } }, { timestamps: true });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/models/Release.js` around lines 14 - 21, The Release model's required fields 'content' and 'publishedBy' lack normalization and size limits; update the schema in Release.js to add trim and maxlength constraints (e.g., set a reasonable max for 'content' like 10000 chars and for 'publishedBy' like 100 or as your domain requires) and normalize 'publishedBy' (trim and optionally lowercase) to prevent oversized payloads and inconsistent author values; ensure validation messages remain descriptive so Mongoose rejects invalid documents.backend/controllers/project.controller.js (1)
700-729: Domain validation improved but could deduplicate entries.The validation now correctly checks that all entries are strings (line 703) and filters empty values (lines 707-709). This addresses the previous review concern. However, consider adding deduplication to prevent storing redundant entries:
Optional enhancement
const cleanedDomains = domains .map(d => d.trim()) - .filter(d => d.length > 0); + .filter(d => d.length > 0) + .filter((d, i, arr) => arr.indexOf(d) === i); // deduplicate🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/controllers/project.controller.js` around lines 700 - 729, The updateAllowedDomains handler currently trims and filters domains into cleanedDomains but doesn't remove duplicates; update the cleanedDomains creation inside updateAllowedDomains to deduplicate values (e.g., use a Set or Array.prototype.filter with indexOf) so only unique, trimmed, non-empty strings are stored, then continue to persist via Project.findOneAndUpdate and update caches via deleteProjectById/setProjectById and deleteProjectByApiKeyCache as before.backend/middleware/verifyApiKey.js (1)
68-68: Consider removingrefererfallback for origin validation.Using
req.headers.refereras a fallback introduces inconsistency sinceReferercan be stripped by browser policies or proxies, and has a different URL format thanOrigin. TheOriginheader is the canonical header for CORS validation. IfOriginis missing, the browser is likely not making a cross-origin request.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@backend/middleware/verifyApiKey.js` at line 68, The origin validation currently falls back to req.headers.referer which is inconsistent with CORS semantics; change the code in verifyApiKey middleware to use only req.headers.origin (i.e., const origin = req.headers.origin) and remove any logic that treats referer as an equivalent fallback, updating any subsequent checks or error messages that reference referer so they assume Origin may be undefined for same-origin requests.frontend/src/pages/ProjectSettings.jsx (1)
926-938: Consider adding domain format validation.The
addDomainfunction only checks for empty strings and duplicates. Invalid entries likenot a domainorhttp://could be saved and later cause issues in the backend's origin matching. Consider basic format validation:Suggested validation
const addDomain = () => { const domain = newDomain.trim(); if (!domain) return; + // Basic validation for wildcard or URL-like format + const isWildcard = domain === '*' || domain.startsWith('*.'); + const isValidUrl = /^https?:\/\/[\w.-]+(:\d+)?$/.test(domain); + if (!isWildcard && !isValidUrl) { + return toast.error("Enter a valid domain (e.g., https://example.com or *.example.com)"); + } + if (domains.includes(domain)) { return toast.error("Domain already added"); }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/ProjectSettings.jsx` around lines 926 - 938, The addDomain function currently only trims and checks duplicates; add a format check before pushing to domains by validating newDomain (e.g., no spaces or URL schemes, must be a valid hostname with optional port). In the addDomain flow (using newDomain, domains, handleUpdate, setNewDomain), run a simple validation (for example a regex that allows letters/numbers, dashes, dots and optional :port and requires at least one dot, or use URL parsing to reject strings with protocols or slashes) and if invalid call toast.error("Invalid domain format") and return; only then build updated and call handleUpdate and setNewDomain. Ensure the validation rejects entries like "not a domain" or "http://example.com" while allowing "example.com" and "sub.example.com:8080".frontend/src/pages/Releases.jsx (2)
18-30: Consider adding request cancellation on unmount.If the component unmounts before the fetch completes,
setReleasesandsetLoadingwill be called on an unmounted component. While React 18+ handles this gracefully, adding an AbortController is a cleaner pattern.♻️ Suggested improvement
useEffect(() => { + const controller = new AbortController(); const fetchReleases = async () => { try { - const res = await axios.get(`${API_URL}/api/releases`); + const res = await axios.get(`${API_URL}/api/releases`, { + signal: controller.signal + }); setReleases(res.data); } catch (err) { - console.error("Failed to fetch releases", err); + if (!axios.isCancel(err)) { + console.error("Failed to fetch releases", err); + } } finally { setLoading(false); } }; fetchReleases(); + return () => controller.abort(); }, []);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/Releases.jsx` around lines 18 - 30, The fetch in the useEffect (function fetchReleases) should be cancellable: create an AbortController, pass its signal into the axios.get call (or an axios CancelToken if your axios version requires), and in the cleanup return () => controller.abort(); also guard calls to setReleases and setLoading by checking for controller.signal.aborted (or catch the AbortError) so you don't call setReleases/setLoading after unmount; update references to useEffect, fetchReleases, setReleases, setLoading and the axios.get invocation accordingly.
123-128: Inconsistent use of hardcoded colors vs CSS variables.The styles mix CSS variables (e.g.,
var(--color-primary)) with hardcoded colors (#fff,#1e1e1e,rgba(255, 255, 255, ...)). This inconsistency may cause issues if theming is added later.Consider replacing hardcoded values with CSS variables for consistency:
#fff→var(--color-text)or similar#1e1e1e→var(--color-bg-tertiary)or similarrgba(255, 255, 255, ...)→ appropriate CSS variable with opacityAlso applies to: 154-156, 173-173
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/Releases.jsx` around lines 123 - 128, Replace hardcoded colors in the markdown styles with CSS variables to ensure consistent theming: update selectors like .markdown-content h1, .markdown-content h2, .markdown-content h3 and other occurrences at the noted ranges (e.g., the rules around lines 154-156 and 173) to use variables such as var(--color-text) instead of `#fff`, var(--color-bg-tertiary) instead of `#1e1e1e`, and use rgba() via a CSS variable with alpha (e.g., rgba(var(--color-text-rgb), 0.8)) or a dedicated translucent variable instead of hardcoded rgba(...) so all themeable colors come from CSS variables.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@backend/controllers/release.controller.js`:
- Around line 5-6: The module reads ADMIN_EMAIL into the constant ADMIN_EMAIL
but does not fail fast if it's undefined; add an explicit guard immediately
after the const ADMIN_EMAIL = process.env.ADMIN_EMAIL; statement that checks for
a falsy value and throws a clear Error (e.g., throw new Error('ADMIN_EMAIL
environment variable is required')) so the app fails to start deterministically
when ADMIN_EMAIL is not configured; ensure the check runs at module
initialization (not inside a route) so functions that rely on ADMIN_EMAIL (e.g.,
any auth logic in this controller) never run with a missing value.
In `@backend/utils/emailService.js`:
- Around line 63-70: The escapeHtml function can throw when passed
null/undefined or non-string values; make it null-safe by coercing its input to
a string up front (e.g., use String(unsafe) or a typeof check) before performing
the chained .replace calls so sendReleaseEmail and other callers won't crash on
malformed payloads; update the escapeHtml helper accordingly and ensure callers
still pass values but rely on the helper to defensively handle non-strings.
- Line 6: The current initialization of the resend client silently falls back to
a dummy key; update the logic around the Resend constructor (the resend
variable) to fail fast when no real key is provided: remove the
're_dummy_key_for_testing' fallback and instead read
process.env.RESEND_API_KEY_2 || process.env.RESEND_API_KEY, and if that value is
missing and NODE_ENV !== 'test' throw a descriptive Error (or exit) so missing
production config is detected at startup; allow the dummy/test key only when
NODE_ENV === 'test' (or when an explicit test flag is set).
In `@frontend/src/config.js`:
- Line 3: Replace the hardcoded ADMIN_EMAIL constant by reading it from an
environment-config mechanism so the frontend and backend use the same source of
truth; change the export for ADMIN_EMAIL in config.js to derive its value from
process.env (or your app's runtime config provider) with a safe fallback only
for local dev, and ensure the frontend build system/documentation instructs
setting the same ADMIN_EMAIL env var used by backend (reference symbol:
ADMIN_EMAIL in config.js and the backend check in release.controller.js) to
avoid divergence and remove PII from source.
---
Duplicate comments:
In `@backend/controllers/release.controller.js`:
- Around line 21-37: Sanitize the user-controlled content before saving to
prevent stored XSS: validate and replace req.body.content with a server-side
sanitized version (e.g., using a library like sanitize-html or DOMPurify for
Node) before you construct newRelease; update the code around the extraction
const { version, title, content } = req.body to sanitize the content variable
and pass the sanitized value into new Release({ version, title, content:
sanitizedContent, publishedBy: req.user.email }); also add the required
import/dependency and unit tests to ensure dangerous HTML/scripts are stripped
or escaped.
- Around line 45-57: The current use of Promise.all over emails.map with
emailQueue.add can reject on the first enqueue error and obscure that the
release was persisted and some jobs were queued; change to a resilient enqueue
loop that collects per-email results (success/failure) by awaiting each
emailQueue.add inside a try/catch (or using Promise.allSettled on the
emails.map) so you can record failures without throwing, then respond with
res.status(201).json including the total attempted, succeeded, and failed counts
and a list or summary of failed email addresses; update code paths around
Promise.all, emails.map, and emailQueue.add to return partial-results and avoid
duplicative retries.
---
Nitpick comments:
In `@backend/controllers/project.controller.js`:
- Around line 700-729: The updateAllowedDomains handler currently trims and
filters domains into cleanedDomains but doesn't remove duplicates; update the
cleanedDomains creation inside updateAllowedDomains to deduplicate values (e.g.,
use a Set or Array.prototype.filter with indexOf) so only unique, trimmed,
non-empty strings are stored, then continue to persist via
Project.findOneAndUpdate and update caches via deleteProjectById/setProjectById
and deleteProjectByApiKeyCache as before.
In `@backend/middleware/verifyApiKey.js`:
- Line 68: The origin validation currently falls back to req.headers.referer
which is inconsistent with CORS semantics; change the code in verifyApiKey
middleware to use only req.headers.origin (i.e., const origin =
req.headers.origin) and remove any logic that treats referer as an equivalent
fallback, updating any subsequent checks or error messages that reference
referer so they assume Origin may be undefined for same-origin requests.
In `@backend/models/Release.js`:
- Around line 14-21: The Release model's required fields 'content' and
'publishedBy' lack normalization and size limits; update the schema in
Release.js to add trim and maxlength constraints (e.g., set a reasonable max for
'content' like 10000 chars and for 'publishedBy' like 100 or as your domain
requires) and normalize 'publishedBy' (trim and optionally lowercase) to prevent
oversized payloads and inconsistent author values; ensure validation messages
remain descriptive so Mongoose rejects invalid documents.
In `@frontend/src/pages/ProjectSettings.jsx`:
- Around line 926-938: The addDomain function currently only trims and checks
duplicates; add a format check before pushing to domains by validating newDomain
(e.g., no spaces or URL schemes, must be a valid hostname with optional port).
In the addDomain flow (using newDomain, domains, handleUpdate, setNewDomain),
run a simple validation (for example a regex that allows letters/numbers,
dashes, dots and optional :port and requires at least one dot, or use URL
parsing to reject strings with protocols or slashes) and if invalid call
toast.error("Invalid domain format") and return; only then build updated and
call handleUpdate and setNewDomain. Ensure the validation rejects entries like
"not a domain" or "http://example.com" while allowing "example.com" and
"sub.example.com:8080".
In `@frontend/src/pages/Releases.jsx`:
- Around line 18-30: The fetch in the useEffect (function fetchReleases) should
be cancellable: create an AbortController, pass its signal into the axios.get
call (or an axios CancelToken if your axios version requires), and in the
cleanup return () => controller.abort(); also guard calls to setReleases and
setLoading by checking for controller.signal.aborted (or catch the AbortError)
so you don't call setReleases/setLoading after unmount; update references to
useEffect, fetchReleases, setReleases, setLoading and the axios.get invocation
accordingly.
- Around line 123-128: Replace hardcoded colors in the markdown styles with CSS
variables to ensure consistent theming: update selectors like .markdown-content
h1, .markdown-content h2, .markdown-content h3 and other occurrences at the
noted ranges (e.g., the rules around lines 154-156 and 173) to use variables
such as var(--color-text) instead of `#fff`, var(--color-bg-tertiary) instead of
`#1e1e1e`, and use rgba() via a CSS variable with alpha (e.g.,
rgba(var(--color-text-rgb), 0.8)) or a dedicated translucent variable instead of
hardcoded rgba(...) so all themeable colors come from CSS variables.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 42992233-0167-4e7d-b6a7-cd48402709bd
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (14)
backend/controllers/project.controller.jsbackend/controllers/release.controller.jsbackend/middleware/verifyApiKey.jsbackend/models/Release.jsbackend/package.jsonbackend/tests/jest.setup.jsbackend/utils/emailService.jsfrontend/package.jsonfrontend/src/components/Layout/Footer.jsxfrontend/src/config.jsfrontend/src/pages/AdminCreateRelease.jsxfrontend/src/pages/ProjectDetails.jsxfrontend/src/pages/ProjectSettings.jsxfrontend/src/pages/Releases.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
- backend/package.json
| const ADMIN_EMAIL = process.env.ADMIN_EMAIL; | ||
|
|
There was a problem hiding this comment.
Fail fast when ADMIN_EMAIL is not configured.
If ADMIN_EMAIL is missing, auth behavior becomes config-dependent and brittle. Add an explicit guard before Line 24 so misconfiguration is surfaced deterministically.
Suggested guard
const ADMIN_EMAIL = process.env.ADMIN_EMAIL;
// CREATE RELEASE (Admin Only)
exports.createRelease = async (req, res) => {
try {
+ if (!ADMIN_EMAIL) {
+ return res.status(500).json({ error: "Server misconfiguration: ADMIN_EMAIL is not set" });
+ }
const { version, title, content } = req.body;Also applies to: 24-26
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/controllers/release.controller.js` around lines 5 - 6, The module
reads ADMIN_EMAIL into the constant ADMIN_EMAIL but does not fail fast if it's
undefined; add an explicit guard immediately after the const ADMIN_EMAIL =
process.env.ADMIN_EMAIL; statement that checks for a falsy value and throws a
clear Error (e.g., throw new Error('ADMIN_EMAIL environment variable is
required')) so the app fails to start deterministically when ADMIN_EMAIL is not
configured; ensure the check runs at module initialization (not inside a route)
so functions that rely on ADMIN_EMAIL (e.g., any auth logic in this controller)
never run with a missing value.
|
|
||
| dotenv.config(); | ||
| const resend = new Resend(process.env.RESEND_API_KEY || 're_dummy_key_for_testing'); | ||
| const resend = new Resend(process.env.RESEND_API_KEY_2 || process.env.RESEND_API_KEY || 're_dummy_key_for_testing'); |
There was a problem hiding this comment.
Do not silently fall back to a dummy Resend key outside tests.
Line 6 masks missing production config and can make email failures show up only at send time. Prefer fail-fast initialization unless explicitly in test mode.
Suggested initialization hardening
-dotenv.config();
-const resend = new Resend(process.env.RESEND_API_KEY_2 || process.env.RESEND_API_KEY || 're_dummy_key_for_testing');
+dotenv.config();
+const resendApiKey = process.env.RESEND_API_KEY_2 || process.env.RESEND_API_KEY;
+if (!resendApiKey && process.env.NODE_ENV !== "test") {
+ throw new Error("Missing RESEND_API_KEY_2 / RESEND_API_KEY");
+}
+const resend = new Resend(resendApiKey || "re_dummy_key_for_testing");📝 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.
| const resend = new Resend(process.env.RESEND_API_KEY_2 || process.env.RESEND_API_KEY || 're_dummy_key_for_testing'); | |
| const resendApiKey = process.env.RESEND_API_KEY_2 || process.env.RESEND_API_KEY; | |
| if (!resendApiKey && process.env.NODE_ENV !== "test") { | |
| throw new Error("Missing RESEND_API_KEY_2 / RESEND_API_KEY"); | |
| } | |
| const resend = new Resend(resendApiKey || "re_dummy_key_for_testing"); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/utils/emailService.js` at line 6, The current initialization of the
resend client silently falls back to a dummy key; update the logic around the
Resend constructor (the resend variable) to fail fast when no real key is
provided: remove the 're_dummy_key_for_testing' fallback and instead read
process.env.RESEND_API_KEY_2 || process.env.RESEND_API_KEY, and if that value is
missing and NODE_ENV !== 'test' throw a descriptive Error (or exit) so missing
production config is detected at startup; allow the dummy/test key only when
NODE_ENV === 'test' (or when an explicit test flag is set).
| const escapeHtml = (unsafe) => { | ||
| return unsafe | ||
| .replace(/&/g, "&") | ||
| .replace(/</g, "<") | ||
| .replace(/>/g, ">") | ||
| .replace(/"/g, """) | ||
| .replace(/'/g, "'"); | ||
| }; |
There was a problem hiding this comment.
Make escapeHtml null-safe to avoid runtime crashes on malformed payloads.
At Line 64, calling .replace on non-string values throws. Coerce first to keep sendReleaseEmail defensive.
Suggested defensive helper
-const escapeHtml = (unsafe) => {
- return unsafe
+const escapeHtml = (unsafe = "") => {
+ return String(unsafe)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
};📝 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.
| const escapeHtml = (unsafe) => { | |
| return unsafe | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">") | |
| .replace(/"/g, """) | |
| .replace(/'/g, "'"); | |
| }; | |
| const escapeHtml = (unsafe = "") => { | |
| return String(unsafe) | |
| .replace(/&/g, "&") | |
| .replace(/</g, "<") | |
| .replace(/>/g, ">") | |
| .replace(/"/g, """) | |
| .replace(/'/g, "'"); | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/utils/emailService.js` around lines 63 - 70, The escapeHtml function
can throw when passed null/undefined or non-string values; make it null-safe by
coercing its input to a string up front (e.g., use String(unsafe) or a typeof
check) before performing the chained .replace calls so sendReleaseEmail and
other callers won't crash on malformed payloads; update the escapeHtml helper
accordingly and ensure callers still pass values but rely on the helper to
defensively handle non-strings.
| // frontend/src/config.js | ||
| export const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:1234'; No newline at end of file | ||
| export const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:1234'; | ||
| export const ADMIN_EMAIL = 'yashpouranik124@gmail.com'; No newline at end of file |
There was a problem hiding this comment.
Hardcoded admin email creates configuration drift risk with backend.
The frontend hardcodes ADMIN_EMAIL while the backend reads from process.env.ADMIN_EMAIL (see backend/controllers/release.controller.js line 5). If these values diverge, a user could pass the frontend guard but fail the backend check, or vice versa. Additionally, hardcoding PII in source code is a compliance concern.
Consider using an environment variable:
-export const ADMIN_EMAIL = 'yashpouranik124@gmail.com';
+export const ADMIN_EMAIL = import.meta.env.VITE_ADMIN_EMAIL;📝 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.
| export const ADMIN_EMAIL = 'yashpouranik124@gmail.com'; | |
| export const ADMIN_EMAIL = import.meta.env.VITE_ADMIN_EMAIL; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/config.js` at line 3, Replace the hardcoded ADMIN_EMAIL constant
by reading it from an environment-config mechanism so the frontend and backend
use the same source of truth; change the export for ADMIN_EMAIL in config.js to
derive its value from process.env (or your app's runtime config provider) with a
safe fallback only for local dev, and ensure the frontend build
system/documentation instructs setting the same ADMIN_EMAIL env var used by
backend (reference symbol: ADMIN_EMAIL in config.js and the backend check in
release.controller.js) to avoid divergence and remove PII from source.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@backend/app.js`:
- Line 92: The releases routes are mounted without authentication
(app.use('/api/releases', releaseRoute)), so createRelease in
release.controller.js assumes req.user exists and throws; update the routes in
releases.js to apply the imported auth/authorization middleware to the POST '/'
endpoint (or to the router globally) so that req.user is populated before
createRelease runs; ensure the middleware symbol used (e.g., authMiddleware or
authorization) is the same one imported in releases.js and that the POST route
points to the createRelease controller.
- Around line 19-21: The app currently requires('./queues/emailQueue') at
startup which creates an IORedis connection synchronously and can throw if Redis
is down; change the initialization so the Redis client in emailQueue.js is
created with proper error handling or lazily initialized: add a top-level
.on('error', handler) and .on('connect'/'ready') handlers to the IORedis client
inside emailQueue.js, and wrap the initial connection attempt in a try/catch or
export an async init function (e.g., initEmailQueue) that backend/app.js can
call after the Express server binds, falling back to degraded behavior if the
connection fails; ensure any unhandled errors are caught and logged via your
logger instead of letting the require crash the process.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f109f628-e205-4c5f-84ae-5438438688db
📒 Files selected for processing (2)
backend/app.jsbackend/routes/releases.js
🚧 Files skipped from review as they are similar to previous changes (1)
- backend/routes/releases.js
| // Initialize Queue Workers | ||
| require('./queues/emailQueue'); | ||
|
|
There was a problem hiding this comment.
Missing error handling for Redis connection at startup.
The synchronous require('./queues/emailQueue') initializes an IORedis connection immediately. If Redis is unavailable when the server starts, the connection will throw synchronously and crash the Express process before it can bind to a port.
Consider adding connection error handlers or lazy initialization to allow the app to start gracefully and degrade when Redis is down.
🛡️ Suggested approach: wrap initialization with error handling
// Initialize Queue Workers
-require('./queues/emailQueue');
+try {
+ require('./queues/emailQueue');
+} catch (err) {
+ console.error('❌ Failed to initialize email queue:', err.message);
+ // Optionally: set a flag to disable email features
+}Alternatively, add .on('error') handlers in emailQueue.js to the IORedis connection to prevent unhandled exceptions.
📝 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.
| // Initialize Queue Workers | |
| require('./queues/emailQueue'); | |
| // Initialize Queue Workers | |
| try { | |
| require('./queues/emailQueue'); | |
| } catch (err) { | |
| console.error('❌ Failed to initialize email queue:', err.message); | |
| // Optionally: set a flag to disable email features | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/app.js` around lines 19 - 21, The app currently
requires('./queues/emailQueue') at startup which creates an IORedis connection
synchronously and can throw if Redis is down; change the initialization so the
Redis client in emailQueue.js is created with proper error handling or lazily
initialized: add a top-level .on('error', handler) and .on('connect'/'ready')
handlers to the IORedis client inside emailQueue.js, and wrap the initial
connection attempt in a try/catch or export an async init function (e.g.,
initEmailQueue) that backend/app.js can call after the Express server binds,
falling back to degraded behavior if the connection fails; ensure any unhandled
errors are caught and logged via your logger instead of letting the require
crash the process.
| app.use('/api/data', limiter, cors(adminCorsOptions), logger, dataRoute); | ||
| app.use('/api/schemas', limiter, cors(adminCorsOptions), logger, schemaRoute); | ||
| app.use('/api/storage', limiter, cors(adminCorsOptions), logger, storageRoute); | ||
| app.use('/api/releases', releaseRoute); |
There was a problem hiding this comment.
Critical: Missing authentication middleware on release routes causes runtime error and security bypass.
The /api/releases route is mounted without authentication middleware. According to backend/routes/releases.js, the authMiddleware is imported but never applied to the POST / endpoint. The createRelease controller (line 24 in release.controller.js) assumes req.user is populated:
if (req.user.email !== ADMIN_EMAIL)Without auth middleware, req.user is undefined, causing a TypeError: Cannot read property 'email' of undefined instead of a proper 401 response. Additionally, unauthenticated requests can reach the controller logic.
🔒 Proposed fix: Apply auth middleware in the route file
In backend/routes/releases.js, apply the imported authorization middleware to the POST route:
// CREATE RELEASE (Admin Only)
-router.post('/', createReleaseLimiter, createRelease);
+router.post('/', createReleaseLimiter, authorization, createRelease);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@backend/app.js` at line 92, The releases routes are mounted without
authentication (app.use('/api/releases', releaseRoute)), so createRelease in
release.controller.js assumes req.user exists and throws; update the routes in
releases.js to apply the imported auth/authorization middleware to the POST '/'
endpoint (or to the router globally) so that req.user is populated before
createRelease runs; ensure the middleware symbol used (e.g., authMiddleware or
authorization) is the same one imported in releases.js and that the POST route
points to the createRelease controller.
There was a problem hiding this comment.
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 (3)
frontend/src/pages/LandingPage/index.jsx (3)
385-386:⚠️ Potential issue | 🟡 MinorLikely typo: "supabase buckets or Supabase Storage" is redundant.
This appears to be a copy error. Based on context (BYO Storage card), this should probably read "S3 buckets or Supabase Storage".
Proposed fix
- Link your supabase buckets or Supabase Storage. We handle the file upload tokens, permissions, and CDN delivery automatically. + Link your S3 buckets or Supabase Storage. We handle the file upload tokens, permissions, and CDN delivery automatically.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/LandingPage/index.jsx` around lines 385 - 386, In the LandingPage component update the paragraph text that currently reads "Link your supabase buckets or Supabase Storage..." to use the intended storage providers string (e.g., "Link your S3 buckets or Supabase Storage..."); locate the JSX paragraph in frontend/src/pages/LandingPage/index.jsx (the <p> element with inline styles around line 385) and replace the redundant "supabase buckets" phrasing with "S3 buckets" so the copy correctly reflects BYO Storage options.
122-122:⚠️ Potential issue | 🟡 Minor
window.innerWidthin render won't respond to viewport changes.The inline style using
window.innerWidthis evaluated only once at render time. Resizing the browser window won't toggle visibility of these elements.Consider using CSS media queries (already defined in style.css for
.nav-linksand.mobile-menu-btn) or a resize listener with state.Proposed fix using CSS classes
-<div className="nav-links" style={{ display: window.innerWidth > 768 ? 'flex' : 'none', gap: '32px', alignItems: 'center', fontSize: '0.95rem', color: '#888', fontWeight: 500 }}> +<div className="nav-links" style={{ gap: '32px', alignItems: 'center', fontSize: '0.95rem', color: '#888', fontWeight: 500 }}>The existing CSS rule at line 539-541 already handles hiding
.nav-linkson mobile:.nav-links { display: none; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/LandingPage/index.jsx` at line 122, The inline style in the LandingPage JSX uses window.innerWidth to toggle display for the element with className "nav-links", which won't update on viewport resize; remove the inline style and rely on the existing CSS media queries (styles for .nav-links and .mobile-menu-btn) or implement a responsive state with a resize listener in the LandingPage component: either delete the style prop on the element that contains className "nav-links" so CSS controls visibility, or add a useState (e.g., isMobile) and window resize handler in the LandingPage component to update that state and conditionally render/assign a CSS class instead of using window.innerWidth directly.
136-140:⚠️ Potential issue | 🟡 MinorSame
window.innerWidthissue at lines 136 and 140.These also evaluate once and won't respond to window resize. Use CSS media queries for responsive visibility instead.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@frontend/src/pages/LandingPage/index.jsx` around lines 136 - 140, The JSX is using window.innerWidth inline to decide visibility for the Link elements and the mobile menu button (the Link to="/login", Link to="/signup" and the button with className "mobile-menu-btn" and onClick calling setIsMobileMenuOpen), which only evaluates once; remove these inline window.innerWidth checks and instead control visibility via CSS media queries or responsive classes (e.g., add distinct classNames like "desktop-only" and "mobile-only" to the Login/Start Free Links and the mobile-menu-btn) and update the stylesheet to show/hide .desktop-only and .mobile-only at the desired breakpoints so the UI responds to window resizes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@frontend/src/pages/LandingPage/index.jsx`:
- Line 468: The Link element with className "btn-hero-secondary" currently has
text "Back to Console" but navigates to "/login"; update this to be consistent
by either changing the to prop on that Link to "/dashboard" if it should return
authenticated users to the console (update any conditional routing if needed),
or change the visible text to "Sign In" if it should send unauthenticated users
to the login page; locate the Link in LandingPage (the element with className
"btn-hero-secondary" and inline padding style) and apply the appropriate change.
In `@frontend/src/pages/LandingPage/style.css`:
- Line 5: Remove the unnecessary quotes around the Inter font-name in the
font-family declaration in LandingPage's stylesheet: locate the font-family line
(font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
sans-serif;) and change 'Inter' to Inter (no quotes) so it complies with the
stylelint font-family-name-quotes rule while keeping the remaining fallbacks
intact.
---
Outside diff comments:
In `@frontend/src/pages/LandingPage/index.jsx`:
- Around line 385-386: In the LandingPage component update the paragraph text
that currently reads "Link your supabase buckets or Supabase Storage..." to use
the intended storage providers string (e.g., "Link your S3 buckets or Supabase
Storage..."); locate the JSX paragraph in
frontend/src/pages/LandingPage/index.jsx (the <p> element with inline styles
around line 385) and replace the redundant "supabase buckets" phrasing with "S3
buckets" so the copy correctly reflects BYO Storage options.
- Line 122: The inline style in the LandingPage JSX uses window.innerWidth to
toggle display for the element with className "nav-links", which won't update on
viewport resize; remove the inline style and rely on the existing CSS media
queries (styles for .nav-links and .mobile-menu-btn) or implement a responsive
state with a resize listener in the LandingPage component: either delete the
style prop on the element that contains className "nav-links" so CSS controls
visibility, or add a useState (e.g., isMobile) and window resize handler in the
LandingPage component to update that state and conditionally render/assign a CSS
class instead of using window.innerWidth directly.
- Around line 136-140: The JSX is using window.innerWidth inline to decide
visibility for the Link elements and the mobile menu button (the Link
to="/login", Link to="/signup" and the button with className "mobile-menu-btn"
and onClick calling setIsMobileMenuOpen), which only evaluates once; remove
these inline window.innerWidth checks and instead control visibility via CSS
media queries or responsive classes (e.g., add distinct classNames like
"desktop-only" and "mobile-only" to the Login/Start Free Links and the
mobile-menu-btn) and update the stylesheet to show/hide .desktop-only and
.mobile-only at the desired breakpoints so the UI responds to window resizes.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 632d5050-f4c1-4081-afdf-35cc8a15be8d
⛔ Files ignored due to path filters (1)
frontend/package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (4)
frontend/lint_results.txtfrontend/package.jsonfrontend/src/pages/LandingPage/index.jsxfrontend/src/pages/LandingPage/style.css
✅ Files skipped from review due to trivial changes (1)
- frontend/lint_results.txt
🚧 Files skipped from review as they are similar to previous changes (1)
- frontend/package.json
| <p style={{ fontSize: '1.25rem', color: '#a1a1aa', marginBottom: '3rem' }}>Join hundreds of developers building the future without the backend headaches.</p> | ||
| <div style={{ display: 'flex', gap: '1rem', justifyContent: 'center', flexWrap: 'wrap' }}> | ||
| <Link to="/signup" className="btn-hero-primary" style={{ padding: '1rem 3rem' }}>Create Your Project</Link> | ||
| <Link to="/login" className="btn-hero-secondary" style={{ padding: '1rem 3rem' }}>Back to Console</Link> |
There was a problem hiding this comment.
"Back to Console" links to /login — text/destination mismatch.
The button text "Back to Console" suggests returning to the dashboard, but it links to the login page. Consider either:
- Change link to
/dashboard(for authenticated users), or - Change text to "Sign In" (for unauthenticated users)
Proposed fix (if targeting authenticated users)
- <Link to="/login" className="btn-hero-secondary" style={{ padding: '1rem 3rem' }}>Back to Console</Link>
+ <Link to="/dashboard" className="btn-hero-secondary" style={{ padding: '1rem 3rem' }}>Back to Console</Link>📝 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.
| <Link to="/login" className="btn-hero-secondary" style={{ padding: '1rem 3rem' }}>Back to Console</Link> | |
| <Link to="/dashboard" className="btn-hero-secondary" style={{ padding: '1rem 3rem' }}>Back to Console</Link> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/pages/LandingPage/index.jsx` at line 468, The Link element with
className "btn-hero-secondary" currently has text "Back to Console" but
navigates to "/login"; update this to be consistent by either changing the to
prop on that Link to "/dashboard" if it should return authenticated users to the
console (update any conditional routing if needed), or change the visible text
to "Sign In" if it should send unauthenticated users to the login page; locate
the Link in LandingPage (the element with className "btn-hero-secondary" and
inline padding style) and apply the appropriate change.
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; | ||
| background-color: #030303; | ||
| color: #fcfcfc; | ||
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; |
There was a problem hiding this comment.
Remove quotes around Inter font family name.
Per the stylelint font-family-name-quotes rule, custom font names that are valid CSS identifiers should not be quoted.
Proposed fix
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;📝 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.
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; | |
| font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; |
🧰 Tools
🪛 Stylelint (17.3.0)
[error] 5-5: Unexpected quotes around "Inter" (font-family-name-quotes)
(font-family-name-quotes)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@frontend/src/pages/LandingPage/style.css` at line 5, Remove the unnecessary
quotes around the Inter font-name in the font-family declaration in
LandingPage's stylesheet: locate the font-family line (font-family: 'Inter',
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;) and change
'Inter' to Inter (no quotes) so it complies with the stylelint
font-family-name-quotes rule while keeping the remaining fallbacks intact.
🚀 feature: Security Hardening & Release Management System
Overview
This PR introduces a comprehensive security overhaul for the platform, including granular API key management, per-project CORS/Rate Limiting, and a robust background-processing system for platform releases and mass email notifications.
🔒 Security Enhancements
1. Granular API Keys (RBAC)
Publishable(prefixedpk_live_) andSecret(prefixedsk_live_) keys.requireSecretKeymiddleware to physically block data-destructive actions when a Publishable key is used.2. Per-Project Allowed Domains (CORS)
https://myapp.com) in their project settings.Originchecks for all requests using Publishable keys, throwing a403 Forbiddenfor unauthorized domains.3. Per-Project Rate Limiting
express-rate-limitthat tracks limits perProjectIDrather than User IP.📢 Release & Email System
1. Public Changelog
/releasesdisplays all platform updates in a modern, chronological timeline.yashpouranik124@gmail.com.2. Throttled Mass Email Queue (BullMQ)
apps.bitbros.indomain andRESEND_API_KEY_2for improved deliverability.3. Premium Email UI
🐛 Bug Fixes
🛠️ Technical Refactors
backend/queues/directory for better maintainability.sendOtpandsendReleaseEmaillogic withinemailService.js.Summary by CodeRabbit
New Features
Improvements
Chores