security-first · Microsoft 365 · open source

nanoclaw

A Microsoft Teams bot that gives a natural-language interface to Outlook mail and calendar, powered by a Claude tool-use loop. It reads freely — and never writes without your explicit confirmation.

How a write happens

Read tools run immediately. Write tools never run on the first request: the agent captures the action as pending, shows a draft, and stops. The gate lives in src/nanoclaw.ts.

01

Draft, don't act

Claude requests send_mail — the loop intercepts it, stores it as a pending action, and replies with a human-readable draft.

02

Confirm in Teams

The write executes only after an affirmative ("sí", "ok", "yes"…) typed by the user in a separate, authenticated Teams message.

03

Injection can't confirm

The confirmation is read from the trusted Teams channel — never from mail or calendar content, so a payload hidden in an email can't approve its own send.

 Microsoft Teams — illustrative transcript (not production traffic)
User:      @nanoclaw respondele al contador que confirmo la revisión

nanoclaw:  📧 *Borrador de mail*
           • **Para:** estudio.contable@…
           • **Asunto:** RE: Contrato Pérez — revisión final
           • **Cuerpo:**
           Hola, confirmo la revisión final del contrato Pérez. Quedo a la espera.
           Saludos.

           Respondé *sí* para confirmar o *no* para cancelar.

           ↑ send_mail was NOT executed — pending action captured.

User:      sí

nanoclaw:  Mail enviado a estudio.contable@… con asunto
           "RE: Contrato Pérez — revisión final".

           ↑ only now does the write run.

The transcript is illustrative, not a recording of live traffic — it reproduces the confirm-before-write flow and the raw Spanish draft string the agent emits (Teams-markdown markers included; the bot was built for a Rioplatense law firm).

Security model

Six mechanisms, each one implemented in the source — file references link the claim to the code.

Fail-closed allow-list

ALLOWED_USER_IDS holds the Teams AAD object IDs permitted to use the bot. An empty or unset list denies everyone; the check runs before any message reaches the agent loop. src/index.ts

Confirm-before-write gate

send_mail and create_event are captured as pending actions and executed only after an explicit affirmative in a separate Teams message. src/nanoclaw.ts

Untrusted-data envelope

Mail and calendar content returned by tools is wrapped as UNTRUSTED EXTERNAL DATA — information to report on, never instructions to follow. Enforced in the wrapper and the system prompt. src/tools.ts · src/nanoclaw.ts

Recipient validation

send_mail validates the to address against a regex before shelling out to the Outlook CLI; malformed addresses are refused. src/tools.ts

Supply-chain pin

The Dockerfile pins the olk (Outlook CLI) binary download to a verified SHA-256 hash. Dockerfile

Secrets in Secret Manager

The deployment scripts store CLIENT_SECRET, ANTHROPIC_API_KEY and the olk keyring password in GCP Secret Manager and injects them as secret-backed environment variables (--update-secrets) — plaintext values never appear in the service configuration. harden.ps1

Tools available to Claude

Four tools. Reads run immediately; writes are gated.

ToolTypeWhat it does
read_mailreadLists recent Outlook messages (subject, from, preview)
read_calendarreadLists upcoming calendar events (subject, start, end)
send_mailwrite · gatedSends email — requires explicit user confirmation
create_eventwrite · gatedCreates a calendar event — requires explicit user confirmation

Project status

Published for code review. Every capability on this page is implemented in the source — none of it is a claim of production traffic.

TypeScript Microsoft 365 Agents / Teams SDK Claude tool-use loop (max 6 steps) olk — Outlook CLI Cloud Run + GCS MIT license