mirror of
https://github.com/ComposioHQ/composio.git
synced 2026-09-22 11:46:35 +08:00
@e2e-tests/node-cjs-basic@0.0.0
356 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dbe5a63965 | Release: update version | ||
|
|
08306f8bc1 | Merge branch 'next' into fix/error-subclass-names | ||
|
|
3c7b938bd1 | Merge branch 'next' into fix/telemetry-request-timeout | ||
|
|
81631f83f4 | Merge branch 'next' into fix/strict-mode-keep-optional-parameters | ||
|
|
9692db5c0a |
fix(openai-agents): honor the strict option
OpenAIAgentsProvider accepted { strict } but always registered tools with
strict: false and additionalProperties: true. Strict mode now registers
tools with strict: true and a schema normalized by toStrictJsonSchema
(optional parameters required-nullable), drops null arguments the tool
schema rejects before execution, and registers tools strict mode cannot
express without strict mode with a warning.
Co-authored-by: AseemPrasad <aseemprasad0520@gmail.com>
Claude-Session: https://claude.ai/code/session_01TDrxCHn2hg51HmxVstSUgs
|
||
|
|
3c3b4dae94 |
fix(mastra): keep optional parameters under strict mode
MastraProvider strict mode used the root-only, input-mutating removeNonRequiredProperties, so "strict" meant something different from the OpenAI providers. It now runs the same toStrictJsonSchema rewrite: optional parameters become required-nullable, tools strict mode cannot express keep their original schema with a warning, 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 |
||
|
|
4f69975475 |
fix(core): keep optional parameters under strict mode instead of dropping them
toStrictJsonSchema now follows the contract OpenAI documents for structured outputs: every property becomes required and optional ones are widened to accept null, so the model keeps every parameter it could pass before. Type arrays stay as they are (the API accepts them and rejects type next to anyOf), so nullable objects stay nullable. Constructs strict mode cannot express (objects with arbitrary keys, allOf, prefixItems, unresolved $refs, non-object roots) are reported in `unsupported` instead of being narrowed. omitNullToolArguments drops a null argument only where the tool's own schema rejects it, so nullable fields keep an explicit null. The keyword taxonomy shared by the three schema walkers now lives in one place. 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
|
||
|
|
449f4e128a | chore: add changeset for symlink path protection | ||
|
|
f174b28c3a |
fix(sdk): omit empty file-uploadable arguments from tool execution (#4238)
This PR: - closes https://github.com/ComposioHQ/composio/issues/4233 - stops forwarding `""` for a `file_uploadable` parameter (e.g. Gmail `attachment`) to the backend, which rejected it with `Input should be a valid dictionary or instance of FileUploadable` - Python: parameterizes the upload walker on a `leaf` handler and adds `FileHelper.drop_empty_file_uploads()`, run on both `Tools.execute` paths when `dangerously_allow_auto_upload_download_files` is off (the default) - TypeScript: moves the walker into the runtime-neutral `walkFileUploadableLeaves()` with a `DELETE_VALUE` sentinel and `dropEmptyFileUploads()`, so the flag-off path also works on edge runtimes; with the flag on, `''` is no longer attempted as an upload (previously threw `Either path or blob must be provided`) - TypeScript: `schemaHasFileProperty()` now looks through `$defs`/`definitions`, so the flag-off pass (and the existing one-shot warning) fire for `$ref`-based file schemas - keeps each SDK's existing `null` semantics: Python omits `None` as before, TypeScript still passes `null` through for schemas with a `null` variant - adds regression tests for both SDKs and a `@composio/core` changeset ## Context Reproduced against staging with `composio==0.16.0` and the current SDK: the live `GMAIL_CREATE_EMAIL_DRAFT` schema is `anyOf[FileUploadable, array[FileUploadable]]` with no `null` variant, and `""` yields the exact error from the issue on `no_auth` file tools (`TEXT_TO_PDF_UPLOAD_FILE`). With this change the backend sees the parameter as not provided, matching what the playground UI sends. |
||
|
|
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 |
||
|
|
700327c2a6 | Merge branch 'next' into chore/changesets-v3-migration | ||
|
|
db7b576437 |
fix(ts): declare the supported Node.js engine floor (#4219)
## Summary The ESM-only transition in #3494 established Node.js 22.22.3 as the minimum supported runtime for the public TypeScript SDK packages, but their published manifests still omit `engines.node`. That leaves package managers without a package-level compatibility signal before an older runtime encounters an ESM loading failure. For example, the current `@composio/core@0.17.0` package fails with `ERR_REQUIRE_ESM` when required on Node.js 22.0.0, while the same load succeeds on Node.js 22.22.3. This aligns the published metadata with the support floor already documented and tested by the repository. Related: #3494 ## Changes - Add `"engines": { "node": ">=22.22.3" }` to all 14 public TypeScript release workspaces. - Add a release-workflow invariant that discovers public TypeScript workspaces from the root workspace configuration and rejects missing or drifted Node.js engine ranges. - Add a patch changeset covering exactly those 14 published packages. <details> <summary>Package scope</summary> - Core packages: `@composio/core`, `@composio/slim`, `@composio/experimental`, and `@composio/json-schema-to-zod` - Providers: Anthropic, Claude Agent SDK, Cloudflare, Google, LangChain, LlamaIndex, Mastra, OpenAI Agents, OpenAI, and Vercel - Excluded as private/unpublished: CLI, CLI keyring, CLI local tools, JSON Schema to Effect Schema, and TypeScript builders </details> ## Type of change - [x] Bug fix - [ ] New feature - [ ] Refactor/Chore - [ ] Documentation - [ ] Breaking change ## How Has This Been Tested? Validated with the repository-pinned Node.js 24.17.0, pnpm 11.8.0, and Bun 1.4.0 toolchain: - `pnpm install --frozen-lockfile` - `pnpm run test:release-workflow` - `pnpm validate:changesets` - `pnpm exec changeset status --since=origin/next` (exactly 14 patch releases) - `pnpm build:packages` (19/19 packages) - `pnpm typecheck` (14/14 tasks) - `pnpm lint:packages` (successful; only pre-existing warnings in untouched source files) - Prettier over every touched file - `git diff --check origin/next...HEAD` The published-package probe also confirmed that `@composio/core@0.17.0` has no `engines` metadata, CommonJS loading fails on Node.js 22.0.0, and the same package loads successfully on Node.js 22.22.3. No lockfiles, generated files, or runtime source files changed. ## Screenshots (if applicable) Not applicable. ## 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 - [x] I added tests or explain why not applicable - [x] I added a changeset if this change affects published packages ## Additional context The runtime floor itself is not new: #3494 shipped and documented it as a breaking change in the existing `0.x` line. This patch makes registry metadata accurately reflect that existing support contract, so the accompanying releases are patches. --------- Co-authored-by: Tomas Golob <tgolob@users.noreply.github.com> Co-authored-by: Alberto Schiabel <jkomyno@users.noreply.github.com> Co-authored-by: jkomyno <alberto@composio.dev> |
||
|
|
e4189aeb57 | chore(release): migrate to Changesets v3 | ||
|
|
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) | ||
|
|
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. |
||
|
|
f031c27cd1 |
Release: update version (#4161)
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to next, this PR will be updated. # Releases ## @composio/core@0.17.0 ### Minor Changes - |
||
|
|
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> |
||
|
|
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. |
||
|
|
c0f16093ed |
fix(errors): set this.name on three ComposioError subclasses
ComposioToolVersionRequiredError, JsonSchemaToZodError, and JsonSchemaRefResolutionError omitted the this.name assignment their sibling subclasses perform, so instances inherited the base class field value and reported name 'ComposioError'. error.name is forwarded to error telemetry, which mis-grouped these three distinct error types under the base name. |
||
|
|
9545806a62 |
fix(telemetry): bound telemetry requests with an AbortSignal timeout
sendMetric and sendErrorLog awaited fetch() with no timeout, so a stalled telemetry endpoint could leave an awaited SDK call pending indefinitely. Bound both best-effort requests with an AbortController and a cleared timer via a shared postWithTimeout helper, mirroring the npm version-check bound in utils/version.ts. The existing catch still swallows the abort, preserving the never-throws contract. |
||
|
|
02196de0b5 |
Release: update version (#4104)
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to next, this PR will be updated. # Releases ## @composio/core@0.16.0 ### Minor Changes - |
||
|
|
a2f6b9699b |
fix(json-schema): integrate Google object type normalization
Co-authored-by: jkomyno <alberto@composio.dev> |
||
|
|
1af694baee | Merge branch 'next' into fix/cross-sdk-json-schema-object-conversion | ||
|
|
c625edc0f7 |
fix(core): reuse fetched schemas for provider execution (#4103)
Reuse fetched raw tool schemas for provider-wrapped execution while preserving modifier isolation, runtime validation, and unknown-slug fallback behavior. Refresh the Python 3.12.13 mise lock entries required by the Audit freshness gate. Co-authored-by: breezeFur <voidexl@outlook.com> |
||
|
|
13cba53b1d |
Release: update version (#4038)
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to next, this PR will be updated. # Releases ## @composio/core@0.15.0 ### Minor Changes - |
||
|
|
3fef432dc0 | fix: fail closed on invalid dynamic schemas | ||
|
|
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.
|
||
|
|
8d4bb3bf7e | chore: merge next into json schema conversion fix | ||
|
|
4a89ff112c | fix(json-schema): enforce dynamic object contracts | ||
|
|
063f03a72f | docs(changesets): explain each behavior change with a before and after | ||
|
|
ac6bbabd31 |
fix(claude-agent-sdk): register complete tool schemas
The provider registered tools through jsonSchemaToZodShape, which returns only
the per-property map. Root-level constraints -- `additionalProperties`, boolean
or schema-valued, and `patternProperties` -- are structurally unrepresentable in
a raw shape, so they were dropped before the Claude Agent SDK ever saw them:
free-form object arguments lost their content, and an unknown key was silently
stripped from tool input rather than rejected.
Registering the complete object schema fixes both. A tool with no input
parameters keeps its closed root explicitly, since a bare `properties: {}` is
now an open object.
Proven at the real SDK boundary rather than against a mocked `tool()`: the new
suite drives createSdkMcpServer over an in-memory MCP transport and asserts on
the advertised inputSchema and on real tools/call results.
|
||
|
|
cb6fe9bf64 |
chore(changesets): release the schema conversion fixes as minor bumps
Python's unknown-key rejection changes behavior for the langchain, langgraph, and crewai providers rather than only fixing a defect, so the release carries a minor bump across both SDKs instead of a patch. |
||
|
|
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.
|
||
|
|
ecd0861741 |
fix(core): release unread response bodies (#4078)
This PR: - follows [Undici's recommendation](https://github.com/nodejs/undici#garbage-collection) to consume or cancel unread response bodies instead of leaving connection-resource cleanup to the garbage collector, which can increase connection pressure and reduce reuse - cancels every intermediate redirect body in `ssrfSafeFetch` before validating and following the next hop - cancels non-success response bodies in both URL-upload callers before preserving their existing errors - adds regression coverage for redirect ordering, redirect-budget exhaustion, and both caller error paths - adds a patch changeset for `@composio/core` - supersedes https://github.com/ComposioHQ/composio/pull/4018 and credits @pacocartones as co-author ## Verification - `pnpm --filter @composio/core test ssrfGuard fileUtils ToolRouterSessionFilesMount` (4 files, 58 tests passed) - `pnpm --filter @composio/core test` (47 files, 1,049 tests passed) - `pnpm --filter @composio/core typecheck` - `pnpm lint:packages` (0 errors; existing warnings only) - Prettier check on all touched files - `pnpm validate:changesets` Co-authored-by: pacocartones <pacocartones@users.noreply.github.com> |
||
|
|
2a6a051bc3 |
refactor(core): remove unused isNewerVersion helper (#4029)
This PR: - closes #4028 - removes the unused `isNewerVersion` helper from the internal version utility - avoids defining malformed-version behavior for a helper with no callers or package export - retains `next`'s tested npm version-check timeout implementation - adds a patch changeset for `@composio/core` - refreshes Python 3.12.13 artifact metadata in `mise.lock` after the upstream build was republished ## Verification - version utility tests: 2 passed - core typecheck: passed - changeset validation: passed - `mise 2026.5.18 lock --platform linux-x64,linux-arm64,macos-arm64,macos-x64`: clean --------- Co-authored-by: jkomyno <alberto@composio.dev> |
||
|
|
86cc3cc399 | chore: merge next into dependabot remediation | ||
|
|
27fbb464f7 | Merge branch 'next' into fix/version-check-fetch-timeout | ||
|
|
1503786358 |
refactor(core): forbid explicit any and type JSON Schema recursively (#3968)
This PR: - is refreshed directly onto the current next branch now that #3966 and #3967 have merged; the PR contains only its intended type-safety work - enforces no explicit any through the standard .oxlintrc.json, pnpm lint, and plain oxlint --fix in lint-staged; docs remain covered by their nested configuration - replaces the loose JSONSchemaProperty in @composio/core with a concrete recursive interface, removing explicit any reachable through Tool.inputParameters and Tool.outputParameters - includes a minor changeset documenting the type narrowing for properties, default, and enum consumers - adds a compile-only public-contract test for recursive schemas, known-key validation, extension keywords, and Tool reachability - removes explicit any from FileToolModifier, ts-builders, core and CLI tests, provider tests, and json-schema-to-zod tests - keeps TypeScript ESLint-parity rules enabled and closes the tool-router example lint/typecheck gap - makes no lockfile changes and no runtime behavior changes ## Context Final PR from the original #3958 split. The migration foundations are already on next via #3966 and #3967, so this branch no longer carries a stacked base commit. |
||
|
|
2ac6ad320a | fix(core): complete version check timeout guard | ||
|
|
e5c9adabc8 | chore(deps): migrate OpenAI runtime to v7 | ||
|
|
4ceaede87a | chore(deps): refresh runtime support dependencies | ||
|
|
51056129ee |
fix(core): detect filesystem case sensitivity for uploads (#4036)
This PR: - fixes #4024 - supersedes #4025 with target-filesystem detection instead of unconditional case folding - uses `is-fs-case-sensitive` on the nearest existing directory so mixed mounts and case-sensitive filesystem configurations are handled correctly - preserves exact matching on case-sensitive filesystems and falls back to conservative case-insensitive matching when detection fails - keeps Node-only detection behind `#platform` and mirrors its runtime dependency in `@composio/slim`, preserving the workerd-safe bundle and Slim import - adds focused Node, workerd, and matcher regressions plus an `@composio/core` patch changeset - verifies 1,044 core tests, five CLI upload tests, the Slim build/import smoke, typecheck, build, ATTW, publint, lint, formatting, frozen install, and changeset validation |
||
|
|
fdbd44f9eb |
Release: update version (#3906)
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to next, this PR will be updated. # Releases ## @composio/core@0.14.1 ### Patch Changes - |
||
|
|
577a3d4013 |
security: fix 3 CodeQL alerts (2026-07-22) (#3909)
## Summary
Automated weekly CodeQL remediation run for 2026-07-22. Closes three
`js/polynomial-redos` code scanning alerts (all severity high) in the
Cloudflare Workers / Edge platform shim.
`ts/packages/core/src/platform/workerd.ts` implements the `Platform`
interface for edge runtimes, where Node's `path` module is unavailable,
so path joining and basename extraction were done with regexes. Three of
those regexes (`/\/+$/` twice, `/^\/+|\/+$/g` once) backtrack
quadratically on long runs of `/`, so a path segment made of many
slashes turns a trim into a hang. They are replaced with index-walk
helpers that scan character codes, which is linear and allocates nothing
when the string is already trimmed.
Output is unchanged for every input. Equivalence was verified
exhaustively over all 137,257 strings of length 6 or less drawn from
`{'/', 'a', '\n', '\r', '.', ' ', '\\'}`, plus 300k randomized
multi-segment `joinPath` compositions and a Unicode surrogate spot
check, with zero mismatches against the original regexes. The `\n` and
`\r` cases matter because JavaScript `$` without the `m` flag anchors to
end of input only, so `/\/+$/` must not trim `"a/\n"`; the index walk
agrees.
All 15 open alerts on `next` were triaged. 3 are fixed here, 12 are
skipped with a recommended disposition for a human to action. Details
below.
## Changes
- `ts/packages/core/src/platform/workerd.ts`: add module private
`trimTrailingSlashes` and `trimSurroundingSlashes`, use them in
`joinPath` and `basename`. No export surface change, no `Platform`
interface change, no other platform implementation touched.
- `ts/packages/core/test/platform/workerd.test.ts`: new behavior pinning
test file.
- `.changeset/fix-workerd-slash-trim-redos.md`: patch bump for
`@composio/core`.
## Type of change
- [x] Bug fix
- [ ] New feature
- [ ] Refactor/Chore
- [ ] Documentation
- [ ] Breaking change
## Fixed
| Alert | Rule | Severity | File | Fix | Test that proves it |
|---|---|---|---|---|---|
|
[#55](https://github.com/ComposioHQ/composio/security/code-scanning/55)
| `js/polynomial-redos` | High |
`ts/packages/core/src/platform/workerd.ts:21` | `joinPath` first
segment: `/\/+$/` replaced with `trimTrailingSlashes` | `joinPath
matches the pre-fix regex implementation for single segments` |
|
[#56](https://github.com/ComposioHQ/composio/security/code-scanning/56)
| `js/polynomial-redos` | High |
`ts/packages/core/src/platform/workerd.ts:23` | `joinPath` later
segments: `/^\/+\|\/+$/g` replaced with `trimSurroundingSlashes` |
`joinPath matches the pre-fix regex implementation for every segment
pair` |
|
[#57](https://github.com/ComposioHQ/composio/security/code-scanning/57)
| `js/polynomial-redos` | High |
`ts/packages/core/src/platform/workerd.ts:43` | `basename`: `/\/+$/`
replaced with `trimTrailingSlashes` | `basename matches the pre-fix
regex implementation for every input` |
On test design: the equivalence tests compare the new implementation
against a copy of the pre-fix regex logic, so they pin behavior but pass
in both states by construction. The test that is actually red before the
fix and green after is `rejects the backtracking slash regexes at the
source level`, which asserts the two vulnerable patterns are absent from
`workerd.ts`. That is what would catch a silent revert to the regex
form. A timing based "pathological input completes within N ms" test was
deliberately not written, because it is flaky in CI.
## Skipped
Twelve alerts were read and deliberately not fixed. None were dismissed
via the API; that decision is yours.
### Test only code, recommend `dismiss: used in tests`
**[#47](https://github.com/ComposioHQ/composio/security/code-scanning/47)**
`py/incomplete-url-substring-sanitization`, High,
`python/tests/test_files.py:2518`. The flagged line is `assert
"example.com" in sanitized`, a pytest assertion checking that a logging
redaction helper preserved the host. It is not a URL authorization
check.
```
gh api -X PATCH /repos/ComposioHQ/composio/code-scanning/alerts/47 \
-f state=dismissed -f dismissed_reason="used in tests" \
-f dismissed_comment="Substring check is a pytest assertion on a log-redaction helper, not an authorization check. codeql-autofix 2026-07-22, PR #3909"
```
**[#54](https://github.com/ComposioHQ/composio/security/code-scanning/54)**
`js/incomplete-sanitization`, High,
`ts/packages/core/test/models/verifyWebhook.integration.test.ts:339`.
`fixture.payload.replace('{', '{ ')` deliberately corrupts a payload so
the test can assert webhook verification fails on whitespace change. It
is the opposite of a sanitizer.
```
gh api -X PATCH /repos/ComposioHQ/composio/code-scanning/alerts/54 \
-f state=dismissed -f dismissed_reason="used in tests" \
-f dismissed_comment="Deliberate payload corruption in a negative webhook-verification test, not a sanitizer. codeql-autofix 2026-07-22, PR #3909"
```
### False positive, recommend `dismiss: false positive`
**[#105](https://github.com/ComposioHQ/composio/security/code-scanning/105)**
and
**[#106](https://github.com/ComposioHQ/composio/security/code-scanning/106)**
`js/prototype-polluting-assignment`, Medium,
`ts/packages/core/src/utils/jsonSchema.ts:255-256`.
The flagged statements are `delete out.$defs` and `delete
out.definitions`. Both use hardcoded literal keys, never attacker
controlled. `out` can never alias `Object.prototype`: every return path
in `walk()` constructs a fresh object via `Object.fromEntries` or object
spread, except the primitive passthrough at line 197, which cannot
return `Object.prototype` because the top level call is seeded with a
value already checked by `isPlainObject`. Two upstream guards further
block the flow CodeQL believes exists: `cloneChildren` filters
`POLLUTING_KEYS` (`__proto__`, `constructor`, `prototype`) at line 171,
and `tryStep` gates property access with
`Object.prototype.hasOwnProperty.call` at line 91. CodeQL cannot see
through that guard because it is wrapped in a tagged union return.
```
gh api -X PATCH /repos/ComposioHQ/composio/code-scanning/alerts/105 \
-f state=dismissed -f dismissed_reason="false positive" \
-f dismissed_comment="delete uses a literal key and out is always a freshly constructed object; POLLUTING_KEYS filter and hasOwnProperty guard block the modeled flow. codeql-autofix 2026-07-22, PR #3909"
```
```
gh api -X PATCH /repos/ComposioHQ/composio/code-scanning/alerts/106 \
-f state=dismissed -f dismissed_reason="false positive" \
-f dismissed_comment="Same flow as alert 105, on delete out.definitions. codeql-autofix 2026-07-22, PR #3909"
```
### Needs a decision, recommend `keep open`
These are not obviously wrong, but the correct fix changes generated or
rendered output, which is a product decision rather than an automated
one. They are left open on purpose.
| Alert | Rule | File | Why it needs a human |
|---|---|---|---|
|
[#48](https://github.com/ComposioHQ/composio/security/code-scanning/48)
| `js/incomplete-sanitization` |
`docs/app/llms.mdx/[[...slug]]/route.ts:626` | The only runtime site in
this group. Escapes `\|` for a markdown table cell without escaping
backslash first, so a description containing `\|` can break out of the
cell. Impact is table formatting in an LLM facing docs endpoint, not
injection into a code sink. Fixing it changes rendered docs output, and
this file is outside the `@composio/core` test baseline used for this
run. |
|
[#49](https://github.com/ComposioHQ/composio/security/code-scanning/49)
| `js/incomplete-sanitization` |
`docs/scripts/generate-meta-tools.ts:176` | Escapes `"` into YAML
frontmatter without escaping backslash first. Build time, first party
tool metadata. Backslash first escaping changes generated docs. |
|
[#50](https://github.com/ComposioHQ/composio/security/code-scanning/50)
| `js/incomplete-sanitization` |
`ts/packages/core/scripts/generate-docs.ts:1008` | `escapeTextForMdx`,
same class. Build time only. |
|
[#51](https://github.com/ComposioHQ/composio/security/code-scanning/51)
| `js/incomplete-sanitization` |
`ts/packages/core/scripts/generate-docs.ts:1008` | Second instance on
the same chain as #50. |
|
[#88](https://github.com/ComposioHQ/composio/security/code-scanning/88)
| `js/incomplete-sanitization` |
`docs/scripts/generate-meta-tools.ts:134` | Markdown table escaping of
`\|` over first party metadata. |
|
[#89](https://github.com/ComposioHQ/composio/security/code-scanning/89)
| `js/incomplete-sanitization` |
`docs/scripts/generate-meta-tools.ts:136` | Same as #88 on the fallback
description branch. |
|
[#90](https://github.com/ComposioHQ/composio/security/code-scanning/90)
| `js/incomplete-multi-character-sanitization` |
`docs/agent/lib/docs.ts:232` | `toCleanMarkdown` strips JSX and HTML
tags in a single pass, so `<<div>div>` leaves `<div>`. It cleans MDX for
LLM context and its output is never rendered as HTML, so it is not a
security sanitizer. Duplicated verbatim in #92. |
|
[#92](https://github.com/ComposioHQ/composio/security/code-scanning/92)
| `js/incomplete-multi-character-sanitization` |
`docs/scripts/build-agent-index.ts:155` | Verbatim duplicate of the
`toCleanMarkdown` helper in #90. If one is addressed, both should be,
ideally by extracting the shared helper. |
## Scanning config recommendations
Six of the twelve skips (#49, #50, #51, #88, #89, #92) share one root
cause: CodeQL scans build and docs generation scripts, and flags string
escaping over first party metadata that never crosses a trust boundary
at runtime.
This repo uses CodeQL **default setup** (`analysis_key` is
`dynamic/github-code-scanning/codeql:analyze`, and there is no committed
CodeQL workflow), so `paths-ignore` is not available. The two options
are to migrate to advanced setup with a committed workflow so
`paths-ignore` can exclude these directories, or to bulk dismiss the
alerts individually.
**Recommendation: do neither blindly, and do not add `paths-ignore` for
`**/scripts/**`.** These are real findings in real code, just low
priority ones over trusted input. Excluding the directory would also
blind the scanner to genuine future vulnerabilities in build tooling,
which is where supply chain problems tend to live. The better path is a
single follow up PR that fixes the escaping properly, escaping backslash
before the other characters, with the generated output diff reviewed as
part of that PR. If the team decides the churn is not worth it,
dismissing each as `won't fix` with that justification is the honest
alternative, and it keeps future scanning intact.
## How Has This Been Tested?
```
pnpm --filter "@composio/core^..." run build # build workspace deps once
pnpm --filter @composio/core run test
pnpm --filter @composio/core run typecheck
```
- Pre-fix baseline on clean `next` at `2f63fe540`: **44 test files, 1034
tests, 0 failures.**
- On this branch: **45 test files, 1040 tests, 0 failures.** That is the
baseline plus this PR's 6 new tests.
- Typecheck: clean.
- There were no pre-existing failures, so nothing here is masking one.
Toolchain note for reproduction: the workspace `preinstall` hook
requires `mise`, and `@composio/json-schema-to-zod` must be built before
the `@composio/core` suite will resolve its imports. Without that build,
9 test files fail with `Failed to resolve entry for package
"@composio/json-schema-to-zod"`, which is an environment artifact and
not a real failure.
## Remaining backlog
- Fixable alerts beyond the 15 alert fix cap: **0**. All 15 open alerts
on `next` were triaged in this run.
- Untriaged alerts beyond the 50 alert triage bound: **0**. The bound
was not reached.
- Open alerts remaining after this PR merges: **12**, all listed above
with a recommended disposition. Four of them have ready to run dismissal
commands, which would permanently shrink every future run's fetch.
## Post-fix security scan
`composio-security:security-diff-scan` was run on this PR's diff as the
final gate, because a remediation can introduce its own bug and CodeQL
will not re-run until CI does.
**Verdict: clean. Zero reportable findings.**
Scope: merge-base `2f63fe540` to head `558d730e6`, all three changed
files. Every deep-review row has a completion receipt, so coverage is
complete rather than sampled. Discovery emitted no technically plausible
candidates, so validation and attack-path analysis were skipped under
the scan's documented zero-candidate rule.
Checks that were run and came back negative:
- The fix removes the denial-of-service primitive rather than relocating
it. No regex remains on the trimming paths, and all three loops strictly
progress toward their bound, so there is no unbounded-work or
infinite-loop path.
- Surrogate-pair safety. `charCodeAt` compares UTF-16 code units, and
`/` is U+002F, which can never appear as either half of a surrogate
pair. No astral-plane input can make the index walk disagree with the
regex it replaced.
- No security control changed. `joinPath` feeds `resolvePath`, which
`sensitiveFileUploadPaths.ts:39` consumes for its upload denylist. That
unchanged consumer was inspected as a negative control: byte-identical
output means it cannot observe a difference, and it independently
re-splits on slash runs at line 48 anyway.
- No new allocation or memory amplification. Both helpers return the
input unchanged when nothing needs trimming, so the fix allocates
strictly less than the `.replace()` calls it replaced.
- The new test file reproduces the two pre-fix regex literals as
reference implementations. They are applied only to a literal `CASES`
array, so there is no taint source, and `test/` is absent from this
package's `files` allowlist, so they are never published to SDK
consumers.
No new critical or high finding was introduced by this diff, so no
commit needed reverting and no alert was reclassified.
## 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 documented behavior changed)
- [x] I added tests or explain why not applicable
- [x] I added a changeset if this change affects published packages
## Additional context
Generated by the `codeql-autofix` skill. The bot never dismisses,
closes, or modifies alerts, and never merges or enables auto merge.
Alerts close only because merged code no longer contains the flaw.
---------
Co-authored-by: Saransh <saranshrana@Saranshs-MacBook-Pro.local>
Co-authored-by: jkomyno <alberto@composio.dev>
|
||
|
|
503b50ab02 |
chore(deps): refresh SDK, Python, and CI dependencies (#3955)
This PR: - splits https://github.com/ComposioHQ/composio/pull/3953 in two: this PR carries every dependency and GitHub Actions bump **except** the docs site, which follows in a stacked PR - consolidates and supersedes Dependabot PRs #3915, #3916, and #3934 through #3942 - adopts TypeScript 7.0.2 for primary compilation while retaining the `@typescript/typescript6` API lane that TypeScript-ESLint still requires, following the upstream side-by-side guidance - refreshes Python core and provider dependencies, lockfiles, and the Ruff 0.16 lint configuration - updates every GitHub Action with a verified newer official release, including majors, while retaining immutable commit SHA pins and migrating setup-uv cache pruning - deletes four per-package `eslint.config.mjs` shims: under ESLint 10 the default per-file config lookup re-anchors the root config's globs into each package, so `pnpm lint` stayed green while the CLI's try/catch, `process.env` and node-builtin bans went unenforced - bounds and documents the new `brace-expansion` and `@hono/node-server` security overrides, raising the `@hono/node-server` floor to 2.0.10 to clear GHSA-9mqv-5hh9-4cgg - preserves intentional compatibility fixtures and lanes for AI SDK 6, Zod 3, TypeScript 5.8, Mastra AI SDK 5, and Python provider constraints ## Context The docs site is a separate Bun workspace with its own `bun.lock` and is not a pnpm workspace member, so the two halves share no lockfile and no build. Splitting them keeps the Fumadocs 11 migration (a breaking API change with real refactoring) reviewable on its own, independently of the mechanical version bumps here. The `brace-expansion` override deliberately spans majors: GHSA-mh99-v99m-4gvg (HIGH) is published as a single `<=5.0.7` range with no 1.x or 2.x backport, so narrowing it to the 5.x line puts `brace-expansion` 2.1.2 back under `core>minimatch>brace-expansion` and `pnpm audit --prod --audit-level=high` exits 1. Verified both ways; the trade-off it buys is recorded inline in `pnpm-workspace.yaml`. Verified on this branch standalone: `pnpm install --frozen-lockfile`, `pnpm lint`, `pnpm typecheck`, `pnpm build:packages`, `pnpm test` (963 tests, 26/26 tasks), and `pnpm audit --prod --audit-level=high`. |
||
|
|
2f63fe5407 |
fix(core): secure Tool Router session URL uploads (#3900)
## Summary - route Tool Router session URL uploads through the SDK's SSRF-safe fetch path - validate every redirect target and reject destinations that resolve to non-public network addresses - stream URL response bodies with a 100 MiB limit instead of buffering unbounded responses - fail closed for URL uploads on edge runtimes where DNS validation is unavailable - apply the bounded response reader to the existing core URL-upload path as well - add a patch changeset for `@composio/core` ## Root cause Tool Router session file uploads fetched string URL inputs directly and buffered the entire response. That bypassed the SSRF checks already used by other SDK upload paths and allowed an untrusted URL to target internal network resources or return an unbounded response body. ## Impact Node.js and Bun URL uploads now validate resolved addresses before connecting and revalidate redirect hops. Cloudflare Workers and other edge runtimes reject URL inputs with a typed error; callers can fetch trusted content themselves and pass a `File` or `ArrayBuffer` instead. ## Validation - focused SSRF and file upload tests: 59 passed - full `@composio/core` suite: 1,034 passed - TypeScript checks with both tsgo and tsc - ESLint and Prettier - package build, ATTW, and publint - changeset validation - Cloudflare Workers live E2E: 5 passed - live Node Tool Router upload/list/download/delete flow - live Node Tool Router HTTPS URL upload and cleanup <!-- codesmith:footer --> --- <a href="https://app.blacksmith.sh/ComposioHQ/codesmith/composio/pr/3900"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img alt="View with Codesmith" src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a> <a href="https://backend.blacksmith.sh/track/enable-autofix?expires=1787224599&installation_model_id=428187&pr_number=3900&repository=ComposioHQ%2Fcomposio&return_to=https%3A%2F%2Fgithub.com%2FComposioHQ%2Fcomposio%2Fpull%2F3900&signature=ef519ac58a7945667565c1c143983f353b8947c988079d76c04afa9ab3b111d1"><picture><source media="(prefers-color-scheme: dark)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img alt="Autofix with Codesmith" src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a> <sup>Need help on this PR? Tag <code>/codesmith</code> with what you need. Autofix is disabled.</sup> <!-- codesmith:autofix:disabled --> <!-- /codesmith:footer --> |
||
|
|
9498679c26 |
fix(release): guard and document CLI releases (#3895)
This PR: - removes the stale `@composio/cli` changeset that wedges `changesets/action` and preserves its release note in the CLI changelog - adds `validate:changesets` before the TypeScript release action and covers ignored-package changesets in the release regression suite - makes the guard read changeset files directly so it also works in shallow and detached CI checkouts - refreshes `mise.lock` after the pinned Python standalone artifacts moved to the 20260718 build - adds the repo-local `cli-release` skill with beta, stable-promotion, verification, and failure-recovery procedures - replaces the contradictory "stable via changeset" contributor guidance with the tested-beta promotion path - extends skill taxonomy, routing probes, and PR path filters so the guard cannot silently drift ## Regression coverage The validator test creates a changeset fixture outside a Git repository, verifies that an ignored CLI package is rejected, then verifies that a normal package changeset passes. This reproduces the shallow-checkout failure without relying on a local `next` ref. ## Verification - `pnpm validate:agent-skills` - `pnpm validate:skill-routing` - `pnpm validate:changesets` - `pnpm test:release-workflow` - `pnpm lint` - `pnpm install --frozen-lockfile` - all 24 TypeScript package test tasks - skill-creator `quick_validate.py` - Prettier check - `git diff --check` - manual beta release [`@composio/cli@0.2.33-beta.294`](https://github.com/ComposioHQ/composio/releases/tag/%40composio/cli%400.2.33-beta.294): 33/33 release and installation jobs passed |
||
|
|
8467efdfb9 |
fix(cli): whoami respects the org selected via orgs switch (#3832)
## Summary `composio whoami` reports the wrong organization after `composio orgs switch`. It always shows the API key's home org, ignoring the switched-to org, even though `composio orgs list` correctly reflects the selection. **Root cause:** `whoami` resolves session info via `getSessionInfoByUserApiKey`, which sent only `x-user-api-key` and omitted `x-org-id`. Without that header the backend falls back to the API key's home org. Consumer commands (`search`, `execute`, …) already forward the selected org via `resolveCommandProject`, so only `whoami` was affected — a display bug, not an API bug. **Verification that the backend honors the header** (direct calls to `GET /api/v3/auth/session/info`): | `x-org-id` | returned project/org | |---|---| | org A | org A | | org B | org B | | (omitted) | API key's home org | ## Changes - `getSessionInfoByUserApiKey` accepts an optional `orgId` and forwards it as `x-org-id` when present (login-time callers pass none, so their behavior is unchanged). - `whoami` passes the selected global org (`ctx.data.orgId`). - Unit tests asserting the header is sent when an org is selected and omitted otherwise. ## Type of change - [x] Bug fix ## How Has This Been Tested? - New unit tests: `test/src/services/composio-clients.test.ts` (2 cases). - Existing `whoami.cmd.test.ts` still passes (5 tests total green). - Manual end-to-end with a real UAK against two orgs: a binary built from this branch reports the switched org correctly (`orgs switch` → `whoami`), whereas the released 0.2.31 binary always reported the home org. ## 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 docs affected) - [x] I added tests or explain why not applicable - [x] I added a changeset if this change affects published packages |