Ctrl K
ComunidadGuías…

AGENTS.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# AGENTS.md — proxy-monster

Entry point for anyone picking up this project. Read this first.

## What it is

proxy-monster is a self-hosted, open-source database access-control proxy for
MySQL and PostgreSQL. It enforces column-level access control — deterministic,
role-based masking and deny — and is lineage-aware: it follows sensitive values
through SQL expressions, functions, subqueries, and joins. Clients connect with
their normal tools (psql, mysql, JDBC) over the native wire protocol; the proxy
masks per role and denies anything it cannot prove safe (**fail-closed**).

The goal is a transparent, in-VPC, self-hostable access-control proxy you own
and can extend, friendly to CLIs and native clients.

## Engine priority

MySQL is the primary target and the correctness bar for shipping — build and
verify it first. PostgreSQL support is experimental and developed alongside;
today PostgreSQL also serves as proxy-monster's own control-plane store.
PostgreSQL-only edge cases — its transactional-DDL behaviors (in-transaction DDL
visibility, Sync/implicit-transaction rollback, deferred-constraint COMMIT
failure) — may be carried as documented known limitations rather than block a
MySQL milestone. Keep MySQL and PostgreSQL separate when splitting work or
weighing review findings, and weight MySQL accordingly.

## Layout

Modules:

- `goproxy/` — Go data-plane wire proxy: token auth, the per-statement `Decide`
  call, inline result masking, and the target-DB broker. Three levels: the
  engine, the target it connects to, and a connection to that target. One
  package per engine (`mysql/`, `postgres/`, `athena/`) holds its provider, SQL,
  table detail, and wire broker; `sqldb/` is what MySQL and PostgreSQL share
  through database/sql (introspection, the schema refetcher, table detail,
  pools); `relay/` is the engine-agnostic Decide, masking and serving; `spi/`
  defines the contracts between them. `cmd/goproxy` builds the provider
  registry; generic packages never import an engine package.
- `cpgo/` — Go front door of the control plane: owns its public HTTP and gRPC
  ports, runs the Kotlin control plane as a child on loopback, and forwards to
  it what Go does not serve yet
  ([docs/cp-go-migration.md](./docs/cp-go-migration.md)).
- `control-plane/` — Kotlin control plane: identity and roles, Cedar
  authorization, the catalog, the per-statement decision, and the admin +
  console API (HTTP and gRPC).
- `analyzer/` — `engines/` is the one list of target engines: each engine's
  facts, canonical names, and sqlglot-go lineage analyzer, which emits each
  statement's required grants. `jvm/` is its JVM FFM binding.
- `engine/` — shared Kotlin enforcement code the control plane calls: the
  JVM-side wrapper around the sqlglot-go analyzer, system classification (the
  dangerous-function and system-catalog manifests), SQL normalization, and the
  mask functions used when a stored approval result is viewed.
- `auth/` — Kotlin OIDC login and the MCP OAuth authorization server.
- `auditmon/` — Go audit-trail monitor: verifies the hash chain, anchors
  off-box, detects anomalies, and exports to a SIEM.
- `pmon/` — Go client daemon: connect with a saved password while it brokers a
  short-lived token upstream. Its menu-bar app is `pmon/tray/`.
  Connection-string formatters and brokers use its
  [provider SPI](./pmon/AGENTS.md).
- `auto-approver/` — `pmon-auto-approver-agent`, the TypeScript auto-approval
  agent cp-go runs once per review, compiled with bun into the server image.
- `sql-action/` — the GitHub Action that files a pull request's SQL files, and
  `pm-ci`, the Go binary it runs
  ([docs/sql-from-github.md](./docs/sql-from-github.md)).
- `mysqlwire/` — Go MySQL wire-protocol codec library (shared by `goproxy` and
  `pmon`).
- `proto/` — protobuf contracts: the proxy↔control-plane gRPC surface and the
  analyzer FFM boundary.
- `web/` — the Next.js console (editor, policies, access, audit, admin).
- `deploy/` — sample seed SQL for the compose target DBs.
- `docs/` — per-workstream design docs.

