> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pipefort.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture

> One scan engine, two callers, two independent auth concerns.

## The big picture

```
React SPA (Vite + TS + Tailwind)
  │  supabase-js: GitHub login + read data (RLS-protected)
  │  fetch() with Supabase JWT → /api/* for privileged work
  ▼
Go API
  • verify the Supabase user JWT
  • mint GitHub App installation tokens (RS256)
  • fetch .github/workflows/* via the GitHub API (no clone)
  • scan in memory with scanner.ScanBytes
  • persist scans + findings (pgx)
  ▼
Postgres (history/trends) + Auth (GitHub login)
```

## Key architectural decisions

### One scan engine, two callers

All detection lives in a single Go scanner package. The CLI reads from disk; the web API reads from the GitHub API. They both end up calling the same `ScanBytes` function:

```go theme={null}
// CLI path
findings, _ := scanner.ScanFile(path)   // ScanFile = os.ReadFile + ScanBytes

// API path
content := githubClient.GetWorkflowBytes(...)
findings, _ := scanner.ScanBytes(filename, content)
```

This is why the CLI and the dashboard always produce identical findings for the same workflow YAML and the same ruleset.

### Two independent auth concerns

<Warning>
  Don't conflate identity with repo access. They use different GitHub flows and different credential types.
</Warning>

<CardGroup cols={2}>
  <Card title="Supabase Auth (GitHub provider)" icon="user-check">
    Establishes **who the user is**. The SPA gets a JWT; the API verifies it (HS256) to extract the user ID for downstream queries.
  </Card>

  <Card title="GitHub App installation" icon="key">
    Grants **repo read access**. The API mints installation tokens (RS256 app JWT) and links each installation to a user via `/api/github/callback`.
  </Card>
</CardGroup>

### Read/write split around RLS

The React client reads Postgres **directly** through supabase-js. Every table has row-level security scoped to `auth.uid() = user_id`, so users can only see their own data even though they're hitting Postgres with their JWT.

The Go API only handles **privileged** work — minting GitHub installation tokens, fetching/scanning, and **writing** scans + findings as the service role (which bypasses RLS). The findings table mirrors the scanner's `Finding` struct 1:1.

### Scan orchestration is client-driven

There is no server-side batch scan. `POST /api/scan` does **one repo** (fetch workflows → `ScanBytes` → persist). The SPA loops it across repos with bounded concurrency so each request stays within serverless time limits.

### No `git clone` in the web app

The API pulls only the workflow YAML via the GitHub Git Trees/Blobs API and scans the bytes in memory, so each per-repo scan is sub-second and fits a serverless time budget. Org-wide scans are orchestrated client-side, one short request per repo, with live progress.

The CLI's `-g owner/repo` mode *does* shallow-clone — it's a one-shot local invocation, not a multi-tenant service.
