426e9eeabd
Voice Workbench / headless workbench (mocked backends) (push) Has been cancelled
Voice Workbench / real acoustic lane (nightly, provisioned only) (push) Has been cancelled
ci / test (push) Has been cancelled
ci / lint-and-format (push) Has been cancelled
ci / build (push) Has been cancelled
ci / dev-startup (push) Has been cancelled
gitleaks / gitleaks (push) Has been cancelled
Markdown Links / Relative Markdown Links (push) Has been cancelled
Quality (Extended) / Homepage Build (PR smoke) (push) Has been cancelled
Quality (Extended) / Comment-only diff guard (push) Has been cancelled
Quality (Extended) / Format + Type Safety Ratchet (push) Has been cancelled
Quality (Extended) / Develop Gate (secret scan + UI determinism) (push) Has been cancelled
Quality (Extended) / Develop Gate (lint) (push) Has been cancelled
Chat shell gestures / Chat shell gesture + parity e2e (push) Has been cancelled
Cloud Gateway Discord / Test (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx @biomejs/biome check packages/lifeops-bench/src, benchmark-lint) (push) Has been cancelled
Benchmark Bridge Tests / benchmark (bunx vitest run --config packages/lifeops-bench/vitest.config.ts --root packages/lifeops-bench --passWithNoTests, benchmark-tests) (push) Has been cancelled
Build Agent Image / build-and-push (push) Has been cancelled
Dev Smoke / bun run dev onboarding chat (push) Has been cancelled
Dev Smoke / Vite HMR dependency-level smoke (push) Has been cancelled
Electrobun Submodule Guard / electrobun gitlink is fetchable (push) Has been cancelled
Publish @elizaos/example-code / check_npm (push) Has been cancelled
Publish @elizaos/example-code / publish_npm (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / verify_version (push) Has been cancelled
Publish @elizaos/plugin-elizacloud / publish_npm (push) Has been cancelled
Sandbox Live Smoke / Sandbox live smoke (push) Has been cancelled
Snap Build & Test / Build Snap (amd64) (push) Has been cancelled
Snap Build & Test / Build Snap (arm64) (push) Has been cancelled
Test Packaging / elizaos CLI global-install smoke (node + bun) (push) Has been cancelled
Cloud Gateway Webhook / Test (push) Has been cancelled
Cloud Tests / lint-and-types (push) Has been cancelled
Cloud Tests / unit-tests (push) Has been cancelled
Cloud Tests / integration-tests (push) Has been cancelled
Cloud Tests / e2e-tests (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Deploy Apps Worker (Product 2) / Determine environment (push) Has been cancelled
Deploy Apps Worker (Product 2) / Deploy apps worker to apps-control host (${{ needs.determine-env.outputs.environment }}) (push) Has been cancelled
Deploy Eliza Provisioning Worker / Determine environment (push) Has been cancelled
Deploy Eliza Provisioning Worker / Deploy worker to Hetzner host (${{ needs.determine-env.outputs.environment }} @ ${{ needs.determine-env.outputs.deployment_sha }}) (push) Has been cancelled
Dev Smoke / Classify changed paths (push) Has been cancelled
supply-chain / sbom (push) Has been cancelled
supply-chain / vulnerability-scan (push) Has been cancelled
Build, Push & Deploy to Phala Cloud / build-and-push (push) Has been cancelled
Test Packaging / Validate Packaging Configs (push) Has been cancelled
Test Packaging / Build & Test PyPI Package (push) Has been cancelled
Test Packaging / PyPI on Python ${{ matrix.python }} (push) Has been cancelled
Test Packaging / Pack & Test JS Tarballs (push) Has been cancelled
UI Fixture E2E / ui-fixture-e2e (push) Has been cancelled
UI Fixture E2E / fixture-e2e (push) Has been cancelled
UI Story Gate / story-gate (push) Has been cancelled
vault-ci / test (macos-latest) (push) Has been cancelled
vault-ci / test (ubuntu-latest) (push) Has been cancelled
vault-ci / test (windows-latest) (push) Has been cancelled
vault-ci / app-core wiring tests (push) Has been cancelled
verify-patches / verify patches/CHECKSUMS.sha256 (push) Has been cancelled
Voice Benchmark Smoke / voice-emotion fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voiceagentbench fixture smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench-quality unit smoke (push) Has been cancelled
Voice Benchmark Smoke / voicebench TypeScript unit (no audio) (push) Has been cancelled
Voice Benchmark Smoke / voice bench smoke summary (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/app-core test bun run --cwd packages/elizaos test bun run --cwd packages/cloud/shared test], app-and-cli) (push) Has been cancelled
Windows CI / windows ([bun run --cwd packages/scenario-runner test bun run --cwd packages/vault test bun run --cwd packages/security test bun run --cwd plugins/plugin-coding-tools test], framework-packages) (push) Has been cancelled
Windows CI / windows ([bun run --cwd plugins/plugin-elizacloud test bun run --cwd plugins/plugin-discord test bun run --cwd plugins/plugin-anthropic test bun run --cwd plugins/plugin-openai test bun run --cwd plugins/plugin-app-control test bun run --cwd plugins/pl… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run build --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/agent --concurrency=4 node packages/scripts/run-bash-linux-only.mjs scripts/verify-riscv64-buildpaths.sh node packages/scripts/run… (push) Has been cancelled
Windows CI / windows ([node packages/scripts/run-turbo.mjs run typecheck --filter=@elizaos/core --filter=@elizaos/shared --filter=@elizaos/cloud-shared --concurrency=4 bun run --cwd packages/core test bun run --cwd packages/shared test], core-runtime, 75) (push) Has been cancelled
220 lines
5.9 KiB
TypeScript
220 lines
5.9 KiB
TypeScript
/**
|
|
* Elevated-permission helpers — the set of moderation/admin permissions worth
|
|
* auditing, plus predicates (`hasElevatedPermissions`, `isElevatedRole`) used
|
|
* when diffing Discord permission changes into the connector's audit events.
|
|
*/
|
|
import type { IAgentRuntime } from "@elizaos/core";
|
|
import type {
|
|
AuditLogEvent,
|
|
Guild,
|
|
GuildMember,
|
|
PermissionOverwrites,
|
|
Role,
|
|
} from "discord.js";
|
|
import type { AuditInfo, PermissionDiff, PermissionState } from "./types";
|
|
|
|
/**
|
|
* Permissions that indicate moderation/admin capabilities.
|
|
* Changes to these permissions are considered elevated and worth tracking.
|
|
*/
|
|
export const ELEVATED_PERMISSIONS = [
|
|
"Administrator",
|
|
"ManageGuild",
|
|
"ManageChannels",
|
|
"ManageRoles",
|
|
"KickMembers",
|
|
"BanMembers",
|
|
"ModerateMembers",
|
|
"ManageMessages",
|
|
"ManageWebhooks",
|
|
"ManageNicknames",
|
|
"MuteMembers",
|
|
"DeafenMembers",
|
|
"MoveMembers",
|
|
"ManageEvents",
|
|
"ManageThreads",
|
|
] as const;
|
|
|
|
/**
|
|
* Check if a role has any elevated (moderation/admin) permissions
|
|
*/
|
|
export function isElevatedRole(role: Role): boolean {
|
|
return ELEVATED_PERMISSIONS.some((p) => role.permissions.has(p));
|
|
}
|
|
|
|
/**
|
|
* Check if an array of permission names contains any elevated permissions
|
|
*/
|
|
export function hasElevatedPermissions(permissions: string[]): boolean {
|
|
return permissions.some((p) =>
|
|
ELEVATED_PERMISSIONS.includes(p as (typeof ELEVATED_PERMISSIONS)[number]),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Fetch the most recent matching audit log entry for an action.
|
|
* Matches by target ID and filters to entries within 10 seconds.
|
|
* Returns null gracefully on errors (rate limits, missing permissions, etc.)
|
|
*/
|
|
export async function fetchAuditEntry(
|
|
guild: Guild,
|
|
actionType: AuditLogEvent,
|
|
target: string,
|
|
runtime: IAgentRuntime,
|
|
): Promise<AuditInfo | null> {
|
|
try {
|
|
const logs = await guild.fetchAuditLogs({ type: actionType, limit: 5 });
|
|
const now = Date.now();
|
|
|
|
for (const entry of logs.entries.values()) {
|
|
// Match by target and ensure entry is recent (within 10 seconds)
|
|
// Type guard: entry.target can be various types, not all have 'id'
|
|
const targetId =
|
|
entry.target && "id" in entry.target ? entry.target.id : undefined;
|
|
if (targetId === target && now - entry.createdTimestamp < 10000) {
|
|
return {
|
|
executorId: entry.executor?.id ?? "unknown",
|
|
executorTag: entry.executor?.tag ?? "Unknown",
|
|
reason: entry.reason,
|
|
};
|
|
}
|
|
}
|
|
} catch (err) {
|
|
// Graceful degradation - rate limits, missing permissions, etc.
|
|
runtime.logger.debug(`Audit log fetch failed (non-critical): ${err}`);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Get the permission state from allow/deny arrays
|
|
*/
|
|
function getState(
|
|
perm: string,
|
|
allow: string[],
|
|
deny: string[],
|
|
): PermissionState {
|
|
if (allow.includes(perm)) {
|
|
return "ALLOW";
|
|
}
|
|
if (deny.includes(perm)) {
|
|
return "DENY";
|
|
}
|
|
return "NEUTRAL";
|
|
}
|
|
|
|
/**
|
|
* Convert an overwrite to a list of changes (used for CREATE/DELETE)
|
|
* For CREATE: from NEUTRAL to the actual state (ALLOW/DENY)
|
|
* For DELETE: from the actual state (ALLOW/DENY) to NEUTRAL
|
|
*/
|
|
function overwriteToChanges(
|
|
ow: PermissionOverwrites,
|
|
isDelete: boolean = false,
|
|
): PermissionDiff[] {
|
|
const changes: PermissionDiff[] = [];
|
|
for (const p of ow.allow.toArray()) {
|
|
changes.push({
|
|
permission: p,
|
|
oldState: isDelete ? "ALLOW" : "NEUTRAL",
|
|
newState: isDelete ? "NEUTRAL" : "ALLOW",
|
|
});
|
|
}
|
|
for (const p of ow.deny.toArray()) {
|
|
changes.push({
|
|
permission: p,
|
|
oldState: isDelete ? "DENY" : "NEUTRAL",
|
|
newState: isDelete ? "NEUTRAL" : "DENY",
|
|
});
|
|
}
|
|
return changes;
|
|
}
|
|
|
|
/**
|
|
* Diff two permission overwrites and determine what changed.
|
|
* Returns the list of changes and the action type (CREATE/UPDATE/DELETE).
|
|
*/
|
|
export function diffOverwrites(
|
|
oldOw: PermissionOverwrites | undefined | null,
|
|
newOw: PermissionOverwrites | undefined | null,
|
|
): { changes: PermissionDiff[]; action: "CREATE" | "UPDATE" | "DELETE" } {
|
|
// Both null - no change
|
|
if (!oldOw && !newOw) {
|
|
return { changes: [], action: "UPDATE" };
|
|
}
|
|
|
|
// Created new overwrite
|
|
if (!oldOw && newOw) {
|
|
return { changes: overwriteToChanges(newOw, false), action: "CREATE" };
|
|
}
|
|
|
|
// Deleted overwrite
|
|
if (oldOw && !newOw) {
|
|
return { changes: overwriteToChanges(oldOw, true), action: "DELETE" };
|
|
}
|
|
|
|
// Updated overwrite - compare old and new
|
|
const changes: PermissionDiff[] = [];
|
|
const oldAllow = oldOw?.allow.toArray() ?? [];
|
|
const oldDeny = oldOw?.deny.toArray() ?? [];
|
|
const newAllow = newOw?.allow.toArray() ?? [];
|
|
const newDeny = newOw?.deny.toArray() ?? [];
|
|
|
|
// Get all permissions that exist in either old or new
|
|
const allPerms = new Set([...oldAllow, ...oldDeny, ...newAllow, ...newDeny]);
|
|
|
|
for (const perm of allPerms) {
|
|
const oldState = getState(perm, oldAllow, oldDeny);
|
|
const newState = getState(perm, newAllow, newDeny);
|
|
if (oldState !== newState) {
|
|
changes.push({ permission: perm, oldState, newState });
|
|
}
|
|
}
|
|
|
|
return { changes, action: "UPDATE" };
|
|
}
|
|
|
|
/**
|
|
* Diff two role permission sets and return what changed.
|
|
*/
|
|
export function diffRolePermissions(
|
|
oldRole: Role,
|
|
newRole: Role,
|
|
): PermissionDiff[] {
|
|
const oldPerms = oldRole.permissions.toArray();
|
|
const newPerms = newRole.permissions.toArray();
|
|
const changes: PermissionDiff[] = [];
|
|
|
|
// Find added permissions
|
|
for (const p of newPerms) {
|
|
if (!oldPerms.includes(p)) {
|
|
changes.push({ permission: p, oldState: "NEUTRAL", newState: "ALLOW" });
|
|
}
|
|
}
|
|
|
|
// Find removed permissions
|
|
for (const p of oldPerms) {
|
|
if (!newPerms.includes(p)) {
|
|
changes.push({ permission: p, oldState: "ALLOW", newState: "NEUTRAL" });
|
|
}
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
/**
|
|
* Diff member roles to find added and removed roles.
|
|
*/
|
|
export function diffMemberRoles(
|
|
oldMember: GuildMember,
|
|
newMember: GuildMember,
|
|
): { added: Role[]; removed: Role[] } {
|
|
const oldRoles = oldMember.roles.cache;
|
|
const newRoles = newMember.roles.cache;
|
|
|
|
return {
|
|
added: [...newRoles.filter((r) => !oldRoles.has(r.id)).values()],
|
|
removed: [...oldRoles.filter((r) => !newRoles.has(r.id)).values()],
|
|
};
|
|
}
|