Skip to content

Device timeline

xdr device timeline downloads a device’s Defender for Endpoint timeline, the same event stream shown on a device’s page in the Defender portal, by talking to the unofficial security.microsoft.com/apiproxy/mtp/mdeTimelineExperience endpoint the portal UI itself uses. It is not part of the documented Microsoft Defender for Endpoint API, has no SLA, and can break without notice.

Advanced Hunting (xdr hunt run) only retains roughly 30 days of raw event data, while the portal’s device timeline retains roughly 180 days. Reach for device timeline when an investigation needs to establish first-seen or earliest-activity evidence beyond what hunt run can see. Use --hours for a narrow window on busy devices or --days for longer lookbacks; the resolved window may be at most 180 days, and anything larger is rejected before any request is made.

CommandDescription
xdr auth portal-cookieAuthenticate to the unofficial Defender portal API via browser cookies
xdr auth portal-logoutClear stored portal cookies; official tokens are preserved
xdr device timeline DEVICEDownload device timeline (unofficial API)
Terminal window
xdr auth portal-cookie mde-curl.txt
xdr device timeline my-workstation --hours 3
xdr device timeline my-workstation --days 30 --output timeline.jsonl

Use xdr auth portal-cookie to import an existing browser session. Activity uses that signed-in user’s portal permissions. Audit attribution must be verified in your tenant; a local auth-status label is not evidence of the actual sign-in or activity-log attribution.

The --refresh-token path redeems the token as the “Microsoft Azure CLI” client, so sign-in logs attribute that activity to Azure CLI.

With --backend portal-cookie, timeline requires stored cookies; --refresh-token (and MDE_REFRESH_TOKEN) is rejected. The default official backend selects a credential in this order: an explicit refresh token, then stored cookies. With neither present the command exits non-zero and names xdr auth portal-cookie as the recovery. There is no other portal credential store; the CLI never signs in to the portal itself.

Section titled “1. xdr auth portal-cookie COOKIE_SOURCE (recommended, validated)”

The canonical capture walkthrough and backend limits live in portal_cookie.md; this section is the short form.

Reuses your existing logged-in security.microsoft.com browser session. It takes the whole browser Cookie header from a file (or - for stdin) and stores it in ~/.xdr-cli/portal_cookies.json (mode 0600 on POSIX; protected by the user-profile ACL on Windows). The store is bound to a non-reversible fingerprint of the configured tenant, so a configured tenant is required before importing. Unbound or different-tenant stores are rejected; re-run portal-cookie after selecting the intended tenant to re-import.

The apiproxy backend needs more than sccauth/xsrf-token: it also relies on the routing cookie X-PortalEndpoint-RouteKey (pins the request to your tenant’s regional backend) and the session cookie s.SessID. Omit those and the backend answers with an opaque HTTP 500, which is why the whole header is required, not just a couple of cookies. To capture it, in Microsoft Edge (logged in to security.microsoft.com) open DevTools, go to Network, right-click the timeline apiproxy request, then Copy and Copy as cURL (bash), save it, and run (only Edge plus the bash cURL variant is tested; Chrome/Firefox and the cmd/PowerShell variants are unverified):

Terminal window
xdr auth portal-cookie mde-curl.txt # a saved "Copy as cURL (bash)" or Cookie header
pbpaste | xdr auth portal-cookie - # or pipe it in via stdin

xdr extracts the full Cookie header, forwards it verbatim (chunked sccauth, chunks:N plus sccauthC1…N, included), and auto-extracts the XSRF token. The only interactive prompt is a hidden xsrf-token fallback, shown only when the header carries no XSRF-TOKEN cookie; an empty answer is rejected. A file or stdin is required rather than a paste prompt because the header runs past the ~4 KB a terminal will accept on one line.

Options:

  • --verify/--no-verify (default: verify). After storing, make one lightweight apiproxy call to confirm the cookies work. The result reports verified and, on failure, a verify_error.
  • --keep-source. By default, on a successful import xdr makes a best-effort overwrite and deletion of the regular source file it validated (it holds a live session bearer credential). Pass --keep-source to keep it. Symlinks are rejected and file identity is rechecked before overwrite; a detected mismatch stops cleanup and warns you to remove the original capture manually. stdin (-) is not a file and is never shredded. A source containing no sccauth cookie is rejected and left untouched (nothing is stored).

2. --refresh-token (env MDE_REFRESH_TOKEN)

Section titled “2. --refresh-token (env MDE_REFRESH_TOKEN)”

For CI/non-interactive use on the official backend: redeems a pre-obtained FOCI refresh token instead of stored cookies. It wins over a stored cookie. Under --backend portal-cookie it is rejected as a configuration error.

xdr auth portal-logout removes ~/.xdr-cli/portal_cookies.json. It does not touch the main ~/.xdr-cli/token_cache.json used by xdr auth login/xdr auth logout. A missing cookie file is a no-op.

Cookie secrets are never passed as device timeline flags. The Cookie header is accepted by portal-cookie only from a file or stdin (-), the XSRF token is auto-extracted from it (or entered at a hidden prompt only when the header has no XSRF-TOKEN cookie), and the result is stored in ~/.xdr-cli/portal_cookies.json. Cookie values never reach argv.

--refresh-token can be passed as a flag, but prefer the MDE_REFRESH_TOKEN environment variable instead. A value passed as --refresh-token lands in shell history and is visible to anyone who can run ps while the command is executing; the environment variable avoids both. The flag form is also captured by session recording: its value is replaced with ***REDACTED*** before being written to the session JSONL (both --refresh-token SECRET and --refresh-token=SECRET forms), but the flag itself is still best avoided. The environment variable never enters argv.

OptionBehaviour
DEVICEHostname or 40-hex MachineId (required).
--fromWindow start; defaults to the lookback window.
--toWindow end; defaults to now.
--days NLookback days when --from is not given (min 1; default 7).
--hours NLookback hours when --from is not given (min 1). Mutually exclusive with --days.
--output PATH, -oAtomically write owner-only JSONL here; refuses existing paths and unsafe shared directories.
--gzip, -zGzip the output file (.jsonl.gz). Requires --output.
--forceReplace an existing --output path; replaces a symlink entry, never its target. Requires --output.
--page-size NEvents per request (min 1; default 1000; max 1000).
--refresh-tokenCI/non-interactive FOCI refresh token (env MDE_REFRESH_TOKEN). Official backend only; rejected under --backend portal-cookie.

--hours and --days are mutually exclusive; supplying both is a usage error. When neither --from, --hours, nor --days is supplied, the default lookback is seven days ending now.

--from and --to accept RFC3339-style values (for example 2026-07-24T21:00:00Z). The accepted formats are YYYY-MM-DDTHH:MM:SS<offset>, YYYY-MM-DDTHH:MM:SS.ffffff<offset>, YYYY-MM-DDTHH:MM:SS, YYYY-MM-DD HH:MM:SS, and YYYY-MM-DD. A value with no UTC offset is treated as UTC. --from must be at or before --to.

The 180-day cap is enforced on the resolved window, not just on --days: an explicit --from/--to span or an hours-based window over the limit is rejected before any request is made, rather than being silently truncated server-side.

Device resolution runs through the selected backend’s inventory. The official backend resolves a hostname through the MDE machines API and, when stored cookies supply the credential, also verifies a supplied MachineId there. With xdr --backend portal-cookie device timeline DEVICE, tenant verification, MachineId verification, and hostname lookup all use portal requests (an exact, unique match in the portal inventory’s 180-day view); no app registration or MSAL credentials are required. On either backend an ambiguous hostname is rejected (the official backend lists the candidate MachineIds; the portal backend asks for a MachineId). Timeline events stream through the same portal adapter.

With no --output, the complete stream is atomically registered under ~/.xdr-cli/results/ and stdout is a receipt plus at most two preview rows. Explicit --output PATH (optionally --gzip) writes a plain raw-JSONL file instead. It writes through an owner-only temporary file and publishes atomically while keeping the original staging descriptor open, refusing an existing path by default. Non-sticky group/world-writable output directories are rejected; owner-only directories and sticky temporary directories are supported. Add --force to replace an existing path; a symlink entry is replaced rather than followed. A failed download leaves no partial destination. A one-line event-count summary is printed to stderr.

This is a read-only command: it does not modify device state.

Identifiers & Advanced Hunting cross-reference

Section titled “Identifiers & Advanced Hunting cross-reference”

The device argument is either a DeviceName (hostname) or a 40-hex DeviceId (MachineId). A hostname is resolved to its MachineId by an exact, unambiguous match in the selected backend’s inventory: the MDE machines API on the official backend, the portal inventory (180-day view) under --backend portal-cookie. A 40-hex value is normally used directly; when stored cookies supply the credential, the selected backend confirms it first, and a mismatch is a conflict error.

The portal timeline’s MachineId is the same identifier as Advanced Hunting’s DeviceId (verified against live tenant data), and the hostname is Advanced Hunting’s DeviceName. So a device timeline run pivots cleanly into Advanced Hunting on DeviceId. Each streamed event also carries this pair under Machine:

Timeline fieldAdvanced Hunting columnMeaning
Machine.MachineId (and the device you pass, once resolved)DeviceId40-hex device identifier
Machine.NameDeviceNameFQDN / hostname (case-insensitive)
Machine.Domain(host domain suffix)AD domain of the host

The same DeviceId + DeviceName pair keys every Device* Advanced Hunting table, so you can cross-reference a timeline against structured hunt data: DeviceInfo, DeviceEvents, DeviceFileCertificateInfo, DeviceFileEvents, DeviceImageLoadEvents, DeviceLogonEvents, DeviceNetworkEvents, DeviceNetworkInfo, DeviceProcessEvents, DeviceRegistryEvents, DeviceTvmInfoGathering, and DeviceTvmSecureConfigurationAssessment.

Pivot predicate (swap in any Device* table, e.g. via xdr hunt run):

let hostname = 'workstation-01.contoso.com';
let device_id = '0000000000000000000000000000000000000000'; // the timeline MachineId
DeviceProcessEvents
| where Timestamp > ago(30d)
| where DeviceId == device_id and DeviceName =~ hostname
| summarize Records = count(), FirstSeen = min(Timestamp), LastSeen = max(Timestamp)
  • Unofficial and unstable. This endpoint is not documented by Microsoft and is not covered by any support agreement. It can change shape or disappear without warning.
  • Rate limits apply. Large windows against busy devices can hit portal-side throttling; use --hours where practical. Page size is tunable via --page-size (default 1000, max 1000) if you need to retune request cadence.
  • Coordinate with your SOC before relying on the refresh-token path in production. --refresh-token/MDE_REFRESH_TOKEN redeems the token as the “Microsoft Azure CLI” client, and that client plus a non-browser (Python) user agent is a pattern some detection content specifically flags as suspicious. Loop in whoever tunes your Entra/Defender detections before running it against a real tenant, especially at scale.
  • Check your organization’s policy on first-party-app impersonation. This applies only to the --refresh-token/MDE_REFRESH_TOKEN path; there is no interactive portal OAuth sign-in, and portal-cookie reuses your own browser session. Authenticating as the Azure CLI’s own client ID to reach an API it wasn’t built for is exactly what “impersonating a first-party Microsoft app”, a practice some organizations explicitly forbid, means. If in doubt, use portal-cookie instead, or don’t use the refresh-token path.
  • README: command reference and the project overview.
  • Session mechanics: how session recording redacts the device timeline --refresh-token value.