Skip to content

security: Require user auth secret everywhere#2458

Open
topher-lo wants to merge 4 commits intomainfrom
harden/require-userauthsecret
Open

security: Require user auth secret everywhere#2458
topher-lo wants to merge 4 commits intomainfrom
harden/require-userauthsecret

Conversation

@topher-lo
Copy link
Copy Markdown
Contributor

@topher-lo topher-lo commented Apr 2, 2026

Summary

This change hardens Tracecat's core secret handling by making USER_AUTH_SECRET required everywhere instead of conditionally optional. It also keeps offline OpenAPI generation deterministic by bootstrapping explicit tooling-only placeholder values instead of relying on a developer .env or real runtime secrets.

Problem

Older deployments could leave USER_AUTH_SECRET unset or empty. That weakened password reset, email verification, and OAuth/OIDC state-token signing, while also leaving the enforcement story inconsistent across the application, Fargate Terraform, and operator documentation.

At the same time, strict import-time config validation started blocking OpenAPI generation because the generator imports the application and therefore inherited the same required secret checks, even though schema generation does not need real secrets.

Root cause

USER_AUTH_SECRET was treated as optional in parts of the stack and only effectively enforced in specific auth modes. Terraform inputs and docs reflected that older assumption, which allowed misconfigured environments to survive until a sensitive auth path was exercised. Tooling paths were also coupled too tightly to runtime configuration.

Fix

The application now fails fast when any of the four core secrets are empty: USER_AUTH_SECRET, TRACECAT__DB_ENCRYPTION_KEY, TRACECAT__SIGNING_SECRET, and TRACECAT__SERVICE_KEY.

Fargate now treats user_auth_secret_arn as a required input with no default = null, and the ECS secret wiring is unconditional so terraform plan fails immediately when the secret ARN is missing.

The self-hosting docs were updated to describe supported deployment options, clarify the required secrets, warn that all of them must be cryptographically secure and non-empty, and position the Enterprise Kubernetes deployment as the path for larger installs.

OpenAPI generation now sets process-local fake values before importing the app, which preserves strict runtime enforcement without requiring real secrets for offline schema generation.

Validation

I validated the config and deployment changes with:

  • uv run pytest tests/unit/test_oidc_config.py
  • uv run ruff check tracecat/config.py tests/unit/test_oidc_config.py tests/conftest.py
  • uv run python scripts/openapi/generate_openapi.py
  • terraform fmt deployments/fargate/variables.tf deployments/fargate/modules/ecs/variables.tf deployments/fargate/modules/ecs/secrets.tf deployments/fargate/main.tf

Notes for operators

Rotating USER_AUTH_SECRET does not invalidate existing logged-in sessions because normal sessions are DB-backed opaque tokens. It does invalidate outstanding password reset tokens, email verification tokens, and in-flight OAuth/OIDC state tokens.


Summary by cubic

Require USER_AUTH_SECRET at API startup and make it mandatory in Fargate, while scoping other secret checks to auth-specific runtime paths so tooling isn’t blocked. OpenAPI generation now seeds process-local placeholders, and Docker Compose passes core secrets to the migrations service.

  • Refactors

    • API startup fails fast if USER_AUTH_SECRET is missing; UserManager enforces it only when basic is enabled.
    • Require OIDC_ISSUER, OIDC_CLIENT_ID, and OIDC_CLIENT_SECRET when oidc is enabled; ignore for other modes.
    • Infra: Fargate requires user_auth_secret_arn (ECS wiring is unconditional; deployments/fargate/scripts/create-aws-secrets.sh creates it). OpenAPI generator seeds fake values before import. Docs add a self-hosting overview and clarify required secrets; .env.example marks required fields.
  • Migration

    • Set a strong USER_AUTH_SECRET in all environments. Ensure all required secrets are non-empty.
    • Fargate: create an AWS Secrets Manager entry for USER_AUTH_SECRET and pass TF_VAR_user_auth_secret_arn; terraform plan will fail if missing.
    • No action needed for OpenAPI generation; it runs without real secrets.

Written for commit 8111d03. Summary will update on new commits.

@mintlify
Copy link
Copy Markdown

mintlify bot commented Apr 2, 2026

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
tracecat 🟢 Ready View Preview Apr 2, 2026, 2:24 AM

@zeropath-ai
Copy link
Copy Markdown

zeropath-ai bot commented Apr 2, 2026

No security or compliance issues detected. Reviewed everything up to 8111d03.

Security Overview
Detected Code Changes
Change Type Relevant files
Configuration changes ► .env.example
    Add Vercel OAuth Environment Variables
► deployments/fargate/main.tf
    Align Fargate module variable names
