# The Stack Weekly, week 44: Patch Node, rotate your keys, and stop buying a load balancer per service

> Seven things worth your time from 19 to 25 October 2026, and three of them are security. Node.js patched a critical bug in its Brotli decoder, a poisoned LiteLLM release stole every key on the machines that pulled it, and a researcher dumped eleven unpatched Tailscale findings. The good news: ECS can now share one load balancer across 25 Fargate services, Claude on Bedrock now returns schema-guaranteed JSON, OpenAI shipped its answer to last week's decision models, and DuckDB 2.0 is out as a long-term release.

- 19–25 October 2026, published 26 October 2026 by Nicolas Dao (Cloudless Labs, Sydney)
- Judged against The Stack: https://cloudlesslabs.com/stack/brief.md
- Web page: https://cloudlesslabs.com/weekly/2026-w44/

## Node.js security release for the Brotli decoder (CVE-2026-111497), by Node.js and Google

- New. Verdict: No change to my pick. Medium risk.
- Pain: A bug in the compression library inside Node.js means a malicious server or client can crash, and possibly take over, any Node service that decompresses Brotli data: webhooks, fetched pages, uploads. You don't know which of your functions run a patched Node.
- Fix: Node.js 22.23.4, 24.22.1 and 26.12.1 fix it. Zip Lambdas on the managed runtime with automatic updates get the fix from AWS without a deploy; pinned functions and container images only get it when you act.
- Value: Critical on paper (9.2), hard to exploit on standard builds, and a free patch. Check your update mode today.
- Company: Node.js (OpenJS Foundation); Brotli by Google
- Product: Fixed upstream on 21 October 2026; CVE published 22 October
- In The Stack: [AWS Lambda](https://cloudlesslabs.com/stack/aws-lambda/index.md), [Lambda container images](https://cloudlesslabs.com/stack/aws-lambda-container-images/index.md), [Node.js](https://cloudlesslabs.com/stack/nodejs-javascript/index.md), [Cloudflare Workers](https://cloudlesslabs.com/stack/cloudflare-workers/index.md)

### What happened

On 21 October, Node.js shipped a security release for a bug in the Brotli decoder it bundles from Google (CVE-2026-111497, nicknamed "Broccoli2Shell"). A crafted Brotli stream makes the decoder keep writing into an 8-byte internal buffer instead of failing. Node.js rates it high; Google, as the authority for the CVE, scores it 9.2 out of 10. It's fixed in **Node.js 22.23.4, 24.22.1 and 26.12.1**, and in Brotli 1.2.1 (1.1.1 and 1.0.10 on older branches).

Node.js names the attack path plainly: data your app decompresses with `node:zlib`. The explainer site adds `fetch()` decoding `br` responses and the Express 5 body parsers. In practice, that's any webhook, upload or fetched page you don't control. The explainer also reports that the public proof of concept only crashes standard builds, and that no attacks had been seen as of 22 October. So this is a patch-today problem, not a panic.

### Where you stand depends on how you deploy

- **Zip Lambda on the managed runtime, update mode Auto** (the default): AWS patches the runtime, and your functions pick it up without a deploy. The catch is that I found no AWS bulletin or runtime release note saying the `nodejs22.x` and `nodejs24.x` runtimes already carry the fixed builds. Log `process.versions.node` from one function and you'll know.
- **Update mode "Function update":** you get the fix on your next deploy. Redeploy.
- **Update mode Manual** (a pinned runtime version): never patched. AWS documents Manual as a temporary rollback. Switch it back to Auto.
- **Container images, on Lambda or Fargate:** nothing patches itself. Rebuild on a base image with a fixed Node, redeploy every function, and check the version in the running code.
- **Node.js 20 and older:** the Node.js post lists only 22, 24 and 26. Move to 22 or 24.
- **Your own copies:** a native Brotli package in `node_modules`, or Python's `brotli`, is a separate fix (1.2.1).

On Cloudflare Workers, Cloudflare runs the runtime, so there's nothing for you to patch. It hasn't said which Brotli build `workerd` uses.

### The argument this settles

I stopped running Lambda as container images years ago ([the stack](https://cloudlesslabs.com/stack/aws-lambda-container-images/index.md)), partly because I was tired of upgrading Node on AWS's calendar. This week shows the other side of that complaint. On the managed runtime, AWS's calendar is the one that patches you. With an image, you own the Node binary and the Brotli inside it, and every function stays vulnerable until you rebuild it. For most teams, letting AWS own the patching is the better deal.

### My verdict

**No change to my pick:** zip Lambdas on the managed runtime, updates on Auto. The script in the examples below lists every Node.js function with its update mode. Run it today, fix the pinned ones, and rebuild your images. The risk is medium: critical on paper, hard to exploit, and mostly patched for you, unless you pinned something.

Sources:

- [Node.js: October 2026 security releases](https://nodejs.org/en/blog/vulnerability/october-2026-security-releases)
- [CVE-2026-111497 (CVE record)](https://cveawg.mitre.org/api/cve/CVE-2026-111497)
- [Lambda Developer Guide: runtime version updates](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-update.html)
- [Broccoli2Shell explainer (third-party)](https://broccoli2shell.com/)

## LiteLLM 1.107.0, a credential stealer published as an update, by LiteLLM (BerriAI)

- New. Verdict: Skip. High risk.
- Pain: A self-hosted LLM proxy holds every model provider key you own, on a box with a cloud role. One bad update, pulled automatically through a `latest` tag, hands all of it to someone else.
- Fix: Keep provider keys where no third-party package runs at install time: a managed gateway with scoped, spend-capped keys, and AWS Secrets Manager read at runtime. If you must self-host, pin by digest and hash.
- Value: The poisoned release was live for under three hours. If you pulled it on 21 October, rotate everything that machine could reach.
- Company: BerriAI: GitHub organisation since December 2022
- Product: LiteLLM, open source since July 2023; a proxy RCE advisory earlier in 2026
- In The Stack: [OpenRouter](https://cloudlesslabs.com/stack/openrouter/index.md), [Cloudflare AI Gateway](https://cloudlesslabs.com/stack/cloudflare-ai-gateway/index.md), [AWS Secrets Manager](https://cloudlesslabs.com/stack/aws-secrets-manager/index.md), [OpenCode](https://cloudlesslabs.com/stack/opencode/index.md)

### What happened

On 21 October, `litellm==1.107.0` was on PyPI from 11:39 to 13:23 UTC. On Docker Hub, the tags `v1.107.0`, `latest`, `main-latest` and `main-stable` pointed at a poisoned image from 11:46 to 14:20 UTC. The source tag on GitHub was clean: the release pipeline had been pointed at a lookalike repository.

LiteLLM's own post-mortem traces it back five months. A master key was committed to a public repository on 27 May and never revoked. On 10 October it returned a GitHub token belonging to an organisation owner, and that token was enough to override branch protection and repoint the release.

SafeDep's analysis describes the payload: files that run every time Python starts, including when you run `pip`. They steal cloud credentials, SSH keys, other processes' environment variables, the instance role and **every provider key stored in the proxy's database**, and install a backdoor that fetches new code every hour.

That last part is what makes this worse than an ordinary bad package. A self-hosted LLM proxy exists to hold all your model keys in one place.

### The same week, the same lesson, twice more

- **Railway** disclosed on 21 October that customer environment variables had been accessed and some decrypted. Its advice: roll every variable.
- **OpenCode**: last week I couldn't confirm a reported breach. OpenCode's own post on X, quoted by StepSecurity, now describes a "large scale distillation attack" that hit 1.4% of OpenCode Console users with linked Anthropic, OpenAI and Google keys. I couldn't open the post itself, so rotate any key you ever linked to the Console. The OpenCode CLI, [my pick](https://cloudlesslabs.com/stack/opencode/index.md), wasn't affected according to its co-founder.

Three companies, one pattern: your keys sat in a system you don't run, next to everyone else's keys.

### If you ran LiteLLM

- Don't run `pip` in a suspect environment, because it triggers the payload. Look for `cook.pth`, `cook.py`, `sitecustomize.py` and a `litellm-1.107.0.dist-info` folder, and compare image digests with the bad one (the commands are in the examples below).
- Rotate everything that host could reach: its IAM role (and check CloudTrail for the role being used from outside your network), every provider key in the proxy's database plus its master and virtual keys, the database password, SSH keys and GitHub tokens.
- Pin images by digest and packages by hash. LiteLLM gives 1.106.2 as its safe version.

### Versus my pick

[OpenRouter](https://cloudlesslabs.com/stack/openrouter/index.md) has nothing for you to install or patch, and each key can carry its own spending limit. It costs 5.5% on credit purchases. [Cloudflare AI Gateway](https://cloudlesslabs.com/stack/cloudflare-ai-gateway/index.md)'s core features are free. A managed gateway is still a third party holding your traffic, but a self-hosted proxy only looks cheaper until an incident forces you to rotate every key you own.

This is how I'd wire it for a client: provider keys in [Secrets Manager](https://cloudlesslabs.com/stack/aws-secrets-manager/index.md), read at runtime by the function's role, one OpenRouter key per application with a spending limit, and no key ever pasted into a hosted console.

### My verdict

**Skip** self-hosting LiteLLM unless data residency forces you to. If it does, treat the proxy like the vault it is: pinned by digest, the smallest possible role, and outbound traffic limited to the model providers. The risk is high: anyone who pulled `latest` that day lost everything on the box.

Sources:

- [LiteLLM: security incident of 21 October 2026](https://docs.litellm.ai/blog/security-incident-oct-21-2026)
- [LiteLLM: the attack chain](https://docs.litellm.ai/blog/security-incident-oct-21-2026-attack-chain)
- [SafeDep: LiteLLM PyPI compromise analysis](https://safedep.io/litellm-pypi-compromise-credential-stealer-backdoor/)
- [Railway: security disclosure, 21 October](https://blog.railway.com/p/security-disclosure-wednesday-october-21st)
- [StepSecurity: the OpenCode Console breach](https://www.stepsecurity.io/blog/opencode-security-breach)
- [OpenRouter pricing](https://openrouter.ai/pricing)
- [Cloudflare AI Gateway pricing](https://developers.cloudflare.com/ai-gateway/reference/pricing/)

## Tailworm, eleven unpatched findings in Tailscale, by Cyberdione Labs (report) and Tailscale

- New. Verdict: No change to my pick. Medium risk.
- Pain: You put Tailscale on client servers so nobody opens an SSH port. A researcher now says one compromised laptop in the tailnet can turn Tailscale SSH and `tailscale serve` into root on those servers, and there's no fixed release yet.
- Fix: Subnet routers aren't in any finding. Update every node to 1.104.2 for this week's separate certificate fix, turn Tailscale SSH off where SSM or OpenSSH can do the job, and tighten the rules where you keep it.
- Value: The default setup (a subnet router into a VPC) stands. Tailscale SSH goes on hold until Tailscale ships a fix.
- Company: Tailscale (founded 2019)
- Product: Subnet routers: GA. Tailscale SSH and Serve: GA, and the source of most 2026 bulletins
- In The Stack: [Tailscale](https://cloudlesslabs.com/stack/tailscale/index.md), [Cloudflare Tunnel (cloudflared)](https://cloudlesslabs.com/stack/cloudflare-tunnel/index.md)

### What was published

On 25 October, Cyberdione Labs published "Tailworm", a report with 11 findings against Tailscale 1.104.2 and headscale 0.29.5. It was tested from 22 to 24 October and published without waiting for a fix. In groups:

- **Tailscale SSH** (findings 1 to 3 and 5): socket forwarding that runs as root, socket replacement, a race on the path allowlist, and an out-of-memory loop.
- **`tailscale serve` and Funnel** (7 to 11): the local API exposed to remote peers through a TCP forward, and guards checked only at configuration time.
- **App connectors** (4): approved users can pick any destination, including the cloud metadata address.
- **headscale** (6): a denial of service on the self-hosted control plane.

Most of them need an attacker who already has an approved account or a compromised device in your tailnet. None of them is a break-in from the internet, and **no finding names subnet routers**, the way I use Tailscale into client VPCs.

### Tailscale's answer so far

There's no bulletin and no fixed release as of 26 October. There is code: on 25 October, public pull requests (21939 to 21945) moved SSH socket forwarding into a monitor that runs as the user instead of root. That suggests Tailscale treats the SSH findings as real. It's also the same family of bugs as bulletins TS-2026-004 to -010 earlier this year. In 2026, Tailscale's bugs keep showing up in SSH and Serve, the features layered on top of the network, not in the network itself.

Separately, bulletin TS-2026-013 (21 October) covers leaked keys for eight eMudhra certificate authorities, fixed in 1.104.2. Update for that one regardless.

### What to do this week

- Update every node to 1.104.2 or later.
- List the machines running Tailscale SSH, and turn it off (`tailscale set --ssh=false`) wherever SSM Session Manager or OpenSSH can do the job. SSM needs no open port and is governed by IAM. Where you keep Tailscale SSH, allow non-root users only, with per-pair rules and check mode.
- Remove app connectors nobody uses, and require IMDSv2 with a hop limit of 1 on instances.
- Check your `serve` config for `unix:` targets.
- Use single-use or ephemeral auth keys, and rotate tagged devices on a schedule.
- On headscale, upgrade to 0.29.6.

### My verdict

**No change:** [Tailscale](https://cloudlesslabs.com/stack/tailscale/index.md) stays my default for subnet routers into client accounts. But I'd stop recommending Tailscale SSH on client servers until Tailscale ships a release with the fix. The risk is medium: low for a plain subnet router, real if you use SSH, Serve or app connectors.

Sources:

- [Tailworm report (Cyberdione Labs)](https://www.tailworm.com/)
- [Tailscale security bulletins](https://tailscale.com/security-bulletins)
- [Tailscale changelog](https://tailscale.com/changelog)
- [tailscale/tailscale PR 21942: SSH forwarding through a monitor](https://github.com/tailscale/tailscale/pull/21942)
- [Tailscale SSH documentation](https://tailscale.com/kb/1193/tailscale-ssh)
- [headscale v0.29.6](https://github.com/juanfont/headscale/releases/tag/v0.29.6)

## Amazon ECS Gateway, by AWS

- New. Verdict: Worth a pilot. Medium risk.
- Pain: Every Fargate service you put on the internet gets its own load balancer, about $32 a month in Sydney before a single request, or one shared load balancer whose listener rules and certificates you maintain by hand.
- Fix: Create one gateway, link it to the cluster, and give each service a three-line connector. ECS builds and runs the shared load balancer, the certificate and the routing rule: up to 25 services per load balancer.
- Value: The gateway is free. From the second service on, you stop paying for a load balancer each.
- Company: AWS
- Product: Available in all commercial Regions since 20 October 2026
- In The Stack: [Fargate services (always on)](https://cloudlesslabs.com/stack/aws-fargate-service/index.md), [Lambda function URLs](https://cloudlesslabs.com/stack/aws-lambda-function-urls/index.md), [API Gateway](https://cloudlesslabs.com/stack/aws-api-gateway/index.md)

### What it is

ECS Gateway (20 October) isn't a new kind of load balancer. It's ECS running ordinary Application Load Balancers in your account, so you don't have to. A platform person creates a gateway once (subnets, public or internal, optional WAF), links it to a cluster, and each service adds a connector that names its container port. ECS then creates the load balancer, a certificate, a generated hostname, a routing rule, a target group and a security group per service. It adds load balancers as each one fills up (25 services each) and deletes the empty ones.

### Before and after

- **Before:** each internet-facing [Fargate service](https://cloudlesslabs.com/stack/aws-fargate-service/index.md) gets its own ALB, listener, target group, certificate and security groups, wired by hand in IaC. Or you share one ALB and keep its rule priorities straight yourself.
- **After:** a three-line connector per service. A new service has a working HTTPS URL as soon as its route turns active.

### Cost

The gateway itself is free. In Sydney, an ALB is $0.0252 an hour ($18.40 a month), plus $5.84 a month for the minimum one capacity unit, plus two public IPv4 addresses in two zones ($7.30). That makes about $31.54 per load balancer per month (my arithmetic from the AWS price list):

| Services | One ALB each | With ECS Gateway |
|---|---|---|
| 1 | $31.54 | $31.54 |
| 5 | $157.70 | $31.54 |
| 25 | $788.50 | $31.54 |

Compute doesn't change, and capacity units follow traffic, so a busy set of services pays more than this. The saving is the fixed cost per load balancer, and it starts with the second service.

### The limits

- HTTPS on port 443 only, and routing by hostname only, not by path.
- The hostnames are generated (`*.ecs.<region>.on.aws`). Custom domains aren't documented, so keep a dedicated ALB for a client's branded domain until they are.
- Same VPC, same account, `awsvpc` networking only, and not for ECS Express Mode services.
- The gateway's subnets, scheme and listener can't be changed later. To change them, create a new gateway and move the services.
- Changing a health check replaces the target group, and that fails on rolling deployments. Set the health check when you first connect the service, or use blue/green.
- Up to 25 services share one load balancer, and its problems. Split gateways by environment.
- AWS says "all commercial Regions". Sydney isn't named on any page, but it's a commercial Region.

**Infrastructure as code:** CloudFormation has `AWS::ECS::Gateway` from day one. Terraform has no resource yet (issue 50573), and Pulumi's aws-native provider hasn't been confirmed. That matters to me: if it isn't in code, it doesn't exist.

### Versus the alternatives

[Lambda function URLs](https://cloudlesslabs.com/stack/aws-lambda-function-urls/index.md) still win for spiky traffic, because there's no hourly fee at all. [API Gateway](https://cloudlesslabs.com/stack/aws-api-gateway/index.md) still wins when you need auth, throttling or path routes. ECS Gateway is for the step I already recommend: when a Lambda is busy all day, move it to Fargate. That step just got cheaper from the second service on.

### My verdict

**Worth a pilot** on internal or non-critical services, through CloudFormation until Pulumi and Terraform catch up. The risk is medium: the saving is real and simple, but the feature is a week old, custom domains are missing, and 25 services share one failure point.

Sources:

- [What's New: Announcing Amazon ECS Gateway](https://aws.amazon.com/about-aws/whats-new/2026/10/announcing-amazon-ecs-gateway/)
- [AWS News Blog: Introducing Amazon ECS Gateway](https://aws.amazon.com/blogs/aws/introducing-amazon-ecs-gateway-a-fully-managed-ingress-solution-for-ecs-services)
- [ECS Developer Guide: Amazon ECS Gateway](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/gateway-overview.html)
- [ECS Developer Guide: connect services to a gateway](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/gateway-connect-service.html)
- [CloudFormation: AWS::ECS::Gateway](https://docs.aws.amazon.com/AWSCloudFormation/latest/TemplateReference/aws-resource-ecs-gateway.html)
- [terraform-provider-aws issue 50573](https://github.com/hashicorp/terraform-provider-aws/issues/50573)

## OpenAI Decisions API, by OpenAI

- Follow-up. Verdict: Watching. Medium risk.
- Pain: You still pay a chat model to answer yes-or-no questions inside your app (urgent, which team, spam), and last week's alternatives were a startup's preview and a model on Workers AI.
- Fix: OpenAI's Decisions API, generally available since 22 October, takes your data and typed questions and returns probabilities and a confidence, with output free. It's the incumbent's version of the category.
- Value: The safe-vendor option, at about 2.4 times Jev's price and with no Australian processing. My starting point doesn't change.
- Company: OpenAI
- Product: Decisions API: beta on 6 October, GA on 22 October 2026, one model (gpt-6-luna)
- In The Stack: [Cloudflare Workers AI](https://cloudlesslabs.com/stack/cloudflare-workers-ai/index.md), [OpenRouter](https://cloudlesslabs.com/stack/openrouter/index.md)

### What changed since last week

Last week's item was a category that didn't exist in August: decision models. TypeSafe's Jev started it, and Cloudflare's open-weight Clef answered it. This week, **OpenAI's Decisions API reached general availability (22 October)**, sixteen days after its beta. One endpoint takes your text, images or JSON plus typed questions: a *predicate* (yes or no, as a probability), a *choice* (one of your labels) or a *score* (a position on your scale). It returns probabilities and a confidence, never prose. There's one model, `gpt-6-luna`.

So the category now has a startup's preview, open weights from Cloudflare and a GA product from the biggest vendor in AI, all within six weeks.

### Price and residency

| | Input, per million tokens | Output | Status |
|---|---|---|---|
| OpenAI Decisions | $0.10 | free | GA |
| Jev (TypeSafe AI) | $0.042 | not charged | early access |
| Clef Flash (Workers AI) | $0.038 | free | open weights |

OpenAI costs about 2.4 times Jev and 2.6 times Clef Flash. In absolute terms all three are tiny: 10 million tokens of support tickets is about $1 at OpenAI's price. The real differences are elsewhere. OpenAI's regional processing covers only the US and Europe, not Australia. Zero data retention is available to eligible customers. It's not on OpenRouter, so my default gateway can't reach it. And its Node SDK already made breaking changes to the Decisions types on the day of GA.

### Jev's bad week

A third-party report on 24 October says a leak exposed every Jev customer's outputs. I found no post from TypeSafe itself. Jev is still early access, so the "play, don't marry" rule I gave it last week already covers this. Until TypeSafe publishes what happened, don't send it personal data.

### My verdict

**Still watching**, and my starting point doesn't change: **start with Clef Flash; open weights are your exit.** Use OpenAI Decisions if the app already runs on OpenAI and needs zero data retention or a US or European region. Whichever you pick, put the call behind your own `predicate / choice / score` interface: the three APIs have the same shape, so switching is a thin adapter. The risk is medium: a mature vendor, but a sixteen-day-old product with one proprietary model and no published deprecation policy.

Sources:

- [OpenAI: Decisions guide](https://developers.openai.com/api/docs/guides/decisions)
- [OpenAI API changelog](https://developers.openai.com/api/docs/changelog)
- [openai-node releases](https://github.com/openai/openai-node/releases)
- [TypeSafe AI](https://typesafe.ai)
- [Workers AI: Clef Flash](https://developers.cloudflare.com/workers-ai/models/clef-flash/)
- [Startup Fortune: Jev leak report (third-party)](https://startupfortune.com/typesafe-ai-admits-a-leak-exposed-every-customers-jev-model-output/)

## Structured outputs for Claude on Amazon Bedrock, by AWS

- Update. Verdict: Worth a pilot. Low risk.
- Pain: You ask Claude for JSON, strip the code fences, parse it in a try/catch, validate it, and retry when a field is missing. The pipeline still breaks now and then, and every retry costs tokens and seconds.
- Fix: Bedrock now constrains Claude's output to your JSON Schema, so the response always parses and always has the shape you asked for. Through the AU inference profile, the request stays in Sydney and Melbourne.
- Value: The parse-validate-retry layer goes away, for Claude data that stays in Australia, at no extra charge I could find.
- Company: AWS and Anthropic
- Product: Generally available since 20 October 2026, every Claude model, Converse and InvokeModel
- In The Stack: [Amazon Bedrock](https://cloudlesslabs.com/stack/aws-bedrock/index.md), [Anthropic API (for client systems)](https://cloudlesslabs.com/stack/anthropic-api/index.md), [Cloudflare Workers AI](https://cloudlesslabs.com/stack/cloudflare-workers-ai/index.md)

### What it is

Since 20 October, Bedrock supports **structured outputs for every Claude model**, through Converse, ConverseStream and InvokeModel. It isn't "JSON mode", which only promises valid JSON. Bedrock compiles your JSON Schema into a grammar and only lets the model produce tokens that fit it, so the response always parses, with the types, required fields and enum values you declared. You pass the schema in `outputConfig.textFormat`, or set `strict: true` on a tool.

### Before and after

- **Before:** a prompt that begs for JSON, code to strip fences, `JSON.parse` in a try/catch, a Zod check and a retry loop, and still the occasional missing field at 3 a.m.
- **After:** pass the schema and parse the result. You still check `stopReason` (a truncated answer isn't valid JSON) and still validate business rules in code, because some keywords aren't supported.

### For Australian clients

Sonnet 5.5 is available through the AU inference profile `au.anthropic.claude-sonnet-5-5`, which only routes to Sydney and Melbourne. Bedrock's docs say cross-Region profiles support structured outputs with no extra setup. No page names the AU profile specifically, so make one test call before you promise it to a client. The price: regional profiles cost 10% over Anthropic's list price, so Sonnet 5.5 is about $2.20 / $11 per million tokens (my arithmetic). I found no surcharge for structured outputs.

The [Anthropic API](https://cloudlesslabs.com/stack/anthropic-api/index.md) has had the same feature for a while, at the same list price, with no Australian residency. So the capability is equal, and the choice between them is about where the data goes. That's exactly why [Bedrock](https://cloudlesslabs.com/stack/aws-bedrock/index.md) is on my list.

### Gotchas, with fixes

- **The first request with a new schema compiles a grammar, for up to about 90 seconds.** Set your client timeout to at least 120 seconds and warm each schema at deploy time. The cache lasts 24 hours, so a schema used once a day recompiles every day.
- **Unsupported keywords return a 400:** `minimum`/`maximum`, `minLength`/`maxLength`, `multipleOf`, recursion. Enforce those in code after parsing.
- **Every object needs `additionalProperties: false`,** and a schema can have at most 12 optional properties and 16 union-typed ones. Make fields required and nullable, or split a big extraction into several calls.
- **In Converse, the schema is a JSON string;** in InvokeModel, it's an object.
- **Citations and structured outputs together return a 400.** Use one or the other.

### Cloudflare's Clairic, the same day

On 19 October, Cloudflare released Clairic in preview on Workers AI: a model that only extracts typed values from text and images, at $0.10 per million input tokens with no output charge. Cloudflare claims 49% lower cost per pipeline, but that comes from its own test on one claims workflow, against GLM-5.3 rather than Claude. No Region is stated, so it's not an option for Australian client data yet. It's interesting, and I'm watching it.

### My verdict

**Worth a pilot** for document and form extraction where the data must stay in Australia. Bedrock itself stays on watch in [the stack](https://cloudlesslabs.com/stack/aws-bedrock/index.md) until I've run it in production. For an Australian client extracting data from documents, this is where I'd start now: Claude through the AU profile, a schema per document type, and the business rules in code. The risk is low: a GA feature from mature vendors, and the only gap is one test call on the AU profile.

Sources:

- [What's New: structured outputs for Claude on Bedrock](https://aws.amazon.com/about-aws/whats-new/2026/10/claude-structured-outputs-bedrock/)
- [Bedrock User Guide: get validated JSON results](https://docs.aws.amazon.com/bedrock/latest/userguide/structured-output.html)
- [Bedrock User Guide: structured outputs for Claude](https://docs.aws.amazon.com/bedrock/latest/userguide/claude-messages-structured-outputs.html)
- [Bedrock: Claude Sonnet 5.5 model card](https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-sonnet-5-5.html)
- [Anthropic: structured outputs](https://platform.claude.com/docs/en/build-with-claude/structured-outputs)
- [Workers AI: Clairic](https://developers.cloudflare.com/workers-ai/models/clairic/)
- [Cloudflare blog: Clairic](https://blog.cloudflare.com/clairic-extraction-model/)

## DuckDB 2.0.0 LTS, by DuckDB Foundation

- Update. Verdict: No change to my pick. Medium risk.
- Pain: A Lambda that needs a few rows from your Parquet lake either waits seconds on an Athena query loop, or runs DuckDB 1.x, which reads many small files on S3 one at a time.
- Fix: DuckDB 2.0 reads S3 with asynchronous I/O and prunes Parquet better, and it's a long-term release supported until October 2027.
- Value: The DuckDB-in-a-Lambda pattern gets a long-lived version. Its database files are one-way, and 1.4 LTS ends on 17 November.
- Company: DuckDB Foundation (non-profit, MIT); the core team at DuckLabs joined AWS on 1 September 2026
- Product: 2.0.0 LTS released 21 October 2026, supported until October 2027
- In The Stack: [DuckDB](https://cloudlesslabs.com/stack/duckdb/index.md), [S3 + Glue + Athena](https://cloudlesslabs.com/stack/aws-athena-data-lake/index.md)

### What changed

DuckDB 2.0.0 shipped on 21 October as the project's second long-term-support release, supported until at least October 2027. For anyone querying a lake on S3, the headline is **asynchronous I/O**: DuckDB fetches row groups in parallel instead of one request at a time. Parquet pruning and partitioned writes are better too. It adds a `VARIANT` type for semi-structured data and Quack, a protocol that lets one DuckDB serve others. The Node client, `@duckdb/node-api` 2.0.0-r.1, was published the same day.

DuckDB calls the S3 change "dramatically faster", and publishes no number. Benchmark 1.5 against 2.0 on your own queries, in the same Lambda, before you repeat the claim.

### Before and after

- **Before:** a Lambda that needs a few rows from the lake calls Athena (billed at least 10 MB a query, a start-poll-fetch loop, seconds of latency), or runs DuckDB 1.x, held back by synchronous reads on many small files.
- **After:** one `SELECT` over `s3://` inside the function, with no polling and parallel reads. As a rough illustration (my arithmetic, not measured): a query that scans 1 GB in 10 seconds on a 2 GB Arm Lambda in Sydney costs about $0.00027 of compute, against $0.005 on Athena. Athena still wins the big scans, because one Lambda tops out at 15 minutes and 10 GB of memory.

That's still my position in [the stack](https://cloudlesslabs.com/stack/duckdb/index.md): DuckDB next to [Athena](https://cloudlesslabs.com/stack/aws-athena-data-lake/index.md), not instead of it.

### Gotchas, with fixes

- **Database files are one-way.** A `.duckdb` file written by 2.0 can't be opened by 1.x. If a build job moves to 2.0 while a Lambda layer stays on 1.x, the Lambda breaks. Upgrade every reader before any writer. (Parquet files aren't DuckDB database files, but test your own anyway.)
- **Extensions are built per version,** so a layer that bundles 1.x `httpfs` or `iceberg` won't load them on 2.0. Rebuild the layer with the 2.0 binary.
- **Single-arrow lambdas** (`x -> x + 1`) are off by default and disappear in 2.1. Rewrite them as `LAMBDA x : x + 1`.
- **1.4 LTS ends on 17 November 2026.** If you're on it, you have three weeks: go to 1.5 (supported until March 2027) now, or plan 2.0.
- **Quack listens on localhost by default.** Don't expose it from a Lambda or a container without TLS and authentication in front.

### Who owns it

The DuckDB Foundation, a non-profit, still holds the MIT-licensed project. The core team at DuckLabs joined AWS on 1 September. The 2.0 release notes say nothing about governance, and I've seen no sign of DuckDB inside Athena or Glue yet.

### My verdict

**No change:** DuckDB stays my pick for Parquet queries inside a Lambda. Keep production on 1.5 for a few weeks, and move to 2.0 after its first patch release, all readers first and the layer rebuilt. The risk is medium: a five-day-old major release with a new storage format, softened by long-term support until October 2027.

Sources:

- [Announcing DuckDB 2.0.0 LTS](https://duckdb.org/2026/10/21/announcing-duckdb-200.html)
- [DuckDB v2.0.0 on GitHub](https://github.com/duckdb/duckdb/releases/tag/v2.0.0)
- [DuckDB release calendar](https://duckdb.org/release_calendar.html)
- [A preview of DuckDB 2.0](https://duckdb.org/2026/08/17/duckdb-20-highlights.html)
- [DuckDB docs: Node client](https://duckdb.org/docs/2.0/clients/node_neo/overview.html)
- [DuckDB docs: S3 API](https://duckdb.org/docs/2.0/core_extensions/httpfs/s3api.html)

## Looked at, skipped

- [Kimi K3.1 Pro](https://artificialanalysis.ai/models/kimi-k3-1-pro) (Moonshot AI): The best model of the week on paper: 55 on Artificial Analysis's index against 56 for Claude Sonnet 5.5, at Sonnet's own price of $2 / $10 per million tokens. Moonshot is its only host, it runs at under half Sonnet's speed, and the open weights are promised by 4 November. Same price isn't a story; I'll look again when other hosts serve the weights.
- MiniMax M3.1 Flash (MiniMax): Scores 43 at about $0.22 a task. DeepSeek V4.1 Flash, last week's pick for bulk work, scores 45 for $0.26 with MIT weights. No reason to switch.
- Lambda Web Functions, one week on (AWS): Still five Regions and still no Sydney. Nothing changed since last week, so it stays on watch.
- Lambda MicroVMs in four more Regions (AWS): Fourteen Regions now, and Sydney still isn't one of them.
- CloudWatch Omni in Sydney (AWS): AI-assisted troubleshooting over your logs and traces, now in Sydney, with no price on the launch page. My problem with CloudWatch is the bill, and a chat window on top of it doesn't touch the bill.
- Agent Skills donated to the Agentic AI Foundation (Anthropic and the Linux Foundation): Disclosure: I build HappySkills on this format, so I'm glad it's under neutral governance. Nothing for you to change this week.
- Cloudflare MCP servers drop /sse (Cloudflare): Old client configs pointing at `/sse` now fail. Change them to `/mcp` and move on.
- Cloudflare Tunnel normalises URLs before ingress matching (Cloudflare): Becomes the default on 25 January 2027. If your Tunnel routes by path, test your rules before then.
- createAI replaces the Workers AI and AI Gateway providers (Cloudflare): Model fallback becomes one parameter and the migration is a rename. Good for AI Gateway, which stays on my watch list.
- AgentCore Gateway model fallback (AWS): A headline with no price, caching or limits behind it yet. Cloudflare AI Gateway already does this for free.
- Resend Forge and Inboxes (Resend): Resend's own mail server and an inbox API for agents, with vendor claims and no published price. Resend stays my pick; I'll test before I repeat the deliverability claims.
- OAuth provider for Managed Better Auth (beta) (Neon): Useful for MCP servers, but Neon Auth still has no SSO or MFA, which is what I'm waiting for.
- Better Auth 1.7.13 and 1.8.0-beta.2 (Better Auth): No new advisory this week. Stay on 1.7.x, never below 1.7.7, and leave the 1.8 beta alone.
- PHP2Shell (CVE-2026-109618 and 109619) (PHP): Not my runtime, but if you host Moodle or WordPress for clients, patch today: PHP 8.2.35, 8.3.36, 8.4.27 or 8.5.12. Lambda has no managed PHP runtime, so custom runtimes and images need a rebuild.
- tsrs, a Rust port of TypeScript (Cloudflare and partners): "Up to 8x faster" is the vendor's own benchmark, and TypeScript 7 is already getting faster on its own. Wait for it to ship.
- GitHub package firewall (preview) (GitHub): Enterprise Cloud only. Its cooldown idea is the one to copy anywhere: don't install a release in its first days. LiteLLM's poisoned version was gone in under three hours.
- Claude Security continuous scanning (Anthropic): Enterprise seats with token billing, and not available with zero data retention. Not for small teams yet.
- Google Agent Compute Engine, Data Agent Kit, and OpenAI and Claude on Google Cloud (Google): Fine products on a platform I don't put clients on. Cloudflare Sandbox stays my pick for running code nobody reviewed.
- PGX (PGX): Thousands of throwaway Postgres databases in one process, says the launch post. No docs or repository I could open, and every number is the launcher's own. A launch tweet isn't a product.
- Upstash Box, Vercel durable-harness and Docker Sandboxes for Harbor (Upstash, Vercel and Docker): Three more agent sandboxes in one week. The category is crowded and young; Cloudflare Sandbox stays my pick.
- Capacity reservations for Aurora and RDS (AWS): You pay for idle capacity to make sure a migration cutover has somewhere to land. Useful once a year, and it makes Aurora dearer, not cheaper.
- DeepSeek V4.1 Flash on Workers AI (Cloudflare): The model launched on 17 October and was last week's pick. On Workers AI it's capped at 20 requests a minute, so OpenRouter stays the way to use it.
- Voxtral 3 (Mistral): A voice model with prices I could only find in the press. Relevant only to voice-agent builds on Twilio, and not proven yet.
- Stripe Treasury for agents (Stripe): Money accounts for AI agents, with an announcement date the press can't agree on. Nothing a client of mine needs this year.

## For the geeks

### Node.js: list your functions and their update mode

Adapted from the Lambda runtime management docs:

```bash
# Every Node.js function, its runtime and its update mode (Auto, FunctionUpdate or Manual)
aws lambda list-functions --query "Functions[?starts_with(Runtime,'nodejs')].FunctionName" --output text | tr '\t' '\n' | while read f; do
  echo "$f $(aws lambda get-function-configuration --function-name "$f" --query Runtime --output text) $(aws lambda get-runtime-management-config --function-name "$f" --query UpdateRuntimeOn --output text)"
done

# Put a pinned function back on automatic updates
aws lambda put-runtime-management-config --function-name NAME --update-runtime-on Auto

# Inside the handler, to see what you actually run:
# console.log(process.versions.node, process.versions.brotli)
```

### LiteLLM: check a host without starting Python

From the indicators in LiteLLM's incident post and SafeDep's analysis:

```bash
# Files the payload drops (never run pip or python from a suspect venv first)
find / \( -name cook.pth -o -name cook.py -o -name 'litellm-1.107.0.dist-info' \) 2>/dev/null
find / -path '*site-packages/sitecustomize.py' 2>/dev/null
ls /tmp/remote_code_loader.lock /tmp/.pc_once_72129118c59f5636c0bb 2>/dev/null

# Containers: the compromised image index digest
docker image inspect --format '{{.RepoDigests}}' ghcr.io/berriai/litellm:main-stable | grep 1bbcb1443bc9 && echo COMPROMISED
```

### Tailscale: check the version and turn SSH off

From the Tailscale SSH docs:

```bash
tailscale version                 # want 1.104.2 or later
tailscale set --ssh=false         # keep another way in (SSM, OpenSSH) before you do this
```

And, where you keep Tailscale SSH, a policy rule for non-root users on their own devices only:

```json
"ssh": [{ "action": "accept", "src": ["autogroup:member"], "dst": ["autogroup:self"], "users": ["autogroup:nonroot"] }]
```

### ECS Gateway: one gateway, many services

From the AWS News Blog and the ECS docs:

```bash
aws ecs create-gateway \
  --name my-team-gateway \
  --infrastructure-role-arn arn:aws:iam::123456789012:role/ECSGatewayRole \
  --configuration '{"HostSharedAlb":{"networkConfiguration":{"subnets":["subnet-080779f9bd887757a","subnet-03d5b2fead6bc6258"],"ipAddressType":"ipv4"},"scheme":"internet-facing"}}'

aws ecs associate-gateway-with-cluster \
  --gateway my-team-gateway \
  --cluster arn:aws:ecs:ap-southeast-2:123456789012:cluster/my-cluster

# Each service: name the container port mapping to expose
aws ecs update-service \
  --cluster my-cluster --service my-service \
  --gateway-connectors '[{"portName": "web"}]'
```

### OpenAI Decisions: route a complaint

The request body from OpenAI's Decisions guide:

```json
{
  "model": "gpt-6-luna",
  "input": "<complaint text>",
  "questions": [
    {
      "type": "choice",
      "name": "department",
      "instructions": "Which department should handle this complaint?",
      "choices": [
        { "value": "billing", "description": "Payments, invoices, and refunds." },
        { "value": "other", "description": "Requests outside these categories." }
      ]
    }
  ]
}
```

### Bedrock: guaranteed JSON from Claude, in Australia

Adapted from the Bedrock structured-output docs (they show the raw Converse request; this is the JavaScript SDK v3 version of it):

```js
import { BedrockRuntimeClient, ConverseCommand } from '@aws-sdk/client-bedrock-runtime';
import { NodeHttpHandler } from '@smithy/node-http-handler';

// The first call with a new schema can take ~90 s to compile: raise the timeout.
const client = new BedrockRuntimeClient({
	region: 'ap-southeast-2',
	requestHandler: new NodeHttpHandler({ requestTimeout: 120_000 }),
});

const schema = {
	type: 'object',
	properties: { title: { type: 'string' }, summary: { type: 'string' }, next_steps: { type: 'string' } },
	required: ['title', 'summary', 'next_steps'],
	additionalProperties: false,
};

const res = await client.send(new ConverseCommand({
	modelId: 'au.anthropic.claude-sonnet-5-5', // AU profile: Sydney and Melbourne only
	messages: [{ role: 'user', content: [{ text: 'Extract the title, summary and next steps from: ...' }] }],
	inferenceConfig: { maxTokens: 1024 },
	outputConfig: {
		textFormat: {
			type: 'json_schema',
			structure: { jsonSchema: { schema: JSON.stringify(schema), name: 'data_extraction' } }, // a JSON string in Converse
		},
	},
}));

if (res.stopReason !== 'end_turn') throw new Error(`Not complete: ${res.stopReason}`); // max_tokens means truncated JSON
console.log(JSON.parse(res.output.message.content[0].text));
```

### DuckDB 2.0: Parquet on S3 from a Lambda

Adapted from DuckDB's Node client and S3 docs:

```js
import { DuckDBInstance } from '@duckdb/node-api'; // 2.0.0-r.1

const instance = await DuckDBInstance.create(':memory:');
const connection = await instance.connect();

// Lambda has no home directory: set one before installing extensions or creating secrets.
await connection.run("SET home_directory='/tmp'");
await connection.run('INSTALL httpfs; LOAD httpfs');
await connection.run("CREATE OR REPLACE SECRET s (TYPE s3, PROVIDER credential_chain, REGION 'ap-southeast-2')");

const result = await connection.run("SELECT * FROM 's3://your-bucket/your_file.parquet' LIMIT 10");
console.log(await result.getRowObjectsJson());
```

## Coverage

Window 19 to 25 October 2026 (UTC). Scanned the AWS What's New and News Blog feeds, the Cloudflare changelog and blog, InfoQ, Hacker News, the release notes and advisories of every tool in the stack, X and Google News, the week's new models on OpenRouter, Hugging Face and Workers AI, plus open searches across every concern in the catalog: 98 candidates, 7 kept. Reddit only through search snippets, as usual. Not exhaustive: if it matters and it's missing, tell me.

## Related

- [Every edition of The Stack Weekly](https://cloudlesslabs.com/weekly/index.md): newest first, with summaries
- [The Stack, in one file](https://cloudlesslabs.com/stack/brief.md): the catalog these items are judged against
- [Map of every Markdown file on cloudlesslabs.com, with what each one answers](https://ask.cloudlesslabs.com)
