sentinel: Evidence System Proposal #115

Open
opened 2026-05-30 15:24:24 -04:00 by cswimr · 0 comments
Owner

Evidence System

Currently, moderators in the Galaxy Discord server have the following workflow:

  • Moderate a user
  • Copy and paste moderation details into a #mod-evidence channel
  • Attach evidence for the moderation to their message in #mod-evidence

This is unnecessarily manual, fragile, and makes finding evidence for moderations difficult. You also can't add new evidence to a pre-existing moderation without writing out another message or editing the original message with a link to a media file.

This issue proposes an alternative: a unified evidence storage system, integrated directly into Sentinel.

Interface

Disclaimer: Tidegear is only a library, and does not provide command implementations. All of the listed commands below would likely be included in SentinelCore, not Tidegear, and as such, could be replaced or added onto by other cogs using Tidegear's Sentinel module.

I plan to support four types of evidence: raw text, HTTP(S) links, Discord messages, and image files. All evidence entries will be immutable (save for deletion).

Adding Evidence

Evidence will be added to moderation cases by using the /evidence [type] slash commands. For example, /evidence text case:2524 content:hello world would add a raw text evidence entry to case 2,524 with the content hello world. All evidence commands will also support an nsfw option, which will default to False and be further explained below.

Viewing Evidence

/case

The existing /case command will be used to view evidence for a specific moderation case. By default, evidence will not be shown, but can be enabled via passing evidence:true. Evidence marked as nsfw will be hidden from the resulting embed if sent outside of an NSFW channel. If the embed is being posted in an NSFW channel, all NSFW content will be spoilered and be explicitly marked as NSFW.

/evidence check

This command will be limited to guild administrators, and will show a list of all moderation cases that have "rotted" evidence. This is mainly useful for HTTP(S) links and Discord messages. How "rotted" evidence is determined will be explained in the technical details later. Evidence entries listed in this command can easily be deleted through this command, or through the dedicated /evidence remove command - this command will also be limited to guild administrators, most likely.

Technical Details

This section will go over some of the details of actually implementing this system. Of course, I've not actually started implementing it yet, so this section may not reflect the final implementation.

Metadata Storage

Evidence entries will be stored in the pre-existing SQLite database, as part of a new evidence_entry table. This will use the same many-to-one architecture as moderation changes already do. The schema might look something like this (pseudocode):

class EvidenceEntry:
    id: int # autoincrement
    moderation_id: int # foreign key to the Moderation table
    moderator_id: int # the moderator who added this evidence entry
    type: EvidenceTypes # enum
    timestamp: datetime
    content: str | None # either the raw content of the evidence entry, or the haah of an image file in the file store
    partial_message_id: int # foreign key to the PartialMessage table
    nsfw: bool
    last_rot_check: datetime # the last time this evidence entry was checked for evidence rot

File Storage

I'm thinking of implementing three different backends for file storage: local storage, LMDB storage, and S3-compatible storage. For local storage, files will be stored in SentinelCore's runtime data directory. Similarly, the LMDB database in LMDB mode will be stored there as well. In S3 mode, a local cache of image files will be kept in the runtime data directory. The maximum size of this cache directory will be configurable by the bot owner, and old entries will automatically be deleted.

All image files will be re-encoded to the WebP format and compressed with owner-configurable compression settings prior to storage. Then, the images will be pixel content-hashed, and will only be written to the file store if there is not already a file matching the produced hash in the store. Effectively, this means the file store will be content-addressed. There will be both a ratelmiit for file uploads and a maximum size limit, to prevent service abuse.

Do note that the bot owner is solely responsible for illegal content uploaded to this system. File storage will be disabled by default and enabling it will require acknowledging legal risks. Due to this being a FOSS project, I can't really implement CSAM detection or anything like that.

Discord Message Storage (Snapshotting)

Snapshots of Discord messages would be stored in another database table, partial_message. Only text content will be stored, not attachments. I am specifically not planning on allowing for snapshots to be updated. Instead, a new snapshot if a message is added as a piece of evidence, and the snapshot in the database doesn't match the message content. The schema of this table might look something like this (pseudocode):

class PartialMessage:
    id: int # autoincrement
    discord_id: int # the actual message ID provided by Discord; no unique constraint
    channel_id: int # foreign key to the PartialChannel table; represents the channel this message was sent in
    content: str | None # the raw text content of this message at the time of snapshotting
    timestamp: datetime # the datetime this message was snapshotted

Rot Detection

Evidence entries containing HTTP(S) URLs or images will periodically be checked for "rot" on an interval configurable by the bot owner. This will involve re-querying the URLs / file store backend to ensure the content is still accessible. A frequently-ran task will retrieve a list of all evidence entries that haven't been checked since current_time - interval. The purpose of this is to avoid sudden bursts of HTTP requests to external services that could potentially result in ratelimiting. There will also be a configurable jitter to further prevent request spam.

# Evidence System Currently, moderators in the Galaxy Discord server have the following workflow: - Moderate a user - Copy and paste moderation details into a `#mod-evidence` channel - Attach evidence for the moderation to their message in `#mod-evidence` This is unnecessarily manual, fragile, and makes finding evidence for moderations difficult. You also can't add new evidence to a pre-existing moderation without writing out another message or editing the original message with a link to a media file. This issue proposes an alternative: a unified evidence storage system, integrated directly into Sentinel. ## Interface ***Disclaimer: Tidegear is only a library, and does not provide command implementations. All of the listed commands below would likely be included in [SentinelCore](https://c.csw.im/cswimr/SeaCogs), not Tidegear, and as such, could be replaced or added onto by other cogs using Tidegear's Sentinel module.*** I plan to support four types of evidence: raw text, HTTP(S) links, Discord messages, and image files. All evidence entries will be immutable (save for deletion). ### Adding Evidence Evidence will be added to moderation cases by using the `/evidence [type]` slash commands. For example, `/evidence text case:2524 content:hello world` would add a raw text evidence entry to case `2,524` with the content `hello world`. All evidence commands will also support an `nsfw` option, which will default to `False` and be further explained below. ### Viewing Evidence #### `/case` The existing `/case` command will be used to view evidence for a specific moderation case. By default, evidence will not be shown, but can be enabled via passing `evidence:true`. Evidence marked as `nsfw` will be hidden from the resulting embed if sent outside of an NSFW channel. If the embed is being posted in an NSFW channel, all NSFW content will be spoilered and be explicitly marked as NSFW. #### `/evidence check` This command will be limited to guild administrators, and will show a list of all moderation cases that have "rotted" evidence. This is mainly useful for HTTP(S) links and Discord messages. How "rotted" evidence is determined will be explained in the technical details later. Evidence entries listed in this command can easily be deleted through this command, or through the dedicated `/evidence remove` command - this command will also be limited to guild administrators, most likely. ## Technical Details This section will go over some of the details of actually implementing this system. Of course, I've not actually started implementing it yet, so this section may not reflect the final implementation. ### Metadata Storage Evidence entries will be stored in the pre-existing SQLite database, as part of a new `evidence_entry` table. This will use the same many-to-one architecture as moderation changes already do. The schema might look something like this (pseudocode): ```py class EvidenceEntry: id: int # autoincrement moderation_id: int # foreign key to the Moderation table moderator_id: int # the moderator who added this evidence entry type: EvidenceTypes # enum timestamp: datetime content: str | None # either the raw content of the evidence entry, or the haah of an image file in the file store partial_message_id: int # foreign key to the PartialMessage table nsfw: bool last_rot_check: datetime # the last time this evidence entry was checked for evidence rot ``` ### File Storage I'm thinking of implementing three different backends for file storage: local storage, [LMDB storage](https://en.wikipedia.org/wiki/Lightning_Memory-Mapped_Database), and [S3-compatible storage](https://en.wikipedia.org/wiki/Amazon_S3). For local storage, files will be stored in SentinelCore's runtime data directory. Similarly, the LMDB database in LMDB mode will be stored there as well. In S3 mode, a local cache of image files will be kept in the runtime data directory. The maximum size of this cache directory will be configurable by the bot owner, and old entries will automatically be deleted. All image files will be re-encoded to the [WebP](https://en.wikipedia.org/wiki/WebP) format and compressed with owner-configurable compression settings prior to storage. Then, the images will be pixel content-hashed, and will only be written to the file store if there is not already a file matching the produced hash in the store. Effectively, this means the file store will be content-addressed. There will be both a ratelmiit for file uploads and a maximum size limit, to prevent service abuse. **Do note that the bot owner is solely responsible for illegal content uploaded to this system.** File storage will be disabled by default and enabling it will require acknowledging legal risks. Due to this being a FOSS project, I can't really implement CSAM detection or anything like that. ### Discord Message Storage (Snapshotting) Snapshots of Discord messages would be stored in another database table, `partial_message`. Only text content will be stored, not attachments. I am specifically *not* planning on allowing for snapshots to be updated. Instead, a new snapshot if a message is added as a piece of evidence, and the snapshot in the database doesn't match the message content. The schema of this table might look something like this (pseudocode): ```py class PartialMessage: id: int # autoincrement discord_id: int # the actual message ID provided by Discord; no unique constraint channel_id: int # foreign key to the PartialChannel table; represents the channel this message was sent in content: str | None # the raw text content of this message at the time of snapshotting timestamp: datetime # the datetime this message was snapshotted ``` ### Rot Detection Evidence entries containing HTTP(S) URLs or images will periodically be checked for "rot" on an interval configurable by the bot owner. This will involve re-querying the URLs / file store backend to ensure the content is still accessible. A frequently-ran task will retrieve a list of all evidence entries that haven't been checked since `current_time - interval`. The purpose of this is to avoid sudden bursts of HTTP requests to external services that could potentially result in ratelimiting. There will also be a configurable jitter to further prevent request spam.
cswimr self-assigned this 2026-05-30 15:24:24 -04:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cswimr/tidegear#115
No description provided.