From 86a495cf7307e7581841409d2b633049a84fd33f Mon Sep 17 00:00:00 2001 From: Prashant Bajpai <34747455+prashantasdeveloper@users.noreply.github.com> Date: Tue, 22 Sep 2026 17:19:53 +0530 Subject: [PATCH 1/3] =?UTF-8?q?chore:=20=F0=9F=A4=96=20resolve=20SonarClou?= =?UTF-8?q?d=20quality=20gate=20failures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses every open issue the quality gate reported on master. Security (New Code rating C -> A): - disable dependency lifecycle scripts on CI installs with `yarn --immutable --mode=skip-build`, running the repo's own trusted postinstall as an explicit step so the @polkadot dedupe still happens - pin sequoia-pgp/fast-forward to a full commit SHA (v1.0.0), clearing the outstanding security hotspot Code smells: - mark never-reassigned members readonly - prefer node: protocol imports, Number.isNaN / Number.parseInt, Date.now() and Array.prototype.flatMap over lodash flatten - avoid an object literal as a default parameter in ApiArrayResponse - use String.raw for the regex literals in jest.config.js - sort the Dockerfile apk packages, use `[[ ]]` in the shell scripts and give the integration-test cleanup trap an explicit return - extract cloneRepo/buildAndPushImage out of the Jenkinsfile node block to keep control flow nesting within 3 levels Tests: - complete the vacuous assertion in the batchBumpNonce spec, which called expect() with no matcher and so asserted nothing S2187 is ignored for the datastore repo specs: they delegate to shared suites (`XRepo.test(repo)`) whose `it` blocks Sonar's static analysis cannot see, making "add some tests to this file" a false positive. --- .github/workflows/fast-forward.yml | 2 +- .github/workflows/integration-test.yml | 5 +- .github/workflows/main.yml | 22 +++- Dockerfile | 6 +- Jenkinsfile | 118 ++++++++++-------- jest.config.js | 4 +- prepareRelease.sh | 2 +- scripts/integration-test.sh | 9 +- sonar-project.properties | 10 ++ .../decorators/get-custom-claim-type.pipe.ts | 2 +- src/commands/write-swagger.ts | 3 +- src/common/decorators/swagger.ts | 8 +- src/common/utils/functions.ts | 3 +- .../decorators/validation.ts | 2 +- src/datastore/postgres/repos/api-keys.repo.ts | 2 +- .../postgres/repos/notification.repo.ts | 5 +- .../postgres/repos/subscription.repo.ts | 5 +- src/datastore/postgres/source.ts | 3 +- src/identities/dto/register-identity.dto.ts | 2 +- src/message/artemis/artemis.service.ts | 4 +- src/notifications/notifications.service.ts | 4 +- src/notifications/notifications.util.ts | 3 +- .../offline-starter.service.ts | 3 +- .../offline-submitter.service.ts | 2 +- src/polymesh/polymesh.service.ts | 2 +- src/signing/config/signers.config.ts | 3 +- .../services/fireblocks-signing.service.ts | 2 +- .../subscriptions.service.spec.ts | 6 +- src/subscriptions/subscriptions.service.ts | 6 +- src/transactions/transactions.service.ts | 4 +- 30 files changed, 148 insertions(+), 104 deletions(-) create mode 100644 sonar-project.properties diff --git a/.github/workflows/fast-forward.yml b/.github/workflows/fast-forward.yml index 6c7eddee..627d145b 100644 --- a/.github/workflows/fast-forward.yml +++ b/.github/workflows/fast-forward.yml @@ -16,7 +16,7 @@ jobs: steps: - name: Fast forwarding - uses: sequoia-pgp/fast-forward@v1 + uses: sequoia-pgp/fast-forward@ea7628bedcb0b0b96e94383ada458d812fca4979 # v1.0.0 with: merge: true # To reduce the workflow's verbosity, use 'on-error' diff --git a/.github/workflows/integration-test.yml b/.github/workflows/integration-test.yml index 7156aec6..7619534f 100644 --- a/.github/workflows/integration-test.yml +++ b/.github/workflows/integration-test.yml @@ -17,6 +17,9 @@ jobs: cache: 'yarn' cache-dependency-path: 'yarn.lock' - name: install dependencies - run: yarn --immutable + # dependency lifecycle scripts are disabled to avoid executing untrusted code + run: yarn --immutable --mode=skip-build + - name: run trusted postinstall + run: yarn run postinstall - name: test run: yarn test:integration diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index af0dbee1..5a481cd8 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,7 +30,10 @@ jobs: node-version: '22.x' cache: 'yarn' - name: install dependencies - run: yarn --immutable + # dependency lifecycle scripts are disabled to avoid executing untrusted code + run: yarn --immutable --mode=skip-build + - name: run trusted postinstall + run: yarn run postinstall - name: lint run: yarn lint @@ -50,7 +53,10 @@ jobs: node-version: '22.x' cache: 'yarn' - name: install dependencies - run: yarn --immutable + # dependency lifecycle scripts are disabled to avoid executing untrusted code + run: yarn --immutable --mode=skip-build + - name: run trusted postinstall + run: yarn run postinstall - name: build run: yarn build @@ -70,7 +76,10 @@ jobs: node-version: '22.x' cache: 'yarn' - name: install dependencies - run: yarn --immutable + # dependency lifecycle scripts are disabled to avoid executing untrusted code + run: yarn --immutable --mode=skip-build + - name: run trusted postinstall + run: yarn run postinstall - name: test run: yarn test:cov @@ -91,7 +100,10 @@ jobs: node-version: '22.x' cache: 'yarn' - name: install dependencies - run: yarn --immutable + # dependency lifecycle scripts are disabled to avoid executing untrusted code + run: yarn --immutable --mode=skip-build + - name: run trusted postinstall + run: yarn run postinstall # - name: integration test # run: yarn test:integration - name: Setup SSH signing key @@ -138,7 +150,7 @@ jobs: steps: - name: Checking if fast forwarding is possible - uses: sequoia-pgp/fast-forward@v1 + uses: sequoia-pgp/fast-forward@ea7628bedcb0b0b96e94383ada458d812fca4979 # v1.0.0 with: merge: false # To reduce the workflow's verbosity, use 'on-error' diff --git a/Dockerfile b/Dockerfile index eba4b93d..eb075e54 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,11 @@ FROM node:lts-alpine3.17 AS builder RUN apk add --no-cache \ - python3 \ - make \ cmake \ g++ \ - jq + jq \ + make \ + python3 WORKDIR /app/builder RUN chown -R node: /app diff --git a/Jenkinsfile b/Jenkinsfile index 0f08c36e..b38a9a11 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -10,6 +10,68 @@ def withSecretEnv(List varAndPasswordList, Closure closure) { ////////////////////////////////////////////////////////////////////////////////////////// +def cloneRepo() { + withCredentials([ + usernamePassword(credentialsId: 'vault_approle', + usernameVariable: 'VAULT_ROLE_ID', + passwordVariable: 'VAULT_SECRET_ID'), + ]) { + + env.VAULT_ADDR = 'https://127.0.0.1:8200/' + env.VAULT_CACERT = '/etc/ssl/certs/vault.tls.chain.pem' + + withSecretEnv([[var: 'VAULT_TOKEN', + password: sh (returnStdout: true, + label: 'Login To Vault', + script: '''\ + vault write -field=token \ + -format=table \ + auth/approle/login \ + role_id="$VAULT_ROLE_ID" \ + secret_id="$VAULT_SECRET_ID" + '''.stripIndent()).trim()]]) { + stage('Clone Repo') { + sh (label: 'Clone Repo', + script: '/usr/local/bin/gitea-clone-repo.sh') + } + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// + +def buildAndPushImage() { + env.AWS_ACCOUNT_NUMBER = env.AWS_ACCOUNT_NUMBER_DATACENTER_PRIMARY + env.AWS_REGION = env.AWS_REGION_DATACENTER_PRIMARY + env.AWS_DEFAULT_REGION = env.AWS_REGION + env.CONTAINER_REGISTRY = "${env.AWS_ACCOUNT_NUMBER}.dkr.ecr.${AWS_REGION}.amazonaws.com" + env.GIT_COMMIT = sh (returnStdout: true, + label: 'Read Git Commit', + script: 'git rev-parse HEAD').trim() + + echo "GIT_COMMIT: ${env.GIT_COMMIT}" + + stage('Build') { + sh (label: 'Build', + script: '''#!/bin/bash + docker build -f Dockerfile -t "${CONTAINER_REGISTRY}/polymesh/${PROJECT_NAME}:${GIT_COMMIT}" . + ''') + } + + stage('Push') { + sh (label: 'Push', + script: '''#!/bin/bash + + aws ecr get-login-password | \ + docker login "$CONTAINER_REGISTRY" --username AWS --password-stdin + + docker push "${CONTAINER_REGISTRY}/polymesh/${PROJECT_NAME}:${GIT_COMMIT}" || true + ''') + } +} + +////////////////////////////////////////////////////////////////////////////////////////// + node { env.PROJECT_NAME = 'polymesh-rest-api' @@ -30,62 +92,10 @@ node { dir("${JENKINS_HOME}/workspace/${JOB_NAME}/${BUILD_NUMBER}") { - withCredentials([ - usernamePassword(credentialsId: 'vault_approle', - usernameVariable: 'VAULT_ROLE_ID', - passwordVariable: 'VAULT_SECRET_ID'), - ]) { - - env.VAULT_ADDR = 'https://127.0.0.1:8200/' - env.VAULT_CACERT = '/etc/ssl/certs/vault.tls.chain.pem' - - withSecretEnv([[var: 'VAULT_TOKEN', - password: sh (returnStdout: true, - label: 'Login To Vault', - script: '''\ - vault write -field=token \ - -format=table \ - auth/approle/login \ - role_id="$VAULT_ROLE_ID" \ - secret_id="$VAULT_SECRET_ID" - '''.stripIndent()).trim()]]) { - stage('Clone Repo') { - sh (label: 'Clone Repo', - script: '/usr/local/bin/gitea-clone-repo.sh') - } - } - } + cloneRepo() dir("${PROJECT_NAME}") { - - env.AWS_ACCOUNT_NUMBER = env.AWS_ACCOUNT_NUMBER_DATACENTER_PRIMARY - env.AWS_REGION = env.AWS_REGION_DATACENTER_PRIMARY - env.AWS_DEFAULT_REGION = env.AWS_REGION - env.CONTAINER_REGISTRY = "${env.AWS_ACCOUNT_NUMBER}.dkr.ecr.${AWS_REGION}.amazonaws.com" - env.GIT_COMMIT = sh (returnStdout: true, - label: 'Read Git Commit', - script: 'git rev-parse HEAD').trim() - - echo "GIT_COMMIT: ${env.GIT_COMMIT}" - - stage('Build') { - sh (label: 'Build', - script: '''#!/bin/bash - docker build -f Dockerfile -t "${CONTAINER_REGISTRY}/polymesh/${PROJECT_NAME}:${GIT_COMMIT}" . - ''') - } - - stage('Push') { - sh (label: 'Push', - script: '''#!/bin/bash - - aws ecr get-login-password | \ - docker login "$CONTAINER_REGISTRY" --username AWS --password-stdin - - docker push "${CONTAINER_REGISTRY}/polymesh/${PROJECT_NAME}:${GIT_COMMIT}" || true - ''') - } - + buildAndPushImage() } } } diff --git a/jest.config.js b/jest.config.js index 66deaacd..407e2490 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,13 +1,13 @@ module.exports = { preset: 'ts-jest/presets/js-with-babel', - transformIgnorePatterns: ['/node_modules/(?![@polymeshassociation/src]).+\\.js$'], + transformIgnorePatterns: [String.raw`/node_modules/(?![@polymeshassociation/src]).+\.js$`], testEnvironment: 'node', moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], testPathIgnorePatterns: ['/dist/*'], moduleNameMapper: { '~/(.*)': '/src/$1', }, - testRegex: '.*\\.spec\\.ts$', + testRegex: String.raw`.*\.spec\.ts$`, coverageDirectory: './coverage', collectCoverage: true, restoreMocks: true, diff --git a/prepareRelease.sh b/prepareRelease.sh index d5b78aad..cee6ec91 100755 --- a/prepareRelease.sh +++ b/prepareRelease.sh @@ -10,7 +10,7 @@ CHAIN_TAG='8.0.0-testnet-debian' # This lets it work on arm64, like Mac Books ARCHITECTURE=$(uname -m) CHAIN_REPO=polymeshassociation/polymesh -if [ "$ARCHITECTURE" = "arm64" ]; then +if [[ "$ARCHITECTURE" == "arm64" ]]; then CHAIN_REPO="polymeshassociation/polymesh-arm64" fi diff --git a/scripts/integration-test.sh b/scripts/integration-test.sh index ae41a128..f00ee6bf 100755 --- a/scripts/integration-test.sh +++ b/scripts/integration-test.sh @@ -9,7 +9,8 @@ git clone https://github.com/PolymeshAssociation/polymesh-dev-env.git "$DEV_ENV_ pushd "$INTEGRATION_DIR" -yarn +# lifecycle scripts are disabled to avoid executing untrusted dependency code +yarn --mode=skip-build echo "starting up environment" yarn test:start echo "env is set up" @@ -24,14 +25,16 @@ export POLYMESH_MIDDLEWARE_V2_URL="http://localhost:3000" # register cleanup to stop the background service function cleanup() { echo "cleaning up test environment" - if [ -n "${SERVICE_PID}" ]; then + if [[ -n "${SERVICE_PID}" ]]; then echo "Cleaning up service with PID: ${SERVICE_PID}" kill "${SERVICE_PID}" fi yarn test:stop - rm -rf $DEV_ENV_DIR + rm -rf "$DEV_ENV_DIR" + + return 0 } trap cleanup EXIT INT TERM diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 00000000..b2941a93 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,10 @@ +sonar.projectKey=PolymeshAssociation_polymesh-rest-api +sonar.organization=polymeshassociation + +# The datastore repo specs delegate to a shared, reusable suite that each implementation +# must pass (e.g. `NotificationRepo.test(repo)`). The `it(...)` blocks live in the +# `*.repo.suite.ts` helpers, so Sonar's static analysis cannot see them and wrongly reports +# S2187 ("Add some tests to this file or delete it"). These files do run real tests. +sonar.issue.ignore.multicriteria=e1 +sonar.issue.ignore.multicriteria.e1.ruleKey=typescript:S2187 +sonar.issue.ignore.multicriteria.e1.resourceKey=src/datastore/**/repos/*.repo.spec.ts diff --git a/src/claims/decorators/get-custom-claim-type.pipe.ts b/src/claims/decorators/get-custom-claim-type.pipe.ts index c4908423..31f49b87 100644 --- a/src/claims/decorators/get-custom-claim-type.pipe.ts +++ b/src/claims/decorators/get-custom-claim-type.pipe.ts @@ -17,5 +17,5 @@ export class GetCustomClaimTypePipe implements PipeTransform { } function isNumericString(value: string): boolean { - return !isNaN(Number(value)); + return !Number.isNaN(Number(value)); } diff --git a/src/commands/write-swagger.ts b/src/commands/write-swagger.ts index 00d4faba..26464121 100644 --- a/src/commands/write-swagger.ts +++ b/src/commands/write-swagger.ts @@ -1,6 +1,7 @@ +import { writeFileSync } from 'node:fs'; + import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; -import { writeFileSync } from 'fs'; import { AppModule } from '~/app.module'; import { swaggerDescription, swaggerTitle } from '~/common/utils'; diff --git a/src/common/decorators/swagger.ts b/src/common/decorators/swagger.ts index 900dedae..5ee88b0a 100644 --- a/src/common/decorators/swagger.ts +++ b/src/common/decorators/swagger.ts @@ -23,18 +23,16 @@ import { ResultsModel } from '~/common/models/results.model'; export const ApiArrayResponse = ( model: TModel, { - paginated, + paginated = true, example, examples, description, }: { - paginated: boolean; + paginated?: boolean; example?: unknown; examples?: unknown[] | Record; description?: string; - } = { - paginated: true, - } + } = {} ): ReturnType => { const extraModels = []; let items; diff --git a/src/common/utils/functions.ts b/src/common/utils/functions.ts index 845c2684..996ea18f 100644 --- a/src/common/utils/functions.ts +++ b/src/common/utils/functions.ts @@ -6,7 +6,6 @@ import { TxTags, } from '@polymeshassociation/polymesh-sdk/types'; import { randomBytes } from 'crypto'; -import { flatten } from 'lodash'; import { promisify } from 'util'; import { TransactionBaseDto } from '~/common/dto/transaction-base-dto'; @@ -28,7 +27,7 @@ import { import { ResultType } from '~/transactions/types'; export function getTxTags(): string[] { - return flatten(Object.values(TxTags).map(txTag => Object.values(txTag))); + return Object.values(TxTags).flatMap(txTag => Object.values(txTag)); } export function getTxTagsWithModuleNames(): string[] { diff --git a/src/corporate-actions/decorators/validation.ts b/src/corporate-actions/decorators/validation.ts index c11b4f8a..890f3981 100644 --- a/src/corporate-actions/decorators/validation.ts +++ b/src/corporate-actions/decorators/validation.ts @@ -15,7 +15,7 @@ export function IsCaCheckpoint() { validator: { async validate(value: unknown) { if (value instanceof Date) { - return !isNaN(new Date(value).getTime()); + return !Number.isNaN(new Date(value).getTime()); } if (value instanceof CorporateActionCheckpointDto) { return (await validateClass(value)).length === 0; diff --git a/src/datastore/postgres/repos/api-keys.repo.ts b/src/datastore/postgres/repos/api-keys.repo.ts index 32f58c00..bd4a714a 100644 --- a/src/datastore/postgres/repos/api-keys.repo.ts +++ b/src/datastore/postgres/repos/api-keys.repo.ts @@ -11,7 +11,7 @@ import { UserModel } from '~/users/model/user.model'; @Injectable() export class PostgresApiKeyRepo implements ApiKeyRepo { - constructor(@InjectRepository(ApiKey) private apiKeyRepo: Repository) {} + constructor(@InjectRepository(ApiKey) private readonly apiKeyRepo: Repository) {} public async createApiKey(user: UserModel): Promise { const secret = await generateBase64Secret(32); diff --git a/src/datastore/postgres/repos/notification.repo.ts b/src/datastore/postgres/repos/notification.repo.ts index f8cd326e..c02302c2 100644 --- a/src/datastore/postgres/repos/notification.repo.ts +++ b/src/datastore/postgres/repos/notification.repo.ts @@ -11,7 +11,10 @@ import { NotificationParams } from '~/notifications/types'; @Injectable() export class PostgresNotificationRepo implements NotificationRepo { - constructor(@InjectRepository(Notification) private notificationRepo: Repository) {} + constructor( + @InjectRepository(Notification) + private readonly notificationRepo: Repository + ) {} public async create(params: NotificationParams): Promise { const entity = this.notificationRepo.create({ diff --git a/src/datastore/postgres/repos/subscription.repo.ts b/src/datastore/postgres/repos/subscription.repo.ts index 756a243d..0b1dcc48 100644 --- a/src/datastore/postgres/repos/subscription.repo.ts +++ b/src/datastore/postgres/repos/subscription.repo.ts @@ -11,7 +11,10 @@ import { SubscriptionParams } from '~/subscriptions/types'; @Injectable() export class PostgresSubscriptionRepo implements SubscriptionRepo { - constructor(@InjectRepository(Subscription) private subscriptionRepo: Repository) {} + constructor( + @InjectRepository(Subscription) + private readonly subscriptionRepo: Repository + ) {} public async create(params: SubscriptionParams): Promise { const entity = this.subscriptionRepo.create({ diff --git a/src/datastore/postgres/source.ts b/src/datastore/postgres/source.ts index e75d9905..bfc27890 100644 --- a/src/datastore/postgres/source.ts +++ b/src/datastore/postgres/source.ts @@ -1,7 +1,8 @@ /* istanbul ignore file */ +import path from 'node:path'; + import * as dotenv from 'dotenv'; -import path from 'path'; import { DataSource } from 'typeorm'; import { readPostgresConfigFromEnv } from '~/datastore/postgres/utils'; diff --git a/src/identities/dto/register-identity.dto.ts b/src/identities/dto/register-identity.dto.ts index e9414730..4baf89fd 100644 --- a/src/identities/dto/register-identity.dto.ts +++ b/src/identities/dto/register-identity.dto.ts @@ -39,7 +39,7 @@ export class RegisterIdentityDto extends TransactionBaseDto { @ApiPropertyOptional({ description: 'Date at which the Identity will expire. Deprecated: as of chain v8 this no longer has any on-chain effect', - example: new Date(new Date().getTime() + +365 * 24 * 60 * 60 * 1000).toISOString(), + example: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(), type: 'string', deprecated: true, }) diff --git a/src/message/artemis/artemis.service.ts b/src/message/artemis/artemis.service.ts index 54f6d33c..9cf8d525 100644 --- a/src/message/artemis/artemis.service.ts +++ b/src/message/artemis/artemis.service.ts @@ -30,8 +30,8 @@ type AddressStore = Record; @Injectable() export class ArtemisService extends MessageService implements OnApplicationShutdown { - private receivers: Receiver[] = []; - private addressStore: Partial = {}; + private readonly receivers: Receiver[] = []; + private readonly addressStore: Partial = {}; private connectionPromise?: Promise; constructor( diff --git a/src/notifications/notifications.service.ts b/src/notifications/notifications.service.ts index 999cbc31..fd1384c2 100644 --- a/src/notifications/notifications.service.ts +++ b/src/notifications/notifications.service.ts @@ -19,8 +19,8 @@ import { SubscriptionsService } from '~/subscriptions/subscriptions.service'; @Injectable() export class NotificationsService { - private maxTries: number; - private retryInterval: number; + private readonly maxTries: number; + private readonly retryInterval: number; constructor( @Inject(notificationsConfig.KEY) config: ConfigType, diff --git a/src/notifications/notifications.util.ts b/src/notifications/notifications.util.ts index 13decd1f..d6e9b5df 100644 --- a/src/notifications/notifications.util.ts +++ b/src/notifications/notifications.util.ts @@ -1,4 +1,5 @@ -import { createHmac } from 'crypto'; +import { createHmac } from 'node:crypto'; + import stringify from 'json-stable-stringify'; import { NotificationPayload } from '~/notifications/types'; diff --git a/src/offline-starter/offline-starter.service.ts b/src/offline-starter/offline-starter.service.ts index 6057c027..a46972cd 100644 --- a/src/offline-starter/offline-starter.service.ts +++ b/src/offline-starter/offline-starter.service.ts @@ -1,6 +1,7 @@ +import { randomUUID } from 'node:crypto'; + import { Injectable } from '@nestjs/common'; import { GenericPolymeshTransaction } from '@polymeshassociation/polymesh-sdk/types'; -import { randomUUID } from 'crypto'; import { AddressName } from '~/common/utils/amqp'; import { PolymeshLogger } from '~/logger/polymesh-logger.service'; diff --git a/src/offline-submitter/offline-submitter.service.ts b/src/offline-submitter/offline-submitter.service.ts index 7e5329b7..d5c26f24 100644 --- a/src/offline-submitter/offline-submitter.service.ts +++ b/src/offline-submitter/offline-submitter.service.ts @@ -34,7 +34,7 @@ export class OfflineSubmitterService { public async submit(body: OfflineSignatureModel): Promise { const { id, signature, payload } = body; const { address, nonce: rawNonce } = payload.payload; - const nonce = parseInt(rawNonce, 16); + const nonce = Number.parseInt(rawNonce, 16); this.logger.debug(`received signature for: ${id}`); const transaction = await this.offlineTxRepo.createTx({ diff --git a/src/polymesh/polymesh.service.ts b/src/polymesh/polymesh.service.ts index 80b204b6..945cef00 100644 --- a/src/polymesh/polymesh.service.ts +++ b/src/polymesh/polymesh.service.ts @@ -9,7 +9,7 @@ import { ScheduleService } from '~/schedule/schedule.service'; @Injectable() export class PolymeshService implements OnModuleDestroy, OnModuleInit { - private heartbeatIntervalId = 'polymeshHeartbeat'; + private readonly heartbeatIntervalId = 'polymeshHeartbeat'; constructor( @Inject(POLYMESH_API) public readonly polymeshApi: Polymesh, diff --git a/src/signing/config/signers.config.ts b/src/signing/config/signers.config.ts index 5aec94e9..204bebda 100644 --- a/src/signing/config/signers.config.ts +++ b/src/signing/config/signers.config.ts @@ -1,7 +1,8 @@ /* istanbul ignore file */ +import { readFileSync } from 'node:fs'; + import { registerAs } from '@nestjs/config'; -import { readFileSync } from 'fs'; export default registerAs('signer-accounts', () => { const { diff --git a/src/signing/services/fireblocks-signing.service.ts b/src/signing/services/fireblocks-signing.service.ts index 3facf420..ff020cf6 100644 --- a/src/signing/services/fireblocks-signing.service.ts +++ b/src/signing/services/fireblocks-signing.service.ts @@ -27,7 +27,7 @@ export class FireblocksSigningService extends SigningService { private handleToDerivationPath(handle: string): DerivationPath { const sections = handle.split('-').map(Number); - if (sections.some(isNaN) || sections.length > 3 || handle === '') { + if (sections.some(Number.isNaN) || sections.length > 3 || handle === '') { throw new AppValidationError( 'Fireblocks `signer` field should be 3 integers formatted like: `x-y-z`' ); diff --git a/src/subscriptions/subscriptions.service.spec.ts b/src/subscriptions/subscriptions.service.spec.ts index 6d76a656..4de36b83 100644 --- a/src/subscriptions/subscriptions.service.spec.ts +++ b/src/subscriptions/subscriptions.service.spec.ts @@ -292,12 +292,10 @@ describe('SubscriptionsService', () => { }); describe('batchBumpNonce', () => { - it('should mark a group of subscriptions as done', async () => { + it('should bump the nonce of a group of subscriptions', async () => { await service.batchBumpNonce([1, 2]); - const result = await service.findAll(); - - expect(result.every(({ nextNonce }) => nextNonce === 1)); + expect(mockSubscriptionRepo.incrementNonces).toHaveBeenCalledWith([1, 2]); }); }); }); diff --git a/src/subscriptions/subscriptions.service.ts b/src/subscriptions/subscriptions.service.ts index bb294fdb..a317116f 100644 --- a/src/subscriptions/subscriptions.service.ts +++ b/src/subscriptions/subscriptions.service.ts @@ -16,9 +16,9 @@ import { SubscriptionStatus } from '~/subscriptions/types'; @Injectable() export class SubscriptionsService { - private ttl: number; - private maxTries: number; - private retryInterval: number; + private readonly ttl: number; + private readonly maxTries: number; + private readonly retryInterval: number; constructor( @Inject(subscriptionsConfig.KEY) config: ConfigType, diff --git a/src/transactions/transactions.service.ts b/src/transactions/transactions.service.ts index d329624c..d0e7b3d6 100644 --- a/src/transactions/transactions.service.ts +++ b/src/transactions/transactions.service.ts @@ -32,7 +32,7 @@ export class TransactionsService { * in-memory transaction store by Transaction Identifier. We use a Map * to be able to recycle indexes easily and remove elements in a performant way */ - private transactionStore: Map< + private readonly transactionStore: Map< number, { /** @@ -51,7 +51,7 @@ export class TransactionsService { > = new Map(); private currentId = 0; - private legitimacySecret: string; + private readonly legitimacySecret: string; constructor( @Inject(transactionsConfig.KEY) config: ConfigType, From 0114397509493500c7b162265e3072601e86d013 Mon Sep 17 00:00:00 2001 From: Prashant Bajpai <34747455+prashantasdeveloper@users.noreply.github.com> Date: Wed, 23 Sep 2026 16:21:10 +0530 Subject: [PATCH 2/3] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20the=20unus?= =?UTF-8?q?ed=20Jenkinsfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Jenkins pipeline is no longer used, so drop it along with the CODEOWNERS entry that pointed at it. This also removes the three S134 control flow nesting smells the previous commit refactored the file to satisfy. --- .github/CODEOWNERS | 1 - Jenkinsfile | 103 --------------------------------------------- 2 files changed, 104 deletions(-) delete mode 100644 Jenkinsfile diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 81198ce1..eb444d6a 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,3 +1,2 @@ * @VictorVicente @polymath-eric @prashantasdeveloper @sansan -Jenkinsfile @PolymeshAssociation/SRE ./github @PolymeshAssociation/SRE diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index b38a9a11..00000000 --- a/Jenkinsfile +++ /dev/null @@ -1,103 +0,0 @@ -////////////////////////////////////////////////////////////////////////////////////////// - -def withSecretEnv(List varAndPasswordList, Closure closure) { - wrap([$class: 'MaskPasswordsBuildWrapper', varPasswordPairs: varAndPasswordList]) { - withEnv(varAndPasswordList.collect { "${it.var}=${it.password}" }) { - closure() - } - } -} - -////////////////////////////////////////////////////////////////////////////////////////// - -def cloneRepo() { - withCredentials([ - usernamePassword(credentialsId: 'vault_approle', - usernameVariable: 'VAULT_ROLE_ID', - passwordVariable: 'VAULT_SECRET_ID'), - ]) { - - env.VAULT_ADDR = 'https://127.0.0.1:8200/' - env.VAULT_CACERT = '/etc/ssl/certs/vault.tls.chain.pem' - - withSecretEnv([[var: 'VAULT_TOKEN', - password: sh (returnStdout: true, - label: 'Login To Vault', - script: '''\ - vault write -field=token \ - -format=table \ - auth/approle/login \ - role_id="$VAULT_ROLE_ID" \ - secret_id="$VAULT_SECRET_ID" - '''.stripIndent()).trim()]]) { - stage('Clone Repo') { - sh (label: 'Clone Repo', - script: '/usr/local/bin/gitea-clone-repo.sh') - } - } - } -} - -////////////////////////////////////////////////////////////////////////////////////////// - -def buildAndPushImage() { - env.AWS_ACCOUNT_NUMBER = env.AWS_ACCOUNT_NUMBER_DATACENTER_PRIMARY - env.AWS_REGION = env.AWS_REGION_DATACENTER_PRIMARY - env.AWS_DEFAULT_REGION = env.AWS_REGION - env.CONTAINER_REGISTRY = "${env.AWS_ACCOUNT_NUMBER}.dkr.ecr.${AWS_REGION}.amazonaws.com" - env.GIT_COMMIT = sh (returnStdout: true, - label: 'Read Git Commit', - script: 'git rev-parse HEAD').trim() - - echo "GIT_COMMIT: ${env.GIT_COMMIT}" - - stage('Build') { - sh (label: 'Build', - script: '''#!/bin/bash - docker build -f Dockerfile -t "${CONTAINER_REGISTRY}/polymesh/${PROJECT_NAME}:${GIT_COMMIT}" . - ''') - } - - stage('Push') { - sh (label: 'Push', - script: '''#!/bin/bash - - aws ecr get-login-password | \ - docker login "$CONTAINER_REGISTRY" --username AWS --password-stdin - - docker push "${CONTAINER_REGISTRY}/polymesh/${PROJECT_NAME}:${GIT_COMMIT}" || true - ''') - } -} - -////////////////////////////////////////////////////////////////////////////////////////// - -node { - - env.PROJECT_NAME = 'polymesh-rest-api' - env.GIT_REPO = "ssh://git@ssh.gitea.polymesh.dev:4444/Deployment/${PROJECT_NAME}.git" - - properties([[$class: 'BuildDiscarderProperty', - strategy: [$class: 'LogRotator', - numToKeepStr: '7', - daysToKeepStr: '7', - artifactNumToKeepStr: '7', - artifactDaysToKeepStr: '7']]]) - - if (env.CHANGE_BRANCH) { - env.GIT_BRANCH = env.CHANGE_BRANCH // Job was started from a pull request - } else { - env.GIT_BRANCH = env.BRANCH_NAME - } - - dir("${JENKINS_HOME}/workspace/${JOB_NAME}/${BUILD_NUMBER}") { - - cloneRepo() - - dir("${PROJECT_NAME}") { - buildAndPushImage() - } - } -} - -////////////////////////////////////////////////////////////////////////////////////////// From 215427a908120ff0bf6c8b0a1c9d54c066339415 Mon Sep 17 00:00:00 2001 From: Prashant Bajpai <34747455+prashantasdeveloper@users.noreply.github.com> Date: Wed, 23 Sep 2026 16:22:20 +0530 Subject: [PATCH 3/3] =?UTF-8?q?chore:=20=F0=9F=A4=96=20correct=20the=20COD?= =?UTF-8?q?EOWNERS=20patterns=20and=20owners?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `./github` is syntactically valid but never matches, so changes under `.github/` were silently falling through to the catch-all owners rather than requiring SRE review. Use a root-anchored `/.github/` instead. Also replace the individually named catch-all owners with the Middleware team. Three of the four no longer resolve, which left a single valid reviewer on every path. --- .github/CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index eb444d6a..2367a17e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,2 @@ -* @VictorVicente @polymath-eric @prashantasdeveloper @sansan -./github @PolymeshAssociation/SRE +* @PolymeshAssociation/middleware +/.github/ @PolymeshAssociation/SRE