Security hardening for production deployment

- Remove secrets from Dockerfile build args, pass as runtime env vars only
- Add non-root user to Docker container
- Add SKU format validation to prevent S3 key injection
- Sanitize error responses in sanity-lookup route
- Fix zod import to use @medusajs/framework/zod
- Clean up .env.template defaults and .dockerignore
This commit is contained in:
Brendon Heinst 2026-02-24 17:19:13 +01:00
parent 1c91d57899
commit 6b2187de2a
6 changed files with 23 additions and 19 deletions

View file

@ -9,3 +9,5 @@ coverage
.cache
.vscode
.idea
.claude
CLAUDE.md

View file

@ -1,9 +1,9 @@
STORE_CORS=http://localhost:8000,https://docs.medusajs.com
ADMIN_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com
AUTH_CORS=http://localhost:5173,http://localhost:9000,https://docs.medusajs.com
STORE_CORS=http://localhost:3000
ADMIN_CORS=http://localhost:5173,http://localhost:9000
AUTH_CORS=http://localhost:5173,http://localhost:9000,http://localhost:3000
REDIS_URL=redis://localhost:6379
JWT_SECRET=supersecret
COOKIE_SECRET=supersecret
JWT_SECRET=
COOKIE_SECRET=
DATABASE_URL=
DB_NAME=medusa-v2

View file

@ -1,22 +1,15 @@
FROM node:20-alpine AS builder
WORKDIR /app
# Only build-time vars needed for admin dashboard compilation
ARG STORE_CORS
ARG ADMIN_CORS
ARG AUTH_CORS
ARG DATABASE_URL
ARG REDIS_URL
ARG JWT_SECRET
ARG COOKIE_SECRET
ARG MEDUSA_BACKEND_URL
ENV STORE_CORS=$STORE_CORS
ENV ADMIN_CORS=$ADMIN_CORS
ENV AUTH_CORS=$AUTH_CORS
ENV DATABASE_URL=$DATABASE_URL
ENV REDIS_URL=$REDIS_URL
ENV JWT_SECRET=$JWT_SECRET
ENV COOKIE_SECRET=$COOKIE_SECRET
ENV MEDUSA_BACKEND_URL=$MEDUSA_BACKEND_URL
COPY package.json package-lock.json ./
@ -27,13 +20,16 @@ RUN npm run build
FROM node:20-alpine
WORKDIR /app/server
COPY --from=builder /app/.medusa/server .
RUN addgroup -S medusa && adduser -S medusa -G medusa
COPY --from=builder --chown=medusa:medusa /app/.medusa/server .
RUN npm install --legacy-peer-deps
COPY start.sh .
COPY trptk-pricing.json .
COPY --chown=medusa:medusa start.sh .
COPY --chown=medusa:medusa trptk-pricing.json .
RUN chmod +x start.sh
USER medusa
ENV NODE_ENV=production
EXPOSE 9000
CMD ["sh", "start.sh"]

View file

@ -1,7 +1,7 @@
import { z } from "zod"
import { z } from "@medusajs/framework/zod"
export const PostAdminDownloadGrant = z.object({
sku: z.string().min(1),
sku: z.string().regex(/^[A-Za-z]{2,5}\d{3,5}_[A-Za-z0-9]+$/, "Invalid SKU format"),
product_title: z.string().min(1),
variant_title: z.string().min(1),
note: z.string().optional(),

View file

@ -96,8 +96,9 @@ export async function GET(
available_variants: release.availableVariants || null,
})
} catch (err: any) {
console.error("Sanity query failed:", err)
res.status(500).json({
message: `Sanity query failed: ${err.message}`,
message: "Sanity query failed",
})
}
}

View file

@ -25,7 +25,12 @@ function getS3Client(): S3Client {
*
* Convention: SKU "TTK0001_352K24B2CH" key "ttk0001/ttk0001_352k24b2ch.zip"
*/
const SKU_PATTERN = /^[A-Za-z]{2,5}\d{3,5}_[A-Za-z0-9]+$/
export function skuToS3Key(sku: string): string {
if (!SKU_PATTERN.test(sku)) {
throw new Error(`Invalid SKU format: ${sku}`)
}
const lower = sku.toLowerCase()
const catalogueNumber = lower.split("_")[0]
return `${catalogueNumber}/${lower}.zip`