-
Notifications
You must be signed in to change notification settings - Fork 22
security: Sprint 0 emergency fixes #42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This does not match the current architecture. Extra does not currently provide an API authentication plugin. The AccessResolver performs authorization after a verified caller identity has already been supplied; it does not validate API keys or JWTs at the HTTP boundary. This should explain that authentication is handled by the client gateway, host application, or API middleware, and that the verified identity/context is then passed to Extra for access-control decisions. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| # Security Policy | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove this file from the commit |
||
|
|
||
| ## Supported Versions | ||
|
|
||
| | Version | Supported | | ||
| |---------|-----------| | ||
| | `main` (beta) | Yes — active development, receives security fixes | | ||
| | < 1.0 releases | No — no stable releases yet | | ||
|
|
||
| This project is in **beta**. Security fixes are applied to `main` and shipped in the next container image build (`ghcr.io/extra-org/extra`). There are no long-term support branches. | ||
|
|
||
| --- | ||
|
|
||
| ## Reporting a Vulnerability | ||
|
|
||
| If you discover a security vulnerability, please report it **privately** — do not open a public GitHub issue. | ||
|
|
||
| **Preferred:** [GitHub Security Advisories](https://github.com/extra-org/extra/security/advisories/new) | ||
|
|
||
| ### What to include | ||
|
|
||
| - Description of the vulnerability | ||
| - Steps to reproduce | ||
| - Affected version or commit | ||
| - Potential impact assessment | ||
| - Any suggested fix (if applicable) | ||
|
|
||
| ### Response timeline | ||
|
|
||
| | Step | Target | | ||
| |------|--------| | ||
| | Acknowledgment | 72 hours | | ||
| | Triage and severity assessment | 7 days | | ||
| | Fix or mitigation | 14 days for critical, 30 days for others | | ||
| | Public disclosure | After fix is released, coordinated with reporter | | ||
|
|
||
| We follow **coordinated disclosure**. Please do not publicly disclose the vulnerability until a fix is available and you have been notified. | ||
|
|
||
| --- | ||
|
|
||
| ## Important: Beta Software — Known Security Gaps | ||
|
|
||
| This project is **not production-ready**. The following critical security gaps are known and documented. **Do not expose this software to an untrusted network without addressing them.** | ||
|
|
||
| | Gap | Risk | Status | | ||
| |-----|------|--------| | ||
| | **No authentication** on any API endpoint (`/invoke`, `/stream`, `/conversations`) | Critical | Open — Sprint 1 planned | | ||
| | **Access control not enforced** — the `protected` node feature is structurally wired but the Security/Context Gate that populates real identity is not implemented | Critical | Open — Sprint 1 planned | | ||
| | **No rate limiting** — no per-IP or per-endpoint request throttling | High | Open — Sprint 1 planned | | ||
| | **No built-in TLS** — must be handled by a reverse proxy | High | Open — Sprint 3 planned | | ||
| | **No security headers** — missing `X-Content-Type-Options`, `X-Frame-Options`, `Strict-Transport-Security` | Medium | Open — Sprint 1 planned | | ||
| | **No SSE disconnect detection** — engine continues processing after client disconnects | Medium | Open — Sprint 2 planned | | ||
| | **No server timeouts** — uvicorn keep-alive and request timeouts not configured | Medium | Open — Sprint 2 planned | | ||
|
|
||
| For the full remediation plan, see [docs/SECURITY_SPRINTS.md](docs/SECURITY_SPRINTS.md). | ||
|
|
||
| --- | ||
|
|
||
| ## Security Features Already Implemented | ||
|
|
||
| Despite the gaps above, the following security practices are in place: | ||
|
|
||
| | Practice | Description | | ||
| |----------|-------------| | ||
| | YAML secret scanning | Rejects literal secrets and credential shapes; only variable references are allowed | | ||
| | Non-root Docker user | Container runs as `agent` user, not root | | ||
| | CORS default deny | Empty allowed origins list by default; must be explicitly configured | | ||
| | Request size limits | Pydantic `max_length` on all string fields in request schemas | | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should be described as request-field length validation, not as a complete HTTP request-size limit. Pydantic validates the field only after the request body has already been read and parsed. It is also not currently applied to all request string fields, for example |
||
| | Execution cost guardrails | Per-run token and cost limits prevent runaway LLM spend | | ||
| | MCP auth headers never logged | Authentication headers passed to MCP servers are excluded from logs | | ||
| | SQL injection prevention | All database access goes through SQLAlchemy ORM | | ||
| | Safe YAML loading | YAML is loaded without code execution or arbitrary object construction | | ||
| | Sensitive argument masking | HITL approval prompts mask sensitive tool arguments | | ||
| | Request ID sanitization | Request IDs are regex-validated and capped at 64 characters | | ||
|
|
||
| --- | ||
|
|
||
| ## Deployment Security Recommendations | ||
|
|
||
| Before exposing Extra to any network, you **must**: | ||
|
|
||
| 1. **Implement authentication** — write a custom auth plugin that validates API keys or JWT tokens. See the plugin architecture in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). | ||
|
|
||
| 2. **Run behind a reverse proxy** — use nginx, Caddy, or a cloud load balancer to terminate TLS and add security headers. | ||
|
|
||
| 3. **Restrict CORS origins** — set `CORS_ORIGINS` in your `.env` to the exact domains that should access the API. | ||
|
|
||
| 4. **Use Docker network isolation** — bind to `127.0.0.1:port:port` and expose through the container platform's networking. | ||
|
|
||
| 5. **Monitor logs** — watch for authentication failures, approval rejections, and protected-node access attempts. | ||
|
|
||
| 6. **Pin container versions** — use `ghcr.io/extra-org/extra:v1.2.3` (specific tag), not `:latest`. | ||
|
|
||
| --- | ||
|
|
||
| ## Security Roadmap | ||
|
|
||
| | Sprint | Focus | Status | | ||
| |--------|-------|--------| | ||
| | Sprint 0 | Emergency fixes (error responses, non-root Docker, request limits) | Completed | | ||
| | Sprint 1 | Auth & access control (authentication middleware, rate limiting, security headers) | Planned | | ||
| | Sprint 2 | Input hardening (timeouts, SSE cleanup, path validation, log redaction) | Planned | | ||
| | Sprint 3 | Production readiness (TLS docs, health checks, audit logging) | Planned | | ||
|
|
||
| Full details: [docs/SECURITY_SPRINTS.md](docs/SECURITY_SPRINTS.md) | ||
|
|
||
| --- | ||
|
|
||
| ## Security Update Process | ||
|
|
||
| - Security fixes are merged to `main` and released via [release-please](https://github.com/googleapis/release-please) automation. | ||
| - Container images are published to `ghcr.io/extra-org/extra` on each release. | ||
| - Users should subscribe to [GitHub Releases](https://github.com/extra-org/extra/releases) for notifications. | ||
| - For critical vulnerabilities, we will publish a security advisory on GitHub with affected versions and upgrade instructions. | ||
|
|
||
| --- | ||
|
|
||
| ## References | ||
|
|
||
| - [Security Sprints](docs/SECURITY_SPRINTS.md) — full remediation plan | ||
| - [Architecture](docs/ARCHITECTURE.md) — system design and security boundaries | ||
| - [Runtime Lifecycle](docs/RUNTIME_LIFECYCLE.md) — request flow and security gate | ||
| - [Plugin Context/Auth](docs/SIDECAR_CONTEXT_AUTH.md) — access control design | ||
|
foeed marked this conversation as resolved.
foeed marked this conversation as resolved.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the manager still defaults to |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| # Local development compose — exposes services on 127.0.0.1 only. | ||
| # Not intended for production deployment. | ||
|
|
||
| services: | ||
| engine: | ||
| build: . | ||
| ports: | ||
| - "127.0.0.1:8090:8090" | ||
| volumes: | ||
| - ./examples:/workspace:ro | ||
| env_file: | ||
| - .env | ||
| command: ["serve", "--host", "0.0.0.0", "--config", "/workspace/enterprise-knowledge-assistant/agents.yaml"] | ||
| healthcheck: | ||
| test: ["CMD", "python", "-c", "import httpx; httpx.get('http://127.0.0.1:8090/health')"] | ||
| interval: 30s | ||
| timeout: 5s | ||
| retries: 3 | ||
|
|
||
| manager: | ||
| build: . | ||
| ports: | ||
| - "127.0.0.1:8100:8100" | ||
| volumes: | ||
| - ./examples:/workspace:ro | ||
| env_file: | ||
| - .env | ||
| command: ["agent-manager", "--host", "0.0.0.0", "--config", "/workspace/enterprise-knowledge-assistant/agents.yaml"] | ||
| healthcheck: | ||
| test: ["CMD", "python", "-c", "import httpx; httpx.get('http://127.0.0.1:8100/health')"] | ||
| interval: 30s | ||
| timeout: 5s | ||
| retries: 3 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| # Security Sprints | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need commit this file? if we handle only sprint 0 now, and have more gaps, you can open an issue for other or just create fix PR :) |
||
|
|
||
| Actionable remediation plan from the security audit (July 2026). | ||
| Each sprint is ordered — do them in sequence. | ||
|
|
||
| --- | ||
|
|
||
| ## Sprint 0 — Emergency Fixes (1-2 days) | ||
|
|
||
| Quick wins, low effort, high impact. | ||
|
|
||
| | # | Task | Files | Effort | | ||
| |---|------|-------|--------| | ||
| | 1 | **Generic error responses** — replace `str(exc)` with sanitized messages in all API error returns. Log full exceptions server-side only. | `agent_engine/api/app.py:243,297,332` `agent_manager/api/routes.py:64,115` | 2h | | ||
| | 2 | **Non-root Docker user** — add `RUN useradd -r -s /bin/false agent` and `USER agent` before entrypoint. | `Dockerfile` | 30m | | ||
| | 3 | **Request size limits** — add `max_length=4096` (or similar) to all `str` fields in request schemas. | `agent_manager/api/schemas.py` `agent_engine/api/app.py` | 1h | | ||
| | 4 | **Default host 127.0.0.1** — change default `--host` from `0.0.0.0` to `127.0.0.1` in both servers. | `agentctl/main.py:190` `agent_manager/config.py:29` | 30m | | ||
| | 5 | **Docker Compose** — add `docker-compose.yml` with `engine` (8090) and `manager` (8100) services, SQLite volume, health checks, `.env` passthrough. | `docker-compose.yml` | 30m | | ||
|
|
||
| **Definition of done:** `make check` passes. No `str(exc)` in HTTP responses. Docker runs as non-root. `docker compose up` starts both services. | ||
|
|
||
| --- | ||
|
|
||
| ## Sprint 1 — Auth & Access Control (1-2 weeks) | ||
|
|
||
| The critical missing piece. | ||
|
|
||
| | # | Task | Files | Effort | | ||
| |---|------|-------|--------| | ||
| | 1 | **Auth middleware** — implement a FastAPI dependency that validates API key or JWT bearer token. Apply to all routes in both APIs. | `agent_engine/api/app.py` `agent_manager/api/app.py` `agent_manager/api/deps.py` | 3-5d | | ||
| | 2 | **Wire auth context to AccessFilter** — populate `AuthContext` from the verified identity so `protected` nodes are actually enforced. | `engine/langgraph/engine.py:669-674` `engine/langgraph/filters.py:42-67` `docs/SIDECAR_CONTEXT_AUTH.md` | 2-3d | | ||
| | 3 | **Rate limiting** — add `slowapi` or equivalent. Per-IP and per-endpoint limits, especially on `/invoke` and `/stream`. | `agent_engine/api/app.py` `agent_manager/api/app.py` | 1d | | ||
| | 4 | **Security headers** — add middleware for `X-Content-Type-Options`, `X-Frame-Options`, `Strict-Transport-Security`, `Referrer-Policy`. | Both API app files | 2h | | ||
| | 5 | **Tighten CORS** — restrict `allow_methods` and `allow_headers` to specific values instead of `*`. | `agent_manager/api/web.py:24-25` | 30m | | ||
|
|
||
| **Definition of done:** All endpoints require valid credentials. `protected: true` nodes reject unauthenticated callers. `make check` passes. | ||
|
|
||
| --- | ||
|
|
||
| ## Sprint 2 — Input Hardening (1 week) | ||
|
|
||
| Defense-in-depth. | ||
|
|
||
| | # | Task | Files | Effort | | ||
| |---|------|-------|--------| | ||
| | 1 | **Server timeouts** — configure uvicorn `timeout_keep_alive` and request timeouts. | `agentctl/main.py:208` `agent_manager/cli.py:38` | 2h | | ||
| | 2 | **SSE disconnect detection** — abort engine processing when client disconnects mid-stream. | `agent_engine/api/app.py:264-303` `agent_manager/api/routes.py:91-119` | 1d | | ||
| | 3 | **Path validation** — verify `import_from_path()` resolves within the expected `plugins/` directory. | `loaders/_import.py:22-29` | 2h | | ||
| | 4 | **Dependency lockfile** — generate `uv.lock` or `pip-compile` output for reproducible builds. | `pyproject.toml` | 1h | | ||
| | 5 | **Log redaction** — add a redaction pass in `StructuredFormatter` for values matching secret patterns at DEBUG level. | `logging_config.py` `observability/providers/logging/provider.py` | 4h | | ||
|
|
||
| **Definition of done:** Timeouts configured. SSE cleans up on disconnect. Lockfile committed. `make check` passes. | ||
|
|
||
| --- | ||
|
|
||
| ## Sprint 3 — Production Readiness (ongoing) | ||
|
|
||
| Operational security. | ||
|
|
||
| | # | Task | Files | Effort | | ||
| |---|------|-------|--------| | ||
| | 1 | **TLS docs** — document that TLS must be handled by reverse proxy. Add `--ssl-keyfile`/`--ssl-certfile` options to `agentctl serve`. | `agentctl/main.py` `docs/` | 1d | | ||
| | 2 | **Health check** — add `GET /healthz` that returns 200 when engine is ready. Add `HEALTHCHECK` to Dockerfile. | `agent_engine/api/app.py` `Dockerfile` | 2h | | ||
| | 3 | **Audit logging** — log all authentication failures, approval decisions, and protected-node access attempts. | `agent_engine/api/app.py` `engine/langgraph/filters.py` | 1d | | ||
| | 4 | **Pentest checklist** — create a doc with test cases for an external security review. | `docs/SECURITY_PENTEST.md` | 4h | | ||
|
|
||
| **Definition of done:** Deployment docs include TLS guidance. Docker image has health check. Audit log covers sensitive operations. | ||
|
|
||
| --- | ||
|
|
||
| ## Appendix: What's Already Done Well | ||
|
|
||
| | Practice | Location | | ||
| |----------|----------| | ||
| | YAML secret scanning (keys + credential shapes) | `parsers/yaml/parser.py:483-498` | | ||
| | Sensitive argument masking in HITL approvals | `approvals/sanitization.py` | | ||
| | CORS default deny (empty origins list) | `agent_manager/config.py:33` | | ||
| | Request ID sanitization (regex + 64-char cap) | `agent_engine/api/app.py:44-56` | | ||
| | MCP auth headers never logged | `runtime/hooks/mcp.py:81-85` | | ||
| | Execution limits per run (cost guardrails) | `runtime/execution.py` | | ||
| | Access filter fail-closed on exceptions | `engine/langgraph/filters.py:51-67` | | ||
| | `.env` files gitignored | `.gitignore` | | ||
| | SQL injection prevention via ORM | `infrastructure/persistence/` | | ||
|
foeed marked this conversation as resolved.
|
|
foeed marked this conversation as resolved.
|
|
foeed marked this conversation as resolved.
|
Uh oh!
There was an error while loading. Please reload this page.