Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/phase-150-git-arg-injection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"gitsema": patch
---

Security (Phase 150 / review11 §2.1 + §3.2): close the network-reachable git
argument-injection sink. A caller-supplied "ref" beginning with `-` (e.g.
`--output=/path`) was parsed by git as a *flag*, turning `git log` into an
arbitrary-file-write primitive reachable via `semantic_bisect`/`triage`. All
git call sites that take a user-influenced ref now route through a shared
`runGit()` helper that rejects leading-`-` refs before spawning git and always
inserts git's `--end-of-options` separator so a value can never be read as a
flag (`resolveRefToTimestamp`, `parseDateArg`, `getMergeBase`,
`getBranchExclusiveBlobs`).
15 changes: 15 additions & 0 deletions .changeset/phase-151-read-route-authz.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"gitsema": patch
---

Security (Phase 151 / review11 §2.2): enforce repo authorization on read
routes. The multi-tenant grant model (`repo_grants` / `resolveUserRepoAccess`)
was defined but never checked on the ~16 search/analysis/evolution/graph/
insights routes, so any caller could read any repo's indexed content by naming
its `repoId`. A new `repoAuthMiddleware` now runs after `repoSessionMiddleware`
and, in multi-tenant mode, requires the caller to hold a `read` grant on the
addressed repo unless it is `public` (else 403). Multi-tenant mode is opt-in
via `GITSEMA_MULTI_TENANT` (defaulting to `GITSEMA_SERVE_KEY` presence); the
global serve key and legacy per-repo scoped tokens bypass the check, and a
default open single-dev server is unaffected. Repo-level only — per-branch
grant filtering is deferred to a follow-on phase.
15 changes: 15 additions & 0 deletions .changeset/phase-152-byok-ssrf-list-bounds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"gitsema": patch
---

Security (Phase 152 / review11 §3.1 + §3.3). **BYOK SSRF guard:** on
`tools serve`, a caller-supplied `byok.http_url` is now validated before the
server calls it — non-`http(s)` schemes and hosts resolving to loopback,
link-local (incl. the `169.254.169.254` cloud-metadata IP), or RFC-1918
private ranges are rejected by default. Operators re-permit specific internal
hosts (e.g. a local model server) via the new `GITSEMA_BYOK_ALLOW_HOSTS`
allowlist. This is a behavior change for anyone pointing BYOK at a
`localhost`/private endpoint — add the host to the allowlist. **List-tool
bounds:** the network-exposed `deps` and `blast_radius` `depth` parameter is
now upper-bounded (max 64) on both the HTTP route and MCP tool, closing the
last unbounded traversal-depth input from the Phase 147/148 exposure.
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,8 @@ Configuration is via environment variables or the `gitsema config` command (pers
| `GITSEMA_LOG_MAX_BYTES` | `1048576` | Log rotation threshold (1 MB) |
| `GITSEMA_SERVE_PORT` | `4242` | Port for `gitsema tools serve` HTTP server |
| `GITSEMA_SERVE_KEY` | *(optional)* | Bearer token required by `gitsema tools serve` |
| `GITSEMA_MULTI_TENANT` | *(unset → follows `GITSEMA_SERVE_KEY` presence)* | Opt-in read-route authorization gate (Phase 151). When multi-tenant mode is active, a request naming a `repoId` requires the caller to hold a `read` grant on that repo (or the repo to be `public`); the global key and legacy per-repo scoped tokens bypass the check. Explicit `1`/`true`/`yes`/`on` enables it, `0`/`false` disables it even when a serve key is set; when unset, enforcement follows `GITSEMA_SERVE_KEY` presence. A default open server (no key, no flag) is unaffected. |
| `GITSEMA_BYOK_ALLOW_HOSTS` | *(empty)* | Comma-separated host/CIDR allowlist that re-permits otherwise-blocked BYOK endpoint hosts (loopback/link-local/RFC-1918). BYOK narrator/guide endpoints are SSRF-guarded by default (Phase 152); add internal model-server hosts here to allow them. |
| `GITSEMA_LLM_URL` | *(optional)* | OpenAI-compatible URL for `--narrate` LLM summaries |
| `GITSEMA_DATA_DIR` | `~/.gitsema/data` | Root directory for `gitsema tools serve`'s persisted repo clones + index DBs (`repos/<repoId>/{repo,index.db}`, `registry.db`) |
| `GITSEMA_STORAGE_BACKEND` | `sqlite` | `sqlite` \| `postgres` \| `qdrant` — selects the storage profile (Phase 101–103) |
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ Configuration is read from environment variables or persisted via `gitsema confi
| `GITSEMA_LOG_MAX_BYTES` | `1048576` | Log rotation threshold (1 MB) |
| `GITSEMA_SERVE_PORT` | `4242` | Port for `gitsema tools serve` |
| `GITSEMA_SERVE_KEY` | *(optional)* | Bearer token required by `gitsema tools serve` |
| `GITSEMA_MULTI_TENANT` | *(unset → follows `GITSEMA_SERVE_KEY`)* | Opt-in read-route authorization: when active, a request naming a `repoId` requires the caller to hold a `read` grant on that repo (or the repo to be `public`). `1`/`true`/`yes`/`on` enables, `0`/`false` disables even with a serve key; unset follows `GITSEMA_SERVE_KEY` presence. A default open server (no key, no flag) is unaffected. |
| `GITSEMA_BYOK_ALLOW_HOSTS` | *(empty)* | Comma-separated host/CIDR allowlist re-permitting otherwise-blocked BYOK endpoint hosts (loopback/link-local/RFC-1918). BYOK narrator/guide endpoints are SSRF-guarded by default. |
| `GITSEMA_WEBSOCKET_KEY` | *(optional)* | Bearer token fallback for `tools mcp --websocket`/`tools lsp --websocket` when `--key` is omitted |
| `GITSEMA_MCP_HTTP_KEY` | *(optional)* | Bearer token fallback for `tools mcp --http` when `--key` is omitted |
| `GITSEMA_MAX_BODY_SIZE` | `1mb` | Body-size cap for `tools serve` and `tools mcp --http` (e.g. `5mb`) |
Expand Down Expand Up @@ -347,6 +349,8 @@ Both commands are **safe-by-default**: with no narrator model configured (or wit
| `--byok-max-tokens <n>` | — | Max tokens per BYOK call |
| `--byok-temperature <n>` | — | Temperature for BYOK calls |

> **SSRF note (server deployments):** on `gitsema tools serve`, the server issues the request to the caller-supplied `--byok-http-url`/`byok.http_url`. To prevent it being used to reach internal/metadata endpoints, URLs whose host resolves to loopback, link-local (incl. `169.254.169.254`), or RFC-1918 private ranges are **rejected by default**. Re-permit specific internal hosts via `GITSEMA_BYOK_ALLOW_HOSTS` (comma-separated host/CIDR allowlist). This does not affect local CLI use, only what a networked server will call.

Configure a narrator model with `gitsema models add <name> --narrator --http-url <url> [--key <token>] --activate`, with a local Ollama model (`gitsema models add <name> --narrator --provider ollama [--global-name <tag>] --activate`, see "Ollama" below), or with a local CLI AI tool: `gitsema models add <name> --narrator --provider cli --cli-command <tool> [--cli-args "<args>"] --activate` (see "CLI-based AI tool backends" below). Alternatively, pass `--byok-http-url` (and optionally `--byok-api-key`/`--byok-model`) for a one-off, never-persisted request-scoped model — it bypasses the configured/allow-listed model selection entirely (Phase 130).

#### `--narrate` on other commands
Expand Down
Loading
Loading