Published on

How I Added Snyk, SonarQube Cloud, and Kilo Code Review to Every PR in My Monorepo

By Andrew Blase
Authors
  • avatar
    Name
    Andrew Blase
    Twitter

The Case for a Security and Quality Layer on Every PR

This is the third article in my CI/CD series for TypeScript monorepos. In the first article in this series, I covered setting up Jest with an 80% coverage gate — the testing infrastructure that everything else depends on. In the second article in this series, I walked through Husky and GitHub Actions to enforce typechecking, linting, and tests on every PR. This article adds the security and code quality scanning layer on top.

I run a TypeScript monorepo — NestJS backend, Next.js frontend, shared packages. With the test infrastructure and basic CI pipeline in place, the next gap was clear: nothing was checking for vulnerable dependencies, static code quality issues, or catching the subtle problems that slip through automated testing.

Here's what I was still missing:

  • A dependency with a known CVE merging silently because nobody ran npm audit
  • AI-generated code that compiles fine but introduces a logic bug or dead code path
  • Dependency updates arriving in 15 separate PRs per week, each one creating context-switch noise

This article is the exact setup I put in place to fix all three. It's not theory — it's the config I'm running today. By the end, every PR in my repo goes through:

  1. Snyk — dependency vulnerability scanning
  2. SonarQube Cloud — static analysis, quality gates, security hotspots
  3. Kilo Code Review — AI-powered inline review on the diff
  4. Dependabot — limited to one PR per day per ecosystem so the board stays clean

Let's walk through each one.

Developer reviewing a secure pull request with Snyk SonarQube and Kilo code review badges glowing in a colorful cartoon workspace

Direct Answer

To add automated security and code quality checks to every GitHub pull request: (1) create a Snyk account and add the snyk/actions workflow, (2) connect your repo to SonarQube Cloud and add the SonarSource/sonarqube-scan-action workflow, (3) install the Kilo GitHub App and enable Code Reviews in your Kilo dashboard, and (4) configure Dependabot with open-pull-requests-limit: 1 to prevent PR flooding. All four run automatically on every PR with no manual intervention required.


1. Snyk — Catch Vulnerable Dependencies Before They Hit Main

What it does: Snyk scans your package.json (and lockfile) against its vulnerability database and fails the check if it finds any issues above your severity threshold. It catches CVEs in direct and transitive dependencies.

Why it matters for TypeScript projects: You're pulling in dozens of packages per service. One vulnerable transitive dependency in your NestJS backend can expose an entire API. Snyk catches this automatically — before the merge.

Cost: Free for open source and small teams. Paid tiers add team management, license compliance, and container scanning.

Setup

Step 1: Create a Snyk account

Go to snyk.io and sign up. Connect your GitHub account.

Step 2: Get your Snyk API token

In your Snyk dashboard: Settings → General → Auth Token. Copy it.

Step 3: Add it as a GitHub secret

In your repo: Settings → Secrets and variables → Actions → New repository secret

Name it SNYK_TOKEN.

Step 4: Add the workflow

# .github/workflows/snyk.yml
name: Snyk Security Scan

on:
  pull_request:
    branches:
      - main
      - develop

jobs:
  snyk:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Run Snyk to check for vulnerabilities
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high

The --severity-threshold=high flag means Snyk only fails the build on high or critical CVEs. You can drop it to medium if you want stricter enforcement — I recommend starting with high to avoid alert fatigue.

For a monorepo, run Snyk in each package directory:

      - name: Snyk - backend
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high
        working-directory: ./backend

      - name: Snyk - frontend
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high
        working-directory: ./frontend

Once this is running, you'll see a Snyk check on every PR. If it fails, you have a vulnerable package somewhere in the dependency tree. Fix it before merging.


2. SonarQube Cloud — Static Analysis That Actually Finds Real Problems

What it does: SonarQube Cloud (formerly SonarCloud) runs static analysis on your source code and reports code smells, bugs, security hotspots, duplications, and test coverage gaps. It posts a "Quality Gate" result directly on the PR.

Why it matters: This is the one that catches AI-generated code problems. When you use Copilot or Claude to write a chunk of code, it often compiles and even passes tests — but it introduces dead code, deeply nested conditionals, or subtle security patterns that SonarQube flags. I've caught several real issues this way.

Cost: Free for public repos. Paid for private (pricing is per line of code analyzed).

Setup

Step 1: Create a SonarQube Cloud account

Go to sonarcloud.io and sign up with your GitHub account.

Step 2: Set up your organization and project

Click Analyze new project → Select your GitHub repo → Follow the setup wizard. SonarQube Cloud will create an organization tied to your GitHub org.

Step 3: Get your SONAR_TOKEN

In SonarQube Cloud: My Account → Security → Generate Token. Copy it.

Step 4: Add the GitHub secret

Settings → Secrets and variables → Actions → New repository secret

Name it SONAR_TOKEN.

Step 5: Add sonar-project.properties

Create this file in your repo root:

# sonar-project.properties
sonar.projectKey=your-org_your-repo
sonar.organization=your-org
sonar.sources=.
sonar.exclusions=**/node_modules/**,**/*.test.ts,**/*.spec.ts
sonar.tests=.
sonar.test.inclusions=**/*.test.ts,**/*.spec.ts
sonar.typescript.lcov.reportPaths=coverage/lcov.info

Replace your-org_your-repo and your-org with the values SonarQube Cloud assigned during setup.

Step 6: Add the workflow

# .github/workflows/sonar.yml
name: SonarQube Cloud Analysis

on:
  pull_request:
    branches:
      - main
      - develop

jobs:
  sonarcloud:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Required for branch analysis

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'

      - name: Install dependencies
        run: npm ci

      - name: Run tests with coverage
        run: npm run test:coverage

      - name: SonarQube Scan
        uses: SonarSource/sonarqube-scan-action@v3
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}

The fetch-depth: 0 on checkout is important — SonarQube needs full git history for accurate branch analysis and new-code tracking.

Once this is running, SonarQube Cloud will post a PR decoration showing the Quality Gate status (pass/fail) and a breakdown of new issues introduced by the PR. You can configure the Quality Gate thresholds in the SonarQube Cloud dashboard.


3. Kilo Code Review — AI Review on the Diff Before a Human Looks at It

What it does: Kilo's Code Review Agent connects to GitHub via a GitHub App and automatically reviews every pull request when it's opened or updated. It analyzes the diff and posts inline comments on specific lines, plus a summary comment with overall findings. Severity tags (critical, warning, info) make triage fast.

Why it matters: Human code review is valuable but slow. Kilo does the first pass — catching the obvious issues, missed edge cases, and potential bugs — before anyone spends time in the review queue. In practice, it catches things I'd otherwise miss because I'm reviewing my own code.

Cost: Requires a Kilo Code account at app.kilo.ai. Uses credits per review based on the AI model used. No GitHub Actions runner cost since it runs on Kilo's infrastructure.

Setup

This is the easiest of the three to set up — there's no workflow YAML to write. Kilo runs entirely through its GitHub App.

Step 1: Create a Kilo Code account

Go to app.kilo.ai and create an account.

Step 2: Install the KiloConnect GitHub App

In your Kilo dashboard: Integrations → GitHub → Configure

You'll be redirected to GitHub to authorize the KiloConnect app. Choose whether to grant access to all repositories or specific ones, then click Install & Authorize.

Step 3: Configure the Code Review Agent

Navigate to Code Reviews in your Kilo dashboard (personal: app.kilo.ai/code-reviews, or your organization dashboard).

Configure:

  • AI Model — Defaults to Claude Sonnet 4.5; you can switch models
  • Review Style — Strict, Balanced, or Lenient (I use Balanced)
  • Repository Selection — All repos or specific ones
  • Focus Areas — Security, performance, bugs, style, testing, documentation (pick what matters to your team)
  • Max Review Time — 5 to 30 minutes
  • Custom Instructions — Optional team-specific guidelines (e.g., "This is a NestJS project using dependency injection — flag any violations of SOLID principles")

Step 4: Open a pull request

That's it. When you open a PR (or push new commits to one), Kilo automatically triggers a review. It posts a summary comment and inline comments directly on the PR diff.

What the review looks like in practice:

Kilo posts a top-level comment summarizing findings, then inline comments like:

⚠️ warning: This function has a cyclomatic complexity of 14. Consider breaking it into smaller functions for maintainability.

🔴 critical: Potential SQL injection risk — user input is being concatenated directly into the query string at line 47.

When you push new commits, the previous review is automatically cancelled and a fresh one starts — no stale feedback cluttering the PR.

Note: Kilo Code Review runs on Kilo's cloud infrastructure. No GitHub Actions workflow file is needed for the Code Review feature specifically. If you want to trigger Kilo as a command-line agent via PR comments (e.g., /kilo do X), there's a separate GitHub Action for that — but for automated code review, the GitHub App handles everything.


4. Dependabot — One PR Per Day, Not Twenty

Dependabot is great in theory and exhausting in practice if you don't tune it. By default, it can open a separate PR for every outdated package — which, in an active monorepo, means waking up to 15–20 open PRs that all need attention.

The fix is two settings:

  1. open-pull-requests-limit: 1 per ecosystem — caps how many open Dependabot PRs exist at once
  2. groups — bundles related packages (like all dev dependencies) into a single PR

Here's the config I'm running for a three-package monorepo (root, /frontend, /backend):

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
    open-pull-requests-limit: 1
    groups:
      dev-dependencies:
        patterns:
          - "*"
        exclude-patterns:
          - "eslint*"
          - "prettier*"

  - package-ecosystem: "npm"
    directory: "/frontend"
    schedule:
      interval: "daily"
    open-pull-requests-limit: 1

  - package-ecosystem: "npm"
    directory: "/backend"
    schedule:
      interval: "daily"
    open-pull-requests-limit: 1

Why I excluded eslint* and prettier* from the group: These tools sometimes have breaking config changes between major versions. I'd rather review their PRs individually than have a grouped update silently break my linting setup.

With this config, I get at most one new Dependabot PR per package directory per day. When the previous one merges, a new one opens. The board stays manageable.


How It All Works Together

When a developer opens a PR against main or develop:

  1. Snyk runs and checks all affected package.json files for CVEs. If a high/critical vulnerability exists in the dependency tree, the check fails.
  2. SonarQube Cloud runs static analysis on the changed code and posts a Quality Gate result. It shows how many new issues the PR introduces.
  3. Kilo Code Review triggers automatically via the GitHub App, analyzes the diff, and posts inline comments + a summary.
  4. All three show up as required checks in the PR (you can mark them required under Settings → Branch protection rules).

The result: by the time a human reviewer looks at the PR, the obvious issues are already flagged. Vulnerable dependencies are blocked. Code quality regressions are visible. The human review can focus on architecture, intent, and context — not syntax or CVEs.

Dependabot runs in the background, feeding the CI pipeline with one dependency update per day per package. Each Dependabot PR goes through the same Snyk and SonarQube checks, which is exactly the behavior you want — you're not patching vulnerabilities while introducing new ones.


Conclusion

This is the final layer of the three-part CI/CD stack. The first article in this series gave you a test infrastructure with an 80% coverage gate. The second article in this series wired up Husky and GitHub Actions so typecheck, lint, and tests run on every PR automatically. This article adds the security and quality scanning layer that catches what tests and linting can't.

This setup took me a few hours to put together and has already saved real time. Snyk caught a vulnerable version of jsonwebtoken in a Dependabot PR before I merged it. SonarQube flagged a dead code path in an AI-generated NestJS service. Kilo caught a missing null check I'd have caught eventually — but it caught it in five minutes instead of during QA.

None of these tools require ongoing maintenance once they're configured. They just run. That's the point.

If you're building on TypeScript — whether it's a NestJS/Next.js monorepo like mine or a single-service project — this is a reasonable security and quality baseline. The free tiers on Snyk and SonarQube make it accessible even for solo projects. Kilo's credits-based pricing means you only pay when a review runs.

Set it up once. Let it run on every PR.


FAQ

Is Snyk GitHub Actions free for private repositories?

Snyk offers a free tier that includes limited scans per month for private repositories, and is fully free for open source projects. For small teams (up to 3 developers), the free tier is typically sufficient for CI/CD integration. Paid plans unlock unlimited tests, team management, and additional features like container and IaC scanning.

Does SonarQube Cloud work with TypeScript monorepos?

Yes. You configure a single sonar-project.properties file at the root with sonar.sources pointing to your source directories. For monorepos, you can also set up multiple SonarQube projects (one per package) and use the sonar.projectKey field to separate them. The SonarSource/sonarqube-scan-action supports this pattern natively.

How does Kilo Code Review handle large PRs?

Kilo has a configurable max review time (5–30 minutes). For very large PRs, it may not review every file in the diff — it prioritizes the most impactful changes. If reviews are timing out on large PRs, increase the max review time in your Code Reviews configuration on the Kilo dashboard. Alternatively, keeping PRs small (a good practice anyway) ensures Kilo can do a complete review every time.


Sources: Snyk GitHub Actions documentation, SonarQube Cloud GitHub Actions setup, Kilo Code Reviews documentation, GitHub Dependabot configuration reference