@gakr-gakr/discord 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/account-inspect-api.ts +6 -0
- package/action-runtime-api.ts +1 -0
- package/api.ts +130 -0
- package/autobot.plugin.json +15 -0
- package/channel-config-api.ts +1 -0
- package/channel-plugin-api.ts +3 -0
- package/config-api.ts +4 -0
- package/configured-state.ts +6 -0
- package/contract-api.ts +21 -0
- package/directory-contract-api.ts +4 -0
- package/doctor-contract-api.ts +1 -0
- package/index.ts +24 -0
- package/package.json +79 -0
- package/runtime-api.actions.ts +15 -0
- package/runtime-api.lookup.ts +22 -0
- package/runtime-api.monitor.ts +50 -0
- package/runtime-api.send.ts +79 -0
- package/runtime-api.threads.ts +31 -0
- package/runtime-api.ts +181 -0
- package/runtime-setter-api.ts +3 -0
- package/secret-contract-api.ts +4 -0
- package/security-audit-contract-api.ts +1 -0
- package/security-contract-api.ts +4 -0
- package/session-key-api.ts +1 -0
- package/setup-entry.ts +9 -0
- package/setup-plugin-api.ts +3 -0
- package/src/account-inspect.ts +131 -0
- package/src/accounts.ts +205 -0
- package/src/actions/handle-action.guild-admin.ts +421 -0
- package/src/actions/handle-action.ts +402 -0
- package/src/actions/runtime.guild.ts +446 -0
- package/src/actions/runtime.messaging.messages.ts +226 -0
- package/src/actions/runtime.messaging.reactions.ts +67 -0
- package/src/actions/runtime.messaging.runtime.ts +73 -0
- package/src/actions/runtime.messaging.send.ts +336 -0
- package/src/actions/runtime.messaging.shared.ts +97 -0
- package/src/actions/runtime.messaging.ts +37 -0
- package/src/actions/runtime.moderation-shared.ts +48 -0
- package/src/actions/runtime.moderation.ts +116 -0
- package/src/actions/runtime.presence.ts +117 -0
- package/src/actions/runtime.shared.ts +86 -0
- package/src/actions/runtime.ts +87 -0
- package/src/api.ts +219 -0
- package/src/approval-handler.runtime.ts +636 -0
- package/src/approval-native.ts +219 -0
- package/src/approval-runtime.ts +14 -0
- package/src/approval-shared.ts +56 -0
- package/src/audit-core.ts +178 -0
- package/src/audit.ts +32 -0
- package/src/channel-actions.runtime.ts +1 -0
- package/src/channel-actions.ts +254 -0
- package/src/channel-api.ts +29 -0
- package/src/channel.conversation.ts +159 -0
- package/src/channel.loaders.ts +50 -0
- package/src/channel.runtime.ts +1 -0
- package/src/channel.setup.ts +12 -0
- package/src/channel.ts +728 -0
- package/src/chunk.ts +321 -0
- package/src/client.ts +143 -0
- package/src/component-custom-id.ts +72 -0
- package/src/components-registry.ts +356 -0
- package/src/components.builders.ts +410 -0
- package/src/components.modal.ts +124 -0
- package/src/components.parse.ts +407 -0
- package/src/components.ts +54 -0
- package/src/components.types.ts +187 -0
- package/src/config-schema.ts +6 -0
- package/src/config-ui-hints.ts +354 -0
- package/src/conversation-identity.ts +58 -0
- package/src/delivery-retry.ts +56 -0
- package/src/directory-cache.ts +116 -0
- package/src/directory-config.ts +58 -0
- package/src/directory-live.ts +135 -0
- package/src/doctor-contract.ts +477 -0
- package/src/doctor-shared.ts +5 -0
- package/src/doctor.ts +340 -0
- package/src/draft-chunking.ts +43 -0
- package/src/draft-stream.ts +162 -0
- package/src/error-body.ts +38 -0
- package/src/exec-approvals.ts +110 -0
- package/src/gateway-logging.ts +67 -0
- package/src/group-policy.ts +113 -0
- package/src/guilds.ts +29 -0
- package/src/inbound-event-delivery.ts +135 -0
- package/src/interactive-dispatch.ts +104 -0
- package/src/internal/api.commands.ts +51 -0
- package/src/internal/api.guild.ts +164 -0
- package/src/internal/api.interactions.ts +53 -0
- package/src/internal/api.messages.ts +113 -0
- package/src/internal/api.reactions.ts +38 -0
- package/src/internal/api.ts +61 -0
- package/src/internal/api.users.ts +19 -0
- package/src/internal/api.webhooks.ts +13 -0
- package/src/internal/client.ts +310 -0
- package/src/internal/command-deploy.ts +352 -0
- package/src/internal/commands.ts +188 -0
- package/src/internal/components.base.ts +65 -0
- package/src/internal/components.message.ts +279 -0
- package/src/internal/components.modal.ts +95 -0
- package/src/internal/components.ts +31 -0
- package/src/internal/discord.ts +11 -0
- package/src/internal/embeds.ts +35 -0
- package/src/internal/entity-cache.ts +98 -0
- package/src/internal/event-queue.ts +185 -0
- package/src/internal/gateway-close-codes.ts +25 -0
- package/src/internal/gateway-dispatch.ts +96 -0
- package/src/internal/gateway-identify-limiter.ts +26 -0
- package/src/internal/gateway-lifecycle.ts +75 -0
- package/src/internal/gateway-rate-limit.ts +104 -0
- package/src/internal/gateway.ts +479 -0
- package/src/internal/interaction-dispatch.ts +162 -0
- package/src/internal/interaction-options.ts +98 -0
- package/src/internal/interaction-response.ts +53 -0
- package/src/internal/interactions.ts +378 -0
- package/src/internal/listeners.ts +91 -0
- package/src/internal/modal-fields.ts +95 -0
- package/src/internal/payload.ts +69 -0
- package/src/internal/rest-body.ts +115 -0
- package/src/internal/rest-errors.ts +88 -0
- package/src/internal/rest-routes.ts +50 -0
- package/src/internal/rest-scheduler.ts +557 -0
- package/src/internal/rest.ts +322 -0
- package/src/internal/schemas.ts +36 -0
- package/src/internal/structures.ts +280 -0
- package/src/internal/test-builders.test-support.ts +167 -0
- package/src/internal/voice.ts +49 -0
- package/src/media-detection.ts +28 -0
- package/src/mentions.ts +147 -0
- package/src/monitor/ack-reactions.ts +70 -0
- package/src/monitor/agent-components-auth.ts +7 -0
- package/src/monitor/agent-components-context.ts +154 -0
- package/src/monitor/agent-components-data.ts +224 -0
- package/src/monitor/agent-components-dm-auth.ts +177 -0
- package/src/monitor/agent-components-guild-auth.ts +322 -0
- package/src/monitor/agent-components-helpers.runtime.ts +3 -0
- package/src/monitor/agent-components-helpers.ts +34 -0
- package/src/monitor/agent-components-reply.ts +10 -0
- package/src/monitor/agent-components.deps.runtime.ts +2 -0
- package/src/monitor/agent-components.dispatch.ts +359 -0
- package/src/monitor/agent-components.handlers.ts +303 -0
- package/src/monitor/agent-components.modal.ts +160 -0
- package/src/monitor/agent-components.plugin-interactive.ts +187 -0
- package/src/monitor/agent-components.runtime.ts +14 -0
- package/src/monitor/agent-components.system-controls.ts +215 -0
- package/src/monitor/agent-components.ts +70 -0
- package/src/monitor/agent-components.types.ts +58 -0
- package/src/monitor/agent-components.wildcard-controls.ts +171 -0
- package/src/monitor/allow-list.ts +631 -0
- package/src/monitor/auto-presence.ts +356 -0
- package/src/monitor/channel-access.ts +102 -0
- package/src/monitor/commands.ts +9 -0
- package/src/monitor/dm-command-auth.ts +259 -0
- package/src/monitor/dm-command-decision.ts +49 -0
- package/src/monitor/exec-approvals.ts +161 -0
- package/src/monitor/format.ts +45 -0
- package/src/monitor/gateway-handle.ts +34 -0
- package/src/monitor/gateway-metadata.ts +298 -0
- package/src/monitor/gateway-plugin.ts +302 -0
- package/src/monitor/gateway-registry.ts +37 -0
- package/src/monitor/gateway-supervisor.ts +206 -0
- package/src/monitor/inbound-context.ts +95 -0
- package/src/monitor/inbound-dedupe.ts +79 -0
- package/src/monitor/inbound-job.ts +118 -0
- package/src/monitor/listeners.queue.ts +91 -0
- package/src/monitor/listeners.reactions.ts +594 -0
- package/src/monitor/listeners.ts +150 -0
- package/src/monitor/message-channel-info.ts +96 -0
- package/src/monitor/message-forwarded.ts +114 -0
- package/src/monitor/message-handler.batch-gate.ts +19 -0
- package/src/monitor/message-handler.context.ts +492 -0
- package/src/monitor/message-handler.dm-preflight.ts +119 -0
- package/src/monitor/message-handler.draft-preview.ts +436 -0
- package/src/monitor/message-handler.hydration.ts +198 -0
- package/src/monitor/message-handler.module-test-helpers.ts +31 -0
- package/src/monitor/message-handler.preflight-channel-access.ts +86 -0
- package/src/monitor/message-handler.preflight-channel-context.ts +58 -0
- package/src/monitor/message-handler.preflight-context.ts +54 -0
- package/src/monitor/message-handler.preflight-helpers.ts +164 -0
- package/src/monitor/message-handler.preflight-history.ts +23 -0
- package/src/monitor/message-handler.preflight-logging.ts +36 -0
- package/src/monitor/message-handler.preflight-pluralkit.ts +28 -0
- package/src/monitor/message-handler.preflight-runtime.ts +28 -0
- package/src/monitor/message-handler.preflight-thread.ts +49 -0
- package/src/monitor/message-handler.preflight.ts +822 -0
- package/src/monitor/message-handler.preflight.types.ts +115 -0
- package/src/monitor/message-handler.process.ts +1033 -0
- package/src/monitor/message-handler.routing-preflight.ts +112 -0
- package/src/monitor/message-handler.ts +309 -0
- package/src/monitor/message-media.ts +536 -0
- package/src/monitor/message-run-queue.ts +101 -0
- package/src/monitor/message-text.ts +171 -0
- package/src/monitor/message-utils.ts +34 -0
- package/src/monitor/model-picker-preferences.ts +184 -0
- package/src/monitor/model-picker.state.ts +364 -0
- package/src/monitor/model-picker.test-utils.ts +26 -0
- package/src/monitor/model-picker.ts +38 -0
- package/src/monitor/model-picker.view.ts +722 -0
- package/src/monitor/native-command-agent-reply.ts +125 -0
- package/src/monitor/native-command-arg-ui.ts +233 -0
- package/src/monitor/native-command-auth.ts +309 -0
- package/src/monitor/native-command-bypass.ts +13 -0
- package/src/monitor/native-command-context.ts +109 -0
- package/src/monitor/native-command-dispatch.ts +35 -0
- package/src/monitor/native-command-model-picker-apply.ts +209 -0
- package/src/monitor/native-command-model-picker-interaction.ts +516 -0
- package/src/monitor/native-command-model-picker-ui.ts +357 -0
- package/src/monitor/native-command-reply.ts +185 -0
- package/src/monitor/native-command-route.ts +91 -0
- package/src/monitor/native-command-status.ts +76 -0
- package/src/monitor/native-command-ui.ts +26 -0
- package/src/monitor/native-command-ui.types.ts +20 -0
- package/src/monitor/native-command.args.ts +45 -0
- package/src/monitor/native-command.options.ts +153 -0
- package/src/monitor/native-command.runtime.ts +51 -0
- package/src/monitor/native-command.ts +747 -0
- package/src/monitor/native-command.types.ts +9 -0
- package/src/monitor/native-interaction-channel-context.ts +50 -0
- package/src/monitor/preflight-audio.runtime.ts +9 -0
- package/src/monitor/preflight-audio.ts +130 -0
- package/src/monitor/presence-cache.ts +61 -0
- package/src/monitor/presence.ts +50 -0
- package/src/monitor/provider-session.runtime.ts +12 -0
- package/src/monitor/provider.acp.ts +89 -0
- package/src/monitor/provider.allowlist.ts +398 -0
- package/src/monitor/provider.cleanup.ts +41 -0
- package/src/monitor/provider.commands.ts +129 -0
- package/src/monitor/provider.config-log.ts +45 -0
- package/src/monitor/provider.deploy-errors.ts +362 -0
- package/src/monitor/provider.deploy.ts +221 -0
- package/src/monitor/provider.interactions.ts +160 -0
- package/src/monitor/provider.lifecycle.ts +562 -0
- package/src/monitor/provider.runtime.ts +1 -0
- package/src/monitor/provider.startup-log.ts +32 -0
- package/src/monitor/provider.startup.ts +323 -0
- package/src/monitor/provider.ts +688 -0
- package/src/monitor/reply-context.ts +64 -0
- package/src/monitor/reply-delivery.ts +216 -0
- package/src/monitor/reply-safety.ts +96 -0
- package/src/monitor/rest-fetch.ts +97 -0
- package/src/monitor/route-resolution.ts +140 -0
- package/src/monitor/sender-identity.ts +81 -0
- package/src/monitor/startup-status.ts +10 -0
- package/src/monitor/status.ts +22 -0
- package/src/monitor/system-events.ts +55 -0
- package/src/monitor/thread-bindings.config.ts +35 -0
- package/src/monitor/thread-bindings.discord-api.ts +310 -0
- package/src/monitor/thread-bindings.lifecycle.ts +354 -0
- package/src/monitor/thread-bindings.manager.ts +554 -0
- package/src/monitor/thread-bindings.messages.ts +6 -0
- package/src/monitor/thread-bindings.persona.ts +25 -0
- package/src/monitor/thread-bindings.session-adapter.ts +229 -0
- package/src/monitor/thread-bindings.session-shared.ts +59 -0
- package/src/monitor/thread-bindings.session-updates.ts +35 -0
- package/src/monitor/thread-bindings.state.ts +540 -0
- package/src/monitor/thread-bindings.ts +48 -0
- package/src/monitor/thread-bindings.types.ts +83 -0
- package/src/monitor/thread-channel-context.ts +112 -0
- package/src/monitor/thread-session-close.ts +63 -0
- package/src/monitor/thread-title.ts +181 -0
- package/src/monitor/threading.auto-thread.ts +287 -0
- package/src/monitor/threading.cache.ts +45 -0
- package/src/monitor/threading.starter.ts +288 -0
- package/src/monitor/threading.ts +20 -0
- package/src/monitor/threading.types.ts +102 -0
- package/src/monitor/timeouts.ts +84 -0
- package/src/monitor/typing.ts +17 -0
- package/src/monitor.gateway.ts +75 -0
- package/src/monitor.ts +28 -0
- package/src/network-config.ts +79 -0
- package/src/normalize.ts +86 -0
- package/src/outbound-adapter.ts +327 -0
- package/src/outbound-approval.ts +29 -0
- package/src/outbound-components.ts +86 -0
- package/src/outbound-payload.ts +208 -0
- package/src/outbound-send-context.ts +92 -0
- package/src/outbound-session-route.ts +72 -0
- package/src/pluralkit.ts +58 -0
- package/src/preview-streaming.ts +18 -0
- package/src/probe.runtime.ts +1 -0
- package/src/probe.ts +237 -0
- package/src/proxy-fetch.ts +92 -0
- package/src/proxy-request-client.ts +21 -0
- package/src/recipient-resolution.ts +39 -0
- package/src/resolve-allowlist-common.ts +39 -0
- package/src/resolve-channels.ts +369 -0
- package/src/resolve-users.ts +184 -0
- package/src/retry.ts +98 -0
- package/src/runtime-api.ts +64 -0
- package/src/runtime-config.ts +16 -0
- package/src/runtime.ts +23 -0
- package/src/secret-config-contract.ts +140 -0
- package/src/security-audit.runtime.ts +1 -0
- package/src/security-audit.ts +208 -0
- package/src/security-contract.ts +47 -0
- package/src/security-doctor.ts +20 -0
- package/src/security.ts +60 -0
- package/src/send-target-parsing.ts +14 -0
- package/src/send.channels.ts +139 -0
- package/src/send.components.ts +391 -0
- package/src/send.emojis-stickers.ts +57 -0
- package/src/send.guild.ts +170 -0
- package/src/send.message-request.ts +112 -0
- package/src/send.messages.ts +229 -0
- package/src/send.outbound.ts +459 -0
- package/src/send.permissions.ts +283 -0
- package/src/send.reactions.ts +155 -0
- package/src/send.receipt.ts +69 -0
- package/src/send.shared.ts +469 -0
- package/src/send.ts +82 -0
- package/src/send.types.ts +191 -0
- package/src/send.typing.ts +9 -0
- package/src/send.voice.ts +140 -0
- package/src/send.webhook.ts +137 -0
- package/src/session-contract.ts +3 -0
- package/src/session-key-normalization.ts +47 -0
- package/src/setup-account-state.ts +144 -0
- package/src/setup-adapter.ts +14 -0
- package/src/setup-core.ts +215 -0
- package/src/setup-runtime-helpers.ts +10 -0
- package/src/setup-surface.ts +132 -0
- package/src/shared-interactive.ts +167 -0
- package/src/shared.ts +197 -0
- package/src/status-issues.ts +201 -0
- package/src/subagent-hooks.ts +232 -0
- package/src/target-parsing.ts +70 -0
- package/src/target-resolver.ts +129 -0
- package/src/targets.ts +12 -0
- package/src/token.ts +107 -0
- package/src/ui-colors.ts +27 -0
- package/src/ui.ts +20 -0
- package/src/voice/access.ts +126 -0
- package/src/voice/audio.ts +249 -0
- package/src/voice/capture-state.ts +120 -0
- package/src/voice/command.ts +284 -0
- package/src/voice/config.ts +8 -0
- package/src/voice/ingress.ts +164 -0
- package/src/voice/manager.runtime.ts +14 -0
- package/src/voice/manager.ts +1155 -0
- package/src/voice/prompt.ts +22 -0
- package/src/voice/realtime.ts +1370 -0
- package/src/voice/receive-recovery.ts +159 -0
- package/src/voice/sanitize.ts +29 -0
- package/src/voice/sdk-runtime.ts +14 -0
- package/src/voice/segment.ts +160 -0
- package/src/voice/session.ts +81 -0
- package/src/voice/speaker-context.ts +127 -0
- package/src/voice/tts.ts +151 -0
- package/src/voice-message.ts +474 -0
- package/subagent-hooks-api.ts +27 -0
- package/test-api.ts +4 -0
- package/thread-binding-api.ts +1 -0
- package/timeouts.ts +6 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { coerceNativeSetting, normalizeAllowFromList } from "autobot/plugin-sdk/channel-policy";
|
|
2
|
+
import { readChannelAllowFromStore } from "autobot/plugin-sdk/conversation-runtime";
|
|
3
|
+
import { isDangerousNameMatchingEnabled } from "autobot/plugin-sdk/dangerous-name-runtime";
|
|
4
|
+
import {
|
|
5
|
+
resolveNativeCommandsEnabled,
|
|
6
|
+
resolveNativeSkillsEnabled,
|
|
7
|
+
} from "autobot/plugin-sdk/native-command-config-runtime";
|
|
8
|
+
import type { ResolvedDiscordAccount } from "./accounts.js";
|
|
9
|
+
import type { AutoBotConfig } from "./runtime-api.js";
|
|
10
|
+
import { isDiscordMutableAllowEntry } from "./security-doctor.js";
|
|
11
|
+
|
|
12
|
+
function normalizeOptionalString(value: string | null | undefined): string | undefined {
|
|
13
|
+
const normalized = value?.trim();
|
|
14
|
+
return normalized ? normalized : undefined;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function addDiscordNameBasedEntries(params: {
|
|
18
|
+
target: Set<string>;
|
|
19
|
+
values: unknown;
|
|
20
|
+
source: string;
|
|
21
|
+
}) {
|
|
22
|
+
if (!Array.isArray(params.values)) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
for (const value of params.values) {
|
|
26
|
+
if (!isDiscordMutableAllowEntry(String(value))) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
const text = normalizeOptionalString(String(value)) ?? "";
|
|
30
|
+
if (!text) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
params.target.add(`${params.source}:${text}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function collectDiscordSecurityAuditFindings(params: {
|
|
38
|
+
cfg: AutoBotConfig;
|
|
39
|
+
accountId?: string | null;
|
|
40
|
+
account: ResolvedDiscordAccount;
|
|
41
|
+
orderedAccountIds: string[];
|
|
42
|
+
hasExplicitAccountPath: boolean;
|
|
43
|
+
}) {
|
|
44
|
+
const findings: Array<{
|
|
45
|
+
checkId: string;
|
|
46
|
+
severity: "info" | "warn" | "critical";
|
|
47
|
+
title: string;
|
|
48
|
+
detail: string;
|
|
49
|
+
remediation?: string;
|
|
50
|
+
}> = [];
|
|
51
|
+
const discordCfg = params.account.config ?? {};
|
|
52
|
+
const accountId =
|
|
53
|
+
normalizeOptionalString(params.accountId) ?? params.account.accountId ?? "default";
|
|
54
|
+
const dangerousNameMatchingEnabled = isDangerousNameMatchingEnabled(discordCfg);
|
|
55
|
+
const storeAllowFrom = await readChannelAllowFromStore("discord", process.env, accountId).catch(
|
|
56
|
+
() => [],
|
|
57
|
+
);
|
|
58
|
+
const discordNameBasedAllowEntries = new Set<string>();
|
|
59
|
+
const discordPathPrefix =
|
|
60
|
+
params.orderedAccountIds.length > 1 || params.hasExplicitAccountPath
|
|
61
|
+
? `channels.discord.accounts.${accountId}`
|
|
62
|
+
: "channels.discord";
|
|
63
|
+
|
|
64
|
+
addDiscordNameBasedEntries({
|
|
65
|
+
target: discordNameBasedAllowEntries,
|
|
66
|
+
values: discordCfg.allowFrom,
|
|
67
|
+
source: `${discordPathPrefix}.allowFrom`,
|
|
68
|
+
});
|
|
69
|
+
addDiscordNameBasedEntries({
|
|
70
|
+
target: discordNameBasedAllowEntries,
|
|
71
|
+
values: (discordCfg.dm as { allowFrom?: unknown } | undefined)?.allowFrom,
|
|
72
|
+
source: `${discordPathPrefix}.dm.allowFrom`,
|
|
73
|
+
});
|
|
74
|
+
addDiscordNameBasedEntries({
|
|
75
|
+
target: discordNameBasedAllowEntries,
|
|
76
|
+
values: storeAllowFrom,
|
|
77
|
+
source: "~/.autobot/credentials/discord-allowFrom.json",
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const guildEntries = (discordCfg.guilds as Record<string, unknown> | undefined) ?? {};
|
|
81
|
+
for (const [guildKey, guildValue] of Object.entries(guildEntries)) {
|
|
82
|
+
if (!guildValue || typeof guildValue !== "object") {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const guild = guildValue as Record<string, unknown>;
|
|
86
|
+
addDiscordNameBasedEntries({
|
|
87
|
+
target: discordNameBasedAllowEntries,
|
|
88
|
+
values: guild.users,
|
|
89
|
+
source: `${discordPathPrefix}.guilds.${guildKey}.users`,
|
|
90
|
+
});
|
|
91
|
+
const channels = guild.channels;
|
|
92
|
+
if (!channels || typeof channels !== "object") {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
for (const [channelKey, channelValue] of Object.entries(channels as Record<string, unknown>)) {
|
|
96
|
+
if (!channelValue || typeof channelValue !== "object") {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
const channel = channelValue as Record<string, unknown>;
|
|
100
|
+
addDiscordNameBasedEntries({
|
|
101
|
+
target: discordNameBasedAllowEntries,
|
|
102
|
+
values: channel.users,
|
|
103
|
+
source: `${discordPathPrefix}.guilds.${guildKey}.channels.${channelKey}.users`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (discordNameBasedAllowEntries.size > 0) {
|
|
109
|
+
const examples = Array.from(discordNameBasedAllowEntries).slice(0, 5);
|
|
110
|
+
const more =
|
|
111
|
+
discordNameBasedAllowEntries.size > examples.length
|
|
112
|
+
? ` (+${discordNameBasedAllowEntries.size - examples.length} more)`
|
|
113
|
+
: "";
|
|
114
|
+
findings.push({
|
|
115
|
+
checkId: "channels.discord.allowFrom.name_based_entries",
|
|
116
|
+
severity: dangerousNameMatchingEnabled ? "info" : "warn",
|
|
117
|
+
title: dangerousNameMatchingEnabled
|
|
118
|
+
? "Discord allowlist uses break-glass name/tag matching"
|
|
119
|
+
: "Discord allowlist contains name or tag entries",
|
|
120
|
+
detail: dangerousNameMatchingEnabled
|
|
121
|
+
? "Discord name/tag allowlist matching is explicitly enabled via dangerouslyAllowNameMatching. This mutable-identity mode is operator-selected break-glass behavior and out-of-scope for vulnerability reports by itself. " +
|
|
122
|
+
`Found: ${examples.join(", ")}${more}.`
|
|
123
|
+
: "Discord name/tag allowlist matching uses normalized slugs and can collide across users. " +
|
|
124
|
+
`Found: ${examples.join(", ")}${more}.`,
|
|
125
|
+
remediation: dangerousNameMatchingEnabled
|
|
126
|
+
? "Prefer stable Discord IDs (or <@id>/user:<id>/pk:<id>), then disable dangerouslyAllowNameMatching."
|
|
127
|
+
: "Prefer stable Discord IDs (or <@id>/user:<id>/pk:<id>) in channels.discord.allowFrom and channels.discord.guilds.*.users, or explicitly opt in with dangerouslyAllowNameMatching=true if you accept the risk.",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const nativeEnabled = resolveNativeCommandsEnabled({
|
|
132
|
+
providerId: "discord",
|
|
133
|
+
providerSetting: coerceNativeSetting(
|
|
134
|
+
(discordCfg.commands as { native?: unknown } | undefined)?.native,
|
|
135
|
+
),
|
|
136
|
+
globalSetting: params.cfg.commands?.native,
|
|
137
|
+
});
|
|
138
|
+
const nativeSkillsEnabled = resolveNativeSkillsEnabled({
|
|
139
|
+
providerId: "discord",
|
|
140
|
+
providerSetting: coerceNativeSetting(
|
|
141
|
+
(discordCfg.commands as { nativeSkills?: unknown } | undefined)?.nativeSkills,
|
|
142
|
+
),
|
|
143
|
+
globalSetting: params.cfg.commands?.nativeSkills,
|
|
144
|
+
});
|
|
145
|
+
if (!nativeEnabled && !nativeSkillsEnabled) {
|
|
146
|
+
return findings;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const defaultGroupPolicy = params.cfg.channels?.defaults?.groupPolicy;
|
|
150
|
+
const groupPolicy =
|
|
151
|
+
(discordCfg.groupPolicy as string | undefined) ?? defaultGroupPolicy ?? "allowlist";
|
|
152
|
+
const guildsConfigured = Object.keys(guildEntries).length > 0;
|
|
153
|
+
const hasAnyUserAllowlist = Object.values(guildEntries).some((guild) => {
|
|
154
|
+
if (!guild || typeof guild !== "object") {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
const record = guild as Record<string, unknown>;
|
|
158
|
+
if (Array.isArray(record.users) && record.users.length > 0) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
const channels = record.channels;
|
|
162
|
+
if (!channels || typeof channels !== "object") {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return Object.values(channels as Record<string, unknown>).some((channel) => {
|
|
166
|
+
if (!channel || typeof channel !== "object") {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
const channelRecord = channel as Record<string, unknown>;
|
|
170
|
+
return Array.isArray(channelRecord.users) && channelRecord.users.length > 0;
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
const dmAllowFromRaw = (discordCfg.dm as { allowFrom?: unknown } | undefined)?.allowFrom;
|
|
174
|
+
const dmAllowFrom = Array.isArray(dmAllowFromRaw) ? dmAllowFromRaw : [];
|
|
175
|
+
const ownerAllowFromConfigured =
|
|
176
|
+
normalizeAllowFromList([...dmAllowFrom, ...storeAllowFrom]).length > 0;
|
|
177
|
+
const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
|
|
178
|
+
|
|
179
|
+
if (!useAccessGroups && groupPolicy !== "disabled" && guildsConfigured && !hasAnyUserAllowlist) {
|
|
180
|
+
findings.push({
|
|
181
|
+
checkId: "channels.discord.commands.native.unrestricted",
|
|
182
|
+
severity: "critical",
|
|
183
|
+
title: "Discord slash commands are unrestricted",
|
|
184
|
+
detail:
|
|
185
|
+
"commands.useAccessGroups=false disables sender allowlists for Discord slash commands unless a per-guild/channel users allowlist is configured; with no users allowlist, any user in allowed guild channels can invoke /… commands.",
|
|
186
|
+
remediation:
|
|
187
|
+
"Set commands.useAccessGroups=true (recommended), or configure channels.discord.guilds.<id>.users (or channels.discord.guilds.<id>.channels.<channel>.users).",
|
|
188
|
+
});
|
|
189
|
+
} else if (
|
|
190
|
+
useAccessGroups &&
|
|
191
|
+
groupPolicy !== "disabled" &&
|
|
192
|
+
guildsConfigured &&
|
|
193
|
+
!ownerAllowFromConfigured &&
|
|
194
|
+
!hasAnyUserAllowlist
|
|
195
|
+
) {
|
|
196
|
+
findings.push({
|
|
197
|
+
checkId: "channels.discord.commands.native.no_allowlists",
|
|
198
|
+
severity: "warn",
|
|
199
|
+
title: "Discord slash commands have no allowlists",
|
|
200
|
+
detail:
|
|
201
|
+
"Discord slash commands are enabled, but neither an owner allowFrom list nor any per-guild/channel users allowlist is configured; /… commands will be rejected for everyone.",
|
|
202
|
+
remediation:
|
|
203
|
+
"Add your user id to channels.discord.allowFrom (or approve yourself via pairing), or configure channels.discord.guilds.<id>.users.",
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return findings;
|
|
208
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { isRecord } from "autobot/plugin-sdk/string-coerce-runtime";
|
|
2
|
+
|
|
3
|
+
type UnsupportedSecretRefConfigCandidate = {
|
|
4
|
+
path: string;
|
|
5
|
+
value: unknown;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const unsupportedSecretRefSurfacePatterns = [
|
|
9
|
+
"channels.discord.threadBindings.webhookToken",
|
|
10
|
+
"channels.discord.accounts.*.threadBindings.webhookToken",
|
|
11
|
+
] as const;
|
|
12
|
+
|
|
13
|
+
export function collectUnsupportedSecretRefConfigCandidates(
|
|
14
|
+
raw: unknown,
|
|
15
|
+
): UnsupportedSecretRefConfigCandidate[] {
|
|
16
|
+
if (!isRecord(raw)) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
if (!isRecord(raw.channels) || !isRecord(raw.channels.discord)) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const candidates: UnsupportedSecretRefConfigCandidate[] = [];
|
|
24
|
+
const discord = raw.channels.discord;
|
|
25
|
+
const threadBindings = isRecord(discord.threadBindings) ? discord.threadBindings : null;
|
|
26
|
+
if (threadBindings) {
|
|
27
|
+
candidates.push({
|
|
28
|
+
path: "channels.discord.threadBindings.webhookToken",
|
|
29
|
+
value: threadBindings.webhookToken,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const accounts = isRecord(discord.accounts) ? discord.accounts : null;
|
|
34
|
+
if (!accounts) {
|
|
35
|
+
return candidates;
|
|
36
|
+
}
|
|
37
|
+
for (const [accountId, account] of Object.entries(accounts)) {
|
|
38
|
+
if (!isRecord(account) || !isRecord(account.threadBindings)) {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
candidates.push({
|
|
42
|
+
path: `channels.discord.accounts.${accountId}.threadBindings.webhookToken`,
|
|
43
|
+
value: account.threadBindings.webhookToken,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return candidates;
|
|
47
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function isDiscordMutableAllowEntry(raw: string): boolean {
|
|
2
|
+
const text = raw.trim();
|
|
3
|
+
if (!text || text === "*") {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const maybeMentionId = text.replace(/^<@!?/, "").replace(/>$/, "");
|
|
8
|
+
if (/^\d+$/.test(maybeMentionId)) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
for (const prefix of ["discord:", "user:", "pk:"]) {
|
|
13
|
+
if (!text.startsWith(prefix)) {
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
return text.slice(prefix.length).trim().length === 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return true;
|
|
20
|
+
}
|
package/src/security.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createScopedDmSecurityResolver } from "autobot/plugin-sdk/channel-config-helpers";
|
|
2
|
+
import { createOpenProviderConfiguredRouteWarningCollector } from "autobot/plugin-sdk/channel-policy";
|
|
3
|
+
import {
|
|
4
|
+
resolveDiscordAccountAllowFrom,
|
|
5
|
+
resolveDiscordAccountDmPolicy,
|
|
6
|
+
type ResolvedDiscordAccount,
|
|
7
|
+
} from "./accounts.js";
|
|
8
|
+
import type { ChannelPlugin } from "./channel-api.js";
|
|
9
|
+
|
|
10
|
+
const resolveDiscordDmPolicy = createScopedDmSecurityResolver<ResolvedDiscordAccount>({
|
|
11
|
+
channelKey: "discord",
|
|
12
|
+
resolvePolicy: (account) => account.config.dmPolicy,
|
|
13
|
+
resolveAllowFrom: (account) => account.config.allowFrom,
|
|
14
|
+
resolveAccess: ({ cfg, account }) => ({
|
|
15
|
+
dmPolicy: resolveDiscordAccountDmPolicy({ cfg, accountId: account.accountId }),
|
|
16
|
+
allowFrom: resolveDiscordAccountAllowFrom({ cfg, accountId: account.accountId }),
|
|
17
|
+
}),
|
|
18
|
+
policyPathSuffix: "dmPolicy",
|
|
19
|
+
normalizeEntry: (raw) =>
|
|
20
|
+
raw
|
|
21
|
+
.trim()
|
|
22
|
+
.replace(/^(discord|user):/i, "")
|
|
23
|
+
.replace(/^<@!?(\d+)>$/, "$1"),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const collectDiscordSecurityWarnings =
|
|
27
|
+
createOpenProviderConfiguredRouteWarningCollector<ResolvedDiscordAccount>({
|
|
28
|
+
providerConfigPresent: (cfg) => cfg.channels?.discord !== undefined,
|
|
29
|
+
resolveGroupPolicy: (account) => account.config.groupPolicy,
|
|
30
|
+
resolveRouteAllowlistConfigured: (account) =>
|
|
31
|
+
Object.keys(account.config.guilds ?? {}).length > 0,
|
|
32
|
+
configureRouteAllowlist: {
|
|
33
|
+
surface: "Discord guilds",
|
|
34
|
+
openScope: "any channel not explicitly denied",
|
|
35
|
+
groupPolicyPath: "channels.discord.groupPolicy",
|
|
36
|
+
routeAllowlistPath: "channels.discord.guilds.<id>.channels",
|
|
37
|
+
},
|
|
38
|
+
missingRouteAllowlist: {
|
|
39
|
+
surface: "Discord guilds",
|
|
40
|
+
openBehavior: "with no guild/channel allowlist; any channel can trigger (mention-gated)",
|
|
41
|
+
remediation:
|
|
42
|
+
'Set channels.discord.groupPolicy="allowlist" and configure channels.discord.guilds.<id>.channels',
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
let discordSecurityAuditModulePromise:
|
|
47
|
+
| Promise<typeof import("./security-audit.runtime.js")>
|
|
48
|
+
| undefined;
|
|
49
|
+
|
|
50
|
+
async function loadDiscordSecurityAuditModule() {
|
|
51
|
+
discordSecurityAuditModulePromise ??= import("./security-audit.runtime.js");
|
|
52
|
+
return await discordSecurityAuditModulePromise;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const discordSecurityAdapter = {
|
|
56
|
+
resolveDmPolicy: resolveDiscordDmPolicy,
|
|
57
|
+
collectWarnings: collectDiscordSecurityWarnings,
|
|
58
|
+
collectAuditFindings: async (params) =>
|
|
59
|
+
(await loadDiscordSecurityAuditModule()).collectDiscordSecurityAuditFindings(params),
|
|
60
|
+
} satisfies NonNullable<ChannelPlugin<ResolvedDiscordAccount>["security"]>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
parseDiscordTarget,
|
|
3
|
+
type DiscordTarget,
|
|
4
|
+
type DiscordTargetParseOptions,
|
|
5
|
+
} from "./target-parsing.js";
|
|
6
|
+
|
|
7
|
+
export type SendDiscordTarget = DiscordTarget;
|
|
8
|
+
|
|
9
|
+
type SendDiscordTargetParseOptions = DiscordTargetParseOptions;
|
|
10
|
+
|
|
11
|
+
export const parseDiscordSendTarget = (
|
|
12
|
+
raw: string,
|
|
13
|
+
options: SendDiscordTargetParseOptions = {},
|
|
14
|
+
): SendDiscordTarget | undefined => parseDiscordTarget(raw, options);
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { APIChannel } from "discord-api-types/v10";
|
|
2
|
+
import {
|
|
3
|
+
createGuildChannel,
|
|
4
|
+
deleteChannel,
|
|
5
|
+
deleteChannelPermission,
|
|
6
|
+
editChannel,
|
|
7
|
+
moveGuildChannels,
|
|
8
|
+
putChannelPermission,
|
|
9
|
+
} from "./internal/discord.js";
|
|
10
|
+
import { resolveDiscordRest } from "./send.shared.js";
|
|
11
|
+
import type {
|
|
12
|
+
DiscordChannelCreate,
|
|
13
|
+
DiscordChannelEdit,
|
|
14
|
+
DiscordChannelMove,
|
|
15
|
+
DiscordChannelPermissionSet,
|
|
16
|
+
DiscordReactOpts,
|
|
17
|
+
} from "./send.types.js";
|
|
18
|
+
|
|
19
|
+
export async function createChannelDiscord(
|
|
20
|
+
payload: DiscordChannelCreate,
|
|
21
|
+
opts: DiscordReactOpts,
|
|
22
|
+
): Promise<APIChannel> {
|
|
23
|
+
const rest = resolveDiscordRest(opts);
|
|
24
|
+
const body: Record<string, unknown> = {
|
|
25
|
+
name: payload.name,
|
|
26
|
+
};
|
|
27
|
+
if (payload.type !== undefined) {
|
|
28
|
+
body.type = payload.type;
|
|
29
|
+
}
|
|
30
|
+
if (payload.parentId) {
|
|
31
|
+
body.parent_id = payload.parentId;
|
|
32
|
+
}
|
|
33
|
+
if (payload.topic) {
|
|
34
|
+
body.topic = payload.topic;
|
|
35
|
+
}
|
|
36
|
+
if (payload.position !== undefined) {
|
|
37
|
+
body.position = payload.position;
|
|
38
|
+
}
|
|
39
|
+
if (payload.nsfw !== undefined) {
|
|
40
|
+
body.nsfw = payload.nsfw;
|
|
41
|
+
}
|
|
42
|
+
return await createGuildChannel(rest, payload.guildId, {
|
|
43
|
+
body,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function editChannelDiscord(
|
|
48
|
+
payload: DiscordChannelEdit,
|
|
49
|
+
opts: DiscordReactOpts,
|
|
50
|
+
): Promise<APIChannel> {
|
|
51
|
+
const rest = resolveDiscordRest(opts);
|
|
52
|
+
const body: Record<string, unknown> = {};
|
|
53
|
+
if (payload.name !== undefined) {
|
|
54
|
+
body.name = payload.name;
|
|
55
|
+
}
|
|
56
|
+
if (payload.topic !== undefined) {
|
|
57
|
+
body.topic = payload.topic;
|
|
58
|
+
}
|
|
59
|
+
if (payload.position !== undefined) {
|
|
60
|
+
body.position = payload.position;
|
|
61
|
+
}
|
|
62
|
+
if (payload.parentId !== undefined) {
|
|
63
|
+
body.parent_id = payload.parentId;
|
|
64
|
+
}
|
|
65
|
+
if (payload.nsfw !== undefined) {
|
|
66
|
+
body.nsfw = payload.nsfw;
|
|
67
|
+
}
|
|
68
|
+
if (payload.rateLimitPerUser !== undefined) {
|
|
69
|
+
body.rate_limit_per_user = payload.rateLimitPerUser;
|
|
70
|
+
}
|
|
71
|
+
if (payload.archived !== undefined) {
|
|
72
|
+
body.archived = payload.archived;
|
|
73
|
+
}
|
|
74
|
+
if (payload.locked !== undefined) {
|
|
75
|
+
body.locked = payload.locked;
|
|
76
|
+
}
|
|
77
|
+
if (payload.autoArchiveDuration !== undefined) {
|
|
78
|
+
body.auto_archive_duration = payload.autoArchiveDuration;
|
|
79
|
+
}
|
|
80
|
+
if (payload.availableTags !== undefined) {
|
|
81
|
+
body.available_tags = payload.availableTags.map((t) => ({
|
|
82
|
+
...(t.id !== undefined && { id: t.id }),
|
|
83
|
+
name: t.name,
|
|
84
|
+
...(t.moderated !== undefined && { moderated: t.moderated }),
|
|
85
|
+
...(t.emoji_id !== undefined && { emoji_id: t.emoji_id }),
|
|
86
|
+
...(t.emoji_name !== undefined && { emoji_name: t.emoji_name }),
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
return await editChannel(rest, payload.channelId, {
|
|
90
|
+
body,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function deleteChannelDiscord(channelId: string, opts: DiscordReactOpts) {
|
|
95
|
+
const rest = resolveDiscordRest(opts);
|
|
96
|
+
await deleteChannel(rest, channelId);
|
|
97
|
+
return { ok: true, channelId };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function moveChannelDiscord(payload: DiscordChannelMove, opts: DiscordReactOpts) {
|
|
101
|
+
const rest = resolveDiscordRest(opts);
|
|
102
|
+
const body: Array<Record<string, unknown>> = [
|
|
103
|
+
{
|
|
104
|
+
id: payload.channelId,
|
|
105
|
+
...(payload.parentId !== undefined && { parent_id: payload.parentId }),
|
|
106
|
+
...(payload.position !== undefined && { position: payload.position }),
|
|
107
|
+
},
|
|
108
|
+
];
|
|
109
|
+
await moveGuildChannels(rest, payload.guildId, { body });
|
|
110
|
+
return { ok: true };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function setChannelPermissionDiscord(
|
|
114
|
+
payload: DiscordChannelPermissionSet,
|
|
115
|
+
opts: DiscordReactOpts,
|
|
116
|
+
) {
|
|
117
|
+
const rest = resolveDiscordRest(opts);
|
|
118
|
+
const body: Record<string, unknown> = {
|
|
119
|
+
type: payload.targetType,
|
|
120
|
+
};
|
|
121
|
+
if (payload.allow !== undefined) {
|
|
122
|
+
body.allow = payload.allow;
|
|
123
|
+
}
|
|
124
|
+
if (payload.deny !== undefined) {
|
|
125
|
+
body.deny = payload.deny;
|
|
126
|
+
}
|
|
127
|
+
await putChannelPermission(rest, payload.channelId, payload.targetId, { body });
|
|
128
|
+
return { ok: true };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function removeChannelPermissionDiscord(
|
|
132
|
+
channelId: string,
|
|
133
|
+
targetId: string,
|
|
134
|
+
opts: DiscordReactOpts,
|
|
135
|
+
) {
|
|
136
|
+
const rest = resolveDiscordRest(opts);
|
|
137
|
+
await deleteChannelPermission(rest, channelId, targetId);
|
|
138
|
+
return { ok: true };
|
|
139
|
+
}
|