Skip to content

gateway, gui: Claude for Microsoft 365 can use magpie over local HTTPS - #1331

Open
svcvit wants to merge 4 commits into
yetone:mainfrom
svcvit:codex/m365-https-gateway
Open

svcvit wants to merge 4 commits into
yetone:mainfrom
svcvit:codex/m365-https-gateway

Conversation

@svcvit

@svcvit svcvit commented Oct 8, 2026 •

Copy link
Copy Markdown

What changes

Claude for Microsoft 365's Word, Excel and PowerPoint add-ins can connect to magpie at https://127.0.0.1:8787.

  • adds an opt-in, loopback-only HTTPS listener that reuses the ordinary gateway's Server.Handler
  • exposes only GET /v1/models, POST /v1/messages, and their preflight on that listener
  • creates or reuses a normal named Claude for Microsoft 365 gateway key, so limits, model/account restrictions, Routing and Usage work as they do for other callers
  • adds https://pivot.claude.ai only while Microsoft 365 support is enabled
  • answers credentialed CORS and Private Network Access preflight without changing other configured browser origins' response headers
  • adds Settings controls for the HTTPS endpoint, certificate state and key copy, translated in every language magpie currently ships

The ordinary agent gateway remains HTTP and keeps its current address, so existing agent configuration does not change.

Security boundary and lifecycle

This PR intentionally adds a network listener and an optional macOS trust-store write, called out here for the community-PR safety review:

  • the listener binds only 127.0.0.1:8787; it never binds a LAN interface
  • its route wrapper returns 404 for every route except /v1/models, /v1/messages, and preflight on those paths
  • every browser call from the allowed remote origin still needs an enabled named gateway key
  • the CA has a critical Name Constraints extension permitting only exact localhost, 127.0.0.1/32, and ::1/128, with .localhost subdomains excluded
  • the leaf covers only localhost, 127.0.0.1, and ::1; its private key is mode 0600
  • the CA private key exists only while signing the leaf and is never written to disk
  • certificate generation does not trust the CA
  • automatic trust actions are exposed only on macOS; installation is an explicit click into the user's default keychain
  • macOS removal uses security delete-certificate -t, deleting both the certificate and its user trust setting
  • Windows and other systems show the certificate path for manual trust management; the Windows certutil path stays unavailable through the UI and API until it has been validated end to end with real Windows Office
  • no dependency, workflow, release script, Dockerfile, telemetry, or external service is added

Turning support off commits the security boundary first: it saves M365=false, removes https://pivot.claude.ai, and stops the HTTPS listener. It then removes macOS certificate trust. A listener or trust cleanup failure is reported separately with manual cleanup guidance and cannot leave the setting or origin enabled. Disable also works when this Magpie is not serving the ordinary gateway.

A failed enable—whether the ordinary gateway restart fails or the HTTPS listener does not start—rolls the setting and origin back to off. The named gateway key remains so a later enable can reuse it; it can still be revoked from Gateway keys.

Subsystem behavior

The current contract, platform behavior, lifecycle and failure semantics are documented in docs/subsystems/m365.md and linked from the subsystem index.

The Microsoft 365 setting is machine-local under Settings.KeepOwn: sync or backup cannot start a listener on another computer or attach it to a key that is absent there.

Verification

Passed after rebasing onto upstream/main 674b6ca2:

go test ./internal/gateway ./internal/gui -run 'TestM365|TestCORSSettingCannotDrift' -count=1
go test -race ./internal/gateway ./internal/gui -run 'TestM365|TestCORS' -count=2
go vet ./...
GOOS=linux go vet -tags nogui ./...
GOOS=windows go vet -tags nogui ./...
GOOS=darwin go build -tags nogui ./...
GOOS=linux go build -tags nogui ./...
GOOS=windows go build -tags nogui ./...
node --check internal/gui/assets/app.js
node --check internal/gui/assets/i18n.js
git diff --check

The M365 GUI regression passed in Chromium and WebKit at narrow width in English, Simplified Chinese, Traditional Chinese, Japanese, and German. It covers both the macOS automatic-trust controls and the non-macOS manual-certificate-path UI.

