Skip to content

feat(credentials): migrate to global resource + spec reconciliation#1570

Merged
mergify[bot] merged 6 commits into
mainfrom
spec/ambient-model-reconciliation
May 12, 2026
Merged

feat(credentials): migrate to global resource + spec reconciliation#1570
mergify[bot] merged 6 commits into
mainfrom
spec/ambient-model-reconciliation

Conversation

@markturansky
Copy link
Copy Markdown
Contributor

Summary

  • Credentials: project-scoped → global: Routes move from /api/ambient/v1/projects/{id}/credentials to /api/ambient/v1/credentials; project_id FK removed from model and DB via DROP COLUMN IF EXISTS migration (ID 202505120001)
  • NetworkPolicy fix: runner-networkpolicy.yaml now allows all ingress (- {} wildcard) so NodePort traffic from kube-proxy reaches the frontend pod; fixes Cypress E2E timeout on POST /api/projects
  • Spec reconciliation: specs/api/ambient-model.spec.md updated to match current implementation — Agent ERD fields, ScheduledSession fields, RBAC routes, coverage matrix, int32 types, status → Active
  • SDK re-generated: credential_api.go / credential.go updated to top-level paths; extension file fixed to use literal /credentials/ path (generator does not emit basePath() for top-level resources)
  • OpenAPI: vertex added to credentials provider enum; triggered_by_user_id removed from Session schema; display_name removed from Project schema

Test plan

  • cd components/ambient-sdk/go-sdk && go test ./... passes
  • cd components/ambient-cli && go test ./... passes
  • Cypress E2E POST /api/projects no longer times out (NodePort ingress fix)
  • GET /api/ambient/v1/credentials returns 200 (global route active)
  • GET /api/ambient/v1/projects/{id}/credentials returns 404 (old route gone)
  • DB migration 202505120001 drops project_id column on upgrade

🤖 Generated with Claude Code

markturansky and others added 6 commits May 12, 2026 17:16
Spec reconciliation run against components/ambient-api-server. Fixes stale
spec entries, removes a phantom OpenAPI field, and drops an unused DB column.

Spec changes (specs/api/ambient-model.spec.md):
- Status: Active (was "Proposed — Pending Consensus")
- Agent ERD: added 12 undocumented fields (parent_agent_id, owner_user_id,
  display_name, description, repo_url, workflow_id, llm_model,
  llm_temperature, llm_max_tokens, bot_account_name, resource_overrides,
  environment_variables); documented ignite_handler propagation to Session
- ScheduledSession ERD: added timeout, inactivity_timeout,
  stop_on_run_finished, runner_type
- Session field types: int → int32 for llm_max_tokens, timeout,
  sdk_restart_count
- Ignite Response example: triggered_by_user_id → created_by_user_id
- Credentials: document scoping gap (impl=project-scoped, spec=global);
  added vertex to provider enum table; CLI table updated to ✅ project-scoped
- RBAC endpoints: added GET/{id} and PATCH/{id} for role_bindings as ✅;
  added 4 scoped role_binding query endpoints as 🔲
- Coverage matrix: credentials, RBAC full CRUD, project update all ✅
- CLI reference: project update ✅; RBAC list/get/delete ✅
- CLI Known Gaps: removed now-implemented items; added credential bind gap
- Agent section prose: full field table with all 15 fields

OpenAPI changes (components/ambient-api-server/openapi/):
- openapi.credentials.yaml: added vertex to provider enum
- openapi.projects.yaml: removed display_name property
- openapi.sessions.yaml: removed triggered_by_user_id (phantom field —
  never existed in model.go)

Code changes (components/ambient-api-server/plugins/projects/):
- model.go: removed DisplayName from Project and ProjectPatchRequest
- grpc_handler.go: removed DisplayName from create/update handlers
- grpc_presenter.go: removed DisplayName from projectToProto()
- migration.go: added dropDisplayNameMigration() (ID 202505090001)
- plugin.go: registered new drop-column migration
- factory_test.go: removed DisplayName from test factory
- grpc_integration_test.go: removed display_name test assertions
- plugins/projectSettings/factory_test.go: removed stale DisplayName

Workflow update (workflows/sessions/ambient-model.workflow.md):
- Added 2026-05-09 run log with 5 lessons learned

