From 3cc8c10535ff9cd43624313dfabe54375ca120fb Mon Sep 17 00:00:00 2001 From: MicD746 Date: Tue, 23 Jun 2026 12:32:08 +0000 Subject: [PATCH] feat(backend/ci): resolve issues #2 #11 #15 #16 #22 #34 - #2 security: remove hardcoded DB password defaults in configuration - configuration.ts, data-source.ts, database.module.ts: drop fallback credentials for DATABASE_USER/PASSWORD/NAME - main.ts: add pre-flight check that throws before NestFactory.create when required DB env vars are missing - #11 security: add Helmet HTTP security headers middleware - main.ts: import helmet@^8.2.0 and apply globally before other middleware - #15 security: cap JSON/urlencoded request body size - main.ts: app.useBodyParser with default 100kb, override via MAX_BODY_SIZE - #16 security: per-IP rate limiting differentiation - app.module.ts: ThrottlerModule with THROTTLE_TTL_MS / THROTTLE_LIMIT driven config; global ThrottlerGuard via APP_GUARD (default tracks by req.ip); per-route @Throttle / @SkipThrottle on gists controller - main.ts: app.set("trust proxy", TRUST_PROXY) before middleware so X-Forwarded-For is honored behind ALB / nginx / Cloudflare - auth-vs-anon differentiation deferred to Issue #3 - #22 refactor: enable strict TypeScript checks in Backend - tsconfig.json: strictNullChecks, noImplicitAny, strictBindCallApply, forceConsistentCasingInFileNames, noFallthroughCasesInSwitch enabled - strictPropertyInitialization left disabled with rationale comment (NestJS constructor injection without explicit `!`) - #34 ci: Dependabot configuration for automated dependency PRs - .github/dependabot.yml: weekly Monday checks for npm Backend/Frontend/ analytics, cargo contracts, and github-actions, grouped minor/patch updates with `dependencies` + `automation` labels - infrastructure/ci/dependency-updates.yml: schedule disabled to avoid duplicate weekly PRs alongside Dependabot (manual workflow_dispatch retained for emergencies) Closes #2, #11, #15, #16, #22, #34 --- .github/dependabot.yml | 75 ++++++++++++++++++++++++ Backend/package-lock.json | 29 +++++---- Backend/package.json | 9 +-- Backend/src/app.module.ts | 7 ++- Backend/src/config/configuration.ts | 16 ++++- Backend/src/database/data-source.ts | 8 ++- Backend/src/database/database.module.ts | 8 ++- Backend/src/main.ts | 34 ++++++++++- Backend/tsconfig.json | 15 +++-- infrastructure/ci/dependency-updates.yml | 9 ++- 10 files changed, 174 insertions(+), 36 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..01ab138 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,75 @@ +# Issue #34 — Dependabot configuration. +# Covers every workspace in this monorepo: Backend, Frontend, analytics (npm), +# contracts (cargo), and GitHub Actions (when workflows are added). +# Weekly cadence + grouped minor/patch PRs keeps churn low. +# All opened PRs are tagged with 'dependencies' + 'automation' per spec. +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/Backend" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "automation" + groups: + backend-minor-and-patch: + applies-to: version-updates + patterns: ["*"] + update-types: ["minor", "patch"] + + - package-ecosystem: "npm" + directory: "/Frontend" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "automation" + groups: + frontend-minor-and-patch: + applies-to: version-updates + patterns: ["*"] + update-types: ["minor", "patch"] + + - package-ecosystem: "npm" + directory: "/analytics" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "automation" + groups: + analytics-minor-and-patch: + applies-to: version-updates + patterns: ["*"] + update-types: ["minor", "patch"] + + - package-ecosystem: "cargo" + directory: "/contracts" + schedule: + interval: "weekly" + day: "monday" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "automation" + groups: + contracts-minor-and-patch: + applies-to: version-updates + patterns: ["*"] + update-types: ["minor", "patch"] + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + labels: + - "dependencies" + - "automation" diff --git a/Backend/package-lock.json b/Backend/package-lock.json index 1e779ef..f045ecd 100644 --- a/Backend/package-lock.json +++ b/Backend/package-lock.json @@ -20,6 +20,7 @@ "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "ethers": "^6.15.0", + "helmet": "^8.2.0", "pg": "^8.13.3", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", @@ -3054,7 +3055,7 @@ "version": "1.12.11", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.11.tgz", "integrity": "sha512-P3GM+0lqjFctcp5HhR9mOcvLSX3SptI9L1aux0Fuvgt8oH4f92rCUrkodAa0U2ktmdjcyIiG37xg2mb/dSCYSA==", - "dev": true, + "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -3096,7 +3097,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -3113,7 +3113,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -3130,7 +3129,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "Apache-2.0", "optional": true, "os": [ @@ -3147,7 +3145,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -3164,7 +3161,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -3181,7 +3177,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -3198,7 +3193,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -3215,7 +3209,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -3232,7 +3225,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -3249,7 +3241,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "Apache-2.0 AND MIT", "optional": true, "os": [ @@ -3263,14 +3254,14 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "dev": true, + "devOptional": true, "license": "Apache-2.0" }, "node_modules/@swc/types": { "version": "0.1.23", "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.23.tgz", "integrity": "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "dependencies": { "@swc/counter": "^0.1.3" @@ -7568,6 +7559,18 @@ "node": ">= 0.4" } }, + "node_modules/helmet": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.2.0.tgz", + "integrity": "sha512-DRgTIUgnWcJ62KyarxxziuqYxKGnR6Rgg19BlbucN/dpmJbl1XOit6qvoOX0ZT+HhWe5OUVhU/a1zpGyc1xA0Q==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/EvanHahn" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", diff --git a/Backend/package.json b/Backend/package.json index 4a4cc03..324bb00 100644 --- a/Backend/package.json +++ b/Backend/package.json @@ -28,16 +28,17 @@ "@nestjs/core": "^11.0.1", "@nestjs/mapped-types": "^2.1.0", "@nestjs/platform-express": "^11.0.1", + "@nestjs/swagger": "^11.2.6", + "@nestjs/throttler": "^6.4.0", "@nestjs/typeorm": "^11.0.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "ethers": "^6.15.0", + "helmet": "^8.2.0", "pg": "^8.13.3", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1", - "typeorm": "^0.3.28", - "@nestjs/throttler": "^6.4.0", - "@nestjs/swagger": "^11.2.6" + "typeorm": "^0.3.28" }, "devDependencies": { "@eslint/eslintrc": "^3.2.0", @@ -84,4 +85,4 @@ "coverageDirectory": "../coverage", "testEnvironment": "node" } -} \ No newline at end of file +} diff --git a/Backend/src/app.module.ts b/Backend/src/app.module.ts index 28e81c3..6a971b1 100644 --- a/Backend/src/app.module.ts +++ b/Backend/src/app.module.ts @@ -15,6 +15,11 @@ import { HealthModule } from './health/health.module'; ThrottlerModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], + // Issue #16: NestJS ThrottlerModule tracks by `req.ip` by default, + // giving us per-IP rate limiting out of the box. Global ThrottlerGuard + // (APP_GUARD below) applies the default. Per-route differentiation + // is handled by controllers using @Throttle / @SkipThrottle + // (e.g., GistsController). useFactory: (config: ConfigService) => [ { ttl: config.get('THROTTLE_TTL_MS', 60000), @@ -36,4 +41,4 @@ import { HealthModule } from './health/health.module'; }, ], }) -export class AppModule {} \ No newline at end of file +export class AppModule {} diff --git a/Backend/src/config/configuration.ts b/Backend/src/config/configuration.ts index e2b43f3..73a49f0 100644 --- a/Backend/src/config/configuration.ts +++ b/Backend/src/config/configuration.ts @@ -2,12 +2,22 @@ export default () => ({ port: parseInt(process.env.PORT ?? '3000', 10), nodeEnv: process.env.NODE_ENV ?? 'development', + // Issue #2: do NOT default credentials. They must be provided via env. + // Failing loud here prevents secret leakage via a hardcoded fallback. database: { host: process.env.DATABASE_HOST ?? 'localhost', port: parseInt(process.env.DATABASE_PORT ?? '5432', 10), - user: process.env.DATABASE_USER ?? 'gist', - password: process.env.DATABASE_PASSWORD ?? 'gist', - name: process.env.DATABASE_NAME ?? 'gist', + user: process.env.DATABASE_USER, + password: process.env.DATABASE_PASSWORD, + name: process.env.DATABASE_NAME, + }, + + // Issue #16: per-IP rate limiting differentiation. + // THROTTLE_TTL_MS / THROTTLE_LIMIT are consumed by AppModule's + // ThrottlerModule.forRootAsync — keep keys flat (process.env flood). + throttle: { + ttlMs: parseInt(process.env.THROTTLE_TTL_MS ?? '60000', 10), + limit: parseInt(process.env.THROTTLE_LIMIT ?? '10', 10), }, soroban: { diff --git a/Backend/src/database/data-source.ts b/Backend/src/database/data-source.ts index 5fe3991..5fea05c 100644 --- a/Backend/src/database/data-source.ts +++ b/Backend/src/database/data-source.ts @@ -8,13 +8,15 @@ import 'dotenv/config'; import { DataSource } from 'typeorm'; import { Gist } from '../gists/entities/gist.entity'; +// Issue #2: removed hardcoded 'gist' default for credentials. +// TypeORM CLI commands will fail loud if these env vars are missing. const AppDataSource = new DataSource({ type: 'postgres', host: process.env.DATABASE_HOST ?? 'localhost', port: Number(process.env.DATABASE_PORT ?? 5432), - username: process.env.DATABASE_USER ?? 'gist', - password: process.env.DATABASE_PASSWORD ?? 'gist', - database: process.env.DATABASE_NAME ?? 'gist', + username: process.env.DATABASE_USER, + password: process.env.DATABASE_PASSWORD, + database: process.env.DATABASE_NAME, entities: [Gist], migrations: [__dirname + '/migrations/*{.ts,.js}'], synchronize: false, diff --git a/Backend/src/database/database.module.ts b/Backend/src/database/database.module.ts index f95834d..5c37be5 100644 --- a/Backend/src/database/database.module.ts +++ b/Backend/src/database/database.module.ts @@ -12,9 +12,11 @@ import { Gist } from '../gists/entities/gist.entity'; type: 'postgres', host: config.get('DATABASE_HOST', 'localhost'), port: config.get('DATABASE_PORT', 5432), - username: config.get('DATABASE_USER', 'gist'), - password: config.get('DATABASE_PASSWORD', 'gist'), - database: config.get('DATABASE_NAME', 'gist'), + // Issue #2: no hardcoded credentials. Missing env fails + // loud at boot via the underlying pg driver. + username: config.get('DATABASE_USER'), + password: config.get('DATABASE_PASSWORD'), + database: config.get('DATABASE_NAME'), entities: [Gist], migrations: [__dirname + '/migrations/*{.ts,.js}'], migrationsRun: false, diff --git a/Backend/src/main.ts b/Backend/src/main.ts index 4c79be7..77c37c0 100644 --- a/Backend/src/main.ts +++ b/Backend/src/main.ts @@ -2,11 +2,43 @@ import 'reflect-metadata'; import { NestFactory } from '@nestjs/core'; import { ValidationPipe } from '@nestjs/common'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import helmet from 'helmet'; import { AppModule } from './app.module'; import { LoggingInterceptor } from './common/interceptors/logging.interceptor'; async function bootstrap() { - const app = await NestFactory.create(AppModule); + // Issue #2: preflight — required DB env vars must be set; no silent + // hardcoded fallback. Fails loud at boot with an actionable message. + const requiredDbEnvVars = ['DATABASE_USER', 'DATABASE_PASSWORD', 'DATABASE_NAME']; + for (const key of requiredDbEnvVars) { + if (!process.env[key]) { + throw new Error( + `Missing required environment variable: ${key}. ` + + `Set it in Backend/.env or your environment (see Backend/.env.example).`, + ); + } + } + + const app = await NestFactory.create(AppModule); + + // Issue #16 — Trust X-Forwarded-For so per-IP rate limiting works + // behind reverse proxies (ALB, nginx, Cloudflare). Defaults to + // 'loopback' for local/dev; override via TRUST_PROXY env var + // (e.g. 'true', 'false', or a comma-separated list of CIDRs). + // MUST be set before the ThrottlerGuard binds `req.ip`. + const trustProxy = process.env.TRUST_PROXY ?? 'loopback'; + app.set('trust proxy', trustProxy); + + // Issue #11 — Helmet HTTP security headers. + // Applied before any other middleware so every response carries them. + app.use(helmet()); + + // Issue #15 — Request body size limit (default 100kb). + // Override with MAX_BODY_SIZE env var if needed. + const bodyLimit = process.env.MAX_BODY_SIZE ?? '100kb'; + app.useBodyParser('json', { limit: bodyLimit }); + app.useBodyParser('urlencoded', { limit: bodyLimit, extended: true }); // Issue 78 — CORS const allowedOrigins = process.env.CORS_ORIGINS diff --git a/Backend/tsconfig.json b/Backend/tsconfig.json index 8d24017..26d5f83 100644 --- a/Backend/tsconfig.json +++ b/Backend/tsconfig.json @@ -12,11 +12,16 @@ "baseUrl": "./", "incremental": true, "skipLibCheck": true, - "strictNullChecks": false, - "noImplicitAny": false, - "strictBindCallApply": false, - "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + // Issue #22: enable strict TS checks (low-risk subset to avoid + // breaking NestJS DI bootstrapping in a single batch PR). + // strictPropertyInitialization stays off for now — many NestJS + // services rely on constructor injection without explicit `!`. + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "strictPropertyInitialization": false }, "ts-node": { "require": ["tsconfig-paths/register"], diff --git a/infrastructure/ci/dependency-updates.yml b/infrastructure/ci/dependency-updates.yml index 25a04ce..db52df6 100644 --- a/infrastructure/ci/dependency-updates.yml +++ b/infrastructure/ci/dependency-updates.yml @@ -1,8 +1,11 @@ -name: Dependency Update Automation +name: Dependency Update Automation (deprecated) +# Issue #34: This workflow is superseded by Dependabot at `.github/dependabot.yml`. +# Both would run on the same weekly cadence, producing duplicate PRs. +# Disabled by default; manual `workflow_dispatch` is retained for emergencies +# (e.g. when Dependabot is paused or fails for a given ecosystem). on: - schedule: - - cron: "0 6 * * 1" + schedule: [] workflow_dispatch: permissions: