Skip to content

Repository files navigation

Gitnet API

Backend for Gitnet — an autonomous AI software engineering platform. FastAPI + async SQLAlchemy + Postgres, with a GitHub App integration, a sandboxed project workspace, a controlled command execution layer, and a Gemini-powered AI agent that works through the same tools a human user has.

Frontend (Next.js, Vercel) is a separate repository and talks to this API over HTTPS + JWT bearer auth.

Architecture at a glance

app/
  core/        settings, db engine, JWT, password hashing, error types, logging
  models/      SQLAlchemy models (see schema.sql for the raw DDL)
  schemas/     Pydantic request/response models
  routers/     FastAPI routers (one per resource area)
  services/    auth, GitHub App client, import, log broker, cleanup
  workspace/   sandboxed file/dir access, git operations, zip import
  commands/    controlled subprocess executor + Gitnet command registry + workflow engine
  ai/          Gemini client, tool schemas, tool dispatcher, agent loop, skills/rules
alembic/       DB migrations
tests/         pytest suite (unit + integration)
schema.sql     hand-maintained mirror of the SQLAlchemy models, for direct review/apply

Security model (read this before changing workspace/command code)

Every file and command operation for a project workspace funnels through a small number of choke points, deliberately kept small so they're easy to audit:

  • app/workspace/paths.pyresolve_workspace_path() is the only sanctioned way to turn a user-supplied relative path into a filesystem path. It rejects absolute paths, null bytes, and anything that resolves (after following symlinks) outside the workspace root. All file read/write/delete/list operations go through this.
  • app/workspace/zip_import.py — validates every entry in an uploaded ZIP before extracting anything: rejects path traversal, symlink entries, oversized files, and suspiciously high compression ratios (zip bombs).
  • app/commands/executor.py — the only place a subprocess is ever spawned. No shell is invoked (asyncio.create_subprocess_exec, not shell=True), the binary must be on an explicit allowlist, a denylist of shell metacharacters is checked, the working directory is pinned to the project workspace, the environment is scrubbed of backend secrets, and every call has a hard timeout.
  • AI tools (app/ai/tool_dispatcher.py) call into these exact same primitives — the AI has no separate, less-restricted code path. If a human can't do it through the API, the AI can't do it either.

Local setup

Requires Python 3.12, Postgres 14+, and pip.

python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

cp .env.example .env
# fill in DATABASE_URL, SESSION_SECRET at minimum to run locally

# apply the schema (either works; Alembic is authoritative for prod)
psql "$DATABASE_URL" -f schema.sql
# or:
alembic upgrade head

uvicorn app.main:app --reload

Visit http://127.0.0.1:8000/health to confirm it's up, /docs for interactive API docs (FastAPI's built-in Swagger UI).

Running tests

pip install -r requirements.txt
createdb gitnet_test   # or point DATABASE_URL at any throwaway Postgres db
DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost/gitnet_test \
  ENV=test pytest -q

The suite creates its own tables on first run and truncates between tests — no manual fixture data needed. Security-critical paths (path traversal, zip-slip, symlink injection, zip bombs, command injection, cross-user authorization) each have dedicated tests in tests/unit/ and tests/integration/.

Deploying

Database: Neon (serverless Postgres) works well for the free tier. Make sure your DATABASE_URL includes ?ssl=require (or sslmode=require) for Neon.

Backend: Render, as a standard Python web service.

  • Build command: pip install -r requirements.txt
  • Start command: uvicorn app.main:app --host 0.0.0.0 --port $PORT
  • Set all variables from .env.example in Render's environment settings.
  • Run alembic upgrade head once (Render's shell, or a one-off deploy hook) before the first request hits a fresh database.

Frontend: Vercel, pointed at ALLOWED_ORIGINS for CORS.

GitHub App: create one at https://github.com/settings/apps/new with:

  • Repository permissions: Contents (read/write), Pull requests (read/write), Metadata (read)
  • Webhook URL: https://<your-backend>/github/webhook
  • Generate a private key from the app settings page and set it as GITHUB_PRIVATE_KEY (escape newlines as \n if storing as a single env var).

Gemini: get API keys from Google AI Studio. Up to 3 keys can be configured (GEMINI_API_KEY_1/2/3) — Gitnet rotates to the next key automatically when one hits a quota/rate error, with a cooldown before retrying an exhausted key. This is entirely optional; /ai/tasks will return a clear error if no keys are set, everything else works without it.

API surface (see /docs for full schemas)

Area Routes
Auth POST /auth/register, /login, /refresh, /logout, GET /me, password reset, session management
Projects GET/POST /projects, POST /projects/import/github, POST /projects/import/zip, DELETE /projects/{id}
Files GET .../files, /files/read, PUT /files/write, DELETE /files, GET /files/search
Git GET .../git/status, /git/diff, POST /git/commit, /git/push, /git/pull-request
Commands POST .../commands — runs a controlled Gitnet command (status/diff/add/branch/install/build/test/run)
Workflows GET/POST .../workflows, POST .../workflows/{id}/run
Logs GET .../logs/stream — Server-Sent Events, real-time
GitHub installation callback, list installations/repositories, webhook receiver
AI POST /ai/tasks (runs in the background), GET /ai/tasks/{id}, GET /ai/tasks?project_id=

See direct.md for the exact request/response shapes and the Gitnet command / workflow-step vocabulary the frontend and AI both target.

Known constraints (by design, not oversights)

  • Ephemeral workspaces. Project files live on local disk under WORKSPACE_ROOT and are cleaned up after WORKSPACE_IDLE_TTL_MINUTES of inactivity. Re-importing (GitHub re-clone or ZIP re-upload) restores a workspace; project metadata and history survive in Postgres regardless. This keeps the backend stateless-enough to run comfortably on Render's single-instance free/starter tiers without needing a persistent volume or object storage for an MVP.
  • Single-instance log streaming. The real-time log broker (app/services/log_broker.py) is in-process pub/sub, not backed by Redis. Fine for one backend instance; would need a shared channel to scale horizontally.
  • AI agent step ceiling. Each AI task caps at MAX_AGENT_STEPS (12) tool calls to bound cost and prevent runaway loops, not because the model can't usefully do more.

About

Gitnet's backend for importing, editing, diffing, automating, and pushing projects to GitHub.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages