Skip to content

Commit 5ea3bcd

Browse files
authored
Fix CodeQL string handling alerts (#23)
1 parent a4b42eb commit 5ea3bcd

20 files changed

Lines changed: 98 additions & 38 deletions

File tree

packages/gatekeeper-cloudflare/src/cloudflare.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { skipRpcValidation, validateRpc } from "capnweb-validate";
33
import {
44
GatekeeperVendor as GatekeeperVendorIface, Gatekeeper, GatekeeperUserVerifier, VendorDescription,
55
GatekeeperConnectCallback, GatekeeperConnectOptions, AccountDescription,
6-
SupportedResource, ResourceConfiguratorFrame,
6+
SupportedResource, ResourceConfiguratorFrame, stripTrailingSlashes,
77
} from "@gadgets/workshop-shared/gatekeeper";
88
import { CloudflareGatekeeperUser } from "@gadgets/workshop-shared/cloudflare-gatekeeper";
99
import { getOAuthConfig, buildAuthorizeUrl, generatePkce, exchangeCode, refreshTokens, AUTH_SCOPES, FULL_SCOPES } from "./oauth";
@@ -67,7 +67,7 @@ type Env = Cloudflare.Env & {
6767
};
6868

6969
function getBaseUrl(env: Env) {
70-
return (env.BASE_URL || "http://localhost:8787/gatekeeper/cloudflare").replace(/\/+$/, "");
70+
return stripTrailingSlashes(env.BASE_URL || "http://localhost:8787/gatekeeper/cloudflare");
7171
}
7272

7373
function getBasePath(env: Env) {

packages/gatekeeper-confluence/src/confluence-markdown.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,16 @@ function renderMacro(el: HTMLElement): string | null {
121121
}
122122

123123
const tableRow = (vals: string[]): string => `| ${vals.join(" | ")} |`;
124+
// Escape backslashes as well as pipes so an existing backslash cannot neutralize the pipe escape.
125+
const escapeTableCell = (value: string): string =>
126+
value.replace(/[\\|]/g, character => `\\${character}`);
124127

125128
function renderTable(el: HTMLElement): string {
126129
const rows = el.querySelectorAll("tr");
127130
if (rows.length === 0) return "";
128131
const cells = (row: HTMLElement) =>
129132
(row.childNodes.filter(n => isElement(n) && ["td", "th"].includes(tagOf(n))) as HTMLElement[])
130-
.map(c => renderInline(c.childNodes).trim().replace(/\|/g, "\\|"));
133+
.map(c => escapeTableCell(renderInline(c.childNodes).trim()));
131134

132135
const header = cells(rows[0]);
133136
const lines = [tableRow(header), tableRow(header.map(() => "---"))];

packages/gatekeeper-email/src/email.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
AccountDescription,
1616
SupportedResource,
1717
ResourceConfiguratorFrame,
18+
stripTrailingSlashes,
1819
} from '@gadgets/workshop-shared/gatekeeper';
1920
import {
2021
EmailSession,
@@ -64,7 +65,7 @@ type Env = Cloudflare.Env & {
6465
}
6566

6667
function getBaseUrl(env: Env) {
67-
return (env.BASE_URL || "http://localhost:8787/gatekeeper/email").replace(/\/+$/, "");
68+
return stripTrailingSlashes(env.BASE_URL || "http://localhost:8787/gatekeeper/email");
6869
}
6970

7071
function getBasePath(env: Env) {

packages/gatekeeper-github/src/github.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DurableObject, RpcStub, RpcTarget, WorkerEntrypoint } from "cloudflare:
22
import { skipRpcValidation, validateRpc } from "capnweb-validate";
33
import {
44
ApprovalQueue,
5+
stripTrailingSlashes,
56
type ActionDescription,
67
type AccountDescription,
78
type Cursor,
@@ -355,7 +356,7 @@ function constantTimeEqual(a: string, b: string): boolean {
355356
}
356357

357358
function getBaseUrl(env: Env): string {
358-
return (env.BASE_URL ?? "http://localhost:8787/gatekeeper/github").replace(/\/+$/, "");
359+
return stripTrailingSlashes(env.BASE_URL ?? "http://localhost:8787/gatekeeper/github");
359360
}
360361

361362
function getBasePath(env: Env): string {

packages/gatekeeper-google/src/google.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { WorkerEntrypoint, DurableObject, RpcTarget, RpcStub } from "cloudflare:workers";
22
import { skipRpcValidation, validateRpc } from "capnweb-validate";
3-
import { GatekeeperUser, GatekeeperUserVerifier, GatekeeperVendor as GatekeeperVendorIface, Gatekeeper, ResourceDescription, ApprovalQueue, ObservationDescription, VendorDescription, GatekeeperConnectCallback, GatekeeperConnectOptions, AccountDescription, SupportedResource, ResourceConfiguratorFrame, Cursor, ActionKind } from '@gadgets/workshop-shared/gatekeeper';
3+
import { GatekeeperUser, GatekeeperUserVerifier, GatekeeperVendor as GatekeeperVendorIface, Gatekeeper, ResourceDescription, ApprovalQueue, ObservationDescription, VendorDescription, GatekeeperConnectCallback, GatekeeperConnectOptions, AccountDescription, SupportedResource, ResourceConfiguratorFrame, Cursor, ActionKind, stripTrailingSlashes } from '@gadgets/workshop-shared/gatekeeper';
44
import { exchangeAuthCode, getAccessToken, getGoogleAccountDescription, getGoogleVerifiedEmail, GmailApi, GmailMessageRaw, GmailOutboundMessage, GoogleAccessToken, normalizeEmailRecipients, revokeGoogleToken } from "./google-api";
55
import {
66
GmailSession, GmailThread, GmailMessage,
@@ -158,7 +158,7 @@ function validateGmailQueryForGrouping(query: string): void {
158158
}
159159

160160
function getBaseUrl(env: Env) {
161-
return (env.BASE_URL || "http://localhost:8787/gatekeeper/google").replace(/\/+$/, "");
161+
return stripTrailingSlashes(env.BASE_URL || "http://localhost:8787/gatekeeper/google");
162162
}
163163

164164
function getBasePath(env: Env) {

packages/gatekeeper-homeassistant/src/homeassistant.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { DurableObject, RpcStub, RpcTarget, WorkerEntrypoint } from "cloudflare:
22
import { skipRpcValidation, validateRpc } from "capnweb-validate";
33
import {
44
ApprovalQueue,
5+
stripTrailingSlashes,
56
type AccountDescription,
67
type AvatarImage,
78
type Gatekeeper,
@@ -104,7 +105,7 @@ function constantTimeEqual(a: string, b: string): boolean {
104105
}
105106

106107
function getBaseUrl(env: Env): string {
107-
return (env.BASE_URL ?? "http://localhost:8787/gatekeeper/homeassistant").replace(/\/+$/, "");
108+
return stripTrailingSlashes(env.BASE_URL ?? "http://localhost:8787/gatekeeper/homeassistant");
108109
}
109110

110111
function getBasePath(env: Env): string {
@@ -318,8 +319,7 @@ export default {
318319
throw new Error("URL must use http:// or https://");
319320
}
320321
// Strip trailing slash
321-
normalizedUrl = `${u.protocol}//${u.host}${u.pathname.replace(/\/+$/, "")}`;
322-
if (normalizedUrl.endsWith("/")) normalizedUrl = normalizedUrl.slice(0, -1);
322+
normalizedUrl = `${u.protocol}//${u.host}${stripTrailingSlashes(u.pathname)}`;
323323
} catch (e: any) {
324324
return new Response(
325325
CONNECT_FORM_HTML({ actionUrl: req.url, error: `Invalid URL: ${e.message}` }),

packages/gatekeeper-linear/src/linear.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { WorkerEntrypoint, DurableObject, RpcTarget, RpcStub } from "cloudflare:
22
import { skipRpcValidation, validateRpc } from "capnweb-validate";
33
import {
44
GatekeeperUser,
5+
stripTrailingSlashes,
56
GatekeeperUserVerifier,
67
GatekeeperVendor as GatekeeperVendorIface,
78
Gatekeeper,
@@ -176,7 +177,7 @@ function badRequest(message: string): Response {
176177
}
177178

178179
function getBaseUrl(env: Env): string {
179-
return (env.BASE_URL ?? "http://localhost:8787/gatekeeper/linear").replace(/\/+$/, "");
180+
return stripTrailingSlashes(env.BASE_URL ?? "http://localhost:8787/gatekeeper/linear");
180181
}
181182

182183
function getBasePath(env: Env): string {

packages/gatekeeper-mcp-portal/src/portal.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { validateRpc, skipRpcValidation } from "capnweb-validate";
1010
import { createLogger } from "@gadgets/backend-utils/logger";
1111
import {
1212
matchesResourceUrlPattern,
13+
stripTrailingSlashes,
1314
type AvatarImage,
1415
type Gatekeeper,
1516
type GatekeeperConnectCallback,
@@ -99,7 +100,7 @@ const PORTAL_COLOR = "#f6821f";
99100
// Helpers
100101

101102
function getBaseUrl(env: Env): string {
102-
return (env.BASE_URL ?? "http://localhost:8787/gatekeeper/mcp-portal").replace(/\/+$/, "");
103+
return stripTrailingSlashes(env.BASE_URL ?? "http://localhost:8787/gatekeeper/mcp-portal");
103104
}
104105

105106
async function fetchPortalServers(

packages/gatekeeper-mcp/src/mcp.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { RpcStub, RpcTarget, WorkerEntrypoint } from "cloudflare:workers";
1010
import { validateRpc, skipRpcValidation } from "capnweb-validate";
1111
import { createLogger } from "@gadgets/backend-utils/logger";
1212
import {
13+
stripTrailingSlashes,
1314
type AvatarImage,
1415
type Gatekeeper,
1516
type GatekeeperConnectCallback,
@@ -84,7 +85,7 @@ const MCP_AVATAR: AvatarImage = { url: MCP_LOGO_URL };
8485
// Helpers
8586

8687
function getBaseUrl(env: Env): string {
87-
return (env.BASE_URL ?? "http://localhost:8787/gatekeeper/mcp").replace(/\/+$/, "");
88+
return stripTrailingSlashes(env.BASE_URL ?? "http://localhost:8787/gatekeeper/mcp");
8889
}
8990

9091
// ---------------------------------------------------------------------------

packages/gatekeeper-notion/src/notion.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import { DurableObject, RpcStub, RpcTarget, WorkerEntrypoint } from "cloudflare:workers";
1717
import { skipRpcValidation, validateRpc } from "capnweb-validate";
1818
import {
19+
stripTrailingSlashes,
1920
type AccountDescription,
2021
type ApprovalQueue,
2122
type Gatekeeper,
@@ -122,7 +123,7 @@ type StoredAccountInfo = {
122123
};
123124

124125
function getBaseUrl(env: Env): string {
125-
return (env.BASE_URL || "http://localhost:8787/gatekeeper/notion").replace(/\/+$/, "");
126+
return stripTrailingSlashes(env.BASE_URL || "http://localhost:8787/gatekeeper/notion");
126127
}
127128

128129
function getBasePath(env: Env): string {
@@ -204,7 +205,7 @@ function idFromResourceUrl(url: string): string | null {
204205
return null;
205206
}
206207
if (!/(^|\.)notion\.so$/i.test(parsed.hostname)) return null;
207-
if (parsed.pathname.replace(/\/+$/, "").length === 0) return null;
208+
if (stripTrailingSlashes(parsed.pathname).length === 0) return null;
208209
try {
209210
return parseNotionId(url);
210211
} catch {

0 commit comments

Comments
 (0)