Building in public — waitlist open

Code ships. Threats don't.

Syntinel reviews every Pull Request the way an attacker would, then writes a test that exploits each serious finding and runs it in an isolated sandbox — so you see proof, not a guess. Critical issues block the merge.

pull_request #47 feat: payment processor
10def process_payment(amount, user_id):
11 # charge the connected account
12 charge = stripe.Charge.create(
13 amount=amount * 100,
14 currency="usd",
15 customer=user_id,
16 )
17 return charge.id
critical · null pointer
2 critical 1 high
merge blocked

AI writes the code.
Who reviews the reviewer?

AI-generated code ships fast and introduces subtle bugs the linter was never designed to catch. Human reviewers skim for obvious issues. CI tests only what you remembered to test — not the edge cases an attacker will find first.

01

Linters don't think

Static analysis catches patterns. It cannot reason about what happens when amount is -9999, or what breaks when two threads collide on the same record.

02

Tests cover the happy path

Developers write tests for the code they meant to write. Adversarial edge cases — the ones attackers reach for first — stay uncovered until production finds them.

03

CI catches it after the fact

A failed pipeline 10 minutes after push is a broken flow. Context has switched. The branch is polluted. The cost is measured in reviewer time, not compute.

Two engines. One verdict.

Step 01

PR opened

A developer opens a pull request. Syntinel receives the GitHub webhook and starts reviewing the diff straight away. Every new push to the PR is reviewed again.

Step 02

Adversarial review

An AI model prompted to think like an attacker reviews logic, data flow and security boundaries — injection, broken authorization, path traversal, weak crypto, leaked secrets.

Step 03

Exploit tests, sandboxed

For each high or critical finding, Syntinel writes a test that attacks the code with the finding's proof input and runs it in a locked-down sandbox — no network, read-only filesystem. On our seeded benchmark, 4 of 5 vulnerability classes are confirmed this way on every run, and each test passes once the bug is fixed. Python today.

Step 04

Verdict posted

Findings are posted as a PR comment with severity, proof and a fix. A CRITICAL finding fails the Syntinel check, which blocks the merge when the check is required. The team sees exactly what failed and why.

Review in the pull request. Pre-empt it at the keyboard.

GitHub App

The team's safety net.

Every PR reviewed automatically, with a merge-blocking check on critical findings. Opening to waitlist teams first.

  • PR review comments with severity labels
  • Exploit test run in a sandbox for high and critical findings
  • Merge blocking on CRITICAL findings
  • Check run status on every commit
  • Re-reviewed on every push to the PR
Join the waitlist
CLI package

The developer's weapon.

Run it before you commit. Scans your whole repository and writes a client-ready report — no waiting on CI.

$pip install syntinel
  • Full-repository adversarial AI review
  • Markdown and JSON reports
  • Semgrep deterministic rules included
  • Pre-commit hook integration
  • Exits non-zero on CRITICAL — CI-ready
Join the waitlist

Watch it find what you missed.

Illustrative examples of the findings Syntinel reports. Click a tab.

1from stripe import Charge
2from .db import conn
3
4def process_payment(amount, user_id):
5 # Charge the user's card
6 cents = amount * 100
7 charge = Charge.create(
8 amount=cents, customer=user_id
9 )
10 return charge.id
11
12def find_txn(user_id, ref):
13 cur = conn.cursor()
14 # Lookup by reference
15 cur.execute(f"SELECT * FROM txn WHERE ref='{ref}'")
16 return cur.fetchone()
1import hashlib
2
3def hash_password(pw): return hashlib.md5(pw.encode()).hexdigest()
4
5def verify(user, pw):
6 stored = users.get(user)
7 if not stored: return False
8 return hash_password(pw) == stored.password
9
10from flask import Blueprint, request
11bp = Blueprint("auth", __name__)
12
13@bp.route("/login", methods=["POST"])
14def login():
15 body = request.json or {}
16 u = body.get("user")
17 p = body.get("pass")
18 if verify(u, p):
19 return issue_token(u)
20 return {"error": "bad creds"}, 401
21
22# TODO: rate limit — no throttle on this route
1from flask import Flask, request, jsonify
2app = Flask(__name__)
3
4@app.route("/compute", methods=["POST"])
5def compute():
6 body = request.get_json()
7 expr = body.get("expr", "")
8
9 # Quick-and-dirty calculator endpoint
10 try:
11 result = eval(expr)
12 except Exception as e:
13 return jsonify(error=str(e)), 400
14 return jsonify(result=result)
Syntinel found 2 critical issues — merge would be blocked.
illustrative example

Your next PR.

Is Syntinel watching it?