► deployments/fargate/modules/ecs/secrets.tf
    Add USER_AUTH_SECRET to secrets
► deployments/fargate/modules/ecs/variables.tf
    Make USER_AUTH_SECRET mandatory
► deployments/fargate/scripts/create-aws-secrets.sh
    Add USER_AUTH_SECRET generation
► deployments/fargate/variables.tf
    Make USER_AUTH_SECRET mandatory
► docs/docs.json
    Add self-hosting overview to docs
► docs/overview/getting-started.mdx
    Update self-hosting link
► docs/overview/introduction.mdx
    Update self-hosting link
► docs/self-hosting/aws-fargate.mdx
    Update required secrets list
► docs/self-hosting/docker-compose.mdx
    Add self-hosting requirements snippet
► docs/self-hosting/overview.mdx
    Create self-hosting overview page
► docs/snippets/self-hosting-requirements.mdx
    Add self-hosting requirements snippet
► scripts/openapi/generate_openapi.py
    Configure OpenAPI env with required secrets
► tests/conftest.py
    Add required base secrets for tests
► tests/unit/test_oidc_config.py
    Add required base secrets fixture
    Test missing OIDC client secret
    Test config allows missing core secrets
    Test user manager requires USER_AUTH_SECRET
► tracecat/api/app.py
    Require USER_AUTH_SECRET for app startup
► tracecat/auth/users.py
    Require USER_AUTH_SECRET for basic auth
► tracecat/config.py
    Require USER_AUTH_SECRET when basic auth is enabled
    Require OIDC secrets when OIDC auth is enabled
    Strip whitespace from env vars
Enhancement ► tracecat/config.py
    Add _require_non_empty_config helper function

@topher-lo topher-lo changed the title [codex] Require user auth secret everywhere security: Require user auth secret everywhere Apr 2, 2026
@topher-lo topher-lo added the security Security related issue label Apr 2, 2026
@topher-lo topher-lo marked this pull request as ready for review April 2, 2026 02:29
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 17 files

Confidence score: 3/5

  • There is a concrete user-impacting risk in tracecat/config.py: using in for the OIDC gate can trigger a startup crash in mixed auth setups like {BASIC, OIDC}.
  • Because this is a high-severity, high-confidence finding (7/10, 9/10) affecting app initialization, the PR carries moderate merge risk until the condition is tightened to an exact set check.
  • Pay close attention to tracecat/config.py - the auth-type gate logic should use exact matching to avoid crashing when multiple auth modes are configured.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tracecat/config.py">

<violation number="1" location="tracecat/config.py:400">
P1: Use an exact set match (`== {AuthType.OIDC}`) instead of a membership check (`in`) for the OIDC config gate. In mixed auth configurations (e.g., `{BASIC, OIDC}`), this membership check will crash the app at startup if OIDC vars are missing, preventing BASIC auth from being usable. The old code intentionally used an exact match so BASIC remains available and OIDC initialization fails later only when actually exercised.

(Based on your team's feedback about requiring OIDC_ISSUER only when auth types is exactly OIDC.) [FEEDBACK_USED]</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

)
"""Executor JWT TTL in seconds (default: 900 seconds)."""

if AuthType.OIDC in TRACECAT__AUTH_TYPES:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Use an exact set match (== {AuthType.OIDC}) instead of a membership check (in) for the OIDC config gate. In mixed auth configurations (e.g., {BASIC, OIDC}), this membership check will crash the app at startup if OIDC vars are missing, preventing BASIC auth from being usable. The old code intentionally used an exact match so BASIC remains available and OIDC initialization fails later only when actually exercised.

