Skip to content

feat(integrations): add catalog-backed connections#2766

Open
daryllimyt wants to merge 1 commit into
mainfrom
feat/integrations-catalog
Open

feat(integrations): add catalog-backed connections#2766
daryllimyt wants to merge 1 commit into
mainfrom
feat/integrations-catalog

Conversation

@daryllimyt
Copy link
Copy Markdown
Contributor

@daryllimyt daryllimyt commented May 26, 2026

Summary

  • Add a catalog-backed integrations API that anchors connector rows separately from credential storage
  • Project static Secret and OAuthIntegration records as connection summaries
  • Add the frontend catalog/detail/connect flow and admin seeding command

Tests

  • 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.py
  • PG_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.py
  • pnpm -C frontend check

Copy link
Copy Markdown
Contributor Author

daryllimyt commented May 26, 2026

@daryllimyt daryllimyt added integrations Pre-built actions ui Improvements or additions to UI/UX api Improvements or additions to the backend API migration Database migration tests Changes to unit and integration tests labels May 26, 2026
Comment on lines +730 to +751
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()
Copy link
Copy Markdown

@zeropath-ai zeropath-ai Bot May 26, 2026

Choose a reason for hiding this comment

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

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.

View reasoning

Comment on lines +679 to +728
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)
Copy link
Copy Markdown

@zeropath-ai zeropath-ai Bot May 26, 2026

Choose a reason for hiding this comment

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

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.

View reasoning

@daryllimyt daryllimyt force-pushed the feat/integrations-ia-split branch from 9ac0901 to b3fead8 Compare May 26, 2026 20:10
@daryllimyt daryllimyt force-pushed the feat/integrations-catalog branch from 5ea6e90 to a904707 Compare May 26, 2026 20:10
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.

14 issues found

Confidence score: 1/5

  • There are concrete, high-confidence security regressions: tracecat/validation/service.py bypasses the secret:read scope path, and tracecat/integrations/catalog/router.py is missing require_scope guards 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, and tracecat/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(
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: 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])
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: 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>

Comment thread tracecat/db/models.py
Comment on lines +4680 to +4687
__table_args__ = (
UniqueConstraint(
"workspace_id",
"namespace",
name="uq_integration_workspace_namespace",
),
Index("ix_integration_namespace", "namespace"),
)
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: 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>
Suggested change
__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
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: 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)
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: 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])
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: 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>

Comment thread frontend/src/lib/hooks/integrations-catalog.ts
`/workspaces/${workspaceId}/integrations${query ? `?${query}` : ""}`
)
}, [router, searchParams, workspaceId])
const selectedIntegrationId = searchParams?.get(INTEGRATION_ID_PARAM) ?? null
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: 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>

Comment on lines +153 to 164
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>
)
}
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: 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>
Suggested change
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:
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.

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>

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +35 to +37
@catalog_router.get("/catalog", response_model=list[CatalogIntegrationRead])
async def list_catalog(
role: WorkspaceUserRouteRole,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +72 to +75
sa.UniqueConstraint(
"workspace_id",
"namespace",
name="uq_integration_workspace_namespace",
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@daryllimyt daryllimyt changed the base branch from feat/integrations-ia-split to graphite-base/2766 May 27, 2026 02:16
@daryllimyt daryllimyt force-pushed the graphite-base/2766 branch from b3fead8 to cc93322 Compare May 27, 2026 02:20
@daryllimyt daryllimyt force-pushed the feat/integrations-catalog branch from a904707 to 6df1781 Compare May 27, 2026 02:20
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +731 to +734
stmt = select(Secret).where(
Secret.id == connection_id,
Secret.workspace_id == self.workspace_id,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +390 to +396
result = await self.session.execute(
select(Secret.id)
.where(
Secret.workspace_id == self.workspace_id,
Secret.name == namespace,
Secret.environment == DEFAULT_SECRETS_ENVIRONMENT,
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@daryllimyt daryllimyt force-pushed the feat/integrations-catalog branch from 6df1781 to 446d70a Compare May 27, 2026 13:50
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@daryllimyt daryllimyt force-pushed the graphite-base/2766 branch from 1a40421 to 22bf0a8 Compare May 28, 2026 02:45
@daryllimyt daryllimyt force-pushed the feat/integrations-catalog branch from 446d70a to 905cca5 Compare May 28, 2026 02:45
@daryllimyt daryllimyt changed the base branch from graphite-base/2766 to main May 28, 2026 02:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Improvements or additions to the backend API integrations Pre-built actions migration Database migration tests Changes to unit and integration tests ui Improvements or additions to UI/UX

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant