Skip to content

Security: TheCloudHub/anylake

Security

docs/security.md

Security

Secure by design: deny by default, credentials never stored in the clear, and identity delegated to whatever directory you already run.

Sign-in

Studio opens on a cloud-console-style sign-in page. Email and password works out of the box — the first account created becomes the lake administrator — and Google or Microsoft SSO light up when configured:

export ANYLAKE_GOOGLE_CLIENT_ID=... ANYLAKE_GOOGLE_CLIENT_SECRET=...
export ANYLAKE_MS_CLIENT_ID=... ANYLAKE_MS_CLIENT_SECRET=... ANYLAKE_MS_TENANT=...
anylake studio /data/lake     # redirect URIs: /auth/callback/{google,microsoft}

Accounts live in the lake (_security/users.json) with PBKDF2-HMAC-SHA256 hashing at 240k iterations and per-user salts; sessions are random 256-bit tokens in HttpOnly, SameSite cookies. SSO uses the OAuth 2.0 authorization-code flow with CSRF state validation.

Machine clients (Grafana, scripts) authenticate with --token / ANYLAKE_TOKEN. --no-accounts disables sign-in entirely for local prototyping.

Serve behind TLS in production — session cookies are not marked Secure, so a plaintext deployment exposes them.

Directory-backed identity

For real organisations, skip local accounts and connect the directory you already have:

from anylake.auth import LdapProvider, OidcProvider, Authorizer

# on-prem Active Directory / any LDAP
idp = LdapProvider("ldaps://ad.corp.com",
                   "uid={username},ou=people,dc=corp,dc=com",
                   "ou=groups,dc=corp,dc=com")

# or any cloud entity: Entra ID, Google, Okta, Keycloak, AWS Identity Center
idp = OidcProvider(issuer="https://login.microsoftonline.com/<tenant>/v2.0",
                   audience="api://anylake")

alice = idp.authenticate(username="alice", password="...")   # or token=jwt

Authorization

Deny-by-default RBAC: a table you lack a grant for is never registered in your session, so it doesn't exist for that query — no metadata leakage. Grants live in the lake itself, not in a separate service.

az = Authorizer(lake)
az.grant("group:analysts", "sales*", ["read"])
Engine(lake, principal=alice, authorizer=az).sql("SELECT ...")

Data protection

Data files are immutable with content-addressed names, so there are no overwrite attacks. Transport is TLS throughout (ldaps://, HTTPS object storage, verified JWT signatures). At-rest encryption is delegated to the store's native SSE/KMS/CMEK — on by default, no key juggling.

SQL surfaces are read-only and single-statement: Studio, Grafana, and the MCP query tool reject anything that isn't a lone SELECT.

Known limitation

RBAC is enforced in-process. Anyone holding the underlying storage credentials can read the Parquet files directly, bypassing grants. Closing this needs a mediating server that brokers all access — see the roadmap. Until then, treat storage credentials as the real security boundary and scope them per environment.

There aren't any published security advisories