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
135 changes: 0 additions & 135 deletions CHANGELOG.md

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions DESIGN_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -2744,7 +2744,7 @@ Circular inheritance is detected via chain tracking and raises `TemplateInherita
| **Docker API** | aiodocker | Async-native Docker API client for `DockerSandbox` backend |
| **Tool Integration** | MCP SDK (`mcp`) | Industry standard for LLM-to-tool integration |
| **Agent Comms** | A2A Protocol compatible | Future-proof inter-agent communication |
| **Authentication** | PyJWT + argon2-cffi | JWT (HMAC HS256/384/512) for session tokens, Argon2id for password hashing, SHA-256 for API key storage |
| **Authentication** | PyJWT + argon2-cffi | JWT (HMAC HS256/384/512) for session tokens, Argon2id for password hashing, HMAC-SHA256 for API key storage (keyed with server secret) |
| **Config Format** | YAML + Pydantic validation | Human-readable config with strict validation |
| **CLI** | TBD (future, if needed) | Thin wrapper around the REST API for terminal use. May not be needed — interactive Scalar docs at `/docs/api` and `curl`/`httpie` may suffice |

Expand Down Expand Up @@ -3172,12 +3172,12 @@ synthorg/
│ │ ├── app.py # Litestar application factory, lifecycle hooks
│ │ ├── approval_store.py # In-memory approval queue storage
│ │ ├── auth/ # JWT + API key authentication subsystem
│ │ │ ├── config.py # AuthConfig (frozen Pydantic, HMAC algorithm, exclude paths)
│ │ │ ├── config.py # AuthConfig (frozen Pydantic, JWT HMAC algorithm, exclude paths)
│ │ │ ├── controller.py # AuthController (setup, login, change-password, me)
│ │ │ ├── middleware.py # ApiAuthMiddleware (JWT-first, API key fallback)
│ │ │ ├── models.py # User, ApiKey, AuthenticatedUser, AuthMethod
│ │ │ ├── secret.py # JWT secret resolution (env var → persistence → auto-generate)
│ │ │ └── service.py # AuthService (Argon2id password hashing, JWT ops, API key hashing)
│ │ │ └── service.py # AuthService (Argon2id password hashing, JWT ops, HMAC-SHA256 API key hashing)
│ │ ├── bus_bridge.py # Message-bus → WebSocket bridge
│ │ ├── channels.py # WebSocket channel definitions
│ │ ├── config.py # API configuration models (ServerConfig, CorsConfig)
Expand Down Expand Up @@ -3237,6 +3237,7 @@ synthorg/
│ │ ├── ci.yml # Lint + type-check + test (parallel)
│ │ ├── docker.yml # Build → scan → push → sign (GHCR)
│ │ ├── dependency-review.yml # License allow-list on PRs
│ │ ├── release.yml # Release Please (automated versioning + GitHub Releases)
│ │ └── secret-scan.yml # Gitleaks on push/PR + weekly
│ ├── actions/
│ │ └── setup-python-uv/ # Composite action: Python + uv install
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ SynthOrg lets you spin up a synthetic organization staffed entirely by AI agents

### Security

- Authentication — JWT + API key, Argon2id hashing, first-run admin setup
- Authentication — JWT + API key, Argon2id password hashing, HMAC-SHA256 API key hashing, first-run admin setup
- SecOps agent — rule engine (soft-allow/hard-deny, fail-closed), audit log, output scanner, risk classifier
- Progressive trust — 4 strategies behind `TrustStrategy` protocol
- Autonomy levels — 5 tiers, presets, resolver, change strategies
Expand Down
10 changes: 8 additions & 2 deletions src/ai_company/api/auth/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ class AuthConfig(BaseModel):
before the first request is served.

Attributes:
jwt_secret: HMAC signing key (resolved at startup, repr-hidden).
jwt_secret: HMAC signing key for JWT tokens and API key
hashing (resolved at startup, repr-hidden). Rotating
this invalidates all stored API key hashes.
jwt_algorithm: JWT signing algorithm (HMAC family only).
jwt_expiry_minutes: Token lifetime in minutes.
min_password_length: Minimum password length for setup/change.
Expand All @@ -52,7 +54,11 @@ class AuthConfig(BaseModel):
jwt_secret: str = Field(
default="",
repr=False,
description="JWT signing secret (resolved at startup)",
description=(
"JWT signing secret (resolved at startup). "
"Also used as the HMAC key for API key hash computation — "
"rotating this secret invalidates all stored API key hashes."
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using jwt_secret as the HMAC key only protects API key hashes if that secret is stored separately from the leaked data. In the default setup, the JWT secret is persisted in the same persistence backend (settings table), so a full DB dump would still enable offline guessing. Consider introducing a separate, non-persisted API-key hashing secret (env/KMS) or explicitly documenting the threat model/assumptions here.

Suggested change
"rotating this secret invalidates all stored API key hashes."
"rotating this secret invalidates all stored API key hashes. "
"Security note: API key hashes are only protected against offline "
"guessing if this secret is not persisted alongside them (for "
"example, it should be provided via an environment variable or KMS "
"rather than stored in the same database/settings backend)."

Copilot uses AI. Check for mistakes.
),
)
Comment on lines 54 to 62
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JWT secret rotation silently breaks all API key authentication

jwt_secret now serves two distinct security purposes: JWT signing/verification and HMAC key for API key hashing. The field description documents this, but the operational consequence is a sharp footgun: a developer who rotates jwt_secret specifically to invalidate all active JWT sessions (a routine post-incident response) will also silently invalidate every stored API key hash in the database — without any warning at startup or rotation time.

Consider raising a startup warning (or at minimum a logger.warning) when the secret changes relative to the previously persisted value, so operators are aware that API keys will need to be regenerated. Alternatively, a separate api_key_hmac_secret field would fully decouple the two rotation lifecycles.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/ai_company/api/auth/config.py
Line: 54-62

Comment:
**JWT secret rotation silently breaks all API key authentication**

`jwt_secret` now serves two distinct security purposes: JWT signing/verification and HMAC key for API key hashing. The field description documents this, but the operational consequence is a sharp footgun: a developer who rotates `jwt_secret` specifically to invalidate all active JWT sessions (a routine post-incident response) will also silently invalidate every stored API key hash in the database — without any warning at startup or rotation time.

Consider raising a startup warning (or at minimum a `logger.warning`) when the secret changes relative to the previously persisted value, so operators are aware that API keys will need to be regenerated. Alternatively, a separate `api_key_hmac_secret` field would fully decouple the two rotation lifecycles.

How can I resolve this? If you propose a fix, please make it concise.

jwt_algorithm: Literal["HS256", "HS384", "HS512"] = Field(
default="HS256",
Expand Down
Loading
Loading