diff --git a/.gitea/workflows/security.yml b/.gitea/workflows/security.yml new file mode 100644 index 0000000..7c779f1 --- /dev/null +++ b/.gitea/workflows/security.yml @@ -0,0 +1,31 @@ +name: Backend security + +on: + push: + branches: + - '**' + pull_request: + schedule: + - cron: '17 5 * * *' + +jobs: + security: + runs-on: ubuntu-latest + steps: + - uses: https://github.com/actions/checkout@v4 + with: + fetch-depth: 0 + - name: Record checked revision + run: git rev-parse HEAD + - name: Scan history with Gitleaks 8.30.1 + run: | + curl --fail --location --silent --show-error https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz -o /tmp/gitleaks.tar.gz + echo '551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb /tmp/gitleaks.tar.gz' | sha256sum --check + mkdir -p /tmp/gitleaks-bin + tar -xzf /tmp/gitleaks.tar.gz -C /tmp/gitleaks-bin gitleaks + /tmp/gitleaks-bin/gitleaks git --log-opts=--all --redact=100 --no-banner + - uses: https://github.com/actions/setup-node@v4 + with: + node-version-file: .nvmrc + - name: Audit runtime and development dependencies + run: npm audit --include=dev --audit-level=low diff --git a/.gitea/workflows/verify.yml b/.gitea/workflows/verify.yml new file mode 100644 index 0000000..560f50a --- /dev/null +++ b/.gitea/workflows/verify.yml @@ -0,0 +1,33 @@ +name: Backend verify + +on: + push: + branches: + - '**' + +jobs: + verify: + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend + steps: + - uses: https://github.com/actions/checkout@v4 + with: + path: backend + - name: Checkout frontend contract + run: git clone --depth 1 --branch main https://git.personal.ksv741.ru/WealthLedger/frontend.git ../frontend + - name: Record checked revisions + run: | + git rev-parse HEAD + git -C ../frontend rev-parse HEAD + - uses: https://github.com/actions/setup-node@v4 + with: + node-version-file: backend/.nvmrc + - run: npm ci + - name: Generate Prisma client before typechecks + run: npx --no-install prisma generate + - name: Run all backend gates + run: npm run verify + - name: Run isolated SQLite and runtime OpenAPI suites + run: npm run test:e2e diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 0000000..733ee42 --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,17 @@ +# Reviewed historical false positives only; new commits remain scanned. +# Test fixtures: base64 of the deterministic string 0123456789abcdef repeated twice. +25d7c5780b60343fecb4f1ae9a30392776eb5aec:src/config/env.validation.spec.ts:generic-api-key:30 +31cbba8654f01adf11c52e12c2b286200d6cad7d:src/config/env.validation.spec.ts:generic-api-key:10 +31cbba8654f01adf11c52e12c2b286200d6cad7d:src/config/env.validation.spec.ts:generic-api-key:17 +31cbba8654f01adf11c52e12c2b286200d6cad7d:src/config/env.validation.spec.ts:generic-api-key:42 +31cbba8654f01adf11c52e12c2b286200d6cad7d:test/e2e-env.cjs:generic-api-key:4 +326fb67def9b3878d5447ef838ae96d50bf493d6:src/tbank/brokerage-operations.service.spec.ts:generic-api-key:15 +34c2b79e276c9575996604f3f862bc866cfe44e7:src/tbank/upcoming-events.service.spec.ts:generic-api-key:15 +c0a4a9388074bdce20138dd5649805ed24a9e9fa:src/tbank/brokerage-accounts.service.spec.ts:generic-api-key:12 +ce24a84b7fb48091d2b91836eb7b702a2f811c96:test/e2e-env.cjs:generic-api-key:16 +dfca11c81f7e6dadce3ca6f961d265c5e3f8ff8e:src/tbank/portfolio.service.spec.ts:generic-api-key:16 +# Historical .env.example JWT_SECRET is an explicit replace-with placeholder. +4cbfc2621cfc54df3d09786905130c28a2323a04:.env.example:generic-api-key:4 +# Historical CircleCI badge copied verbatim from nestjs/typescript-starter README. +# Verified against https://github.com/nestjs/typescript-starter/blob/master/README.md +f8dcfb71c1767696a9aa6741ddaf6575416c901e:README.md:generic-api-key:5 diff --git a/README.md b/README.md index 1a08311..0789dd1 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,28 @@ threshold. `npm run test:e2e` отдельно проверяет все HTTP su выполните обе команды. Результаты — в [remediation registry](../docs/development/audit-remediation.md). +### CI и обновления зависимостей + +`.gitea/workflows/verify.yml` запускает `npm ci`, генерацию Prisma client, +`npm run verify` и `npm run test:e2e` на каждом push ветки. Frontend snapshot +берётся из `main` соседнего репозитория; лог сохраняет оба SHA. Изменение +контракта согласуется с этой frontend revision. Успешный unit gate не заменяет +SQLite/e2e; ошибка любой suite завершает job с FAIL. + +`security.yml` сканирует всю Git-историю Gitleaks 8.30.1 с полностью скрытыми +значениями и выполняет `npm audit --include=dev --audit-level=low` при push, +PR и ежедневно. `.gitleaksignore` перечисляет только проверенные исторические +совпадения: детерминированные fixtures, placeholder и badge из Nest starter. +Новые коммиты этими исключениями не покрываются. + +Renovate группирует minor/patch updates, а major требует согласования в +Dependency Dashboard. Автоматический merge не включён. Overrides в +`package.json` закрывают advisories транзитивных `multer`, `deepmerge-ts`, +`mysql2` и `js-yaml`; удаляйте их, когда родительские пакеты примут исправленные +версии. Совместимость Prisma override проверяется генерацией и SQLite migrations. +Required checks настраиваются отдельно в Gitea: наличие workflow само по себе +не блокирует merge. + Общий `tsconfig.json` включает `strict: true` и `noFallthroughCasesInSwitch` для приложения, тестов и сборок. ESLint запрещает non-null assertions (`value!`) и definite-assignment assertions (`field!: Type`). diff --git a/package-lock.json b/package-lock.json index 1af0b02..22d12c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1595,9 +1595,9 @@ } }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.1.tgz", - "integrity": "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==", + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.15.2.tgz", + "integrity": "sha512-6EuL879VkRA+1Cz578mKMiKvjPNEuk6+r1JaFzoSWejZmtf7xWbIyw1e3KkxlkzTIt9Taw6JBhEppG7utc1P+w==", "dev": true, "license": "MIT", "dependencies": { @@ -6254,13 +6254,23 @@ } }, "node_modules/deepmerge-ts": { - "version": "7.1.5", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", - "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-8.0.2.tgz", + "integrity": "sha512-uqbvqLUMrc6p0MO+WBRtTxY55hmyh94WRwI5a++PZe54X+bfVh59FSN7uWCBCW1CCVjzjnrwzfI8zidE2obMMw==", "devOptional": true, + "funding": [ + { + "type": "ko-fi", + "url": "https://ko-fi.com/rebeccastevens" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/deepmerge-ts" + } + ], "license": "BSD-3-Clause", "engines": { - "node": ">=16.0.0" + "node": ">=16.9.0" } }, "node_modules/defaults": { @@ -6303,16 +6313,6 @@ "node": ">=0.4.0" } }, - "node_modules/denque": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", - "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", - "devOptional": true, - "license": "Apache-2.0", - "engines": { - "node": ">=0.10" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -7170,9 +7170,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", - "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", "devOptional": true, "funding": [ { @@ -8990,9 +8990,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", "dev": true, "funding": [ { @@ -9728,9 +9728,9 @@ "license": "MIT" }, "node_modules/multer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.2.0.tgz", - "integrity": "sha512-6rdyFg2kLrMh9Jee7/BMPuV9lEAd7lLW2YUpF9/YxR7njyoUwwQ0ZPh3TaIY50Sw6vlyD2HW3wGOkTS4P79xrQ==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.3.0.tgz", + "integrity": "sha512-cjNbm3sttszgZeGfJR124D+jFEfkXCVAsoPBmFn9X7UxmDSFHWqE2CoEj0vrmSpuAFnqWR1Szcm9QTsiHr60Xw==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", @@ -9800,24 +9800,25 @@ } }, "node_modules/mysql2": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz", - "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==", + "version": "3.24.4", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.4.tgz", + "integrity": "sha512-A2olluVlj0mvgyIRRISMEzXc51m+21mRtcMVjJyIpt2GG98+XrC9m9HzsqcMsX2LcnfccJvY5NB22g8fENBnOA==", "devOptional": true, "license": "MIT", "dependencies": { - "aws-ssl-profiles": "^1.1.1", - "denque": "^2.1.0", + "aws-ssl-profiles": "^1.1.2", "generate-function": "^2.3.1", - "iconv-lite": "^0.7.0", - "long": "^5.2.1", - "lru.min": "^1.0.0", - "named-placeholders": "^1.1.3", - "seq-queue": "^0.0.5", - "sqlstring": "^2.3.2" + "iconv-lite": "^0.7.3", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.5.1" }, "engines": { "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" } }, "node_modules/named-placeholders": { @@ -10803,9 +10804,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.16.0.tgz", + "integrity": "sha512-h6fhOIaRrID2CbEY2fqs+7t+UXZo+MLAnU5gRIq85uFtdiUPCdsApMlHhXogKVM4HM2DVbIjGNTTYH2OcmP1vA==", "license": "BSD-3-Clause", "dependencies": { "es-define-property": "^1.0.1", @@ -11229,12 +11230,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/seq-queue": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz", - "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q==", - "devOptional": true - }, "node_modules/serve-static": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", @@ -11479,14 +11474,20 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/sqlstring": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz", - "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==", + "node_modules/sql-escaper": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.5.1.tgz", + "integrity": "sha512-4toX5E1fQbBrpfXidaHnF0669nkAdETeIPTs2SUjxxD7RRIs9ICG4gtpmfc68JCEKehsdwLFqBu9VlQqZ1P1gg==", "devOptional": true, "license": "MIT", "engines": { - "node": ">= 0.6" + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" } }, "node_modules/stack-utils": { diff --git a/package.json b/package.json index 7855287..dadcf7d 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,15 @@ "overrides": { "@nestjs/swagger@11.4.5": { "js-yaml": "5.2.3" + }, + "@nestjs/platform-express": { + "multer": "2.3.0" + }, + "@prisma/config@7.9.1": { + "deepmerge-ts": "8.0.2" + }, + "prisma": { + "mysql2": "3.24.4" } }, "jest": { diff --git a/renovate.json b/renovate.json index 7190a60..8f0899c 100644 --- a/renovate.json +++ b/renovate.json @@ -1,3 +1,28 @@ { - "$schema": "https://docs.renovatebot.com/renovate-schema.json" + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended", + ":semanticCommitTypeAll(chore)" + ], + "timezone": "Europe/Moscow", + "labels": [ + "dependencies" + ], + "dependencyDashboard": true, + "rangeStrategy": "bump", + "packageRules": [ + { + "matchUpdateTypes": [ + "minor", + "patch" + ], + "groupName": "non-major dependencies" + }, + { + "matchUpdateTypes": [ + "major" + ], + "dependencyDashboardApproval": true + } + ] }