feat(integrations): add catalog-backed connections#2766
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
| async def delete_connection(self, connection_id: uuid.UUID) -> None: | ||
| stmt = select(Secret).where( | ||
| Secret.id == connection_id, | ||
| Secret.workspace_id == self.workspace_id, | ||
| ) | ||
| result = await self.session.execute(stmt) | ||
| secret = result.scalar_one_or_none() | ||
| if secret is not None: | ||
| await self.session.delete(secret) | ||
| await self.session.flush() | ||
| return | ||
|
|
||
| oauth_stmt = select(OAuthIntegration).where( | ||
| OAuthIntegration.id == connection_id, | ||
| OAuthIntegration.workspace_id == self.workspace_id, | ||
| ) | ||
| oauth_result = await self.session.execute(oauth_stmt) | ||
| oauth = oauth_result.scalar_one_or_none() | ||
| if oauth is None: | ||
| raise TracecatNotFoundError(f"Connection {connection_id} not found") | ||
| await self.session.delete(oauth) | ||
| await self.session.flush() |
There was a problem hiding this comment.
Broken Access Control: Unrestricted Deletion of OAuth Integrations (Severity: MEDIUM)
An attacker could delete any OAuth integration connections within a workspace due to a missing authorization check in tracecat/integrations/catalog/service.py. This could lead to a denial-of-service for affected integrations and data corruption by removing critical connection secrets.
View details in ZeroPath
Automatic patch generation was not possible for this finding.
| async def create_connection( | ||
| self, | ||
| integration: Integration, | ||
| params: CatalogConnectionCreate, | ||
| ) -> CatalogConnectionRead: | ||
| """Create or replace a static Secret for an integration environment.""" | ||
| if not isinstance(params, CatalogStaticKVConnectionCreate): | ||
| raise ValueError(f"Unsupported connection payload: {type(params).__name__}") | ||
| if not params.keys: | ||
| raise ValueError("At least one credential field is required.") | ||
| environment = params.environment.strip() or DEFAULT_SECRETS_ENVIRONMENT | ||
|
|
||
| encrypted_keys = encrypt_keyvalues( | ||
| [ | ||
| SecretKeyValue(key=key, value=SecretStr(value)) | ||
| for key, value in params.keys.items() | ||
| ], | ||
| key=self._encryption_key, | ||
| ) | ||
| result = await self.session.execute( | ||
| select(Secret).where( | ||
| Secret.workspace_id == self.workspace_id, | ||
| Secret.name == integration.namespace, | ||
| Secret.environment == environment, | ||
| ) | ||
| ) | ||
| secret = result.scalar_one_or_none() | ||
| if secret is None: | ||
| secret = Secret( | ||
| workspace_id=self.workspace_id, | ||
| name=integration.namespace, | ||
| type=SecretType.CUSTOM, | ||
| description=f"Credentials for {integration.display_name}.", | ||
| encrypted_keys=encrypted_keys, | ||
| environment=environment, | ||
| ) | ||
| else: | ||
| secret.type = SecretType.CUSTOM | ||
| secret.encrypted_keys = encrypted_keys | ||
| self.session.add(secret) | ||
| await self.session.flush() | ||
| await self.session.refresh(secret) | ||
| logger.info( | ||
| "Stored static integration credentials", | ||
| secret_id=secret.id, | ||
| integration_id=integration.id, | ||
| auth_method=params.auth_method, | ||
| workspace_id=self.workspace_id, | ||
| ) | ||
| return self._static_connection_read(integration, secret) |
There was a problem hiding this comment.
Broken Access Control: Over-Permissive Credential Modification (Severity: MEDIUM)
Any workspace member can overwrite static integration credentials, leading to potential data compromise or service disruption. This occurs in tracecat/integrations/catalog/service.py where the create_connection method lacks sufficient authorization checks before updating existing secrets, which causes insecure modification of sensitive integration data.
View details in ZeroPath
Automatic patch generation was not possible for this finding.
9ac0901 to
b3fead8
Compare
5ea6e90 to
a904707
Compare
There was a problem hiding this comment.
14 issues found
Confidence score: 1/5
- There are concrete, high-confidence security regressions:
tracecat/validation/service.pybypasses thesecret:readscope path, andtracecat/integrations/catalog/router.pyis missingrequire_scopeguards on catalog routes, which can allow unauthorized reads/writes. - Data integrity risk is also high in
tracecat/db/models.py: the new catalog constraints appear to allow invalid global/workspace namespace states, which can lead to contradictory records and hard-to-repair consistency issues. - Several medium-severity issues add operational risk (frontend loading dead-end in
frontend/src/components/integrations/integration-detail-panel.tsx, unpaginated catalog endpoints, stale cache after delete), so this does not look safe to merge yet. - Pay close attention to
tracecat/validation/service.py,tracecat/integrations/catalog/router.py, andtracecat/db/models.py- RBAC enforcement and catalog scoping invariants need to be fixed before merge.
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="alembic/versions/b7e1c9f2a3d4_consolidated_integrations_phase_1.py">
<violation number="1" location="alembic/versions/b7e1c9f2a3d4_consolidated_integrations_phase_1.py:78">
P2: Redundant unique index created on primary key column `id`; primary key already creates a unique index.</violation>
</file>
<file name="tracecat/integrations/catalog/seed.py">
<violation number="1" location="tracecat/integrations/catalog/seed.py:115">
P2: This seed does not perform the documented upsert behavior; existing namespaces are skipped instead of being updated, so metadata can go stale on reruns.</violation>
</file>
<file name="tracecat/validation/service.py">
<violation number="1" location="tracecat/validation/service.py:90">
P1: This change bypasses the `secret:read` scope check by using `search_secrets` (unguarded) instead of the scope-protected `get_secret_by_name`, creating an RBAC regression in secret validation.</violation>
</file>
<file name="frontend/src/components/integrations/connect-dialog.tsx">
<violation number="1" location="frontend/src/components/integrations/connect-dialog.tsx:489">
P2: Handle `createConnection` rejection in `submit` to avoid unhandled promise rejections on failed saves.</violation>
</file>
<file name="tracecat/integrations/catalog/router.py">
<violation number="1" location="tracecat/integrations/catalog/router.py:35">
P2: New catalog list/search endpoints are unpaginated; they should use the project’s cursor-based pagination pattern to avoid unbounded responses and API-shape drift.</violation>
<violation number="2" location="tracecat/integrations/catalog/router.py:35">
P1: Catalog integration routes are missing `require_scope` RBAC guards, allowing catalog connection reads/writes without the integration scope checks used elsewhere.</violation>
</file>
<file name="tracecat/db/models.py">
<violation number="1" location="tracecat/db/models.py:4680">
P1: The new integration catalog constraints don’t enforce platform/workspace scoping invariants. As written, duplicate global namespaces are possible (`workspace_id IS NULL`), and `source` can contradict `workspace_id`, which can cause cross-scope catalog data inconsistencies.</violation>
</file>
<file name="frontend/src/app/workspaces/[workspaceId]/integrations/page.tsx">
<violation number="1" location="frontend/src/app/workspaces/[workspaceId]/integrations/page.tsx:135">
P2: Validate the selected id or handle the detail error state; otherwise stale/manual `integrationId` values leave the dialog stuck on its skeleton.</violation>
<violation number="2" location="frontend/src/app/workspaces/[workspaceId]/integrations/page.tsx:153">
P2: Wait for the scope check to resolve before denying access; otherwise the page flashes "Access denied" while scopes are still loading.</violation>
<violation number="3" location="frontend/src/app/workspaces/[workspaceId]/integrations/page.tsx:255">
P1: Restore an `integration:update` gate around the detail panel (or make the panel itself scope-aware). As written, read-only users can open a dialog that exposes connect/configure/delete actions.</violation>
</file>
<file name="alembic/versions/c8f2d1e4a5b6_backfill_secret_namespaces_into_integrations.py">
<violation number="1" location="alembic/versions/c8f2d1e4a5b6_backfill_secret_namespaces_into_integrations.py:66">
P2: Do not use a silent no-op downgrade for a one-way data migration; raise an explicit `NotImplementedError` so rollback attempts don’t appear successful while leaving upgraded data in place.</violation>
</file>
<file name="packages/tracecat-admin/tracecat_admin/commands/integrations.py">
<violation number="1" location="packages/tracecat-admin/tracecat_admin/commands/integrations.py:17">
P3: This adds another duplicate `async_command` implementation; centralizing the wrapper in one shared helper would avoid repeated copies across command modules.</violation>
</file>
<file name="frontend/src/components/integrations/integration-detail-panel.tsx">
<violation number="1" location="frontend/src/components/integrations/integration-detail-panel.tsx:63">
P2: The dialog can get stuck in an infinite loading skeleton when integration detail fetch fails, because `!renderedIntegration` is treated as loading instead of error/empty state.</violation>
</file>
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
| registry_secret.name, environment=environment | ||
| ) | ||
| except TracecatNotFoundError as e: | ||
| defined_secrets = await secrets_service.search_secrets( |
There was a problem hiding this comment.
P1: This change bypasses the secret:read scope check by using search_secrets (unguarded) instead of the scope-protected get_secret_by_name, creating an RBAC regression in secret validation.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tracecat/validation/service.py, line 90:
<comment>This change bypasses the `secret:read` scope check by using `search_secrets` (unguarded) instead of the scope-protected `get_secret_by_name`, creating an RBAC regression in secret validation.</comment>
<file context>
@@ -86,19 +87,21 @@ async def validate_single_secret(
- registry_secret.name, environment=environment
- )
- except TracecatNotFoundError as e:
+ defined_secrets = await secrets_service.search_secrets(
+ SecretSearch(names={registry_secret.name}, environment=environment)
+ )
</file context>
| # ---------------------------------------------------------------------- | ||
|
|
||
|
|
||
| @catalog_router.get("/catalog", response_model=list[CatalogIntegrationRead]) |
There was a problem hiding this comment.
P1: Catalog integration routes are missing require_scope RBAC guards, allowing catalog connection reads/writes without the integration scope checks used elsewhere.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tracecat/integrations/catalog/router.py, line 35:
<comment>Catalog integration routes are missing `require_scope` RBAC guards, allowing catalog connection reads/writes without the integration scope checks used elsewhere.</comment>
<file context>
@@ -0,0 +1,163 @@
+# ----------------------------------------------------------------------
+
+
+@catalog_router.get("/catalog", response_model=list[CatalogIntegrationRead])
+async def list_catalog(
+ role: WorkspaceUserRouteRole,
</file context>
| __table_args__ = ( | ||
| UniqueConstraint( | ||
| "workspace_id", | ||
| "namespace", | ||
| name="uq_integration_workspace_namespace", | ||
| ), | ||
| Index("ix_integration_namespace", "namespace"), | ||
| ) |
There was a problem hiding this comment.
P1: The new integration catalog constraints don’t enforce platform/workspace scoping invariants. As written, duplicate global namespaces are possible (workspace_id IS NULL), and source can contradict workspace_id, which can cause cross-scope catalog data inconsistencies.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tracecat/db/models.py, line 4680:
<comment>The new integration catalog constraints don’t enforce platform/workspace scoping invariants. As written, duplicate global namespaces are possible (`workspace_id IS NULL`), and `source` can contradict `workspace_id`, which can cause cross-scope catalog data inconsistencies.</comment>
<file context>
@@ -4647,6 +4657,82 @@ class OAuthStateDB(TimestampMixin, Base):
+ """
+
+ __tablename__ = "integration"
+ __table_args__ = (
+ UniqueConstraint(
+ "workspace_id",
</file context>
| __table_args__ = ( | |
| UniqueConstraint( | |
| "workspace_id", | |
| "namespace", | |
| name="uq_integration_workspace_namespace", | |
| ), | |
| Index("ix_integration_namespace", "namespace"), | |
| ) | |
| __table_args__ = ( | |
| Index( | |
| "ix_integration_workspace_namespace_unique", | |
| "workspace_id", | |
| "namespace", | |
| unique=True, | |
| postgresql_where=text("workspace_id IS NOT NULL"), | |
| ), | |
| Index( | |
| "ix_integration_platform_namespace_unique", | |
| "namespace", | |
| unique=True, | |
| postgresql_where=text("workspace_id IS NULL"), | |
| ), | |
| CheckConstraint( | |
| "(source = 'platform' AND workspace_id IS NULL) OR (source = 'workspace' AND workspace_id IS NOT NULL)", | |
| name="ck_integration_source_workspace_scope", | |
| ), | |
| Index("ix_integration_namespace", "namespace"), | |
| ) |
| ) : null} | ||
| </div> | ||
|
|
||
| <IntegrationDetailPanel |
There was a problem hiding this comment.
P1: Restore an integration:update gate around the detail panel (or make the panel itself scope-aware). As written, read-only users can open a dialog that exposes connect/configure/delete actions.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/app/workspaces/[workspaceId]/integrations/page.tsx, line 255:
<comment>Restore an `integration:update` gate around the detail panel (or make the panel itself scope-aware). As written, read-only users can open a dialog that exposes connect/configure/delete actions.</comment>
<file context>
@@ -1,1044 +1,262 @@
- ) : null}
+ </div>
+
+ <IntegrationDetailPanel
+ workspaceId={workspaceId}
+ integrationId={selectedIntegrationId}
</file context>
| name="uq_integration_workspace_namespace", | ||
| ), | ||
| ) | ||
| op.create_index(op.f("ix_integration_id"), "integration", ["id"], unique=True) |
There was a problem hiding this comment.
P2: Redundant unique index created on primary key column id; primary key already creates a unique index.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At alembic/versions/b7e1c9f2a3d4_consolidated_integrations_phase_1.py, line 78:
<comment>Redundant unique index created on primary key column `id`; primary key already creates a unique index.</comment>
<file context>
@@ -0,0 +1,87 @@
+ name="uq_integration_workspace_namespace",
+ ),
+ )
+ op.create_index(op.f("ix_integration_id"), "integration", ["id"], unique=True)
+ op.create_index("ix_integration_namespace", "integration", ["namespace"])
+
</file context>
| # ---------------------------------------------------------------------- | ||
|
|
||
|
|
||
| @catalog_router.get("/catalog", response_model=list[CatalogIntegrationRead]) |
There was a problem hiding this comment.
P2: New catalog list/search endpoints are unpaginated; they should use the project’s cursor-based pagination pattern to avoid unbounded responses and API-shape drift.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tracecat/integrations/catalog/router.py, line 35:
<comment>New catalog list/search endpoints are unpaginated; they should use the project’s cursor-based pagination pattern to avoid unbounded responses and API-shape drift.</comment>
<file context>
@@ -0,0 +1,163 @@
+# ----------------------------------------------------------------------
+
+
+@catalog_router.get("/catalog", response_model=list[CatalogIntegrationRead])
+async def list_catalog(
+ role: WorkspaceUserRouteRole,
</file context>
| `/workspaces/${workspaceId}/integrations${query ? `?${query}` : ""}` | ||
| ) | ||
| }, [router, searchParams, workspaceId]) | ||
| const selectedIntegrationId = searchParams?.get(INTEGRATION_ID_PARAM) ?? null |
There was a problem hiding this comment.
P2: Validate the selected id or handle the detail error state; otherwise stale/manual integrationId values leave the dialog stuck on its skeleton.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/app/workspaces/[workspaceId]/integrations/page.tsx, line 135:
<comment>Validate the selected id or handle the detail error state; otherwise stale/manual `integrationId` values leave the dialog stuck on its skeleton.</comment>
<file context>
@@ -1,1044 +1,262 @@
- `/workspaces/${workspaceId}/integrations${query ? `?${query}` : ""}`
- )
- }, [router, searchParams, workspaceId])
+ const selectedIntegrationId = searchParams?.get(INTEGRATION_ID_PARAM) ?? null
- const setConnectParams = useCallback(
</file context>
| if (canRead !== true) { | ||
| return ( | ||
| <div> | ||
| Error: {providersError?.message || mcpIntegrationsError?.message} | ||
| <div className="flex h-full items-center justify-center p-6"> | ||
| <Alert className="max-w-md"> | ||
| <AlertTitle>Access denied</AlertTitle> | ||
| <AlertDescription> | ||
| You do not have permission to view integrations. | ||
| </AlertDescription> | ||
| </Alert> | ||
| </div> | ||
| ) | ||
| } |
There was a problem hiding this comment.
P2: Wait for the scope check to resolve before denying access; otherwise the page flashes "Access denied" while scopes are still loading.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/app/workspaces/[workspaceId]/integrations/page.tsx, line 153:
<comment>Wait for the scope check to resolve before denying access; otherwise the page flashes "Access denied" while scopes are still loading.</comment>
<file context>
@@ -1,1044 +1,262 @@
- return null
- }
- if (providersError || mcpIntegrationsError) {
+ if (canRead !== true) {
return (
- <div>
</file context>
| if (canRead !== true) { | |
| return ( | |
| <div> | |
| Error: {providersError?.message || mcpIntegrationsError?.message} | |
| <div className="flex h-full items-center justify-center p-6"> | |
| <Alert className="max-w-md"> | |
| <AlertTitle>Access denied</AlertTitle> | |
| <AlertDescription> | |
| You do not have permission to view integrations. | |
| </AlertDescription> | |
| </Alert> | |
| </div> | |
| ) | |
| } | |
| if (canRead === undefined) { | |
| return <CenteredSpinner /> | |
| } | |
| if (!canRead) { | |
| return ( | |
| <div className="flex h-full items-center justify-center p-6"> | |
| <Alert className="max-w-md"> | |
| <AlertTitle>Access denied</AlertTitle> | |
| <AlertDescription> | |
| You do not have permission to view integrations. | |
| </AlertDescription> | |
| </Alert> | |
| </div> | |
| ) | |
| } |
| app = typer.Typer(no_args_is_help=True) | ||
|
|
||
|
|
||
| def async_command[F: Callable[..., Any]](func: F) -> F: |
There was a problem hiding this comment.
P3: This adds another duplicate async_command implementation; centralizing the wrapper in one shared helper would avoid repeated copies across command modules.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/tracecat-admin/tracecat_admin/commands/integrations.py, line 17:
<comment>This adds another duplicate `async_command` implementation; centralizing the wrapper in one shared helper would avoid repeated copies across command modules.</comment>
<file context>
@@ -0,0 +1,54 @@
+app = typer.Typer(no_args_is_help=True)
+
+
+def async_command[F: Callable[..., Any]](func: F) -> F:
+ """Decorator to run async functions in typer commands."""
+
</file context>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a904707d03
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| @catalog_router.get("/catalog", response_model=list[CatalogIntegrationRead]) | ||
| async def list_catalog( | ||
| role: WorkspaceUserRouteRole, |
There was a problem hiding this comment.
Enforce integration scopes on catalog routes
The new catalog router handlers are mounted with WorkspaceUserRouteRole but never apply @require_scope(...), unlike the existing integrations routes that gate reads/writes by integration:* scopes. As written, any authenticated workspace user can call catalog list/detail and also create/delete connections directly, which bypasses the intended scope-based authorization model for integration management.
Useful? React with 👍 / 👎.
| sa.UniqueConstraint( | ||
| "workspace_id", | ||
| "namespace", | ||
| name="uq_integration_workspace_namespace", |
There was a problem hiding this comment.
Add null-safe uniqueness for platform integration namespaces
This uniqueness rule does not actually prevent duplicate platform rows because PostgreSQL treats NULL values as distinct in unique constraints. Since platform entries use workspace_id = NULL, multiple rows with the same namespace can be inserted, which can lead to duplicated catalog entries and unstable behavior in seed/list flows that assume one platform row per namespace.
Useful? React with 👍 / 👎.
b3fead8 to
cc93322
Compare
a904707 to
6df1781
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6df178166a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| stmt = select(Secret).where( | ||
| Secret.id == connection_id, | ||
| Secret.workspace_id == self.workspace_id, | ||
| ) |
There was a problem hiding this comment.
Restrict connection deletes to catalog-owned secrets
Because this lookup only checks Secret.id and workspace_id, DELETE /integrations/connections/{connection_id} will delete any workspace secret whose UUID is supplied, even if that secret is not a catalog connection for the selected integration; the normal secrets deletion path is protected by secret:delete, so this new endpoint can remove unrelated workflow credentials when a user can access it or learns a secret id. Require the secret to be associated with a visible catalog integration (or take and verify the integration_id) before deleting.
Useful? React with 👍 / 👎.
| result = await self.session.execute( | ||
| select(Secret.id) | ||
| .where( | ||
| Secret.workspace_id == self.workspace_id, | ||
| Secret.name == namespace, | ||
| Secret.environment == DEFAULT_SECRETS_ENVIRONMENT, | ||
| ) |
There was a problem hiding this comment.
Count non-default static credentials as connected
When a user creates a static connection for a non-default environment such as production, list_connection_summaries() will show that secret as an existing connection, but this status check still returns not_configured because it filters to only the default environment. The catalog cards and dialogs use auth_options.status to decide whether an integration is connected, so valid environment-specific credentials are shown as not connected and users are prompted to add credentials again; check for any workspace secret for the namespace instead of only default.
Useful? React with 👍 / 👎.
6df1781 to
446d70a
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 446d70a06d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| return counts | ||
|
|
||
|
|
||
| async def seed_platform_integrations(session: AsyncSession) -> int: |
There was a problem hiding this comment.
Seed catalog during normal startup or migration
In deployments that only run Alembic and start the API, the new /integrations/catalog page has no platform rows to read: seed_platform_integrations is only referenced by the new admin CLI (rg seed_platform_integrations finds no migration/startup caller), while the backfill migration only creates rows for secrets that already exist in a workspace. That leaves fresh workspaces without built-in OAuth/API-key integrations until an operator discovers and runs a separate command, so the catalog-backed page can appear empty after a normal upgrade.
Useful? React with 👍 / 👎.
| (item) => | ||
| item.id === connectParam && | ||
| (connectGrantType == null || item.grant_type === connectGrantType) | ||
| if (canRead !== true) { |
There was a problem hiding this comment.
Show a loading state while scopes resolve
useScopeCheck returns undefined while scopes are loading, but this check treats anything other than true as denied. On every authorized page load where the scopes query is still pending, users see the Access denied panel before their permissions resolve; the previous page explicitly handled the undefined state with a spinner. Gate only canRead === false here (or render CenteredSpinner for undefined) so authorized users do not get a transient denial state.
Useful? React with 👍 / 👎.
1a40421 to
22bf0a8
Compare
446d70a to
905cca5
Compare

Summary
SecretandOAuthIntegrationrecords as connection summariesTests
uv run ruff check .uv run basedpyright packages/tracecat-admin/tracecat_admin/cli.py packages/tracecat-admin/tracecat_admin/commands/integrations.py tests/unit/test_integrations_catalog_auth.py tests/unit/test_validation_service_secrets.py tracecat/api/app.py tracecat/db/models.py tracecat/integrations/catalog/__init__.py tracecat/integrations/catalog/router.py tracecat/integrations/catalog/schemas.py tracecat/integrations/catalog/seed.py tracecat/integrations/catalog/service.py tracecat/integrations/enums.py tracecat/secrets/service.py tracecat/validation/service.pyPG_PORT=5532 REDIS_PORT=6479 MINIO_PORT=9100 TEMPORAL_PORT=7333 uv run pytest tests/unit/test_integrations_catalog_auth.py tests/unit/test_validation_service_secrets.pypnpm -C frontend check