user_activity
Audit one Google Workspace account across apps to see what an attacker did after compromise, surfacing security flags, OAuth grants, source IPs, and a merged activity timeline.
Instructions
Everything ONE account did across Workspace in a window, newest first.
The first question after an upstream identity provider reports an account
as compromised: did the attacker get into Google, and what did they do
there? login_audit cannot answer it — it is a domain-wide view of the
events Google itself raised, so it shows neither one named account's
logins nor the logins Google did not flag. This tool asks the Reports API
for that one account (userKey=<username>, server-side) across several
applications at once and merges the answers.
Read flags first. It holds the events that change the account's
security posture or mark it as under attack, regardless of how much
ordinary activity surrounds them: a second factor enrolled or disabled
(2sv_enroll survives a password reset — it is the attacker's way back
in), recovery email/phone changes, a password change, outbound mail
forwarding to another domain, Google's own risky_sensitive_action_*
verdicts, a login Google marked is_suspicious, and every OAuth
authorize (client and scopes — note that a service account
impersonating the user through domain-wide delegation, this server's own
Gmail tools included, appears here too, as an authorize whose
client_id is the service account's numeric id). Google's
account_disabled_* / suspicious_login / gov_attack_warning
verdicts are flags too, but they are NOT served under the account's own
userKey (their actor is Google), so they are fetched separately the
way login_audit does and reported under the synthetic application
login_verdicts — its event_errors names any verdict probe that
failed. ips lists the distinct source addresses with first/last seen
and which applications each touched — the input to ip_activity for
the "did the same source reach other accounts" question. timeline is
the merged event list (Drive read/edit/download noise is counted under
applications.drive.events but kept off the timeline), cut at
max_events with timeline_truncated saying so. Counts on
applications, ips and the timeline are raw deliveries: an event
the API delivers under two applications counts twice there and once in
flags.
Coverage is explicit: window.start is the time actually asked for,
each application reports its own capped (more pages existed) or
error (that application failed — the others still answer), and
capped at the top is their union, a failed application included.
Reports data for the current day can lag by hours, so an empty window
right after an incident is "not visible yet", not "nothing happened". A
missing DWD scope or a rejected credential is a whole-call
{"error": ...}.
Read-only (Reports API activities().list only, the same
admin.reports.audit.readonly DWD scope as login_audit).
Args:
username: Exact user email, passed through as the Reports API
userKey (primary or alias address both work on Google's side).
hours: Window length ending now.
applications: Comma-separated Reports API application names to walk.
Default: login, token, user_accounts, drive, groups, calendar,
chat, meet, saml. gmail is deliberately not in the default —
not every tenant has it — pass it explicitly where it exists.
max_pages: Page budget per application (1000 events per page). The
verdict probes use their own small budget (VERDICT_MAX_PAGES).
max_events: Timeline rows returned (newest first); flags, ips and
counts are computed over everything fetched regardless.
domain: Configured [domain.*] section to route the lookup
through. Default: resolved from the username's suffix.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| hours | No | ||
| domain | No | ||
| username | Yes | ||
| max_pages | No | ||
| max_events | No | ||
| applications | No |