Sixta Connect

Gate risky migrations in CI

The same DBRE-grade analysis, on every pull or merge request. When a change touches a migration (Django, Alembic, Flyway, Liquibase), a .sql file, or native SQL in Java code, SIXTA reviews it, comments the findings, annotates the diff, and fails the check if it's dangerous, before it merges.

This page is the setup. For what the gate checks and why it works, read the CI gate explained.

What you get

A comment

The full report on the MR/PR, upserted in place. Never spammy.

Inline findings

On the diff: GitLab Code Quality, or GitHub code scanning via SARIF.

A gate

The build fails at a severity you choose (High by default). Tune it, or soak with it off.

One free key powers both your editor and your pipeline. Get a key, add it as a CI secret named SIXTA_API_KEY, then pick your platform and your migration framework below and copy the matching snippet.

GitHub

Two ways in. Repositories whose migrations are plain .sql files (Flyway, Prisma, golang-migrate, dbmate) need only the GitHub App: install it, link it, done. Django, Alembic, and Spring Boot migrations are rendered to SQL inside your CI, so those repositories use the workflow below (which also covers .sql, and runs anywhere Actions does).

The GitHub App: install, link, done

Install the GitHub App

  1. Install the App on the repositories you want reviewed.
  2. Link the installation to your SIXTA account when the install flow lands you on the setup page. A free key takes about 30 seconds; linking names the account your reviews run under.
  3. Open a pull request that changes a .sql migration. That's the whole setup.

Installed already? Finish the setup here.

Every such pull request gets the full report as a comment, the SIXTA review check by sixta-connect[bot] (require it in branch protection to make the gate binding), and confirmed saves on your review record. An optional .sixta.yml supplies engine and engine_version hints; Flyway undo files and .down.sql/.rollback.sql companions feed the rollback audit. A repository that runs the workflow below keeps the workflow authoritative: never two reviews.

The workflow: Django, Alembic, Spring Boot (and .sql anywhere)

For migrations that must be rendered in your environment, save your framework's snippet as .github/workflows/sixta.yml and add SIXTA_API_KEY as a repository secret (Settings → Secrets and variables → Actions). The PR comment uses the built-in GITHUB_TOKEN; the permissions: block grants what it needs, including id-token: write so the App (when installed) posts the SIXTA review check for the run. fetch-depth: 0 is required so the review can diff against the PR base. Expand your framework:

Plain .sql migrations (Prisma, golang-migrate, dbmate)

The kit reads changed .sql files directly. There is nothing to render, so this config has no service container, no DATABASE_URL, and no setup input.

name: SIXTA SQL review
on:
  pull_request:
    paths: ["**/*.sql"]
permissions:
  contents: read
  pull-requests: write      # PR comment
  security-events: write    # SARIF upload
  id-token: write           # the “SIXTA review” check by the sixta-connect App (optional)
jobs:
  sixta:
    runs-on: ubuntu-latest
    env:
      SIXTA_API_KEY: ${{ secrets.SIXTA_API_KEY }}
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }        # the review diffs against the PR base
      - uses: sixta-systems/sixta-ci@v1
        with:
          engine_version: "16"          # match production (verdicts are version-dependent)
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: sixta.sarif }
Spring Boot (Flyway or Liquibase)

Flyway migrations and SQL-format Liquibase changelogs are read directly: no database, no JVM, no setup input. The engine is auto-detected from pom.xml, build.gradle, or application.* (set engine to override). Flyway ${placeholder} values resolve from spring.flyway.placeholders.*, undo files feed the rollback audit, and Java-based Flyway migrations are flagged for human review. Native SQL in Java code (@Query(nativeQuery = true), JdbcTemplate, MyBatis mappers) is reviewed as queries. XML/YAML Liquibase changelogs additionally need the Liquibase CLI on the runner (install it via setup); SQL-format changelogs need nothing.

name: SIXTA SQL review
on:
  pull_request:
    paths: ["**/db/migration/**", "**/db/changelog/**", "**/*.sql", "**/*.java", "**/mapper/**/*.xml"]
permissions:
  contents: read
  pull-requests: write      # PR comment
  security-events: write    # SARIF upload
  id-token: write           # the “SIXTA review” check by the sixta-connect App (optional)
jobs:
  sixta:
    runs-on: ubuntu-latest
    env:
      SIXTA_API_KEY: ${{ secrets.SIXTA_API_KEY }}
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }        # the review diffs against the PR base
      - uses: sixta-systems/sixta-ci@v1
        with:
          engine_version: "16"          # match production (verdicts are version-dependent)
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: sixta.sarif }
Django

Django migrations are Python, so the job renders them to SQL with sqlmigrate first. That needs your project installed (the setup input) and a database for the planner: the disposable, empty postgres service below, never your data.

name: SIXTA SQL review
on:
  pull_request:
    paths: ["**/migrations/*.py", "**/*.sql"]
permissions:
  contents: read
  pull-requests: write      # PR comment
  security-events: write    # SARIF upload
  id-token: write           # the “SIXTA review” check by the sixta-connect App (optional)
jobs:
  sixta:
    runs-on: ubuntu-latest
    services:
      postgres:             # disposable empty DB for sqlmigrate rendering, never your data
        image: postgres:16
        env: { POSTGRES_DB: sixta_ci, POSTGRES_USER: sixta_ci, POSTGRES_PASSWORD: sixta_ci }
        ports: ["5432:5432"]
        options: >-
          --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
    env:
      SIXTA_API_KEY: ${{ secrets.SIXTA_API_KEY }}
      DATABASE_URL: postgres://sixta_ci:sixta_ci@localhost:5432/sixta_ci
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }        # the review diffs against the PR base
      - uses: sixta-systems/sixta-ci@v1
        with:
          engine_version: "16"          # match production (verdicts are version-dependent)
          setup: pip install -r requirements.txt    # whatever makes manage.py runnable
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: sixta.sarif }
Alembic (SQLAlchemy)

Alembic migrations render offline via alembic upgrade --sql, so the job installs Alembic (the setup input) and needs no database at all. If your config isn't at the default alembic.ini, point at it with the alembic_config input. Data migrations (op.bulk_insert, op.get_bind) are flagged for human review, like Django's RunPython.

name: SIXTA SQL review
on:
  pull_request:
    paths: ["**/versions/*.py", "**/*.sql"]
permissions:
  contents: read
  pull-requests: write      # PR comment
  security-events: write    # SARIF upload
  id-token: write           # the “SIXTA review” check by the sixta-connect App (optional)
jobs:
  sixta:
    runs-on: ubuntu-latest
    env:
      SIXTA_API_KEY: ${{ secrets.SIXTA_API_KEY }}
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }        # the review diffs against the PR base
      - uses: sixta-systems/sixta-ci@v1
        with:
          engine_version: "16"          # match production (verdicts are version-dependent)
          setup: pip install alembic    # renders migrations offline, no database needed
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with: { sarif_file: sixta.sarif }

GitLab

A merge-request comment, inline Code Quality findings, and a pipeline gate. Pick your framework and add the include to .gitlab-ci.yml (GitLab ≥ 16.6). Store SIXTA_API_KEY as a masked CI/CD variable (Settings → CI/CD → Variables). For MR comments, also add SIXTA_BOT_TOKEN (a project access token, api scope, Reporter role). CI_JOB_TOKEN can't post notes. Merge-request pipelines must be enabled; the job triggers on merge_request_event and never runs in branch-only pipelines (the most common "it doesn't run" cause).

Plain .sql migrations (Prisma, golang-migrate, dbmate)

Changed .sql files are read directly. No setup input, no database.

include:
  - component: gitlab.com/sixta-systems/sixta-ci/sixta-review@0.8
    inputs:
      engine_version: "16"    # match production (verdicts are version-dependent)

Spring Boot (Flyway or Liquibase)

Flyway migrations and SQL-format Liquibase changelogs are read directly, and the engine is auto-detected from your build files (set engine to override). The template's default setup no-ops when there is no requirements.txt, so a JVM repo needs only this. XML/YAML Liquibase changelogs additionally need the Liquibase CLI on the runner, installed via the setup input.

include:
  - component: gitlab.com/sixta-systems/sixta-ci/sixta-review@0.8
    inputs:
      engine_version: "16"    # match production (verdicts are version-dependent)

Django

The template brings its own disposable, empty database service for sqlmigrate rendering (it never sees your data); you only supply the install command.

include:
  - component: gitlab.com/sixta-systems/sixta-ci/sixta-review@0.8
    inputs:
      engine_version: "16"                      # match production (verdicts are version-dependent)
      setup: pip install -r requirements.txt    # whatever makes manage.py runnable

Alembic (SQLAlchemy)

Renders offline, no database. If your config isn't at the default alembic.ini, add the alembic_config input.

include:
  - component: gitlab.com/sixta-systems/sixta-ci/sixta-review@0.8
    inputs:
      engine_version: "16"          # match production (verdicts are version-dependent)
      setup: pip install alembic    # renders migrations offline, no database needed

@0.5 follows patch releases automatically; pin @0.5.0 for an exact version (the component uses plain semver, not the v1 tag the GitHub Action uses). On self-managed GitLab instances that can't reach the gitlab.com catalog, swap the component: line for the remote include, with the same inputs: - remote: "https://raw.githubusercontent.com/sixta-systems/sixta-ci/v1/templates/sixta-review.yml"

Common inputs

inputdefaultwhat it does
engine_version(none)Production engine version (e.g. 16 or 8.0.35). Verdicts are version-dependent, so set it.
gatehighFail at ≥ this severity: critical / high / medium / low / none.
fail_modeopenSIXTA unreachable → open: warn & pass; closed: fail. Findings always gate.
engineautopostgresql or mysql; auto detects it from pom.xml / build.gradle / application.* and falls back to postgresql.
setup(none)Install command for frameworks whose migrations need rendering (Django: your project's requirements; Alembic: pip install alembic; Liquibase XML/YAML: the Liquibase CLI). Omit for .sql-only and Flyway repos.

The full input reference, the pre-commit hook, and a runnable demo are in the CI kit repo. Works today on Django, Alembic, Flyway, and Liquibase migrations, plain .sql files, and native SQL embedded in Java code and MyBatis mappers; other frameworks aren't supported yet.

Find us on the GitHub Marketplace →  ·  and the GitLab CI/CD Catalog →

Prefer to work in your editor? SIXTA is a one-URL MCP connector too: same analysis, the DBA at your cursor.

Get your free key →