Skip to content
Secret Storage

Secret Storage

TeamWeb AI manages several types of sensitive credentials: LLM provider API keys, channel plugin tokens (Slack bot tokens, Telegram bot tokens), third-party service credentials (WordPress, Unsplash, analytics), and user passwords. Each is handled differently depending on where it’s configured and how it’s used.

Environment Variables

Core infrastructure secrets are stored as environment variables, loaded from a .env file or the deployment environment:

SecretPurpose
SECRET_KEYFlask session signing and CSRF token generation
DATABASE_URLPostgreSQL connection string
REDIS_URLRedis broker connection
S3_ACCESS_KEY_ID / S3_SECRET_ACCESS_KEYS3-compatible media storage (optional)

These are never stored in the database and are only available to the web and worker processes.

Always set a strong, unique SECRET_KEY in production. The default value in development is not secure. This key signs session cookies and CSRF tokens — if compromised, an attacker could forge sessions.

Plugin Configuration Secrets

LLM provider API keys, channel tokens, and third-party service credentials are configured through the plugin system and stored in the database as JSON:

Plugin TypeStorage LevelDatabase Table
LLM / Embedding providersGlobal (one active)core_plugin_configs
Channel plugins (Slack, Telegram, etc.)Per assistantassistant_plugins
Tool plugins (WordPress, Unsplash, etc.)Per projectproject_plugins

Each plugin defines a configuration schema that can mark fields as secret. Secret fields receive special treatment in the UI:

  • When displaying a configuration form, secret fields are shown as empty with a placeholder indicating a value is already stored
  • On form submission, if a secret field is left empty and a value already exists, the existing value is preserved — preventing accidental overwrites
  • Secret values are never rendered back into form fields or exposed in API responses
Technical Details

Storage format — Plugin configuration is stored in config_json columns as plain JSON. Fields marked secret: true in the plugin’s config_schema are not encrypted at rest — the protection is at the application layer (masking in forms, not returning values in responses). Database-level access to these tables would expose the raw credentials.

Masking implementation — When rendering a plugin configuration form, the system iterates over the schema and replaces any field with secret: true with an empty string in the display data. A separate has_secrets dictionary tracks which secret fields already have stored values, so the UI can show “Value stored” indicators without revealing the actual value. On POST, the save logic checks whether each secret field was left blank — if so, it keeps the existing value from the database rather than overwriting with an empty string.

Implications — Since secrets are stored as plain JSON in PostgreSQL, protecting the database is critical. Anyone with database access (direct SQL, backup files, or a compromised database connection string) can read all configured API keys and tokens. See Database Protection for how TeamWeb AI isolates the database from untrusted code execution.

User Passwords

User passwords are hashed using Werkzeug’s password hashing utilities before storage. Plaintext passwords are never stored or logged. The hashing uses a secure algorithm with salt — even with database access, recovering passwords requires a brute-force attack against the hash.

Session Security

TeamWeb AI uses Flask-Login for session management with server-side session state. Sessions are signed using the SECRET_KEY to prevent tampering. Public chat visitors receive a separate session with a unique identifier that is isolated from authenticated user sessions.

CSRF Protection

All form submissions are protected by CSRF tokens via Flask-WTF. The CSRF middleware is applied globally, with targeted exemptions only for:

  • Webhook endpoints — Inbound messages from Slack, Telegram, and Postmark are verified using channel-specific mechanisms (HMAC signatures, secret tokens) rather than CSRF tokens
  • Public chat — Anonymous chat uses session-based identification instead of CSRF tokens

Webhook Verification

Each channel plugin implements its own request verification to ensure inbound webhooks are authentic:

ChannelVerification Method
SlackHMAC-SHA256 signature using the signing secret, verified against the X-Slack-Signature header with timestamp replay protection
TelegramSecret token comparison via X-Telegram-Bot-Api-Secret-Token header
PostmarkRelies on external network-level controls (IP allowlisting)

Unverified requests are rejected before any message processing occurs. After verification, the system also checks that the sender is a registered, active user — unknown senders receive a 403 response.

Best Practices

  • Use strong, unique values for SECRET_KEY and POSTGRES_PASSWORD in production
  • Restrict database access to only the application services that need it
  • Use a secrets manager or encrypted environment variable store in production deployments
  • Rotate API keys periodically, especially if you suspect a credential leak
  • Review the Database Protection and Code Sandboxing pages to understand how TeamWeb AI isolates untrusted code from your credentials