chore(moderation): remove the built-in NCMEC integration (#3332)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-09 01:39:04 +02:00
1 parent f390e610b9
commit 454fefdb92
146 files changed
+234 -4075

No files matched your search

@@ -140,7 +140,7 @@ The acting credential must already have every value in `acls`, unless it has `*`
| --- | --- | --- |
| name<sup>1</sup> | string | The name given to the key (1-100 characters) |
| expires_in_days?<sup>2</sup> | integer | The number of days until the key expires (1-365) |
| acls<sup>3</sup> | array[string] | The [ACLs](/admin-api/#acl-registry) stored on the key, each a registry value (at most 106) |
| acls<sup>3</sup> | array[string] | The [ACLs](/admin-api/#acl-registry) stored on the key, each a registry value (at most 105) |
<sup>1</sup> A value that is empty after trimming is rejected, so whitespace alone is not a name
@@ -218,7 +218,7 @@ An update never rotates the credential, and no field on this route changes the e
| Field | Type | Description |
| --- | --- | --- |
| name?<sup>1</sup> | string | The replacement name for the key (1-100 characters) |
| acls?<sup>2</sup> | array[string] | The complete replacement set of [ACLs](/admin-api/#acl-registry), each a registry value (at most 106) |
| acls?<sup>2</sup> | array[string] | The complete replacement set of [ACLs](/admin-api/#acl-registry), each a registry value (at most 105) |
<sup>1</sup> A value that is empty after trimming is rejected
@@ -36,7 +36,7 @@ Admin authorisation runs before request validation on every operation except [Qu
An operation names one or more ACLs and admits the request when the caller holds at least one of them. The wildcard `*` satisfies every requirement on its own.
Where an operation stacks several requirements, Fluxer evaluates each separately and the caller must satisfy all of them. [Create NCMEC report](/admin-api/messages/#create-ncmec-report) is the widest example and stacks `csam:submit_ncmec`, `message:delete`, `user:delete`, and `archive:trigger:user` as independent requirements.
Where an operation stacks several requirements, Fluxer evaluates each separately and the caller must satisfy all of them. [List users](/admin-api/users/#list-users) is one example and stacks `user:view:email` or `user:view:ip` on top of `user:lookup` when a request uses a selector that needs one.
The effective ACL set of a session or an Admin OAuth2 bearer credential is the set stored on the account. The effective set of an Admin API key is the set stored on the key, subject to the owner check above. [Set user ACLs](/admin-api/users/#set-user-acls) writes an account's set. Holding `admin:authenticate` or `*` is the whole definition of an account that can reach the Admin API.
@@ -61,7 +61,7 @@ A body with none of those fields resolves to no ACL at all and applies no change
Fluxer checks each ACL an Admin grants against the ACLs that Admin holds. [Set user ACLs](/admin-api/users/#set-user-acls) and [Create Admin API key](/admin-api/api-keys/#create-admin-api-key) both refuse to write an ACL the acting Admin does not itself hold, with 403 `MISSING_ACL`. A wildcard holder is exempt. Set user ACLs also refuses the acting Admin's own account with 403 `ACCESS_DENIED`, and it looks up the target account before it checks the granted ACLs, so an unknown ID fails first with 404 `UNKNOWN_USER`.
[Set user ACLs](/admin-api/users/#set-user-acls), [Create Admin API key](/admin-api/api-keys/#create-admin-api-key), and [Update Admin API key](/admin-api/api-keys/#update-admin-api-key) each accept at most 106 ACLs and validate every entry against the registry, so a value outside it returns 400 `INVALID_FORM_BODY`.
[Set user ACLs](/admin-api/users/#set-user-acls), [Create Admin API key](/admin-api/api-keys/#create-admin-api-key), and [Update Admin API key](/admin-api/api-keys/#update-admin-api-key) each accept at most 105 ACLs and validate every entry against the registry, so a value outside it returns 400 `INVALID_FORM_BODY`.
:::caution[`*` satisfies every present and future ACL]
A key whose owning account holds `*` skips the owner check. An Admin holding `*` can grant any ACL.
@@ -311,7 +311,6 @@ An entry with any other action has `access` set to `write`.
| Value | Description |
| --- | --- |
| NCMEC Report | An attachment was submitted through the NCMEC reporting integration |
| annotate_ban | A note was appended to the current ban of an account |
| approve_discovery_application | A discovery application was approved |
| approve_registration | A pending registration was approved |
@@ -482,7 +481,7 @@ Returns every Admin permission string the instance recognises, in [ACL registry]
| Field | Type | Description |
| --- | --- | --- |
| acls<sup>1</sup> | array[string] | The permission strings the Admin API recognises (at most 106 entries) |
| acls<sup>1</sup> | array[string] | The permission strings the Admin API recognises (at most 105 entries) |
<sup>1</sup> The response is the registry itself and does not vary with the caller's own ACL set
@@ -550,7 +549,6 @@ The registry is returned in this order by [List ACLs](#list-acls). A value outsi
| bulk:delete:user_messages | Queues the bulk message deletion task |
| bulk:update:guild_features | Queues the bulk guild feature task |
| bulk:update:user_flags | Queues the bulk account flag task |
| csam:submit_ncmec | Submits an attachment through the NCMEC reporting integration |
| discovery:remove | Removes a guild from [discovery](/admin-api/discovery/) |
| discovery:review | Reads, reviews, and edits discovery applications, categories, and listings |
| gateway:memory_stats | Reads main [Gateway](/admin-api/gateway/) statistics, memory statistics, and voice state counts |
@@ -1,18 +1,18 @@
---
# SPDX-License-Identifier: AGPL-3.0-or-later
title: Admin messages
description: Message browsing and search, permanent deletion, shred jobs, and NCMEC submission.
description: Message browsing and search, permanent deletion, and shred jobs.
---
import RouteHeader from '@/components/RouteHeader.astro';
Admin message routes read, search, and permanently delete the messages of one channel. Others report the progress of a shred job and submit one attachment to NCMEC, the National Center for Missing and Exploited Children.
Admin message routes read, search, and permanently delete the messages of one channel. Another reports the progress of a shred job.
Shredding and bulk deletion for a whole account live under [Admin users](/admin-api/users/).
## Admin message object
A flattened moderation view of one message. It has the author identity, the content and timestamp, the attachments with their NCMEC submission state, and the NSFW context of the channel and guild.
A flattened moderation view of one message. It has the author identity, the content and timestamp, the attachments, and the NSFW context of the channel and guild.
Every operation that returns Admin message objects also returns the matching public message objects in a parallel `message_responses` array, so a client that needs the full wire shape joins on `id`.
@@ -40,14 +40,11 @@ Every operation that returns Admin message objects also returns the matching pub
| content | string | The message content (0-4,000 characters) |
| timestamp | ISO8601 timestamp | When the message was sent |
| attachments | array[[Admin message attachment](#admin-message-attachment-object) object] | The attachments of the message (at most 10) |
| user_prior_ncmec_report_ids?<sup>3</sup> | array[string] | The NCMEC report IDs previously filed against the author, ascending (at most 100) |
<sup>1</sup> Every object in one response has the same values, taken from the channel named in the request path or query. All are null when the channel no longer exists
<sup>2</sup> Always absent from the objects these operations return. The same member is set on the [report message context object](/admin-api/reports/#report-message-context-object)
<sup>3</sup> Written on every object these operations return, and empty when no NCMEC report has been filed against the author
### Example
```json
@@ -66,14 +63,13 @@ Every operation that returns Admin message objects also returns the matching pub
"author_avatar": "a1b2c3d4e5f60718293a4b5c6d7e8f90",
"content": "read the pinned message",
"timestamp": "2026-03-04T18:19:07.220Z",
"attachments": [],
"user_prior_ncmec_report_ids": []
"attachments": []
}
```
### Admin message attachment object
An attachment entry has the ordinary attachment metadata together with the NCMEC submission state Fluxer holds for it.
An attachment entry has the ordinary attachment metadata.
#### Structure
@@ -86,26 +82,9 @@ An attachment entry has the ordinary attachment metadata together with the NCMEC
| content_type | ?string | The recorded MIME type, or null when none was recorded |
| width | ?integer | The pixel width, or null when no dimensions were recorded |
| height | ?integer | The pixel height, or null when no dimensions were recorded |
| size?<sup>4</sup> | ?integer | The attachment size in bytes, or null when no size was recorded |
| ncmec_status | string | The [NCMEC submission status](#ncmec-submission-status) of this attachment |
| ncmec_report_id<sup>5</sup> | ?string | The report ID NCMEC assigned (1-256 characters), or null when none has been assigned |
| ncmec_failure_reason<sup>6</sup> | ?string | The recorded reason the last submission failed (1-4,000 characters), or null |
| size?<sup>3</sup> | ?integer | The attachment size in bytes, or null when no size was recorded |
<sup>4</sup> Always emitted, so it is null and never absent when no size was recorded
<sup>5</sup> Assigned only once NCMEC accepts the report, so it is null while `ncmec_status` is `not_submitted` and after a failed submission
<sup>6</sup> Set only when `ncmec_status` is `failed`, and it stores the raw upstream error. The sanitised wording appears in the failing response body instead
#### NCMEC submission status
| Value | Description |
| --- | --- |
| not_submitted | The attachment has never been submitted to NCMEC |
| submitted | The attachment was submitted to NCMEC and NCMEC accepted the report |
| failed | The submission failed, the opened report was retracted, and the attachment can be submitted again |
A submission that is still running is stored as `submitting` and returned unchanged, so tolerate a status outside this table. An attachment in that state is refused a second submission by [Create NCMEC report](#create-ncmec-report).
<sup>3</sup> Always emitted, so it is null and never absent when no size was recorded
## Message shred status object
@@ -396,73 +375,3 @@ One [Admin audit entry](/admin-api/#admin-audit-entry-object) is recorded with t
### Rate limit
100 requests per minute for each authenticated user, on the shared `admin:message:operation` bucket.
## Create NCMEC report
<RouteHeader method="POST" path="/v1/admin/messages/ncmec-reports" />
Submits one image or video attachment to NCMEC and starts the account enforcement workflow for its author. Requires `csam:submit_ncmec`, `message:delete`, `user:delete`, and `archive:trigger:user`.
A caller missing more than one of the four is refused with 403 `MISSING_ACL` naming the first it does not hold, in the order listed. The wildcard satisfies them all.
This operation ignores `X-Audit-Log-Reason`. The reason on every audit entry the workflow produces is built from the assigned NCMEC report ID and the channel and attachment IDs, and it is returned as `audit_log_reason`.
### JSON body
| Field | Type | Description |
| --- | --- | --- |
| channel_id | snowflake | The channel the attachment was sent in |
| message_id | snowflake | The message the attachment belongs to |
| attachment_id | snowflake | The attachment to submit |
| filename | string | The exact filename of the attachment (1-255 characters) |
| source_report_id?<sup>1</sup> | snowflake | The report whose frozen evidence has the attachment |
| reporter_full_name | string | The full legal name of the submitting Admin (1-200 characters) |
| confirmed_viewed<sup>2</sup> | boolean | Confirmation that the submitting Admin has viewed the attachment |
<sup>1</sup> Required when the live message no longer exists or has no author, in which case the attachment is read from that report's frozen evidence. Supplying it otherwise only records the report ID
<sup>2</sup> The literal value `true` is the only accepted value. Any other value fails body validation
### Response body
| Field | Type | Description |
| --- | --- | --- |
| success | boolean | Always true |
| ncmec_report_id | string | The report ID NCMEC assigned to this submission (1-256 characters) |
| audit_log_reason<sup>3</sup> | string | The reason recorded on every audit entry the workflow produced (1-4,000 characters) |
<sup>3</sup> The NCMEC report ID followed by the channel ID and attachment ID, so one string identifies the submission in the audit log and at NCMEC
### Response
| Status | Body | Condition |
| --- | --- | --- |
| 200 | response body | Attachment was submitted and the workflow advanced |
| 400 | [error response](/admin-api/#error-response) | The resolved attachment is neither an image nor a video and the request returns `INVALID_FORM_BODY` against `attachment_id` |
| 404<sup>1</sup> | [error response](/admin-api/#error-response) | `UNKNOWN_MESSAGE` when nothing resolves the attachment, or `UNKNOWN_REPORT` when `source_report_id` names no report |
| 409 | [error response](/admin-api/#error-response) | `NCMEC_ALREADY_SUBMITTED` when a submission for the attachment is already running or has already succeeded |
| 500 | [error response](/admin-api/#error-response) | Submission fails and the request returns `NCMEC_SUBMISSION_FAILED` |
<sup>1</sup> The attachment resolves from a live message with an author, or from frozen report evidence
:::danger[A successful request files a real NCMEC report]
The request disables the author's account and schedules that account for deletion. Nothing withdraws a completed submission. Fluxer retracts the report only when a step after it was opened fails.
:::
### Side effects
Fluxer resolves the attachment from the live message when that message still exists and has an author, and from the frozen evidence named by `source_report_id` otherwise. Fluxer accepts the attachment only when its resolved content type begins with `image/` or `video/`.
The attachment is `submitting` while the request runs, then `submitted` with its report ID on success. A failed submission attempts to retract the opened report, marks the attachment `failed` with the raw reason, and returns `NCMEC_SUBMISSION_FAILED`. A failed retraction is logged without changing that recorded state.
When the resolved attachment has an author whose account an earlier NCMEC submission has not already disabled, Fluxer sets the account's deleted and disabled flags, clears any temporary ban, and records the deletion reason for child sexual content. It then schedules deletion 60 days ahead, deletes every authentication session, sends [User Update](/gateway/events/#user-update) to that account, and triggers a user archive for that account. When the account's public user fields change, Fluxer also sends [Guild Member Update](/gateway/events/#guild-member-update) to each guild the account is in.
Message content is deleted only after that archive completes. The archive is re-checked every 15 seconds by default, for at most 240 attempts. Once the archive is complete the reported message is deleted, its attachments are purged, and [Message Delete](/gateway/events/#message-delete) is sent.
A successful submission records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with the action `NCMEC Report`. It targets the reported user, or targets the source report when the attachment resolved to no author. Triggering the archive records a second entry with the action `trigger_user_archive` targeting the same user, skipped when the account already has an archive taken after the enforcement. Both entries record the reason returned as `audit_log_reason`.
An attachment that resolved to no author is submitted to NCMEC and audited. No account is disabled, no archive is triggered, and no message content is deleted.
### Rate limit
100 requests per minute for each authenticated user, on the shared `admin:message:operation` bucket.
@@ -22,7 +22,7 @@ Fluxer reads the tag, username, display name, and discriminator of both accounts
## Admin report object
A report is written once, when the reporter files it. [Update report](#update-report) moves it from PENDING to RESOLVED with an optional comment. [Schedule user deletion](/admin-api/users/#schedule-user-deletion) for any reason other than a user request also resolves every pending report against the deleted account, with no public comment, and sends each reporter the generic notice [Update report](#update-report) describes. A report is deleted at the end of its [retention period](#report-retention).
A report is written once, when the reporter files it. [Update report](#update-report) moves it from PENDING to RESOLVED with an optional comment. [Schedule user deletion](/admin-api/users/#schedule-user-deletion) for one of the abuse reasons listed there also resolves the pending reports against the deleted account, with no public comment, and sends each reporter the generic notice [Update report](#update-report) describes. A report in the `child_safety`, `underage_user`, or `self_harm` category is never resolved this way and stays PENDING. A report is deleted at the end of its [retention period](#report-retention).
[List reports](#list-reports) returns this object without message context. [Get report](#get-report) returns the [Admin report detail object](#admin-report-detail-object), which extends it.
@@ -32,7 +32,7 @@ A report is written once, when the reporter files it. [Update report](#update-re
| --- | --- | --- |
| report_id | snowflake | The ID of the report |
| reporter_id<sup>1</sup> | ?snowflake | The account that filed the report |
| reporter_tag<sup>1</sup> <sup>2</sup> | ?string | The reporter username and four-digit discriminator joined by `#` |
| reporter_tag<sup>1</sup> <sup>2</sup> <sup>18</sup> | ?string | The reporter username and four-digit discriminator joined by `#` |
| reporter_username<sup>1</sup> <sup>2</sup> | ?string | The username of the reporter |
| reporter_global_name<sup>1</sup> <sup>2</sup> | ?string | The display name of the reporter, or null when unset |
| reporter_discriminator<sup>1</sup> <sup>2</sup> | ?string | The zero-padded four-digit reporter discriminator |
@@ -50,7 +50,7 @@ A report is written once, when the reporter files it. [Update report](#update-re
| reporter_good_faith_confirmed?<sup>7</sup> <sup>13</sup> | ?boolean | Whether the reporter confirmed the notice in good faith |
| additional_info | ?string | The free-text detail the reporter supplied |
| reported_user_id<sup>14</sup> | ?snowflake | The reported account, or null for a guild report |
| reported_user_tag<sup>2</sup> | ?string | The reported username and four-digit discriminator joined by `#` |
| reported_user_tag<sup>2</sup> <sup>18</sup> | ?string | The reported username and four-digit discriminator joined by `#` |
| reported_user_username<sup>2</sup> | ?string | The username of the reported account |
| reported_user_global_name<sup>2</sup> | ?string | The display name of the reported account, or null when unset |
| reported_user_discriminator<sup>2</sup> | ?string | The zero-padded four-digit reported account discriminator |
@@ -67,7 +67,7 @@ A report is written once, when the reporter files it. [Update report](#update-re
| reported_webhook_guild_id?<sup>4</sup> <sup>7</sup> <sup>14</sup> <sup>15</sup> | ?snowflake | The guild the webhook belonged to |
| reported_webhook_created_at?<sup>7</sup> <sup>14</sup> | ?ISO8601 timestamp | When the webhook was created, derived from its ID |
| reported_webhook_creator_id?<sup>4</sup> <sup>7</sup> <sup>14</sup> <sup>15</sup> | ?snowflake | The account that created the webhook |
| reported_webhook_creator_tag?<sup>4</sup> <sup>7</sup> <sup>14</sup> <sup>15</sup> <sup>16</sup> | ?string | The creator username and four-digit discriminator joined by `#` |
| reported_webhook_creator_tag?<sup>4</sup> <sup>7</sup> <sup>14</sup> <sup>15</sup> <sup>16</sup> <sup>18</sup> | ?string | The creator username and four-digit discriminator joined by `#` |
| reported_webhook_creator_username?<sup>4</sup> <sup>7</sup> <sup>14</sup> <sup>15</sup> <sup>16</sup> | ?string | The username of the creator |
| reported_webhook_creator_global_name?<sup>4</sup> <sup>7</sup> <sup>14</sup> <sup>15</sup> <sup>16</sup> | ?string | The display name of the creator, or null when unset |
| reported_webhook_creator_discriminator?<sup>4</sup> <sup>7</sup> <sup>14</sup> <sup>15</sup> <sup>16</sup> | ?string | The zero-padded four-digit creator discriminator |
@@ -128,6 +128,8 @@ A report is written once, when the reporter files it. [Update report](#update-re
<sup>17</sup> Null when the report names no reported account, which includes a report about a message a webhook sent
<sup>18</sup> On an instance with the `none` tag style the tag of an account that is not a bot is the bare username, with no `#0000`
### Example
```json
@@ -235,15 +237,14 @@ A message report captures the reported message together with at most 25 messages
| content | string | The captured message content, empty when the message had none |
| timestamp<sup>6</sup> | ISO8601 timestamp | When the message was sent |
| attachments<sup>7</sup> | array[[report message attachment](#report-message-attachment-object) object] | The captured attachment copies |
| author_id<sup>9</sup> | snowflake | The author of the message |
| author_username<sup>9</sup> | string | The captured author username |
| author_id<sup>8</sup> | snowflake | The author of the message |
| author_username<sup>8</sup> | string | The captured author username |
| author_global_name<sup>4</sup> | ?string | The display name of the author |
| author_discriminator<sup>9</sup> | string | The captured zero-padded four-digit author discriminator |
| author_avatar<sup>9</sup> | ?string | The captured author avatar hash, or null when unset |
| webhook_id?<sup>3</sup> <sup>10</sup> | ?snowflake | The [webhook](/http-api/webhooks/) that sent the message |
| author_bot?<sup>3</sup> <sup>11</sup> | ?boolean | Whether the author account is a bot |
| missing_attachments?<sup>3</sup> <sup>12</sup> | array[[report missing attachment](#report-missing-attachment-object) object] | Attachments that could not be copied into the report |
| user_prior_ncmec_report_ids?<sup>3</sup> <sup>8</sup> | array[string] | The NCMEC report IDs previously filed against the author |
| author_discriminator<sup>8</sup> | string | The captured zero-padded four-digit author discriminator |
| author_avatar<sup>8</sup> | ?string | The captured author avatar hash, or null when unset |
| webhook_id?<sup>3</sup> <sup>9</sup> | ?snowflake | The [webhook](/http-api/webhooks/) that sent the message |
| author_bot?<sup>3</sup> <sup>10</sup> | ?boolean | Whether the author account is a bot |
| missing_attachments?<sup>3</sup> <sup>11</sup> | array[[report missing attachment](#report-missing-attachment-object) object] | Attachments that could not be copied into the report |
<sup>1</sup> The channel of the captured entry, otherwise the reported channel. An entry for which neither records a channel is left out of `message_context`
@@ -259,15 +260,13 @@ A message report captures the reported message together with at most 25 messages
<sup>7</sup> Fluxer drops an evidence copy that records no file name, resolves to no channel, or receives no download URL, so this array can be shorter than the original
<sup>8</sup> Sorted ascending, with at most 100 entries, and empty when no NCMEC report has been filed against the author or a webhook sent the message
<sup>8</sup> For a message a webhook sent, the webhook ID, the name and avatar hash the message was sent with, and the discriminator `0000`, as the [message object](/http-api/messages/#message-object) shows the author of a webhook message
<sup>9</sup> For a message a webhook sent, the webhook ID, the name and avatar hash the message was sent with, and the discriminator `0000`, as the [message object](/http-api/messages/#message-object) shows the author of a webhook message
<sup>9</sup> Null for a message an account sent
<sup>10</sup> Null for a message an account sent
<sup>10</sup> Read from the author account when the response is produced. Null for a message a webhook sent and for an author account that no longer resolves
<sup>11</sup> Read from the author account when the response is produced. Null for a message a webhook sent and for an author account that no longer resolves
<sup>12</sup> Empty when every attachment was copied, and on a report filed before missing attachments were recorded. An attachment is listed here when its copy failed or, for a file embedded in the message, when the file was no longer stored
<sup>11</sup> Empty when every attachment was copied, and on a report filed before missing attachments were recorded. An attachment is listed here when its copy failed or, for a file embedded in the message, when the file was no longer stored
### Report message attachment object
@@ -277,27 +276,14 @@ A message report captures the reported message together with at most 25 messages
| --- | --- | --- |
| id | snowflake | The ID of the attachment |
| filename | string | The captured file name |
| url<sup>13</sup> | string | The download URL for the evidence copy |
| url<sup>12</sup> | string | The download URL for the evidence copy |
| nsfw | ?boolean | Whether the attachment was marked NSFW |
| content_type | ?string | The captured MIME type, or null when unknown |
| width | ?integer | The pixel width, or null for a non-image attachment |
| height | ?integer | The pixel height, or null for a non-image attachment |
| size?<sup>3</sup> | ?integer | The size in bytes, or null when unknown |
| ncmec_status<sup>14</sup> | string | The [NCMEC submission status](#ncmec-submission-statuses) |
| ncmec_report_id | ?string | The NCMEC report ID, or null when the attachment was never submitted |
| ncmec_failure_reason | ?string | The failure detail, or null unless `ncmec_status` is failed |
<sup>13</sup> The URL expires after five minutes. Fetch the report again for fresh attachment links
<sup>14</sup> An in-flight submission is stored as `submitting`, a value the registry below does not list
#### NCMEC submission statuses
| Value | Description |
| --- | --- |
| not_submitted | The attachment has never been submitted to NCMEC |
| submitted | The attachment has been submitted to NCMEC |
| failed | Submission to NCMEC was attempted and failed |
<sup>12</sup> The URL expires after five minutes. Fetch the report again for fresh attachment links
### Report missing attachment object
@@ -313,9 +299,9 @@ An attachment of a captured message that Fluxer could not copy into the report.
| content_type | ?string | The captured MIME type, or null when unknown |
| width | ?integer | The pixel width, or null for a non-image attachment |
| height | ?integer | The pixel height, or null for a non-image attachment |
| size<sup>15</sup> | ?integer | The size in bytes, or null when unknown |
| size<sup>13</sup> | ?integer | The size in bytes, or null when unknown |
<sup>15</sup> `0` for a file embedded in the message that was no longer stored when the report was filed
<sup>13</sup> `0` for a file embedded in the message that was no longer stored when the report was filed
## Report profile snapshot object
@@ -710,7 +696,7 @@ Resolving a report notifies the reporter whatever `notify_reporter` says, as the
The report moves to RESOLVED and records `resolved_at`, `resolved_by_admin_id`, and the public comment.
Fluxer records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with target type `report`, target ID equal to the report ID, and action `resolve_report`. The metadata is `report_id`, `report_type`, `notify_reporter`, `reporter_dm_sent`, `reporter_email_sent`, and `reporter_dsa_email_sent`. When Fluxer does not attempt a notice, `reporter_notice_skipped` names the reason: `reporter_account_deleted`, `reporter_report_banned`, `reporter_lookup_failed`, or `no_reporter_email`.
Fluxer records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with target type `report`, target ID equal to the report ID, and action `resolve_report`. The metadata is `report_id`, `report_type`, `notify_reporter`, `reporter_dm_sent`, `reporter_email_sent`, and `reporter_dsa_email_sent`, plus `resolution` when the request sets one. When Fluxer does not attempt a notice, `reporter_notice_skipped` names the reason: `reporter_account_deleted`, `reporter_report_banned`, `reporter_lookup_failed`, or `no_reporter_email`.
Fluxer notifies the reporter of every resolution. A reporter with an account receives a system direct message, and an email when the account has an address. A Digital Services Act notice has no reporter account, so its verified address receives a decision email with the report ID, the comment when one is shared, and how to appeal. Nothing is sent when the reporter account is deleted or barred from reporting.
@@ -59,7 +59,7 @@ When the caller lacks `user:view:email`, `user:view:dob`, or `user:view:ip`, Flu
| deletion_audit_log_reason | ?string | The private reason stored with the pending deletion, and null without `audit_log:view` |
| deletion_scheduled_by | ?snowflake | The ID of the account that scheduled the pending deletion, or null when it was not recorded |
| deletion_scheduled_at | ?ISO8601 timestamp | The time the pending deletion was scheduled, or null when it was not recorded |
| acls<sup>7</sup> | array[string] | Effective [Admin ACLs](/admin-api/#acl-registry), with at most 106 entries |
| acls<sup>7</sup> | array[string] | Effective [Admin ACLs](/admin-api/#acl-registry), with at most 105 entries |
| traits<sup>8</sup> | array[string] | The free-form operator labels set on the account, with at most 100 entries |
| has_totp<sup>9</sup> | boolean | Whether a TOTP authenticator is registered |
| authenticator_types<sup>9</sup> <sup>12</sup> | array[integer] | Registered [authenticator types](/http-api/users/#authenticator-types), with at most 10 entries |
@@ -135,6 +135,7 @@ The `flags` field of the [Admin user object](#admin-user-object) is a 64-bit bit
| 1 &lt;&lt; 4 | FRIENDLY_BOT | Bot accepts friend requests from users |
| 1 &lt;&lt; 5 | FRIENDLY_BOT_MANUAL_APPROVAL | Bot requires manual approval for friend requests |
| 1 &lt;&lt; 6 | SPAMMER | Account is flagged as a spammer |
| 1 &lt;&lt; 7 | PROFILE_HIDDEN | Profile details are hidden from other users |
| 1 &lt;&lt; 33 | HIGH_GLOBAL_RATE_LIMIT | Account has elevated global rate limits |
| 1 &lt;&lt; 34 | DELETED | Account has been deleted |
| 1 &lt;&lt; 36 | SELF_DELETED | Account was self-deleted |
@@ -1147,7 +1148,7 @@ A path naming the acting account fails with 403 `ACCESS_DENIED` before the accou
| Field | Type | Description |
| --- | --- | --- |
| acls<sup>1</sup> | array[string] | Replacement [Admin ACLs](/admin-api/#acl-registry), with at most 106 values |
| acls<sup>1</sup> | array[string] | Replacement [Admin ACLs](/admin-api/#acl-registry), with at most 105 values |
<sup>1</sup> Required. A value outside the [ACL registry](/admin-api/#acl-registry) fails body validation, and a repeated value is collapsed
@@ -1595,9 +1596,9 @@ Fluxer cancels a Stripe subscription on the account without proration and refund
The account holder is emailed the deadline and the supplied `public_reason` when `notify_user` is true and the account has an email address. The wording depends on `reason_code`: `USER_REQUESTED` and `INACTIVITY` get a neutral notice, and every other code gets an enforcement notice. A failed email is logged and the schedule is still stored.
For every reason other than `USER_REQUESTED`, Fluxer also blocks the account's email address and resolves the pending reports against the account. These enforcement steps are best-effort and can fail without cancelling the deletion schedule.
For every reason other than `USER_REQUESTED`, Fluxer also blocks the account's email address. For the reasons `SPAM`, `CHEATING_OR_EXPLOITATION`, `COORDINATED_RAIDING`, `AUTOMATION_OR_SELFBOT`, `SCAM_OR_SOCIAL_ENGINEERING`, `HARASSMENT_OR_BULLYING`, `BAN_EVASION`, `TOKEN_OR_CREDENTIAL_SCAM`, `HATE_SPEECH_OR_EXTREMIST_CONTENT`, `MALICIOUS_LINKS_OR_MALWARE`, and `IMPERSONATION_OR_FAKE_IDENTITY`, Fluxer also resolves the pending reports against the account. A pending report in the `child_safety`, `underage_user`, or `self_harm` category is left for a moderator and stays pending. Every other reason leaves all reports pending. These enforcement steps are best-effort and can fail without cancelling the deletion schedule.
[User Update](/gateway/events/#user-update) is emitted after the sessions have already been deleted. The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `schedule_deletion`, target type `user`, and the metadata keys `days`, `reason_code`, `pending_deletion_at`, `notify_user`, and `notification_sent`. An attempted email adds `notification_template`. A replacement adds `replaced_pending_deletion_at`, `replaced_scheduled_by`, `replaced_scheduled_at`, and `replaced_reason_code` for the schedule it replaced. The identifier bans record their own blocklist entries. For every reason other than `USER_REQUESTED`, Fluxer also records a second entry with action `auto_resolve_reports_on_deletion`, target type `user`, and the metadata keys `outcome`, `found_count`, `resolved_count`, `failed_count`, and `notified_count`. `outcome` is `completed`, `search_unavailable` when the instance has no report search backend and every report stays pending, or `enumeration_failed` when listing the pending reports failed part way and only those already found were resolved. Each resolved report sends its reporter the generic notice [Update report](/admin-api/reports/#update-report) describes, with no public comment.
[User Update](/gateway/events/#user-update) is emitted after the sessions have already been deleted. The operation records one [Admin audit entry](/admin-api/#admin-audit-entry-object) with action `schedule_deletion`, target type `user`, and the metadata keys `days`, `reason_code`, `pending_deletion_at`, `notify_user`, and `notification_sent`. An attempted email adds `notification_template`. A replacement adds `replaced_pending_deletion_at`, `replaced_scheduled_by`, `replaced_scheduled_at`, and `replaced_reason_code` for the schedule it replaced. The identifier bans record their own blocklist entries. For each reason that resolves reports, Fluxer also records a second entry with action `auto_resolve_reports_on_deletion`, target type `user`, and the metadata keys `outcome`, `found_count`, `resolved_count`, `failed_count`, and `notified_count`. `found_count` leaves out the reports in the three categories that stay pending. No such entry is written for any other reason. `outcome` is `completed`, `search_unavailable` when the instance has no report search backend and every report stays pending, or `enumeration_failed` when listing the pending reports failed part way and only those already found were resolved. Each resolved report sends its reporter the generic notice [Update report](/admin-api/reports/#update-report) describes, with no public comment.
### Rate limit
@@ -770,7 +770,7 @@ One visible message was deleted.
| guild_id? | snowflake | Guild the channel belongs to |
| member?<sup>2</sup> | [guild member](/http-api/guild-members/#guild-member-object) object | The author's guild member object, present in a guild channel |
<sup>1</sup> Both fields are omitted when an instance administrator deleted the message through the Admin API, when Fluxer deleted it after a CSAM report, when Fluxer deleted it because content moderation blocked a link preview in it, or when Fluxer removed a published message and its copies together, and `author_id` is also omitted for a message with no author
<sup>1</sup> Both fields are omitted when an instance administrator deleted the message through the Admin API, when Fluxer deleted it because content moderation blocked a link preview in it, or when Fluxer removed a published message and its copies together, and `author_id` is also omitted for a message with no author
<sup>2</sup> The `user` field is removed from it, and the whole field is absent when `author_id` is absent or the author is no longer a member
@@ -774,14 +774,6 @@ The requested scope isn't supported or you don't have permission to request it
You don't have the permissions required to perform this action
### `NCMEC_ALREADY_SUBMITTED`
This content has already been submitted to NCMEC
### `NCMEC_SUBMISSION_FAILED`
We couldn't submit the report to NCMEC
### `NOT_A_BOT_APPLICATION`
This application isn't a bot
@@ -1843,10 +1835,6 @@ You must start a session before sending messages
Name can't be empty after normalization
### `NCMEC_ATTACHMENT_MUST_BE_IMAGE_OR_VIDEO`
Only image or video attachments can be reported to NCMEC
### `NEW_EMAIL_MUST_BE_DIFFERENT`
New email must be different from your current email
@@ -516,7 +516,7 @@ Default `http://localhost:3900`. The S3 API address. `media-proxy` defaults to e
#### `FLUXER_S3_PUBLIC_ENDPOINT`
No default. The host substituted into presigned URLs, which are the only place it appears. Compose forwards it from `.env`, and uses `FLUXER_S3_ENDPOINT` when it is unset, except on `app-proxy`, which gets only a value `.env` sets and adds it to its Content Security Policy.
No default. The host substituted into presigned URLs, which are the only place it appears. Compose forwards it from `.env`, and uses `FLUXER_S3_ENDPOINT` when it is unset, except on `app-proxy`, which gets only a value `.env` sets and adds it to its Content Security Policy. `admin` reads it to find the origin that report downloads are presigned against. See `FLUXER_S3_BUCKET_REPORTS`.
#### `FLUXER_S3_FORCE_PATH_STYLE`
@@ -548,7 +548,7 @@ Default `fluxer-uploads`. Raw uploads. `media-proxy` defaults to `uploads`, `app
#### `FLUXER_S3_BUCKET_REPORTS`
Default `fluxer-reports`. Abuse report evidence. Read by `api` and `worker`. Compose forwards it from `.env` with a default of `fluxer-reports`, so every service and `seaweedfs-init` name the same bucket.
Default `fluxer-reports`. Abuse report evidence. Read by `api`, `worker`, and `admin`. Compose forwards it from `.env` with a default of `fluxer-reports`, so every service and `seaweedfs-init` name the same bucket. `admin` allows one image origin for report attachments and profile snapshots in its Content Security Policy, the origin `api` presigns report downloads against. It works that origin out from this name, `FLUXER_S3_PUBLIC_ENDPOINT` or else `FLUXER_S3_ENDPOINT`, `FLUXER_S3_FORCE_PATH_STYLE` and `FLUXER_S3_BUCKET_UPLOADS`, so give `admin` the same values as `api`. The images load only when that origin is an address browsers can reach.
#### `FLUXER_S3_BUCKET_HARVESTS`
@@ -1002,26 +1002,6 @@ With prices saved in the dashboard, add the retired ID to its legacy prices befo
All are optional.
#### `FLUXER_NCMEC_ENABLED`
Default `false`. NCMEC reporting. Compose forwards it from `.env`.
#### `FLUXER_NCMEC_BASE_URL`
Default empty. The reporting endpoint, `https://report.cybertip.org/ispws` in production. Required when reporting is on. Compose forwards it from `.env`.
#### `FLUXER_NCMEC_USERNAME`
Default empty. The reporting login. Required when reporting is on. Compose forwards it from `.env`.
#### `FLUXER_NCMEC_PASSWORD`
Default empty. The reporting password. Required when reporting is on. Compose forwards it from `.env`.
#### `FLUXER_NCMEC_REPORTER_EMAIL`
Default empty. The contact address on reports. Required when reporting is on. Compose forwards it from `.env`.
#### `FLUXER_CLAMAV_ENABLED`
Default `false`. Upload virus scanning. Compose forwards it from `.env`, and no ClamAV container ships.
@@ -1046,7 +1026,7 @@ With feeds off, Fluxer checks none of this data. The first worker start with fee
#### `FLUXER_REPORT_RETENTION_DRY_RUN`
Default unset, which keeps the dry run. Read by `worker`. The daily [report retention](/admin-api/reports/#report-retention) job deletes reports 365 days after they were filed, with their evidence copies and search entries, unless a legal hold is in place. Until this is `false` or `0`, the job only logs what it would delete. Any other value, or no value, keeps the dry run. Check the counts in the `Processed report retention` log line before you turn deletion on, because deleted evidence cannot be restored. Compose does not forward it, so add it to `worker` through an override file.
Default unset, which keeps the dry run. Read by `worker`. The daily [report retention](/admin-api/reports/#report-retention) job deletes reports 365 days after they were filed, with their evidence copies and search entries, unless a legal hold is in place. Until this is `false` or `0`, the job only logs what it would delete. Any other value, or no value, keeps the dry run. Check the counts in the `Processed report retention` log line before you turn deletion on, because deleted evidence cannot be restored. Compose forwards it from `.env`.
#### `FLUXER_BREACHED_PASSWORD_CHECK_ENABLED`
@@ -1735,10 +1715,6 @@ Compose writes each of these from the `.env` name given, so setting the name its
`BUILD_VERSION`, `FLUXER_BUILD_VERSION` and `RELEASE_CHANNEL` describe the shipped build. `POD_NAME` and `HOSTNAME` come from the platform.
#### Report retention
`FLUXER_REPORT_RETENTION_DRY_RUN`, so the [report retention](/admin-api/reports/#report-retention) job stays in dry run until an override sets it on `worker`.
#### Test and development switches
`FLUXER_TEST_MODE_ENABLED`, `FLUXER_TEST_HARNESS_TOKEN`, `FLUXER_DISABLE_RATE_LIMITS`, `FLUXER_VALIDATE_RESPONSES` and `FLUXER_RELAX_REGISTRATION_RATE_LIMITS`. None of them belongs on a production instance.
@@ -2197,7 +2173,7 @@ Volume names are prefixed with the Compose project name, so `postgres-data` is `
| --- | --- | --- |
| fluxer | `FLUXER_S3_BUCKET_CDN` | Avatars, guild and entity assets, themes, entrance sounds, memes, and processed attachments |
| fluxer-uploads | `FLUXER_S3_BUCKET_UPLOADS` | Raw attachment uploads, before processing |
| fluxer-reports | `FLUXER_S3_BUCKET_REPORTS` | Abuse report evidence, and NCMEC payloads where that integration is on |
| fluxer-reports | `FLUXER_S3_BUCKET_REPORTS` | Abuse report evidence |
| fluxer-harvests | `FLUXER_S3_BUCKET_HARVESTS` | User and guild data archives |
`FLUXER_S3_BUCKET_STATIC` names an optional bucket. The shipped stack does not use or create it.
@@ -76,7 +76,7 @@ An edit of a published message reaches every copy that still exists, including c
Deleting a published message keeps each copy and edits it: the content becomes `[Original message deleted]`, attachments, embeds, and stickers are removed, `edited_timestamp` is set, and the `SOURCE_MESSAGE_DELETED` flag is added. The files of the published message are deleted with it. Deleting a copy removes that copy alone, and the files it showed stay with the published message.
When an instance administrator or a CSAM report removes a published message, every copy is deleted outright. When one of them removes a copy, the published message and every other copy are deleted too, so identical content leaves every guild at once. A published message that link preview moderation deletes takes its copies with it the same way.
When an instance administrator removes a published message, every copy is deleted outright. When an instance administrator removes a copy, the published message and every other copy are deleted too, so identical content leaves every guild at once. A published message that link preview moderation deletes takes its copies with it the same way.
## Cleanup