Note: proto/ambient/v1/projects.proto still declares display_name (buf not
available); proto3 semantics make this safe — field transmits as zero value.
Follow-up: remove when buf is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Strip accidental 100644→100755 mode changes on all modified files
- Add TODO(proto-cleanup) comments to projects.proto on the three
  display_name fields (Project, CreateProjectRequest, UpdateProjectRequest)
  so the next engineer with buf available finds them immediately

Note on credential:token-reader guard: neither POST nor PATCH /role_bindings
currently guards against assigning this role via the user-facing API — both
handlers accept any RoleId without validation. This is a security gap in both
operations, not just PATCH. Tracking as a follow-up item; out of scope for
this reconciliation PR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The NetworkPolicy applied podSelector:{} (all pods) with a single
ingress rule allowing only runner pods. kindnet (kind v1.35 / kind
v0.27) enforces NetworkPolicy, so this blocked all same-namespace
pod-to-pod traffic — including frontend → backend-api requests that
Cypress exercises in E2E tests.

Add an explicit same-namespace allow rule so intra-namespace traffic
continues while still permitting runner pods from any namespace.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The NetworkPolicy with policyTypes:Ingress creates an implicit deny-all
for every pod it selects. NodePort traffic (kube-proxy → frontend pod)
does not originate from a pod, so it was blocked even after the
same-namespace podSelector rule was added.

Replace the same-namespace rule with a wildcard ingress rule (- {}) that
permits all traffic sources, including NodePort and external clients.
The runner-pod rule is preserved for forward compatibility with any
future default-deny policy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Credentials are now a global resource at /api/ambient/v1/credentials
instead of /api/ambient/v1/projects/{id}/credentials, matching the
spec target state.

Changes:
- openapi.credentials.yaml: global paths, project_id removed from schema
- plugins/credentials/model.go: drop ProjectID field
- plugins/credentials/migration.go: add drop-column migration (202505120001)
  that removes project_id; prior addProjectIDMigration kept for existing DBs
- plugins/credentials/handler.go: remove project path-var guards and
  project-filter injection from List
- plugins/credentials/presenter.go: remove ProjectId from responses;
  drop projectID param from ConvertCredential
- plugins/credentials/plugin.go: subrouter moved from /projects to
  /credentials; register new drop-column migration
- plugins/credentials/factory_test.go: remove ProjectID from factory
- SDK regenerated; credential_extensions.go updated to use literal path
  (generator does not emit basePath() for top-level resources)
- specs/api/ambient-model.spec.md: remove implementation-gap notes;
  mark all credential routes as implemented
- workflows/sessions/ambient-model.workflow.md: document migration and
  basePath() lesson for extension files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…egeneration

Spec SHA256 changed when vertex was added to the credentials provider enum;
regeneration updated the spec hash and timestamp in all generated files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@netlify
Copy link
Copy Markdown

netlify Bot commented May 12, 2026

Deploy Preview for cheerful-kitten-f556a0 canceled.

Name Link
🔨 Latest commit d73ae3c
🔍 Latest deploy log https://app.netlify.com/projects/cheerful-kitten-f556a0/deploys/6a0367279e015b000871ba94

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

Walkthrough

