diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 32edffbe..a6b1330c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,15 +1,15 @@ name: CI on: + push: + branches: [main] + pull_request: + branches: [main] workflow_dispatch: -defaults: - run: - working-directory: anythingmcp - jobs: backend: - name: Backend (lint, test, build) + name: Backend (lint, typecheck, test, build) runs-on: ubuntu-latest services: @@ -29,8 +29,10 @@ jobs: env: DATABASE_URL: postgresql://amcp:testpassword@localhost:5432/anythingmcp_test - JWT_SECRET: ci-test-jwt-secret-at-least-32-chars - ENCRYPTION_KEY: ci-test-encryption-key-32-chars! + # CI-only secrets — long enough to pass our boot validation, never used + # outside the test database. + JWT_SECRET: ci-test-jwt-secret-at-least-32-chars-aaaaaa + ENCRYPTION_KEY: ci-test-encryption-key-32-chars-aaaaaaaa steps: - uses: actions/checkout@v4 @@ -39,29 +41,36 @@ jobs: with: node-version: 22 cache: npm - cache-dependency-path: anythingmcp/package-lock.json - name: Install dependencies run: npm ci - name: Generate Prisma client run: npx prisma generate - working-directory: anythingmcp/packages/backend + working-directory: packages/backend - name: Run Prisma migrations run: npx prisma migrate deploy - working-directory: anythingmcp/packages/backend + working-directory: packages/backend + + - name: Lint + run: npm run lint + working-directory: packages/backend + + - name: Type-check + run: npx tsc --noEmit -p tsconfig.json + working-directory: packages/backend - name: Run tests run: npm test - working-directory: anythingmcp/packages/backend + working-directory: packages/backend - name: Build backend run: npm run build - working-directory: anythingmcp/packages/backend + working-directory: packages/backend frontend: - name: Frontend (build) + name: Frontend (lint, typecheck, build) runs-on: ubuntu-latest steps: @@ -71,14 +80,21 @@ jobs: with: node-version: 22 cache: npm - cache-dependency-path: anythingmcp/package-lock.json - name: Install dependencies run: npm ci + - name: Lint + run: npm run lint + working-directory: packages/frontend + + - name: Type-check + run: npx tsc --noEmit -p tsconfig.json + working-directory: packages/frontend + - name: Build frontend run: npm run build - working-directory: anythingmcp/packages/frontend + working-directory: packages/frontend env: NEXT_PUBLIC_API_URL: http://localhost:4000 @@ -93,4 +109,3 @@ jobs: - name: Build unified image run: docker build -t anythingmcp:ci . - working-directory: anythingmcp diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..510c0c74 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,41 @@ +name: CodeQL + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + # Weekly Monday 06:00 UTC, in case dependencies introduce new advisories. + - cron: '0 6 * * 1' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [javascript-typescript] + + steps: + - uses: actions/checkout@v4 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + queries: security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{ matrix.language }}" diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml new file mode 100644 index 00000000..bb44a7bb --- /dev/null +++ b/.github/workflows/trivy.yml @@ -0,0 +1,77 @@ +name: Trivy + +on: + push: + branches: [main] + pull_request: + branches: [main] + schedule: + # Daily — new CVEs land in the Trivy DB constantly. + - cron: '0 5 * * *' + +jobs: + filesystem: + name: Filesystem scan + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + + steps: + - uses: actions/checkout@v4 + + - name: Run Trivy filesystem scan + uses: aquasecurity/trivy-action@master + with: + scan-type: fs + scan-ref: . + format: sarif + output: trivy-fs.sarif + ignore-unfixed: true + severity: CRITICAL,HIGH + # Lockfiles + Dockerfile + IaC. Skips secrets to avoid false positives + # on test fixtures; we already enforce required-secret validation at + # boot. + scanners: vuln,misconfig + skip-dirs: node_modules,.next,dist + env: + TRIVY_DISABLE_VEX_NOTICE: "true" + + - name: Upload Trivy SARIF + if: always() + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: trivy-fs.sarif + category: trivy-fs + + image: + name: Docker image scan + runs-on: ubuntu-latest + if: github.event_name != 'pull_request' + permissions: + contents: read + security-events: write + + steps: + - uses: actions/checkout@v4 + + - name: Build image + run: docker build -t anythingmcp:scan . + + - name: Run Trivy image scan + uses: aquasecurity/trivy-action@master + with: + image-ref: anythingmcp:scan + format: sarif + output: trivy-image.sarif + ignore-unfixed: true + severity: CRITICAL,HIGH + env: + TRIVY_DISABLE_VEX_NOTICE: "true" + + - name: Upload Trivy image SARIF + if: always() + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: trivy-image.sarif + category: trivy-image diff --git a/package-lock.json b/package-lock.json index 333e4443..a8675af0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1171,9 +1171,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", - "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { @@ -1184,7 +1184,7 @@ "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", - "minimatch": "^3.1.3", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -5870,20 +5870,20 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.56.1.tgz", - "integrity": "sha512-Jz9ZztpB37dNC+HU2HI28Bs9QXpzCz+y/twHOwhyrIRdbuVDxSytJNDl6z/aAKlaRIwC7y8wJdkBv7FxYGgi0A==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", + "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/type-utils": "8.56.1", - "@typescript-eslint/utils": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/type-utils": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5893,9 +5893,9 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.56.1", + "@typescript-eslint/parser": "^8.59.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { @@ -5909,17 +5909,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.56.1.tgz", - "integrity": "sha512-klQbnPAAiGYFyI02+znpBRLyjL4/BrBd0nyWkdC0s/6xFLkXYQ8OoRrSkqacS1ddVxf/LDyODIKbQ5TgKAf/Fg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", + "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3" }, "engines": { @@ -5931,18 +5931,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.56.1.tgz", - "integrity": "sha512-TAdqQTzHNNvlVFfR+hu2PDJrURiwKsUvxFn1M0h95BB8ah5jejas08jUWG4dBA68jDMI988IvtfdAI53JzEHOQ==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", + "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.56.1", - "@typescript-eslint/types": "^8.56.1", + "@typescript-eslint/tsconfig-utils": "^8.59.1", + "@typescript-eslint/types": "^8.59.1", "debug": "^4.4.3" }, "engines": { @@ -5953,18 +5953,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.56.1.tgz", - "integrity": "sha512-YAi4VDKcIZp0O4tz/haYKhmIDZFEUPOreKbfdAN3SzUDMcPhJ8QI99xQXqX+HoUVq8cs85eRKnD+rne2UAnj2w==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1" + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5975,9 +5975,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.56.1.tgz", - "integrity": "sha512-qOtCYzKEeyr3aR9f28mPJqBty7+DBqsdd63eO0yyDwc6vgThj2UjWfJIcsFeSucYydqcuudMOprZ+x1SpF3ZuQ==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", + "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", "dev": true, "license": "MIT", "engines": { @@ -5988,21 +5988,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.56.1.tgz", - "integrity": "sha512-yB/7dxi7MgTtGhZdaHCemf7PuwrHMenHjmzgUW1aJpO+bBU43OycnM3Wn+DdvDO/8zzA9HlhaJ0AUGuvri4oGg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", + "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/utils": "8.56.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1", "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6013,13 +6013,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", "dev": true, "license": "MIT", "engines": { @@ -6031,21 +6031,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.56.1.tgz", - "integrity": "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.56.1", - "@typescript-eslint/tsconfig-utils": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/visitor-keys": "8.56.1", + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6055,7 +6055,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { @@ -6082,13 +6082,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -6111,16 +6111,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.56.1.tgz", - "integrity": "sha512-HPAVNIME3tABJ61siYlHzSWCGtOoeP2RTIaHXFMPqjrQKCGB9OgUVdiNgH7TJS2JNIQ5qQ4RsAUDuGaGme/KOA==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", + "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.56.1", - "@typescript-eslint/types": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1" + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6131,17 +6131,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.56.1.tgz", - "integrity": "sha512-KiROIzYdEV85YygXw6BI/Dx4fnBlFQu6Mq4QE4MOH9fFnhohw6wX/OAvDY2/C+ut0I3RSPKenvZJIVYqJNkhEw==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.56.1", + "@typescript-eslint/types": "8.59.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -18371,9 +18371,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -18749,16 +18749,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.56.1.tgz", - "integrity": "sha512-U4lM6pjmBX7J5wk4szltF7I1cGBHXZopnAXCMXb3+fZ3B/0Z3hq3wS/CCUB2NZBNAExK92mCU2tEohWuwVMsDQ==", + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.1.tgz", + "integrity": "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.56.1", - "@typescript-eslint/parser": "8.56.1", - "@typescript-eslint/typescript-estree": "8.56.1", - "@typescript-eslint/utils": "8.56.1" + "@typescript-eslint/eslint-plugin": "8.59.1", + "@typescript-eslint/parser": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -18769,7 +18769,7 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/uglify-js": { @@ -19840,6 +19840,7 @@ "zod": "^4.3.6" }, "devDependencies": { + "@eslint/js": "^9.39.3", "@nestjs/cli": "^11.0.0", "@nestjs/schematics": "^11.0.0", "@nestjs/testing": "^11.0.1", @@ -19862,7 +19863,8 @@ "ts-loader": "^9.5.2", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "typescript": "^5.7.3" + "typescript": "^5.7.3", + "typescript-eslint": "^8.59.1" } }, "packages/backend/node_modules/@types/node": { @@ -19908,6 +19910,7 @@ "tailwind-merge": "^3.0.0" }, "devDependencies": { + "@eslint/eslintrc": "^3.3.5", "@types/node": "^22.10.7", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", diff --git a/packages/backend/eslint.config.mjs b/packages/backend/eslint.config.mjs new file mode 100644 index 00000000..371e03cd --- /dev/null +++ b/packages/backend/eslint.config.mjs @@ -0,0 +1,25 @@ +// Minimal flat ESLint config so `npm run lint` runs in CI. +// We deliberately keep the rule set small: this codebase has historic `any` +// usage that the Sprint 2/3 refactors will tighten progressively. The CI +// gate today is "no parse errors, no clearly broken code". +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { ignores: ['dist/**', 'node_modules/**', 'src/generated/**'] }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + '@typescript-eslint/no-empty-object-type': 'off', + '@typescript-eslint/no-unsafe-function-type': 'off', + '@typescript-eslint/no-require-imports': 'off', + 'no-empty': ['warn', { allowEmptyCatch: true }], + }, + }, +); diff --git a/packages/backend/package.json b/packages/backend/package.json index 184d2228..3a91d82c 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -60,6 +60,7 @@ "zod": "^4.3.6" }, "devDependencies": { + "@eslint/js": "^9.39.3", "@nestjs/cli": "^11.0.0", "@nestjs/schematics": "^11.0.0", "@nestjs/testing": "^11.0.1", @@ -82,7 +83,8 @@ "ts-loader": "^9.5.2", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "typescript": "^5.7.3" + "typescript": "^5.7.3", + "typescript-eslint": "^8.59.1" }, "jest": { "moduleFileExtensions": [ diff --git a/packages/backend/src/connectors/parsers/curl.parser.ts b/packages/backend/src/connectors/parsers/curl.parser.ts index 256fa58f..35578402 100644 --- a/packages/backend/src/connectors/parsers/curl.parser.ts +++ b/packages/backend/src/connectors/parsers/curl.parser.ts @@ -42,7 +42,7 @@ export class CurlParser { private parseSingleCurl(command: string): ParsedTool | null { // Remove leading "curl" keyword - let cmd = command.replace(/^\s*curl\s+/i, '').trim(); + const cmd = command.replace(/^\s*curl\s+/i, '').trim(); let method = 'GET'; const headers: Record = {}; @@ -284,7 +284,7 @@ export class CurlParser { const basePath = `${parsed.protocol}//${parsed.host}`; // Restore variables in path - let path = parsed.pathname.replace(/PLACEHOLDER_([a-zA-Z0-9_]+)/g, '{{$1}}'); + const path = parsed.pathname.replace(/PLACEHOLDER_([a-zA-Z0-9_]+)/g, '{{$1}}'); // Parse query params parsed.searchParams.forEach((value, key) => { @@ -299,7 +299,7 @@ export class CurlParser { const pathStart = url.indexOf('/', url.indexOf('//') + 2); const queryStart = url.indexOf('?'); - let path = pathStart >= 0 + const path = pathStart >= 0 ? (queryStart >= 0 ? url.substring(pathStart, queryStart) : url.substring(pathStart)) : '/'; diff --git a/packages/backend/src/connectors/parsers/openapi.parser.ts b/packages/backend/src/connectors/parsers/openapi.parser.ts index 7b9baa09..cea99838 100644 --- a/packages/backend/src/connectors/parsers/openapi.parser.ts +++ b/packages/backend/src/connectors/parsers/openapi.parser.ts @@ -1,5 +1,5 @@ import { Injectable, Logger } from '@nestjs/common'; -// eslint-disable-next-line @typescript-eslint/no-var-requires + const SwaggerParser = require('swagger-parser'); import axios from 'axios'; import { assertSafeOutboundUrl } from '../../common/ssrf.util'; diff --git a/packages/backend/src/main.ts b/packages/backend/src/main.ts index 6ff0ac16..c3304adb 100644 --- a/packages/backend/src/main.ts +++ b/packages/backend/src/main.ts @@ -101,7 +101,30 @@ async function bootstrap() { swaggerOptions: { persistAuthorization: true }, }); - await app.listen(port); + // Drain in-flight requests and close DB / Redis connections cleanly when + // the platform sends SIGTERM (k8s rolling deploy, docker stop, Railway + // restart). Without this, long-running tool invocations would be killed + // mid-flight and the audit log entry never written. + app.enableShutdownHooks(); + + const server = await app.listen(port); + server.keepAliveTimeout = 65_000; + server.headersTimeout = 66_000; + + for (const signal of ['SIGTERM', 'SIGINT'] as const) { + process.once(signal, async () => { + logger.log(`Received ${signal}, shutting down gracefully...`); + try { + await app.close(); + logger.log('Shutdown complete.'); + process.exit(0); + } catch (err) { + logger.error(`Error during shutdown: ${err}`); + process.exit(1); + } + }); + } + logger.log(`AnythingMCP backend running on: http://localhost:${port}`); logger.log(`Swagger docs: http://localhost:${port}/api/docs`); logger.log(`MCP endpoint (global): http://localhost:${port}/mcp`); diff --git a/packages/backend/src/organizations/organizations.service.ts b/packages/backend/src/organizations/organizations.service.ts index 775bdf87..bb2e50c6 100644 --- a/packages/backend/src/organizations/organizations.service.ts +++ b/packages/backend/src/organizations/organizations.service.ts @@ -141,7 +141,7 @@ export class OrganizationsService { }); let autoCreated = false; - let finalUserId = userId; + const finalUserId = userId; const result = await this.prisma.$transaction(async (tx) => { // Migrate orphans pre-cascade diff --git a/packages/frontend/eslint.config.mjs b/packages/frontend/eslint.config.mjs new file mode 100644 index 00000000..4f441465 --- /dev/null +++ b/packages/frontend/eslint.config.mjs @@ -0,0 +1,26 @@ +// Flat ESLint config — eslint-config-next v16+ ships flat configs directly. +import nextCoreWebVitals from 'eslint-config-next/core-web-vitals'; +import nextTypescript from 'eslint-config-next/typescript'; + +export default [ + { ignores: ['.next/**', 'node_modules/**', 'next-env.d.ts'] }, + ...(Array.isArray(nextCoreWebVitals) ? nextCoreWebVitals : [nextCoreWebVitals]), + ...(Array.isArray(nextTypescript) ? nextTypescript : [nextTypescript]), + { + rules: { + // The dashboard has historical `any` and bare img tags; these will be + // tightened in the dedicated frontend sprint. + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': [ + 'warn', + { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }, + ], + '@next/next/no-img-element': 'off', + 'react/no-unescaped-entities': 'off', + 'react-hooks/exhaustive-deps': 'warn', + // Pre-existing patterns that the dedicated frontend sprint will clean up. + 'react-hooks/set-state-in-effect': 'warn', + '@next/next/no-html-link-for-pages': 'warn', + }, + }, +]; diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 59445a80..22e0e82a 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -32,6 +32,7 @@ "tailwind-merge": "^3.0.0" }, "devDependencies": { + "@eslint/eslintrc": "^3.3.5", "@types/node": "^22.10.7", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0",