snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-15 15:05:30 +05:00
commit 64898b2c79
8999 changed files with 1757358 additions and 0 deletions

View File

View File

@@ -0,0 +1,31 @@
# Zulip Thinkst integration
Get your Thinkst Canary and Canarytoken alerts in Zulip!
This integration works with Canarytokens from Thinkst's paid product.
For [canarytokens.org][canarytokens], see the
[Canarytokens](/integrations/canarytoken) integration!
{start_tabs}
1. {!create-an-incoming-webhook.md!}
1. {!generate-webhook-url-basic.md!}
1. Go to your Thinkst Canary settings, and click on **Webhooks** in the
left sidebar. Select the **Generic** tab, and click **Add Generic
Webhook**.
1. Enter the generated URL above. Click **Save**.
{end_tabs}
{!congrats.md!}
![](/static/images/integrations/thinkst/001.png)
### Related documentation
{!webhooks-url-specification.md!}
[canarytokens]: https://canarytokens.org

View File

@@ -0,0 +1,32 @@
{
"ReverseDNS": "attacker.local",
"CanaryName": "foo-bar",
"Description": "Consolidated Network Port Scan",
"CanaryPort": "443",
"Timestamp": "2020-07-20 16:18:40 (UTC)",
"CanaryIP": "1.1.1.1",
"AlertType": "CanaryIncident",
"Intro": "A portscan has been done on several of your canaries by the host 1.1.1.1.",
"IncidentHash": "3f25fec9e18c7673dcc468800b7af0a6",
"CanaryLocation": "dining room",
"SourceIP": "1.1.1.1",
"AdditionalDetails": [
[
"Incident",
"Consolidated Network Port Scan"
],
[
"Source",
"1.1.1.1"
],
[
"Targets",
"1.0.0.1, 1.0.0.2"
],
[
"Background Context",
"You have had 8 incidents from 1.0.0.1 previously."
]
],
"CanaryID": "00000000aa8a310e"
}

View File

@@ -0,0 +1,27 @@
{
"ReverseDNS": "attacker-ip.local",
"CanaryName": "0000000testnode",
"Description": "Dummy Incident",
"Timestamp": "2020-06-09 13:59:38 (UTC)",
"CanaryIP": "1.1.1.1",
"AlertType": "CanaryIncident",
"Intro": "This is a dummy incident.",
"IncidentHash": "aa875f255f94e3ffe40dc85cf1a8b1e0",
"CanaryPort": 8080,
"SourceIP": "2.2.2.2",
"AdditionalDetails": [
[
"Field1",
"VALUE1"
],
[
"Field2",
"VALUE2"
],
[
"Field3",
"VALUE3"
]
],
"CanaryID": "0000000testnode"
}

View File

@@ -0,0 +1,32 @@
{
"ReverseDNS": "",
"CanaryName": "bar-foo",
"Description": "Shared File (Re)Opened",
"CanaryPort": 445,
"Timestamp": "2020-07-20 16:27:20 (UTC)",
"CanaryIP": "1.1.1.1",
"AlertType": "CanaryIncident",
"Intro": "Shared File (Re)Opened has been detected against one of your Canaries (bar-foo) at 1.1.1.1.",
"IncidentHash": "a7bb317ba2072415462233cef3bc615a",
"CanaryLocation": "dining room",
"SourceIP": "1.1.1.1",
"AdditionalDetails": [
[
"User",
"guest"
],
[
"Filename",
"secret/bar.doc"
],
[
"Additional Information",
"This file 'secret/bar.doc' was previously opened by the host 'zulip-dev' on 2020-07-20 16:18:56.\n\nIt was also opened 2 times before by the same host, on 2020-06-10 14:33:50, 2020-06-18 19:02:33."
],
[
"Background Context",
"You have had 20 incidents from 1.1.1.1 previously."
]
],
"CanaryID": "00000000aa8a310e"
}

View File