Credentials are migrated from project-scoped API routes (/projects/{id}/credentials/*) to global routes (/credentials/*). The project_id field is removed from the Credential model, handler validation, and all generated SDKs. A database migration drops the project_id column with rollback support.

Changes

Credential Scoping Migration

Layer / File(s) Summary
OpenAPI schema and route definitions
components/ambient-api-server/openapi/openapi.credentials.yaml
Routes moved from /projects/{id}/credentials to /credentials; Credential schema removes project_id from required fields; new schemas added (CredentialList, CredentialPatchRequest, CredentialTokenResponse); path parameters changed to use cred_id instead of nested project id.
Data model structure
components/ambient-api-server/plugins/credentials/model.go
Removed ProjectID field from Credential struct definition.
Database migration
components/ambient-api-server/plugins/credentials/migration.go
Added conditional ALTER TABLE IF EXISTS guards to existing project ID migration; introduced new dropProjectIDMigration() to drop project_id column with rollback that re-adds it.
Handler and routing layer
components/ambient-api-server/plugins/credentials/handler.go, components/ambient-api-server/plugins/credentials/plugin.go
Handler methods (Create, Get, List, Patch, Delete, GetToken) no longer validate or filter by project ID; routes wired under /credentials subrouter with cred_id path parameters; removed fmt and regexp imports and safeProjectIDPattern validation helper; migration registration updated to include dropProjectIDMigration().
Presenter and API response
components/ambient-api-server/plugins/credentials/presenter.go
ConvertCredential signature simplified to remove projectID parameter; PresentCredential no longer includes ProjectId in response payload.
Go SDK client
components/ambient-sdk/go-sdk/client/credential_api.go, components/ambient-sdk/go-sdk/client/credential_extensions.go, components/ambient-sdk/go-sdk/types/credential.go
CredentialAPI methods updated to call /credentials endpoints directly instead of using removed basePath() helper; GetToken hardcoded to /credentials/<id>/token; Credential type removes ProjectID field and CredentialBuilder removes ProjectID setter and validation.
Python SDK client
components/ambient-sdk/python-sdk/ambient_platform/_credential_api.py, components/ambient-sdk/python-sdk/ambient_platform/credential.py
CredentialAPI methods refactored to POST/GET/PATCH/DELETE /credentials directly; removed _base_path() helper and urllib.parse.quote import; Credential model removes project_id field, builder method, and required validation.
TypeScript SDK client
components/ambient-sdk/ts-sdk/src/credential_api.ts, components/ambient-sdk/ts-sdk/src/credential.ts
CredentialAPI removed basePath() method and hardcoded /credentials endpoint paths for all CRUD operations; Credential and builder types remove project_id field and projectId() setter; validation updated to require only name and provider.
SDK code generation metadata refresh
components/ambient-sdk/{go,python,ts}-sdk/**/*.go, components/ambient-sdk/{go,python,ts}-sdk/**/*.py, components/ambient-sdk/{go,python,ts}-sdk/**/*.ts (all other files)
All generated SDK files across Go, Python, and TypeScript regenerated with updated OpenAPI spec SHA256 and generation timestamps; no functional changes to other resources (Agent, InboxMessage, Project, Session, Role, etc.).
Factory test and documentation
components/ambient-api-server/plugins/credentials/factory_test.go, specs/api/ambient-model.spec.md, workflows/sessions/ambient-model.workflow.md
Test helper newCredential removes ProjectID field and sets Provider: "github"; API specification and workflow documentation updated to describe global credentials (/credentials) instead of project-scoped routes; credential binding design note updated to mark credential bind as planned but not yet implemented.

Sequence Diagram

sequenceDiagram
    participant Client as Client App
    participant API as API Server
    participant DB as Database

    Note over Client,DB: Before: Project-scoped Credentials
    Client->>API: GET /projects/{id}/credentials
    API->>API: Validate project exists
    API->>DB: SELECT * FROM credentials WHERE project_id = ?
    DB-->>API: credentials
    API-->>Client: Credentials list

    Note over Client,DB: After: Global Credentials
    Client->>API: GET /credentials
    API->>API: No project validation
    API->>DB: SELECT * FROM credentials
    DB-->>API: credentials
    API-->>Client: Credentials list

    Note over Client,DB: Token Retrieval
    Client->>API: GET /credentials/{cred_id}/token
    API->>DB: SELECT token FROM credentials WHERE id = ?
    DB-->>API: decrypted token
    API-->>Client: CredentialTokenResponse
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • ambient-code/platform#1514: Implements the RBAC role/binding design that enables global credential access control (referenced in this PR's spec updates).
  • ambient-code/platform#1289: Introduced project-scoped credential routing (project_id field, project-scoped handler logic, project-based migrations) that this PR directly reverses.
  • ambient-code/platform#1377: Related credential API surface changes including token retrieval endpoint updates and SDK type adjustments.

Suggested labels

queued

Suggested reviewers

  • jsell-rh
  • jeremyeder
  • Gkrumbach07
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main changes: credentials migration to global resource and spec reconciliation, which aligns with the primary focus of the PR.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, covering the credentials migration, NetworkPolicy fix, spec reconciliation, SDK regeneration, and OpenAPI changes with a test plan.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch spec/ambient-model-reconciliation

Comment @coderabbitai help to get the list of available commands and usage tips.

@mergify mergify Bot added the queued label May 12, 2026
@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented May 12, 2026

Merge Queue Status

  • Entered queue2026-05-12 17:54 UTC · Rule: default
  • Checks skipped · PR is already up-to-date
  • Merged2026-05-12 17:55 UTC · at d73ae3c806fcfcfbf623634aa64d331d1384e936 · squash

This pull request spent 56 seconds in the queue, including 9 seconds running CI.

Required conditions to merge

@mergify mergify Bot merged commit 8229beb into main May 12, 2026
64 of 67 checks passed
@mergify mergify Bot deleted the spec/ambient-model-reconciliation branch May 12, 2026 17:55
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/ambient-sdk/python-sdk/ambient_platform/agent.py (1)

80-80: ⚠️ Potential issue | 🟠 Major

Fix the Python SDK generator to use field(default_factory=list) for list-typed fields in *List dataclasses.

The generator template components/ambient-sdk/generator/templates/python/types.py.tmpl has items: list[{{.Resource.Name}}] = () which creates a type mismatch: the field is annotated as list[Type] but defaults to () (empty tuple). This violates mypy strict mode requirements specified in the coding guidelines.

This affects all 11 generated *List classes (AgentList, UserList, SessionList, etc.). The from_dict method correctly assigns a list, but the field annotation and default are incompatible.

Update line 66 in the template:

items: list[{{.Resource.Name}}] = field(default_factory=list)

And add the import:

from dataclasses import dataclass, field

Note: mypy is configured in pyproject.toml but not enforced in CI workflows, so this issue isn't caught during generation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/ambient-sdk/python-sdk/ambient_platform/agent.py` at line 80, The
list-typed fields in the generated "*List" dataclasses (e.g., AgentList,
UserList, SessionList) use a tuple default which mismatches the list annotation;
update the generator template types.py.tmpl to set items:
list[{{.Resource.Name}}] = field(default_factory=list) and add the dataclasses
import for field (i.e., ensure "from dataclasses import dataclass, field" is
present) so the generated classes have a correct list default; modify the
template line that currently has items: list[{{.Resource.Name}}] = ()
accordingly to fix all generated *List classes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@components/ambient-sdk/python-sdk/ambient_platform/_credential_api.py`:
- Line 26: The credential API currently interpolates raw resource IDs into
request paths (see uses in methods around resp = self._client._request("GET",
f"/credentials/{resource_id}") and similar calls at the other sites), which
breaks for IDs containing reserved characters; import quote from urllib.parse
and replace raw resource_id with quote(resource_id, safe="") in every path
construction in this module (e.g., the GET, PUT/POST, DELETE methods that build
"/credentials/{resource_id}" and any other "/credentials/{id}" usages) so the ID
is URL-path-escaped before calling self._client._request.

---

Outside diff comments:
In `@components/ambient-sdk/python-sdk/ambient_platform/agent.py`:
- Line 80: The list-typed fields in the generated "*List" dataclasses (e.g.,
AgentList, UserList, SessionList) use a tuple default which mismatches the list
annotation; update the generator template types.py.tmpl to set items:
list[{{.Resource.Name}}] = field(default_factory=list) and add the dataclasses
import for field (i.e., ensure "from dataclasses import dataclass, field" is
present) so the generated classes have a correct list default; modify the
template line that currently has items: list[{{.Resource.Name}}] = ()
accordingly to fix all generated *List classes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 05d9aceb-df1f-432a-852d-e3029cca561b

📥 Commits

Reviewing files that changed from the base of the PR and between a7fe697 and d73ae3c.

📒 Files selected for processing (87)
  • components/ambient-api-server/openapi/openapi.credentials.yaml
  • components/ambient-api-server/plugins/credentials/factory_test.go
  • components/ambient-api-server/plugins/credentials/handler.go
  • components/ambient-api-server/plugins/credentials/migration.go
  • components/ambient-api-server/plugins/credentials/model.go
  • components/ambient-api-server/plugins/credentials/plugin.go
  • components/ambient-api-server/plugins/credentials/presenter.go
  • components/ambient-sdk/go-sdk/client/agent_api.go
  • components/ambient-sdk/go-sdk/client/client.go
  • components/ambient-sdk/go-sdk/client/credential_api.go
  • components/ambient-sdk/go-sdk/client/credential_extensions.go
  • components/ambient-sdk/go-sdk/client/inbox_message_api.go
  • components/ambient-sdk/go-sdk/client/iterator.go
  • components/ambient-sdk/go-sdk/client/project_api.go
  • components/ambient-sdk/go-sdk/client/project_settings_api.go
  • components/ambient-sdk/go-sdk/client/role_api.go
  • components/ambient-sdk/go-sdk/client/role_binding_api.go
  • components/ambient-sdk/go-sdk/client/scheduled_session_api.go
  • components/ambient-sdk/go-sdk/client/session_api.go
  • components/ambient-sdk/go-sdk/client/session_message_api.go
  • components/ambient-sdk/go-sdk/client/user_api.go
  • components/ambient-sdk/go-sdk/types/agent.go
  • components/ambient-sdk/go-sdk/types/base.go
  • components/ambient-sdk/go-sdk/types/credential.go
  • components/ambient-sdk/go-sdk/types/inbox_message.go
  • components/ambient-sdk/go-sdk/types/list_options.go
  • components/ambient-sdk/go-sdk/types/project.go
  • components/ambient-sdk/go-sdk/types/project_settings.go
  • components/ambient-sdk/go-sdk/types/role.go
  • components/ambient-sdk/go-sdk/types/role_binding.go
  • components/ambient-sdk/go-sdk/types/scheduled_session.go
  • components/ambient-sdk/go-sdk/types/session.go
  • components/ambient-sdk/go-sdk/types/session_message.go
  • components/ambient-sdk/go-sdk/types/user.go
  • components/ambient-sdk/python-sdk/ambient_platform/__init__.py
  • components/ambient-sdk/python-sdk/ambient_platform/_agent_api.py
  • components/ambient-sdk/python-sdk/ambient_platform/_base.py
  • components/ambient-sdk/python-sdk/ambient_platform/_credential_api.py
  • components/ambient-sdk/python-sdk/ambient_platform/_inbox_message_api.py
  • components/ambient-sdk/python-sdk/ambient_platform/_iterator.py
  • components/ambient-sdk/python-sdk/ambient_platform/_project_api.py
  • components/ambient-sdk/python-sdk/ambient_platform/_project_settings_api.py
  • components/ambient-sdk/python-sdk/ambient_platform/_role_api.py
  • components/ambient-sdk/python-sdk/ambient_platform/_role_binding_api.py
  • components/ambient-sdk/python-sdk/ambient_platform/_scheduled_session_api.py
  • components/ambient-sdk/python-sdk/ambient_platform/_session_api.py
  • components/ambient-sdk/python-sdk/ambient_platform/_session_message_api.py
  • components/ambient-sdk/python-sdk/ambient_platform/_user_api.py
  • components/ambient-sdk/python-sdk/ambient_platform/agent.py
  • components/ambient-sdk/python-sdk/ambient_platform/client.py
  • components/ambient-sdk/python-sdk/ambient_platform/credential.py
  • components/ambient-sdk/python-sdk/ambient_platform/inbox_message.py
  • components/ambient-sdk/python-sdk/ambient_platform/project.py
  • components/ambient-sdk/python-sdk/ambient_platform/project_settings.py
  • components/ambient-sdk/python-sdk/ambient_platform/role.py
  • components/ambient-sdk/python-sdk/ambient_platform/role_binding.py
  • components/ambient-sdk/python-sdk/ambient_platform/scheduled_session.py
  • components/ambient-sdk/python-sdk/ambient_platform/session.py
  • components/ambient-sdk/python-sdk/ambient_platform/session_message.py
  • components/ambient-sdk/python-sdk/ambient_platform/user.py
  • components/ambient-sdk/ts-sdk/src/agent.ts
  • components/ambient-sdk/ts-sdk/src/agent_api.ts
  • components/ambient-sdk/ts-sdk/src/base.ts
  • components/ambient-sdk/ts-sdk/src/client.ts
  • components/ambient-sdk/ts-sdk/src/credential.ts
  • components/ambient-sdk/ts-sdk/src/credential_api.ts
  • components/ambient-sdk/ts-sdk/src/inbox_message.ts
  • components/ambient-sdk/ts-sdk/src/inbox_message_api.ts
  • components/ambient-sdk/ts-sdk/src/index.ts
  • components/ambient-sdk/ts-sdk/src/project.ts
  • components/ambient-sdk/ts-sdk/src/project_api.ts
  • components/ambient-sdk/ts-sdk/src/project_settings.ts
  • components/ambient-sdk/ts-sdk/src/project_settings_api.ts
  • components/ambient-sdk/ts-sdk/src/role.ts
  • components/ambient-sdk/ts-sdk/src/role_api.ts
  • components/ambient-sdk/ts-sdk/src/role_binding.ts
  • components/ambient-sdk/ts-sdk/src/role_binding_api.ts
  • components/ambient-sdk/ts-sdk/src/scheduled_session.ts
  • components/ambient-sdk/ts-sdk/src/scheduled_session_api.ts
  • components/ambient-sdk/ts-sdk/src/session.ts
  • components/ambient-sdk/ts-sdk/src/session_api.ts
  • components/ambient-sdk/ts-sdk/src/session_message.ts
  • components/ambient-sdk/ts-sdk/src/session_message_api.ts
  • components/ambient-sdk/ts-sdk/src/user.ts
  • components/ambient-sdk/ts-sdk/src/user_api.ts
  • specs/api/ambient-model.spec.md
  • workflows/sessions/ambient-model.workflow.md
💤 Files with no reviewable changes (1)
  • components/ambient-api-server/plugins/credentials/model.go


def get(self, resource_id: str) -> Credential:
resp = self._client._request("GET", f"{self._base_path()}/{resource_id}")
resp = self._client._request("GET", f"/credentials/{resource_id}")
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether path escaping is already performed centrally, and confirm current call sites.
fd -i '(_credential_api|client|_base)\.py$' components/ambient-sdk/python-sdk/ambient_platform
rg -n -C3 --iglob '*_credential_api.py' '"/credentials/\{resource_id\}"|quote\(' components/ambient-sdk/python-sdk/ambient_platform
rg -n -C6 --iglob '*client.py' 'def _request\(' components/ambient-sdk/python-sdk/ambient_platform

Repository: ambient-code/platform

Length of output: 3792


🏁 Script executed:

# Get full _request method implementation
sed -n '119,200p' components/ambient-sdk/python-sdk/ambient_platform/client.py

Repository: ambient-code/platform

Length of output: 2621


🏁 Script executed:

# Check if quote is used anywhere in the codebase currently
rg 'from urllib.parse import|quote\(' components/ambient-sdk/python-sdk/

Repository: ambient-code/platform

Length of output: 1271


🏁 Script executed:

# Check Go SDK credential API for comparison
fd -i 'credential.*\.go$' components/ambient-sdk/go-sdk | head -5

Repository: ambient-code/platform

Length of output: 293


🏁 Script executed:

# Check Go SDK credential API for encoding pattern
sed -n '1,50p' components/ambient-sdk/go-sdk/client/credential_api.go | head -40

Repository: ambient-code/platform

Length of output: 1253


🏁 Script executed:

# Check if credential_api.py has quote import
head -15 components/ambient-sdk/python-sdk/ambient_platform/_credential_api.py

Repository: ambient-code/platform

Length of output: 546


🏁 Script executed:

# Check all uses of resource_id in credential_api.py
grep -n 'resource_id' components/ambient-sdk/python-sdk/ambient_platform/_credential_api.py

Repository: ambient-code/platform

Length of output: 501


URL-encode resource_id before inserting it into credential API paths.

Lines 26, 35, and 39 interpolate raw IDs directly into the request path. The Go SDK equivalent (credential_api.go) uses url.PathEscape(id) for this exact reason. Other Python API files in the same codebase (_agent_api.py, _scheduled_session_api.py, etc.) already follow this pattern by importing and using quote(). IDs containing reserved characters like /, ?, #, or spaces will break request routing or target unintended endpoints.

Proposed fix
 from __future__ import annotations
 
 from typing import Any, Iterator, Optional, TYPE_CHECKING
+from urllib.parse import quote
@@
-        resp = self._client._request("GET", f"/credentials/{resource_id}")
+        resp = self._client._request("GET", f"/credentials/{quote(resource_id, safe='')}")
@@
-        resp = self._client._request("PATCH", f"/credentials/{resource_id}", json=data)
+        resp = self._client._request("PATCH", f"/credentials/{quote(resource_id, safe='')}", json=data)
@@
-        self._client._request("DELETE", f"/credentials/{resource_id}", expect_json=False)
+        self._client._request("DELETE", f"/credentials/{quote(resource_id, safe='')}", expect_json=False)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@components/ambient-sdk/python-sdk/ambient_platform/_credential_api.py` at
line 26, The credential API currently interpolates raw resource IDs into request
paths (see uses in methods around resp = self._client._request("GET",
f"/credentials/{resource_id}") and similar calls at the other sites), which
breaks for IDs containing reserved characters; import quote from urllib.parse
and replace raw resource_id with quote(resource_id, safe="") in every path
construction in this module (e.g., the GET, PUT/POST, DELETE methods that build
"/credentials/{resource_id}" and any other "/credentials/{id}" usages) so the ID
is URL-path-escaped before calling self._client._request.

@mergify mergify Bot removed the queued label May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant