Skip to content

Security Model ​

Read this before integrating anything beyond your own local development environment.

Current state (honest, as of Milestone 2C) ​

  • Authorization: Bearer <credential> is real authentication, and is enforced in any production-like deployment. The engine resolves every bearer token against a server-minted, Postgres-backed PrincipalCredential (hashed at rest — the raw secret is never stored, and shown to you exactly once at issuance/rotation time). An unrecognized, revoked, or expired credential is rejected; there is no fallback to trusting the token's mere presence.
  • X-Tenant-ID can no longer grant you a tenant by itself. In enforced mode it is accepted only as a selection hint among your own authenticated principal's active tenant memberships — useful if you belong to more than one tenant — and an invalid or unauthorized value is rejected outright, never silently substituted with a tenant you didn't ask for.
  • Role-based access control is real and centralized. Every tenant membership carries one of four roles (tenant_owner, integration_admin, integration_developer, integration_viewer), each mapped to an explicit, deny-by-default set of permissions. A role without a granted permission is rejected (role_not_permitted), not silently allowed.
  • submitted_by/owner-style fields remain caller-declared, free-text descriptive fields. They are not cross-checked against your authenticated identity beyond the permission check on the request itself — set them honestly; the platform trusts the field's content but not its authorship.
  • Development mode still exists, and is clearly separated. Outside a production-like deployment (MANAGEMENT_AUTH_MODE=development, the default for local development), a request presenting no bearer token at all falls back to the legacy X-Tenant-ID/X-Actor-Id/X-Platform-Role headers, and every such request is visibly logged as a bypass. This fallback is structurally unreachable once MANAGEMENT_AUTH_MODE=enforced — required in any production-like deployment.
  • No real OIDC/OAuth2 human login exists yet. The only implemented authentication method is the opaque bearer credential above — closer to a service-account/API-key model than a browser-based user login.
  • No real personal data should ever be sent to this platform. Every synthetic case, source, and example in this documentation uses fabricated data.
  • No production-readiness claims beyond the authentication/authorization boundary above. Real deployment also requires Postgres persistence (PERSISTENCE_BACKEND=postgres), a real Secret Manager backend for provider credentials, and the operational bootstrap procedure described below — see the root README's Cloud Run deployment section.
  • No customer identity integration exists (no SSO, no IAM-issued role claims layered on top of the credential model above).
  • No system-of-record integration exists (for example EHR/FHIR or a loan origination system). All subject context is synthetic and supplied directly by the caller.
  • Policies are global across all tenants in this version — there is no per-tenant policy configuration yet, even though reading/toggling them is now a tenant-permission-gated operation (policy:read/policy:update).

Getting a credential ​

Every tenant user, tenant service account, and platform admin authenticates with a bearer credential issued through the admin API by a platform admin:

POST /admin/management-principals/:id/credentials
Authorization: Bearer <a platform admin's own credential>
{ "credential_type": "management-api-key", "environment": "production" }

The response's secret field is your bearer credential — store it like any other API key; it is shown exactly once and never retrievable again. Use it as Authorization: Bearer <secret> on every request. If you belong to more than one tenant, add X-Tenant-ID: <tenant_id> to select which one a request acts on (omit it if you belong to exactly one tenant — it's selected automatically).

If a credential is compromised, ask a platform admin to revoke it (POST /admin/principal-credentials/:id/revoke) — this takes effect immediately, on your very next request.

Customer-owned model credentials (Initiative 2/3) ​

Managed execution (POST /runtime/agents/:agentId/execute) requires a tenant-owned Provider Connection, reached through an Execution Profile — CARC (Giggle AI Innovation) never pays for or supplies model credentials on a customer's behalf. The precise guarantee:

  • A RegisteredAgent needs an execution_profile_id, and that ExecutionProfile needs both adapter_id and provider_connection_id set, before managed execution will run; there is no fallback path that substitutes a different profile, connection, or a shared credential when any of these is missing.
  • Both the ExecutionProfile and the ProviderConnection it references are always looked up scoped to the requesting agent's own tenant_id — a tenant can never resolve, and therefore can never execute against, another tenant's profile or connection. This includes Giggle AI Innovation's own internal/demo connection and profile (seeded only under the engine's configured default tenant at boot) — exactly as unreachable from any other tenant as any other tenant's resources would be.
  • Raw provider credentials are never stored in a RegisteredAgent, an ExecutionProfile, an AgentRun, a DecisionContextRecord, a RuntimeEvent, a log line, or any API response — only an opaque credential_reference pointer on ProviderConnection (see Provider Connections). ExecutionProfile itself has no field that could ever hold a credential.
  • Agents registered before Initiative 3 with the old adapter_id+ provider_connection_id shape are transparently migrated to an auto-created ExecutionProfile at the HTTP boundary — this migration shim never accepts a caller-supplied "internal/demo" flag, so it cannot be used to point a customer tenant at Giggle's own credentials either.
  • POST /runtime/agents/:agentId/execute is a separate machine-to-machine authorization boundary (IntegrationAuthorizationMiddleware), gated by an IntegrationCredentialBinding, not by the management bearer credential described above — the two credential types are never interchangeable in either direction.

Roles and permissions ​

RoleSummary
tenant_ownerEvery tenant permission.
integration_adminEvery tenant permission.
integration_developerFull read/write across every tenant-scoped resource, except rotating a production integration credential.
integration_viewerRead-only across every tenant-scoped resource.

A tenant membership is assigned one role by a platform admin (POST /admin/management-principals/:id/memberships); changing it later goes through PATCH /admin/tenant-memberships/:id/role.

SDK credential-plane separation (Milestone 2D) ​

The SDK is split into three classes — CarcManagementClient, CarcAdminClient, CarcRuntimeClient — one per credential plane above, so a management credential can never be passed where an integration runtime credential is expected or vice versa without an explicit, visible rename at the call site. See SDK Guide for the full breakdown of which routes each client can reach. This is a structural guarantee, not just a naming convention: each class only exposes the methods for its own plane.

Audit trails ​

Every Integration Governance mutation and runtime authorization outcome (IntegrationAuditEvent) and every management-identity mutation (PrincipalAuditEvent) is recorded as an immutable, append-only event — metadata on both never carries a raw secret or subject payload. Three platform-admin-only read routes exist:

GET /admin/integrations/:id/audit-events               # one integration's complete trail
GET /admin/audit-events/integration-governance          # tenant-wide, filterable by integration/environment/actor/action/correlation_id/time range
GET /admin/audit-events/principals                       # platform-wide management-identity trail, same filter set

The two audit stores remain structurally separate in storage — the Control Console's Audit Timeline view (see Control Console) aggregates both into one chronological list only at the presentation layer, never by merging them server-side.

Control Console session model (Milestone 2D) ​

The Control Console (apps/control-console) is a pure API-client SPA — it never talks to Postgres directly, never bypasses this authentication layer, and never uses MANAGEMENT_AUTH_MODE=development's header-based identity in a production build. It has no backend-for-frontend server of its own today, so its session model is a deliberate, disclosed trade-off: the signed-in bearer credential is held only in React state (in memory), never in localStorage/sessionStorage/a cookie, and is cleared on sign-out or tab close. This is explicitly not final enterprise SSO — see "Intended production direction" below for the extension point.

Intended production direction ​

The following remain on the roadmap, not implemented:

  • Real OIDC/OAuth2 human login. Both the engine and the Control Console are structured so this can land as an additive change: the engine would gain an OIDC-token-accepting authentication path alongside (not replacing) the existing bearer-credential path authenticateManagementPrincipal already centralizes, and the Console would replace its in-memory bearer-credential session (above) with a real browser session — ideally via a backend-for-frontend issuing secure, HTTP-only, same-site cookies, since a pure static SPA has no safe place to hold a long-lived session token itself. Until then, the Console's login flow and the bearer-credential model above are the only implemented option.
  • A self-service credential issuance flow — today, only a platform admin can issue a credential for anyone, including a tenant's own users.
  • Per-tenant policy configuration — policies remain global across all tenants; only list/enable/disable is tenant-permission-gated today.
  • Customer-controlled deployment: this platform designed to run inside a customer's own infrastructure boundary, not as a shared multi-tenant SaaS.

What to do today ​

  • Run this platform only in trusted environments until you've completed the Cloud Run bootstrap/enforcement steps in the root README.
  • Use only synthetic data, never real personal information.
  • Treat any credential (PrincipalCredential secret, ProviderConnection credential, IntegrationCredentialBinding reference) as a real secret: never commit it, never log it, and revoke it immediately if you suspect it leaked.
  • If you are integrating today without MANAGEMENT_AUTH_MODE=enforced configured, you are running in development mode — confirm your deployment's ENVIRONMENT/MANAGEMENT_AUTH_MODE before relying on any of the guarantees above in a real deployment.

Developer Platform v1: Consequential Agent Runtime Control Platform