(Based on your team's feedback about requiring OIDC_ISSUER only when auth types is exactly OIDC.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tracecat/config.py, line 400:

<comment>Use an exact set match (`== {AuthType.OIDC}`) instead of a membership check (`in`) for the OIDC config gate. In mixed auth configurations (e.g., `{BASIC, OIDC}`), this membership check will crash the app at startup if OIDC vars are missing, preventing BASIC auth from being usable. The old code intentionally used an exact match so BASIC remains available and OIDC initialization fails later only when actually exercised.

(Based on your team's feedback about requiring OIDC_ISSUER only when auth types is exactly OIDC.) </comment>

<file context>
@@ -385,12 +391,48 @@ def _parse_auth_types() -> set[AuthType]:
 )
 """Executor JWT TTL in seconds (default: 900 seconds)."""
 
+if AuthType.OIDC in TRACECAT__AUTH_TYPES:
+    if not OIDC_ISSUER:
+        raise ValueError(
</file context>
Suggested change
if AuthType.OIDC in TRACECAT__AUTH_TYPES:
if TRACECAT__AUTH_TYPES == {AuthType.OIDC}:

@topher-lo topher-lo temporarily deployed to internal-registry-ci April 2, 2026 02:33 — with GitHub Actions Inactive
@topher-lo topher-lo had a problem deploying to internal-registry-ci April 2, 2026 02:33 — with GitHub Actions Failure
@blacksmith-sh
Copy link
Copy Markdown
Contributor

blacksmith-sh bot commented Apr 2, 2026

Found 8 test failures on Blacksmith runners:

Failures

Test View Logs
test_organization_settings/test_list_org_settings View Logs
test_organization_settings/test_sensitive_setting_handling View Logs
test_organization_settings/test_update_audit_settings View Logs
test_organization_settings/test_update_audit_settings_can_clear View Logs
test_organization_settings/test_update_git_settings View Logs
test_sandbox/test_auth_sandbox_optional_secret_missing_optional_key View Logs
test_sandbox/test_auth_sandbox_optional_secret_with_all_keys View Logs
TestAgentWorkerThrashing/test_interleaved_concurrent_bursts View Logs

Fix in Cursor

@topher-lo topher-lo temporarily deployed to internal-registry-ci April 2, 2026 02:50 — with GitHub Actions Inactive
@topher-lo topher-lo temporarily deployed to internal-registry-ci April 2, 2026 02:50 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/unit/test_oidc_config.py">

<violation number="1" location="tests/unit/test_oidc_config.py:154">
P2: This test now expects config reload to succeed with USER_AUTH_SECRET unset, which contradicts the new “required everywhere” secret policy and removes coverage for the fail-fast check.</violation>

<violation number="2" location="tests/unit/test_oidc_config.py:190">
P2: This test now asserts that missing core secrets are acceptable, which weakens coverage for the required-secret checks and can mask regressions in the fail-fast enforcement.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines +190 to +198
def test_config_allows_missing_core_tracecat_secrets(
monkeypatch, var_name: str
) -> None:
with monkeypatch.context() as env:
env.setenv("TRACECAT__AUTH_TYPES", "saml")
env.delenv(var_name, raising=False)
importlib.reload(config)

importlib.reload(config)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This test now asserts that missing core secrets are acceptable, which weakens coverage for the required-secret checks and can mask regressions in the fail-fast enforcement.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/unit/test_oidc_config.py, line 190:

<comment>This test now asserts that missing core secrets are acceptable, which weakens coverage for the required-secret checks and can mask regressions in the fail-fast enforcement.</comment>

<file context>
@@ -180,25 +179,50 @@ def test_config_requires_oidc_client_secret_when_oidc_enabled(monkeypatch) -> No
 )
-def test_config_requires_core_tracecat_secrets(
-    monkeypatch, var_name: str, expected_message: str
+def test_config_allows_missing_core_tracecat_secrets(
+    monkeypatch, var_name: str
 ) -> None:
</file context>
Suggested change
def test_config_allows_missing_core_tracecat_secrets(
monkeypatch, var_name: str
) -> None:
with monkeypatch.context() as env:
env.setenv("TRACECAT__AUTH_TYPES", "saml")
env.delenv(var_name, raising=False)
importlib.reload(config)
importlib.reload(config)
def test_config_requires_core_tracecat_secrets(
monkeypatch, var_name: str
) -> None:
with monkeypatch.context() as env:
env.setenv("TRACECAT__AUTH_TYPES", "saml")
env.delenv(var_name, raising=False)
with pytest.raises(KeyError, match=f"{var_name} must be set"):
importlib.reload(config)
importlib.reload(config)

importlib.reload(config)


def test_config_allows_missing_user_auth_secret_when_auth_type_does_not_use_it(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: This test now expects config reload to succeed with USER_AUTH_SECRET unset, which contradicts the new “required everywhere” secret policy and removes coverage for the fail-fast check.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/unit/test_oidc_config.py, line 154:

<comment>This test now expects config reload to succeed with USER_AUTH_SECRET unset, which contradicts the new “required everywhere” secret policy and removes coverage for the fail-fast check.</comment>

<file context>
@@ -150,15 +151,13 @@ def test_config_keeps_oauth_aliases_for_oidc_credentials(monkeypatch) -> None:
 
 
-def test_config_requires_user_auth_secret_unconditionally(monkeypatch) -> None:
+def test_config_allows_missing_user_auth_secret_when_auth_type_does_not_use_it(
+    monkeypatch,
+) -> None:
</file context>

@topher-lo topher-lo temporarily deployed to internal-registry-ci April 2, 2026 03:03 — with GitHub Actions Inactive
@topher-lo topher-lo had a problem deploying to internal-registry-ci April 2, 2026 03:04 — with GitHub Actions Failure
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

security Security related issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant