Cloudflare OS and the Shadow AI Problem: Four Controls That Actually Hold

·11 min read·Evergreen Tools Team

On October 19, 2026, Cloudflare announced an expanded partner ecosystem around Cloudflare OS, its AI workspace. The parts that matter operationally: one-click integrations with systems of record including Asana, Box, Canva, ClickHouse, GitHub, Gong and Notion; deployment partners in EPAM, HappyCog and Towards AI; and a platform that runs on Cloudflare's infrastructure rather than on employee laptops, with AI Gateway built in to route work to an appropriate model and keep token bills visible. The problem statement in the release is the honest part, and it is not a Cloudflare-specific problem: employees already use AI on their own, sensitive files end up in systems nobody approved, AI-generated code ships without review, and costs climb without anyone tracking them. IT is left choosing between restricting access and losing the productivity, or allowing everything and dealing with the fallout.

Glowing connections over a dark globe

One-click connectors are convenient in exactly the proportion they are risky.

Shadow AI is an inventory problem before it is a policy problem

Every governance programme I have seen fail, failed at the same step: writing a policy for a population it had never counted. Before you can decide which AI tools are acceptable, you need to know which endpoints your machines actually talk to, how much data leaves, and who is sending it.

Egress logs give you that in an afternoon, and the metric to sort by is not request count. A vendor with ten requests and two gigabytes uploaded is a bigger problem than one with ten thousand small requests. Sort by bytes out, then look at the user distribution: a single user uploading heavily to an unapproved host is a conversation, not a policy.

This is also the number that makes the business case. When someone asks why a managed workspace is worth paying for, "eleven unapproved AI vendors received 4.2 GB from finance last month" is a stronger argument than any framework diagram.

# You cannot govern what you have not inventoried. Start with egress logs: which
# AI endpoints your fleet actually talks to, and who the top talkers are.

import json
from collections import Counter, defaultdict

AI_SUFFIXES = (
    "openai.com", "anthropic.com", "generativelanguage.googleapis.com",
    "openrouter.ai", "huggingface.co", "mistral.ai", "cohere.ai",
    "deepseek.com", "moonshot.cn", "bigmodel.cn",
)

def is_ai(host: str) -> bool:
    return any(host.endswith(s) for s in AI_SUFFIXES)

requests = Counter()
bytes_out = Counter()
users = defaultdict(set)

for line in open("egress.jsonl"):
    r = json.loads(line)
    if not is_ai(r["host"]):
        continue
    requests[r["host"]] += 1
    bytes_out[r["host"]] += r.get("bytes_out", 0)
    users[r["host"]].add(r["user"])

for host, n in requests.most_common(15):
    print(f"{host:45} req={n:6} out={bytes_out[host]/1e6:8.1f}MB users={len(users[host])}")

# Bytes-out is the number that matters. A host with few requests and huge uploads
# is the one to look at first, not the one with the highest request count.

Connectors are the new firewall rules

One-click connectivity to Asana, Box, GitHub and Notion is genuinely useful and genuinely dangerous in the same proportion. The control that holds is least privilege expressed as data: which connector, which scopes, which teams, read or write. Expressed as data because a review then becomes a diff, and an exception gets a name attached to it.

Cloudflare's framing is that a workspace lets administrators connect those resources in minutes and that tools get no more data access than necessary. Notice that the second half is a scope list, not an instruction to a model. "Do not access more than necessary" is unenforceable as prose and enforceable as a scope set.

Start read-only. A write-capable connector to a system of record is a production change deployed by a prompt, and the rollback story is usually missing. Add write scope after you have watched a team's read usage for two weeks and know what normal looks like.

# Connectors are the new firewall rules. Write them as data, so a review is a diff
# and an exception has a name attached to it.

CONNECTORS = {
  "notion": {"scopes": ["read:pages"], "data_class": "internal",
             "allowed_teams": ["docs", "eng"], "write": False},
  "github": {"scopes": ["repo:read", "pr:create"], "data_class": "source",
             "allowed_teams": ["eng"], "write": True},
  "box":    {"scopes": ["read:folder:finance"], "data_class": "confidential",
             "allowed_teams": ["finance"], "write": False},
}

def validate(request):
    c = CONNECTORS.get(request["connector"])
    if not c:
        return "DENY: connector not approved"
    if request["team"] not in c["allowed_teams"]:
        return f"DENY: team {request['team']} not allowed for {request['connector']}"
    if request["write"] and not c["write"]:
        return "DENY: write not permitted on this connector"
    over = set(request["scopes"]) - set(c["scopes"])
    if over:
        return f"DENY: scopes not approved -> {sorted(over)}"
    return "ALLOW"

# Least privilege has to be enforced at the connector, not requested in a prompt.
# "Do not access more data than necessary" is a scope list, not an instruction.
print(validate({"connector": "box", "team": "eng", "write": False,
                "scopes": ["read:folder:finance"]}))

Token budgets need an owner, or they are just numbers

The release mentions AI Gateway routing work to the right model to curtail out-of-control token bills, which is the correct architectural instinct: put the enforcement point where the calls happen, not in a monthly report. But the enforcement mechanism only works if spend is attributed to a team that can be asked a question about it.

There are two failure modes and both are common. A hard stop with no owner produces an outage nobody expected, because the person who hit the cap did not know a cap existed. A soft cap with no alerting produces a bill nobody noticed, because the alert went to a channel nobody reads.

The version that works has three properties: attribution to a team, a named owner, and a fail-closed error the user can actually act on. A blocked request that says "eng budget exhausted, owner is X, request an increase here" is annoying. A blocked request that says "403" is an incident.

# Token budgets only work if they are enforced below the UI. Check at the gateway,
# attribute to a team, and fail closed with a clear error instead of a surprise bill.

from dataclasses import dataclass

@dataclass
class Budget:
    team: str
    monthly_usd: float
    hard_stop: bool = True

BUDGETS = {
    "eng":     Budget("eng", 900.0),
    "support": Budget("support", 250.0, hard_stop=False),
}

def check(team: str, spent_usd: float, requested_usd: float) -> dict:
    b = BUDGETS.get(team) or Budget(team, 0.0)
    projected = spent_usd + requested_usd
    if projected <= b.monthly_usd:
        return {"allow": True, "remaining": round(b.monthly_usd - projected, 2)}
    if b.hard_stop:
        return {"allow": False, "reason": f"{team} budget exhausted",
                "remaining": round(b.monthly_usd - spent_usd, 2)}
    return {"allow": True, "reason": "soft cap exceeded, alerting owner",
            "remaining": 0.0}

# The point of attribution is that somebody can be asked a question about it.
# A budget with no owner is a number, not a control.
print(check("eng", 880.0, 40.0), check("support", 240.0, 40.0))
Team of people working together around laptops

The pilot team is the control group. Skipping it turns rollout into an anecdote.

Reviewing AI-generated code without pretending to detect it

The release's list of problems includes code generated by AI being shipped without review. The instinct is to build a detector. Do not, because detecting authorship from source is unreliable, contentious, and gets worse as models improve at writing like the surrounding codebase.

Require attribution instead. A commit trailer naming the assistant is auditable, cheap, and does not depend on anyone's judgement about style. Then define privileged paths, migrations, infrastructure, IAM, and require a named human reviewer in the commit message for those. The rule is simple enough that a script can enforce it and a developer can satisfy it in ten seconds.

This is the same trade-off as most governance: make the honest path the easy path, and make the easy path visible. Reviewers who are told which commits involved an agent read them differently, and that difference is the actual control.

#!/usr/bin/env bash
# Review gate for AI-generated code. Do not try to detect "AI-ness" — nobody can.
# Require attribution instead, then allow policy by path and by author.

set -euo pipefail

BASE="${1:-origin/main}"

for sha in $(git rev-list "$BASE"..HEAD); do
  msg=$(git log -1 --format=%B "$sha")
  files=$(git show --pretty="" --name-only "$sha")

  # convention: a trailer, added by whoever ran the agent
  if ! grep -qi '^Assisted-by:.*ai' <<<"$msg"; then
    echo "WARN $sha no AI attribution trailer"
  fi

  # high-risk paths need a human reviewer named in the commit
  if grep -Eq '(\.sql$|migrations/|infra/|iam/)' <<<"$files"; then
    grep -qi '^Reviewed-by:' <<<"$msg" || {
      echo "BLOCK $sha touches privileged paths without Reviewed-by"; exit 1; }
  fi
done

echo "review gate passed"
# That is the whole mechanism: attribution is auditable, intent is not.

The rollout order is the control

The tempting rollout is company-wide enablement in week one, because the tooling makes it a checkbox. That is also how you end up in month three with no idea what a task costs, which workflows improved, or whether review quality moved. Enablement without measurement is a subscription, not a programme.

A pilot team with one measurable workflow gives you two numbers that matter: cost per completed task, and review time per change. If cost per task falls and review time holds, you have something to scale. If cost per task falls and review time doubles, you have moved the work rather than removed it, and you should know that before inviting the second team.

The partners named in the announcement, EPAM, HappyCog and Towards AI, are doing exactly this kind of translation work for enterprises, which is a useful signal about where the hard part actually is. It is not the connectors. It is deciding what a team should stop doing, and proving the replacement is better.

#!/usr/bin/env bash
# Rollout checklist. The order is the control: measure first, expand second.
set -euo pipefail
cat <<'EOF'
[ ] inventory egress to AI endpoints (hosts, bytes out, users)
[ ] pick one pilot team with a measurable workflow
[ ] allowlist connectors for that team only, least scope, read-only first
[ ] set a per-team token budget with a named owner and a hard stop
[ ] require AI attribution trailers + human review on privileged paths
[ ] run 2 weeks; record cost per task, review time, incident count
[ ] publish the numbers to the pilot team before inviting a second team
[ ] expand only if cost per task and incident count both hold
EOF

# The failure mode of every "AI enablement" programme is enabling everyone in week
# one and discovering in month three that nobody knows what a task costs.
Laptop screen showing code review with security warnings

Attribution is auditable. Detecting whether code smells like AI is not.

📌 Frequently Asked Questions

What did Cloudflare actually announce?

An expanded partner ecosystem for Cloudflare OS, its AI workspace: one-click integrations with systems of record including Asana, Box, Canva, ClickHouse, GitHub, Gong and Notion, deployment and professional-services partners including EPAM, HappyCog and Towards AI, and workspaces that run on Cloudflare infrastructure rather than employee laptops, with AI Gateway included for model routing and token-cost control.

Is this the same as blocking AI tools?

No. The release frames the problem as employees using AI tools on their own, which puts sensitive files in unapproved systems, ships unreviewed AI-generated code and lets costs climb untracked. The product direction is a governed workspace rather than a blanket block, because blocking access also blocks the productivity the business wants.

Do connectors get full access to the connected app?

Cloudflare's description is that the workspace connects to systems of record without granting the tool access to more data than is absolutely necessary. In practice that means scope-level control at the connector, which is why connector permissions should be expressed as reviewable data rather than as instructions in a prompt.

How do I measure whether an AI rollout worked?

Pick one workflow, one team, and two numbers: cost per completed task and review time per change. Record both before the pilot and after two weeks. Expanding to a second team only when both hold is the difference between a programme and an anecdote.

Should I try to detect AI-generated code?

Detection is unreliable and gets worse as models imitate the surrounding codebase. Require attribution instead with a commit trailer, then gate privileged paths such as migrations, infrastructure and IAM on a named human reviewer. That is auditable and enforceable by a script.