Knowledge Hub
Comprehensive guides and references for the OpenFrame platform
OpenFrame Gen1 is Here · Our AI platform for autonomous IT is out of beta.
Comprehensive guides and references for the OpenFrame platform
This guide covers the security architecture, authentication patterns, secrets management, and security guidelines for developing with OpenFrame OSS Tenant.
OpenFrame uses the Spring Authorization Server to provide per-tenant OAuth2/OIDC. Every tenant has its own OAuth2 client stored in MongoDB, and all JWTs are RSA-signed with a per-tenant key pair.
graph LR
Client["Client (Browser/Agent)"] --> GW["Gateway :8081"]
GW --> Auth["Auth Server :8082\nOAuth2/OIDC"]
Auth --> Mongo[("MongoDB\nRegistered Clients")]
Auth --> Client
GW --> API["API Service :8080"]
GW --> GW
Key security properties:
TenantKeyService) stored in MongoDBIssuerUrlProvidertenant_id, machine_id (for agents), and standard OIDC claimsThe Gateway (openframe-gateway-service-core) validates all incoming JWTs before forwarding requests:
X-Tenant-Id and X-Machine-Id headers and forwards them to backend services401 UnauthorizedAll API service data queries enforce tenant scoping via TenantAwareMongoTemplate. The tenant ID is read from the JWT claim and applied as a mandatory filter on every MongoDB query — it is not possible to access data from another tenant.
The openframe-client Rust agent uses the OAuth2 Client Credentials flow:
/clients/api/agents/register using a pre-shared X-Initial-Key (the agent registration secret). MongoDB stores the new machine record and returns {machineId, clientId, clientSecret}.clientId and clientSecret for a JWT via POST /clients/oauth/token (Client Credentials grant).TokenRefreshRunManager service automatically refreshes tokens before expiry.Security principle: The initial key (
X-Initial-Key/ Agent Registration Secret) is single-use and should be rotated after successful registration. It is managed byAgentRegistrationSecretService.
The Tauri desktop app receives authentication tokens encrypted with AES-256-GCM. The TokenDecryptionService in the Tauri Rust backend decrypts the token before passing it to the React frontend via IPC. The encryption secret is provisioned by the platform, never stored in the app bundle.
Sensitive credentials (tool API keys, OAuth2 client secrets) stored in MongoDB are encrypted using the EncryptionService from openframe-core-crypto. The encryption key is provided through secure environment configuration — never hardcoded.
All REST and GraphQL inputs use Java Bean Validation (@Valid, @NotBlank, @ValidEmail, @TenantDomain). Custom validators include:
| Validator | Purpose |
|---|---|
@ValidEmail / ValidEmailValidator |
Validates email format; optionally checks against disposable domain lists |
@TenantDomain / TenantDomainValidator |
Validates tenant domain slug format |
GraphQL mutations validate inputs through DGS data fetchers before passing to service layer. All GraphQL errors are returned via GraphQLExceptionHandler and GlobalExceptionHandler.
The openframe-chat frontend uses typed GraphQL queries/mutations (via graphql-request) which prevents injection of arbitrary query structures. User input is never interpolated directly into query strings.
| Vulnerability | Mitigation in OpenFrame |
|---|---|
| Cross-Tenant Data Leakage | TenantAwareMongoTemplate enforces tenant scoping on all DB queries; JWT tenant_id claim is verified at the Gateway |
| JWT Forgery | Per-tenant RSA-2048 key pairs; Gateway validates issuer URL and signature before forwarding |
| Replay Attacks | JWTs have short expiry; refresh token rotation via RefreshTokenGenerator |
| CSRF | APIs use Authorization header Bearer tokens (not cookies for API calls); CORS restricted via CorsConfig |
| API Key Leakage | API keys stored encrypted in MongoDB; key stats tracked separately; rate limiting applied in Gateway via RateLimitService |
| Script Injection (RMM) | Script arguments are tokenized by ScriptArgsTokenizer before dispatch; not shell-interpolated |
| Open Redirect | OAuth2 redirect targets validated by DefaultRedirectTargetResolver against an allowlist |
| WebSocket Hijacking | Gateway's WebSocketServiceSecurityDecorator enforces JWT validation for WebSocket upgrades |
| Secret | Used By | Notes |
|---|---|---|
| MongoDB connection URI | All Spring Boot services | Contains credentials |
| Agent Registration Secret | openframe-client, Management Service | Single-use per agent; rotate after registration |
| OAuth2 client secrets | Authorization Server, API clients | Stored encrypted in MongoDB |
| RSA key pairs (per tenant) | Authorization Server | Generated automatically; stored in MongoDB encrypted |
| AES-256-GCM encryption key | openframe-chat (Tauri) | Provisioned at deployment; never bundled in app |
| API keys | External API consumers | Stored encrypted; hashed for validation |
For local development, use a .env file or export variables in your shell. Never commit .env files.
# Example local development environment variables
export SPRING_DATA_MONGODB_URI="mongodb://localhost:27017/openframe_dev"
export OPENFRAME_ENCRYPTION_KEY="local-dev-key-not-for-production"
Add .env to your .gitignore:
# .gitignore
.env
*.env
.env.local
When reviewing PRs, pay attention to:
@PreAuthorize or equivalent tenant checks are appliedTenantAwareMongoTemplate or equivalent scoping@Valid and custom validators; never pass raw input to queries# Run Spring Security integration tests
mvn test -pl openframe/services/openframe-api -Dtest="*SecurityTest,*AuthTest"
# Run all tests including security tests
mvn test
OpenFrame supports SSO via Google and Microsoft OIDC through the Authorization Server:
GoogleClientRegistrationStrategy — Handles Google OAuth2 client registrationMicrosoftClientRegistrationStrategy — Handles Microsoft Azure AD integrationSsoAuthorizationRequestResolver — Customizes OIDC authorization requests per providerSSOConfigService — Manages per-tenant SSO configuration stored in MongoDBSSO provider credentials (Client ID, Client Secret) are stored encrypted in MongoDB's SSOConfig collection, never in configuration files.
Security issues should be reported through the OpenMSP Slack community (not through public GitHub issues):