Skip to content

Security and Roles

Balaji Shankaran edited this page Oct 4, 2026 · 2 revisions

Security and roles

Two kinds of caller

Since 0.2.0 the gateway tells an SMO-internal caller from an rApp:

  • internal: an SMO module (or the GUI backend). It proves it by presenting the enrollment secret (X-SMO-Enrollment, the compose secret secrets/enrollment_secret) when it registers with SME. It is mounted into SME and every SMO module, and never into an rApp. Internal invokers may hold the smo-internal and smo-gui scopes.
  • rApp: everything else. It may only ask for smo-rapp.

SME records the role per invoker and returns it on token introspection; R1 Termination forwards it as X-R1-Role (any caller-supplied value is dropped).

What an rApp may and may not do

Two lists in smo/shared/smo_shared/roles.py decide it at the gateway, before any module is called:

  • Deny-list (INTERNAL_ONLY): routes an rApp may not call in any method: per-rApp limits, KPI definitions, config-history purge, the kill switch, safeguard subscriptions and refusals, and the KPI standard-set and publish routes.
  • Allow-list for changes (RAPP_MAY_CHANGE, since 0.3.0): an rApp may make a change (POST, PUT, PATCH, DELETE) only on what the AI Runtime SDK uses, the consumer-facing request routes of the AI/ML services, config jobs and their rollback (where its safeguards apply), and the rApp-facing modules. Everywhere else a change is refused with 403 ROLE_NOT_PERMITTED: onboarding, instance management, orchestration, other invokers' registrations, and administration of the services.

Reads are still open to every valid token apart from the deny-list. An SMO module is never refused by either list.

A stopped rApp

When an operator stops an rApp (see rApp Safeguards) the gateway refuses every change it makes, and every change an SMO module makes on its behalf, with 403 RAPP_KILLED. It can still read, delete what it made, get a token and roll back its own config job.

Audit trail

Every authenticated change through the gateway (including the ones it refuses) is written to a tamper-evident hash chain: who (invoker id and role), what (method and path, never the query or body), the result and the correlation id. python -m smo_shared.audit verify finds an edited, removed or inserted row; export writes the rows as JSON lines or syslog with a closing head line to keep elsewhere. See Operations.

Settings

Variable Meaning
SMO_ROLE_ENFORCEMENT enforce (default) or audit (count and log, then allow; for rolling upgrades and for moving an rApp that calls a route outside the allow-list)
R1_KILL_SWITCH on (default) or off: whether the gateway refuses changes by a stopped rApp
R1_AUDIT on (default) or off: whether the gateway records changes in the audit chain
SME_ALLOW_OPEN_ENROLLMENT tests and development only

Per-instance rApp identity

rApp Management registers an SME invoker for every instance; its id is the instance's oauthClientId. POST /instances/{id}/credentials issues the client id and secret once (Cache-Control: no-store) and rotates them on a repeat while the instance is DEPLOYING. Terminating the instance removes the invoker. On Kubernetes the credentials can be delivered straight into a Secret for the workload (see Helm on Kubernetes).

Other controls

  • No default credentials: scripts/init_secrets.sh creates the secrets. Rotation runbooks for each secret: smo/docs/SECRETS.md (scripts/rotate_db_password.sh for the database password).
  • TLS edge profile with HSTS; the GUI session cookie is Secure by default.
  • Containers run non-root with capabilities dropped; request body size is capped and R1 rate-limits callers.
  • Reporting a vulnerability: see SECURITY.md (do not open a public issue). Supported: main and the newest 0.MINOR line (currently 0.3.x).

Clone this wiki locally