Skip to content

Security model ​

Who may call what ​

  • Three roles, each including the ones before it: viewer < operator < admin. Every route of /api/v1 names its least role; a route that names none needs viewer for a GET and operator for anything else. Too low a role is 403 forbidden_role. The full table is Roles and permissions.
  • Credentials, tried in order: a bearer token whose SHA-256 is in JOOBQUI_API_TOKENS (with an optional :viewer, :operator or :admin; no role means operator), a token an admin created in the console (kept only as a hash and looked up on every request, so revoking it works at once), then the console's session cookie.
  • Browser sessions are encrypted, signed cookies that stop working JOOBQUI_SESSION_TTL_MINUTES after sign-in (60 by default). A cookie must send X-Requested-With: joobqui on every state-changing request; bearer tokens are exempt. Signing out revokes the session on the server.
  • The SDK gateway (/api/v1/sdk/*) takes one shared token, JOOBQUI_SDK_TOKEN, compared in constant time. It acts as an operator on the gateway routes only. Unset, the gateway answers 503 sdk_token_not_configured; a wrong token gets 401 sdk_token_required. The gateway exposes no management operation (pause, resume, re-drive, cancel, terminate, concurrency): those are console operations.
  • Two-factor authentication (TOTP with recovery codes) can be required per role (JOOBQUI_MFA_REQUIRED_ROLES), and OpenID Connect single sign-on maps the provider's groups to roles (JOOBQUI_OIDC_ROLE_MAP). See Accounts, SSO, 2FA, roles.

What is recorded ​

Every state-changing call is written to the audit log as METHOD /path, with a better row (token.create, user.role, mfa.reset, ...) where the route records one. Changes to who has access or what protects it are critical: an intent row is written first, and the call is refused with 503 when it cannot be. Enqueues, starts, schedule and blueprint registrations through the SDK gateway are audited too (sdk.job.enqueue, sdk.workflow.start, ...). See Auditing.

What is hidden ​

  • Secret-looking values are masked in every answer: job payloads, schedule arguments, stream events, workflow inputs, outputs and query results. A key is secret-looking when its name looks like a credential (passwords, secrets, tokens, keys, authorization headers, cookies and the like). The match is generous, so some harmless names are masked too.
  • Sealed job fields (encrypt_fields in the SDKs, encrypted_property in Crystal) are encrypted with JOOBQUI_PAYLOAD_KEYS before the job is stored; Redis never holds them in plain text. See Payload encryption.
  • Secrets at rest: notification-channel URLs and keys and two-factor secrets are sealed (AES-256-GCM) with JOOBQUI_SECRETS_KEY; API tokens and recovery codes are stored only as hashes; passwords as bcrypt hashes.
  • A secret never appears in a log line, an audit row, an error message or an answer, except the one answer that hands it out once (a new API token).

JoobQ Core is MIT licensed. JoobQ Pro needs a licence key.