@@ -0,0 +1,20 @@
{
"ReverseDNS": "attacker.local",
"CanaryName": "foo-bar",
"Description": "Host Port Scan",
"CanaryPort": "1723",
"Timestamp": "2020-07-20 16:26:30 (UTC)",
"CanaryIP": "1.1.1.1",
"AlertType": "CanaryIncident",
"Intro": "Host Port Scan has been detected against one of your Canaries (foo-bar) at 1.1.1.1.",
"IncidentHash": "9060473d2da98afe494adad648495620",
"CanaryLocation": "dining room",
"SourceIP": "1.1.1.1",
"AdditionalDetails": [
[
"Partial Ports",
"443, 554, 80, 1723, 22"
]
],
"CanaryID": "00024808d6c2f7e5"
}

View File

@@ -0,0 +1,32 @@
{
"ReverseDNS": "",
"CanaryName": "foo-bar",
"Description": "HTTP Login Attempt",
"CanaryPort": 80,
"Timestamp": "2020-07-20 14:55:45 (UTC)",
"CanaryIP": "1.1.1.1",
"AlertType": "CanaryIncident",
"Intro": "HTTP Login Attempt has been detected against one of your Canaries (foo-bar) at 1.1.1.1.",
"IncidentHash": "1ef86f5cf4090a5252c6f453c5cd46bd",
"CanaryLocation": "dining room",
"SourceIP": "1.1.1.1",
"AdditionalDetails": [
[
"Username",
"regular"
],
[
"Password",
"*******"
],
[
"User-Agent",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15"
],
[
"Background Context",
"You have had 14 incidents from 1.1.1.1 previously."
]
],
"CanaryID": "00000000d9febd04"
}

View File

@@ -0,0 +1,28 @@
{
"ReverseDNS": "attacker.local",
"CanaryName": "foo-bar",
"Description": "SSH Login Attempt",
"CanaryPort": 22,
"Timestamp": "2020-07-20 16:26:22 (UTC)",
"CanaryIP": "1.1.1.1",
"AlertType": "CanaryIncident",
"Intro": "SSH Login Attempt has been detected against one of your Canaries (foo-bar) at 1.1.1.1.",
"IncidentHash": "4ef51a936d05e0a6b4846378c8c38d2c",
"CanaryLocation": "dining room",
"SourceIP": "1.1.1.1",
"AdditionalDetails": [
[
"Username",
"foo"
],
[
"Key",
"*******"
],
[
"Background Context",
"You have had 9 incidents from 1.1.1.1 previously."
]
],
"CanaryID": "00024808d6c2f7e5"
}

View File

@@ -0,0 +1,40 @@
{
"Token": "dbwx4d68flwh2u5zku56nogu6",
"Intro": "A MS Word .docx Document Canarytoken has been triggered over doc-msword by the source IP 1.1.1.1.",
"Description": "MS Word .docx Document",
"Triggered": 5,
"AdditionalDetails": [
[
"Accept",
"*/*"
],
[
"Accept-Encoding",
"gzip, deflate"
],
[
"Accept-Language",
"en-gb"
],
[
"Background Context",
"You have had 21 incidents from 1.1.1.1 previously."
],
[
"Connection",
"keep-alive"
],
[
"Dst Port",
80
],
[
"User-Agent",
"Mozilla/4.0 (compatible; ms-office; MSOffice 16)"
]
],
"Timestamp": "2020-07-20 14:40:15 (UTC)",
"Reminder": "test document",
"IncidentHash": "db6f9b5528c6c6c385cb3bb63f5949c8",
"AlertType": "CanarytokenIncident"
}

View File

@@ -0,0 +1,48 @@
{
"Token": "ew5n8gqtb82m4uegrttarn2zu",
"Intro": "A Remote Web Image Canarytoken has been triggered over web-image by the source IP 1.1.1.1.",
"Description": "Remote Web Image",
"Triggered": 37,
"AdditionalDetails": [
[
"Accept",
"image/webp,*/*"
],
[
"Accept-Encoding",
"gzip, deflate"
],
[
"Accept-Language",
"en-US,en;q=0.5"
],
[
"Background Context",
"You have had 12 incidents from 1.1.1.1 previously."
],
[
"Cache-Control",
"max-age=0"
],
[
"Connection",
"keep-alive"
],
[
"Dnt",
"1"
],
[
"Dst Port",
80
],
[
"User-Agent",
"Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0"
]
],
"Timestamp": "2020-07-20 16:33:12 (UTC)",
"Reminder": "test image",
"IncidentHash": "533395067f6d655cd19384bc6991cc0f",
"AlertType": "CanarytokenIncident"
}

View File

@@ -0,0 +1,256 @@
from zerver.lib.test_classes import WebhookTestCase
class ThinkstHookTests(WebhookTestCase):
def test_canary_dummy(self) -> None:
expected_message = (
"**:alert: Canary *0000000testnode* has been triggered!**\n\n"
"This is a dummy incident.\n\n"
"**Incident ID:** `aa875f255f94e3ffe40dc85cf1a8b1e0`\n"
"**Kind:** Dummy Incident\n"
"**Timestamp:** <time:2020-06-09T13:59:38+00:00>\n"
"**Canary IP:** `1.1.1.1`\n"
"**Source IP:** `2.2.2.2`\n"
"**Reverse DNS:** `attacker-ip.local`\n"
"**Field1:** VALUE1\n"
"**Field2:** VALUE2\n"
"**Field3:** VALUE3"
)
self.check_webhook(
"canary_dummy",
"canary alert - 0000000testnode",
expected_message,
content_type="application/x-www-form-urlencoded",
)
def test_canary_consolidated_port_scan(self) -> None:
expected_message = (
"**:alert: Canary *foo-bar* has been triggered!**\n\n"
"A portscan has been done on several of your canaries by the host "
"1.1.1.1.\n\n"
"**Incident ID:** `3f25fec9e18c7673dcc468800b7af0a6`\n"
"**Kind:** Consolidated Network Port Scan\n"
"**Timestamp:** <time:2020-07-20T16:18:40+00:00>\n"
"**Canary IP:** `1.1.1.1`\n"
"**Canary location:** dining room\n"
"**Source IP:** `1.1.1.1`\n"
"**Reverse DNS:** `attacker.local`\n"
"**Incident:** Consolidated Network Port Scan\n"
"**Source:** 1.1.1.1\n"
"**Targets:** 1.0.0.1, 1.0.0.2\n"
"**Background Context:** You have had 8 incidents from 1.0.0.1 previously."
)
self.check_webhook(
"canary_consolidated_port_scan",
"canary alert - foo-bar",
expected_message,
content_type="application/x-www-form-urlencoded",
)
def test_canary_file_access(self) -> None:
expected_message = (
"**:alert: Canary *bar-foo* has been triggered!**\n\n"
"Shared File (Re)Opened has been detected against one of your Canaries "
"(bar-foo) at 1.1.1.1.\n\n"
"**Incident ID:** `a7bb317ba2072415462233cef3bc615a`\n"
"**Kind:** Shared File (Re)Opened\n"
"**Timestamp:** <time:2020-07-20T16:27:20+00:00>\n"
"**Canary IP:** `1.1.1.1`\n"
"**Canary location:** dining room\n"
"**Source IP:** `1.1.1.1`\n"
"**User:** guest\n"
"**Filename:** secret/bar.doc\n"
"**Additional Information:** This file 'secret/bar.doc' was "
"previously opened by the host 'zulip-dev' on 2020-07-20 16:18:56.\n\n"
"It was also opened 2 times before by the same host, on 2020-06-10 "
"14:33:50, 2020-06-18 19:02:33.\n"
"**Background Context:** You have had 20 incidents from 1.1.1.1 previously."
)
self.check_webhook(
"canary_file_access",
"canary alert - bar-foo",
expected_message,
content_type="application/x-www-form-urlencoded",
)
def test_canary_host_port_scan(self) -> None:
expected_message = (
"**:alert: Canary *foo-bar* has been triggered!**\n\n"
"Host Port Scan has been detected against one of your Canaries "
"(foo-bar) at 1.1.1.1.\n\n"
"**Incident ID:** `9060473d2da98afe494adad648495620`\n"
"**Kind:** Host Port Scan\n"
"**Timestamp:** <time:2020-07-20T16:26:30+00:00>\n"
"**Canary IP:** `1.1.1.1`\n"
"**Canary location:** dining room\n"
"**Source IP:** `1.1.1.1`\n"
"**Reverse DNS:** `attacker.local`\n"
"**Partial Ports:** 443, 554, 80, 1723, 22"
)
self.check_webhook(
"canary_host_port_scan",
"canary alert - foo-bar",
expected_message,
content_type="application/x-www-form-urlencoded",
)
def test_canary_http_login(self) -> None:
expected_message = (
"**:alert: Canary *foo-bar* has been triggered!**\n\n"
"HTTP Login Attempt has been detected against one of your Canaries "
"(foo-bar) at 1.1.1.1.\n\n"
"**Incident ID:** `1ef86f5cf4090a5252c6f453c5cd46bd`\n"
"**Kind:** HTTP Login Attempt\n"
"**Timestamp:** <time:2020-07-20T14:55:45+00:00>\n"
"**Canary IP:** `1.1.1.1`\n"
"**Canary location:** dining room\n"
"**Source IP:** `1.1.1.1`\n"
"**Username:** regular\n"
"**Password:** `*******`\n"
"**User-Agent:** Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) "
"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15\n"
"**Background Context:** You have had 14 incidents from 1.1.1.1 previously."
)
self.check_webhook(
"canary_http_login",
"canary alert - foo-bar",
expected_message,
content_type="application/x-www-form-urlencoded",
)
def test_canary_ssh_login(self) -> None:
expected_message = (
"**:alert: Canary *foo-bar* has been triggered!**\n\n"
"SSH Login Attempt has been detected against one of your Canaries "
"(foo-bar) at 1.1.1.1.\n\n"
"**Incident ID:** `4ef51a936d05e0a6b4846378c8c38d2c`\n"
"**Kind:** SSH Login Attempt\n"
"**Timestamp:** <time:2020-07-20T16:26:22+00:00>\n"
"**Canary IP:** `1.1.1.1`\n"
"**Canary location:** dining room\n"
"**Source IP:** `1.1.1.1`\n"
"**Reverse DNS:** `attacker.local`\n"
"**Username:** foo\n"
"**Key:** `*******`\n"
"**Background Context:** You have had 9 incidents from 1.1.1.1 previously."
)
self.check_webhook(
"canary_ssh_login",
"canary alert - foo-bar",
expected_message,
content_type="application/x-www-form-urlencoded",
)
def test_canary_with_specific_topic(self) -> None:
self.url = self.build_webhook_url(topic="foo")
expected_message = (
"**:alert: Canary *0000000testnode* has been triggered!**\n\n"
"This is a dummy incident.\n\n"
"**Incident ID:** `aa875f255f94e3ffe40dc85cf1a8b1e0`\n"
"**Kind:** Dummy Incident\n"
"**Timestamp:** <time:2020-06-09T13:59:38+00:00>\n"
"**Canary IP:** `1.1.1.1`\n"
"**Source IP:** `2.2.2.2`\n"
"**Reverse DNS:** `attacker-ip.local`\n"
"**Field1:** VALUE1\n"
"**Field2:** VALUE2\n"
"**Field3:** VALUE3"
)
self.check_webhook(
"canary_dummy",
"foo",
expected_message,
content_type="application/x-www-form-urlencoded",
)
def test_canarytoken_msword(self) -> None:
expected_message = (
"**:alert: Canarytoken *test document* has been triggered!**\n\n"
"A MS Word .docx Document Canarytoken has been triggered over doc-msword "
"by the source IP 1.1.1.1.\n\n"
"**Incident ID:** `db6f9b5528c6c6c385cb3bb63f5949c8`\n"
"**Token:** `dbwx4d68flwh2u5zku56nogu6`\n"
"**Kind:** MS Word .docx Document\n"
"**Timestamp:** <time:2020-07-20T14:40:15+00:00>\n"
"**Triggered:** 5 times\n"
"**Accept:** `*/*`\n"
"**Accept-Encoding:** gzip, deflate\n"
"**Accept-Language:** en-gb\n"
"**Background Context:** You have had 21 incidents from 1.1.1.1 "
"previously.\n"
"**Connection:** keep-alive\n"
"**Dst Port:** 80\n"
"**User-Agent:** Mozilla/4.0 (compatible; ms-office; MSOffice 16)"
)
self.check_webhook(
"canarytoken_msword",
"canarytoken alert - test document",
expected_message,
content_type="application/x-www-form-urlencoded",
)
def test_canarytoken_remote_image(self) -> None:
expected_message = (
"**:alert: Canarytoken *test image* has been triggered!**\n\n"
"A Remote Web Image Canarytoken has been triggered over web-image by the "
"source IP 1.1.1.1.\n\n"
"**Incident ID:** `533395067f6d655cd19384bc6991cc0f`\n"
"**Token:** `ew5n8gqtb82m4uegrttarn2zu`\n"
"**Kind:** Remote Web Image\n"
"**Timestamp:** <time:2020-07-20T16:33:12+00:00>\n"
"**Triggered:** 37 times\n"
"**Accept:** `image/webp,*/*`\n"
"**Accept-Encoding:** gzip, deflate\n"
"**Accept-Language:** en-US,en;q=0.5\n"
"**Background Context:** You have had 12 incidents from 1.1.1.1 "
"previously.\n"
"**Cache-Control:** max-age=0\n"
"**Connection:** keep-alive\n"
"**Dnt:** 1\n"
"**Dst Port:** 80\n"
"**User-Agent:** Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 "
"Firefox/78.0"
)
self.check_webhook(
"canarytoken_remote_image",
"canarytoken alert - test image",
expected_message,
content_type="application/x-www-form-urlencoded",
)
def test_canarytoken_with_specific_topic(self) -> None:
self.url = self.build_webhook_url(topic="foo")
expected_message = (
"**:alert: Canarytoken *test document* has been triggered!**\n\n"
"A MS Word .docx Document Canarytoken has been triggered over doc-msword "
"by the source IP 1.1.1.1.\n\n"
"**Incident ID:** `db6f9b5528c6c6c385cb3bb63f5949c8`\n"
"**Token:** `dbwx4d68flwh2u5zku56nogu6`\n"
"**Kind:** MS Word .docx Document\n"
"**Timestamp:** <time:2020-07-20T14:40:15+00:00>\n"
"**Triggered:** 5 times\n"
"**Accept:** `*/*`\n"
"**Accept-Encoding:** gzip, deflate\n"
"**Accept-Language:** en-gb\n"
"**Background Context:** You have had 21 incidents from 1.1.1.1 "
"previously.\n"
"**Connection:** keep-alive\n"
"**Dst Port:** 80\n"
"**User-Agent:** Mozilla/4.0 (compatible; ms-office; MSOffice 16)"
)
self.check_webhook(
"canarytoken_msword",
"foo",
expected_message,
content_type="application/x-www-form-urlencoded",
)