Key docs:

- [`README.md`](./README.md) — project front door.
- [`ARCHITECTURE.md`](./ARCHITECTURE.md) — the components, topology, trust
  boundaries, and ports.
- [`DESIGN.md`](./DESIGN.md) — the design decisions and the
  decision-to-enforcement flow.
- [`INSTALL.md`](./INSTALL.md) — install, run locally, and deploy (local + AWS).
- [`CONTRIBUTING.md`](./CONTRIBUTING.md) — how to build, test, and contribute.
- [`SECURITY.md`](./SECURITY.md) — how to report a vulnerability.
- [`KNOWN_LIMITATIONS.md`](./KNOWN_LIMITATIONS.md) — accepted caveats and gaps.
- [`docs/README.md`](./docs/README.md) — the design-doc index, plus a summary of
  what's built.

## Stack

- The control-plane is Kotlin/JVM; the wire proxy and the lineage analyzer are
  Go. Kotlin/JVM was the original choice, for an in-process JVM lineage engine;
  after the engine moved to sqlglot-go the system is being ported to Go
  incrementally, and the control-plane is the last Kotlin component. The
  analyzer still runs inside the JVM through a Foreign Function & Memory binding
  to a Go c-shared library (JDK 24 — the Java compiler is pinned to
  `--release 24` and every module targets JVM 24).
- Lineage: the sqlglot-go probe in `analyzer/` resolves column lineage — it
  parses each statement and traces which source columns every output and
  predicate derives from. proxy-monster owns the probe; sqlglot-go is a library
  dependency.
- Enforcement: masking and deny. When a query touches masked columns the
  analyzer rewrites `SELECT *` to an explicit column list (a faithful,
  semantically-identical rewrite) so mask ordinals are fixed; the proxy then
  masks those columns inline on the result stream. Anything the analyzer cannot
  prove safe is denied. Masking is result-stream rewriting — the query is not
  rewritten to inject mask expressions.
- Identity: OIDC. The IdP's group claim provisions local group membership (via
  SCIM or JIT on first login); local groups map to roles through the
  `group_role` map. The IdP never mints roles directly. Okta is the reference
  provider; any OIDC IdP works ([docs/auth-model.md](./docs/auth-model.md)).
