Skip to content

Commit 8a44621

Browse files
authored
feat(cloudflare): add WAF rulesets, rate limiting, Zero Trust Access, R2, Workers, and Tunnels (#6740)
* feat(cloudflare): add WAF rulesets, rate limiting, Zero Trust Access, R2, Workers, and Tunnels Extends the Cloudflare integration past DNS/zones/cache with the security and Zero Trust surface: - Rulesets engine (zone-scoped): list rulesets, get a ruleset, read a phase entry point, and create/update/delete rules. WAF managed-rule overrides are surfaced through the http_request_firewall_managed entry point, since Cloudflare has no dedicated overrides endpoint. - Rate limiting (zone-scoped) via the current Rulesets-based http_ratelimit phase, not the deprecated rate_limits endpoint. - Cloudflare Access (account-scoped): applications, application policies, groups, identity providers, and service tokens. - R2 buckets, Workers scripts/routes, and cloudflared Tunnels. Destructive operations (delete application, delete policy, revoke service token, delete rule, delete bucket) spell out their blast radius, and every tool branches on the envelope's success flag rather than the HTTP status. Security events are intentionally omitted: Cloudflare exposes them only through the GraphQL firewallEventsAdaptive dataset, whose field list is not documented outside schema introspection. * fix(cloudflare): correct docs drift and remove any from the tool layer Validation pass over all 47 Cloudflare tools against developers.cloudflare.com. - Two tool descriptions still escaped a quote as \'. That reaches the model verbatim and truncates the generated MDX cell — the get_zone_settings `value` output row was missing from the published docs entirely. Both are now template literals, and the row is back. - list_rulesets ignored pagination. The endpoint pages by cursor via result_info.cursors.after (not page/per_page), so a zone with many rulesets silently truncated with no way to page. Expose per_page + cursor and return the next cursor. - The managed-ruleset override description claimed action and enabled were the overridable properties. They are the ones the Rulesets engine documents at every level, but individual managed rulesets add more: an OWASP Core Ruleset rule override also takes score_threshold. Corrected in both the tool output description and the block's action-parameters wand prompt. (sensitivity_level is a DDoS override, not a WAF one — deliberately absent.) - list_tunnels/get_tunnel dropped the documented `metadata` field. - list_r2_buckets appended order=name whenever any filter was set. `order` only qualifies `direction`, and `name` is its sole documented value. - Path-interpolated IDs are trimmed, so a pasted ID with trailing whitespace no longer 404s. - Replaced every `any` in the integration with checked types: a shared CloudflareEnvelope plus per-resource raw payload interfaces, read through readCloudflareResponse. The mappers in utils.ts were the widest hole — typing them caught four real output-shape mismatches (identity provider read_only, service token enabled, DNS record meta/priority, certificate geo_restrictions) that `any` had been hiding. - BlockMeta only described DNS and zone work. Added templates and skills for the WAF, rate limiting, and Zero Trust Access surfaces the block now has. Confirmed against the docs and left unchanged: rulesets/rate limiting are zone-scoped and Access/R2/Workers scripts/Tunnels are account-scoped while Workers routes are zone-scoped; tunnels live under /accounts/{id}/cfd_tunnel; the ratelimit object is a sibling of action/expression, not nested in action_parameters; every rate limiting period and mitigation_timeout option matches the documented set; R2 delete returns an empty result so echoing the requested bucket name is correct; app-nested Access policy endpoints are current, not deprecated; and every tool fails on a 200 carrying success:false. * fix(cloudflare): stop per-operation subblock defaults colliding on a shared id Subblock initial values are seeded into block state keyed by subblock id — both stores/workflows/utils.ts and lib/workflows/defaults.ts assign `subBlocks[subBlock.id]` in a plain forEach — so two controls sharing an id leave one stored value and the last definition in file order wins. Four ids were duplicated with differing defaults: - `type` was defined four times. The Access "Application Type" control is last, so every new block seeded `type = 'self_hosted'` and the three DNS record controls inherited it — Create DNS Record sent a Zero Trust application type as its record type. The subblock added on this branch broke a default on tools that shipped long before it. - `status` was defined three times. The empty tunnel filter is last, so List Certificates lost its `all` default. - `proxied` was defined three times. An empty filter is last, so Create DNS Record lost its explicit `false`. - `action` was defined twice. The rate limiting dropdown is last, so the ruleset-rule action input was seeded `block`, quietly making "block live traffic" the default for a WAF custom rule the user never configured. Give the colliding controls their own ids and map them back to the tool params per operation, ahead of the coercions that read them, so each operation keeps its own default. The other 17 duplicated ids agree on their value and are left shared. Adds tests covering each separated default plus a sweep asserting no id carries two different seeded values, so a future duplicate goes red. * fix(cloudflare): generate array include rules and allow bootstrapping a phase ruleset The Access policy include wand asked for a JSON object while the tool parses the field with parseJsonArrayParam, so generated rules failed validation. Switch it to json-array, whose prompt reinforcement omits the object braces. Rate limiting and WAF custom rules could only be appended to an existing ruleset, but a zone that has never had a rule in a phase has no entry point ruleset and returns 404, leaving no way to add the first rule. Add cloudflare_create_ruleset for the documented POST /zones/{id}/rulesets bootstrap, seeded with optional initial rules. * fix(cloudflare): correct verified API defects and stop filters leaking into writes Independent re-validation of all 48 tools against developers.cloudflare.com turned up defects that the shipped tools would have hit on their happy path. Delete DNS record reported every success as a failure. That endpoint is the one Cloudflare v4 response with no envelope — its documented body is `{"result":{"id":...}}` with no `success` — so `!data.success` was always true. Branch on an explicit `=== false` instead. The two replace-semantics PATCH endpoints could silently destroy live config. Update rate limit rule defaulted a missing action to `block`, converting an existing `log` or challenge rule into a hard block on real traffic; update ruleset rule left action and expression optional and had no `ratelimit` or `logging` passthrough, so updating a rate limiting rule stopped it rate limiting. Both now require the fields the replacement needs, and the ruleset rule carries the two nested objects through. Access applications were unbuildable for most types: `domain` was required, but it does not exist on the saas, app_launcher, warp, biso, dash_sso, infrastructure, mcp, mcp_portal, or proxy_endpoint request variants. The application type enum was also six values behind. Access group `is_default` is an array of rule objects, not a boolean. Purge cache merged every supplied target into one body, but the purge body is a one-of over the five target kinds; it now names the conflict instead. The remaining fixes are documentation drift: the priority field is MX and URI only (an SRV record carries priority inside its content), the certificate status filter documents only "all", the Worker tag filter takes tag:allowed pairs, and the managed-rule override list conflated the DDoS-only sensitivity_level with the WAF rule-level set. Separately, controls that share a subBlock id share one stored value, and `shouldSerializeSubBlock` short-circuits on `mode: 'advanced'` before it evaluates `condition` — so a hidden list filter was reaching a write. A `list_dns_records` content filter could overwrite a record's content, cache tags could be written onto a DNS record, and the zone status enum could reach the tunnel list, whose enum is disjoint. Filters that differ from the value they collided with now carry their own id, remapped through one table before any coercion. Sharings that mean the same thing everywhere are unchanged. Aliases are cleared by explicit assignment rather than destructuring, because the executor merges the mapper's output over the raw inputs and a merely omitted key survives as its raw subBlock string. The tests assert on that merged result, and three mechanical invariants now go red on a new collision: no id spans a read filter and a written value, no dropdown id carries two option sets, and no hidden advanced control feeds an operation that cannot render it. That last one found the name filter reaching three list operations. * docs(cloudflare): point self_hosted_domains at its replacement Cloudflare deprecated the field in favour of destinations, which the tools already surface. The output stays — Cloudflare still returns it — but the description now says which one to read. * refactor(cloudflare): drop a dead exception from the empty-type guard create_dns_record now takes its record type from the recordType control, whose dropdown has no empty option, so the operation can never reach this guard with an empty type. Clear it unconditionally. * fix(cloudflare): point the canvas sentences at the renamed filter controls The list filters that were split off their write-side twin kept their old ids in canvasPresentation, so seven clauses referenced a control that is no longer visible for that operation — check:canvas-sentences catches exactly this, and a broken clause fails silently on the card rather than throwing. * fix(cloudflare): stop the rate limiting action defaulting on a replacing update Making action required on update_rate_limit_rule was only half the fix: the Action dropdown still seeded block for the update operation too, so an update that edited only the threshold kept sending block and converted a live log or challenge rule into a hard block — exactly the harm the required flag was meant to prevent. The update now has its own control with no seeded value, so the action is something the caller states rather than inherits. The certificate status filter also still offered Active and Pending, which Cloudflare does not document for that endpoint; the only documented value is all, and omitting it returns active packs. * fix(cloudflare): stop the Access replacements seeding a type and a decision Same class as the rate limiting action: both Access updates are full replacements, and the shared controls seeded self_hosted and allow for the update operations too. Editing only a policy's include rules would silently convert a live deny, bypass, or non_identity policy to allow — widening who gets in — and editing an application would rewrite what it IS. Each update now has its own required control with no seeded value, so the type and the decision are stated rather than inherited. Regression tests cover both, and the canvas sentence follows the renamed decision control.
1 parent cabd2e2 commit 8a44621

59 files changed

Lines changed: 10118 additions & 215 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/content/docs/en/integrations/cloudflare.mdx

Lines changed: 1166 additions & 7 deletions
Large diffs are not rendered by default.

apps/sim/blocks/blocks/cloudflare.ts

Lines changed: 1965 additions & 163 deletions
Large diffs are not rendered by default.

apps/sim/lib/integrations/integrations.json

Lines changed: 144 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"updatedAt": "2026-08-16",
2+
"updatedAt": "2026-08-15",
33
"integrations": [
44
{
55
"type": "onepassword",
@@ -3811,8 +3811,8 @@
38113811
"type": "cloudflare",
38123812
"slug": "cloudflare",
38133813
"name": "Cloudflare",
3814-
"description": "Manage DNS, domains, certificates, and cache",
3815-
"longDescription": "Integrate Cloudflare into the workflow. Manage zones (domains), DNS records, SSL/TLS certificates, zone settings, DNS analytics, and cache purging via the Cloudflare API.",
3814+
"description": "Manage DNS, WAF, Zero Trust access, and edge infrastructure",
3815+
"longDescription": "Integrate Cloudflare into the workflow. Manage zones (domains), DNS records, SSL/TLS certificates, zone settings, DNS analytics, and cache purging. Configure WAF rulesets, managed rule overrides, and rate limiting rules through the current Rulesets engine. Administer Cloudflare Access (Zero Trust) applications, policies, groups, identity providers, and service tokens, and inspect R2 buckets, Workers scripts and routes, and Cloudflare Tunnels.",
38163816
"bgColor": "#F5F6FA",
38173817
"iconName": "CloudflareIcon",
38183818
"docsUrl": "https://docs.sim.ai/integrations/cloudflare",
@@ -3868,9 +3868,149 @@
38683868
{
38693869
"name": "Purge Cache",
38703870
"description": "Purges cached content for a zone. Can purge everything or specific files/tags/hosts/prefixes."
3871+
},
3872+
{
3873+
"name": "List Rulesets",
3874+
"description": "Lists every ruleset defined on a zone across all phases (WAF custom rules, managed rules, rate limiting, transform rules, and more). The list response deliberately omits the rules inside each ruleset — use \"Get Ruleset\" to read them. Requires an API token with Zone WAF Read (or another matching ruleset Read permission)."
3875+
},
3876+
{
3877+
"name": "Get Ruleset",
3878+
"description": "Reads a single zone ruleset including every rule it contains, in evaluation order. Requires an API token with Zone WAF Read (or another matching ruleset Read permission)."
3879+
},
3880+
{
3881+
"name": "Get Phase Entry Point Ruleset",
3882+
"description": "Reads the entry point ruleset for a phase on a zone, including all of its rules. This is how you find the ruleset ID you need before adding, updating, or deleting a rule — for example http_request_firewall_custom for WAF custom rules, http_request_firewall_managed for managed-ruleset deployments and overrides, or http_ratelimit for rate limiting rules. Requires an API token with Zone WAF Read (or another matching ruleset Read permission)."
3883+
},
3884+
{
3885+
"name": "Create Ruleset",
3886+
"description": ""
3887+
},
3888+
{
3889+
"name": "Create Ruleset Rule",
3890+
"description": "Adds a rule to a zone ruleset. Use \"Get Phase Entry Point Ruleset\" first to find the ruleset ID for the phase you want (for example http_request_firewall_custom for a WAF custom rule, or http_request_firewall_managed with action \"execute\" to deploy a managed ruleset). The rule is appended to the end of the ruleset unless a position is given. Requires an API token with Zone WAF Edit (or another matching ruleset Write permission)."
3891+
},
3892+
{
3893+
"name": "Update Ruleset Rule",
3894+
"description": "Updates a rule in a zone ruleset. Cloudflare replaces the rule definition rather than merging it, so you must send every field you want the rule to keep — any field you omit is reset to its default. Read the current rule with \"Get Ruleset\" first. Requires an API token with Zone WAF Edit (or another matching ruleset Write permission)."
3895+
},
3896+
{
3897+
"name": "Delete Ruleset Rule",
3898+
"description": "Permanently deletes a rule from a zone ruleset. This takes effect immediately on live traffic and cannot be undone — deleting a WAF custom rule, a managed-ruleset deployment, or a rate limiting rule removes that protection from the zone. Also use this to delete rate limiting rules, which live in the http_ratelimit phase ruleset. Requires an API token with Zone WAF Edit (or another matching ruleset Write permission)."
3899+
},
3900+
{
3901+
"name": "List Managed Ruleset Overrides",
3902+
"description": "Lists the WAF managed rulesets deployed on a zone together with the overrides applied to each one. Cloudflare has no dedicated overrides endpoint — overrides live on the \"execute\" rules of the http_request_firewall_managed phase entry point ruleset, which this reads. Requires an API token with Zone WAF Read."
3903+
},
3904+
{
3905+
"name": "List Rate Limiting Rules",
3906+
"description": "Lists the rate limiting rules on a zone by reading the http_ratelimit phase entry point ruleset. This uses the current Rulesets-based rate limiting API; the legacy rate_limits endpoint is no longer available. The returned ruleset ID is what \"Create Rate Limiting Rule\", \"Update Rate Limiting Rule\", and \"Delete Ruleset Rule\" need. Requires an API token with Zone WAF Read."
3907+
},
3908+
{
3909+
"name": "Create Rate Limiting Rule",
3910+
"description": "Creates a rate limiting rule in the http_ratelimit phase entry point ruleset of a zone, using the current Rulesets-based rate limiting API (the legacy rate_limits endpoint is no longer available). Run \"List Rate Limiting Rules\" first to get the ruleset ID. Requires an API token with Zone WAF Edit."
3911+
},
3912+
{
3913+
"name": "Update Rate Limiting Rule",
3914+
"description": "Updates a rate limiting rule in the http_ratelimit phase entry point ruleset of a zone, using the current Rulesets-based rate limiting API. Cloudflare replaces the rule definition rather than merging it, so send the complete rule — every field you omit is reset. Run \"List Rate Limiting Rules\" first to read the current definition and get the ruleset ID. Requires an API token with Zone WAF Edit."
3915+
},
3916+
{
3917+
"name": "List Access Applications",
3918+
"description": "Lists the Cloudflare Access (Zero Trust) applications protecting an account. Requires an API token with Account Access: Apps and Policies Read."
3919+
},
3920+
{
3921+
"name": "Get Access Application",
3922+
"description": "Reads a single Cloudflare Access (Zero Trust) application, including its attached policies. Requires an API token with Account Access: Apps and Policies Read."
3923+
},
3924+
{
3925+
"name": "Create Access Application",
3926+
"description": "Creates a Cloudflare Access (Zero Trust) application that puts an identity check in front of a hostname. Until at least one policy is attached the application denies everyone, so pair this with \"Create Access Policy\". Requires an API token with Account Access: Apps and Policies Edit."
3927+
},
3928+
{
3929+
"name": "Update Access Application",
3930+
"description": "Updates a Cloudflare Access (Zero Trust) application. This replaces the application definition rather than merging it, so send every field the application should keep — anything you omit reverts to its default, which can widen or break access. Read the current configuration with \"Get Access Application\" first. Requires an API token with Account Access: Apps and Policies Edit."
3931+
},
3932+
{
3933+
"name": "Delete Access Application",
3934+
"description": "Permanently deletes a Cloudflare Access (Zero Trust) application and every policy attached to it. The hostname it protected is immediately left without an Access identity check, so anyone who can reach it can reach the origin. This cannot be undone. Requires an API token with Account Access: Apps and Policies Edit."
3935+
},
3936+
{
3937+
"name": "List Access Policies",
3938+
"description": "Lists the Cloudflare Access (Zero Trust) policies attached to an application, in precedence order. Requires an API token with Account Access: Apps and Policies Read."
3939+
},
3940+
{
3941+
"name": "Create Access Policy",
3942+
"description": "Creates a Cloudflare Access (Zero Trust) policy on an application, deciding who may reach it. A policy takes effect on live traffic as soon as it is created — an allow policy with a broad include rule grants access immediately. Requires an API token with Account Access: Apps and Policies Edit."
3943+
},
3944+
{
3945+
"name": "Update Access Policy",
3946+
"description": "Updates a Cloudflare Access (Zero Trust) policy on an application. This replaces the policy definition rather than merging it, so send every rule the policy should keep — omitted exclude or require rules are dropped, which can widen who gets in. The change applies to live traffic immediately. Read the current policy with \"List Access Policies\" first. Requires an API token with Account Access: Apps and Policies Edit."
3947+
},
3948+
{
3949+
"name": "Delete Access Policy",
3950+
"description": "Permanently deletes a Cloudflare Access (Zero Trust) policy from an application. This changes who can reach the application the moment it runs: removing an allow policy locks out everyone it covered, and removing a deny or require policy drops that restriction. This cannot be undone. Requires an API token with Account Access: Apps and Policies Edit."
3951+
},
3952+
{
3953+
"name": "List Access Groups",
3954+
"description": "Lists the reusable Cloudflare Access (Zero Trust) groups in an account. Groups bundle identity rules that policies can reference by ID. Requires an API token with Account Access: Organizations, Identity Providers, and Groups Read."
3955+
},
3956+
{
3957+
"name": "List Access Identity Providers",
3958+
"description": "Lists the identity providers configured for Cloudflare Access (Zero Trust) in an account, such as Okta, Entra ID, Google Workspace, or a one-time PIN. Use the returned IDs to restrict an application with allowed_idps. Requires an API token with Account Access: Organizations, Identity Providers, and Groups Read."
3959+
},
3960+
{
3961+
"name": "List Access Service Tokens",
3962+
"description": "Lists the Cloudflare Access (Zero Trust) service tokens in an account, which let machines authenticate to Access-protected applications. Client secrets are never returned by this endpoint — only on creation. Requires an API token with Account Access: Service Tokens Read."
3963+
},
3964+
{
3965+
"name": "Create Access Service Token",
3966+
"description": "Creates a Cloudflare Access (Zero Trust) service token so a machine can authenticate to Access-protected applications. This is the only response that ever contains the client secret — Cloudflare will not return it again, so capture it in the same run. Requires an API token with Account Access: Service Tokens Edit."
3967+
},
3968+
{
3969+
"name": "Revoke Access Service Token",
3970+
"description": "Permanently deletes a Cloudflare Access (Zero Trust) service token, revoking it. Every machine or integration still presenting that client ID and secret is locked out of the Access-protected applications immediately, and the secret cannot be recovered. This cannot be undone. Requires an API token with Account Access: Service Tokens Edit."
3971+
},
3972+
{
3973+
"name": "List R2 Buckets",
3974+
"description": "Lists the R2 object storage buckets in an account. Requires an API token with Account Workers R2 Storage Read."
3975+
},
3976+
{
3977+
"name": "Get R2 Bucket",
3978+
"description": "Reads the metadata of a single R2 object storage bucket. Requires an API token with Account Workers R2 Storage Read."
3979+
},
3980+
{
3981+
"name": "Create R2 Bucket",
3982+
"description": "Creates an R2 object storage bucket in an account. The location hint and jurisdiction are fixed at creation and cannot be changed later. Requires an API token with Account Workers R2 Storage Edit."
3983+
},
3984+
{
3985+
"name": "Delete R2 Bucket",
3986+
"description": "Permanently deletes an R2 object storage bucket. Cloudflare only deletes an empty bucket, and the deletion cannot be undone. Requires an API token with Account Workers R2 Storage Edit."
3987+
},
3988+
{
3989+
"name": "List Worker Scripts",
3990+
"description": "Lists the Workers scripts deployed in an account. Requires an API token with Account Workers Scripts Read."
3991+
},
3992+
{
3993+
"name": "Get Worker Script Settings",
3994+
"description": "Reads the deployment settings of a single Workers script — bindings, compatibility date and flags, limits, observability, placement, and tail consumers. The plain \"get script\" endpoint in the Cloudflare API returns raw JavaScript source rather than JSON, so this settings endpoint is the structured way to inspect one script. Requires an API token with Account Workers Scripts Read."
3995+
},
3996+
{
3997+
"name": "List Worker Routes",
3998+
"description": "Lists the Workers routes on a zone, showing which URL patterns are handled by which Worker script. Unlike the Workers script endpoints, routes are zone-scoped. Requires an API token with Zone Workers Routes Read."
3999+
},
4000+
{
4001+
"name": "List Tunnels",
4002+
"description": "Lists the Cloudflare Tunnels (cloudflared) in an account, with their health status and active connections. Requires an API token with Account Cloudflare Tunnel Read."
4003+
},
4004+
{
4005+
"name": "Get Tunnel",
4006+
"description": "Reads a single Cloudflare Tunnel (cloudflared), including its health status and active connector connections. Requires an API token with Account Cloudflare Tunnel Read."
4007+
},
4008+
{
4009+
"name": "Get Tunnel Configuration",
4010+
"description": "Reads the configuration of a remotely-managed Cloudflare Tunnel — its ingress rules, origin request settings, and WARP routing. Only tunnels whose configuration source is \"cloudflare\" have a remote configuration; locally-managed tunnels keep it in their own config file. Requires an API token with Account Cloudflare Tunnel Read."
38714011
}
38724012
],
3873-
"operationCount": 13,
4013+
"operationCount": 48,
38744014
"triggers": [],
38754015
"triggerCount": 0,
38764016
"authType": "api-key",

0 commit comments

Comments
 (0)