Skip to content

Security

The public Sandbox is open. A production EvidentSource deployment requires authentication. Bearer callers receive grants from their tokens; verified ALB-OIDC console users receive grants from an explicit server policy.

Read this whole page before relying on it. With EVS_REQUIRE_AUTH on, the server verifies tokens itself, or trusts a layer in front of it only when told to; the sections below say how to deploy safely.

The server’s EVS_REQUIRE_AUTH setting decides whether a request must carry an identity. The AWS Marketplace template sets it from its RequireAuth parameter, which defaults to true.

With authentication off, the HTTP API allows every request that carries no credential. Such a request acts as an anonymous caller holding every permission. That is how the Sandbox runs, and it is never appropriate for real data. A request that does carry a token the server refuses, malformed or expired, is refused with 401 even then, rather than served anonymously. Authorization: Bearer with an empty token counts as no credential.

A caller authenticates with a JSON Web Token:

  • HTTP: Authorization: Bearer <token>
  • gRPC: the authorization metadata key, with the same value.

With EVS_JWKS_URI set, the server verifies each token’s signature itself against the key set your identity provider publishes, and checks its issuer, audience and expiry. Configure it with:

SettingWhat it is
EVS_JWKS_URIThe provider’s key set: the jwks_uri of its OpenID configuration
EVS_JWT_ISSUERThe iss every token must carry
EVS_JWT_AUDIENCEAn audience a token must name; separate several with commas
EVS_JWKS_CA_FILEOptional: a PEM file of your CA, if the provider’s certificate chains to a private one

The default audience contract checks aud as a string or array. Set EVS_JWT_AUDIENCE_MODE=cognito-access for Cognito access tokens: the server requires a signed scalar client_id matching EVS_JWT_AUDIENCE and token_use=access. Use a dedicated Cognito app client for this service; configure its user pool as the issuer and keep grants least privileged. ID tokens, wrong clients and tokens missing these claims are refused. Signature, issuer, expiry, subject and not-before verification remain enforced. The aud-string and aud-array modes additionally require the selected claim shape.

In the Marketplace template, set JwtAudienceMode to cognito-access and JwtAudience to that app client ID. For ordinary aud tokens, choose aud-string or aud-array (the existing default). Both ALB listeners and the server check the same claim shape and value. This template accepts exactly one printable ASCII audience without commas or whitespace; standalone server audience lists remain supported. There is no setting to disable audience verification.

Whenever EVS_JWKS_URI is set, the server fetches the key set at startup, retrying for up to about a minute, and won’t start if it can’t, so it needs a network route to the identity provider. In the Marketplace template, these come from JwtJwksEndpoint, JwtIssuer and JwtAudience (one audience), which are required when RequireAuth is true. The load balancer verifies tokens on its HTTPS and gRPC listeners too. Requests to the MCP hostname skip the load balancer’s check, but the MCP service forwards the caller’s token, and the server verifies it.

With EVS_REQUIRE_AUTH on, the server won’t start unless something verifies tokens. It needs EVS_JWKS_URI, an ALB OIDC grants policy, or EVS_TRUST_EDGE_IDENTITY=true, which tells it to trust what a layer in front of it asserts: tokens that layer verified, and the identity headers an AWS load balancer sets after an OIDC sign-in. Set edge trust only when every request reaches the server through that layer. Otherwise anyone who can reach the server can write their own token. With EVS_JWKS_URI set as well, bearer tokens are still verified. Without a grants policy, trusted ALB headers identify a caller but assign no grants.

For a console behind an ALB authenticate-oidc or authenticate-cognito action, set EVS_REQUIRE_AUTH=true and EVS_ALB_OIDC_GRANTS_FILE to an operator-owned JSON file available inside each server. For example:

{
"alb_arn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/console/0123456789abcdef",
"issuer": "https://your-oidc-issuer.example",
"client_id": "your-console-app-client",
"subjects": {
"the-users-exact-oidc-subject": {
"databases": { "production": ["reader"] }
}
}
}

Replace the ARN, issuer, client and subject with your actual OIDC deployment values. Use the identity provider’s stable sub, rather than matching email or groups. The server verifies the signature of x-amzn-oidc-data, its ALB signer, issuer, client and signed expiry, then grants only the listed roles. Unlisted users receive no grants and see an empty catalog. The unsigned identity header and any user-info grant claims cannot grant access.

databases accepts reader, writer, deployer and database_deleter on exact database names. A subject can explicitly use global: ["database_creator"] or all_databases: ["reader"], but add those only when that broader scope is needed. Unknown fields, roles and roles in the wrong scope fail startup.

Commercial AWS and GovCloud ALBs are supported. The server needs outbound HTTPS access to AWS’s regional ALB public-key endpoint. Restrict target ingress to your ALB and configure its authenticate action on every console route. Signature verification supports this mode without EVS_TRUST_EDGE_IDENTITY. If API clients also use bearer tokens, configure EVS_JWKS_URI and its issuer/audience settings; bearer tokens are verified first. Without JWKS or explicit edge trust, a presented bearer token is refused.

Distribute the policy through your deployment’s protected configuration mount or image and set the file path in its server environment. The stock Marketplace template configures JWT verification; it does not create an OIDC login action or mount this file. Customize the deployment for OIDC console sign-in.

Policy changes require restarting every server. Updated nodes apply the new grants on the next request, including with an existing ALB session. Existing requests keep their captured identity; server-streaming gRPC RPCs stop at signed token expiry or server shutdown. HTTP streamed presentations are authorized when the request is admitted. Private State View responses cached by a browser can remain visible for up to a minute. No grants policy is enabled by default.

Keep the load balancer internal (AlbScheme, the default), so that only callers inside your network can reach it at all. An internal load balancer limits who can connect; it doesn’t make a token trustworthy.

A token’s evs:grants claim describes what the caller may do. Grants name roles, either globally, for named databases, or for every database:

{
"evs:grants": {
"global": ["database_creator"],
"databases": {
"orders": ["reader", "writer"],
"orders-staging": ["reader", "writer", "deployer", "database_deleter"]
},
"all_databases": ["reader"]
}
}
RoleScopePermits
database_creatorglobalCreating databases
readerdatabaseQuerying events, rendering State Views
writerdatabaseEverything reader permits, plus appending transactions and running State Changes
deployerdatabasePublishing State Changes and State Views, and expiring a State View’s snapshots
database_deleterdatabaseDeleting the database

Roles combine: a caller holds the union of every role it is granted. deployer doesn’t include reader.

With authentication on, every HTTP route and gRPC call that names a database checks the caller’s grants on it:

  • reading anything (events, the log, transactions, State Views, listings, definitions, and the web pages that show them) needs reader;
  • appending a transaction or running a State Change needs writer;
  • publishing a State Change or State View, or expiring a State View’s snapshots, needs deployer;
  • deleting a database needs database_deleter, and creating one database_creator.

The database catalog lists only the databases a caller holds a grant on. A caller with no grant on a database gets 403 (gRPC PERMISSION_DENIED) for anything that names it. One exception is inherent: creating a database whose name is taken answers 409 (gRPC ALREADY_EXISTS), so a caller allowed to create databases can learn that a name is in use.

Every authenticated gRPC stream ends at its token’s expiry with UNAUTHENTICATED. Reconnect with a fresh token; the server checks its grants again. Expiry releases an idle or backpressured stream’s source. The terminal status arrives when transport capacity permits. Data already sent or buffered cannot be recalled. With authentication required, a streaming credential must include an expiry; a trusted-edge identity without one cannot open a stream.

Bearer grants are carried in signed tokens. Removing a grant at your identity provider does not invalidate tokens already issued: their grants remain usable until expiry, including on an open stream. Issue short-lived tokens to bound that revocation window. The server does not poll your provider for grant changes.

Verified ALB-OIDC grants come from the policy loaded at startup. After changing that policy and restarting every server, new requests use the updated grants, even with the same still-valid ALB token. Open streams retain their opening grants until token expiry or server shutdown.

The server takes the caller’s identity from the token:

  • sub is the caller’s subject.
  • evs:principal describes the caller: name, email, type (human, agent or system), provider, and upstream_id. For an agent, delegator names the human it acts for, with a subject and a name.
  • Without evs:principal, the server reads name and email from the token’s top level.
  • The caller’s attributes — subject, name, email, issuer, principal type and delegator — are recorded on transactions appended directly, and on State Changes run synchronously, over HTTP or gRPC.
  • Every event carries authtype, the kind of caller, and for an identified caller its authid.
  • Asynchronous commands record the caller too: the server writes the authenticated caller onto the record it produces. Kafka’s access control is the trust boundary: a record produced to the command topic directly records whatever caller attributes it carries, so grant produce rights on {database}.{database_id}.async-command only to producers you trust to say who they act for.
  • With authentication off, a caller over HTTP is not identified: its events carry authtype unauthenticated, and a State Change sees an unauthenticated caller. Over gRPC, a caller that presents a token is still recorded as itself.

On an event, authtype, authid and the other auth attributes come from the caller’s credentials, or from the record an asynchronous command carries; the server discards any copy a client puts on the event itself. They are context, not proof: they aren’t signed, and they record who the credentials or the record said the caller was, so read authid together with authtype. See Decision Traces & AI-Readiness.

With telemetry enabled, HTTP and gRPC request traces record the admitted caller’s enduser.id and optional enduser.email and auth.provider. These request spans are emitted at INFO and survive the default info filter. auth.source identifies the credential path: bearer, alb_oidc, or authenticated for a custom extractor. A token’s provider claim cannot change that source. Optional requests without identity record anonymous; missing required identity or a credential the server cannot admit records refused. An issuer outage still returns UNAVAILABLE/503, rather than declaring the token invalid. Bearer attribution follows the verification posture configured above; optional unverified or edge-trusted identity is not independent signature proof. Credentials and raw token claims are omitted from these trace fields. gRPC health and reflection bypass authentication; gRPC health request traces are filtered, and reflection traces carry no authentication verdict. HTTP health probes also bypass authentication and retain their request traces.

The server needs no secrets of its own. Its access to S3 comes from the IAM role its tasks run with, which the Marketplace template creates. Don’t configure static AWS keys.

  • RequireAuth is true, and JwtIssuer, JwtJwksEndpoint and JwtAudience are set.
  • The load balancer is internal, unless it must be reachable from outside your network.
  • Each caller’s token grants only the databases and roles it needs.
  • The TLS certificate in ACM covers the API hostname.
  • You watch the traces and metrics the template’s collector sends to AWS X-Ray and CloudWatch.