A complete go test -tags nogui ./... was attempted, but was interrupted after more than four minutes in unrelated internal/agent tests. It did not report an M365 failure and is not claimed as a pass.

Real-device evidence

The real Claude add-in for PowerPoint on macOS was tested with https://127.0.0.1:8787 and the dedicated Magpie key: it connected successfully, populated routed models, and sent a real prompt through Magpie. The screenshots are in this PR comment and also show that the add-in's Gateway URL is editable.

Windows Office real-device validation has not been run. Accordingly, automatic certificate install/removal remains disabled on Windows rather than being presented as verified support.


🎬 界面预览

CI 用这个 PR 的代码构建并真实运行 magpie(沙盒环境,配置了真实的 DeepSeek key),按改动自动操作、截图和录屏 · dc2d1ee · 运行记录

改动(按代码):在「设置 › 网络与共享」里新增「Microsoft 365」一节:一个开关,以及开启后显示的仅本机 HTTPS 地址、证书状态与信任操作、网关密钥复制和自动加入的允许网页。

Note

沙盒里看不到:沙盒大概不是 macOS,所以「信任证书 / 移除信任」这类自动信任按钮不会出现,只能看到证书路径那一行;Word、Excel、PowerPoint 里的 Claude 加载项本身在沙盒中无法运行,这个 HTTPS 监听器真正被加载项调用也看不到。另外若 127.0.0.1:8787 已被占用或本进程没有在提供网关,开启会失败并在这一节显示错误,而不是显示地址、证书与密钥。

播放录屏

▶️ 点图打开录屏(可暂停、拖动)· 直接下载 mp4 · 红线是鼠标轨迹,红色圆环是点击

设置中的 Microsoft 365 一节

未开启时这一节显示的内容

未开启时这一节显示的内容

开启 Claude for Microsoft 365

开启后这一节显示的内容:地址、证书、密钥、允许的网页

开启后这一节显示的内容:地址、证书、密钥、允许的网页

点击「开启」后,Microsoft 365 这一节的显示

点击「开启」后,Microsoft 365 这一节的显示

点「复制密钥」后这一节的样子

点「复制密钥」后这一节的样子

⚠️ 有 3 步没能照计划执行
  • 设置中的 Microsoft 365 一节 · scroll : nothing visible matches #m365List (1 in the page)
  • 开启 Claude for Microsoft 365 · click #m365List button.text: nothing visible matches #m365List button.text with "复制密钥" (0 in the page)
  • 开启 Claude for Microsoft 365 · shot #status: nothing visible matches #status (1 in the page)

@svcvit
svcvit deployed to ui-preview October 8, 2026 14:51 — with GitHub Actions Active
github-actions Bot added a commit that referenced this pull request Oct 8, 2026
@yetone

yetone commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Sorry for the slow review, and thanks for listing the security boundary in the description.

This PR trips our community-PR harm check, so I haven't run it. It is held for the maintainer to decide. It changes system trust and opens a new network surface:

  • Root CA in the user's trust store:
    • internal/gateway/m365_cert.go:79 runs security add-trusted-cert -r trustRoot -p ssl -k <login keychain> on macOS.
    • :81 runs certutil -user -addstore Root on Windows.
    • internal/gui/m365.go:106 reaches both through POST /api/settings/m365/certificate/{action}.
    • The CA's private key is never written to disk (:129 generates it and discards it after signing the leaf), which limits the damage. Still, it's a trust-anchor change on the user's machine, and a maintainer has to rule on whether magpie does that at all.
  • A second listener on a fixed port: internal/gateway/m365.go:16-21, 127.0.0.1:8787, started from backend.go whenever the setting is on.
  • A remote origin granted credentialed CORS on the gateway:
    • internal/gateway/cors.go:49-50 sends Access-Control-Allow-Credentials: true for https://pivot.claude.ai.
    • internal/gui/m365.go:68-69 adds that origin to CORSOrigins when the setting is turned on. Turning it off doesn't remove the origin again.

There's also a product question beyond the code: the request comes from Anthropic's own add-in, and it would be routed to other providers' models.

I'll leave the PR open until the maintainer decides. If it's a yes, I'll do the full run then: tests, vet on the three OSes, and the GUI tests in both engines and every language.

@yetone

yetone commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Thanks @svcvit. Letting Claude for Microsoft 365 use magpie is welcome, but not with the certificate setup as it stands. Here are the conditions for merging.

  1. The root certificate. Today it is added to the user's trust store as a general trusted root (security add-trusted-cert -r trustRoot, m365_cert.go:79; certutil -user -addstore Root, :81). If its private key ever leaves the machine or gets copied, it can impersonate any HTTPS site to that user. For magpie to install one, it has to:
    • be name-constrained to 127.0.0.1, localhost and ::1 (a critical NameConstraints extension);
    • keep its key readable only by the user (0600, or the Keychain), never in a world-readable path;
    • be removed from the trust store when the setting is turned off, with a test for each OS.
  2. The pivot.claude.ai origin. It has to be added only while the setting is on and taken out the moment it's turned off. Right now it stays allowed after disable.
  3. The port. 8787 is fixed. If it's taken, magpie should say so in Settings rather than fail silently. Please also confirm the add-in really requires that exact port.
  4. Real use. Please show it working with the real add-in in Word or Excel on macOS and on Windows, and say what was run.

With those in place I'll review it again line by line and run it.

@svcvit
svcvit force-pushed the codex/m365-https-gateway branch from bf4bf59 to c768a2a Compare October 13, 2026 04:19
@svcvit
svcvit force-pushed the codex/m365-https-gateway branch from c768a2a to 1859a1c Compare October 13, 2026 04:21
@svcvit

svcvit commented Oct 13, 2026

Copy link
Copy Markdown
Author

Thanks for the concrete conditions. I rebased onto current main and pushed fixes for 1–3.

  1. Root certificate

    • The CA now carries a critical NameConstraints extension: exact DNS localhost (subdomains are explicitly excluded), plus 127.0.0.1/32 and ::1/128.
    • The server private key remains 0600; the CA private key is still never persisted.
    • Turning the setting off removes the CA from the current user's trust store before saving the setting as off. There are command-shape tests for both macOS (security delete-certificate) and Windows (certutil -user -delstore Root), plus a route test that verifies disable removes trust and the origin.
    • Existing invalid/unconstrained certificates are rejected and regenerated; if an old CA is trusted, it is removed before replacement.
  2. Origin lifecycle

    • https://pivot.claude.ai is now coupled to the switch in both directions: enable adds it, disable removes it immediately.
    • The general CORS settings route cannot remove it while M365 support is on or retain/add it while support is off.
    • A failed enable rolls the setting and origin back to off.
  3. Port 8787

    • An occupied port is preserved in M365Status.Error, enable rolls back to off, and Settings still shows the URL plus the bind error while off.
    • 8787 is the default documented and used by the real add-in workflow, but the add-in's Gateway URL field is editable; the add-in does not technically require only that port. Magpie keeps 8787 for compatibility with the existing documented setup and the cc-gateway reference implementation.

Verification run on this revision:

  • focused M365 gateway, GUI, CORS and settings-preservation tests
  • go test -race ./internal/gateway -run TestM365 -count=5
  • go vet ./internal/gateway ./internal/settings ./internal/gui
  • Windows amd64 cross-compilation for gateway, settings and GUI test binaries
  • the M365 GUI test in Chromium and WebKit, English and Simplified Chinese
  1. Real add-in
    • The earlier revision was manually accepted with the real add-in in Word, Excel and PowerPoint on macOS using https://127.0.0.1:8787, a trusted generated CA and the dedicated Magpie gateway key; model listing and streamed Messages API calls worked through Magpie.
    • I do not have a Windows Office seat available from this machine, so I cannot honestly claim the requested Windows real-add-in run yet. I will add the exact Windows steps/results once that is run.

