Skip to content

v0.7.8: security hardening, code hygiene, MSFT oauth provider, new atlassian assets and google calendar tools#5088

Open
waleedlatif1 wants to merge 23 commits into
mainfrom
staging
Open

v0.7.8: security hardening, code hygiene, MSFT oauth provider, new atlassian assets and google calendar tools#5088
waleedlatif1 wants to merge 23 commits into
mainfrom
staging

Conversation

@waleedlatif1

@waleedlatif1 waleedlatif1 commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

waleedlatif1 and others added 21 commits June 15, 2026 13:12
…5060)

* fix(providers): pin Azure OpenAI/Anthropic endpoints to validated IP (TOCTOU SSRF)

* fix(providers): fail closed when Azure endpoint validates without a pinnable IP

* refactor(security): drop pinned-fetch agent pool, keep per-call dispatcher

* refactor(security): make createPinnedFetch the single pinned-fetch source

* refactor(security): drop pinned-fetch agent pool; keep per-call dispatcher
…cern modules (#5069)

* refactor(table): extract row ordering, executions, and tx helpers from service.ts

Move the row position/fractional-ordering internals to rows/ordering.ts,
the row-execution (workflow-group result) internals to rows/executions.ts,
and the shared tx-timeout helpers to tx.ts. Pure code-motion — verbatim
bodies, identical behavior. service.ts: 5324 -> 4442 lines.

* refactor(table): extract row CRUD and query into rows/service.ts

Move insert/update/upsert/delete/replace/batch row writes, queryRows/
getRowById reads, and findRowMatches into rows/service.ts. Verbatim
bodies; consumers repointed; @/lib/table barrel re-exports the new module
so callers are unchanged. service.ts: 4442 -> 2788 lines.

* refactor(table): extract column/schema management into columns/service.ts

Move add/rename/delete column ops and column-type/constraint updates into
columns/service.ts. addTableColumnsWithTx stays in service.ts (table-creation
primitive) to avoid a cycle. Verbatim bodies; barrel re-exports the module.
service.ts: 2788 -> 2149 lines.

* refactor(table): extract job state machine and export jobs into jobs/service.ts

Move tableJobs reads/mapping, the job lifecycle state machine, and export-job
queries into jobs/service.ts. service.ts imports latestJob* one-way for table
metadata enrichment (no cycle). Import-data orchestration helpers stay in
service.ts for now. Verbatim bodies. service.ts: 2149 -> 1791 lines.

* refactor(table): extract workflow-group management into workflow-groups/service.ts

Move add/update/delete workflow groups + outputs and pruneStale into
workflow-groups/service.ts, preserving the dynamic backfill-runner import
(cycle-breaker). Verbatim bodies; no cycle. service.ts: 1791 -> 851 lines.

* refactor(table): extract import-job data ops into import-data.ts

Move bulk insert, schema setup, and append/replace import operations into
import-data.ts (consumed by import-runner + import route). service.ts is now
the pure table-entity module (root CRUD + shared lock/column primitives).
Verbatim bodies; no cycle. service.ts: 851 -> 664 lines (5324 at start).

* refactor(table): restore private visibility and drop dead helpers post-split

Un-export DerivedJobFields/JOB_PROJECTION/mapJobRow (file-local in jobs/service.ts,
were private pre-split) so they no longer leak into the @/lib/table barrel. Remove
dead code carried into the new modules: countTables (createTable does its own inline
count check) and the unused buildOrderedRowValues/OrderedRowValue pair.

* refactor(table): use absolute imports throughout and fix an orphaned TSDoc

Normalize all relative imports under lib/table to absolute @/lib/table/...
per the project import rule (the split modules and a few pre-existing
holdouts), and relocate the getTableById TSDoc that had drifted above
applyColumnOrderToSchema. Comment/import-only; zero behavior change.
…5074)

GET /api/credential-sets/invitations returned every pending, unexpired
link-only (null-email) invitation across all organizations, including the
bearer token. Any authenticated user could enumerate and accept another
org's invitation, joining its credential set (cross-tenant access).

