Skip to content
Merged
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
23 changes: 22 additions & 1 deletion apps/api/src/trust-portal/trust-portal.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -993,8 +993,11 @@
});
}

const requiresVercelTxt = trustRecord?.isVercelDomain === true;
const isVerified =
isCnameVerified && isTxtVerified && isVercelTxtVerified;
isCnameVerified &&
isTxtVerified &&
(!requiresVercelTxt || isVercelTxtVerified);

if (!isVerified) {
return {
Expand All @@ -1017,6 +1020,24 @@
},
});

// Trigger Vercel to re-verify the domain so it provisions SSL and starts serving.
// Without this, Vercel doesn't know DNS has been configured and the domain stays inactive
// (previously required CS to manually click "Refresh" in Vercel dashboard).
if (process.env.TRUST_PORTAL_PROJECT_ID && process.env.VERCEL_TEAM_ID) {
try {
await this.vercelApi.post(
`/v9/projects/${process.env.TRUST_PORTAL_PROJECT_ID}/domains/${domain}/verify`,
{},
{ params: { teamId: process.env.VERCEL_TEAM_ID } },
);
Comment on lines +1028 to +1032

Check failure

Code scanning / CodeQL

Server-side request forgery Critical

The
URL
of this request depends on a
user-provided value
.

Copilot Autofix

AI 1 day ago

General approach: ensure that any user-controlled value used in the URL of an outgoing request is either (1) strictly validated against a whitelist pattern and rejected if invalid, and/or (2) safely encoded so it cannot change the structure of the URL (e.g., cannot inject extra /, ?, #, etc.). For domains, the safest is to validate them against a DNS-hostname regex and then, when building a URL path segment, encode them with encodeURIComponent or use axios’ url/params in a way that treats them as data, not structure.

Best fix here: reuse the existing validateDomain logic for checkDnsRecords (already present and called at line 903) to guarantee that domain is a syntactically valid DNS hostname, and then also encode the domain when embedding it in the Vercel URL path. This means:

  • Do not trust that all internal callers always go through checkDnsRecords; defensive coding at the sink is cheap and robust.
  • Convert the Vercel call from:
    `/v9/projects/${process.env.TRUST_PORTAL_PROJECT_ID}/domains/${domain}/verify`
    to:
    const safeDomain = encodeURIComponent(domain);
    `/v9/projects/${process.env.TRUST_PORTAL_PROJECT_ID}/domains/${safeDomain}/verify`
  • Optionally, call this.validateDomain(domain) again right before building the URL. This ensures that even if checkDnsRecords is ever reused or refactored, the sink remains safe.

Concretely, within apps/api/src/trust-portal/trust-portal.service.ts, in the checkDnsRecords method near lines 1023–1038:

  1. Introduce a local const safeDomain = encodeURIComponent(domain); inside the if (process.env.TRUST_PORTAL_PROJECT_ID && process.env.VERCEL_TEAM_ID) block, before calling this.vercelApi.post.
  2. Use safeDomain instead of domain in the URL template string.
  3. Optionally, add this.validateDomain(domain); at the start of that if block as a belt-and-suspenders validation (cheap and uses existing code).

No new imports or external libraries are needed; encodeURIComponent is built-in.


Suggested changeset 1
apps/api/src/trust-portal/trust-portal.service.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/apps/api/src/trust-portal/trust-portal.service.ts b/apps/api/src/trust-portal/trust-portal.service.ts
--- a/apps/api/src/trust-portal/trust-portal.service.ts
+++ b/apps/api/src/trust-portal/trust-portal.service.ts
@@ -1024,9 +1024,12 @@
     // Without this, Vercel doesn't know DNS has been configured and the domain stays inactive
     // (previously required CS to manually click "Refresh" in Vercel dashboard).
     if (process.env.TRUST_PORTAL_PROJECT_ID && process.env.VERCEL_TEAM_ID) {
+      // Defensive: ensure the domain is valid and safely encoded before using it in a URL path segment.
+      this.validateDomain(domain);
+      const safeDomain = encodeURIComponent(domain);
       try {
         await this.vercelApi.post(
-          `/v9/projects/${process.env.TRUST_PORTAL_PROJECT_ID}/domains/${domain}/verify`,
+          `/v9/projects/${process.env.TRUST_PORTAL_PROJECT_ID}/domains/${safeDomain}/verify`,
           {},
           { params: { teamId: process.env.VERCEL_TEAM_ID } },
         );
EOF
@@ -1024,9 +1024,12 @@
// Without this, Vercel doesn't know DNS has been configured and the domain stays inactive
// (previously required CS to manually click "Refresh" in Vercel dashboard).
if (process.env.TRUST_PORTAL_PROJECT_ID && process.env.VERCEL_TEAM_ID) {
// Defensive: ensure the domain is valid and safely encoded before using it in a URL path segment.
this.validateDomain(domain);
const safeDomain = encodeURIComponent(domain);
try {
await this.vercelApi.post(
`/v9/projects/${process.env.TRUST_PORTAL_PROJECT_ID}/domains/${domain}/verify`,
`/v9/projects/${process.env.TRUST_PORTAL_PROJECT_ID}/domains/${safeDomain}/verify`,
{},
{ params: { teamId: process.env.VERCEL_TEAM_ID } },
);
Copilot is powered by AI and may make mistakes. Always verify output.
} catch (error) {
// Non-fatal — domain is verified on our side, Vercel will eventually pick it up
this.logger.warn(
`Failed to trigger Vercel domain verification for ${domain}: ${error}`,
);
}
}

return {
success: true,
isCnameVerified,
Expand Down
Loading