feat(credentials): saved credentials for 21 token-and-host blocks on one host-aware check factory
Summary
-
21 more blocks get a saved credential and a credential-only version. This time the connection is a token plus its host, region, or account ID: Algolia, Azure DevOps, Convex, Devin, Dynatrace, Email Bison, Grafana, Infisical, Jotform, Jupyter (
jupyter_v3), Logfire, Mailgun, New Relic, Okta, Plane, Supabase, Thrive, Tinybird, Twilio SMS and Twilio Voice (one shared Twilio credential), and Upstash. -
One check factory for every vendor.
tokenCheckValidatorgains:domainandworkspaceIdnormalizers: the normalized domain is what the credential stores and what runs use;- an
egressprofile, which sends checks to a user-chosen host through the same SSRF-guarded egress the tools use (no redirects, size cap,ENOTFOUND→site_not_found); errorBodyEchoesToken, which keeps a vendor's token-echoing error body out of logs (Tinybird);acceptsBodycan name the credential from the verified reply.
Shared
normalizeHttpBaseUrl,allowedDomainandrefuseUrlCredentialshelpers replace the per-vendor URL and region rules.GitLab moves onto it, so every token check is a short declaration in
validators/token-checks.ts. -
Every host-deciding param comes from the credential.
- A typed host is normalized the way the tools build their URLs, then verified at that host.
- A region or account ID is checked before any request, so a value that could steer the host or path is refused before the key is sent.
- The registry contract check enforces this for every successor.
-
The connect form sends URL-shaped domains as typed. Instance and deployment URLs keep their scheme and path, and the server check normalizes them. Before this, it stripped them to a bare host, which broke Convex and dropped Jupyter and Grafana sub-paths.
-
Azure DevOps URLs are traversal-safe. Every project-scoped URL goes through one builder that runs the organization, project and ids through
safeUrlPathSegment, so a project like../../other-orgcan't carry a saved token into another organization. A legitimate name, including one with spaces or pasted already encoded, builds the same URL as before. -
Integration skills teach the pattern.
add-integration,add-block,add-toolsandvalidate-integrationnow describe token credentials (descriptor, check declaration, the shared policy mapping every host-deciding param) and credential-only successors for blocks that take a secret inline. -
New credentials default to a name with their domain or account ID (e.g. "Azure DevOps (contoso)"), and the audit log records it.
-
Databricks keyless tokens last up to an hour. Databricks gives its access token the same expiry as Sim's subject token, so long Genie polls no longer lose auth. The issuer caps any requested lifetime at an hour.
Release-audit fixes (credentials as a whole)
- No stray host in user params. A policy that maps credential fields owns them, so the generic
domain/instanceUrl/apiDomain/cloudIdfills no longer write a stored host into a same-named param. Before this, Thrive create/update user sent its region host as the user's domain. - Agent tool schema. With a saved credential selected, the model is neither asked for nor allowed to send what the credential supplies (Azure DevOps
organization, Infisical base URL). - Mistyped host under Bun.
ENOTFOUNDis read from the error or its cause, so a mistyped host or account id givessite_not_foundunder Bun too, not a retryable outage. - Saved secrets need a bound tool. A service-account secret is released only to a tool bound to its provider. The token route refuses a bare credential ID.
- Unsafe typed URLs refused. The connect check refuses a typed instance URL with a non-http(s) scheme or embedded credentials (Email Bison, Jupyter, Logfire).
- Docs and copy. Successor docs (Jotform, Dynatrace, Logfire, Plane, Infisical, Fathom, Supabase), self-hosting, the AWS keyless step, and ElevenLabs/Instantly/Okta copy.
Existing workflows don't change
- Legacy blocks only gain
hideFromToolbar: trueandsunset;jupyter_v2becomes legacy forjupyter_v3, andjupyter(v1) now points atjupyter_v3. - A parity run dumped every block and tool at staging and at this branch, using the real registries, and compared them:
- Blocks: builder output for every operation, serializer output, and missing-field errors.
- Tools: request URL, method, headers and body.
- Everything is identical except the new successors, the legacy flags, the added
oauth(required: false) on these tools, and Azure DevOps URLs (below).
- All 74 existing
tokenCheckValidatorvalidators were run through 11 reply scenarios at both revisions: 814 cases, no differences.
Held back
Each of these doesn't fit the pattern cleanly:
- Optional key or keyless servers: Dagster, Temporal, Qdrant (its operation values don't match its tool IDs either).
- Two secrets or multiple auth modes: Splunk, PostHog, Zendesk, Elasticsearch.
- No side-effect-free check: Google AppSheet, RevenueCat.
- A2A: no reliable key check.
- Host or IDs that don't fit: LogRocket (key plus two IDs and a host), Pinecone (host chosen per call).
- PitchBook: its check may cost credits; unverified.
- Downdetector: the auth model is unclear.
- GitHub: it already has OAuth repository and app-installation connections, so a token credential needs a design pass of its own.
Type of Change
- New feature
Testing
-
New tests:
- Factory: guarded egress at the normalized host, normalizer refusal before any request,
site_not_found, no token in error details. - A table of vendors that refuse a host, region or account ID that could steer the request.
- Every user-chosen-host vendor refuses credentials embedded in the URL before any request.
- Azure DevOps anonymous-identity refusal.
- GitLab display name and audit domain.
- Issuer lifetime cap.
- Serializer contract rows for all 21 blocks.
Each guard was reverted to watch its test go red.
- Factory: guarded egress at the normalized host, normalizer refusal before any request,
-
Targeted Vitest (99 files, 1,760 tests),
test:scripts,bun run lint,type-check,check-block-registry.ts origin/staging(incl. the saved-credential contract), and the canvas-sentences, docs, integration-catalog, deployment-config, block-successors, tool-metadata, boundaries, client-boundary, tool-registry-boundary, react-query, test-pattern, test-types, comment, utils, explicit-any, file-names, source-conventions and unused-exports audits. -
Parallel audits: per block (all 21), the factory and every existing validator, backwards compatibility (parity dump), and a coverage sweep of every block that still takes an inline secret. Fake keys were sent to each public vendor endpoint to confirm it rejects them.
Checklist
- Code follows project style guidelines
- Self-reviewed my changes
- Tests added/updated and passing (new tests pass the
test-auditauthoring gate) - No new warnings introduced
- I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)