Scope the listing strictly to invitations addressed to the caller's own
email. Open-link invites remain redeemable only via the out-of-band
/credential-account/[token] URL.
…ment (#5072)

* feat(jsm): add Atlassian Assets (Insight/CMDB) tools for asset management

Add nine JSM Assets tools so workflows can read and write Atlassian Assets
(Insight/CMDB) objects — the foundation for keeping JSM asset tables in sync
for software/hardware asset management.

Tools (wired into the Jira Service Management block):
- jsm_list_object_schemas, jsm_get_object_schema
- jsm_list_object_types, jsm_get_object_type_attributes
- jsm_search_objects_aql (AQL search with pagination)
- jsm_get_object, jsm_create_object, jsm_update_object, jsm_delete_object

Each tool proxies through an internal route that resolves the Jira cloudId and
the Assets workspaceId, then calls the Assets API via the OAuth 2.0 (3LO)
gateway form (/ex/jira/{cloudId}/jsm/assets/workspace/{workspaceId}/v1).

Adds the CMDB OAuth scopes to the jira provider (read/write/delete cmdb-object,
read cmdb-schema/type/attribute) with descriptions, contract schemas for each
route, and block operations/subBlocks/outputs. Bumps the API-validation route
baseline for the nine new routes.

* refactor(jsm): harden Assets param coercion and response typing

- Add toOptionalInt helper so non-numeric pagination inputs never emit NaN
  into the Assets query string (startAt/maxResults/page/resultsPerPage)
- Replace Record<string, any> in mapAssetObject with typed Raw* interfaces

* fix(jsm): validate Assets workspaceId and honor `last` pagination flag

Address review findings on the Assets tools:
- Add validateAssetsWorkspaceId and guard the workspaceId in every Assets
  route before it is interpolated into the API path (mirrors the existing
  cloudId guard) — prevents a crafted workspaceId from escaping the
  workspace-scoped path
- Object schema list now falls back to the `last` flag when `isLast` is
  absent, so pagination doesn't stop early

* feat(jsm): allow overriding the auto-resolved Assets workspace

Atlassian provisions one Assets workspace per site, so workspace discovery
uses values[0] by design. For the rare multi-workspace site, expose an
advanced "Assets Workspace ID" override on the block that flows through to
every Assets operation, and document the single-workspace assumption.

* refactor(jsm): include Assets responses in the JsmResponse union

Append the nine Assets tool response types to JsmResponse for completeness
and consistency with the rest of the JSM tool surface.
* fix(uploads): authorize internal file URLs before download

downloadFileFromUrl treated any URL containing /api/files/serve/ as
trusted-internal and read the object straight from storage by key with
no access check, while every other resolution path in the file calls
verifyFileAccess. Reachable during workflow execution via file[] inputs
(type: 'url'), letting an authenticated user read arbitrary storage
objects across tenants by supplying a storage key.

Thread the caller's userId into downloadFileFromUrl and run
verifyFileAccess(key, userId, undefined, context, false) on the resolved
key before downloadFile; fail closed when no userId is present. Update
all callers (execution file inputs, tool file outputs, KB ingestion);
webhook and chat inputs already thread userId via processExecutionFiles.

* chore(uploads): log denied internal file downloads for rollout telemetry

* fix(uploads): derive internal file context from key, not query param

Cursor Bugbot flagged a context-spoofing bypass: downloadFileFromUrl
resolved context via parseInternalFileUrl, which honors a caller-controlled
?context= query param. An attacker could label a private storage key with a
world-readable context (profile-pictures/og-images/workspace-logos) so
verifyFileAccess short-circuits to granted while downloadFile still reads the
private object.

Infer context from the key only (inferContextFromKey), mirroring how
/api/files/serve resolves it; ignore the query param. Also move the userId
guard ahead of key resolution so auth failures surface first.

* docs(uploads): move context-derivation rationale into TSDoc

* fix(uploads): match internal file marker in URL path only

isInternalFileUrl matched the /api/files/serve/ substring anywhere in the
string, so a crafted URL could carry it in a query string or fragment and
skip DNS/SSRF validation. Match it in the path component only.

The raw path is checked without URL normalization on purpose: the files
parse route relies on traversal sequences surviving this check (an absolute
https://host/api/files/serve/../.. URL must classify as internal so the '..'
check rejects it, rather than being normalized to /etc/... and waved through
as external). Host is intentionally not gated — cross-tenant reads are
prevented at the storage sink by verifyFileAccess, and host-allowlisting
would break self-hosted/multi-domain deployments. Adds unit tests.

* consolidate access, billing principals

---------

Co-authored-by: Vikhyath Mondreti <vikhyath@simstudio.ai>
* feat(auth): OAuth-only signup with Microsoft provider

- Remove email/password form from /signup — Google, Microsoft, GitHub OAuth only
- Add Microsoft as a social provider (MICROSOFT_CLIENT_ID / MICROSOFT_CLIENT_SECRET / DISABLE_MICROSOFT_AUTH)
- Wire microsoftAvailable through provider checker, API contract, providers route, and all auth UI
- Hide "Continue with email" in auth modal signup view; login view unchanged
- Fix MicrosoftIcon SVG to use official brand colors and proportions

* fix(auth): remove unused useSession, guard invalid-callback warn with ref

* feat(auth): gate email signup via DISABLE_EMAIL_SIGNUP flag

* feat(auth): restore signup email form gated by NEXT_PUBLIC_DISABLE_EMAIL_SIGNUP

* refactor(auth): single DISABLE_EMAIL_SIGNUP env var controls both ui and backend

* fix(config): restore isHosted hostname check
* feat(db): zero-downtime migration safety lint + db-migrate skill

Add scripts/check-migrations-safety.ts (check:migrations), a CI gate that
classifies statements in newly-added migrations into hard errors (rewrite),
annotate-to-acknowledge contract ops (`-- migration-safe: <reason>`), and
backfill warnings. Wire it into test-build.yml. Add the /db-migrate skill as
the judgment half (expand/contract phasing, app-code cross-ref, annotation
authoring).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* feat(skills): run cleanup and db-migrate safety checks in /ship

* fix(db): address review — DROP INDEX lock symmetry, RENAME CONSTRAINT false-positive, alter-type literal match

- Non-concurrent DROP INDEX is now a hard error (ACCESS EXCLUSIVE lock), symmetric with CREATE INDEX; DROP INDEX CONCURRENTLY after a COMMIT passes clean. Removes the false-confidence annotate path.
- RENAME rule narrowed to RENAME COLUMN / table RENAME TO; RENAME CONSTRAINT and ALTER INDEX ... RENAME (metadata-only) no longer flagged.
- alter-type regex now requires TYPE to follow the column identifier, so it no longer matches TYPE inside a string default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(db): enforce IF EXISTS on DROP INDEX CONCURRENTLY for replay idempotency

Symmetric with the CREATE INDEX CONCURRENTLY rule: a post-COMMIT DROP INDEX
CONCURRENTLY replays from the top on failure, so without IF EXISTS it aborts
re-dropping an already-gone index.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* improvement(skills): gate /ship cleanup on UI changes; default migration base to staging

- /ship runs /cleanup only when the diff touches UI code (.tsx or apps/sim/components|hooks|stores); the six passes are React-only.
- /ship runs check:migrations against origin/staging (the PR base).
- check:migrations default baseRef is now origin/staging instead of origin/main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* docs(db-migrate): add contract-pending TODO convention for deferred drops

Establishes a durable, greppable marker (`contract-pending(<precondition>): ...`)
left on the legacy column in schema.ts when an expand defers a drop, so the
contract phase doesn't rot. The outstanding-work list is `grep -rn contract-pending`;
the contract PR's `-- migration-safe:` annotation references the expand and deletes
the marker.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(webhooks): cap request body size on public webhook receivers

Public, unauthenticated webhook endpoints read the entire request body
into memory before any lookup or signature verification, letting a caller
exhaust pod memory with arbitrarily large bodies.

Bound the body via the existing size-limited stream reader (content-length
guard + streamed cap) and return 413 on oversize. Applies to
parseWebhookBody (trigger receiver) and the agentmail route. Cap defaults
to 10 MB, overridable via WEBHOOK_MAX_REQUEST_BYTES.

* refactor(webhooks): extract shared body-size cap to constants module

Address review feedback: hoist WEBHOOK_MAX_BODY_BYTES into a single
lib/webhooks/constants.ts so the trigger receiver and AgentMail route share
one source of truth instead of recomputing the env-derived cap (prevents
drift). Also drop the redundant request clone when the body stream is null.

* refactor(webhooks): drop redundant null-body branch in capped readers

Both capped body readers had an `if (!stream)` fallback to an uncapped
`.text()`/empty string. `readStreamToBufferWithLimit` already returns an
empty buffer for a null stream, so the branch is redundant and the
`.text()` fallback was a theoretical bypass (chunked request, no
content-length, null body). Collapse both to a single capped read.

* chore(webhooks): drop inline comments from capped body readers
Validate the user-supplied vLLM endpoint (request.azureEndpoint) against
the central SSRF guard and pin the connection to the resolved IP before
issuing any request, mirroring the Azure OpenAI/Anthropic providers. The
operator-configured VLLM_BASE_URL stays trusted and unvalidated.
Pass allowHttp to validateUrlWithDNS so plain-HTTP self-hosted vLLM
endpoints are permitted. This only relaxes the protocol check; the
private/reserved-IP blocklist and blocked-port checks still apply, so
SSRF protection is unchanged.
* feat(feature-flags): AppConfig-backed gated feature flags

* fix(ci): repoint 'Validate feature flags' step to env-flags.ts after rename

* improvement(feature-flags): drop in-code defaults; fallback resolves a per-flag secret, gating is AppConfig-only

* improvement(feature-flags): make flag names a closed set so every flag requires a fallback secret

* improvement(feature-flags): single FEATURE_FLAGS registry — each entry defines name, description, and fallback in one place

* improvement(feature-flags): fallback is the env secret key (keyof typeof env), resolved to a boolean
…ct-point tools (#5082)

* feat(grafana): validate integration and add folder, health, and contact-point tools

- Require alert-rule title/ruleGroup/data in the block (create would 400 without them)
- Trim UID path params across dashboard and alert-rule tools to avoid copy-paste 404s
- Use Grafana brand color for the block background
- Surface previously-unsettable list params (limit, starred, annotation type)
- Add get/update/delete folder, check data source health, get health, and create contact point tools
- Strip non-TSDoc comments; regenerate docs and integrations.json

* fix(grafana): scope block param remaps per operation to prevent cross-operation leaks
* refactor(connectors): split client metadata from server runtime + cover node:net in client bundle

The browser build broke with `Cannot find module 'node:net'`. Server-only
SSRF code in `input-validation.server.ts` (`dns/promises`, and since PR #5060
`undici` → `node:net`/`node:tls`) is statically reachable from the client
bundle via the tool/connector registries, which the workflow editor imports
for metadata. Node networking builtins have no browser shim, so Turbopack
cannot compile them for the client.

Two changes:

1. Split each connector's client-safe declarative metadata into a sibling
   `meta.ts` (`<name>ConnectorMeta`), mirroring the `XBlockMeta` /
   `BLOCK_META_REGISTRY` pattern. `connectors/registry.ts` is now the
   client-safe `CONNECTOR_META_REGISTRY` (+ `getConnectorMeta` /
   `getAllConnectorMeta`); the full registry with runtime fns moves to
   `connectors/registry.server.ts`. Client components consume the meta
   registry; the sync engine and knowledge API routes consume the server
   registry. This removes connectors from the client's server-only graph.
   Connector metadata is byte-for-byte identical before/after; runtime fns
   are untouched.

2. Extend the existing #4899 `turbopack.resolveAlias` browser stub — which
   already mapped `dns`/`dns/promises` to an empty module for the browser —
   to also cover `net`/`tls` (+ `node:` variants), since `undici` now pulls
   those in. The remaining tool/provider definitions still reach
   `input-validation.server` server-side; the browser-only stub keeps those
   Node builtins out of the client bundle while the real modules stay on the
   server, so SSRF validation and IP pinning are unaffected.

Connector authoring/validation skills updated to teach the meta.ts split.

* fix(icons): use Square logo glyph only, drop wordmark

* fix(connectors): share Discord max-messages default across meta and runtime

Discord defined DEFAULT_MAX_MESSAGES separately in meta.ts (config placeholder)
and discord.ts (sync behavior), which could drift. Export it from meta.ts and
import it in the runtime, matching the single-source pattern used by the other
connectors (e.g. gmail, intercom).

* refactor(tools): route grafana/agiloft egress server-side, drop SSRF browser shim

Move the server-only SSRF-pinned fetch out of the grafana (update_dashboard,
update_alert_rule) and agiloft (11 record/search tools) definitions and into
internal API routes, the same pattern the rest of the server-side tools (and
agiloft's own attach/retrieve) already use. The tool definitions are now purely
declarative (request → internal route), so they no longer import
`input-validation.server` and the tools registry is fully client-safe.

With connectors (meta split) and these tools no longer reaching server-only code
from the client bundle, the browser no longer pulls in `dns`/`net`/`tls`:

- Add `import 'server-only'` to `input-validation.server.ts` so any future client
  import fails loudly at build time instead of silently bloating the bundle.
- Remove the `turbopack.resolveAlias` browser stub and delete
  `empty-node-fallback.browser.ts` — the root cause is fixed, the shim is gone.

Behavior is unchanged: each route runs the exact merge/validation/fetch logic the
tool ran before (every header, param branch, JSON-parse guard, error string, and
SSRF pinning preserved); only the location of execution moved from the client-
bundled definition to a server route.

* fix(connectors): move onedrive tagDefinitions into meta; drop server-only guard

- onedrive's tagDefinitions lived in the runtime file, so the client meta
  registry returned undefined for it and the add-connector tag opt-out section
  stopped rendering for onedrive. Move it into meta.ts like the other connectors
  so client and server see identical metadata (verified across all 50).
- Remove the 'server-only' import from input-validation.server.ts: the meta/route
  split already keeps it out of the client bundle, and blocks/tools registries
  don't use the guard either.

* fix(grafana): surface upstream error when the prefetch GET fails

Check response.ok on the existing-resource GET in both update routes and return
the upstream status/body, matching how the tool framework surfaced GET errors
before the move to internal routes (the framework checks response.ok before
transformResponse). Without this, a failed prefetch produced a generic
'Failed to fetch existing ...' message and dropped Grafana's error detail.

* fix(grafana): reject invalid panels JSON instead of silently ignoring it

Grafana's dashboard API treats panels as a required array and returns 400 on
invalid JSON; this route already errors on every other JSON param. Return
'Invalid JSON for panels parameter' instead of swallowing the parse error and
proceeding with a misleading success.

* fix(grafana): trim dashboard/alert-rule UID in route URLs (carry over #5082)

PR #5082 added .trim() on dashboardUid/alertRuleUid in the original tool URL
builders. Those tools now build their URLs in the internal routes, so apply the
same trim there to preserve that behavior.

* fix(grafana): route update_folder egress server-side (carry over #5082)

#5082 added a grafana update_folder tool that does SSRF-pinned fetch in its
postProcess, re-introducing the client-bundle leak. Convert it to the internal
API route pattern like the other update tools so the def is declarative and
input-validation.server stays out of the client bundle.

* fix(grafana): surface route failures in transformResponse instead of masking them

The grafana update tools' transformResponse hardcoded success: true and dropped
the route's error, so an upstream/validation failure (HTTP 200 with
{ success: false, error }) was reported to the workflow as a success. Forward
data.success and data.error (matching the agiloft tools) so failures propagate
as before the move to internal routes.
…flags (#5086)

* feat(feature-flags): migrate 3 env-flags to AppConfig-backed runtime flags

* fix(feature-flags): hardcode workflow-columns on, fix feature-flags tests

* chore(feature-flags): document mothership-beta userId targeting limitation
…lendar + sharing tools (#5084)

* feat(google-calendar): wire freebusy, align tools with API v3, add calendar + sharing tools

* fix(google-calendar): address review — trust offset timezones, make list_acl showDeleted usable, harden unshare error parse, clarify update attendees

* fix(google-calendar): wire list q/pageToken into block, harden invite PUT error parse

* fix(google-calendar): make list orderBy user-selectable, clarify update timeZone applies to start/end

* fix(google-calendar): require timeZone for recurring timed events, clarify recurrence replace semantics

* fix(google-calendar): validate grantee before building share ACL body

Throw a clear error when scopeType is user/group/domain but scopeValue is
missing or blank, instead of POSTing a scope-type-only body that the Calendar
ACL API rejects with an opaque error.
@vercel

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
docs Ready Ready Preview, Comment Jun 16, 2026 4:04am

Request Review

@cursor

cursor Bot commented Jun 16, 2026

Copy link
Copy Markdown

PR Summary

High Risk
Touches authentication, authorization, billing-adjacent routes, DB migration policy, and large refactors (connectors registries, table module split) alongside new public integration surface—worth careful staged deploy and companion-repo coordination.

Overview
This release bundles security and auth changes: Microsoft social login on login/signup and the landing auth modal (with OAuth-centric signup flow), tighter credential-set invitation listing (pending invites scoped to the caller’s email only), plus related hardening called out in the release (CSRF on session execution, webhook body caps, validated provider/upload paths).

Platform structure shifts deploy-time booleans from feature-flags.ts to env-flags.ts, while AppConfig-backed runtime feature flags (isFeatureEnabled) take per-org/user/admin gating; CI and agent docs (add-feature-flag, db-migrate, /ship) are updated accordingly. Knowledge connectors now use client-safe meta.ts plus registry.server.ts for sync/API code paths. The monolithic table service.ts is split into focused modules (rows, jobs, import-data, workflow-groups, etc.) with API routes retargeted.

CI adds a non-blocking companion PR workflow for cross-repo lockstep merges and a check:migrations gate for zero-downtime SQL. Integrations/docs expand JSM Assets (CMDB), Google Calendar (free/busy, sharing, Meet/recurrence), and Grafana (folders, health, contact points). Minor docs/icon tweaks (Square, Microsoft) accompany the above.

Reviewed by Cursor Bugbot for commit b7d30c8. Configure here.

@github-actions github-actions Bot added the requires-mothership-merge Has a companion PR on the mothership/copilot side — merge in lockstep label Jun 16, 2026
@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown

⚠️ Cross-repo companion check

One or more companion PRs aren't merged into main yet (aggregated across the feature PRs in this release). Merging this without them will leave copilot and sim out of sync — merge them in lockstep.

  • ⚠️ simstudioai/copilot#309 — merged into staging (this PR targets main) — ci(companion): cross-repo companion-PR warning + mship-release
  • ⚠️ simstudioai/copilot#311 — merged into staging (this PR targets main) — improvement(ci): directional labels + companion-check refinements
  • ⚠️ simstudioai/copilot#313 — merged into staging (this PR targets main) — feat(scheduled-tasks): migrate job system to scheduled tasks

#5070)

* improvement(perm-groups): allow workspace filter for permission groups

* show errors correctly

* address comments

* address concurrent edit concern

* address locks

* address comments"

* index migration safety

* address at route level
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

requires-mothership-merge Has a companion PR on the mothership/copilot side — merge in lockstep

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants