Inspiration
A maintainer reviewing community pull requests almost merged pull_request_target combined with a checkout of the PR's head commit the pattern that lets a stranger's code run with your repo's secrets. It was caught by luck, not process. That's not a rare near-miss; it's a well-documented attack pattern that still gets merged into real repositories because catching it depends on a human remembering one specific YAML combination under deadline pressure. We wanted that to stop depending on luck.
What it does
Gatecheck is a GitHub App that watches every push and pull request and can halt a CI pipeline before any step runs not flag it after the fact. It has two engines sharing one dashboard: a deterministic rule engine (16 detectors, zero LLM) that parses workflow YAML via a real AST walk to catch supply-chain RCE patterns, hardcoded secrets, privileged containers, and unpinned actions in milliseconds; and a six-agent Gemini 2.5 Flash pipeline that reviews every PR in parallel across security, bugs, performance, readability, best practices, and docs, synthesized into one verdict with a confidence score. A published GitHub Action queries our decision API as the first step of any workflow and exits non-zero before checkout if the verdict is halt.
How we built it
Gatecheck is a GitHub App that watches every push and pull request and can halt a CI pipeline before any step runs not flag it after the fact. The halt decision lives in Aurora DSQL, and the database isn't just storage: when a push webhook and a re-run scan both try to write a verdict for the same commit, a SQL-level severity-rank guard ensures a lower-severity write can never downgrade a stored critical halt to an allow, even on a retry concurrency safety enforced by the database itself, not application code that could be wrong. On top of that sits two engines sharing one dashboard: a deterministic rule engine (16 detectors, zero LLM) that parses workflow YAML via a real AST walk to catch supply-chain RCE patterns, hardcoded secrets, privileged containers, and unpinned actions in milliseconds; and a six-agent Gemini 2.5 Flash pipeline that reviews every PR in parallel across security, bugs, performance, readability, best practices, and docs, synthesized into one verdict with a confidence score. A published GitHub Action queries our decision API backed by that same DSQL table as the first step of any workflow and exits non-zero before checkout if the verdict is halt.
Accomplishments that we're proud of
Getting the most-severe-wins conflict resolution enforced entirely in SQL a single INSERT ... ON CONFLICT with a severity-rank guard rather than trusting application code to get a security-critical comparison right every time. Proving the halt mechanism live, end to end push a bad PR, watch the dashboard log it, watch the Action fail before checkout. And keeping the deterministic gate genuinely fast and free of LLM cost, so the always-on security layer stays cheap to run no matter how much PR volume scales.
Challenges we ran into
The real challenge was Aurora DSQL itself — in a good way. DSQL has no row-level locking: no SELECT FOR UPDATE, no SKIP LOCKED. Our job queue couldn't use the standard claim pattern, so claimNextJob does an optimistic SELECT then a conditional UPDATE ... WHERE status = 'pending' — if a second worker already claimed the row, the update just returns nothing instead of double-processing. The harder version of the same problem: a push webhook and a re-run scan can both try to write the halt verdict for the same commit at the same time, and the loser must never downgrade a block to an allow — that's not a bug, it's a security regression. We solved it with one SQL statement: INSERT ... ON CONFLICT where every column is guarded by a severity-rank CASE, so a lower-severity write can't overwrite a stored critical decision even on a retry. We catch DSQL's actual serialization-failure code (40001) and retry with exponential backoff.
What we learned
A database's limitation is often a design instruction, not an obstacle DSQL not having row locks didn't make the queue harder to build correctly, it made the easy-but-wrong version (a lock-based queue assuming no contention) impossible to reach for. What we got in return was a database that's honest about conflict instead of hiding it behind a queue: DSQL tells you immediately, via a real serialization error, the instant two writes actually collide, rather than silently serializing them behind a lock and letting you assume correctness you never verified. Optimistic concurrency control rewards idempotent design over conflict avoidance: once job claiming could tolerate an occasional double-claim, because the halt-decision writer could absorb a duplicate scan without ever losing a verdict, the two pieces only needed to be correct together, not perfect individually. That's also exactly the property that lets DSQL scale as a distributed system without a single point of lock contention slowing everything else down.
What's next for GateCheck
Expanding the rule library privileged containers, shell-injection via untrusted PR titles, self-hosted runner exposure. Auto-fix pull requests for findings with a deterministic fix, like pinning an action to a SHA. Organization-level enrollment, one click to cover every repo with a posture rollup dashboard. Cross-customer false-positive learning, downgrading a rule's default severity if it's consistently muted across many repos. And a self-hostable deployment option for regulated customers who can't use SaaS-only tooling.
Built With
- amazon-web-services
- dsql
- gemini
- llm
- vercel


Log in or sign up for Devpost to join the conversation.