mirror of
https://github.com/ComposioHQ/composio.git
synced 2026-09-22 11:46:35 +08:00
docs/changelog-update-dafe138
535 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
84684c6c8d |
fix(python): mirror the strict-mode incompatibility and branch rules
to_strict_json_schema reports the same constructs as the TypeScript implementation (tuple items, boolean subschemas, malformed properties, oneOf beside anyOf, conditional and dependency keywords), accepts a root typed ["object"], leaves enum/const that already include null alone, and omit_null_tool_arguments follows the composition branch matching the argument's shape. Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com> Claude-Session: https://claude.ai/code/session_01TDrxCHn2hg51HmxVstSUgs |
||
|
|
3ca07210d3 |
test(json-schema): drive strict-mode cases from a shared corpus
strict-cases.json (one byte-identical copy per language, next to object-cases.json) pins the exact strict schema or the reported incompatibilities for 44 shapes: optional widening at every depth, nullable type arrays, compositions, enum/const wrapping, annotation stripping, keyword-named and prototype-named properties, dynamic-key and free-form objects, allOf/prefixItems, $defs recursion, dangling and external refs, malformed required, non-object roots, plus null-omission argument pairs. The TypeScript suite pins the implementation and the Python suite checks parity against the same file. Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com> Claude-Session: https://claude.ai/code/session_01TDrxCHn2hg51HmxVstSUgs |
||
|
|
e4d24bc04b |
fix(python): treat a non-array required as absent under strict mode
to_strict_json_schema iterated a string-valued required character by character, so a malformed required kept same-named properties non-nullable while the TypeScript implementation treats it as absent and widens every property. Both SDKs now agree. Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com> Claude-Session: https://claude.ai/code/session_01TDrxCHn2hg51HmxVstSUgs |
||
|
|
e6fb9f9d32 |
feat(core): keep $defs recursion under strict mode
OpenAI structured outputs support local $ref pointers, including recursive definitions, so toStrictJsonSchema no longer inlines them: $defs and definitions are normalized where they are declared, an optional $ref property is widened with an anyOf null branch, and external or dangling $refs are reported as unsupported. omitNullToolArguments follows local $refs when deciding whether a null is accepted. The Vercel provider still inlines definitions before converting to Zod, which does not follow $ref. Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com> Claude-Session: https://claude.ai/code/session_01TDrxCHn2hg51HmxVstSUgs |
||
|
|
15e2b72f3d |
fix(python): emit strict and keep base provider config in OpenAIResponsesProvider
The strict flag now calls the base initializer (schema_config kwargs keep working), emits strict on the wrapped tool, and mirrors the TypeScript pipeline: optional parameters become required-nullable, unsupported schemas downgrade the tool to non-strict, and null arguments the tool schema rejects are dropped before execution. Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com> Claude-Session: https://claude.ai/code/session_01TDrxCHn2hg51HmxVstSUgs |
||
|
|
9e958948c5 |
fix(core): block sensitive upload paths hidden behind a symlinked directory (#4218)
# Description
Found by the scheduled security audit, while checking whether a test
failure on my other PR was pre-existing. It was — and the reason it
fails is a real gap in a shipped security control.
`isBlockedSensitiveFileUploadPath` (the GHSA-hp3h-89pf-5q58 denylist)
matched deny segments **only against the symlink-resolved path**. That
catches a benign name pointing at a secret — `~/innocent-name ->
~/nested/.aws/creds`, which the existing test covers — but misses the
inverse:
| Layout | Written path | Resolved path | Blocked before? |
|---|---|---|---|
| `~/innocent -> ~/.aws/creds` | no `.aws` | **`.aws`** | yes |
| `~/.claude -> /state/claude` | **`.claude`** | no `.claude` | **no** |
`~/.claude/settings.json` resolves to `/state/claude/settings.json`,
which has no `.claude` segment, so it sailed through — and `.claude` is
on the denylist precisely because it *"may contain API keys and project
context read by assistants"*.
This layout is not exotic. Dotfile managers (chezmoi, stow, yadm) and
containerised home directories produce it routinely — Composio's own
agent sandbox image has `/home/zen/.claude -> /state/claude`. For every
user in that shape the control was silently inactive, which is the worst
failure mode for a denylist: no error, no warning, upload proceeds.
The same hiding trick applies to the basename check (`~/.env ->
/state/plain-config`), so that path is fixed too.
## Why CI never caught this
`ts/packages/core/test/utils/sensitiveFileUploadPaths.test.ts` **already
asserts** the blocked behaviour:
```ts
expect(isBlockedSensitiveFileUploadPath(path.join(os.homedir(), '.claude', 'settings.json'))).toBe(true);
```
That assertion has been failing on `next` on any machine where
`~/.claude` is a symlink. It passes in CI only because `~/.claude` does
not exist on the runners: `existsSync` is false, no `realpath` runs, and
the written path keeps its `.claude` segment. The test is
environment-dependent, so green CI was never evidence the control
worked.
## The fix
The TypeScript `normalizePath` helper now returns both the written and
resolved segments, and the segment scan and basename check each consider
both. The Python guard now applies the same rule. Either path can carry
the denied name, so both SDKs inspect both forms.
# How did I test this PR
**The TypeScript fix is gated by tests — 3 fail without it, 13/13 pass
with it.**
Without the `src` change (test file only):
```
× blocks common credential directory segments
× blocks a sensitive directory that is itself a symlink to a plain path
× blocks a denied basename whose symlink target is named innocuously
Tests 3 failed | 10 passed (13)
```
With the fix:
```
Test Files 1 passed (1)
Tests 13 passed (13)
```
Note the first of those three is the **pre-existing** assertion quoted
above — this PR turns it green rather than adding it.
Three tests added, each building a real symlink in a temp dir:
- sensitive directory that is itself a symlink to a plain path (the
`~/.claude -> /state/claude` case), asserting both
`isBlockedSensitiveFileUploadPath` and that `assertSafeFileUploadPath`
throws
- denied basename whose symlink target is named innocuously (`.env ->
plain-config`)
- **negative case**: an ordinary file reached through a symlinked
directory (`docs/document.pdf`) is still allowed, so the fix does not
over-block
The Python parity change adds the same three cases. Before the Python
source change, the sensitive written directory and basename both
returned `False`; with the fix, all 11 focused Python tests pass.
**Full verification:**
| Command | Result |
|---|---|
| `vitest run` in `ts/packages/core` | **48 files, 1114 tests passed** |
| `pnpm typecheck` (workspace) | **14/14 tasks successful**, exit 0 |
| `oxlint` on both changed files | exit 0, clean |
| `prettier --check` on both changed files | "All matched files use
Prettier code style!" |
| `nox -s chk` in `python/` | Ruff and mypy passed |
| `nox -s tst` in `python/` | **1339 passed, 33 skipped**, exit 0 |
# Security
- No dependency changes, no new network calls, no new imports. The diff
is limited to the equivalent TypeScript and Python guards, their tests,
and the required `@composio/core` patch changeset.
- This **strengthens** an existing control and cannot weaken it: the
previous match set is a strict subset of the new one, so nothing that
was blocked before is allowed now. The added negative test pins that the
widening does not over-block ordinary files.
- **Grype** — `grype dir:ts/packages/core --only-fixed --fail-on medium`
→ reported below.
- **Socket** — could not run; `doppler secrets get SOCKET_API_TOKEN
--plain --project hermes --config dev_zen` returns empty in this cron
sandbox, so `socket ci` exits `Auth Error`. Reporting rather than
skipping silently.
- Unrelated pre-existing note: the repo's `pnpm audit --prod` comment
flags `extract-zip <=2.0.1` with `Patched versions >=2.0.2`, a version
that does not exist on npm. Details in #4217.
Origin: cron-48e51eab745f /
[zen-cron-44e260352d1a](https://zen.corp.composio.io/dashboard/#/chat/zen-cron-44e260352d1a)
Triggered by: saransh@composio.dev | Source: unknown
Session:
https://zen.corp.composio.io/dashboard/#/chat/zen-cron-44e260352d1a
|
||
|
|
43545ec889 | fix(python): block sensitive paths hidden by symlinks | ||
|
|
52f8861c95 |
docs(gemini): update to use gemini 3.7 and new models (#4244)
## Summary Updates docs and sample scripts to use latest Gemini models. ## Changes - Vertex & Gemini sample scripts updated - Plus accompanying markdown ## Type of change - [ ] Bug fix - [ ] New feature - [ ] Refactor/Chore - [x] Documentation - [ ] Breaking change ## How Has This Been Tested? Visual inspection ## Checklist - [x] I have read the Code of Conduct and this PR adheres to it - [x] I ran linters/tests locally and they passed - [x] I updated documentation as needed - [ ] I added tests or explain why not applicable - [ ] I added a changeset if this change affects published packages ## Additional context |
||
|
|
4ee3ff83bc | fix(python): preserve null nested file containers | ||
|
|
96c6260b8c | Merge branch 'next' into 37f-docs | ||
|
|
e3d2c093e3 | docs(gemini): update to use gemini 3.7 and new models | ||
|
|
cf25f25363 | fix(python): preserve nullable file upload arguments | ||
|
|
fe66cbeb77 |
fix(sdk): omit empty file-uploadable arguments from tool execution
Both SDKs forwarded "" for a file_uploadable parameter (e.g. Gmail attachment) verbatim to the backend, which rejected it with a Pydantic validation error. Python only dropped it inside the opt-in auto-upload walker; TypeScript never did, and with auto-upload on it tried to upload the empty string. Run a schema-aware, upload-free pass on the default execute path that omits empty file values, and reuse the same walker for staging when auto-upload is enabled. Closes #4233 |
||
|
|
ec24c54ffc |
fix(deps-dev): bump langchain-openai
Bumps the pip-version group with 1 update in the /python directory: [langchain-openai](https://github.com/langchain-ai/langchain). Updates `langchain-openai` from 1.4.3 to 1.6.0 - [Release notes](https://github.com/langchain-ai/langchain/releases) - [Commits](https://github.com/langchain-ai/langchain/compare/langchain-openai==1.4.3...langchain-openai==1.6.0) --- updated-dependencies: - dependency-name: langchain-openai dependency-version: 1.5.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: pip-version ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
0421f1fb52 |
chore(deps): stop Dependabot proposing ag2 majors for composio-autogen (#4236)
## Summary Closes #4197 as not-actionable and stops Dependabot from re-raising it. `ag2` 1.0 removed the top-level `autogen` package. The 1.x wheel ships only `ag2`: ``` $ python -c "import zipfile; print(sorted({n.split('/')[0] for n in zipfile.ZipFile('ag2-1.0.2-py3-none-any.whl').namelist()}))" ['ag2', 'ag2-1.0.2.dist-info'] ``` `composio_autogen/provider.py` imports `autogen`, `autogen.agentchat.register_function`, and `autogen.agentchat.conversable_agent.ConversableAgent` — none of which exist in 1.x. #4197 widened the requirement to `<2.0` and CI resolved `ag2==1.0.2`, which failed the fresh-install import guard on all three Python versions: ``` File ".../composio_autogen/provider.py", line 6, in <module> import autogen ModuleNotFoundError: No module named 'autogen' ``` This is the same failure mode as #3728 (pyautogen 0.10 shipping no `autogen`), one framework rename later. Adopting ag2 1.x is a provider rewrite against the new `ag2.tools` / middleware API, not a requirement widening — so it needs its own PR, not an automated bump. ## Changes - `.github/dependabot.yml`: ignore `version-update:semver-major` for `ag2` in the pip ecosystem, matching the existing convention for compatibility-boundary majors. Security updates are unaffected. - Record why the `<1.0` cap exists in both `pyproject.toml` and `setup.py`, so the next reader doesn't widen it by hand. ## Follow-up Migrating `composio-autogen` to ag2 1.x remains open and unscheduled. Remove the ignore entry when that lands. |
||
|
|
41be258076 |
fix(examples): fail tool_router_mcp when COMPOSIO_API_KEY is unset (#4222)
Spotted while fixing the provider-dependency failures in #4221. ## Problem `python/examples/tool_router/tool_router_mcp.py` guarded a missing credential like this: ```python api_key = os.environ.get("COMPOSIO_API_KEY") if not api_key: print("Error: COMPOSIO_API_KEY environment variable not set") print("Please set it using: export COMPOSIO_API_KEY='your_api_key'") return ``` `return` exits the coroutine normally, so `asyncio.run(main())` completes and the process exits **0**. The live-examples harness scores entries by exit code, so a run that did nothing at all was recorded **green** — the worst kind of failure for a canary, since it reports success while testing nothing. ## Fix Raise instead, using the `require_env` helper the sibling examples already use (`examples/tool_router/preload.py`): ```python def require_env(name: str) -> str: value = os.environ.get(name) if not value: raise RuntimeError(f"Set {name} before running this example.") return value ``` ## Verification Replaying the harness's own invocation with the variable unset: ``` $ unset COMPOSIO_API_KEY $ uv run --project python --with 'openai-agents>=0.19' \ --with ./python/providers/openai_agents --with ./python \ python python/examples/tool_router/tool_router_mcp.py before: exit=0 # silently "passes" after: exit=1 # RuntimeError: Set COMPOSIO_API_KEY before running this example. ``` `ruff check`, `ruff format --check`, and `nox -s chk_examples` all pass. ## Scope I checked whether other examples share the pattern. `examples/tool_router/authorize.py` prints the same message, but calls `exit(1)` at module level rather than returning from a coroutine, so it already fails correctly — left alone. This was the only instance. |
||
|
|
2f6a8a5ec9 |
fix(python): own the proxy_execute response shape (#4180)
> ### ⚠️ Breaking change > > `proxy_execute()` now returns a dict instead of the generated `SessionProxyExecuteResponse` model. Every caller since `py@0.11.4` that reads the result with attribute access breaks at runtime with `AttributeError`. > > ```python > # before > response.status > > # after > response["status"] > ``` > > `data`, `headers`, and `binary_data` follow the same rule. No version bump or changelog entry ships in this PR. That omission is deliberate, so the release call stays explicit. Details below. ## Summary Builds on @AseemPrasad's #4163, which spotted a real problem. Python's `proxy_execute()` returns the generated client's `SessionProxyExecuteResponse` directly, while TypeScript's `proxyExecute()` projects onto a curated shape. Returning the generated model leaks a regenerated artifact into a public SDK return type. This PR keeps that fix and resolves the review findings on top. #4163's commit is preserved with its original authorship. The commits on top carry the correction and the review fixes. ## What changed relative to #4163 | | #4163 | Here | |---|---|---| | Key casing | `binaryData`, `contentType`, `expiresAt` | `binary_data`, `content_type`, `expires_at` | | `status` type | declared `int`, returned `200.0` | declared `int`, returns `200` | | Test doubles | `SimpleNamespace` | real `SessionProxyExecuteResponse` / `BinaryData` | | `mypy` | fails `nox -s chk` | clean | | Docs | 3 snippets left broken | fixed | **Casing.** Python public APIs use snake_case and TypeScript public APIs use camelCase. The fields and their meanings match across SDKs, and the spelling follows each language. `session.delete()` already works this way (`session_id` in Python, `sessionId` in TypeScript), and so does `RemoteFile` (`expires_at` / `expiresAt`). **`status` and `size` are narrowed to `int`.** The generated model types both as `float` and pydantic coerces, so a response read straight off it renders `200.0` where TypeScript renders `200`. #4163 declared `int` but still returned `200.0`. That mismatch also failed `nox -s chk`: ``` composio/core/models/session_context.py:56: error: Incompatible types (expression has type "float", TypedDict item "status" has type "int") [typeddict-item] ``` **Tests use the real generated models again.** `SimpleNamespace` accepts any attribute name and any type, so it silently tolerates a client regeneration that renames or retypes a field. It was also what hid the `float` coercion, since `assert result == {"status": 200}` passes against `200.0`. The suite now asserts the narrowed types directly. This matters ahead of the `composio-client` 2.x migration, which types every response field as `Any` and removes type checking on this projection entirely. The tests become the only remaining check. **Simplification.** The projection folds into `proxy_execute_impl`, so both entry points are a single call rather than an impl-then-normalize pair. `response.binary_data` is read directly instead of through `getattr(..., None)`. The defensive default could never fire on a typed response, but it made mypy infer `Any` and stop checking the projection. **Docs.** Three Python snippets that read the result as attributes are fixed, and the response-shape table gets a per-language column. The follow-up commit also marks `headers` and `data` as nullable in that table, replaces the "returns the upstream response verbatim" claim with what the projection actually does, and documents that `expires_at` can be absent in TypeScript and `None` in Python. ## Breaking change The method has shipped since `py@0.11.4`. Both directions of the old access pattern were already inconsistent in the repo. `python/examples/custom_tools_agent_test.py:95` does `res["status"]`, which raises `TypeError` on `next` today and is fixed by this PR. The doc snippets did attribute access and are updated here. No changelog entry and no version bump are included. That is deliberate, so the release call stays explicit rather than implied by the merge. ## How Has This Been Tested? ```bash cd python mypy --config-file config/mypy.ini composio/ tests/ # clean ruff check --config config/ruff.toml composio/ tests/ # clean pytest tests/ # 1336 passed, 33 skipped ``` `ruff format` was run with the repo's pinned toolchain. ## Type of change - [x] Bug fix - [ ] New feature - [ ] Refactor/Chore - [ ] Documentation - [x] Breaking change ## Checklist - [x] I ran linters/tests locally and they passed - [x] I updated documentation as needed - [x] I added tests or explain why not applicable - [ ] I added a changeset if this change affects published packages. Not applicable: `AGENTS.md` reserves changesets for published TypeScript packages https://claude.ai/code/session_01GsD8zvAhrjFwk144oWkD9K --------- Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com> Co-authored-by: Kshitij Jhunjhunwala <113939507+KJ-11@users.noreply.github.com> |
||
|
|
e57661755b |
Merge branch 'next' of https://github.com/ComposioHQ/composio into asimcomposio
# Conflicts: # python/composio/core/provider/_openai_responses.py # ts/packages/providers/openai/src/OpenAIResponsesProvider.ts |
||
|
|
04817cb20d | fix(openai): recursive strict-mode schema normalization for structured outputs (+ Python parity) | ||
|
|
5608d89cbc |
docs(auth): point custom OAuth callback URL to v1
Consumer product and the backend-provided default redirect URI use the v1 callback (api/v1/auth-apps/add), but the developer docs instructed the v3.1 URL (api/v3.1/toolkits/auth/callback). Users copied the wrong URL into their OAuth app allow lists. Align all instructional docs + the Python example with v1. The migration guide (migration-guide/new-sdk.mdx) is left unchanged: it documents the v1->v3 history and flipping it would make it contradictory. Fixes UXE-261. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a14f9d537f |
fix(py): normalize toolkit-less tool metadata (#4178)
This PR: - closes #4152 - normalizes model- and mapping-shaped tool responses at the Python SDK boundary - uses `unknown` for direct/schema modifiers and `composio` for Tool Router execution, matching TypeScript - caches the untouched fetched tool before schema modifiers so execution keeps the original toolkit version metadata - reuses the cached tool during execution instead of retrieving the same schema twice - adds behavior regressions for schema, before/after execution, single-fetch execution, raw metadata preservation, and TypeScript parity - verifies the Python regressions against both the current Stainless client and `ComposioHQ/composio-client` at `605f508e` |
||
|
|
d544006a25 |
fix(sdk): pin the validated address when fetching URLs (SSRF DNS rebinding) (#4172)
Fixes #4151. ## The problem Both SDKs validated a URL by resolving its hostname, and then handed the *hostname* to the HTTP client, which resolved it again when it opened the socket. Two lookups, two answers: a short-TTL record under an attacker's control answers publicly for the check and with `169.254.169.254`, `127.0.0.1`, or RFC 1918 space for the connect. The guard passes and the connection lands inside the network — classic TOCTOU DNS rebinding, documented in both modules until now as a known residual. ```mermaid sequenceDiagram participant SDK participant DNS as Attacker DNS participant Meta as 169.254.169.254 Note over SDK,Meta: before SDK->>DNS: resolve evil.example.com (validate) DNS-->>SDK: 93.184.216.34 — passes the guard SDK->>DNS: resolve evil.example.com (connect) DNS-->>SDK: 169.254.169.254 SDK->>Meta: GET /latest/meta-data/… Meta-->>SDK: credentials ``` ## The fix Resolve once, validate every answer, then connect to the address that was validated. There is no second lookup left to rebind. - **Python** — `safe_get` / `safe_request` mount a transport adapter that swaps the connect target for the duration of the socket connect only. The `Host` header and TLS SNI keep the hostname, so certificate verification is unchanged; rewriting `conn._dns_host` for the whole connection would have sent `Host: <ip>` and offered the IP as SNI, failing against every real origin. Every fetch call site now goes through those two helpers, so no `requests.get` sits next to a bare check any more: - `_files.py::_fetch_file_from_url`, `_files.py::FileDownloadable.download` - `tool_router_session_files.py::_fetch_url_bytes` - `safe_request`, per redirect hop - **TypeScript** — `assertSafeFetchTarget` returns the validated address and `ssrfSafeFetch` hands `fetch` a dispatcher pinned to it, re-pinned per redirect hop. The dispatcher goes to the runtime's own `fetch`, so callers that stub `globalThis.fetch` keep working. The pinned `lookup` answers both shapes Node calls it with — the address *list* it uses for Happy Eyeballs, and the single `(address, family)` it uses when `autoSelectFamily` is off — since answering in the wrong shape is rejected as an invalid address. - A fail-closed peer assertion runs on the Python side before a byte is written to the socket — redundant while pinning works, and a tripwire if a urllib3 upgrade ever breaks it. - `workerd` is unchanged: it already fails closed for user-supplied URLs. Redirect *validation* already existed in both SDKs (`safe_request` / `ssrfSafeFetch`); what was missing was re-pinning each hop. ## Tests The existing suites could not express this bug: they mock both the resolver and the HTTP client, so check and use are the same mock. The new tests use real sockets. - `python/tests/test_url_safety_pinning.py` — two loopback servers and a resolver that answers the first lookup with one endpoint and every later one with another, which is what a short-TTL rebinding record does. Asserts the rebound endpoint receives **zero** connections, and that `Host` still carries the hostname. Both tests fail on `next` and pass here. - `ts/packages/core/test/utils/pinnedDispatcher.node.test.ts` — a real server plus a hostname under `.invalid`, which RFC 2606 guarantees never resolves. A request that arrives proves the connect used the pinned address and never consulted DNS. The third case shows the contrast: unpinned, the same fetch cannot resolve at all. - `ssrfGuard.test.ts` gains assertions that each hop is pinned to that hop's own validated address. - `pinnedDispatcher.node.test.ts` also pins with `setDefaultAutoSelectFamily(false)`, which is the branch Node takes for the single-address callback. ## Notes - Supersedes #4157, which diagnosed this correctly. Its post-response peer check turned out not to hold: with an HTTP/1.0 or `Connection: close` server, urllib3 detaches the socket (`conn.sock is None`) while `r.content` still returns the full body, so the check fails open exactly where exfiltration succeeds. That is why the assertion here runs at connect time instead. - The Python package now declares `urllib3>=2` directly. `url_safety` imports it for `NameResolutionError`, which only exists from 2.0, and the pinning adapter reaches into 2.x connection internals; `requests` alone allows 1.x, where `import composio` would have failed outright. - `@composio/core` gains an `undici` dependency, pinned to `^7`: undici 8 dispatchers are rejected by the `fetch` in every Node version this package supports (22/24/25, verified). The real-socket test runs on the full CI matrix, so a future incompatibility fails loudly instead of silently un-pinning. - `undici` is imported on first pinned request rather than at module load: importing it installs a process-wide global dispatcher, which would have handed the host application's own unrelated `fetch` calls this package's undici merely because it imported `@composio/core`. - Residuals, now documented in the modules: - Requests routed through an environment proxy keep the pre-flight check only. The proxy resolves the hostname itself and the SDK cannot see or pin that resolution. - A process that does perform a pinned fetch still ends up on this package's `Agent` if nothing had claimed the global dispatcher slot yet. undici defines that slot non-configurable, so it cannot be handed back — assigning `undefined` leaves the runtime's own `fetch` asserting on a missing dispatcher. |
||
|
|
e3534e7ea6 |
chore(sdk): prepare Python 0.20.0 and TypeScript 0.17.0 releases (#4170)
This PR: - bumps `composio` and all 13 provider distributions from `0.19.0` to `0.20.0`, keeping `python/composio/__version__.py` and `uv.lock` aligned with package metadata - adds `docs/content/changelog/08-19-26-sdk-releases.mdx`, the combined customer-facing changelog for both SDKs, documenting the session-aware provider tool-call helpers from #4098 (Python `composio` 0.20.0, TypeScript `@composio/core`/`@composio/slim` 0.17.0, `@composio/anthropic` 0.11.0, `@composio/openai` 0.12.0) and the API-response URL validation and Python file-handling fixes shipped since the last train - documents `@composio/core` `0.17.0` in that entry so the generated Changesets release PR (#4161) passes the release-workflow guard ## Release sequence 1. Merge this PR. 2. Merge #4161 (the generated "Release: update version" PR) once it goes green. Merging publishes the TypeScript packages to npm. 3. Tag the resulting `next` commit `py@0.20.0` to publish the Python packages to PyPI. 4. Follow-up PR: bump `docs/package.json` pins to `@composio/core@^0.17.0`, `@composio/anthropic@^0.11.0`, `@composio/openai@^0.12.0` and remove the now-stale `@errors: 2345` Twoslash TODO markers in the provider docs, once the npm publish lands. ## Verification - Verified the release-workflow guard reads the new changelog rows for Python `0.20.0` and TypeScript `@composio/core` `0.17.0` - All 13 Python `pyproject.toml`/`setup.py` pairs bumped consistently; `uv.lock` regenerated |
||
|
|
760f8d0367 |
fix(sdk): route provider tool calls through sessions (#4098)
## Problem Provider tool-call helpers always used the globally injected direct `Tools.execute` function. When a model received tools from `session.tools()`, calling `handleToolCalls` or `handle_tool_calls` therefore discarded the Tool Router session context and caused session meta-tools such as `COMPOSIO_SEARCH_TOOLS` to fail. Calling `session.execute()` manually preserved the session, but bypassed provider behavior such as Anthropic input normalization and schema-alias restoration. ## Root fix - Add an explicit execution target to the non-agentic provider helpers: - TypeScript: `handleToolCalls(session, response)` and `executeToolCall(session, call)` - Python: `handle_tool_calls(response=response, session=session)` and `execute_tool_call(tool_call=call, session=session)` - Route normalized provider arguments through the supplied Tool Router session. - Map session responses back to each helper's existing result shape. - Keep provider-specific normalization before execution, including Anthropic schema-alias restoration. - Reject direct-only options and modifiers when the selected target is a session, including plain JavaScript calls that bypass the TypeScript overloads. - Update OpenAI and Anthropic examples to use the session-aware helpers. - Harden the docs policy test so setup and execution split across fences in one sample are still detected. ## Docs review follow-ups - Reword the concepts-page prohibition so it forbids user-ID-bound helper calls, not the helpers themselves, matching the provider pages in this PR. - Add minimum-version callouts to the OpenAI and Anthropic provider pages (Python `composio` newer than 0.19.0; TypeScript `@composio/core` ≥ 0.17.0 with `@composio/openai` ≥ 0.12.0 / `@composio/anthropic` ≥ 0.11.0), pointing older versions at `session.execute()`. - Bump `docs/package.json` to `@composio/core` `^0.15.0` and `@composio/openai` `^0.11.0` (the published majors at the time of the bump; `@composio/core` 0.16.0 and `composio` 0.19.0 have since released from `next` without this PR, so its changeset will publish core 0.17.0 and the next Python minor) and annotate each `@errors: 2345` Twoslash marker with a TODO naming the minor version that retires it; since this changeset releases minors, all three pins need a manual range bump to retire the markers. This version of twoslash only throws on *unlisted* errors, so a stale marker cannot break the build — it would only mask future TS2345s, which the TODOs now track. - Update `SESSION_GUARDRAILS` (the block appended to `.md` responses for agents): add a session-execution bullet (scoped to the OpenAI and Anthropic helpers, with `session.execute()` for every other provider) and qualify the direct-execution list with "with a user ID". The session-execution static test now scans the guardrail blocks like the execute-version test already did. - Tighten the docs detector: the Python branch is bounded to the helper call's argument list (tolerating one level of nested calls) instead of running past the closing paren, and the TypeScript branch catches whole user-ID identifiers (`userId`, `user_id`, `uid`) without flagging session variables like `userSession` — each edge has a regression test. - Note on the Google provider page that its `executeToolCall` is not session-aware yet. ## Compatibility and release Existing user-ID calls remain unchanged and continue to use direct tool execution. The new session call forms are additive. The changeset applies minor releases to `@composio/core`, `@composio/openai`, and `@composio/anthropic` — the new session overloads are a type-level break for provider subclasses, so patch was too small. The configured fixed group also includes `@composio/slim`. The docs site intentionally checks examples against currently published SDK declarations. The three new TypeScript calls therefore carry exact Twoslash `TS2345` release-skew annotations; remove them (per the inline TODOs) once `docs/package.json` picks up `@composio/core` ≥ 0.17.0, `@composio/openai` ≥ 0.12.0, and `@composio/anthropic` ≥ 0.11.0. ## Verification - `@composio/core`: 1,061 tests passed; typecheck passed - `@composio/openai`: 34 tests passed; typecheck passed - `@composio/anthropic`: 53 tests passed; typecheck passed - Python provider and aliasing suites: 40 passed, 4 skipped - Focused Python mypy and Ruff checks passed - Docs static suite: 208 tests passed (including the new guardrail-scan and detector cases) - Docs production build passed with the bumped `@composio/core` 0.15.0 / `@composio/openai` 0.11.0, including Twoslash, TypeScript, and all generated pages - Docs lint passed; lint reports only existing warnings - Changeset status reports the expected minor packages --------- Co-authored-by: Soumya Medapati <soumyamedapati@soumyas-air.local.meter> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: jkomyno <alberto@composio.dev> |
||
|
|
b1c6671b20 |
fix(py): send Content-Type on S3 PUTs and parse Content-Length (#4168)
This PR: - fixes #4153 - supersedes https://github.com/ComposioHQ/composio/pull/4154 (fork PR authored against a pre-#4146 tree; its tests patch `requests.put`, which `upload()` no longer calls after #4146, so they fail on merge) - adds `parse_content_length()` in `composio.utils.url_safety`, shared by both URL fetch helpers (`_files.py::_fetch_file_from_url` and `tool_router_session_files.py::_fetch_url_bytes`), so a malformed/negative remote `Content-Length` degrades to unknown size under the streamed byte count instead of raising a raw `ValueError` - converges the file and bytes upload paths onto `_upload_to_presigned_url()`: one PUT that sends the `Content-Type` the presign request was signed with and raises `ErrorUploadingFile` carrying the HTTP status (a 403 no longer collapses into a path-only error) - single-sources the presign wire shape via `_request_presigned_upload()`; `from_path()` forwards the exact mimetype it minted, so signed and sent content types cannot drift - mirrors the TypeScript SDK: `uploadFileToS3` funnels path/URL/File inputs through one uploader that always sends `Content-Type` and throws on non-2xx, and `readResponseBodyWithLimit` trusts `Content-Length` only as a hint - adds tests against the `safe_request` seam (`test_file_upload_robustness.py`, plus one malformed-header case for `RemoteFile.buffer()`) ## Context Both defects in #4153 were symptoms of two parallel upload paths drifting: the bytes path sent `Content-Type` and raised with the status, the file path did neither. Patching the symptom in place (as #4154 proposed) would have left three presigned PUT sites and two error contracts in the tree; this PR deletes the drift vector instead. Full suite green (1320 passed), `make chk` and `make snt` clean; Python-only change, so no changeset per `AGENTS.md`. --------- Co-authored-by: Mustaqeem66 <265153888+Mustaqeem66@users.noreply.github.com> |
||
|
|
1d0d54140e |
docs(openai): replace assistants examples with responses
Refactor the OpenAI assistant demo to use the OpenAIResponsesProvider and streamline task execution with the Responses API. |
||
|
|
03429c7404 | fix(python): create the cache directory on first use instead of at import time (#4162) | ||
|
|
6ba9179b48 |
fix(files): validate URLs from API responses before fetching them (#4146)
This PR: - builds on top of https://github.com/ComposioHQ/composio/pull/4144 - routes every fetch whose URL comes from an API response through the SSRF guards that already existed — ten sinks, five per SDK: the tool-execution download, both S3 presigned uploads, `RemoteFile.buffer()`/`blob()`, and the Tool Router session file upload - adds `safe_request()` (Python), which follows redirects itself and re-validates each hop, so a validated URL cannot 302 into private space and an S3 307 region redirect still works - makes `RemoteFile.buffer()` (Python) share `_fetch_url_bytes` with the user-supplied-URL path instead of duplicating it — it previously read `response.content` with no size cap, no redirect control, and no target validation - adds `ssrfSafeFetchWhereSupported` (TypeScript): the full guard on Node, a plain `fetch` on workerd, so Tool Router session file transfers keep working in edge runtimes rather than failing closed - documents DNS rebinding as a known residual in both guards - adds tests that assert the guard *runs* — blocked URL, sink never called, nothing written — rather than that a transfer succeeds ## Context `python/AGENTS.md` states the trust boundary: every field of an API response is untrusted input, because the backend may be compromised or the connection MITM'd. Both SDKs enforced that for URLs a *user* passes in (`composio.utils.url_safety`, `ssrfGuard.node.ts`) and left the URLs an API *response* supplies unguarded — backwards relative to the stated model. A response naming an internal address turned the SDK into a request proxy for it, and the fetched bytes were written to disk or returned to the caller, typically into an LLM context. `RemoteFile.buffer()` was the weakest of the ten: four lines above it, `_fetch_from_url` validated its target, refused redirects, and streamed against a 100 MiB cap; `buffer()` did none of the three. The only difference between them was which side of the trust boundary the URL arrived from. Three decisions worth review: - **The guard is unconditional.** Presigned URLs are public, so nothing legitimate should resolve to private space. There is no escape hatch, and no new configuration surface. - **The tool-execution download stays uncapped.** It streams straight to disk, so `_MAX_RESPONSE_SIZE` — a memory-exhaustion bound — does not apply, and tool attachments legitimately exceed it. `RemoteFile.buffer()` *is* capped, because it buffers in memory. - **Uploads follow redirects with re-validation rather than refusing them**, since S3 can answer a PUT with a 307 region redirect. Downloads keep `allow_redirects=False`, matching the existing fetch paths. Python now matches the TypeScript guard's per-hop re-validation, which was the better of the two implementations. Verified locally: `make chk` and `make tst` clean on the Python side; `pnpm -C packages/core test` 1095 passed, typecheck and lint clean. |
||
|
|
f67d565743 |
refactor(python): consolidate path construction from untrusted input (#4144)
This PR: - centralizes filesystem path construction for API-provided slugs and filenames in `composio.utils.safe_path` - rejects traversal, Windows-invalid names, invalid Unicode, and overlong encoded filenames before creating directories or writing files - normalizes trusted roots consistently and routes both Python download paths through the shared helpers - adds a fail-closed AST guard for new dynamic path construction, including direct `Path(...)` calls - isolates provider initialization from the real home directory and makes the home-write guard report changes without deleting them - removes the obsolete download filename wrapper ## Verification - `pytest -q`: 1,248 passed, 47 skipped - repository-configured Ruff checks and formatting passed for every changed Python file - targeted mypy checks passed for the changed helpers and tests |
||
|
|
7e560fd072 |
chore(deps-dev): bump langchain-openai from 1.4.1 to 1.4.3 in /python in the pip-version group across 1 directory (#4132)
Bumps the pip-version group with 1 update in the /python directory: [langchain-openai](https://github.com/langchain-ai/langchain). Updates `langchain-openai` from 1.4.1 to 1.4.3 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/langchain-ai/langchain/releases">langchain-openai's releases</a>.</em></p> <blockquote> <h2>langchain-openai==1.4.3</h2> <p>Changes since langchain-openai==1.4.2</p> <p>release(openai): 1.4.3 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39485">#39485</a>) fix(openai): filter invalid tool calls from content (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39366">#39366</a>) chore(openai): update guidance for responses API for OpenAI-compatible providers (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39327">#39327</a>) chore(openai): update docstring for <code>include_response_headers</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39326">#39326</a>)</p> <h2>langchain-openai==1.4.2</h2> <p>Changes since langchain-openai==1.4.1</p> <p>release(openai): 1.4.2 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39322">#39322</a>) fix(openai): handle <code>ContextWindowExceededError</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39300">#39300</a>) chore: bump the minor-and-patch group across 3 directories with 7 updates (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39187">#39187</a>) fix(openai): filter langchain-generated content block IDs (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39209">#39209</a>) fix(openai): preserve Responses <code>text</code> options (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39204">#39204</a>) fix(openai): redact MCP <code>authorization</code> (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39155">#39155</a>) chore(model-profiles): refresh model profile data (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39050">#39050</a>)</p> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/langchain-ai/langchain/commit/56daacc8dff4103f430b121711f601ae503bddf9"><code>56daacc</code></a> release(openai): 1.4.3 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39485">#39485</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/24e8d2b5960ce52985d99332d29ab503fc4be5f9"><code>24e8d2b</code></a> chore(model-profiles): refresh model profile data (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39344">#39344</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/9b4ef2ab34bdb6f2313bef3f8ee25202eb089ef2"><code>9b4ef2a</code></a> fix(openai): filter invalid tool calls from content (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39366">#39366</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/9f738f5297d14f46f0341f6c2d773cbf12962e82"><code>9f738f5</code></a> fix(core): compat with pydantic 2.14 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39328">#39328</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/d048fbe170573b6e7056b5ef5f78d8451e54abaf"><code>d048fbe</code></a> chore: bump h2 from 4.3.0 to 4.4.1 in /libs/langchain (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39325">#39325</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/a0a434c18550a21ed9137ddc5f0533cb74f8416b"><code>a0a434c</code></a> chore: bump h2 from 4.3.0 to 4.4.1 in /libs/langchain_v1 (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39324">#39324</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/c318abed446e7d1a0e872a6967b9b17bc6f4761c"><code>c318abe</code></a> fix(text-splitters): raise TypeError for non-dict, non-convertible input to R...</li> <li><a href="https://github.com/langchain-ai/langchain/commit/1499aa9900987c6050670eb1cbd47bc4db0a6d18"><code>1499aa9</code></a> fix(core): stop StructuredPrompt from mutating caller kwargs (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39174">#39174</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/0424e03dc71cc487dd11e5fa71d083533cb26816"><code>0424e03</code></a> fix(core): preserve flat tool args schema for <code>RootModel</code> runnables (<a href="https://redirect.github.com/langchain-ai/langchain/issues/39307">#39307</a>)</li> <li><a href="https://github.com/langchain-ai/langchain/commit/bc16168d710d8b594d0bc622c70361876bd9cbda"><code>bc16168</code></a> fix(langchain): preserve history on <code>SummarizationMiddleware</code> summary failure...</li> <li>Additional commits viewable in <a href="https://github.com/langchain-ai/langchain/compare/langchain-openai==1.4.1...langchain-openai==1.4.3">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
8086ef02cb |
fix(examples): align Python and TypeScript examples with current backend (#4107)
## Summary - Repairs runnable TypeScript and Python examples for current backend requirements, including authenticated MCP endpoints, current transports, valid tool identifiers, provider limits, and resource uniqueness. - Replaces placeholder resource IDs with explicit `COMPOSIO_EXAMPLES_*` configuration and makes failed examples exit loudly. - Adds `scripts/examples-provision.mjs` as an idempotent provisioning check for a disposable examples project. ## Scope - This PR no longer changes the Python SDK runtime or generated-client dependency. - Python remains pinned to the published `composio-client==1.43.0` in `pyproject.toml`, `setup.py`, and `uv.lock`. - The owned 2.x client integration is deferred until that client completes its release guarantees and is explicitly published. - The scheduled live workflow and manifest remain deferred until their runner is tracked. ## Verification - `make chk` - `make tst` (`927 passed, 33 skipped`) - Hosted checks rerun against commit `12691aca7`. |
||
|
|
26ec42a38c |
chore(py): prepare 0.19.0 release (#4114)
This PR: - builds on top of https://github.com/ComposioHQ/composio/pull/4094 - bumps `composio` and all 12 provider distributions from `0.18.2` to `0.19.0` - keeps `python/composio/__version__.py` and `uv.lock` aligned with package metadata - documents recursive provider argument-presence preservation in the existing cross-SDK changelog - passes the release-workflow guard, Ruff, mypy, provider type inference, and 1,167 Python tests - builds and validates 26 wheel/sdist artifacts with Twine - must be retargeted to `next` after #4094 merges |
||
|
|
64fbc4ee03 | fix(py): preserve nested provider argument presence | ||
|
|
82e25a603e | Merge branch 'fix/cross-sdk-json-schema-object-conversion' into fix/python-provider-argument-serialization | ||
|
|
1af694baee | Merge branch 'next' into fix/cross-sdk-json-schema-object-conversion | ||
|
|
cec99e7a32 | test(py): run provider serialization coverage in CI | ||
|
|
5bb1e20518 |
fix(py): preserve serialized provider arguments
Co-authored-by: Aditya Chawla <aditya17.mitmpl2023@learner.manipal.edu> |
||
|
|
191d919eef | fix(python): harden dynamic schema validation | ||
|
|
c337e5696e |
chore(sdk): prepare Python 0.18.2 and TypeScript 0.15.0 releases (#4095)
Prepares the next SDK train: Python `composio` `0.18.2` and the TypeScript packages that the pending Changesets will publish (`@composio/core` and `@composio/slim` `0.15.0`, `@composio/openai` `0.11.0`, `@composio/experimental` and `@composio/json-schema-to-zod` `0.2.2`). No CLI release is part of this train. ## What this PR does - bumps Python core and all 13 provider packages to `0.18.2`, including `python/composio/__version__.py` and the root plus per-provider `uv.lock` files - adds `docs/content/changelog/08-07-26-sdk-releases.mdx`, the combined customer-facing changelog for both SDKs - documents `@composio/core` `0.15.0` in that entry so the generated release PR passes the release-workflow guard - leaves the pending Changesets untouched; the generated release PR remains responsible for npm versioning and publication ## Release sequence 1. Merge this PR. 2. Let Changesets regenerate the "Release: update version" PR, wait for it to go green, then merge it. Merging publishes the TypeScript packages. 3. Tag the resulting `next` commit `py@0.18.2` to publish the Python packages to PyPI. ## Notes - The root `uv.lock` diff carries ~28 lines of marker-annotation churn beyond the version bumps. That is the repo-pinned `uv` `0.8.19` normalising markers the committed lockfile had recorded differently; no dependency versions change. - `@composio/mastra` and the TypeScript providers are unchanged: their declared peer ranges still accept `@composio/core` `0.15.0`, so Changesets does not bump them. ## Verification - `pnpm test:release-workflow` — passes (Python package, runtime, and provider versions agree; both SDK versions documented in the changelog) - `uv run pytest tests/` in `python/` — 925 passed, 35 skipped - `uv run --package composio python -c "import composio"` — reports `0.18.2` - `bun test tests/static/` in `docs/` — 195 pass, 1 pre-existing failure (`getLLMText — version pointer`, also fails on a clean tree) - `bun run types:check` in `docs/` — MDX generation and route typegen succeed; the final `tsc` step could not run locally because `typescript-7` is absent from `docs/node_modules` |
||
|
|
3fef432dc0 | fix: fail closed on invalid dynamic schemas | ||
|
|
1f94167595 |
fix(python): walk only schema positions when checking dynamic refs
The dynamic-key reference walker descended into every nested dict, so a $ref-shaped value stored under const, default, enum, or examples was treated as a schema reference and raised while the tool was wrapped. Restrict the walk to Draft 7 schema positions. |
||
|
|
051c8c5357 |
fix(telemetry): redact secrets inside JSON payloads (#4043)
## Summary
Telemetry error text is redacted before it leaves the process, but the
key/value rule only matches when the separator follows the key name
directly:
```
\b(authorization|api[-_]?key|...|pwd)\b(\s*[:=]+\s*)(["']?)([^\s"',}&]+)\3
```
In JSON — and in a Python `dict` repr — the key's own closing quote sits
between the name and the colon, so `{"api_key": "..."}` never matches
and the value is sent verbatim. That is the shape error messages usually
carry: an API error envelope, a rejected request body echoed back, or an
f-string interpolating a config dict. Both SDKs share the pattern and
both are affected.
The fix moves the optional quote into the separator group
(`(["']?\s*[:=]+\s*)`). The key name, separator, and quoting are all
preserved in the output; only the value is replaced.
Measured with `redact_sensitive_text` / `redactSensitiveText` directly,
before and after:
| input | before | after |
|---|---|---|
| `api_key=sk-1` | redacted | redacted |
| `api_key: "sk-1"` | redacted | redacted |
| `x-api-key: sk-live-hdr` | redacted | redacted |
| `{"api_key": "sk-live-abc"}` | **leaks** | `{"api_key": "[REDACTED]"}`
|
| `{"api_key":"sk-live-abc"}` | **leaks** | redacted |
| `{"api_key" : "sk-live-abc"}` | **leaks** | redacted |
| `{"refresh_token":"rt-abc.def-123"}` | **leaks** | redacted |
| `{"x-api-key":"sk-hdr","user":"bob"}` | **leaks** | redacted,
`"user":"bob"` kept |
| `{'client_secret': 'cs-live-abc'}` | **leaks** | redacted |
| `the password field is required` | untouched | untouched |
| `no separator here "api_key" and nothing else` | untouched | untouched
|
The end-to-end path in Python is `composio/core/models/base.py:60-61`,
which passes `str(e)` and `traceback.format_exc()` through the redactor
into the telemetry `error` field. A tool failure whose message
interpolates the rejected request body — `Error executing tool: request
body was rejected: {"toolkit": "GMAIL", "arguments": {"api_key": "...",
...}}` — leaked the customer's key today.
## Changes
- `python/composio/utils/redaction.py`,
`ts/packages/core/src/telemetry/redact.ts`: allow a quote between the
key name and the separator.
- Tests on both sides for JSON, minified JSON, spaced-colon JSON, dict
repr, the realistic serialized-payload shape, key/quote preservation,
and no-false-positive cases.
- Changeset for `@composio/core`.
## Type of change
- [x] Bug fix
## How Has This Been Tested?
Node 24.13.0 / pnpm 11.8.0 / Python 3.10.20 (uv), Windows.
- `uv run pytest tests/test_redaction.py` — 13 passed. With
`redaction.py` reverted and the new tests kept, 9 of them fail; the 4
that still pass are the pre-existing test plus the three
no-false-positive controls.
- `npx vitest run test/telemetry/redact.test.ts` in `ts/packages/core` —
10 passed. With `redact.ts` reverted, the 3 new cases fail.
- Full Python suite: 913 passed, same 4 failures as on `next` unchanged
(3 are `ModuleNotFoundError: composio_langchain` from providers not
installed in my env; 1 is `test_empty_name_safe_fails_at_write_time`,
which expects `IsADirectoryError` but Windows raises `PermissionError`
when opening a directory). Collection 944 → 956, exactly the 12 tests
added.
- Full `@composio/core` vitest: 798 passed / 2 failed, byte-identical to
the unchanged baseline (794 passed / same 2 failed — both Windows
path-and-symlink cases). 11 test files fail to load in both runs because
I installed with `--ignore-scripts`; the repo's `preinstall` hook
doesn't run in my shell.
- `ruff format --check` and `ruff check` clean on the two Python files;
`prettier --write` applied to the two TypeScript files.
## Checklist
- [x] I have read the Code of Conduct and this PR adheres to it
- [x] I ran linters/tests locally and they passed
- [ ] I updated documentation as needed — no user-facing behavior change
to document
- [x] I added tests or explain why not applicable
- [x] I added a changeset if this change affects published packages
## Additional context
This is defence-in-depth, so it stays deliberately narrow: same denylist
of key names, same value character class, no new rules. Bare-prefix
secrets (`sk-...`, `ghp_...`) and PEM blocks are still not matched by
either SDK — worth a separate look, but widening the pattern is a
different risk trade-off than closing a shape the rule already intends
to cover.
|
||
|
|
4f009baa69 | fix(python): resolve nested dynamic refs from document root | ||
|
|
4a89ff112c | fix(json-schema): enforce dynamic object contracts | ||
|
|
bbeacac653 |
fix(python): preserve and police dynamic object content in schema conversion
`json_schema_to_model` and `json_schema_to_pydantic_type` now share one compiled object policy, so provider-facing Pydantic models stop silently discarding valid free-form arguments and stop ignoring `patternProperties` and explicit `additionalProperties` controls. Accepted dynamic keys are written back into `__pydantic_extra__`, which is what `LangchainProvider.wrap_tool` re-reads with `getattr`. Acceptance and rejection are asserted from the shared cross-SDK corpus, so the Python entry points cannot diverge from the TypeScript converters. |
||
|
|
5e57815af1 |
fix(json-schema): accept and preserve dynamic object content across converters
Property-less objects (`{ type: "object" }`, `properties: {}`) were being
collapsed to a strict empty object by the Zod converter and given an implicit
`additionalProperties: false` by the Effect converter, so valid free-form
payloads such as METABASE_POST_API_CARD.dataset_query were rejected at the CLI
boundary. `ToolSchema.parse` separately dropped root `patternProperties` and
rejected a schema-valued root `additionalProperties`.
Dynamic keys are now routed to exactly the schemas that apply to them, and the
shared acceptance contract lives in one checked-in corpus with byte-identical
TypeScript and Python copies.
|
||
|
|
f286ca98ac |
chore(deps): bump the uv group across 2 directories with 1 update (#4052)
Bumps the uv group with 1 update in the / directory: [cryptography](https://github.com/pyca/cryptography). Bumps the uv group with 1 update in the /python/providers/claude_agent_sdk directory: [cryptography](https://github.com/pyca/cryptography). Updates `cryptography` from 49.0.0 to 50.0.0 <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst">cryptography's changelog</a>.</em></p> <blockquote> <p>50.0.0 - 2026-07-31</p> <pre><code> * **SECURITY ISSUE**: :func:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_der` and its PEM and S/MIME variants no longer expose distinguishable errors or timing when unwrapping a ``RecipientInfo``'s ``encryptedKey``, which could act as a Bleichenbacher oracle for callers that decrypt untrusted messages. A random key is now substituted on failure, as described in :rfc:`3218`. Credit to **@X1AOxiang** for reporting the issue. **CVE-2026-69247** * Deprecated Diffie-Hellman key exchange over finite fields (FFDH). Everything FFDH is deprecated, including the types in ``cryptography.hazmat.primitives.asymmetric.dh`` and loading FFDH keys or parameters with the key loading APIs. Users should migrate to a more modern key exchange algorithm. * Added ``xof()`` class methods to :class:`~cryptography.hazmat.primitives.hashes.SHAKE128` and :class:`~cryptography.hazmat.primitives.hashes.SHAKE256` for constructing algorithm instances configured for use with :class:`~cryptography.hazmat.primitives.hashes.XOFHash`. * The :mod:`X.509 verification <cryptography.x509.verification>` APIs are now considered stable and are subject to our API stability policy. * Added the :doc:`/cobblestone` recipe, an implementation of the Cobblestone-128 and Cobblestone-256 instantiations of the `C2SP chunked-encryption specification <https://c2sp.org/chunked-encryption>`_ for streaming authenticated encryption of large messages. * Parsing a Signed Certificate Timestamp list now rejects encodings that carry trailing bytes after the list or after an individual SCT, instead of silently ignoring them. * Added support for using :class:`~cryptography.x509.Name` as a field type in the :doc:`/hazmat/asn1/index` module. * Loading a public key or an EC private key now rejects DER where the ``subjectPublicKey`` (or EC ``publicKey``) ``BIT STRING`` declares a non-zero number of unused bits, instead of silently ignoring it. * Parsing a CRL entry's ``InvalidityDate`` extension now rejects a ``GeneralizedTime`` that carries fractional seconds or another non-DER form, matching the strict encoding already required for every other X.509 time field. * :func:`~cryptography.x509.ocsp.load_der_ocsp_request` and :func:`~cryptography.x509.ocsp.load_der_ocsp_response` now reject a request or response whose ``version`` field is not ``v1``, the only version defined by RFC 6960, matching the version validation already performed when loading certificates, CSRs and CRLs. * :class:`~cryptography.hazmat.primitives.hashes.XOFHash` is now supported when building against AWS-LC. * HMAC (and therefore PBKDF2-HMAC) with SHA-3 hashes is now supported when building against AWS-LC. * Diffie-Hellman (:doc:`/hazmat/primitives/asymmetric/dh`) is now supported when building against AWS-LC. </tr></table> </code></pre> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/pyca/cryptography/commit/dcb7050b807b00392fa9fe2eac7cb362fcf355cc"><code>dcb7050</code></a> Prepare for 50.0.0 release (<a href="https://redirect.github.com/pyca/cryptography/issues/15372">#15372</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/53fccd93413a8d7f07d6d8999681f27b75cffa3f"><code>53fccd9</code></a> Don't leak how PKCS#7 encryptedKey decryption failed (<a href="https://redirect.github.com/pyca/cryptography/issues/15369">#15369</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/d472f978470fbefa521b86d98b2ecccbbb4d1dd8"><code>d472f97</code></a> Add <code>from __future__ import annotations</code> to all src/ Python files (<a href="https://redirect.github.com/pyca/cryptography/issues/15371">#15371</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/908773d53829fb1466c6db364b31321c3cd8eb9a"><code>908773d</code></a> Bump downstream dependencies in CI (<a href="https://redirect.github.com/pyca/cryptography/issues/15368">#15368</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/2cc07cc948948211899bcb0cddd1fddf86e95812"><code>2cc07cc</code></a> Bump BoringSSL, OpenSSL, AWS-LC in CI (<a href="https://redirect.github.com/pyca/cryptography/issues/15367">#15367</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/c94ede9f040fa44942f7139772603419000acf66"><code>c94ede9</code></a> chore(deps): bump ruff from 0.16.0 to 0.16.1 (<a href="https://redirect.github.com/pyca/cryptography/issues/15366">#15366</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/67a8308dc9ea4cce6056e0f1438f903c208c3f35"><code>67a8308</code></a> chore(deps): bump virtualenv from 21.7.0 to 21.7.1 (<a href="https://redirect.github.com/pyca/cryptography/issues/15365">#15365</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/95018ffcdbbc510fd92fc872e3a3e80aa6e58596"><code>95018ff</code></a> Release the GIL in one-shot AEAD encrypt/decrypt (<a href="https://redirect.github.com/pyca/cryptography/issues/15361">#15361</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/6954733eaf55a0074abf88f06f7242dfca3a5d02"><code>6954733</code></a> Release the GIL during DH and DSA parameter generation (<a href="https://redirect.github.com/pyca/cryptography/issues/15364">#15364</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/6893b94c33e948f6240082461424cfb5da2dacc6"><code>6893b94</code></a> Import _serialization instead of serialization in x509/extensions (<a href="https://redirect.github.com/pyca/cryptography/issues/15363">#15363</a>)</li> <li>Additional commits viewable in <a href="https://github.com/pyca/cryptography/compare/49.0.0...50.0.0">compare view</a></li> </ul> </details> <br /> Updates `cryptography` from 49.0.0 to 50.0.0 <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst">cryptography's changelog</a>.</em></p> <blockquote> <p>50.0.0 - 2026-07-31</p> <pre><code> * **SECURITY ISSUE**: :func:`~cryptography.hazmat.primitives.serialization.pkcs7.pkcs7_decrypt_der` and its PEM and S/MIME variants no longer expose distinguishable errors or timing when unwrapping a ``RecipientInfo``'s ``encryptedKey``, which could act as a Bleichenbacher oracle for callers that decrypt untrusted messages. A random key is now substituted on failure, as described in :rfc:`3218`. Credit to **@X1AOxiang** for reporting the issue. **CVE-2026-69247** * Deprecated Diffie-Hellman key exchange over finite fields (FFDH). Everything FFDH is deprecated, including the types in ``cryptography.hazmat.primitives.asymmetric.dh`` and loading FFDH keys or parameters with the key loading APIs. Users should migrate to a more modern key exchange algorithm. * Added ``xof()`` class methods to :class:`~cryptography.hazmat.primitives.hashes.SHAKE128` and :class:`~cryptography.hazmat.primitives.hashes.SHAKE256` for constructing algorithm instances configured for use with :class:`~cryptography.hazmat.primitives.hashes.XOFHash`. * The :mod:`X.509 verification <cryptography.x509.verification>` APIs are now considered stable and are subject to our API stability policy. * Added the :doc:`/cobblestone` recipe, an implementation of the Cobblestone-128 and Cobblestone-256 instantiations of the `C2SP chunked-encryption specification <https://c2sp.org/chunked-encryption>`_ for streaming authenticated encryption of large messages. * Parsing a Signed Certificate Timestamp list now rejects encodings that carry trailing bytes after the list or after an individual SCT, instead of silently ignoring them. * Added support for using :class:`~cryptography.x509.Name` as a field type in the :doc:`/hazmat/asn1/index` module. * Loading a public key or an EC private key now rejects DER where the ``subjectPublicKey`` (or EC ``publicKey``) ``BIT STRING`` declares a non-zero number of unused bits, instead of silently ignoring it. * Parsing a CRL entry's ``InvalidityDate`` extension now rejects a ``GeneralizedTime`` that carries fractional seconds or another non-DER form, matching the strict encoding already required for every other X.509 time field. * :func:`~cryptography.x509.ocsp.load_der_ocsp_request` and :func:`~cryptography.x509.ocsp.load_der_ocsp_response` now reject a request or response whose ``version`` field is not ``v1``, the only version defined by RFC 6960, matching the version validation already performed when loading certificates, CSRs and CRLs. * :class:`~cryptography.hazmat.primitives.hashes.XOFHash` is now supported when building against AWS-LC. * HMAC (and therefore PBKDF2-HMAC) with SHA-3 hashes is now supported when building against AWS-LC. * Diffie-Hellman (:doc:`/hazmat/primitives/asymmetric/dh`) is now supported when building against AWS-LC. </tr></table> </code></pre> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/pyca/cryptography/commit/dcb7050b807b00392fa9fe2eac7cb362fcf355cc"><code>dcb7050</code></a> Prepare for 50.0.0 release (<a href="https://redirect.github.com/pyca/cryptography/issues/15372">#15372</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/53fccd93413a8d7f07d6d8999681f27b75cffa3f"><code>53fccd9</code></a> Don't leak how PKCS#7 encryptedKey decryption failed (<a href="https://redirect.github.com/pyca/cryptography/issues/15369">#15369</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/d472f978470fbefa521b86d98b2ecccbbb4d1dd8"><code>d472f97</code></a> Add <code>from __future__ import annotations</code> to all src/ Python files (<a href="https://redirect.github.com/pyca/cryptography/issues/15371">#15371</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/908773d53829fb1466c6db364b31321c3cd8eb9a"><code>908773d</code></a> Bump downstream dependencies in CI (<a href="https://redirect.github.com/pyca/cryptography/issues/15368">#15368</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/2cc07cc948948211899bcb0cddd1fddf86e95812"><code>2cc07cc</code></a> Bump BoringSSL, OpenSSL, AWS-LC in CI (<a href="https://redirect.github.com/pyca/cryptography/issues/15367">#15367</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/c94ede9f040fa44942f7139772603419000acf66"><code>c94ede9</code></a> chore(deps): bump ruff from 0.16.0 to 0.16.1 (<a href="https://redirect.github.com/pyca/cryptography/issues/15366">#15366</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/67a8308dc9ea4cce6056e0f1438f903c208c3f35"><code>67a8308</code></a> chore(deps): bump virtualenv from 21.7.0 to 21.7.1 (<a href="https://redirect.github.com/pyca/cryptography/issues/15365">#15365</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/95018ffcdbbc510fd92fc872e3a3e80aa6e58596"><code>95018ff</code></a> Release the GIL in one-shot AEAD encrypt/decrypt (<a href="https://redirect.github.com/pyca/cryptography/issues/15361">#15361</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/6954733eaf55a0074abf88f06f7242dfca3a5d02"><code>6954733</code></a> Release the GIL during DH and DSA parameter generation (<a href="https://redirect.github.com/pyca/cryptography/issues/15364">#15364</a>)</li> <li><a href="https://github.com/pyca/cryptography/commit/6893b94c33e948f6240082461424cfb5da2dacc6"><code>6893b94</code></a> Import _serialization instead of serialization in x509/extensions (<a href="https://redirect.github.com/pyca/cryptography/issues/15363">#15363</a>)</li> <li>Additional commits viewable in <a href="https://github.com/pyca/cryptography/compare/49.0.0...50.0.0">compare view</a></li> </ul> </details> <br /> Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/ComposioHQ/composio/network/alerts). </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
0ae443bece |
chore(sdk): prepare Python 0.18.1 and TypeScript 0.14.1 releases (#3988)
This PR: - prepares Python SDK core and provider packages for `0.18.1`, including runtime and lockfile metadata - adds the canonical combined changelog for Python `0.18.1` and the TypeScript releases tracked by https://github.com/ComposioHQ/composio/pull/3906 - documents `@composio/core` `0.14.1` so the generated release PR passes the release-workflow guard - refreshes `mise.lock` so the TypeScript audit workflow reaches the dependency audit - keeps the pending TypeScript Changesets untouched; the generated release PR remains responsible for npm versioning and publication - validates Python with Ruff/mypy, 944 tests, all 13 package builds, and `twine check` - validates TypeScript/docs with the full 26-task `pnpm test`, high-severity production audit, 100 docs static tests, link checks, docs typecheck, and production build |
||
|
|
a3f6b9c8f2 |
fix(py/triggers): swallow malformed chunked frame instead of tearing down subscription (#3892)
## Problem TriggerSubscription._handle_chunked_events is bound directly as a Pysher channel callback. Pysher channel dispatch has no local exception boundary, so malformed chunk frames reached websocket-client error handling and marked the realtime connection failed. The normal payload parser already skips malformed frames, but the chunked path did not. ## Fix - Validate that each decoded frame is an object with string id/chunk, integer non-boolean index, and boolean final fields. - Contain decoder, validation, and reassembly failures at the callback boundary and log only a bounded frame preview. - Clear partial state only when the frame carries a validated string id, allowing that id to be reused safely. - Preserve the valid chunk reassembly path and fix the Chunked docstring typo. ## Regression coverage Tests cover invalid JSON, non-object frames, missing fields, unhashable ids, wrong field types, unexpected decoder failures, and successful same-id reassembly immediately after a bad frame clears partial state. ## Verification - nox -s tst -- tests/test_triggers.py: 83 passed - make chk: Ruff and mypy passed --------- Signed-off-by: Checo <sergio@checo.cc> Co-authored-by: Checo <sergio@checo.cc> Co-authored-by: jkomyno <alberto@composio.dev> |
||
|
|
2a2d77a764 |
fix(py/triggers): stop() deadlock + subscribe() timeout connection leak (#3890)
Fixes #3858 (both bugs reported there). Bug 1 — TriggerSubscription.stop() deadlocks when called from a trigger callback. pysher's Connection.disconnect runs socket.close() then join(timeout) on the websocket thread, but callbacks execute on that same thread's dispatch path (_handle_event blocks on the callback's future.result()), so joining the thread from within a callback that runs on it deadlocks the whole process and _alive is never cleared, so a main thread parked in wait_forever() never exits. Fix: clear _alive synchronously (so wait_forever unblocks immediately) and run disconnect() on a daemon thread, breaking the reentrancy while still closing the socket. Bug 2 — _SubcriptionBuilder.connect() leaks a websocket thread on timeout. The deadline path raised ComposioSDKTimeoutError without calling pusher.disconnect(), so pysher's run loop kept redialing every reconnect_interval for the life of the process — one leaked thread per timed-out subscribe(), and if a leaked connection later succeeded it would authenticate and subscribe the channel of an abandoned TriggerSubscription, silently consuming events. Fix: wrap the wait loop in try/except so the timeout path tears the connection down before re-raising. Added regression tests (TestTriggerSubscriptionStop and TestSubscriptionBuilderConnectTimeout) that reproduce both failure modes offline with mocked pusher connections; all 73 tests in tests/test_triggers.py pass, ruff check + format clean. --------- Co-authored-by: Alberto Schiabel <jkomyno@users.noreply.github.com> Co-authored-by: jkomyno <alberto.schiabel@gmail.com> |