Skip to content

Commit 83189a0

Browse files
SSheppDevclaude
andcommitted
fix(security): address CodeQL high-severity findings
ddlManager.ts — add assertValidOrgId() (regex /^[A-Za-z0-9]{15,18}$/) called at the top of every DDL function so CodeQL's taint tracking can see that orgId is validated before it reaches any SQL string. Matches the same regex already enforced by schemaNameForOrgId() in migrate.ts. app.ts — enable Helmet's Content Security Policy instead of disabling it. Directives are tuned for the bundled React SPA: scripts from self only, unsafe-inline styles for Tailwind, no CDN, no frame embedding. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 19e09d8 commit 83189a0

2 files changed

Lines changed: 28 additions & 2 deletions

File tree

src/api/src/app.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,21 @@ const syncLimiter = rateLimit({
6060
export function createApp() {
6161
const app = express()
6262

63-
// HTTP security headers — CSP disabled to allow the bundled React SPA to load
64-
app.use(helmet({ contentSecurityPolicy: false }))
63+
// HTTP security headers with CSP tuned for the bundled React SPA (no CDN, no inline scripts)
64+
app.use(helmet({
65+
contentSecurityPolicy: {
66+
directives: {
67+
defaultSrc: ["'self'"],
68+
scriptSrc: ["'self'"],
69+
styleSrc: ["'self'", "'unsafe-inline'"], // Tailwind uses inline style attributes
70+
imgSrc: ["'self'", 'data:'],
71+
fontSrc: ["'self'", 'data:'],
72+
connectSrc: ["'self'"],
73+
objectSrc: ["'none'"],
74+
frameAncestors: ["'none'"],
75+
},
76+
},
77+
}))
6578

6679
// JSON body parser
6780
app.use(express.json({ limit: '100kb' }))

src/api/src/sync/ddlManager.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,20 @@ import { schemaNameForOrgId } from '../db/migrate'
77
// ---------------------------------------------------------------------------
88

99
const SF_API_NAME_RE = /^[A-Za-z][A-Za-z0-9_]{0,79}$/
10+
const ORG_ID_RE = /^[A-Za-z0-9]{15,18}$/
1011

1112
function assertValidApiName(name: string, label: string): void {
1213
if (!SF_API_NAME_RE.test(name)) {
1314
throw new Error(`ddlManager: invalid ${label} "${name}"`)
1415
}
1516
}
1617

18+
function assertValidOrgId(orgId: string): void {
19+
if (!ORG_ID_RE.test(orgId)) {
20+
throw new Error(`ddlManager: invalid orgId "${orgId}"`)
21+
}
22+
}
23+
1724
const SYSTEM_COLUMNS = new Set([
1825
'id',
1926
'sf_created_at',
@@ -89,6 +96,7 @@ function quoteIdent(name: string): string {
8996
* grants if the role exists, so newly added orgs are visible to BI users.
9097
*/
9198
export async function createOrgSchema(orgId: string): Promise<void> {
99+
assertValidOrgId(orgId)
92100
const schema = schemaNameForOrgId(orgId)
93101
await pool.query(`CREATE SCHEMA IF NOT EXISTS ${schema}`)
94102

@@ -111,6 +119,7 @@ export async function createOrgSchema(orgId: string): Promise<void> {
111119
* with the user before invoking — this is destructive.
112120
*/
113121
export async function dropOrgSchema(orgId: string): Promise<void> {
122+
assertValidOrgId(orgId)
114123
const schema = schemaNameForOrgId(orgId)
115124
await pool.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`)
116125
}
@@ -128,6 +137,7 @@ export async function createObjectTable(
128137
objectApiName: string,
129138
fields: Array<{ apiName: string; sfType: string }>
130139
): Promise<void> {
140+
assertValidOrgId(orgId)
131141
assertValidApiName(objectApiName, 'objectApiName')
132142
const fieldColumns = fields
133143
.filter(({ apiName }) => !SYSTEM_COLUMNS.has(apiName.toLowerCase()))
@@ -170,6 +180,7 @@ export async function addColumn(
170180
fieldApiName: string,
171181
sfType: string
172182
): Promise<void> {
183+
assertValidOrgId(orgId)
173184
assertValidApiName(objectApiName, 'objectApiName')
174185
assertValidApiName(fieldApiName, 'fieldApiName')
175186
const col = fieldApiName.toLowerCase()
@@ -191,6 +202,7 @@ export async function dropColumn(
191202
objectApiName: string,
192203
fieldApiName: string
193204
): Promise<void> {
205+
assertValidOrgId(orgId)
194206
assertValidApiName(objectApiName, 'objectApiName')
195207
assertValidApiName(fieldApiName, 'fieldApiName')
196208
const col = fieldApiName.toLowerCase()
@@ -210,6 +222,7 @@ export async function dropTable(
210222
orgId: string,
211223
objectApiName: string
212224
): Promise<void> {
225+
assertValidOrgId(orgId)
213226
assertValidApiName(objectApiName, 'objectApiName')
214227
const sql = `DROP TABLE IF EXISTS ${tableRefForOrg(orgId, objectApiName)}`
215228
await pool.query(sql)

0 commit comments

Comments
 (0)