Universal Connections M365 Pre Sandbox
Read-only Microsoft 365, step by step, without a live claim.
Overview and status
Universal Connections (UC) lets a Helpifyr agent read from Microsoft 365 on behalf of one signed-in owner. The connection uses delegated Microsoft Graph permissions only, and it has four separate consent lanes:
| Lane | Delegated permission | Consent call | Lease permission set |
|---|---|---|---|
| Files | Files.Read (plus offline_access and openid) | lane=files (default when lane is omitted) | Files.Read |
Mail.Read (plus offline_access and openid) | lane=mail | Mail.Read | |
| Send | Mail.Send (plus offline_access and openid) | lane=send | Mail.Send |
| Draft | Mail.ReadWrite (plus offline_access and openid) | lane=draft | Mail.ReadWrite |
The consent flow now has four closed lanes: files, mail, send and draft. The sandbox measurements reached an active lease for each lane (#3682, comments 237134 and 237783), but a lease is not a provider side effect. The send and draft operations are not live, see Send and draft stages.
What this page covers, and what it does not
- This is the sandbox / pre-pilot path: a development Microsoft 365 tenant, one owner session, one folder (Files) and one mailbox (Mail), both mapped on the server side, not chosen in the browser.
- The customer path is different. The product decision is that Stalwart is the standard installation and that a customer can later activate Microsoft 365 through Lantern, including the install wizard; from then on Microsoft 365 becomes the source of truth for mail. That wizard does not exist yet, so until it ships, this page describes the sandbox only.
- Sending mail, drafts, writing files, subscriptions and webhooks are not live side effects in this how-to. Admin consent for
Mail.SendandMail.ReadWritewas granted in the sandbox tenant on 2026-10-02, and all four consent lanes reached an active lease, but no draft was created and no mail was sent. The send and draft stages remain guarded and not fully deployed end to end, see Send and draft stages.
Send and draft stages (consent measured, operations not live)
The Owner decided on 2026-10-02 to bring the send stage forward for the sandbox tenant, and to create drafts through Microsoft Graph createDraft. The send and draft consent lanes both reached an active lease in the sandbox (#3682, comments 237134 and 237783). No mail was sent and no draft was created. The downstream operations are not yet fully deployed end to end: Fabric F2 is merged to main, complete-set revocation follow-up #3711 is merged to main, and the current open rollout/migration follow-up is #3714; the deployment rollout remains on the older revision with the required migration/table state absent (#3710, comments 238118, 238326, 238150 and 238154). live_ready stays no.
| Stage | Delegated permission | Admin consent in the sandbox tenant | Keystore broker lane | Consent call in Heddle and Lantern |
|---|---|---|---|---|
| Send | Mail.Send | Granted on 2026-10-02 | Active sandbox lease measured | Wired; request scope openid Mail.Send offline_access; no mail was sent. |
| Draft | Mail.ReadWrite | Granted on 2026-10-02 | Active sandbox lease measured | Wired; request scope exactly openid Mail.ReadWrite offline_access; no draft was created. |
What is fixed by the Owner decisions and by the measurements:
- Sandbox first, no silent switch-on. The capability is built generally and proven in the sandbox tenant first. Activation per tenant stays bound to an Owner go-ahead, the tenant's own admin consent, the guarded-send gates and human approval.
- Guarded send, mode
sandbox_live. One recipient from an allow-list, no attachment, no bulk, and a mandatory reference to the Owner go-ahead for every single mail (a chat yes to recipient and text).production_send_enabledandlive_readystay false. - Approval rights. The sandbox owner persona gets two entitlements for the send path (capability execute and a mail-send approval right), for the sandbox only.
- Consent order that was followed. The Keystore broker tolerance for a reported
Mail.Sendwent live first, then the Owner granted admin consent forMail.Send, then the broker lanessendanddraftand the token-read routes were rolled out, then the Owner granted admin consent forMail.ReadWrite. The four lanes were measured through active leases; the downstream Weft/Fabric operations remain guarded and not live. openidlisted explicitly.openidwas granted but missing from the configured permission list; it was added to the list before the consent, because a tenant-wide consent can revoke granted permissions that are not listed.- One Entra app with tolerance for now. The residual risk is measured, see What the token carries. Two Entra apps are required before the first customer tenant.
Status from the readiness contract
The official result is read from contracts/extensions/m365_readiness_gate_v1.json in Fabric (contract version 1.1.0, observed 2026-09-24T09:52:59Z). This page does not keep a second status ledger.
| Field | Value |
|---|---|
readiness.state | repo_ready |
repo_ready | true |
candidate / in_build | false / false |
live_ready | false |
| Score (equal weights, seven gates) | 7.0 of 7 (100.0 %) |
The seven gates (platform admission, authority chain, provider connection, authority binding, mail read, evidence store, isolation and denial) are each recorded as live through sandbox receipts. The score counts evidenced gates. It is not a production claim, and live_ready stays no. As recorded in the tracking issues when this page was written (2026-10-02), these live proofs are still open (the scp scope list of a refreshed token has been recorded since, see What the token carries):
- Reading through a persisted connection created with the Lantern connections API (not only through operator scripts).
- The revoke drill, and the Microsoft-side revocation step in the runbook.
- Mapping an expired consent (
invalid_grant) toreauthorization_required. - A second tenant and principal for the denial matrix (5 of 32 vectors are live, in one tenant).
- A write and read-back path for the evidence store.
Prerequisites
You need:
- A Microsoft 365 sandbox tenant with a user who owns the folder or mailbox to read. The Fabric activation contract records an E5 developer sandbox with synthetic users. Do not use a customer or production tenant.
- An Entra administrator who can register an application and grant admin consent for the tenant.
- A Lantern owner session in the browser you will use. The consent start requires it, and the whole consent must finish in that same browser.
- An operator who has configured the deployment: the tenant ID and client ID of the Entra app (
LANTERN_M365_TENANT_ID,LANTERN_M365_CLIENT_ID), the server-side mapping for the folder (Files) or the mailbox and its resource scope (Mail), and the client secret in Keystore custody.
This page uses the placeholders <tenant-id>, <client-id> and <lantern-host>. Never paste real tenant IDs, client IDs, secrets or token values into tickets, chats or repositories.
Time windows to plan for
Several short windows run during one consent. The tightest one is 300 s from the authorize call to the handoff, so do not leave the Microsoft dialog open.
| Window | Value | What it bounds |
|---|---|---|
| Lantern initiation record | 300 s | From the authorize call until Heddle's handoff reaches Lantern. After that the initiation is expired and Lantern refuses it. |
| Consent state in Heddle | 600 s | The state, nonce and PKCE record and the state cookie. |
| Handoff after Microsoft's form post | 120 s | The single browser round trip to the completion URL. |
| Microsoft authorization code | about a minute | A staged code must be redeemed within about a minute of issue. |
| Keystore exchange | 120 s timeout | Lantern waits this long for the exchange; the exchange was measured at 26-90 s. |
| Lease | 900 s | Lifetime of an access lease, from the exchange or from each refresh. |
| Lantern session cookie | 3600 s | One hour. After that you must sign in to Lantern again. |
Set up the Entra app
Microsoft's portal labels may change. The values below come from the Fabric contracts and the consent code, not from the portal.
- Register an application in Microsoft Entra ID. Choose a single-tenant account type (
single_tenant, this organizational directory only). The permission manifest suggests the display name "Helpifyr Universal Connections - M365 Sandbox". - Add exactly one redirect URI under a Web platform:
https://<lantern-host>/api/v1/lantern/connections/m365/callback. Entra and Helpifyr compare it by exact match. The consent code pins this URI as a constant and does not read it from configuration, so a different Lantern host needs a code and contract change, not only an Entra change. The flow is a confidential-client flow: Keystore redeems the code with a client secret, and the authorize request usesresponse_mode=form_post. - Check the authentication flags. The Fabric activation contract records implicit access, implicit ID-token and public-client flows as disabled. The consent code requests
response_type=code id_token(a hybrid flow), so Microsoft may require ID-token issuance on the app. The same contract still recordshybrid_id_token_flowasdisabled(readback of 2026-09-18, before the hybrid flow was built). Treat this as a known contract drift and confirm the setting in the sandbox before the first consent. - Add delegated Microsoft Graph permissions:
Files.Read,Mail.Read,Mail.Send,Mail.ReadWrite,offline_accessandopenid. Add no application permissions. Listopenidexplicitly: a tenant-wide admin consent can revoke granted permissions that are not in the list. The four consent lanes request their own closed scope set; they do not merge into one request or grant application permissions. See Send and draft stages. - Grant admin consent for the listed permissions. In the sandbox the app has
Files.ReadandMail.Readadmin-consented, and since 2026-10-02 alsoMail.SendandMail.ReadWrite, which is why Microsoft can complete a lane's consent without a click. The Fabric activation contract originally recorded admin consent asnot_required(Files.Read only, 2026-09-18). That record is outdated for the four-lane setup. The side effect of granting both is described under Option C below. - Create one client secret and hand it to Keystore custody. Keystore reads it through an opaque reference (
M365_OAUTH_CLIENT_CREDENTIAL_REF); Lantern and Heddle never hold it. - Record Conditional Access. The permission manifest requires a Conditional Access readback before a live claim (the sandbox readback recorded zero policies).
- Give the operator the tenant ID and client ID (
<tenant-id>,<client-id>) through the operator's own secure channel.
Screenshot slot
M365-S01(pending)Caption: Entra app registration, Authentication page: one Web redirect URI ending in
/api/v1/lantern/connections/m365/callback; tenant ID and client ID blurred.Provided by: owner session in the Entra portal (open until delivered).
Screenshot slot
M365-S02(pending)Caption: Entra app registration, API permissions page: delegated Microsoft Graph
Files.Read,Mail.Read,Mail.ReadWrite,Mail.Send,offline_accessandopenid, all with admin consent granted; tenant ID and client ID blurred. Planned image files (blurred by the coordinator, committed separately):m365-s02-api-permissions-before.png,m365-s02-api-permissions-after-mail-send.png,m365-s02-api-permissions-after-mail-readwrite.png.Provided by: owner session in the Entra portal (open until delivered).
Consent in Lantern
Give consent once per lane. Each lane uses the same call with a different lane value, and each lane produces its own lease. A Mail consent never reuses the Files lease; the four lanes have separate leases.
Files lane
- Sign in to Lantern as the owner. The Integrations page lists the connections (see the next screenshot slot).
- In the same browser, open
https://<lantern-host>/api/v1/lantern/connections/m365/authorize?lane=files. Leaving outlaneselects the Files lane. - The browser is redirected to Microsoft with the scope
openid Files.Read offline_access, a PKCE S256 challenge, a state and a nonce. - Sign in and approve. Microsoft may show an account picker, see Troubleshooting.
- The browser returns to Lantern and shows
handed_off. Do not reload, go back or reopen the final URL.
Mail lane
- Repeat the steps above with
https://<lantern-host>/api/v1/lantern/connections/m365/authorize?lane=mail. - The scope is
openid Mail.Read offline_access. The mailbox and its resource scope come from the server-side mapping. If that mapping is incomplete, the call stops withcanonical_account_mapping_required. - Any other
lanevalue is rejected before a session, state or cookie is touched.
Send lane
- Use the same owner session and open
https://<lantern-host>/api/v1/lantern/connections/m365/authorize?lane=send. - The measured request scope is
openid Mail.Send offline_access; successful consent reaches an activeMail.Sendlease. - This consent does not send mail. Sending remains behind the guarded Fabric/Weft path, Owner approval and the pending rollout gates.
Draft lane
- Use the same owner session and open
https://<lantern-host>/api/v1/lantern/connections/m365/authorize?lane=draft. - The measured request scope is exactly
openid Mail.ReadWrite offline_access; successful consent reaches an activeMail.ReadWritelease. - This consent does not create a draft. Draft creation remains behind the downstream operation path and is not live.
Screenshot slot
M365-S03(pending)Caption: Lantern Integrations page showing the connection list in the sandbox, before the consent call; account details masked.
Provided by: Coordinator capture with an owner login.
Screenshot slot
M365-S04(pending)Caption: Microsoft consent dialog or account picker ("Pick an account") during the Mail lane consent; tenant and user details blurred.
Provided by: Coordinator capture with an owner login.
Screenshot slot
M365-S05(pending)Caption: Browser answer after a successful consent: the handoff response with status
handed_off.Provided by: Coordinator capture with an owner login.
What happens behind the consent
- Heddle checks the Lantern session, creates state, nonce and a PKCE verifier, hands the verifier to Keystore and asks Lantern for an initiation record.
- Microsoft posts the result to the callback. This is a cross-site POST, so Heddle only stores the body for up to 120 s and answers with a 303 redirect to a completion URL. It makes no trust decision at this step.
- The completion URL is a same-site navigation. Heddle now checks the state cookie, the Lantern session, the nonce, and the ID token (signature, issuer, audience, tenant, nonce). A handoff id and a state can each be used once.
- Heddle stages the authorization code with Keystore, resolves the initiation at Lantern, and sends Lantern an HMAC-signed handoff that carries only opaque references.
- Lantern calls the Keystore exchange once. Keystore redeems the code with the client secret and creates a lease (generation 1, 900 s). The raw code, the verifier and the tokens are never logged and never returned to the browser.
What the token carries (Option C)
Keystore redeems the code without a scope parameter, so Microsoft returns every consented resource scope. A refresh sent with the lane scope also returns the other consented scopes. The owner decided on Option C for the pilot: the broker tolerates a surplus that is a subset of Files.Read and Mail.Read at refresh, logs the names of the surplus scopes, and keeps rejecting every other scope. Since 2026-10-02 the tolerated set also contains Mail.Send and Mail.ReadWrite. A refresh requests Mail.Send only for a lease of the lane send and Mail.ReadWrite only for a lease of the lane draft; the lanes files and mail never request them.
Residual risk, named, measured and accepted by the owner for the sandbox: the token of the Mail lane carries more than its lane. A Mail lease refreshed to generation 2 on 2026-10-02 (after both admin consents) has the token claim scp Files.Read, Mail.Read, Mail.ReadWrite, Mail.Send, openid, profile, email, and the broker logged surplus_scopes=Files.Read,Mail.ReadWrite,Mail.Send. Consequence: a leaked Mail access token can additionally read files, and with the two new consents also change and send mail, as the consenting user. Files.Read is wanted (OneDrive reading); the rest is the price of one Entra app with tolerance. In this mode the lane limit lives in the lease metadata (permission_set) and in Weft and Fabric, not in the token, and the planned human approval is the further gate. Two Entra apps with a hard separation are required before the first customer tenant (Option A, an owner action in the customer tenant); the tolerance can be switched off with M365_REFRESH_TOLERATE_CONSENTED_RESOURCE_SCOPES.
Verify
- Browser answer. A successful consent ends with the response
handed_offand the detailm365 consent handoff accepted by Lantern. A deployment without the Lantern handoff configured answers withstagedinstead. - Operator logs. Heddle logs
m365_consent_callback_exitwithoutcome=staged. Lantern logslantern.m365_exchange_outcomewith the lease generation and opaque references only. Keystore logsexchange_steplines up toissued. No log line contains a code, verifier or token value. - Repository checks without a live call:
python scripts/verify_universal_connections_m365_sandbox_activation.py --checkpython -m pytest -q tests/test_universal_connections_m365_sandbox_activation.py
- Read check. The sandbox read of the Mail lane returned
allowed_read_onlywith zero write, send and subscription calls, and a replay without a second provider call. Run the read inside the 900 s lease window.
A passing verification shows that the sandbox path works. It does not change live_ready.
Troubleshooting
Messages appear in a browser JSON answer, in the Lantern UI, or only in operator logs.
| Message | Where | Cause | What to do |
|---|---|---|---|
authenticated lantern session required | Authorize call, HTTP 401, field detail | The browser has no valid Lantern session. | Sign in to Lantern again, then repeat the authorize call. |
authenticated_lantern_session_required | Completion step, HTTP 401 | The session is gone or differs from the one that started the consent (different browser or profile, or the cookie expired after 3600 s). | Sign in again and start over in one browser. |
authenticated_lantern_session_cookie_missing | Heddle session check | The Lantern session cookie was not sent at all. | Allow cookies for the Lantern host and sign in again. |
access_context_unavailable | Lantern UI, from the access-context call | Lantern could not resolve your access context. HTTP 401 means the session is not accepted; 502 or 503 mean the upstream is unavailable or its contract drifted. The BFF logs lantern.access_context_unavailable. | Sign in again. If it persists, the operator checks the BFF log line. |
invalid_or_expired_state | Completion step, HTTP 400 | The state cookie is missing or does not match, the state is older than 600 s, it was already used, or Heddle restarted (the store is in memory). | Start again from the authorize call, in the same browser, and finish quickly. |
invalid_or_expired_handoff | Completion step, HTTP 400 | The handoff is older than 120 s, was already used (reload or back button), or is unknown. | Start again. Never reopen the completion URL. |
lantern_handoff_failed | Completion step, HTTP 502 | The consent was consumed, but Lantern or Keystore rejected the handoff. The cause code is only in the Heddle log. | Start a new consent. The staged references are not returned. |
| Expired or used code | Operator logs | Keystore rejects with expired authorization code, unstaged, consumed, or revoked authorization code or authorization code replay blocked. Its HTTP answer is the generic invalid_or_unavailable_handoff. Microsoft itself may answer invalid_grant. The code is single-use and lives about a minute. | Start a new consent. An exchange with an unknown outcome is terminal, so never retry the same handoff. |
exchange_timeout | Lantern handoff answer | The Keystore exchange did not answer within 120 s. The one-time code may already be consumed. | Start a new consent. |
| Pick an account | Microsoft page | Microsoft shows an account picker, sometimes after admin consent. It is Microsoft's page, not Helpifyr's. In the sandbox a lane consent also completed once without any click. | Pick the sandbox user this connection is meant for. |
scope_widening_rejected | Keystore log, at refresh | See below. | See below. |
consent_denied_or_provider_error | Completion step, HTTP 400 | The callback carried an error instead of a code, for example because the consent was cancelled. | Start again and approve. |
canonical_account_mapping_required | Lantern authorize, HTTP 424 | The server-side mapping for the lane is incomplete (for Mail: mailbox and resource scope). | Operator completes the mapping. |
missing_tenant_or_client_id | Authorize call, HTTP 503 | Tenant ID or client ID is not configured (detail m365 consent lane is not configured). | Operator configures the Entra app values. |
scope_widening_rejected
Keystore logs scope_widening_rejected surplus_count=1 and fails the refresh with the message token endpoint returned more resource scopes than the admitted permission set. The lease stays on its old generation and expires after 900 s, so reading stops until you consent again.
- Cause with the pilot default: the token response contained a scope outside the tolerated set (
Files.ReadandMail.Readin the pinned pilot state; since 2026-10-02 alsoMail.SendandMail.ReadWrite), for exampleFiles.ReadWrite.Allor any*.Allscope. This is the guard doing its job. Do not widen the admitted set. - Cause with the tolerance switched off: every refresh fails while the app has both rights consented, because Microsoft returns both. This is the strict mode that Option A will restore with one app per lane.
- With the tolerance on, a
Files.Readsurplus on a Mail lease is not a failure. Keystore logsscope_widening_tolerated_at_refreshwith the scope names. That log line is the visible form of the Option C residual risk, so do not hide it from reviewers.
A refresh took about 65 s in the sandbox on 2026-10-01 and about 95 to 100 s on 2026-10-02, so use a client timeout well above that when you call it by script (the sandbox probe uses 120 s).
Revoke and roll back
- Stop one lease. An operator calls the broker route
/internal/m365/oauth/revokewith the lease reference and generation. The lease becomesrevoked, and its access and refresh references are revoked. Refresh and reads then fail withstale or revoked generation blocked. A stale generation is refused. - Let it lapse. Without a refresh a lease ends after 900 s and later calls fail with
expired lease blocked. - Consent again. After a revoke, a new consent needs a new code, state, nonce and PKCE verifier.
- Restore strict scope checking. Set
M365_REFRESH_TOLERATE_CONSENTED_RESOURCE_SCOPEStooffto end the Option C tolerance without a code change. - Remove the Microsoft side. Remove the app's delegated consent or the app registration in the Entra portal. This step is still open in the runbook, and the revoke drill has not run live yet, so treat both as unmeasured. The activation runbook names five rollback steps:
rollback-stop-read,rollback-revoke-connection,rollback-revoke-lease,rollback-remove-registrationandrollback-closeout. - Roll back the broker. Remove the broker service from the active Compose override and restore the previous image. Do not touch the Vaultwarden data volume.
Screenshot slot
M365-S06(pending)Caption: Browser error response
invalid_or_expired_stateafter a consent that was started in one browser and finished too late or elsewhere.Provided by: Coordinator capture with an owner login.
Screenshot slot
M365-S07(pending)Caption: Result of the lease revoke drill: the broker answers with
revokedfor the lease, and a following read is refused.Provided by: Coordinator capture, only after the revoke drill has run.
Limitations
- No live claim.
live_readyis no. Repo-ready and sandbox-evidenced is not live-ready. - Sandbox only. One development tenant, one owner session, one folder and one mailbox. The customer path comes with the install wizard. All four consent lanes reached active leases, but send and draft operations are not live: no draft was created, no mail was sent, and the downstream Fabric/Weft rollout remains incomplete; complete-set revocation #3711 is merged, while rollout/migration follow-up #3714 remains open (see Send and draft stages).
- Option C residual risk. The Mail token also carries
Files.Read,Mail.ReadWriteandMail.Send(measured 2026-10-02, see above). Two Entra apps are required before the first customer tenant. - Admin consent differs from the old contract text. The sandbox app has admin consent for all of its listed rights, while the activation contract still records
not_required. - Pinned redirect URI. The callback URI is a constant in the consent code and in the Keystore broker, so a different Lantern host needs a code change.
- In-memory state. The Heddle consent state and the Lantern initiation store (
single_process_ttl300) live in one process. A restart makes pending consents fail, and you start again. - Short windows. 300 s, 600 s, 120 s, about a minute and 900 s (see Time windows). A lease expires after 900 s unless refreshed, and a refresh was measured at about 65 s (2026-10-01) and about 95 to 100 s (2026-10-02).
- Screenshots are placeholders. The slots above are filled by a separate capture; an Entra slot needs an owner session in the Entra portal.
Reference: activation packet (repo-only)
The following sections keep the repo-only projection of the Universal Connections M365 activation packet. The packet is a synthetic fixture. It does not describe the live sandbox run above.
Safe starting point
The canonical plan names delegated M365 Files, m365_file_graph, OneDrive as the primary Files profile, one opaque folder scope, and read-only access as the smallest safe start. The packet is derived from docs/plans/PLAN_UNIVERSAL_CONNECTIONS.md.
| Boundary | Packet posture |
|---|---|
| Provider capability | workspace.file.read |
| Provider surface | m365_file_graph |
| Read scope | one_folder |
| Write scope | none |
| Graph call allowed (packet) | no |
| Host mutation allowed | no |
Authority and admission chain
The chain is server-derived and revision-bound. Platform admission does not silently enable a tenant, and tenant enablement does not grant a connection.
| Readback | Canonical reference |
|---|---|
| Fabric admission | fabric://admission/m365-file-read |
| Authority snapshot | fabric://authority/m365-sandbox-qa |
| Connection revision | fabric://connection/m365-file-read/revision/1 |
| Capability revision | fabric://capability/workspace.file.read/revision/1 |
| Fabric authority reference | keystore://jhf-fabric/uc/authority-read-token/v1 (opaque, owner/runtime-gated; not live-read evidence) |
| M365 sandbox credential reference | keystore://ref/m365-sandbox-qa (generic opaque fixture reference; not the Fabric authority reference) |
| Opaque folder scope | sandbox_required://onedrive-folder/qa-folder |
Fail-closed matrix
The packet stops on these canonical conditions:
scope_wideningpermission_driftauthority_mismatchprovider_write_attemptmissing_ca_or_consent_evidencesecret_leakagereplay_second_provider_read
The activation contract forbids these execution classes:
tenant_activation_executionconnection_grant_executionagent_tool_emissionprovider_executioncustomer_bundle_publicationcertified_feed_runtimebeam_light_runtimesubscription_entitlement_truthlocal_paid_entitlement_truthtrust_or_signing_key_rotationsecurity_support_until_materializationproduction_write
Source and verification references
- Packet:
contracts/extensions/m365_sandbox_activation_packet_v1.json - Readiness gate:
contracts/extensions/m365_readiness_gate_v1.json - Permission manifest:
contracts/extensions/m365_entra_permission_manifest_v1.json - PKCE contract:
contracts/extensions/m365_delegated_authorization_code_pkce_v1.json - Packet schema:
contracts/extensions/m365_sandbox_activation_packet_v1.schema.json - Activation contract:
contracts/extensions/universal_connections_activation_v1_1.json - Runbook:
docs/UNIVERSAL_CONNECTIONS_M365_SANDBOX_ACTIVATION.md - Plan:
docs/plans/PLAN_UNIVERSAL_CONNECTIONS.md - Consent code: Heddle
lantern_oidc_bridge/m365_consent.py - Initiation and handoff: Lantern
bff/m365-initiation.ts,bff/m365-handoff.ts - Exchange and refresh: Keystore
vaultwarden_oc_keystore/m365_oauth_exchange.py,docs/M365_OAUTH_EXCHANGE_RUNTIME.md - Verifier:
scripts/verify_universal_connections_m365_sandbox_activation.py --check
This page documents a sandbox procedure. It does not activate a tenant for customers, resolve a secret, or assert live readiness.
License
AGPLv3. Project: https://helpifyr.com