diff --git a/packages/scanner-core/package.json b/packages/scanner-core/package.json index 41a3b94..d7dd214 100644 --- a/packages/scanner-core/package.json +++ b/packages/scanner-core/package.json @@ -20,6 +20,7 @@ }, "dependencies": { "@veridion/logger": "workspace:*", + "@veridion/plugin-unchecked-return": "workspace:*", "@veridion/scanner-types": "workspace:*", "@veridion/shared": "workspace:*" }, diff --git a/packages/scanner-core/src/plugin-registry.ts b/packages/scanner-core/src/plugin-registry.ts index 710b1bd..c13f486 100644 --- a/packages/scanner-core/src/plugin-registry.ts +++ b/packages/scanner-core/src/plugin-registry.ts @@ -1,5 +1,23 @@ import { logger } from '@veridion/logger'; import type { AnalysisContext, IRulePlugin, PluginMetadata } from '@veridion/scanner-types'; +import { UncheckedReturnPlugin } from '@veridion/plugin-unchecked-return'; + +/** + * Instantiate the plugins that ship with the scanner. New built-in plugins + * should be added here so they are picked up by {@link createDefaultRegistry}. + */ +export function createBuiltinPlugins(): IRulePlugin[] { + return [new UncheckedReturnPlugin()]; +} + +/** + * Create a registry pre-populated with all built-in plugins. + */ +export function createDefaultRegistry(): PluginRegistry { + const registry = new PluginRegistry(); + registry.registerAll(createBuiltinPlugins()); + return registry; +} export class PluginRegistry { private plugins = new Map(); diff --git a/plugins/unchecked-return/.eslintrc.js b/plugins/unchecked-return/.eslintrc.js new file mode 100644 index 0000000..7c0ed9b --- /dev/null +++ b/plugins/unchecked-return/.eslintrc.js @@ -0,0 +1,4 @@ +const { createConfig } = require('@veridion/eslint-config/base'); + +/** @type {import('eslint').Linter.Config} */ +module.exports = createConfig(__dirname); diff --git a/plugins/unchecked-return/package.json b/plugins/unchecked-return/package.json new file mode 100644 index 0000000..7347460 --- /dev/null +++ b/plugins/unchecked-return/package.json @@ -0,0 +1,28 @@ +{ + "name": "@veridion/plugin-unchecked-return", + "version": "0.1.0", + "private": true, + "description": "Unchecked return value detection plugin for Veridion scanner", + "main": "./src/index.ts", + "types": "./src/index.ts", + "scripts": { + "build": "tsc", + "typecheck": "tsc --noEmit", + "lint": "eslint src/ --max-warnings 0", + "test": "vitest run", + "test:watch": "vitest", + "clean": "rm -rf dist" + }, + "dependencies": { + "@veridion/scanner-types": "workspace:*", + "@veridion/shared": "workspace:*", + "@veridion/logger": "workspace:*" + }, + "devDependencies": { + "@veridion/eslint-config": "workspace:*", + "@veridion/tsconfig": "workspace:*", + "eslint": "^8.57.0", + "typescript": "^5.4.5", + "vitest": "^1.6.0" + } +} diff --git a/plugins/unchecked-return/src/index.test.ts b/plugins/unchecked-return/src/index.test.ts new file mode 100644 index 0000000..6f69c52 --- /dev/null +++ b/plugins/unchecked-return/src/index.test.ts @@ -0,0 +1,162 @@ +import { describe, expect, it } from 'vitest'; + +import { UncheckedReturnPlugin } from './index'; + +const ctx = (contractName: string, sourceCode: string) => ({ + contractName, + sourceCode, + chain: 'ethereum', + language: 'solidity', + compilerVersion: '0.8.19', + metadata: {}, +}); + +describe('UncheckedReturnPlugin', () => { + const plugin = new UncheckedReturnPlugin(); + + it('should have correct metadata', () => { + expect(plugin.metadata.id).toBe('unchecked-return'); + expect(plugin.metadata.severity).toBe('MEDIUM'); + expect(plugin.metadata.category).toBe('UNCHECKED_RETURN'); + }); + + it('should support solidity on ethereum', () => { + expect(plugin.supportsContext(ctx('Test', ''))).toBe(true); + }); + + it('should not support vyper or non-configured chains', () => { + expect( + plugin.supportsContext({ ...ctx('Test', ''), language: 'vyper' }), + ).toBe(false); + expect( + plugin.supportsContext({ ...ctx('Test', ''), chain: 'solana' }), + ).toBe(false); + }); + + it('should detect an unchecked .send() return value', async () => { + const code = ` +contract Vulnerable { + function pay(address payable to, uint256 amount) public { + to.send(amount); + } +}`; + const findings = await plugin.analyze(ctx('Vulnerable', code)); + expect(findings.length).toBe(1); + expect(findings[0]?.pluginId).toBe('unchecked-return'); + expect(findings[0]?.title).toContain('send'); + }); + + it('should detect an unchecked .call() return value', async () => { + const code = ` +contract Vulnerable { + function pay(address to, uint256 amount) public { + to.call{value: amount}(""); + } +}`; + const findings = await plugin.analyze(ctx('Vulnerable', code)); + expect(findings.length).toBe(1); + expect(findings[0]?.title).toContain('call'); + }); + + it('should detect an unchecked .delegatecall() return value', async () => { + const code = ` +contract Vulnerable { + function forward(address impl, bytes calldata data) public { + impl.delegatecall(data); + } +}`; + const findings = await plugin.analyze(ctx('Vulnerable', code)); + expect(findings.length).toBe(1); + expect(findings[0]?.title).toContain('delegatecall'); + }); + + it('should NOT flag a send() checked with require', async () => { + const code = ` +contract Safe { + function pay(address payable to, uint256 amount) public { + require(to.send(amount), "send failed"); + } +}`; + const findings = await plugin.analyze(ctx('Safe', code)); + expect(findings.length).toBe(0); + }); + + it('should NOT flag a call() whose result is captured and checked', async () => { + const code = ` +contract Safe { + function pay(address to, uint256 amount) public { + (bool success, ) = to.call{value: amount}(""); + require(success, "call failed"); + } +}`; + const findings = await plugin.analyze(ctx('Safe', code)); + expect(findings.length).toBe(0); + }); + + it('should NOT flag a send() assigned to a bool variable', async () => { + const code = ` +contract Safe { + function pay(address payable to, uint256 amount) public { + bool ok = to.send(amount); + require(ok); + } +}`; + const findings = await plugin.analyze(ctx('Safe', code)); + expect(findings.length).toBe(0); + }); + + it('should NOT flag a call() used directly in an if condition', async () => { + const code = ` +contract Safe { + function pay(address to) public { + if (to.call("")) { + revert(); + } + } +}`; + const findings = await plugin.analyze(ctx('Safe', code)); + expect(findings.length).toBe(0); + }); + + it('should ignore matches inside comments', async () => { + const code = ` +contract Documented { + // to.send(amount); is unsafe, do not do this + function pay() public {} +}`; + const findings = await plugin.analyze(ctx('Documented', code)); + expect(findings.length).toBe(0); + }); + + it('should flag multiple distinct unchecked calls', async () => { + const code = ` +contract Vulnerable { + function a(address payable to, uint256 amount) public { + to.send(amount); + to.call{value: amount}(""); + } +}`; + const findings = await plugin.analyze(ctx('Vulnerable', code)); + expect(findings.length).toBe(2); + }); + + it('should provide a require(success) fix recommendation', async () => { + const code = ` +contract Vulnerable { + function pay(address to) public { + to.call(""); + } +}`; + const findings = await plugin.analyze(ctx('Vulnerable', code)); + const finding = findings[0]; + expect(finding).toBeDefined(); + if (!finding) return; + const fix = plugin.getFixRecommendation(finding); + expect(fix).toContain('require(success)'); + expect(fix).toContain('Vulnerable.sol'); + }); + + it('should initialize without error', async () => { + await expect(plugin.initialize()).resolves.toBeUndefined(); + }); +}); diff --git a/plugins/unchecked-return/src/index.ts b/plugins/unchecked-return/src/index.ts new file mode 100644 index 0000000..94d4bac --- /dev/null +++ b/plugins/unchecked-return/src/index.ts @@ -0,0 +1,147 @@ +import type { + AnalysisContext, + FindingResult, + IRulePlugin, + PluginMetadata, +} from '@veridion/scanner-types'; +import { FindingSeverity } from '@veridion/shared'; + +const metadata: PluginMetadata = { + id: 'unchecked-return', + name: 'Unchecked Return Value Detector', + version: '1.0.0', + description: + 'Detects low-level calls (address.call, address.send, address.delegatecall) whose boolean return value is ignored. Silently ignoring a failed call can leave the contract in an inconsistent state.', + severity: FindingSeverity.MEDIUM, + category: 'UNCHECKED_RETURN', + chains: ['ethereum', 'polygon', 'bsc', 'avalanche', 'arbitrum', 'optimism'], + languages: ['solidity'], + tags: ['unchecked-return', 'low-level-call', 'send', 'call', 'delegatecall', 'error-handling'], + author: 'Veridion', + references: [ + 'https://swcregistry.io/docs/SWC-104', + 'https://consensys.github.io/smart-contract-best-practices/development-recommendations/general/external-calls/', + ], +}; + +interface CallPattern { + readonly kind: 'call' | 'send' | 'delegatecall'; + readonly regex: RegExp; +} + +const CALL_PATTERNS: readonly CallPattern[] = [ + // .call(...) and .call{value: ...}(...) + { kind: 'call', regex: /\.call\s*[({]/ }, + // .delegatecall(...) + { kind: 'delegatecall', regex: /\.delegatecall\s*\(/ }, + // .send(...) + { kind: 'send', regex: /\.send\s*\(/ }, +]; + +export class UncheckedReturnPlugin implements IRulePlugin { + readonly metadata = metadata; + + async initialize(_config?: Record): Promise { + // noop + } + + // eslint-disable-next-line @typescript-eslint/require-await + async analyze(context: AnalysisContext): Promise { + const findings: FindingResult[] = []; + const lines = context.sourceCode.split('\n'); + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (!line || this.isComment(line)) continue; + + for (const pattern of CALL_PATTERNS) { + const match = pattern.regex.exec(line); + if (!match) continue; + // Only report the first matching kind on a given line to avoid duplicates + // (e.g. ".call" also appearing inside a longer expression). + if (this.isReturnChecked(line, match.index)) break; + + findings.push({ + pluginId: this.metadata.id, + title: `Unchecked return value from low-level ${pattern.kind}()`, + description: + `The return value of \`${pattern.kind}()\` is not checked. Low-level calls do not ` + + 'revert on failure; they return a boolean indicating success. Ignoring it means a ' + + 'failed transfer or call is treated as a success, potentially corrupting contract state.', + severity: this.metadata.severity, + filePath: `${context.contractName}.sol`, + lineStart: i + 1, + lineEnd: i + 1, + codeSnippet: line.trim(), + recommendation: + 'Capture the boolean result and validate it, e.g. `(bool success, ) = target.call(...); ' + + 'require(success, "call failed");`.', + confidence: 0.8, + references: this.metadata.references ?? [], + }); + break; + } + } + + return findings; + } + + getFixRecommendation(finding: FindingResult): string { + return `To fix the unchecked return value at ${finding.filePath}:${finding.lineStart}: + +Low-level calls (\`call\`, \`send\`, \`delegatecall\`) return a boolean success flag instead of +reverting. Always capture and check it: + +\`\`\`solidity +// Unsafe: return value ignored +recipient.send(amount); +recipient.call{value: amount}(""); + +// Safe: return value checked +require(recipient.send(amount), "send failed"); + +(bool success, ) = recipient.call{value: amount}(""); +require(success, "call failed"); +\`\`\` + +Prefer \`call\` over \`send\`/\`transfer\` for value transfers, and always guard the result with +\`require(success)\`. For plain Ether transfers that must revert on failure, \`transfer()\` is also +acceptable because it reverts automatically.`; + } + + supportsContext(context: AnalysisContext): boolean { + return ( + this.metadata.chains.includes(context.chain) && + this.metadata.languages.includes(context.language) + ); + } + + /** + * Determine whether the boolean return value of a low-level call is consumed. + * We inspect the code preceding the call on the same line: an assignment, + * destructuring, or use inside a control/condition expression counts as checked. + */ + private isReturnChecked(line: string, callIndex: number): boolean { + const before = line.slice(0, callIndex); + + // Assigned to a variable or destructured: `bool ok = a.send(...)`, `(bool ok, ) = a.call(...)` + if (/[=]\s*$/.test(before) || /\)\s*=\s*$/.test(before) || /=\s*[\w.]*$/.test(before)) { + return true; + } + + // Used inside require/assert/if/while/return or a boolean expression. + if (/\b(require|assert|if|while|return)\s*\(?[^;]*$/.test(before)) { + return true; + } + if (/(&&|\|\||!)\s*[\w.]*$/.test(before)) { + return true; + } + + return false; + } + + private isComment(line: string): boolean { + const trimmed = line.trim(); + return trimmed.startsWith('//') || trimmed.startsWith('*') || trimmed.startsWith('/*'); + } +} diff --git a/plugins/unchecked-return/tsconfig.json b/plugins/unchecked-return/tsconfig.json new file mode 100644 index 0000000..54f3e6d --- /dev/null +++ b/plugins/unchecked-return/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "@veridion/tsconfig/base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", "dist"] +} diff --git a/plugins/unchecked-return/vitest.config.ts b/plugins/unchecked-return/vitest.config.ts new file mode 100644 index 0000000..7dd1325 --- /dev/null +++ b/plugins/unchecked-return/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['src/**/*.test.ts'], + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9f79a6f..ecf38e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,7 +53,7 @@ importers: version: 5.23.0 '@nestjs/bullmq': specifier: ^10.1.1 - version: 10.2.3(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2))(bullmq@5.80.5) + version: 10.2.3(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(bullmq@5.80.5) '@nestjs/common': specifier: ^10.3.9 version: 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -74,10 +74,10 @@ importers: version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22) '@nestjs/swagger': specifier: ^7.3.1 - version: 7.4.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) + version: 7.4.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2) '@nestjs/throttler': specifier: ^6.5.0 - version: 6.5.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2) + version: 6.5.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(reflect-metadata@0.2.2) '@prisma/client': specifier: ^5.14.0 version: 5.22.0(prisma@5.22.0) @@ -156,7 +156,7 @@ importers: version: 10.2.3(chokidar@4.0.3)(typescript@5.9.3) '@nestjs/testing': specifier: ^10.3.9 - version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)) + version: 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(@nestjs/platform-express@10.4.22) '@types/bcryptjs': specifier: ^2.4.6 version: 2.4.6 @@ -556,6 +556,9 @@ importers: '@veridion/logger': specifier: workspace:* version: link:../logger + '@veridion/plugin-unchecked-return': + specifier: workspace:* + version: link:../../plugins/unchecked-return '@veridion/scanner-types': specifier: workspace:* version: link:../scanner-types @@ -854,6 +857,34 @@ importers: specifier: ^1.6.0 version: 1.6.1(@types/node@20.19.43)(jsdom@24.1.3)(terser@5.50.0) + plugins/unchecked-return: + dependencies: + '@veridion/logger': + specifier: workspace:* + version: link:../../packages/logger + '@veridion/scanner-types': + specifier: workspace:* + version: link:../../packages/scanner-types + '@veridion/shared': + specifier: workspace:* + version: link:../../packages/shared + devDependencies: + '@veridion/eslint-config': + specifier: workspace:* + version: link:../../packages/config/eslint + '@veridion/tsconfig': + specifier: workspace:* + version: link:../../packages/config/tsconfig + eslint: + specifier: ^8.57.0 + version: 8.57.1 + typescript: + specifier: ^5.4.5 + version: 5.9.3 + vitest: + specifier: ^1.6.0 + version: 1.6.1(@types/node@20.19.43)(jsdom@24.1.3)(terser@5.50.0) + packages: '@alloc/quick-lru@5.2.0': @@ -5216,10 +5247,6 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@3.15.0: - resolution: {integrity: sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==} - hasBin: true - js-yaml@3.15.1: resolution: {integrity: sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag==} hasBin: true @@ -5228,10 +5255,6 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - js-yaml@4.3.0: - resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} - hasBin: true - js-yaml@4.3.1: resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==} hasBin: true @@ -7727,7 +7750,7 @@ snapshots: '@changesets/parse@0.4.3': dependencies: '@changesets/types': 6.1.0 - js-yaml: 4.3.0 + js-yaml: 4.3.1 '@changesets/pre@2.0.2': dependencies: @@ -8077,7 +8100,7 @@ snapshots: globals: 13.24.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.3.0 + js-yaml: 4.3.1 minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: @@ -8557,15 +8580,15 @@ snapshots: '@tybys/wasm-util': 0.10.3 optional: true - '@nestjs/bull-shared@10.2.3(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2))': + '@nestjs/bull-shared@10.2.3(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)': dependencies: '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2) tslib: 2.8.1 - '@nestjs/bullmq@10.2.3(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2))(bullmq@5.80.5)': + '@nestjs/bullmq@10.2.3(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(bullmq@5.80.5)': dependencies: - '@nestjs/bull-shared': 10.2.3(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2)) + '@nestjs/bull-shared': 10.2.3(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22) '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2) bullmq: 5.80.5 @@ -8700,7 +8723,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@nestjs/swagger@7.4.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2))(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)': + '@nestjs/swagger@7.4.2(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)': dependencies: '@microsoft/tsdoc': 0.15.1 '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -8715,7 +8738,7 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.4 - '@nestjs/testing@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22))': + '@nestjs/testing@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(@nestjs/platform-express@10.4.22)': dependencies: '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -8723,7 +8746,7 @@ snapshots: optionalDependencies: '@nestjs/platform-express': 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22) - '@nestjs/throttler@6.5.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2))(reflect-metadata@0.2.2)': + '@nestjs/throttler@6.5.0(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@10.4.22)(reflect-metadata@0.2.2)': dependencies: '@nestjs/common': 10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 10.4.22(@nestjs/common@10.4.22(class-transformer@0.5.1)(class-validator@0.14.4)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@10.4.22)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -9938,13 +9961,13 @@ snapshots: dependencies: acorn: 8.18.0 - acorn-jsx@5.3.2(acorn@8.17.0): + acorn-jsx@5.3.2(acorn@8.18.0): dependencies: - acorn: 8.17.0 + acorn: 8.18.0 acorn-walk@8.3.5: dependencies: - acorn: 8.17.0 + acorn: 8.18.0 acorn@8.17.0: {} @@ -10559,7 +10582,7 @@ snapshots: dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 - js-yaml: 4.3.0 + js-yaml: 4.3.1 parse-json: 5.2.0 optionalDependencies: typescript: 5.9.3 @@ -10999,7 +11022,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.14.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5(eslint-plugin-import@2.32.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.14.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -11021,7 +11044,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.14.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5(eslint-plugin-import@2.32.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.14.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@4.4.5)(eslint@8.57.1) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -11095,7 +11118,7 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 - js-yaml: 4.3.0 + js-yaml: 4.3.1 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 lodash.merge: 4.6.2 @@ -11109,8 +11132,8 @@ snapshots: espree@9.6.1: dependencies: - acorn: 8.17.0 - acorn-jsx: 5.3.2(acorn@8.17.0) + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -12231,11 +12254,6 @@ snapshots: js-tokens@9.0.1: {} - js-yaml@3.15.0: - dependencies: - argparse: 1.0.10 - esprima: 4.0.1 - js-yaml@3.15.1: dependencies: argparse: 1.0.10 @@ -12245,10 +12263,6 @@ snapshots: dependencies: argparse: 2.0.1 - js-yaml@4.3.0: - dependencies: - argparse: 2.0.1 - js-yaml@4.3.1: dependencies: argparse: 2.0.1 @@ -13218,7 +13232,7 @@ snapshots: read-yaml-file@1.1.0: dependencies: graceful-fs: 4.2.11 - js-yaml: 3.15.0 + js-yaml: 3.15.1 pify: 4.0.1 strip-bom: 3.0.0