- Wire auth: SSO login plus a local broker daemon (`pmon`). Saved connections
  use a fixed localhost password while the daemon injects a short-lived token on
  the upstream hop. The daemon does not renew that token — a login lasts one
  token TTL, then `pmon login` again (see
  [DESIGN.md](./DESIGN.md#identity-and-broker)).
- JIT elevation: a time-boxed, revocable grant of a role to a user (an
  `access_grant`). The grant just adds to the user's effective roles, so the
  same per-query decision applies — the engine has no separate elevation path
  (see [DESIGN.md](./DESIGN.md#jit-elevation-and-approval)).
- Planes: the Kotlin control-plane owns a Postgres store (identity, policy,
  catalog, grants, audit); the Go data-plane proxy holds no store — it connects
  only to its target DB and reads each decision from the control-plane over
  gRPC.
- Web console (`web/`): a SQL editor plus admin for datasources, policies,
  access, and audit, in Next.js.

## New control-plane code: Go first

The control plane is moving to Go slice by slice
([docs/cp-go-migration.md](./docs/cp-go-migration.md)). New control-plane code
goes in `cpgo/`, not `control-plane/`, unless it only extends a Kotlin path that
has not moved yet. Kotlin that stays is the smallest change that keeps a
not-yet-moved path working.

When Kotlin must call Go, use gRPC with a proto contract, never hand-written
JSON DTOs on both sides:

- One service per slice (e.g. `GitHubSource`), in its own file under
  `proto/src/main/proto/`. The generated types are the data classes on both
  sides.
- cp-go serves it on a loopback gRPC listener it starts before the Kotlin child,
  and passes the address to the child. The per-boot `PM_CP_INTERNAL_TOKEN`
  travels in gRPC metadata. Register the Go side on `cpgo/gobridge`; build the
  Kotlin stub on `CpGoBridge.channel`.
- Forward whole operations, which Go runs in its own transaction. A single row
  written inside a Kotlin transaction (a `request_event`, say) stays a Kotlin
  insert until that call site moves; an RPC there would split the transaction.
- A service exists only while its slice is half-moved: delete it when the Kotlin
  caller moves to Go, and list it in `docs/cp-go-migration.md` until then.

Go calling Kotlin stays on the existing `/internal/*` HTTP bridge
(`cpgo/bridge`), which only shrinks.

## Control-plane HTTP API

There is no API reference and no OpenAPI spec. The routes are the surface and
the `@Serializable` Kotlin data classes in each owning file are the
request/response reference. Handler names do not track their path prefixes, so
the map of which file owns which prefix — and the gate each one calls — is in
[docs/authz-model.md](./docs/authz-model.md#http-auth-gates).

An authenticated session alone is never authorization. A route states its
requirement by which gate helper it calls (`requireApi`, `requireAdmin`,
`requireAuthz`, `requireScimAuth`). `PM_AUTH_DEBUG` is an authentication
setting: it adds a login method (`POST /auth/debug`, any principal with any
roles, minted as a real session), and the gates read that session like any
other.

Errors are never English prose on the wire: a route responds
`ApiError(code, params)` with a stable dot-namespaced code the web looks up as
an i18n message key. `Scim.kt` is exempt — its body follows the SCIM 2.0 spec.
See [docs/l10n.md](./docs/l10n.md).

## Conventions

Full list in [CONTRIBUTING.md](./CONTRIBUTING.md#conventions). The three that
most often get violated:

- Comments and docs are not a diary, and this is a **security** rule before it
  is a style one: this repository is public. A comment explains what the code
  cannot say — an outside constraint, a non-obvious failure mode, why not the
  obvious alternative. It must not narrate how the code came to be: no review
  findings, no what-was-tried, no "previously X", no debugging account. That
  narration is what carries host names, paths, internal tooling, and topology
  into a public repository. History goes in the commit message, which is also
  public — the same rule about naming internal systems applies there.
- l10n is non-negotiable. Every user-facing string is localized (English and
  Korean today). The server returns a stable dot-namespaced code via `ApiError`,
  never English prose; every message key must exist in every locale under
  `web/messages/<locale>/` ([docs/l10n.md](./docs/l10n.md)).
- Fail-closed through Cedar, not a hardcoded deny. When the analyzer cannot
  prove a statement safe, route it through the deny-by-default gate
  (`exception.unanalyzable` / `exception.unmaskable`) so a datasource can
  override it while the production floor stays closed. Coverage gaps are
  security gaps.

## Build, test, run

The toolchain is pinned via mise (`mise.toml`), which also defines the tasks:

```sh
mise run dev      # the whole local stack
mise run verify   # the whole gate: lint, JVM tests, Go tests, web tests + build
```

`mise tasks ls` lists the rest, including per-component tasks for running one
piece against a stack you started by hand. The gate is mandatory before pushing
and its DB-backed tests need Docker — details, and the flags that change that,
in [CONTRIBUTING.md](./CONTRIBUTING.md#build-test-run). Install and deploy:
[INSTALL.md](./INSTALL.md).

## Releases

release-please cuts four trains (server, `pmon`, `pm-ci`, `mysqlwire`) from one
config; see [CONTRIBUTING.md](./CONTRIBUTING.md#releases). The paths a commit
touches decide its train.

Never force a version with an empty commit carrying a `Release-As:` footer. An
empty commit touches no path, so release-please counts it toward every train:
`Release-As: 0.1.21` meant for the server also set `pmon` to 0.1.21. Put the
footer on a commit that edits a file in that train's directory, such as its
`CHANGELOG.md`. The one such commit already on `main` is fenced off by
`last-release-sha` in `release-please-config.json`; move that floor forward if
another ever lands.

## Design docs

Index and what's built: [docs/README.md](./docs/README.md). House style:
[CONTRIBUTING.md](./CONTRIBUTING.md#conventions).