@svcvit
svcvit deployed to ui-preview October 13, 2026 04:21 — with GitHub Actions Active
github-actions Bot added a commit that referenced this pull request Oct 13, 2026
@svcvit

svcvit commented Oct 13, 2026

Copy link
Copy Markdown
Author

Adding the real-device evidence for the macOS run.

Tested in the real Claude add-in for PowerPoint on macOS:

  1. Opened Cloud provider or gateway sign-in.
  2. Selected Gateway and entered Magpie's https://127.0.0.1:8787 URL plus the dedicated named gateway key.
  3. Connected successfully.
  4. Sent a real prompt through the add-in.
  5. Confirmed the model picker was populated from Magpie, including routed models.

The screenshots also confirm that the add-in's Gateway URL is editable, so port 8787 is the documented/default setup rather than a hard-coded requirement.

These screenshots are macOS PowerPoint evidence only; Windows Office real-add-in validation is still outstanding.

PowerPoint Claude add-in sign-in PowerPoint Gateway URL and token configuration PowerPoint Claude add-in connected through Magpie

@yetone

yetone commented Oct 13, 2026

Copy link
Copy Markdown
Owner

Sorry for the slow re-review, and thanks for the fixes and the PowerPoint evidence. I re-reviewed at 1859a1c11e828541a3ac0a5ae0bd8a9bd4fb3395, merged locally onto main e1b70c1. I read all three commits line by line.

What I ran (macOS, Go 1.26.8, temp HOME):

  • go build -tags nogui ./..., plus go vet -tags nogui for darwin, linux and windows on gateway, gui and settings: all clean.
  • go test on ./internal/settings and ./internal/gui: ok.
  • go test -race -count=2 -run 'TestM365|TestCORS' on gateway and gui: ok.
  • m365.test.cjs in Chromium and WebKit: 6 pass.
  • I generated a certificate and inspected it with openssl x509 -text. The CA has critical Name Constraints: permitted DNS:localhost, 127.0.0.1/32 and ::1/128, excluded .localhost. It is CA:TRUE with path length 0. The leaf is serverAuth only, with SANs localhost, 127.0.0.1 and ::1, and openssl verify passes. The server key is 0600, and no CA key is on disk.
  • I broke each guarantee by hand, and each test went red:
    • Name Constraints made non-critical: TestM365CertificateCoversOnlyLoopbackAndKeepsNoCAKey fails.
    • IP ranges dropped: the same test fails.
    • The origin left in place by setM365Off: TestM365OffRemovesItsOriginAndCertificateTrust fails.
    • Trust removal skipped on off: the same test fails.
    • The CORS route's coupling dropped: TestCORSSettingCannotDriftFromM365Switch fails.

Conditions 1–3: met in the code. The constraints are right, the origin follows the switch both ways, and a busy port is reported and rolled back.

Still needed before merge:

  1. macOS trust removal leaves the trust setting behind. add-trusted-cert without -d writes a user trust setting. delete-certificate -Z <sha1> <keychain> without -t removes the certificate but not that trust setting; security delete-certificate -h says -t Also delete user trust settings for this certificate. Please add -t, and assert it in TestM365TrustRemovalCommandsOnMacOSAndWindows.

  2. Turning it off can fail and leave everything on. setM365Off returns before s.M365 = false when removeM365Certificate fails. That happens when:

    • the user cancels the macOS auth prompt;
    • certutil fails;
    • on Linux, if the CA was trusted by hand, m365TrustCommand errors.

    In each case the listener, the setting and the pivot.claude.ai origin all stay on. Off should always stop the listener and drop the origin. Then report the trust removal failure separately, with what to remove by hand. Please add a test where removal returns an error.

  3. Off also requires served != nil. The route rejects any request while this magpie isn't serving the gateway, including an off request, so the setting can't be turned off there. Off should work in that state.

  4. Enable failing at the gateway-restart step doesn't roll back. When restartM365Gateway() fails, the handler returns without the setM365Off rollback that the not-running branch does. M365=true and the origin stay saved.

  5. The PR description is out of date. It still says turning off doesn't remove the certificate's trust; the code now does. Please update it.

  6. Condition 4, a real Windows run, is still open. You said so yourself, and thanks for being clear about it. certutil -user -addstore/-delstore Root has only command-shape tests. It needs one real run: install, use from Office, turn off, then check certutil -user -store Root no longer lists it. Until then, please either show the install button only on macOS, or get the Windows run.

This also needs the owner's decision. It installs a trust root into the user's trust store. It is name-constrained, the key is thrown away, and it's only installed when the user clicks. Whether magpie ships that at all is @yetone's call, not a reviewer's. I've raised it with him.

I haven't merged it.

Adds an opt-in loopback-only TLS listener at https://127.0.0.1:8787, a local CA whose signing key is never retained, explicit macOS/Windows trust actions, and a named gateway key for the Office add-in. The existing Anthropic handler, routing, streaming, limits and usage remain the request path.

Verified with M365 gateway, certificate, CORS/PNA, settings and GUI tests; Chromium English/Chinese UI tests; go vet; production and nogui builds; Windows amd64 cross-compilation; and manual Word/Excel/PowerPoint acceptance against the local app.
Close the Microsoft 365 security boundary before attempting certificate cleanup, roll failed enables back, and allow disable without a locally served ordinary gateway. Remove macOS user trust settings with the certificate and keep automatic trust actions macOS-only until Windows is validated with real Office.

Checks: focused gateway/gui M365 and CORS tests; race count 2; darwin/linux/windows vet and nogui builds; Chromium and WebKit M365 GUI tests in en, zh, zh-TW, ja and de; gofmt, node --check and git diff --check. The full nogui suite was attempted but interrupted after unrelated internal/agent tests did not finish.
@svcvit
svcvit force-pushed the codex/m365-https-gateway branch from 1859a1c to dc2d1ee Compare October 16, 2026 02:48
@svcvit
svcvit deployed to ui-preview October 16, 2026 02:48 — with GitHub Actions Active
@svcvit

svcvit commented Oct 16, 2026

Copy link
Copy Markdown
Author

Thanks for the detailed re-review. I rebased onto current main and pushed the follow-up at dc2d1eef9d834ee94cef955ff9812493c3e9f16c.

The six requested items are handled as follows:

  1. macOS trust settings: removal now runs security delete-certificate -t -Z …, and the exact command-shape test requires -t.
  2. Disable closes the boundary before cleanup: it first saves M365=false, removes https://pivot.claude.ai, and stops the HTTPS listener. Certificate-removal failure is returned separately as a warning with manual-removal guidance; it cannot leave the integration on. Tests cover trust-removal and listener-stop failures.
  3. Disable without a served ordinary gateway: the ownership guard now applies only to enable. A regression test disables with served == nil and verifies there is no restart.
  4. Gateway restart failure: a failed restartM365Gateway() now runs the same rollback, leaving the setting off and the origin absent. A regression test covers this branch.
  5. PR description: updated to the current lifecycle and verification record.
  6. Windows: automatic certificate actions are now macOS-only in both the UI and API. Windows and other systems show the CA path and manual trust instructions. The existing certutil implementation remains unreachable through the product until it is validated with real Windows Office. I still do not have a Windows Office environment and do not claim that run.

Additional verification on the rebased head:

  • focused M365/CORS Go tests: pass
  • gateway/gui -race -count=2: pass in a temp HOME
  • go vet ./... plus Linux/Windows nogui vet: pass
  • darwin/Linux/Windows nogui builds: pass
  • M365 GUI test in Chromium and WebKit, narrow width, en, zh, zh-TW, ja, de: pass
  • JS syntax, gofmt, and git diff --check: clean

The complete go test -tags nogui ./... was attempted but interrupted after more than four minutes in unrelated internal/agent tests; it did not report an M365 failure, and I am not counting it as a pass.

github-actions Bot added a commit that referenced this pull request Oct 16, 2026

This branch was successfully deployed

1 active deployment
ui-preview — dc2d1eef Deployed Oct 16, 2026 by svcvit via record #1190
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants