Detection rules › Panther
Panther rules: notion
Notion Audit Log Exported
#A Notion User exported audit logs for your organization’s workspace.
Detection logic
from panther_notion_helpers import notion_alert_context
def rule(event):
event_type = event.deep_get("event", "type", default="<NO_EVENT_TYPE_FOUND>")
return event_type == "workspace.audit_log_exported"
def title(event):
user = event.deep_get("event", "actor", "person", "email", default="<NO_USER_FOUND>")
workspace_id = event.deep_get("event", "workspace_id", default="<NO_WORKSPACE_ID_FOUND>")
duration_in_days = event.deep_get(
"event",
"details",
"duration_in_days",
default="<NO_DURATION_IN_DAYS_FOUND>",
)
return (
f"Notion User [{user}] exported audit logs for the last "
f"{duration_in_days} days for workspace id {workspace_id}"
)
def alert_context(event):
return notion_alert_context(event)
Rule specification
AnalysisType: rule
Filename: notion_workspace_audit_log_exported.py
RuleID: "Notion.Audit.Log.Exported"
DisplayName: "Notion Audit Log Exported"
Enabled: true
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Data Security
- Data Exfiltration
Severity: Medium
Description: A Notion User exported audit logs for your organization’s workspace.
DedupPeriodMinutes: 60
Threshold: 1
Runbook: Possible Data Exfiltration. Follow up with the Notion User to determine if this was done for a valid business reason.
Reference: https://www.notion.so/help/audit-log#export-your-audit-log
Stages and Predicates
Fires on Notion.AuditLogs events when the condition below holds.
Condition
event.typeisworkspace.audit_log_exported
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.type | eq |
| field:"event.type" kind:eq value:"workspace.audit_log_exported" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
duration_in_days | event.details.duration_in_days |
workspace_id | event.workspace_id |
Response runbook
Possible Data Exfiltration. Follow up with the Notion User to determine if this was done for a valid business reason.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"event": {
"actor": {
"id": "..",
"object": "user",
"person": {
"email": "homer.simpson@yourcompany.io"
},
"type": "person"
},
"details": {
"duration_in_days": 30
},
"id": "...",
"ip_address": "...",
"platform": "web",
"timestamp": "2023-05-15T19:14:21.031Z",
"type": "workspace.audit_log_exported",
"workspace_id": ".."
}
}
Notion Login From Blocked IP
#A user attempted to access Notion from a blocked IP address. Note: before deployinh, make sure to add Rule Filters checking if event.ip_address is in a certain CIDR range(s).
Detection logic
from panther_notion_helpers import notion_alert_context
def rule(event):
# Users can specify inline-filters to permit rules based on IPs
return event.deep_get("event", "type", default="<NO_EVENT_TYPE_FOUND>") == "user.login"
def title(event):
user = event.deep_get("event", "actor", "person", "email", default="<NO_USER_FOUND>")
ip_addr = event.deep_get("event", "ip_address", default="<UNKNOWN IP>")
return f"Notion User [{user}] attempted to login from a blocked IP: [{ip_addr}]."
def alert_context(event):
return notion_alert_context(event)
Rule specification
AnalysisType: rule
Filename: notion_login_from_blocked_ip.py
RuleID: "Notion.LoginFromBlockedIP"
DisplayName: "Notion Login From Blocked IP"
Enabled: false
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Network Security Monitoring
- Malicious Connections
- Configuration Required
Severity: Medium
Description: "A user attempted to access Notion from a blocked IP address. Note: before deployinh, make sure to add Rule Filters checking if event.ip_address is in a certain CIDR range(s)."
DedupPeriodMinutes: 60
Threshold: 1
Runbook: Confirm with user if the login was legitimate. If so, determine why the IP is blocked.
Reference: https://www.notion.so/help/allowlist-ip
Stages and Predicates
Fires on Notion.AuditLogs events when the condition below holds.
Condition
event.typeisuser.login
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.type | eq |
| field:"event.type" kind:eq value:"user.login" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
ip_address | event.ip_address |
Response runbook
Confirm with user if the login was legitimate. If so, determine why the IP is blocked.
Notion Login from New Location
#A Notion User logged in from a new location.
Detection logic
import datetime
import json
import time
from panther_detection_helpers.caching import get_dictionary, put_dictionary
from panther_ipinfo_helpers import IPInfoLocation
from panther_notion_helpers import notion_alert_context
# How long (in seconds) to keep previous login locations in cached memory
DEFAULT_CACHE_PERIOD = 2419200
def rule(event):
# Only focused on login events
if event.deep_walk("event", "type") != "user.login":
return False
# Get the user's location, via IPInfo
# Return False if we have no location information
if "ipinfo_location" not in event.get("p_enrichment", {}):
return False
# pylint: disable=global-variable-undefined
global IPINFO_LOC
IPINFO_LOC = IPInfoLocation(event)
path_to_ip = "event.ip_address"
city = IPINFO_LOC.city(path_to_ip) or ""
region = IPINFO_LOC.region(path_to_ip) or ""
country = IPINFO_LOC.country(path_to_ip) or ""
loc_string = "_".join((city, region, country))
# Store the login location. The premise is to create a new entry for each combimation of user
# and location, and then have those records persist for some length of time (4 weeks by
# default).
# Store the login location. Here, we use Panther's cache to store a dictionary, using the
# user's unique ID to ensure it hold data unique to them. In this dictionary, we'll use the
# location strings (loc_string) as the key, and the values will be the timestamp of the last
# recorded login from that location.
user = event.deep_walk("event", "actor", "id")
cache = get_dictionary(user) or {}
# If this is a unit test, convert cache from string
if isinstance(cache, str):
cache = json.loads(cache)
# -- Step 1: Record this login.
new_cache = cache.copy()
new_cache[loc_string] = time.time()
put_dictionary(user, new_cache)
# -- Step 2: Determine if we shoul raise an alert.
if not cache:
# User hasn't been recorded logging in before. Since this is their first login, we don't
# have a baseline to know if it's unusual, so we won't raise an alert.
return False
if is_recent_login(cache, loc_string, event.get("p_parse_time")):
# User has logged in from this location in the recent past. No need to raise an alert.
return False
# User has NOT logged in from this location in the recent past - we should trigger an alert!
return True
def title(event):
path_to_ip = "event.ip_address"
city = IPINFO_LOC.city(path_to_ip)
region = IPINFO_LOC.region(path_to_ip)
country = IPINFO_LOC.country(path_to_ip)
user_email = event.deep_walk("event", "actor", "person", "email", default="UNKNOWN_EMAIL")
return f"Notion [{user_email}] logged in from a new location: {city}, {region}, {country}."
def alert_context(event):
path_to_ip = "event.ip_address"
city = IPINFO_LOC.city(path_to_ip)
region = IPINFO_LOC.region(path_to_ip)
country = IPINFO_LOC.country(path_to_ip)
user_email = event.deep_walk("event", "actor", "person", "email", default="UNKNOWN_EMAIL")
context = notion_alert_context(event)
context["user_email"] = user_email
context["location"] = {"city": city, "region": region, "country": country}
return context
def is_recent_login(cache: dict, loc_string: str, parse_time: str) -> bool:
# Use p_parse_time to calculate current timestamp, so that unit tests work.
now = time.mktime(datetime.datetime.fromisoformat(parse_time[:23]).timetuple())
return (
loc_string in cache # location was previously recorded
and cache[loc_string] > now - DEFAULT_CACHE_PERIOD # last recorded login is recent
)
Rule specification
AnalysisType: rule
Filename: notion_login_from_new_location.py
RuleID: "Notion.LoginFromNewLocation"
DisplayName: "Notion Login from New Location"
Enabled: true
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Identity & Access Management
- Login & Access Patterns
Severity: Medium
Description: A Notion User logged in from a new location.
DedupPeriodMinutes: 60
Threshold: 1 # Number of pages deleted; please change this value to suit your organization's needs.
Runbook: Possible account takeover. Follow up with the Notion User to determine if this login is genuine.
Reference: https://ipinfo.io/products/ip-geolocation-api
Stages and Predicates
Fires on Notion.AuditLogs events when all of the conditions below hold.
Condition
event.typeisuser.loginp_enrichmentcontainsipinfo_location
This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.type | eq |
| field:"event.type" kind:eq value:"user.login" |
p_enrichment | contains |
| field:"p_enrichment" kind:contains value:"ipinfo_location" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
Response runbook
Possible account takeover. Follow up with the Notion User to determine if this login is genuine.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"event": {
"actor": {
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"object": "user",
"person": {
"email": "aragorn.elessar@lotr.com"
},
"type": "person"
},
"details": {
"authType": "email"
},
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"ip_address": "192.168.100.100",
"platform": "web",
"timestamp": "2023-06-12 21:40:28.690000000",
"type": "user.login",
"workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"p_enrichment": {
"ipinfo_location": {
"event.ip_address": {
"city": "Barad-Dur",
"country": "Mordor",
"lat": "0.00000",
"lng": "0.00000",
"postal_code": "55555",
"region": "Mount Doom",
"region_code": "MD",
"timezone": "Middle Earth/Mordor"
}
}
},
"p_event_time": "2023-06-12 21:40:28.690000000",
"p_log_type": "Notion.AuditLogs",
"p_parse_time": "2023-06-12 22:53:51.602223297",
"p_row_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"p_schema_version": 0,
"p_source_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"p_source_label": "Notion Logs"
}
Notion Login WITH AccountChange
#A Notion User logged in then changed their account details.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation |
Rule specification
AnalysisType: correlation_rule
RuleID: "Notion.Login.WITH.AccountChange"
DisplayName: "Notion Login WITH AccountChange"
Enabled: false
Severity: Medium
Description: A Notion User logged in then changed their account details.
Reference: https://www.notion.so/help/account-settings
Runbook: Possible account takeover. Follow up with the Notion User to determine if this email change is genuine.
Reports:
MITRE ATT&CK:
- TA0004:T1098 # Account Manipulation
Detection:
- Group:
- ID: Login
RuleID: Notion.Login
- ID: AccountChange
RuleID: Notion.AccountChange
MatchCriteria:
field_name:
- GroupID: Login
Match: p_alert_context.actor_id
- GroupID: AccountChange
Match: p_alert_context.actor_id
LookbackWindowMinutes: 1800
Schedule:
RateMinutes: 1440
TimeoutMinutes: 5
Stages and Predicates
Fires when the steps below all occur within 30h, correlated by p_alert_context.actor_id. Each step needs one match unless a higher minimum is shown.
Stage 1: step Login
References detection Signal - Notion Login.
Stage 2: step AccountChange
References detection Signal - Notion Account Changed.
Response runbook
Possible account takeover. Follow up with the Notion User to determine if this email change is genuine.
Notion Many Pages Deleted
#A Notion User deleted multiple pages, which were not created or restored from the trash within the same hour.
Detection logic
def rule(_):
return True
def title(event):
user = event.get("user", "<NO_USER_FOUND>")
return f"Notion User [{user}] deleted multiple pages."
Rule specification
AnalysisType: scheduled_rule
Filename: notion_many_pages_deleted_sched.py
RuleID: "Notion.Many.Pages.Deleted.Sched"
DisplayName: "Notion Many Pages Deleted"
Enabled: true
ScheduledQueries:
- Notion Many Pages Deleted Query
Tags:
- Notion
- Data Security
- Data Destruction
Severity: Medium
Description: A Notion User deleted multiple pages, which were not created or restored from the trash within the same hour.
DedupPeriodMinutes: 60
Threshold: 10 # Number of pages deleted; please change this value to suit your organization's needs.
Runbook: Possible Data Destruction. Follow up with the Notion User to determine if this was done for a valid business reason.
Reference: https://www.notion.so/help/duplicate-delete-and-restore-content
Stages and Predicates
Rule logic
This rule alerts on rows returned by its scheduled query Notion Many Pages Deleted Query; its Python module (Detection logic above) shapes the alert rather than filtering.
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field |
|---|
user |
Response runbook
Possible Data Destruction. Follow up with the Notion User to determine if this was done for a valid business reason.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"actions": [
"page.deleted"
],
"id": "1360a5bb-da41-8177-bedb-d015d012392a",
"page_name": "Newslette",
"user": "bob.ross@happytrees.com"
}
Notion Many Pages Deleted [Deprecated]
#(Deprecated due to false-positive rate) A Notion User deleted multiple pages.
Detection logic
from panther_notion_helpers import notion_alert_context
def rule(event):
return event.deep_get("event", "type", default="<NO_EVENT_TYPE_FOUND>") == "page.deleted"
def title(event):
user = event.deep_get("event", "actor", "person", "email", default="<NO_USER_FOUND>")
return f"Notion User [{user}] deleted multiple pages."
def alert_context(event):
context = notion_alert_context(event)
page_id = event.deep_get("event", "details", "target", "page_id", default="<NO_PAGE_ID_FOUND>")
context["page_id"] = page_id
return context
Rule specification
AnalysisType: rule
Filename: notion_many_pages_deleted.py
RuleID: "Notion.Many.Pages.Deleted"
DisplayName: "Notion Many Pages Deleted [Deprecated]"
Status: Deprecated
Enabled: false
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Data Security
- Data Destruction
- Deprecated
Severity: Medium
Description: (Deprecated due to false-positive rate) A Notion User deleted multiple pages.
DedupPeriodMinutes: 60
Threshold: 10 # Number of pages deleted; please change this value to suit your organization's needs.
Runbook: Possible Data Destruction. Follow up with the Notion User to determine if this was done for a valid business reason.
Reference: https://www.notion.so/help/duplicate-delete-and-restore-content
Stages and Predicates
Fires on Notion.AuditLogs events when the condition below holds.
Condition
event.typeispage.deleted
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.type | eq |
| field:"event.type" kind:eq value:"page.deleted" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
Response runbook
Possible Data Destruction. Follow up with the Notion User to determine if this was done for a valid business reason.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"event": {
"actor": {
"id": "af06b6ff-dd5e-4024-b9ef-78fe77f55884",
"object": "user",
"person": {
"email": "homer.simpson@yourcompany.io"
},
"type": "person"
},
"details": {
"parent": {
"database_id": "543af759-3010-4355-a71e-4sdfs3566a",
"type": "database_id"
},
"target": {
"page_id": "93cf05d3-6805-4ddc-abba-adsfjhnlkwje785",
"type": "page_id"
}
},
"id": "768873bf-6b2c-40e8-b27c-1c199c4d6ae7",
"ip_address": "12.12.12.12",
"platform": "web",
"timestamp": "2023-05-24 20:17:41.905000000",
"type": "page.deleted",
"workspace_id": "ea65b016-6abc-4dcf-808b-sdfg445654"
}
}
Notion Many Pages Deleted Query
#A Notion User deleted multiple pages, which were not created or restored from the trash within the same hour.
Rule specification
AnalysisType: scheduled_query
QueryName: Notion Many Pages Deleted Query
Enabled: false
Tags:
- Notion
- Data Security
- Data Destruction
Description: >
A Notion User deleted multiple pages, which were not created or restored from the trash within the same hour.
SnowflakeQuery: |
SELECT
event:actor.person.email AS user
,ARRAY_AGG(event:type) AS actions
,event:details.page_name AS page_name
,event:details.target.page_id AS id
FROM
panther_logs.public.notion_auditlogs
WHERE
p_occurs_since(1 hour)
AND event:type IN ('page.deleted','page.created','page.restored_from_trash')
AND event:details.target.type = 'page_id'
AND page_name != ''
AND event:actor.type = 'person'
GROUP BY id, user, page_name
HAVING
actions = ARRAY_CONSTRUCT('page.deleted')
DatabricksQuery: |
SELECT
event.actor.person.email AS user
,COLLECT_LIST(event:type) AS actions
,event.details.page_name AS page_name
,event.details.target.page_id AS id
FROM
panther_logs.notion_auditlogs
WHERE
p_occurs_since(1 hour)
AND event:type IN ('page.deleted','page.created','page.restored_from_trash')
AND event.details.target.type = 'page_id'
AND event.details.page_name != ''
AND event.actor.type = 'person'
GROUP BY id, user, page_name
HAVING
actions = ARRAY('page.deleted')
Schedule:
RateMinutes: 60
TimeoutMinutes: 2
Stages and Predicates
Stage 1: source
Stage 2: filter
event:typeis one ofpage.deleted,page.created,page.restored_from_trash
This rule also runs imperative logic the parser cannot express as a filter. The conditions above are the structured part it could extract.
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event:type | in |
| field:"event:type" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
user | event:actor . person.email |
actions | ARRAY_AGG ( event:type ) |
page_name | event:details . page_name |
id | event:details . target.page_id |
Notion Many Pages Exported
#A Notion User exported multiple pages.
Detection logic
from panther_notion_helpers import notion_alert_context
def rule(event):
return event.deep_get("event", "type", default="<NO_EVENT_TYPE_FOUND>") == "page.exported"
def title(event):
user = event.deep_get("event", "actor", "person", "email", default="<NO_USER_FOUND>")
return f"Notion User [{user}] exported multiple pages."
def alert_context(event):
context = notion_alert_context(event)
page_id = event.deep_get("event", "details", "target", "page_id", default="<NO_PAGE_ID_FOUND>")
context["page_id"] = page_id
return context
Rule specification
AnalysisType: rule
Filename: notion_many_pages_exported.py
RuleID: "Notion.Many.Pages.Exported"
DisplayName: "Notion Many Pages Exported"
Enabled: true
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Data Security
- Data Exfiltration
Severity: High
Description: A Notion User exported multiple pages.
DedupPeriodMinutes: 60
Threshold: 10 # Number of pages exported; please change this value to suit your organization's needs.
Runbook: Possible Data Exfiltration. Follow up with the Notion User to determine if this was done for a valid business reason.
Reference: https://www.notion.so/help/export-your-content
Stages and Predicates
Fires on Notion.AuditLogs events when the condition below holds.
Condition
event.typeispage.exported
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.type | eq |
| field:"event.type" kind:eq value:"page.exported" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
Response runbook
Possible Data Exfiltration. Follow up with the Notion User to determine if this was done for a valid business reason.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"event": {
"actor": {
"id": "bd37477c-869d-418b-abdb-0fc727b38b5e",
"object": "user",
"person": {
"email": "homer.simpson@yourcompany.io"
},
"type": "person"
},
"details": {
"parent": {
"type": "workspace_id",
"workspace_id": "ab99as87-6abc-4dcf-808b-111999882299"
},
"target": {
"page_id": "3cd2c560-d1b9-474e-b46e-gh8899002763",
"type": "page_id"
}
},
"id": "d4b9963f-12a8-4b01-b597-233a140abf5e",
"ip_address": "12.12.12.12",
"platform": "web",
"timestamp": "2023-06-01 18:57:07.486000000",
"type": "page.exported",
"workspace_id": "ea65b016-6abc-4dcf-808b-e119617b55d1"
}
}
Notion Page API Permissions Changed
#A new API integration was added to a Notion page, or it's permissions were changed.
Detection logic
from panther_notion_helpers import notion_alert_context
# These event types correspond to users adding or editing the default role on a public page
event_types = (
"page.permissions.integration_role_added",
"page.permissions.integration_role_updated",
)
def rule(event):
return event.deep_get("event", "type", default="<NO_EVENT_TYPE_FOUND>") in event_types
def title(event):
user = event.deep_get("event", "actor", "person", "email", default="<NO_USER_FOUND>")
page_id = event.deep_get("event", "details", "target", "page_id", default="<NO_PAGE_ID_FOUND>")
return f"Notion User [{user}] added an integration to page [{page_id}]."
def alert_context(event):
context = notion_alert_context(event)
page_id = event.deep_get("event", "details", "target", "page_id", default="<NO_PAGE_ID_FOUND>")
context["page_id"] = page_id
return context
Rule specification
AnalysisType: rule
Filename: notion_page_accessible_to_api.py
DisplayName: "Notion Page API Permissions Changed"
RuleID: "Notion.PagePerms.APIPermsChanged"
Enabled: true
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Data Security
- Unapproved 3rd Party Apps
Severity: Low
Description: "A new API integration was added to a Notion page, or it's permissions were changed."
DedupPeriodMinutes: 60
Threshold: 1
Runbook: Potential information exposure - review the shared page and rectify if needed.
Reference: https://www.notion.so/help/sharing-and-permissions
Stages and Predicates
Fires on Notion.AuditLogs events when the condition below holds.
Condition
event.typeis one ofpage.permissions.integration_role_added,page.permissions.integration_role_updated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.type | in |
| field:"event.type" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
page_id | event.details.target.page_id |
Response runbook
Potential information exposure - review the shared page and rectify if needed.
Notion Page Guest Permissions Changed
#The external guest permissions for a Notion page have been altered.
Detection logic
from panther_base_helpers import deep_get
from panther_notion_helpers import notion_alert_context
# These event types correspond to users adding or editing the default role on a public page
event_types = ("page.permissions.guest_role_added", "page.permissions.guest_role_updated")
def rule(event):
return event.deep_get("event", "type", default="<NO_EVENT_TYPE_FOUND>") in event_types
def title(event):
user = event.deep_get("event", "actor", "person", "email", default="<NO_USER_FOUND>")
guest = event.deep_get(
"event", "details", "entity", "person", "email", default="<NO_USER_FOUND>"
)
page_id = event.deep_get("event", "details", "target", "page_id", default="<NO_PAGE_ID_FOUND>")
event_type = event.deep_get("event", "type", default="<NO_EVENT_TYPE_FOUND>")
action = {
"page.permissions.guest_role_added": "added a guest",
"page.permissions.guest_role_updated": "changed the guest permissions of",
}.get(event_type, "changed the guest permissions of")
return f"Notion User [{user}] {action} [{guest}] on page [{page_id}]."
def alert_context(event):
context = notion_alert_context(event)
page_id = event.deep_get("event", "details", "target", "page_id", default="<NO_PAGE_ID_FOUND>")
context["page_id"] = page_id
details = event.deep_get("event", "details", default={})
context["guest"] = deep_get(details, "entity", "person", "email", default="<NO_USER_FOUND>")
context["new_permission"] = deep_get(details, "new_permission", default="<UNKNOWN PERMISSION>")
context["old_permission"] = deep_get(details, "old_permission", default="<UNKNOWN PERMISSION>")
return context
Rule specification
AnalysisType: rule
Filename: notion_page_accessible_to_guests.py
RuleID: "Notion.PagePerms.GuestPermsChanged"
DisplayName: "Notion Page Guest Permissions Changed"
Enabled: true
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Data Security
- Information Disclosure
Severity: Low
Description: The external guest permissions for a Notion page have been altered.
DedupPeriodMinutes: 60
Threshold: 1
Runbook: Potential information exposure - review the shared page and rectify if needed.
Reference: https://www.notion.so/help/sharing-and-permissions
Stages and Predicates
Fires on Notion.AuditLogs events when the condition below holds.
Condition
event.typeis one ofpage.permissions.guest_role_added,page.permissions.guest_role_updated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.type | in |
| field:"event.type" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
email | event.details.entity.person.email |
page_id | event.details.target.page_id |
Response runbook
Potential information exposure - review the shared page and rectify if needed.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"event": {
"actor": {
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"object": "user",
"person": {
"email": "aragorn.elessar@lotr.com"
},
"type": "person"
},
"details": {
"entity": {
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"object": "user",
"person": {
"email": "frodo.baggins@lotr.com"
},
"type": "person"
},
"new_permission": "full_access",
"old_permission": "none",
"page_audience": "shared_internally",
"target": {
"page_id": "441356b5-557b-4053-8d2f-7932d2607d66",
"type": "page_id"
}
},
"id": "e18690f8-e24b-4b03-ba6f-123eb7ec0f08",
"timestamp": "2023-08-11 23:02:53.113000000",
"type": "page.permissions.guest_role_added",
"workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
}
Notion SAML SSO Configuration Changed
#A Notion User changed settings to enforce SAML SSO configurations for your organization.
Detection logic
from panther_notion_helpers import notion_alert_context
def rule(event):
return (
event.deep_get("event", "type", default="<NO_EVENT_TYPE_FOUND>")
== "workspace.settings.enforce_saml_sso_config_updated"
)
def title(event):
user = event.deep_get("event", "actor", "person", "email", default="<NO_USER_FOUND>")
workspace_id = event.deep_get("event", "workspace_id", default="<NO_WORKSPACE_ID_FOUND>")
state = event.deep_get(
"event",
"workspace.settings.enforce_saml_sso_config_updated",
"state",
default="<NO_STATE_FOUND>",
)
if state == "enabled":
return (
f"Notion User [{user}] updated settings to enable SAML SSO config "
f"from workspace id {workspace_id}"
)
return (
f"Notion User [{user}] updated settings to disable SAML SSO config "
f"from workspace id {workspace_id}"
)
def severity(event):
state = event.deep_get(
"event",
"workspace.settings.enforce_saml_sso_config_updated",
"state",
default="<NO_STATE_FOUND>",
)
if state == "enabled":
return "INFO"
return "HIGH"
def alert_context(event):
return notion_alert_context(event)
Rule specification
AnalysisType: rule
Filename: notion_workspace_settings_enforce_saml_sso_config_updated.py
RuleID: "Notion.SAML.SSO.Configuration.Changed"
DisplayName: "Notion SAML SSO Configuration Changed"
Enabled: true
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Identity & Access Management
- Credential Security
Severity: High
Description: A Notion User changed settings to enforce SAML SSO configurations for your organization.
DedupPeriodMinutes: 60
Threshold: 1
Runbook: Follow up with the Notion User to determine if this was done for a valid business reason and to ensure these settings get re-enabled quickly for best security practices.
Reference: https://www.notion.so/help/saml-sso-configuration
Stages and Predicates
Fires on Notion.AuditLogs events when the condition below holds.
Condition
event.typeisworkspace.settings.enforce_saml_sso_config_updated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.type | eq |
| field:"event.type" kind:eq value:"workspace.settings.enforce_saml_sso_config_updated" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
workspace_id | event.workspace_id |
Response runbook
Follow up with the Notion User to determine if this was done for a valid business reason and to ensure these settings get re-enabled quickly for best security practices.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"event": {
"actor": {
"id": "..",
"object": "user",
"person": {
"email": "homer.simpson@yourcompany.io"
},
"type": "person"
},
"id": "...",
"ip_address": "...",
"platform": "web",
"timestamp": "2023-05-15T19:14:21.031Z",
"type": "workspace.settings.enforce_saml_sso_config_updated",
"workspace.settings.enforce_saml_sso_config_updated": {
"state": "enabled"
},
"workspace_id": ".."
}
}
Notion SCIM Token Generated
#A Notion User generated a SCIM token.
Detection logic
from panther_notion_helpers import notion_alert_context
def rule(event):
event_type = event.deep_get("event", "type", default="<NO_EVENT_TYPE_FOUND>")
return event_type == "workspace.scim_token_generated"
def title(event):
user = event.deep_get("event", "actor", "person", "email", default="<NO_USER_FOUND>")
workspace_id = event.deep_get("event", "workspace_id", default="<NO_WORKSPACE_ID_FOUND>")
return f"Notion User [{user}] generated a SCIM token for workspace id [{workspace_id}]."
def alert_context(event):
return notion_alert_context(event)
Rule specification
AnalysisType: rule
Filename: notion_scim_token_generated.py
RuleID: "Notion.Workspace.SCIM.Token.Generated"
DisplayName: "Notion SCIM Token Generated"
Enabled: true
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Application Security
- Supply Chain Attack
Description: A Notion User generated a SCIM token.
Severity: Medium
DedupPeriodMinutes: 60
Threshold: 1
Runbook: Possible Initial Access. Follow up with the Notion User to determine if this was done for a valid business reason.
Reference: https://www.notion.so/help/provision-users-and-groups-with-scim
Stages and Predicates
Fires on Notion.AuditLogs events when the condition below holds.
Condition
event.typeisworkspace.scim_token_generated
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.type | eq |
| field:"event.type" kind:eq value:"workspace.scim_token_generated" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
workspace_id | event.workspace_id |
Response runbook
Possible Initial Access. Follow up with the Notion User to determine if this was done for a valid business reason.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"event": {
"actor": {
"id": "..",
"object": "user",
"person": {
"email": "homer.simpson@yourcompany.com"
},
"type": "person"
},
"id": "...",
"ip_address": "...",
"platform": "mac-desktop",
"timestamp": "2023-06-02T20:21:01.873Z",
"type": "workspace.scim_token_generated",
"workspace.scim_token_generated": {},
"workspace_id": "123"
}
}
Notion Sharing Settings Updated
#A Notion User enabled sharing for a Workspace or Teamspace.
Detection logic
from panther_notion_helpers import notion_alert_context
EVENTS = (
"teamspace.settings.allow_public_page_sharing_setting_updated",
"teamspace.settings.allow_guests_setting_updated",
"teamspace.settings.allow_content_export_setting_updated",
"workspace.settings.allow_public_page_sharing_setting_updated",
"workspace.settings.allow_guests_setting_updated",
"workspace.settings.allow_content_export_setting_updated",
)
def rule(event):
return all(
[
event.deep_get("event", "type", default="") in EVENTS,
event.deep_get("event", "details", "state", default="") == "enabled",
]
)
def title(event):
actor = event.deep_get("event", "actor", "person", "email", default="NO_ACTOR_FOUND")
action = event.deep_get("event", "type", default="NO.EVENT.FOUND").split(".")[2]
teamspace = event.deep_get("event", "details", "target", "name", default=None)
if teamspace:
return f"[{actor}] enabled [{action}] for [{teamspace}] Teamspace"
return f"[{actor}] enabled [{action}] for Workspace"
def alert_context(event):
return notion_alert_context(event)
Rule specification
AnalysisType: rule
Filename: notion_sharing_settings_updated.py
RuleID: "Notion.SharingSettingsUpdated"
DisplayName: "Notion Sharing Settings Updated"
Enabled: true
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Data Exfiltration
Description: A Notion User enabled sharing for a Workspace or Teamspace.
Severity: Medium
DedupPeriodMinutes: 60
Threshold: 1
Runbook: Possible Data Exfiltration. Follow up with the Notion User to determine if this was done for a valid business reason.
Stages and Predicates
Fires on Notion.AuditLogs events when all of the conditions below hold.
Condition
event.typeis one ofteamspace.settings.allow_public_page_sharing_setting_updated,teamspace.settings.allow_guests_setting_updated,teamspace.settings.allow_content_export_setting_updated,workspace.settings.allow_public_page_sharing_setting_updated,workspace.settings.allow_guests_setting_updatedevent.details.stateisenabled
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.details.state | eq |
| field:"event.details.state" kind:eq value:"enabled" |
event.type | in |
| field:"event.type" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
name | event.details.target.name |
Response runbook
Possible Data Exfiltration. Follow up with the Notion User to determine if this was done for a valid business reason.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"event": {
"actor": {
"id": "c16137bb-5078-4eac-b026-5cbd2f9a027a",
"object": "user",
"person": {
"email": "aaron@example.com"
},
"type": "person"
},
"details": {
"state": "enabled"
},
"id": "91b29a4b-4978-40e1-ab56-40221f801ce5",
"ip_address": "11.22.33.44",
"platform": "web",
"timestamp": "2023-12-13 16:39:06.860000000",
"type": "workspace.settings.allow_guests_setting_updated",
"workspace_id": "ea65b016-6abc-4dcf-808b-e119617b55d1"
}
}
Notion Teamspace Owner Added
#A Notion User was added as a Teamspace owner.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Privilege Escalation | No specific technique |
Detection logic
from panther_notion_helpers import notion_alert_context
def rule(event):
added = (
event.deep_get("event", "type", default="") == "teamspace.permissions.member_added"
and event.deep_get("event", "details", "role", default="") == "owner"
)
updated = (
event.deep_get("event", "type", default="") == "teamspace.permissions.member_role_updated"
and event.deep_get("event", "details", "new_role", default="") == "owner"
)
return added or updated
def title(event):
actor = event.deep_get("event", "actor", "person", "email", default="NO_ACTOR_FOUND")
member = event.deep_get(
"event", "details", "member", "person", "email", default="NO_MEMBER_FOUND"
)
teamspace = event.deep_get("event", "details", "target", "name", default="NO_TEAMSPACE_FOUND")
return f"[{actor}] added [{member}] as owner of [{teamspace}] Teamspace"
def alert_context(event):
return notion_alert_context(event)
Rule specification
AnalysisType: rule
Filename: notion_teamspace_owner_added.py
RuleID: "Notion.TeamspaceOwnerAdded"
DisplayName: "Notion Teamspace Owner Added"
Enabled: true
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Privilege Escalation
Description: A Notion User was added as a Teamspace owner.
Severity: Medium
DedupPeriodMinutes: 60
Threshold: 1
Runbook: Possible Privilege Escalation. Follow up with the Notion User to determine if this was done for a valid business reason.
Stages and Predicates
Fires on Notion.AuditLogs events when any of the conditions below holds.
Condition
any of:
all of:
event.typeisteamspace.permissions.member_addedevent.details.roleisowner
all of:
event.typeisteamspace.permissions.member_role_updatedevent.details.new_roleisowner
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.details.new_role | eq |
| field:"event.details.new_role" kind:eq value:"owner" |
event.details.role | eq |
| field:"event.details.role" kind:eq value:"owner" |
event.type | eq |
| field:"event.type" kind:eq |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
email | event.details.member.person.email |
name | event.details.target.name |
Response runbook
Possible Privilege Escalation. Follow up with the Notion User to determine if this was done for a valid business reason.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"event": {
"actor": {
"id": "c16137bb-5078-4eac-b026-5cbd2f9a027a",
"object": "user",
"person": {
"email": "malicious.insider@example.com"
},
"type": "person"
},
"details": {
"member": {
"id": "c16137bb-5078-4eac-b026-5cbd2f9a027a",
"object": "user",
"person": {
"email": "bad.dude@example.com"
},
"type": "person"
},
"new_role": "owner",
"target": {
"id": "b8db234d-71eb-49e2-a5ed-7935ca764920",
"name": "General",
"object": "teamspace"
}
},
"id": "6019b995-0158-4430-8263-89ad7905bd1d",
"ip_address": "11.22.33.44",
"platform": "web",
"timestamp": "2023-12-13 16:38:04.264000000",
"type": "teamspace.permissions.member_role_updated",
"workspace_id": "ea65b016-6abc-4dcf-808b-e119617b55d1"
}
}
Notion Workspace Exported
#A Notion User exported an existing workspace.
Detection logic
from panther_notion_helpers import notion_alert_context
def rule(event):
event_type = event.deep_get("event", "type", default="<NO_EVENT_TYPE_FOUND>")
return event_type == "workspace.content_exported"
def title(event):
user = event.deep_get("event", "actor", "person", "email", default="<NO_USER_FOUND>")
workspace_id = event.deep_get("event", "workspace_id", default="<NO_WORKSPACE_ID_FOUND>")
return f"Notion User [{user}] exported a workspace with workspace id [{workspace_id}]."
def alert_context(event):
return notion_alert_context(event)
Rule specification
AnalysisType: rule
Filename: notion_workspace_exported.py
RuleID: "Notion.Workspace.Exported"
DisplayName: "Notion Workspace Exported"
Enabled: true
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Data Security
- Data Exfiltration
Severity: High
Description: A Notion User exported an existing workspace.
DedupPeriodMinutes: 60
Threshold: 1
Runbook: Possible Data Exfiltration. Follow up with the Notion User to determine if this was done for a valid business reason.
Reference: https://www.notion.so/help/workspace-settings#export-an-entire-workspace
Stages and Predicates
Fires on Notion.AuditLogs events when the condition below holds.
Condition
event.typeisworkspace.content_exported
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.type | eq |
| field:"event.type" kind:eq value:"workspace.content_exported" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
workspace_id | event.workspace_id |
Response runbook
Possible Data Exfiltration. Follow up with the Notion User to determine if this was done for a valid business reason.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"event": {
"actor": {
"id": "..",
"object": "user",
"person": {
"email": "homer.simpson@yourcompany.io"
},
"type": "person"
},
"id": "...",
"ip_address": "...",
"platform": "mac-desktop",
"timestamp": "2023-06-02T20:16:41.217Z",
"type": "workspace.content_exported",
"workspace.content_exported": {},
"workspace_id": ".."
}
}
Notion Workspace public page added
#A Notion page was set to public in your worksace.
Detection logic
from panther_notion_helpers import notion_alert_context
def rule(event):
event_type = event.deep_get("event", "type", default="<NO_EVENT_TYPE_FOUND>")
return event_type == "workspace.settings.public_homepage_added"
def title(event):
actor = event.deep_get("event", "actor", "person", "email", default="<NO_EMAIL_FOUND>")
wkspc_id = event.deep_get("event", "workspace_id", default="<NO_WORKSPACE_ID_FOUND>")
db_id = event.deep_get(
"event",
"workspace.settings.public_homepage_added",
"new_public_page",
"database_id",
default="<NO_DATABASE_ID_FOUND>",
)
return f"Notion User [{actor}] added a new public homepage [{db_id}] in workspace [{wkspc_id}]"
def alert_context(event):
context = notion_alert_context(event)
workspace_id = event.deep_get("event", "workspace_id", default="<NO_WORKSPACE_ID_FOUND>")
db_id = event.deep_get(
"event",
"workspace.settings.public_homepage_added",
"new_public_page",
"database_id",
default="<NO_DATABASE_ID_FOUND>",
)
context["workspace_id"] = workspace_id
context["page_id"] = db_id
return context
Rule specification
AnalysisType: rule
Filename: notion_workspace_settings_public_homepage_added.py
RuleID: "Notion.Workspace.Public.Page.Added"
DisplayName: "Notion Workspace public page added"
Enabled: true
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Data Security
- Information Disclosure
Severity: Info
Description: A Notion page was set to public in your worksace.
DedupPeriodMinutes: 60
Threshold: 1
Runbook: A Notion page was made public. Check with the author to determine why this page was made public.
Reference: https://www.notion.so/help/public-pages-and-web-publishing
Stages and Predicates
Fires on Notion.AuditLogs events when the condition below holds.
Condition
event.typeisworkspace.settings.public_homepage_added
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.type | eq |
| field:"event.type" kind:eq value:"workspace.settings.public_homepage_added" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
database_id | event.workspace.settings.public_homepage_added.new_public_page.database_id |
workspace_id | event.workspace_id |
Response runbook
A Notion page was made public. Check with the author to determine why this page was made public.
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"event": {
"actor": {
"id": "44444444-cccc-7777-aaaa-666666666666",
"object": "user",
"person": {
"email": "example@personemail.com"
},
"type": "person"
},
"id": "eeeeeeee-dddd-4444-bbbb-4444444444444",
"ip_address": "00.000.00.000",
"platform": "web",
"timestamp": "2023-05-15T19:14:21.031Z",
"type": "workspace.settings.public_homepage_added",
"workspace.settings.public_homepage_added": {
"new_public_page": {
"database_id": "4b801dc7-d724-4fbb-afd0-9885cbc12405",
"type": "database_id"
}
},
"workspace_id": "vvvvvvvv-dddd-4444-bbbb-666666666666"
}
}
Signal - Notion Account Changed
#A Notion User changed their account information.
MITRE ATT&CK coverage
| Tactic | Techniques |
|---|---|
| Persistence | No specific technique |
Detection logic
from panther_notion_helpers import notion_alert_context
def rule(event):
allowed_event_types = {
"user.settings.login_method.email_updated",
"user.settings.login_method.password_updated",
"user.settings.login_method.password_added",
"user.settings.login_method.password_removed",
}
if event.deep_walk("event", "type") in allowed_event_types:
return True
return False
def title(event):
user_email = event.deep_walk("event", "actor", "person", "email", default="UNKNOWN EMAIL")
action_taken = {
"user.settings.login_method.email_updated": "changed their email",
"user.settings.login_method.password_updated": "changed their password",
"user.settings.login_method.password_added": "added a password to their account",
"user.settings.login_method.password_removed": "removed the password from their account",
}.get(event.deep_get("event", "type"), "altered their account info")
return f"Notion User [{user_email}] {action_taken}."
def alert_context(event):
context = notion_alert_context(event)
context["login_timestamp"] = event.get("p_event_time")
context["actor_id"] = event.deep_walk("event", "actor", "id")
return context
Rule specification
AnalysisType: rule
Filename: notion_account_changed.py
RuleID: "Notion.AccountChange"
DisplayName: "Signal - Notion Account Changed"
Enabled: true
CreateAlert: false
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Identity & Access Management
- Persistence
Severity: Info
Description: A Notion User changed their account information.
DedupPeriodMinutes: 60
Threshold: 1
Reference: https://www.notion.so/help/account-settings
Stages and Predicates
Fires on Notion.AuditLogs events when the condition below holds.
Condition
event.typeis one ofuser.settings.login_method.email_updated,user.settings.login_method.password_updated,user.settings.login_method.password_added,user.settings.login_method.password_removed
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.type | in |
| field:"event.type" kind:in |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"event": {
"actor": {
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"object": "user",
"person": {
"email": "aragorn.elessar@lotr.com"
},
"type": "person"
},
"details": {
"authType": "email"
},
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"ip_address": "192.168.100.100",
"platform": "web",
"timestamp": "2023-06-12 21:40:28.690000000",
"type": "user.settings.login_method.email_updated",
"workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"p_event_time": "2023-06-12 21:40:28.690000000",
"p_log_type": "Notion.AuditLogs",
"p_parse_time": "2023-06-12 22:53:51.602223297",
"p_row_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"p_schema_version": 0,
"p_source_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"p_source_label": "Notion Logs"
}
Signal - Notion Login
#A Notion User logged in.
Detection logic
from panther_notion_helpers import notion_alert_context
def rule(event):
if event.deep_walk("event", "type") == "user.login":
return True
return False
def title(event):
user_email = event.deep_walk("event", "actor", "person", "email", default="UNKNOWN EMAIL")
return f"Notion User [{user_email}] logged in."
def alert_context(event):
context = notion_alert_context(event)
context["login_timestamp"] = event.get("p_event_time")
context["actor_id"] = event.deep_walk("event", "actor", "id")
return context
Rule specification
AnalysisType: rule
Filename: notion_login.py
RuleID: "Notion.Login"
DisplayName: "Signal - Notion Login"
Enabled: true
CreateAlert: false
LogTypes:
- Notion.AuditLogs
Tags:
- Notion
- Identity & Access Management
Severity: Info
Description: A Notion User logged in.
DedupPeriodMinutes: 60
Threshold: 1
Reference: https://www.notion.so/help/account-settings
Stages and Predicates
Fires on Notion.AuditLogs events when the condition below holds.
Condition
event.typeisuser.login
Indicators
These rows show field, operator, and value matches.
| Field | Kind | Values | Search |
|---|---|---|---|
event.type | eq |
| field:"event.type" kind:eq value:"user.login" |
Output fields
Fields the rule emits when it matches, drawn from the rule's alert_context.
| Field | Source |
|---|---|
email | event.actor.person.email |
Worked example
A sample event from the rule's unit tests that triggers a match.Sample Test Event
{
"event": {
"actor": {
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"object": "user",
"person": {
"email": "aragorn.elessar@lotr.com"
},
"type": "person"
},
"details": {
"authType": "email"
},
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"ip_address": "192.168.100.100",
"platform": "web",
"timestamp": "2023-06-12 21:40:28.690000000",
"type": "user.login",
"workspace_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"p_event_time": "2023-06-12 21:40:28.690000000",
"p_log_type": "Notion.AuditLogs",
"p_parse_time": "2023-06-12 22:53:51.602223297",
"p_row_id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"p_schema_version": 0,
"p_source_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"p_source_label": "Notion Logs"
}