View File

@@ -0,0 +1,152 @@
# Webhooks for external integrations.
from datetime import datetime
from django.http import HttpRequest, HttpResponse
from zerver.decorator import webhook_view
from zerver.lib.response import json_success
from zerver.lib.timestamp import datetime_to_global_time
from zerver.lib.typed_endpoint import JsonBodyPayload, typed_endpoint
from zerver.lib.validator import WildValue, check_int, check_string, check_union
from zerver.lib.webhooks.common import OptionalUserSpecifiedTopicStr, check_send_webhook_message
from zerver.models import UserProfile
def is_canarytoken(message: WildValue) -> bool:
"""
Requests sent from Thinkst canaries are either from canarytokens or
canaries, which can be differentiated by the value of the `AlertType`
field.
"""
return message["AlertType"].tame(check_string) == "CanarytokenIncident"
def canary_name(message: WildValue) -> str:
"""
Returns the name of the canary or canarytoken.
"""
if is_canarytoken(message):
return message["Reminder"].tame(check_string)
else:
return message["CanaryName"].tame(check_string)
def canary_kind(message: WildValue) -> str:
"""
Returns a description of the kind of request - canary or canarytoken.
"""
if is_canarytoken(message):
return "canarytoken"
else:
return "canary"
def source_ip_and_reverse_dns(message: WildValue) -> tuple[str | None, str | None]:
"""
Extract the source IP and reverse DNS information from a canary request.
"""
reverse_dns, source_ip = (None, None)
if "SourceIP" in message:
source_ip = message["SourceIP"].tame(check_string)
# `ReverseDNS` can sometimes exist and still be empty.
if "ReverseDNS" in message and message["ReverseDNS"].tame(check_string) != "":
reverse_dns = message["ReverseDNS"].tame(check_string)
return (source_ip, reverse_dns)
def body(message: WildValue) -> str:
"""
Construct the response to a canary or canarytoken request.
"""
title = canary_kind(message).title()
name = canary_name(message)
body = f"**:alert: {title} *{name}* has been triggered!**\n\n{message['Intro'].tame(check_string)}\n\n"
if "IncidentHash" in message:
body += f"**Incident ID:** `{message['IncidentHash'].tame(check_string)}`\n"
if "Token" in message:
body += f"**Token:** `{message['Token'].tame(check_string)}`\n"
if "Description" in message:
body += f"**Kind:** {message['Description'].tame(check_string)}\n"
if "Timestamp" in message:
timestamp = message["Timestamp"].tame(check_string)
# Thinkst datetime format: https://help.canary.tools/hc/en-gb/articles/360012727777-How-do-I-configure-notifications-for-a-Generic-Webhook#h_01K71QZ806C5D49RYB6RBXSZ1B
# "YYYY-MM-DD HH:MM:SS (UTC)"
formatted_timestamp = timestamp.replace(" (UTC)", "Z")
dt = datetime.fromisoformat(formatted_timestamp)
global_time = datetime_to_global_time(dt)
body += f"**Timestamp:** {global_time}\n"
if "CanaryIP" in message:
body += f"**Canary IP:** `{message['CanaryIP'].tame(check_string)}`\n"
if "CanaryLocation" in message:
body += f"**Canary location:** {message['CanaryLocation'].tame(check_string)}\n"
if "Triggered" in message:
unit = "times" if message["Triggered"].tame(check_int) > 1 else "time"
body += f"**Triggered:** {message['Triggered'].tame(check_int)} {unit}\n"
source_ip, reverse_dns = source_ip_and_reverse_dns(message)
if source_ip:
body += f"**Source IP:** `{source_ip}`\n"
if reverse_dns:
body += f"**Reverse DNS:** `{reverse_dns}`\n"
if "AdditionalDetails" in message:
for detail in message["AdditionalDetails"]:
key = detail[0].tame(check_string)
value = detail[1].tame(check_union([check_string, check_int]))
if isinstance(value, str) and "*" in value:
# Thinkst sends passwords as a series of stars which can mess with
# formatting, so wrap these in backticks.
body += f"**{key}:** `{value}`\n"
else:
body += f"**{key}:** {value}\n"
return body
@webhook_view("Thinkst")
@typed_endpoint
def api_thinkst_webhook(
request: HttpRequest,
user_profile: UserProfile,
*,
message: JsonBodyPayload[WildValue],
user_specified_topic: OptionalUserSpecifiedTopicStr = None,
) -> HttpResponse:
"""
Construct a response to a webhook event from a Thinkst canary or canarytoken.
Thinkst offers public canarytokens with canarytokens.org and with their canary
product, but the schema returned by these identically named services are
completely different - canarytokens from canarytokens.org are handled by a
different Zulip integration.
Thinkst's documentation for the schema is linked below, but in practice the JSON
received doesn't always conform.
https://help.canary.tools/hc/en-gb/articles/360002426577-How-do-I-configure-notifications-for-a-Generic-Webhook-
"""
response = body(message)
topic_name = None
if user_specified_topic is not None:
topic_name = user_specified_topic
else:
name = canary_name(message)
kind = canary_kind(message)
topic_name = f"{kind} alert - {name}"
check_send_webhook_message(request, user_profile, topic_name, response)
return json_success(request)