prr-kit 1.1.3 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +260 -235
- package/docs/assets/banner.svg +30 -248
- package/docs/assets/how-it-works.svg +87 -0
- package/package.json +60 -60
- package/src/core/agents/prr-master.agent.yaml +18 -7
- package/src/core/tasks/clear.md +140 -0
- package/src/core/tasks/help.md +15 -13
- package/src/core/workflows/clear/workflow.md +6 -0
- package/src/core/workflows/help/workflow.md +6 -0
- package/src/core/workflows/party-mode/steps/step-01-load-reviewers.md +35 -24
- package/src/core/workflows/party-mode/steps/step-02-discussion.md +45 -25
- package/src/core/workflows/party-mode/workflow.md +2 -2
- package/src/prr/agents/architecture-reviewer.agent.yaml +65 -45
- package/src/prr/agents/business-reviewer.agent.yaml +66 -0
- package/src/prr/agents/general-reviewer.agent.yaml +64 -48
- package/src/prr/agents/performance-reviewer.agent.yaml +65 -45
- package/src/prr/agents/security-reviewer.agent.yaml +67 -43
- package/src/prr/config-template.yaml +97 -0
- package/src/prr/data/stacks/actix.md +55 -0
- package/src/prr/data/stacks/alpine.md +47 -0
- package/src/prr/data/stacks/android.md +53 -0
- package/src/prr/data/stacks/angular.md +96 -0
- package/src/prr/data/stacks/ansible.md +55 -0
- package/src/prr/data/stacks/apollo.md +54 -0
- package/src/prr/data/stacks/astro.md +48 -0
- package/src/prr/data/stacks/aws-cdk.md +55 -0
- package/src/prr/data/stacks/axum.md +56 -0
- package/src/prr/data/stacks/babylonjs.md +55 -0
- package/src/prr/data/stacks/bash.md +53 -0
- package/src/prr/data/stacks/bevy.md +53 -0
- package/src/prr/data/stacks/bootstrap.md +52 -0
- package/src/prr/data/stacks/bun.md +55 -0
- package/src/prr/data/stacks/cpp.md +57 -0
- package/src/prr/data/stacks/csharp.md +95 -0
- package/src/prr/data/stacks/css.md +55 -0
- package/src/prr/data/stacks/cypress.md +53 -0
- package/src/prr/data/stacks/d3.md +53 -0
- package/src/prr/data/stacks/deno.md +49 -0
- package/src/prr/data/stacks/django.md +92 -0
- package/src/prr/data/stacks/docker.md +79 -0
- package/src/prr/data/stacks/drizzle.md +54 -0
- package/src/prr/data/stacks/dynamodb.md +55 -0
- package/src/prr/data/stacks/electron.md +44 -0
- package/src/prr/data/stacks/elixir.md +53 -0
- package/src/prr/data/stacks/expo.md +53 -0
- package/src/prr/data/stacks/expressjs.md +82 -0
- package/src/prr/data/stacks/fastapi.md +88 -0
- package/src/prr/data/stacks/fastify.md +60 -0
- package/src/prr/data/stacks/fiber.md +55 -0
- package/src/prr/data/stacks/firebase.md +43 -0
- package/src/prr/data/stacks/flask.md +46 -0
- package/src/prr/data/stacks/flutter.md +75 -0
- package/src/prr/data/stacks/gin.md +57 -0
- package/src/prr/data/stacks/github-actions.md +71 -0
- package/src/prr/data/stacks/go.md +88 -0
- package/src/prr/data/stacks/godot.md +56 -0
- package/src/prr/data/stacks/graphql.md +76 -0
- package/src/prr/data/stacks/grpc.md +56 -0
- package/src/prr/data/stacks/haskell.md +48 -0
- package/src/prr/data/stacks/helm.md +54 -0
- package/src/prr/data/stacks/hono.md +54 -0
- package/src/prr/data/stacks/htmx.md +38 -0
- package/src/prr/data/stacks/java.md +87 -0
- package/src/prr/data/stacks/jest-vitest.md +87 -0
- package/src/prr/data/stacks/jquery.md +50 -0
- package/src/prr/data/stacks/junit.md +53 -0
- package/src/prr/data/stacks/kotlin.md +89 -0
- package/src/prr/data/stacks/kubernetes.md +148 -0
- package/src/prr/data/stacks/langchain.md +56 -0
- package/src/prr/data/stacks/laravel.md +56 -0
- package/src/prr/data/stacks/libgdx.md +46 -0
- package/src/prr/data/stacks/lit.md +49 -0
- package/src/prr/data/stacks/love2d.md +51 -0
- package/src/prr/data/stacks/lua.md +51 -0
- package/src/prr/data/stacks/mobx.md +54 -0
- package/src/prr/data/stacks/mongodb.md +85 -0
- package/src/prr/data/stacks/monogame.md +51 -0
- package/src/prr/data/stacks/mysql.md +57 -0
- package/src/prr/data/stacks/nestjs.md +95 -0
- package/src/prr/data/stacks/nextjs.md +88 -0
- package/src/prr/data/stacks/nginx.md +55 -0
- package/src/prr/data/stacks/node.md +56 -0
- package/src/prr/data/stacks/nuxtjs.md +91 -0
- package/src/prr/data/stacks/openai-api.md +54 -0
- package/src/prr/data/stacks/opengl.md +54 -0
- package/src/prr/data/stacks/phaser.md +54 -0
- package/src/prr/data/stacks/phoenix.md +55 -0
- package/src/prr/data/stacks/php.md +56 -0
- package/src/prr/data/stacks/playwright.md +86 -0
- package/src/prr/data/stacks/postgresql.md +60 -0
- package/src/prr/data/stacks/prisma.md +81 -0
- package/src/prr/data/stacks/pygame.md +52 -0
- package/src/prr/data/stacks/pytest.md +53 -0
- package/src/prr/data/stacks/python.md +94 -0
- package/src/prr/data/stacks/pytorch.md +54 -0
- package/src/prr/data/stacks/qwik.md +50 -0
- package/src/prr/data/stacks/rails.md +48 -0
- package/src/prr/data/stacks/react-native.md +77 -0
- package/src/prr/data/stacks/react.md +104 -0
- package/src/prr/data/stacks/redis.md +76 -0
- package/src/prr/data/stacks/redux.md +107 -0
- package/src/prr/data/stacks/remix.md +51 -0
- package/src/prr/data/stacks/rust.md +88 -0
- package/src/prr/data/stacks/sass.md +51 -0
- package/src/prr/data/stacks/scala.md +50 -0
- package/src/prr/data/stacks/scikit-learn.md +53 -0
- package/src/prr/data/stacks/sequelize.md +54 -0
- package/src/prr/data/stacks/socket-io.md +54 -0
- package/src/prr/data/stacks/solidity.md +53 -0
- package/src/prr/data/stacks/solidjs.md +45 -0
- package/src/prr/data/stacks/spring-boot.md +92 -0
- package/src/prr/data/stacks/sql.md +85 -0
- package/src/prr/data/stacks/sqlite.md +55 -0
- package/src/prr/data/stacks/styled-components.md +51 -0
- package/src/prr/data/stacks/supabase.md +57 -0
- package/src/prr/data/stacks/svelte.md +77 -0
- package/src/prr/data/stacks/sveltekit.md +54 -0
- package/src/prr/data/stacks/swift.md +61 -0
- package/src/prr/data/stacks/tailwindcss.md +10 -0
- package/src/prr/data/stacks/tanstack-query.md +48 -0
- package/src/prr/data/stacks/tauri.md +52 -0
- package/src/prr/data/stacks/terraform.md +53 -0
- package/src/prr/data/stacks/three.md +53 -0
- package/src/prr/data/stacks/trpc.md +49 -0
- package/src/prr/data/stacks/typeorm.md +40 -0
- package/src/prr/data/stacks/typescript.md +83 -0
- package/src/prr/data/stacks/unity.md +61 -0
- package/src/prr/data/stacks/unreal.md +58 -0
- package/src/prr/data/stacks/vite.md +48 -0
- package/src/prr/data/stacks/vue3.md +95 -0
- package/src/prr/data/stacks/vulkan.md +53 -0
- package/src/prr/data/stacks/wasm.md +49 -0
- package/src/prr/data/stacks/webpack.md +48 -0
- package/src/prr/data/stacks/zig.md +51 -0
- package/src/prr/data/stacks/zustand.md +56 -0
- package/src/prr/workflows/1-discover/select-pr/steps/step-05-confirm.md +1 -0
- package/src/prr/workflows/1-discover/select-pr/workflow.md +1 -1
- package/src/prr/workflows/2-analyze/collect-pr-context/steps/step-01-analyze-files.md +334 -0
- package/src/prr/workflows/2-analyze/collect-pr-context/steps/step-02-collect-sources.md +451 -0
- package/src/prr/workflows/2-analyze/collect-pr-context/steps/step-03-build-knowledge-base.md +337 -0
- package/src/prr/workflows/2-analyze/collect-pr-context/workflow.md +123 -0
- package/src/prr/workflows/2-analyze/describe-pr/steps/step-02-classify.md +12 -6
- package/src/prr/workflows/2-analyze/describe-pr/steps/step-03-walkthrough.md +59 -1
- package/src/prr/workflows/3-review/architecture-review/checklist.md +4 -0
- package/src/prr/workflows/3-review/architecture-review/instructions.xml +32 -4
- package/src/prr/workflows/3-review/architecture-review/workflow.yaml +17 -18
- package/src/prr/workflows/3-review/business-review/checklist.md +27 -0
- package/src/prr/workflows/3-review/business-review/instructions.xml +153 -0
- package/src/prr/workflows/3-review/business-review/workflow.yaml +17 -0
- package/src/prr/workflows/3-review/general-review/checklist.md +5 -1
- package/src/prr/workflows/3-review/general-review/instructions.xml +39 -8
- package/src/prr/workflows/3-review/general-review/workflow.yaml +17 -18
- package/src/prr/workflows/3-review/performance-review/checklist.md +3 -1
- package/src/prr/workflows/3-review/performance-review/instructions.xml +10 -3
- package/src/prr/workflows/3-review/performance-review/workflow.yaml +17 -18
- package/src/prr/workflows/3-review/security-review/checklist.md +2 -1
- package/src/prr/workflows/3-review/security-review/instructions.xml +8 -3
- package/src/prr/workflows/3-review/security-review/workflow.yaml +18 -19
- package/src/prr/workflows/4-improve/improve-code/workflow.yaml +17 -18
- package/src/prr/workflows/6-report/generate-report/steps/step-01-collect.md +9 -2
- package/src/prr/workflows/6-report/generate-report/steps/step-02-organize.md +28 -7
- package/src/prr/workflows/6-report/generate-report/steps/step-03-write.md +6 -4
- package/src/prr/workflows/6-report/generate-report/templates/review-report.template.md +124 -78
- package/src/prr/workflows/6-report/post-comments/steps/step-01-format.md +104 -13
- package/src/prr/workflows/6-report/post-comments/steps/step-02-post.md +92 -21
- package/src/prr/workflows/6-report/post-comments/workflow.md +6 -0
- package/src/prr/workflows/quick/workflow.md +138 -32
- package/src/prr/workflows/0-setup/collect-project-context/steps/step-01-scan-configs.md +0 -106
- package/src/prr/workflows/0-setup/collect-project-context/steps/step-02-extract-rules.md +0 -131
- package/src/prr/workflows/0-setup/collect-project-context/steps/step-03-ask-context.md +0 -194
- package/src/prr/workflows/0-setup/collect-project-context/steps/step-04-save-context.md +0 -161
- package/src/prr/workflows/0-setup/collect-project-context/workflow.md +0 -58
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Docker — Stack-Specific Review Rules
|
|
2
|
+
|
|
3
|
+
> Applies to: GR · SR · PR · AR · BR
|
|
4
|
+
> Detection signals: `Dockerfile*`, `docker-compose*.yml`, `.dockerignore`, `FROM `, `RUN `, `COPY `, `EXPOSE`, `docker` in CI, `compose.yml`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Security
|
|
9
|
+
|
|
10
|
+
- **[CRITICAL]** Container running as `root` (default) → container escape = root on host. Add `USER nonroot` after creating non-root user.
|
|
11
|
+
- **[CRITICAL]** Secrets passed as `ENV` or `ARG` in Dockerfile → visible in image layers and `docker inspect`. Use Docker BuildKit secrets (`--secret`) or runtime env injection.
|
|
12
|
+
- **[CRITICAL]** `.env` file copied into image via `COPY . .` without `.dockerignore` → secrets in image layer.
|
|
13
|
+
- **[HIGH]** Base image tagged as `latest` → non-deterministic builds, unexpected CVEs from upstream updates. Use `node:20.11.1-alpine3.19`.
|
|
14
|
+
- **[HIGH]** Outdated base image with known CVEs → scan with `docker scout cves` or Trivy.
|
|
15
|
+
- **[HIGH]** `docker-compose.yml` with `privileged: true` without justification → container escapes host namespaces.
|
|
16
|
+
- **[HIGH]** `volumes: /:/host` or similar host root mount → full filesystem access from container.
|
|
17
|
+
- **[HIGH]** Ports bound to `0.0.0.0` for internal services → exposed to all interfaces. Use `127.0.0.1:port:port`.
|
|
18
|
+
- **[HIGH]** Hardcoded credentials in `docker-compose.yml` committed to VCS → credential exposure.
|
|
19
|
+
- **[MEDIUM]** `--cap-add SYS_ADMIN` or `--security-opt seccomp=unconfined` → elevated privileges.
|
|
20
|
+
- **[MEDIUM]** No read-only filesystem (`--read-only`) for containers that don't need write access.
|
|
21
|
+
- **[LOW]** Docker socket (`/var/run/docker.sock`) mounted in container → full Docker daemon access = host escape.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Performance
|
|
26
|
+
|
|
27
|
+
- **[HIGH]** Layer order wrong — source code copied before `package.json` + install → cache invalidated every code change. Copy lockfile + install first, then source.
|
|
28
|
+
- **[HIGH]** No multi-stage build for compiled languages → dev deps and build tools in production image.
|
|
29
|
+
- **[HIGH]** Large files/directories not in `.dockerignore` → slow build context transfer on every build.
|
|
30
|
+
- **[MEDIUM]** `RUN apt-get install` without `--no-install-recommends` → bloated image with unnecessary packages.
|
|
31
|
+
- **[MEDIUM]** Multiple separate `RUN` commands → extra layers. Chain with `&&` and `\`.
|
|
32
|
+
- **[MEDIUM]** Not using `--mount=type=cache` for package manager cache in BuildKit → re-downloading packages.
|
|
33
|
+
- **[MEDIUM]** Base image not using Alpine/distroless → unnecessarily large image.
|
|
34
|
+
- **[MEDIUM]** `COPY . .` before `npm install` → code changes invalidate dependency cache.
|
|
35
|
+
- **[LOW]** Not squashing layers in final image → history reveals sensitive intermediate steps.
|
|
36
|
+
- **[LOW]** `apt-get` without `apt-get clean && rm -rf /var/lib/apt/lists/*` → package cache in layer.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
- **[HIGH]** Application state written to container filesystem → lost on restart. Use named volumes for persistent data.
|
|
43
|
+
- **[HIGH]** Missing `HEALTHCHECK` → orchestrator can't detect unhealthy container, continues sending traffic.
|
|
44
|
+
- **[HIGH]** Single container running multiple services → violates SRP, harder to scale, complicated restarts.
|
|
45
|
+
- **[HIGH]** Secret rotation requires image rebuild → use runtime secret injection (Vault, K8s Secrets, AWS SSM).
|
|
46
|
+
- **[MEDIUM]** `docker-compose.yml` used in production without orchestration → no auto-scaling, rolling updates.
|
|
47
|
+
- **[MEDIUM]** Config hardcoded in Dockerfile → image not portable across environments. Use env vars + config files.
|
|
48
|
+
- **[MEDIUM]** `depends_on` used without health check condition → service starts before dependency is ready.
|
|
49
|
+
- **[MEDIUM]** Not using `restart: unless-stopped` / `restart: on-failure` → container doesn't recover from crashes.
|
|
50
|
+
- **[LOW]** Missing `ENTRYPOINT` — only `CMD` → command accidentally overridden at runtime.
|
|
51
|
+
- **[LOW]** Not pinning Docker Compose version in CI → behavior changes with compose updates.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Code Quality
|
|
56
|
+
|
|
57
|
+
- **[HIGH]** No `.dockerignore` file → unpredictable, large build context (`.git`, `node_modules`, `.env`).
|
|
58
|
+
- **[HIGH]** `ADD url ./` instead of `RUN curl + COPY` → `ADD` fetches during build, no verification.
|
|
59
|
+
- **[MEDIUM]** `ADD` used when `COPY` sufficient — `ADD` has implicit tar extraction and remote URL fetching; `COPY` is explicit.
|
|
60
|
+
- **[MEDIUM]** `ENV` variables not documented → unclear what configuration is available.
|
|
61
|
+
- **[MEDIUM]** `EXPOSE` port not matching actual application port → confusing, may break port mapping.
|
|
62
|
+
- **[MEDIUM]** Non-deterministic `apt-get` package versions → builds differ over time. Pin package versions.
|
|
63
|
+
- **[LOW]** Missing `LABEL` annotations (maintainer, version, description) → untracked images in registry.
|
|
64
|
+
- **[LOW]** Long `RUN` command without line continuation `\` → unreadable Dockerfile.
|
|
65
|
+
- **[LOW]** Not using `WORKDIR` → inconsistent working directory, fragile relative paths.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Common Bugs & Pitfalls
|
|
70
|
+
|
|
71
|
+
- **[HIGH]** Process not handling `SIGTERM` → container doesn't stop gracefully, killed after timeout. Use `exec` form `["node", "app.js"]` not shell form.
|
|
72
|
+
- **[HIGH]** PID 1 not reaping zombie processes → memory leak. Use `--init` flag or `tini` as init process.
|
|
73
|
+
- **[HIGH]** `CMD ["npm", "start"]` without shell → environment variables from `ENV` not available in some contexts. Test both.
|
|
74
|
+
- **[MEDIUM]** Container timezone different from host → cron jobs run at wrong time. Set `TZ` env var.
|
|
75
|
+
- **[MEDIUM]** `COPY --chown` not used → files owned by root inside container, non-root user can't write.
|
|
76
|
+
- **[MEDIUM]** DNS resolution failing in container → missing `--dns` or network mode mismatch.
|
|
77
|
+
- **[MEDIUM]** Build arg (`ARG`) vs environment variable (`ENV`) confusion — `ARG` only available during build, `ENV` persists.
|
|
78
|
+
- **[LOW]** Port already in use on host → `docker run` fails with `bind: address already in use`.
|
|
79
|
+
- **[LOW]** Volume mount overwriting container files → `node_modules` from host overlays container's installed deps.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Drizzle ORM — Stack-Specific Review Rules
|
|
2
|
+
|
|
3
|
+
> Applies to: GR · SR · PR · AR · BR
|
|
4
|
+
> Detection signals: `drizzle-orm`, `from 'drizzle-orm'`, `drizzle()`, `pgTable`, `mysqlTable`, `sqliteTable`, `migrate()`, `drizzle-kit`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Security
|
|
9
|
+
- **[CRITICAL]** Raw SQL via `sql` tagged template literal with user input not parameterized (e.g., `sql\`WHERE name = '${userInput}'\``) → SQL injection. Use Drizzle's query builder or pass user values as parameterized bindings: `sql\`WHERE name = ${userInput}\`` (Drizzle auto-parameterizes interpolated values in the `sql` tag).
|
|
10
|
+
- **[HIGH]** Drizzle schema file imported and exposed via an API route response → internal table structure, column names, and types leaked to clients. Never serialize schema objects into HTTP responses; expose only shaped DTOs.
|
|
11
|
+
- **[HIGH]** No row-level access control in query layer → any authenticated user can query any row by guessing IDs. Add a `WHERE userId = currentUserId` (or equivalent tenant filter) to every query that returns user-owned data.
|
|
12
|
+
- **[MEDIUM]** Database credentials stored in `drizzle.config.ts` and committed to the repository → credentials exposed in version control history. Read credentials from environment variables (`process.env.DATABASE_URL`) in the config file.
|
|
13
|
+
- **[MEDIUM]** Migrations run with superuser DB credentials in production CI/CD pipeline → accidental schema destruction possible. Use a migration-only role with `CREATE`, `ALTER`, `DROP` on the app schema; separate from the runtime role.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Performance
|
|
18
|
+
- **[HIGH]** N+1 queries from nested `.findMany()` calls without using `.with()` for relations → one query fired per parent row to load children. Switch to Drizzle's relational query API with `.with({ relation: true })` to load related data in a single query.
|
|
19
|
+
- **[HIGH]** No `.limit()` on list queries → full table scan returned to application memory, OOM risk on large tables. Every `findMany` or `select` on an unbounded table must include `.limit(n)`.
|
|
20
|
+
- **[HIGH]** `db.select()` without column projection (no `{ field: table.field }` shape) → all columns fetched; breaks index-only scans and sends unnecessary data over the wire. Pass a columns object to `db.select({ id: table.id, name: table.name })` for every query.
|
|
21
|
+
- **[MEDIUM]** Multi-table mutations (insert + update across tables) not wrapped in a transaction → partial failure leaves data inconsistent. Use `db.transaction(async (tx) => { ... })` for all operations that must be atomic.
|
|
22
|
+
- **[MEDIUM]** Indexes not defined in the Drizzle schema alongside the table → indexes exist only in migration SQL but not tracked in the schema, causing drift. Define all indexes using `.index()` or `.uniqueIndex()` in the same schema file as the table.
|
|
23
|
+
- **[MEDIUM]** `drizzle-kit push` used in staging or production to apply schema changes → `push` may drop and recreate columns, causing data loss. Use `drizzle-kit generate` to produce migration SQL files, review them, then apply with `drizzle-kit migrate`.
|
|
24
|
+
- **[LOW]** Connection not pooled (no PgBouncer or application-level pool like `pg-pool`) → new TCP connection opened per request; connection exhaustion under load. Configure a connection pool and pass the pooled client to `drizzle()`.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
- **[HIGH]** Database queries written directly in API route handlers or controllers → no separation of data access from transport layer, making queries untestable in isolation. Extract all DB queries into a repository or data-access layer; import the repository into route handlers.
|
|
30
|
+
- **[MEDIUM]** Drizzle's relational query API not used when related data is needed → developers write complex manual JOINs that are harder to maintain and more error-prone. Use `.query.table.findMany({ with: { relation: {} } })` for relation traversal.
|
|
31
|
+
- **[MEDIUM]** Schema spread across many files without a barrel `schema.ts` export → Drizzle relations and `db` instance require all schema tables; missing tables cause runtime errors. Collect all table definitions in a single `schema.ts` (or `schema/index.ts`) barrel export passed to `drizzle()`.
|
|
32
|
+
- **[MEDIUM]** No seed script for development database → developers set up data manually, causing environment divergence. Provide a `seed.ts` script using Drizzle inserts that can be run with `npx tsx seed.ts`.
|
|
33
|
+
- **[LOW]** Table names in schema not matching actual database table names (no `{ name: 'actual_table' }` option) → Drizzle generates incorrect SQL, causing "relation does not exist" errors. Set the explicit table name string in `pgTable('actual_table_name', ...)` when it differs from the variable name.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Code Quality
|
|
38
|
+
- **[HIGH]** Column types in Drizzle schema not aligned with application TypeScript types (e.g., `text()` for a column that only holds enum values) → invalid values accepted at DB level. Use `text('col', { enum: ['a', 'b', 'c'] })` or add a Zod/Valibot parse step at the boundary.
|
|
39
|
+
- **[HIGH]** `.returning()` omitted after `.insert()` when the generated ID or defaults are needed → `result` is an empty array, causing `undefined` reference on the next line. Chain `.returning()` to every INSERT that the application reads back.
|
|
40
|
+
- **[MEDIUM]** `.notNull()` missing on columns that are logically required → `null` values accumulate and cause unexpected `null` checks throughout the codebase. Add `.notNull()` to every column the application treats as mandatory.
|
|
41
|
+
- **[MEDIUM]** `.$type<T>()` not used for columns that hold branded or opaque types (e.g., `UserId`, `OrderId`) → plain `string` or `number` types allow mixing IDs across entities. Use `text('user_id').$type<UserId>()` to brand column types and catch cross-entity ID bugs at compile time.
|
|
42
|
+
- **[MEDIUM]** Drizzle TypeScript types not inferred with `typeof table.$inferSelect` / `.$inferInsert` → manual type duplication drifts from schema over time. Use Drizzle's inferred types everywhere instead of hand-written interfaces.
|
|
43
|
+
- **[LOW]** Table naming inconsistency between Drizzle variable name and the DB table name passed as first arg → confusion when reading error messages or raw SQL logs. Keep Drizzle variable name and table name string identical (both snake_case).
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Common Bugs & Pitfalls
|
|
48
|
+
- **[HIGH]** `.insert().values()` result used without `.returning()` → the return value is a result metadata object (rowCount), not the inserted row; accessing `.id` returns `undefined`. Always add `.returning({ id: table.id })` (or the needed fields) after `.insert().values()`.
|
|
49
|
+
- **[HIGH]** Relations defined in the Drizzle schema with `relations()` but `.with()` not used in the actual query → N+1 still occurs because `relations()` only informs the query API; it does not automatically join. Use `db.query.table.findMany({ with: { relation: {} } })` to activate the relation.
|
|
50
|
+
- **[MEDIUM]** `drizzle-kit push` used against a production database → push computes a diff and may drop columns or tables to match the schema, causing irreversible data loss. Enforce a CI policy that only `drizzle-kit migrate` (with reviewed migration files) runs in production.
|
|
51
|
+
- **[MEDIUM]** Database connection created inside a request handler (e.g., `const db = drizzle(new Pool(...))` per request) → new pool created for every request, exhausting DB connections rapidly. Create the `db` instance once at module initialization and reuse it.
|
|
52
|
+
- **[MEDIUM]** `eq(table.col, undefined)` passed to a WHERE clause when an optional filter is absent → Drizzle generates `WHERE col = NULL` (never matches). Guard optional filters: `...(value !== undefined ? [eq(table.col, value)] : [])` using `and()`.
|
|
53
|
+
- **[LOW]** Drizzle type inference breaking on complex union or conditional columns → TypeScript errors cascade through the codebase. Pin Drizzle and `drizzle-kit` to the same exact semver version and upgrade them together.
|
|
54
|
+
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# DynamoDB — Stack-Specific Review Rules
|
|
2
|
+
|
|
3
|
+
> Applies to: GR · SR · PR · AR · BR
|
|
4
|
+
> Detection signals: `@aws-sdk/client-dynamodb`, `dynamoose`, `from 'dynamodb'`, `DynamoDB.DocumentClient`, `aws-sdk` with DynamoDB, `PK`/`SK` table design
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Security
|
|
9
|
+
- **[CRITICAL]** IAM role or user has `dynamodb:*` on `*` (all tables) → compromise grants full read/write/delete access to every table. Scope IAM policies to specific table ARNs and only the required actions (e.g., `dynamodb:GetItem`, `dynamodb:PutItem`).
|
|
10
|
+
- **[HIGH]** User-controlled input in `FilterExpression` or `KeyConditionExpression` without validation → logical injection allows querying unintended data. Validate all filter values against an allowlist; use `ExpressionAttributeValues` for all values.
|
|
11
|
+
- **[HIGH]** DynamoDB table name constructed from user input → attacker can target arbitrary tables. Hardcode all table names; read them from environment variables set at deployment time.
|
|
12
|
+
- **[HIGH]** No VPC endpoint for DynamoDB → traffic traverses the public internet. Create a VPC gateway endpoint and restrict outbound security group rules accordingly.
|
|
13
|
+
- **[MEDIUM]** Sensitive attributes (PII, tokens) not encrypted at the attribute level → storage-layer encryption alone is insufficient. Use AWS Encryption SDK to encrypt sensitive values before writing to DynamoDB.
|
|
14
|
+
- **[MEDIUM]** CloudTrail not enabled or DynamoDB data events not logged → no audit trail of who read or wrote which items. Enable CloudTrail with DynamoDB data event logging for tables with sensitive data.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Performance
|
|
19
|
+
- **[CRITICAL]** `Scan` used instead of `Query` for data retrieval → reads every item in the table, consuming full provisioned read capacity proportional to table size. Use `Query` with a partition key; reserve `Scan` for one-off administrative operations.
|
|
20
|
+
- **[HIGH]** Hot partition key routing all traffic to a single partition (e.g., current date, fixed status string, or single tenant ID) → partitions cap at 3,000 RCU / 1,000 WCU; throttling occurs. Add a random suffix or shard number to distribute load.
|
|
21
|
+
- **[HIGH]** Missing GSI for access patterns not served by the primary key → every such query falls back to a full `Scan`. Define GSIs for all known access patterns before table creation.
|
|
22
|
+
- **[HIGH]** `FilterExpression` applied to `Scan` or `Query` results → filter runs after reading; full RCU cost charged for all items scanned. Encode filter criteria into the partition or sort key design.
|
|
23
|
+
- **[MEDIUM]** Item size consistently above 10 KB → RCU/WCU rounds up per 4 KB read / 1 KB write. Store large payloads in S3; keep only a reference key in DynamoDB.
|
|
24
|
+
- **[MEDIUM]** `ProjectionExpression` not specified → `GetItem` and `Query` return all attributes, wasting bandwidth and read capacity. Specify only the attributes the application needs.
|
|
25
|
+
- **[LOW]** On-demand capacity used for predictable steady traffic → on-demand costs more per request than provisioned at stable load. Switch to provisioned capacity with auto-scaling.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
- **[CRITICAL]** Table designed relationally with one table per entity type (Users, Orders, Products) → DynamoDB has no cross-table JOINs; relational queries require multiple round trips. Use single-table design with overloaded PK/SK values.
|
|
31
|
+
- **[HIGH]** Access patterns not defined before table schema is finalized → retrofitting requires expensive table rebuilds. Document all query patterns (PK=X, SK starts-with Y) before writing any table creation code.
|
|
32
|
+
- **[HIGH]** Single-table design not used when entity types share access patterns (e.g., fetch user plus orders in one request) → requires two separate calls. Collapse entities into one table; use `Query` with `PK=userId` to retrieve all related items.
|
|
33
|
+
- **[MEDIUM]** TTL attribute not set on ephemeral items (sessions, OTP tokens, locks) → items accumulate indefinitely, increasing storage cost and scan time. Define a Unix-epoch numeric TTL attribute and enable DynamoDB TTL.
|
|
34
|
+
- **[MEDIUM]** DynamoDB Streams not used for event-driven side effects (cache invalidation, notifications) → polling or synchronous calls used instead. Enable Streams and attach a Lambda trigger.
|
|
35
|
+
- **[LOW]** No CloudWatch capacity alarms configured → throttling goes undetected until user-visible errors occur. Set alarms at 80% of provisioned `ConsumedWriteCapacityUnits` / `ConsumedReadCapacityUnits`.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Code Quality
|
|
40
|
+
- **[HIGH]** Table names hardcoded as string literals throughout the codebase → renaming requires a grep-and-replace across all files. Centralize in a constants module or read from `process.env.TABLE_NAME`.
|
|
41
|
+
- **[HIGH]** No error handling for `ProvisionedThroughputExceededException` → throttled requests fail permanently. Use the AWS SDK `maxAttempts` retry config and add exponential backoff for throttling errors.
|
|
42
|
+
- **[MEDIUM]** `TransactWriteItems` not used for multi-item atomic operations (e.g., decrement inventory and create order) → partial success possible if one write fails. Use `TransactWriteItems` (up to 100 items) for all-or-nothing mutations.
|
|
43
|
+
- **[MEDIUM]** Raw `DynamoDB.DocumentClient` used without an abstraction layer → marshall/unmarshall logic scattered throughout. Use DynamoDB Toolbox, Dynamoose, or a thin repository class.
|
|
44
|
+
- **[MEDIUM]** Attribute names conflicting with DynamoDB reserved words (`name`, `status`, `date`, `key`) → queries without `ExpressionAttributeNames` throw `ValidationException`. Map reserved-word attributes using ExpressionAttributeNames.
|
|
45
|
+
- **[LOW]** No integration tests against a local DynamoDB (DynamoDB Local or LocalStack) → key condition expression bugs only caught in production. Add DynamoDB Local to the test environment.
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Common Bugs & Pitfalls
|
|
50
|
+
- **[HIGH]** `BatchWriteItem` response not checked for `UnprocessedItems` → DynamoDB may partially fail a batch silently. Always check `response.UnprocessedItems` and retry unprocessed items with exponential backoff.
|
|
51
|
+
- **[HIGH]** Optimistic locking via `ConditionExpression` not used for concurrent updates → two concurrent requests read the same version, both write the incremented version, and one silently overwrites the other. Use `ConditionExpression: "version = :expected"` and increment version on every write.
|
|
52
|
+
- **[MEDIUM]** `ConsistentRead: true` not used when strong consistency is needed after a write → eventual consistency may return stale data within the roughly 1-second replication window. Set `ConsistentRead: true` for reads that must reflect a preceding write.
|
|
53
|
+
- **[MEDIUM]** Global table replication lag not accounted for in cross-region reads → a write in us-east-1 may not be visible in eu-west-1 for several seconds. Route post-write reads to the write region.
|
|
54
|
+
- **[MEDIUM]** `UpdateItem` with `SET attr = :val` replacing a list attribute instead of appending → entire list overwritten when intent is to add one element. Use `SET listAttr = list_append(listAttr, :newItems)` to append.
|
|
55
|
+
- **[LOW]** Sort key not planned at design time → adding a sort key later requires creating a new table and migrating all data. Decide on composite vs. simple key before the first deployment.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Electron — Stack-Specific Review Rules
|
|
2
|
+
|
|
3
|
+
> Applies to: GR · SR · PR · AR · BR
|
|
4
|
+
> Detection signals: `electron` in `dependencies` · `BrowserWindow` · `ipcMain` · `ipcRenderer` · `app.whenReady()` · `main.js` / `main.ts` as Electron entry
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Security
|
|
9
|
+
|
|
10
|
+
- **[CRITICAL]** `contextIsolation: false` in `webPreferences` → renderer process has direct access to Node.js APIs; XSS in renderer = full system compromise.
|
|
11
|
+
- **[CRITICAL]** `nodeIntegration: true` in `webPreferences` → full Node.js API surface exposed to renderer; one XSS = RCE.
|
|
12
|
+
- **[CRITICAL]** `webSecurity: false` → disables CORS, allows loading local files from remote pages, enables cross-origin attacks.
|
|
13
|
+
- **[HIGH]** IPC handler executing user-controlled commands: `ipcMain.on('run', (e, cmd) => exec(cmd))` → arbitrary command execution from renderer or compromised web content.
|
|
14
|
+
- **[HIGH]** Loading remote URLs in `BrowserWindow` without a strict Content Security Policy → XSS attack surface.
|
|
15
|
+
- **[HIGH]** `shell.openExternal(url)` with user-controlled URL → opens `file://`, `smb://`, or other dangerous protocol URIs. Validate scheme and host allowlist.
|
|
16
|
+
- **[MEDIUM]** Bundling application without code signing → OS security warnings, easier to tamper with by local attacker.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Performance
|
|
21
|
+
|
|
22
|
+
- **[HIGH]** Synchronous IPC `ipcRenderer.sendSync()` → blocks renderer's UI thread until main process responds. Use async `ipcRenderer.invoke()` / `ipcMain.handle()`.
|
|
23
|
+
- **[HIGH]** Sending large data (images, buffers) over IPC as serialized JSON → full serialize/deserialize cost. Use `SharedArrayBuffer` or write to a temp file and pass the path.
|
|
24
|
+
- **[MEDIUM]** Invisible `BrowserWindow` kept alive indefinitely → each window has a full Chromium renderer process (~80-150 MB). Destroy when not needed.
|
|
25
|
+
- **[LOW]** Requiring all Node modules at startup in main process → slow cold start. Use lazy requires inside handlers.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
- **[HIGH]** Business logic placed in renderer process → should live in main process or a dedicated Node service; renderer should be a thin UI layer communicating via IPC.
|
|
32
|
+
- **[HIGH]** `remote` module (deprecated in Electron 12, removed in 14) still in use → security risk, use `contextBridge` to expose specific APIs to renderer.
|
|
33
|
+
- **[MEDIUM]** Entire `ipcRenderer` object exposed via `contextBridge.exposeInMainWorld` → expose a minimal typed API, not the full IPC surface.
|
|
34
|
+
- **[MEDIUM]** No auto-update mechanism (e.g., `electron-updater`) → security patches can't reach users. Implement silent update with user notification.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Common Bugs & Pitfalls
|
|
39
|
+
|
|
40
|
+
- **[HIGH]** `app.quit()` not called after all windows close on Windows/Linux → process keeps running in background (tray icon or just CPU/memory waste). Add `app.on('window-all-closed', () => app.quit())` for non-macOS.
|
|
41
|
+
- **[HIGH]** Accessing `document` / `window` in main process → these are browser globals, undefined in Node.js main process context.
|
|
42
|
+
- **[MEDIUM]** Async operation started from IPC handler completes after `BrowserWindow` is destroyed → callback or `event.reply()` on destroyed window throws error.
|
|
43
|
+
- **[MEDIUM]** Dev tools opened in production build → `webContents.openDevTools()` call not removed. Gate with `!app.isPackaged`.
|
|
44
|
+
- **[LOW]** `process.resourcesPath` used to locate app files → use `path.join(__dirname, ...)` or `app.getAppPath()` for reliable cross-platform paths.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Elixir / Phoenix — Stack-Specific Review Rules
|
|
2
|
+
|
|
3
|
+
> Applies to: GR · SR · PR · AR · BR
|
|
4
|
+
> Detection signals: `*.ex` / `*.exs` files · `mix.exs` · `defmodule` · `use Phoenix.` · `Repo.` · `Ecto.` · `defp` · `%{}` struct patterns
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Security
|
|
9
|
+
|
|
10
|
+
- **[HIGH]** Raw SQL in Ecto: `Repo.query("SELECT * FROM users WHERE id = #{id}")` → SQL injection. Use `Ecto.Query` DSL or parameterized queries: `from(u in User, where: u.id == ^id)`.
|
|
11
|
+
- **[HIGH]** `Phoenix.Token.verify` called without `max_age` option → tokens never expire. Always pass `max_age:` (e.g., 86400 seconds = 1 day).
|
|
12
|
+
- **[HIGH]** `String.to_atom(user_input)` → atoms are never garbage collected; exhausting the atom table crashes the BEAM VM (DoS). Use `String.to_existing_atom/1` or keep as a string.
|
|
13
|
+
- **[MEDIUM]** Missing CSRF protection on non-GET, non-API endpoints → Phoenix includes `Plug.CSRFProtection` by default; ensure it's not removed from the pipeline.
|
|
14
|
+
- **[MEDIUM]** Mass assignment: `User.changeset(user, params)` without a field allowlist in the changeset → any client-supplied field (e.g., `role`, `admin`) gets set. Always use `cast/3` with explicit allowed fields.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Performance
|
|
19
|
+
|
|
20
|
+
- **[HIGH]** N+1 query in Ecto: loading associations inside an `Enum.map` loop → generates N additional queries. Use `Repo.preload/2` or add `:preload` to the initial query.
|
|
21
|
+
- **[HIGH]** `Enum` functions on a large `Stream` that could be composed → `Enum.filter |> Enum.map` creates an intermediate list. Chain `Stream.filter |> Stream.map |> Enum.to_list` for lazy evaluation.
|
|
22
|
+
- **[MEDIUM]** `GenServer` using synchronous `call` for all operations including fire-and-forget mutations → serializes all operations through one process. Use `cast` for operations that don't need a return value.
|
|
23
|
+
- **[MEDIUM]** `Repo.all` without pagination on a potentially large table → memory spike and slow response. Use `Repo.paginate` or `limit/offset` in the query.
|
|
24
|
+
- **[LOW]** Pattern matching on entire map `%{field: val} = large_map` when only one field is needed → still works but is misleading; be explicit about what you're matching.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
|
|
30
|
+
- **[HIGH]** Business logic in Phoenix controller action → extract to a Context module (boundary pattern). Controllers should only translate HTTP ↔ context function calls.
|
|
31
|
+
- **[HIGH]** Process state stored in `Agent` or `GenServer` without a persistence strategy → state is lost on process crash or node restart. Persist critical state to the database.
|
|
32
|
+
- **[MEDIUM]** `Task.async` without a supervisor → unlinked task crash is not propagated to the caller; use `Task.Supervisor` for resilient async work.
|
|
33
|
+
- **[MEDIUM]** Long-lived `GenServer` that receives unbounded messages without backpressure → mailbox grows indefinitely under load. Use `GenStage` or rate limiting.
|
|
34
|
+
- **[LOW]** `use GenServer` for simple key-value storage → consider `ETS` (Erlang Term Storage) for concurrent read-heavy in-memory state.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Code Quality
|
|
39
|
+
|
|
40
|
+
- **[HIGH]** `with` expression without an `else` clause → if any clause returns a non-`{:ok, _}` value, it falls through as the return value of the `with` block. Always add `else` to handle errors explicitly.
|
|
41
|
+
- **[MEDIUM]** Large `case` expression where pattern-matched function clauses would be idiomatic → Elixir's pattern matching on function heads is cleaner than `case` for multiple conditions.
|
|
42
|
+
- **[MEDIUM]** `IO.inspect` / `IO.puts` debug statements left in production code → remove before merge. Use `Logger` for structured logging.
|
|
43
|
+
- **[LOW]** Not using `@spec` on public functions → poor documentation, no Dialyzer type checking benefit.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Common Bugs & Pitfalls
|
|
48
|
+
|
|
49
|
+
- **[HIGH]** `Repo` operations outside a transaction when multiple operations must be atomic → partial write on failure leaves data inconsistent. Use `Repo.transaction/1` or `Ecto.Multi`.
|
|
50
|
+
- **[HIGH]** Calling `Repo.update_all` / `Repo.delete_all` without a `where` clause → modifies or deletes all rows in the table. Always include a `where` condition.
|
|
51
|
+
- **[MEDIUM]** `Map.get(map, key)` returns `nil` for missing keys, not an error → if the key is required, use `Map.fetch!(map, key)` which raises `KeyError` on missing key.
|
|
52
|
+
- **[MEDIUM]** Atom comparison vs string comparison: `"active" == :active` is always `false` → be consistent with atom vs string types for status/enum fields. Ecto enums use atoms; JSON payloads use strings.
|
|
53
|
+
- **[LOW]** `Enum.first(list)` returning `nil` for empty list treated as a valid value → check for `nil` or use pattern matching on the result.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Expo — Stack-Specific Review Rules
|
|
2
|
+
|
|
3
|
+
> Applies to: GR · SR · PR · AR · BR
|
|
4
|
+
> Detection signals: `expo`, `from 'expo'`, `app.json` with `expo`, `from 'expo-router'`, `eas.json`, `from 'expo-constants'`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Security
|
|
9
|
+
- **[CRITICAL]** API keys or secrets placed in `app.json`, `app.config.js`, or `Constants.expoConfig` → values are bundled into the app binary and extractable. Move secrets server-side; use `expo-constants` only for non-sensitive public config.
|
|
10
|
+
- **[HIGH]** Sensitive data (tokens, user credentials) stored in `AsyncStorage` → unencrypted storage accessible on rooted/jailbroken devices. Use `expo-secure-store` which calls the OS keychain/keystore.
|
|
11
|
+
- **[HIGH]** Deep link URI scheme not validated before acting on parameters → open redirect or parameter injection via crafted links. Validate the full URI and all parameters before using deep link data.
|
|
12
|
+
- **[HIGH]** `expo-file-system` used to read/write paths constructed from user input → path traversal outside the app sandbox. Sanitize paths and restrict operations to `FileSystem.documentDirectory` or `cacheDirectory`.
|
|
13
|
+
- **[MEDIUM]** EAS Update (OTA) not configured with code signing → unsigned JavaScript bundles can be injected by a network attacker. Enable `codeSigningCertificate` in `eas.json` for all update channels.
|
|
14
|
+
- **[MEDIUM]** `__DEV__` check absent on verbose logging or debug screens → internal data and stack traces exposed in production builds. Guard all debug output with `if (__DEV__)`.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Performance
|
|
19
|
+
- **[HIGH]** Heavy computation (sorting, encryption, image processing) run on the JavaScript thread → UI freezes. Offload to `expo-task-manager` background tasks, `react-native-worklets-core`, or a native module.
|
|
20
|
+
- **[HIGH]** Large images embedded at source resolution without optimization → bloated bundle and slow render on low-end devices. Use `expo-image-manipulator` to resize/compress assets; provide `@2x`/`@3x` variants.
|
|
21
|
+
- **[HIGH]** React Native `Image` component used instead of `expo-image` → no built-in memory caching, blurhash placeholder support, or progressive loading. Replace with `expo-image` for all image rendering.
|
|
22
|
+
- **[MEDIUM]** Data fetched in `useEffect` on every component mount without caching → repeated network calls on navigation. Use a caching layer (React Query, SWR, or Expo's `useCachedPromise`).
|
|
23
|
+
- **[MEDIUM]** All screens eager-loaded at startup in navigator → slow time-to-interactive. Use `lazy` option in Expo Router or React Navigation to defer screen registration.
|
|
24
|
+
- **[LOW]** `useFonts` from `expo-font` not awaited before rendering text → layout shift or missing glyphs on first frame. Hide the splash screen until fonts are ready with `SplashScreen.preventAutoHideAsync`.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Architecture
|
|
29
|
+
- **[HIGH]** Custom navigation logic reimplemented instead of using Expo Router's file-based routing → navigation state bugs and missed deep linking support. Migrate to the `app/` directory convention with Expo Router.
|
|
30
|
+
- **[HIGH]** Platform-specific logic mixed inline with `Platform.OS === 'ios'` checks throughout components → hard to maintain. Use `.ios.tsx` / `.android.tsx` / `.web.tsx` file extensions for platform-specific implementations.
|
|
31
|
+
- **[MEDIUM]** Production builds done locally with `expo build` (deprecated) instead of EAS Build → inconsistent build environment and toolchain. Switch to `eas build` with a defined build profile.
|
|
32
|
+
- **[MEDIUM]** Static `app.json` used instead of `app.config.ts` → cannot inject environment variables or dynamic values at build time. Migrate to `app.config.ts` and read from `process.env`.
|
|
33
|
+
- **[MEDIUM]** Expo managed workflow plugins not used for native module configuration → manual native code edits that break on `expo prebuild`. Use config plugins for all native configuration.
|
|
34
|
+
- **[LOW]** Single `eas.json` profile for all environments → development, staging, and production share the same build configuration. Define separate `development`, `preview`, and `production` profiles.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Code Quality
|
|
39
|
+
- **[HIGH]** Expo SDK version not pinned or using a major version range → `expo upgrade` introduces breaking changes automatically. Pin to an exact Expo SDK version and upgrade intentionally.
|
|
40
|
+
- **[MEDIUM]** `expo-constants` used for environment switching in runtime code instead of EAS build profiles → environment logic scattered and hard to audit. Centralize environment config in a single `config.ts` module populated at build time.
|
|
41
|
+
- **[MEDIUM]** Permissions (camera, location, notifications) requested without a usage description or at startup → users deny preemptively. Request permissions at the point of use and provide a `PermissionsExplanation` component.
|
|
42
|
+
- **[MEDIUM]** `babel.config.js` not using `babel-preset-expo` → Expo-specific transforms (SVG imports, module resolution) do not work. Ensure `presets: ['babel-preset-expo']` is the base config.
|
|
43
|
+
- **[LOW]** TypeScript strict mode not enabled in `tsconfig.json` → type errors masked. Set `"strict": true` and extend from `expo/tsconfig.base`.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Common Bugs & Pitfalls
|
|
48
|
+
- **[HIGH]** Stale Metro bundler cache producing builds with old code after dependency changes → old module versions used at runtime. Run `expo start --clear` or `npx expo start -c` to clear the cache.
|
|
49
|
+
- **[HIGH]** `expo-av` `Audio` or `Video` playback not stopped or unloaded on component unmount → audio continues playing in the background, leaking native resources. Call `sound.stopAsync()` and `sound.unloadAsync()` in the `useEffect` cleanup.
|
|
50
|
+
- **[MEDIUM]** `KeyboardAvoidingView` using the same `behavior` for iOS and Android → input fields obscured by keyboard on one platform. Use `behavior="padding"` for iOS and `behavior="height"` for Android.
|
|
51
|
+
- **[MEDIUM]** `StatusBar` style not set per screen → status bar text color wrong against the screen's background. Use `expo-status-bar` `StatusBar` component with `style` set in each screen.
|
|
52
|
+
- **[MEDIUM]** `useCallback` and `useMemo` not applied to callbacks passed to `FlatList` `renderItem` → entire list re-renders on parent state change. Memoize `renderItem` and `keyExtractor` callbacks.
|
|
53
|
+
- **[LOW]** `Platform.OS === 'web'` checks missing for Expo Web → components using native-only APIs crash in the browser. Add web guards or provide web-specific implementations for all native API usage.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Express.js — Stack-Specific Review Rules
|
|
2
|
+
|
|
3
|
+
> Applies to: GR · SR · PR · AR · BR
|
|
4
|
+
> Detection signals: `express()`, `app.use(`, `router.get/post/put/delete`, `from 'express'`, `require('express')`, `middleware`, `req.body`, `res.json`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Security
|
|
9
|
+
|
|
10
|
+
- **[CRITICAL]** Missing authentication middleware on protected routes → unauthenticated access.
|
|
11
|
+
- **[CRITICAL]** SQL / NoSQL query built with `req.body`/`req.params` string interpolation → injection.
|
|
12
|
+
- **[CRITICAL]** `res.send(req.body.input)` without sanitization → reflected XSS.
|
|
13
|
+
- **[CRITICAL]** `eval(req.body.code)` or dynamic `require(req.body.module)` → arbitrary code execution.
|
|
14
|
+
- **[HIGH]** Missing `helmet` middleware → no security headers (CSP, X-Frame-Options, HSTS).
|
|
15
|
+
- **[HIGH]** `req.body` used without validation (express-validator, zod, joi) → arbitrary data reaches logic.
|
|
16
|
+
- **[HIGH]** Error handler exposing stack trace: `res.json({ error: err.stack })` → info disclosure.
|
|
17
|
+
- **[HIGH]** Missing rate limiting on auth/sensitive endpoints (`express-rate-limit`) → brute force.
|
|
18
|
+
- **[HIGH]** File upload without MIME type/size validation → arbitrary file stored/executed.
|
|
19
|
+
- **[HIGH]** `res.redirect(req.query.next)` without URL validation → open redirect.
|
|
20
|
+
- **[HIGH]** Prototype pollution via `req.body` merged into objects without `Object.freeze(Object.prototype)`.
|
|
21
|
+
- **[MEDIUM]** Session secret hardcoded or weak → session hijacking.
|
|
22
|
+
- **[MEDIUM]** CORS `origin: '*'` with credentials in production → credential leakage.
|
|
23
|
+
- **[MEDIUM]** JWT stored in localStorage instead of httpOnly cookie → XSS token theft.
|
|
24
|
+
- **[LOW]** `express.static` serving files from root directory → source code exposure.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Performance
|
|
29
|
+
|
|
30
|
+
- **[HIGH]** Synchronous `fs.readFileSync` / `crypto.pbkdf2Sync` in route handler → blocks event loop.
|
|
31
|
+
- **[HIGH]** `async` route handler without try/catch or `express-async-errors` → unhandled rejection crashes process.
|
|
32
|
+
- **[HIGH]** N+1 DB queries per request — loading related data in loop inside route.
|
|
33
|
+
- **[HIGH]** Missing response compression (`compression` middleware) for large JSON responses.
|
|
34
|
+
- **[HIGH]** No connection pooling for DB → new connection per request → latency + exhaustion.
|
|
35
|
+
- **[MEDIUM]** No response caching for idempotent, rarely-changing endpoints.
|
|
36
|
+
- **[MEDIUM]** Missing `req.setTimeout()` → hanging requests hold server resources indefinitely.
|
|
37
|
+
- **[MEDIUM]** `morgan` verbose logging in production → I/O overhead.
|
|
38
|
+
- **[MEDIUM]** Middleware running on all routes including static assets → unnecessary overhead.
|
|
39
|
+
- **[LOW]** `res.json` on large objects without streaming → buffering entire payload in memory.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Architecture
|
|
44
|
+
|
|
45
|
+
- **[HIGH]** Business logic inside route handler → extract to service/controller layer.
|
|
46
|
+
- **[HIGH]** No centralized error handling middleware (`app.use((err, req, res, next) => {...})`) → inconsistent errors.
|
|
47
|
+
- **[HIGH]** Middleware order wrong — auth middleware placed after route it should protect → never runs.
|
|
48
|
+
- **[HIGH]** `next()` called after `res.send()` / `res.json()` → "Cannot set headers after they are sent" error.
|
|
49
|
+
- **[HIGH]** Not using `express.Router` per domain → all routes in single file → unmaintainable.
|
|
50
|
+
- **[MEDIUM]** Missing `app.set('trust proxy', 1)` behind load balancer → wrong `req.ip`, rate limiting broken.
|
|
51
|
+
- **[MEDIUM]** `app.use(express.json())` with no size limit → large payload DoS. Set `{ limit: '1mb' }`.
|
|
52
|
+
- **[MEDIUM]** Global state (module-level variables) used for request-scoped data → not isolated per request.
|
|
53
|
+
- **[MEDIUM]** Not using `AsyncLocalStorage` for request context propagation → prop drilling `req` everywhere.
|
|
54
|
+
- **[LOW]** Missing `404` catch-all handler at end of middleware chain.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Code Quality
|
|
59
|
+
|
|
60
|
+
- **[HIGH]** `next(err)` not called in catch block → error swallowed, request hangs.
|
|
61
|
+
- **[HIGH]** Route params used directly as DB keys without type validation → `req.params.id` is always a string.
|
|
62
|
+
- **[MEDIUM]** `res.status(200).json(...)` on errors → client can't distinguish success from failure.
|
|
63
|
+
- **[MEDIUM]** Inconsistent response shape across routes → no standard `{ data, error }` envelope.
|
|
64
|
+
- **[MEDIUM]** Missing `await` on async middleware → middleware completes without waiting for async work.
|
|
65
|
+
- **[MEDIUM]** Not using TypeScript with `@types/express` → `req.body` is `any`.
|
|
66
|
+
- **[LOW]** `res.send()` for JSON responses instead of `res.json()` → must manually set Content-Type.
|
|
67
|
+
- **[LOW]** Not using environment-specific config (`dotenv` without validation).
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Common Bugs & Pitfalls
|
|
72
|
+
|
|
73
|
+
- **[HIGH]** Async middleware not calling `next()` on error → request hangs indefinitely.
|
|
74
|
+
- **[HIGH]** Route defined after `app.use(errorHandler)` → error handler intercepts all subsequent routes.
|
|
75
|
+
- **[HIGH]** Error-handling middleware with only 3 params `(req, res, next)` → Express doesn't recognize as error handler (needs 4: `err, req, res, next`).
|
|
76
|
+
- **[HIGH]** `app.use(express.json())` placed after route definition → `req.body` undefined.
|
|
77
|
+
- **[MEDIUM]** Wildcard route `app.get('*', ...)` placed before specific routes → all specific routes shadowed.
|
|
78
|
+
- **[MEDIUM]** `router.param()` not used for common param validation → repeated `parseInt(req.params.id)`.
|
|
79
|
+
- **[MEDIUM]** Cookie options (`httpOnly`, `secure`, `sameSite`) not set → vulnerable session cookies.
|
|
80
|
+
- **[MEDIUM]** `res.end()` vs `res.send()` vs `res.json()` confusion → wrong Content-Type header.
|
|
81
|
+
- **[LOW]** `app.listen()` not storing returned server for graceful shutdown.
|
|
82
|
+
- **[LOW]** `express-validator` errors not checked with `validationResult(req)` → validation runs but not enforced.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# FastAPI — Stack-Specific Review Rules
|
|
2
|
+
|
|
3
|
+
> Applies to: GR · SR · PR · AR · BR
|
|
4
|
+
> Detection signals: `from fastapi`, `@app.get`, `@app.post`, `@router.`, `Depends(`, `BaseModel` (pydantic), `uvicorn`, `@asynccontextmanager`, `lifespan`
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Security
|
|
9
|
+
|
|
10
|
+
- **[CRITICAL]** Route missing `Depends(get_current_user)` on protected endpoints → unauthenticated access.
|
|
11
|
+
- **[CRITICAL]** SQL built with f-string/`.format()` with user data → injection. Use parameterized queries.
|
|
12
|
+
- **[CRITICAL]** `eval()` / `exec()` with user-controlled input in any route or utility.
|
|
13
|
+
- **[HIGH]** ORM model returned directly instead of Pydantic response schema → exposes passwords, internal IDs.
|
|
14
|
+
- **[HIGH]** Missing `response_model` on endpoints → no output filtering, arbitrary data returned.
|
|
15
|
+
- **[HIGH]** `allow_origins=["*"]` in production CORS config → any website makes credentialed requests.
|
|
16
|
+
- **[HIGH]** Secrets loaded with `os.getenv("SECRET")` returning `None` silently → app runs without secrets.
|
|
17
|
+
- **[HIGH]** File upload missing MIME type + size validation → arbitrary file upload.
|
|
18
|
+
- **[HIGH]** Missing `HTTPSRedirectMiddleware` in production → tokens/cookies sent over HTTP.
|
|
19
|
+
- **[HIGH]** JWT token not validated for expiry/signature in `get_current_user` dependency.
|
|
20
|
+
- **[HIGH]** User-supplied `redirect_uri` in OAuth flow not validated → open redirect.
|
|
21
|
+
- **[MEDIUM]** Missing rate limiting on auth endpoints → brute force on login.
|
|
22
|
+
- **[MEDIUM]** `background_tasks.add_task()` running with request-scoped DB session that closes → session closed before task runs.
|
|
23
|
+
- **[LOW]** OpenAPI docs (`/docs`, `/redoc`) accessible in production without auth → schema exposed.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Performance
|
|
28
|
+
|
|
29
|
+
- **[HIGH]** Synchronous I/O (`requests.get()`, blocking DB calls) inside `async def` route → blocks event loop. Use `httpx.AsyncClient`, async ORM.
|
|
30
|
+
- **[HIGH]** N+1 ORM queries — loading relationships in loop. Use `selectinload`/`joinedload` with SQLAlchemy async.
|
|
31
|
+
- **[HIGH]** Missing `async` on database session operations with async SQLAlchemy → sync in async context.
|
|
32
|
+
- **[HIGH]** `await` inside `for` loop → sequential DB calls; batch with `asyncio.gather()`.
|
|
33
|
+
- **[HIGH]** Missing pagination on list endpoints → unbounded query.
|
|
34
|
+
- **[HIGH]** Sync function passed to `run_in_executor` without proper thread pool → blocking thread pool worker.
|
|
35
|
+
- **[MEDIUM]** `background_tasks` used for critical path work → fires and forgets, no error handling.
|
|
36
|
+
- **[MEDIUM]** Pydantic v1 `orm_mode` / v2 `from_attributes` missing → ORM objects not serializable.
|
|
37
|
+
- **[MEDIUM]** DB connection not pooled → new connection per request.
|
|
38
|
+
- **[MEDIUM]** Not using `ORJSONResponse` for large JSON payloads → default `JSONResponse` slower.
|
|
39
|
+
- **[LOW]** Multiple middleware layers running on every request including static assets.
|
|
40
|
+
- **[LOW]** `@lru_cache` on dependency returning DB session → sessions shared across requests.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Architecture
|
|
45
|
+
|
|
46
|
+
- **[HIGH]** Business logic inside route function → delegate to service/repository layer.
|
|
47
|
+
- **[HIGH]** DB session directly in route (`db: Session = Depends(get_db)`) without proper lifecycle.
|
|
48
|
+
- **[HIGH]** Not using `APIRouter` per domain → everything in `main.py` → unmaintainable.
|
|
49
|
+
- **[HIGH]** No dependency injection for services → hardcoded globals, not testable.
|
|
50
|
+
- **[MEDIUM]** Missing `lifespan` context manager → using deprecated `@app.on_event("startup")`.
|
|
51
|
+
- **[MEDIUM]** Pydantic validators with side effects (DB queries in validators) → validators should be pure.
|
|
52
|
+
- **[MEDIUM]** Not using `@asynccontextmanager` for resource management in dependencies.
|
|
53
|
+
- **[MEDIUM]** Error handling missing global `@app.exception_handler` → default 500 for all errors.
|
|
54
|
+
- **[MEDIUM]** Not using `HTTPException` with proper status codes → all errors return 200 or 500.
|
|
55
|
+
- **[LOW]** Missing `tags`, `summary`, `description` on routers → poor OpenAPI docs.
|
|
56
|
+
- **[LOW]** Schema inheritance >2 levels deep → confusing field origins.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Code Quality
|
|
61
|
+
|
|
62
|
+
- **[HIGH]** Missing type annotations on route parameters → FastAPI cannot parse/validate.
|
|
63
|
+
- **[HIGH]** `except Exception` swallowing errors in route handlers.
|
|
64
|
+
- **[HIGH]** Pydantic `field_validator` (v2) not raising `ValueError` → validation silently ignored.
|
|
65
|
+
- **[MEDIUM]** `Optional[str]` vs `str | None` inconsistency (pick one per Python version).
|
|
66
|
+
- **[MEDIUM]** `Query(None)` for optional params not using `Annotated` style (modern FastAPI).
|
|
67
|
+
- **[MEDIUM]** Not using `Annotated[str, Query(min_length=1)]` for reusable param validation.
|
|
68
|
+
- **[MEDIUM]** Status codes not semantically correct (201 for create, 204 for delete, etc.).
|
|
69
|
+
- **[MEDIUM]** Missing `response_description` on endpoints → OpenAPI shows no response info.
|
|
70
|
+
- **[LOW]** `Path()` / `Query()` missing `description` → undocumented parameters.
|
|
71
|
+
- **[LOW]** Not using `model_config = ConfigDict(str_strip_whitespace=True)` on input models.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Common Bugs & Pitfalls
|
|
76
|
+
|
|
77
|
+
- **[HIGH]** Mutable default in Pydantic model (`field: list = []`) → shared across all instances.
|
|
78
|
+
- **[HIGH]** Async generator dependency not properly closed on exception → DB session leak.
|
|
79
|
+
- **[HIGH]** `HTTPException` raised inside `background_tasks` → silently swallowed, client got 200 already.
|
|
80
|
+
- **[HIGH]** Route handler `async def` but calling sync-heavy function without `asyncio.to_thread()` → event loop blocked.
|
|
81
|
+
- **[HIGH]** Pydantic v1 and v2 mixed in same project → validation behavior differences cause bugs.
|
|
82
|
+
- **[MEDIUM]** `Form()` and `Body()` used together → FastAPI doesn't support mixing form + JSON.
|
|
83
|
+
- **[MEDIUM]** `status_code=200` on POST creating resource → should be 201.
|
|
84
|
+
- **[MEDIUM]** Dependency exception not propagating correctly when dependency raises inside generator after `yield`.
|
|
85
|
+
- **[MEDIUM]** `PATCH` handler using `PUT` semantics (replacing entire object instead of partial update).
|
|
86
|
+
- **[MEDIUM]** Not using `model.model_dump(exclude_unset=True)` for PATCH → overwriting unset fields with defaults.
|
|
87
|
+
- **[LOW]** Not pinning pydantic version → v1/v2 breaking changes.
|
|
88
|
+
- **[LOW]** `uvicorn --reload` used in production → file watcher overhead.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Fastify — Stack-Specific Review Rules
|
|
2
|
+
|
|
3
|
+
> Applies to: GR · SR · PR · AR · BR
|
|
4
|
+
> Detection signals: from 'fastify', Fastify(), fastify.register(), fastify.route(), schema: with JSON Schema, @fastify/
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Security
|
|
9
|
+
- **[HIGH]** Missing `@fastify/helmet` plugin → security headers (CSP, HSTS, X-Frame-Options) absent. Register it as a global plugin before routes.
|
|
10
|
+
- **[HIGH]** No rate limiting via `@fastify/rate-limit` → brute-force and DoS succeed on public endpoints. Apply globally with per-route overrides.
|
|
11
|
+
- **[CRITICAL]** JSON schema validation disabled or bypassed (`schema` omitted, AJV strict mode off) → prototype pollution and arbitrary data accepted. Always define request schemas.
|
|
12
|
+
- **[HIGH]** Unvalidated file paths in `@fastify/multipart` → path traversal or overwrite attacks. Sanitize filenames and whitelist destination directories.
|
|
13
|
+
- **[HIGH]** CORS wildcard origin with `credentials: true` in `@fastify/cors` → cookies accepted from any domain. Set explicit origins or disable credentials when using wildcard.
|
|
14
|
+
- **[MEDIUM]** Response schema leaking internal fields (e.g., `password`, `internalId`) → sensitive data returned to clients. Define `schema.response` on every route.
|
|
15
|
+
- **[HIGH]** String fields not sanitized beyond JSON schema type checks → stored XSS or injection when data rendered. Add `format` or `pattern` constraints and sanitize.
|
|
16
|
+
- **[MEDIUM]** `fastify.register()` load order placing routes before auth plugin initialized → auth hooks absent. Register security plugins before route plugins.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Performance
|
|
21
|
+
- **[HIGH]** No `schema.response` defined → Fastify falls back to slow `JSON.stringify` instead of fast-json-stringify, reducing throughput 2-5x. Define response schemas on every route.
|
|
22
|
+
- **[CRITICAL]** Missing `return` before `reply.send()` in async handlers → execution continues post-response, causing "Reply already sent" crashes. Always write `return reply.send(...)`.
|
|
23
|
+
- **[HIGH]** Async handler not returning a value or promise → request left unresolved, client hangs. Return the promise or call `reply.send()` in every code path.
|
|
24
|
+
- **[HIGH]** Synchronous CPU-heavy operations (crypto, regex, large JSON) inside handlers → event loop blocked, degrading concurrent requests. Offload to worker threads or async chunks.
|
|
25
|
+
- **[MEDIUM]** Shared services not wrapped with `fastify-plugin` → each plugin scope re-instantiates the service, wasting memory and DB connections. Use `fp()` to share decorators.
|
|
26
|
+
- **[MEDIUM]** Same plugin registered multiple times in nested scopes → duplicate middleware overhead per request. Use `fastify-plugin` where cross-scope sharing is intended.
|
|
27
|
+
- **[MEDIUM]** Pino at `trace` or `debug` level in production → excessive log I/O overhead. Set `logger: { level: 'info' }` and use async pino transport.
|
|
28
|
+
- **[LOW]** No content-type when sending pre-serialized JSON strings → Fastify may re-serialize. Use `reply.type('application/json').send(str)`.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Architecture
|
|
33
|
+
- **[HIGH]** Business logic in route handler functions → untestable monoliths mixing HTTP and domain concerns. Extract into service modules and inject via `fastify.decorate()`.
|
|
34
|
+
- **[HIGH]** Shared services not registered via `fastify.decorate()` → services re-instantiated per module. Decorate the Fastify instance once at startup.
|
|
35
|
+
- **[MEDIUM]** Plugins lacking proper encapsulation → decorators and hooks leak across boundaries, causing ordering bugs. Use `fastify-plugin` only for deliberate cross-scope sharing.
|
|
36
|
+
- **[HIGH]** No `onRequest` or `preHandler` auth hooks on protected routes → routes accessible without a valid token. Scope an auth hook to protected route groups.
|
|
37
|
+
- **[MEDIUM]** All routes defined in one file → codebase grows unmanageable as app scales. Organize into domain-scoped plugins with `fastify.register()` and logical prefixes.
|
|
38
|
+
- **[MEDIUM]** No `fastify.addContentTypeParser()` for non-JSON request bodies → binary or form data silently rejected. Register parsers for all expected content types.
|
|
39
|
+
- **[LOW]** Routes not versioned (`/v1/`, `/v2/`) → breaking changes force simultaneous client updates. Establish prefix-based versioning from the outset.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Code Quality
|
|
44
|
+
- **[MEDIUM]** No TypeScript type provider (`@fastify/type-provider-typebox` or `@fastify/type-provider-zod`) → handler params typed as `any`. Integrate a type provider and colocate schemas with routes.
|
|
45
|
+
- **[MEDIUM]** Route schemas duplicated inline across routes → schema drift and maintenance burden. Register shared schemas with `fastify.addSchema()` and reference via ``.
|
|
46
|
+
- **[HIGH]** No `fastify.setErrorHandler()` → inconsistent error shapes from per-handler try/catch. Define a global error handler normalizing errors to a standard envelope.
|
|
47
|
+
- **[MEDIUM]** No custom `fastify.setNotFoundHandler()` → default 404 format breaks client error parsing. Register a not-found handler returning the standard error shape.
|
|
48
|
+
- **[LOW]** Full request objects logged including auth headers → tokens appear in log aggregators. Configure pino `redact` paths to strip sensitive fields.
|
|
49
|
+
- **[MEDIUM]** Not using `fastify.inject()` in tests → tests need a live port, causing EADDRINUSE conflicts. Use `fastify.inject()` for in-process HTTP testing.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Common Bugs & Pitfalls
|
|
54
|
+
- **[CRITICAL]** `reply.send()` without `return` in async callbacks → "Reply already sent" errors or double-send crashes. Always `return reply.send()`.
|
|
55
|
+
- **[HIGH]** Plugin not awaited at top level → plugin uninitialized when requests arrive, missing decorator errors. Always await `fastify.register()` or use a ready hook.
|
|
56
|
+
- **[HIGH]** Schema `` used before `fastify.addSchema()` → validation fails silently or throws at startup. Register all shared schemas before routes that reference them.
|
|
57
|
+
- **[MEDIUM]** `fastify.close()` not called in tests → port stays bound, EADDRINUSE on next run. Call `fastify.close()` in `afterAll` or `afterEach`.
|
|
58
|
+
- **[HIGH]** `fastify.listen()` without `host: '0.0.0.0'` in containers → server binds loopback only, unreachable externally. Set host based on deployment environment.
|
|
59
|
+
- **[MEDIUM]** No `onError` hook or uncaught exception handler → errors in lifecycle hooks crash the process without diagnostics. Attach an `onError` hook and process-level handler.
|
|
60
|
+
- **[MEDIUM]** Mixing async handlers with callback-style `reply.send()` → control-flow bugs where promise resolves before reply is sent. Pick one pattern and apply consistently.
|