Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
Tools/GitLabGitLab/eveo7/aa-discord-audit
Defensive ToolsConfiguration AuditingIdentity & Access Management (IAM)Incident Response
GitLabeveo7/aa-discord-audit

AA Discord Audit

Reconciliation audit for Alliance Auth's Discord integration: finds guild members holding AA-managed roles that Auth never granted and strips or kicks them under an operator-controlled policy. Standalone community Django app.

View Repository
1 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

aa-discord-audit

PyPI version Supported Python versions License: MIT

Reconciliation audit for Alliance Auth's Discord integration. Compares the actual role assignments in the configured Discord guild against the state expressed in Alliance Auth (Groups + State per user) and closes the gap through which moderators can hand-assign AA-named roles to users AA does not know about.

Status: alpha (0.1.x). The public API and settings may still change before 1.0.

Contents

  • Safety posture
  • How it works
  • Requirements
  • Installation
  • Quick start
  • Permissions
  • Settings
  • Management commands
  • Periodic audit (Celery beat)
  • Celery tasks
  • Operator dashboard
  • Observability
  • Limitations
  • Documentation
  • Development

Safety posture

The audit is safe by default:

  • The first run after install is locked to dry-run regardless of operator settings. Releasing the lock requires an explicit InitialAuditAcknowledgement (admin or shell only).
  • The default policy is report for every category — destructive actions are opt-in.
  • Audit-trail rows (AuditRun, AuditFinding, AuditInvocation, ConfigChangeLog) are append-only at the manager and instance layers; bulk update() / bulk_update() are blocked.
  • Webhook URLs are treated as credentials and redacted from logs, exception messages, and persisted argv.

How it works

Each guild member is classified into one category:

CategoryMeaning
unknown_guestDiscord member AA knows nothing about
linked_no_permIdentity known to AA but lacks discord.access_discord
bot_filteredConfigured bot account — never acted upon

The operator maps each category to one action:

ActionBehaviour
reportRecord the finding; no Discord-side change
stripRemove AA-managed roles
strip_kickRemove AA-managed roles, then kick from guild

The mapping is the AA_DISCORD_AUDIT_POLICY setting; per-group and per-state overrides nest inside each category.

Requirements

  • Python 3.10–3.13
  • Django 4.2 or 5.2
  • Alliance Auth 4.x or 5.x
  • Alliance Auth's Discord service module (allianceauth.services.modules.discord) installed and configured (bot token + guild)

Installation

root@kitploit:~
pip install aa-discord-audit

In your Auth local.py:

root@kitploit:~
# `aa_discord_audit` must appear AFTER
# `allianceauth.services.modules.discord` so the discord module's
# models load first; `apps.ready()` raises `ImproperlyConfigured`
# otherwise.
INSTALLED_APPS += ["aa_discord_audit"]

MIDDLEWARE += [
    "aa_discord_audit.current_user.CurrentUserMiddleware",
]

Then run migrations:

root@kitploit:~
python manage.py migrate aa_discord_audit

The CurrentUserMiddleware is mandatory — apps.ready() raises ImproperlyConfigured if it is missing. It is what lets the ConfigChangeLog signal handler attribute admin edits to a real user instead of <system>.

Quick start

  1. Grant aa_discord_audit.run_audit to the operator role that runs audits.

  2. Run a dry-run audit:

    root@kitploit:~
    python manage.py audit_discord_roles --action report
    
  3. Review findings under Discord Audit → Audit runs in the Auth dashboard.

  4. Release the first-run lock — either create an InitialAuditAcknowledgement row through the admin, or run python manage.py audit_acknowledge_initial. Both require aa_discord_audit.run_audit and aa_discord_audit.acknowledge_initial_audit.

  5. Re-run with the destructive action of your choice when ready.

Permissions

The manage_* codenames are split per blast radius so a junior with manage_bot_account_uid cannot also defang the audit by editing ManagedRoleException.

Settings

All settings are optional. Defaults are safe.

root@kitploit:~
# Action policy. Bare-string form below is shorthand for
# {"default": "<action>"}; use the nested form for per-group / per-state
# overrides keyed by AA group name and state name.
AA_DISCORD_AUDIT_POLICY = {
    "unknown_guest":  "report",
    "linked_no_perm": "report",
    # "linked_no_perm": {
    #     "default":  "strip",
    #     "by_state": {"Guest": "report"},
    #     "by_group": {"Directors": "report"},
    # },
}

# AA-notify fan-out to permission holders.
AA_DISCORD_AUDIT_NOTIFY_ADMINS = True

# Discord webhook for run summaries. Treat as a credential.
AA_DISCORD_AUDIT_WEBHOOK_URL = None

# uids skipped as bot accounts (in addition to the BotAccountUid admin
# table).
AA_DISCORD_AUDIT_BOT_UIDS = []

# Auto-discover bot accounts by Discord nickname heuristics. Reserved
# for v2; not implemented in the MVP. The startup validator raises
# ImproperlyConfigured if set to True — keep this False and use the
# explicit BotAccountUid admin table instead.
AA_DISCORD_AUDIT_AUTO_DISCOVER_BY_NICKNAME = False

# Retention. 0 disables pruning; the validator refuses 0 unless the
# acknowledged flag below is also set.
AA_DISCORD_AUDIT_RUN_RETENTION_DAYS = 180
AA_DISCORD_AUDIT_RETENTION_OPT_OUT_ACKNOWLEDGED = False

# Idempotency-key TTL. When positive, prune_audit_runs releases
# AuditRun.idempotency_key on rows older than the cutoff while the
# row itself stays for RUN_RETENTION_DAYS. 0 (default) disables the
# expiry; the key dies with the row.
AA_DISCORD_AUDIT_IDEMPOTENCY_KEY_TTL_DAYS = 0

# Per-run deadline. The Celery task's soft_time_limit follows.
AA_DISCORD_AUDIT_RUN_DEADLINE_MINUTES = 60

# Rolling 24h rate limit on accepted audit triggers per user.
# DISABLED is an opt-out gate; refuses to take effect unless
# explicitly toggled.
AA_DISCORD_AUDIT_RUN_RATE_LIMIT_PER_DAY = 5
AA_DISCORD_AUDIT_RUN_RATE_LIMIT_DISABLED = False

# Discord webhook delivery tuning.
AA_DISCORD_AUDIT_WEBHOOK_TIMEOUT = 10
AA_DISCORD_AUDIT_WEBHOOK_MAX_RETRIES = 3

# Opt-in: bulk PATCH role strip. Faster on large guilds; off by default
# while we collect operator feedback on Discord-side rate-limit shape.
AA_DISCORD_AUDIT_USE_BULK_ROLE_STRIP = False

# Periodic (Celery beat) audit. Destructive actions on the unattended
# beat require this explicit opt-in, independent of the one-shot
# first-run lock: a beat run carrying strip/kick is coerced to report
# otherwise, with a startup warning on mismatch.
AA_DISCORD_AUDIT_BEAT_ALLOW_DESTRUCTIVE = False

# Coarse global floor between beat runs, in minutes. 0 (default)
# disables it — the Celery beat schedule is the primary cadence
# control. A backstop against a misconfigured fast schedule
# (QueueOnce only dedupes concurrent ticks, not back-to-back ones).
AA_DISCORD_AUDIT_BEAT_MIN_INTERVAL_MINUTES = 0

# Pin the CLI acting user to a Django username (the "service principal"
# pattern). The management command has no HTTP request, so the actor is
# normally derived from the OS login; set this for clean, stable
# ConfigChangeLog attribution from cron and manual CLI alike. Resolved
# and permission-checked per invocation.
AA_DISCORD_AUDIT_CLI_ACTOR = None

# Guild presence sampling. Requires the [metrics] extra and
# aa-discord-audit's own /audit/discord/metrics scrape target.
# PRESENCE_ENABLED is independent of [metrics]: even without
# the extra, disabling it suppresses the background sampler task.
AA_DISCORD_AUDIT_PRESENCE_ENABLED = True

# Desired presence sample cadence in minutes. Values below 5 fall
# back to the default of 10.
AA_DISCORD_AUDIT_PRESENCE_SAMPLE_INTERVAL_MINUTES = 10

# Gate for the members_by_group aggregate gauge. Disable if the
# number of AA groups causes unacceptable cardinality.
AA_DISCORD_AUDIT_METRICS_MEMBERS_BY_GROUP = True

# Gate for the members_by_role aggregate gauge. Off by default —
# Discord guilds can have hundreds of roles; enable only after
# reviewing role count to avoid a cardinality explosion.
AA_DISCORD_AUDIT_METRICS_MEMBERS_BY_ROLE = False

Management commands

A long-running audit_discord_roles reacts cleanly to SIGTERM and SIGINT: the run is flipped to INTERRUPTED and the apply loop drops out at the next finding boundary so partial work is durable in the audit trail. audit_discord_roles --resume <run_id> picks the run up from the remaining PENDING findings.

Periodic audit (Celery beat)

audit_orphan_members runs the same build + apply pipeline on a schedule you wire into Celery beat (it is not scheduled by default). The unattended path is gated for safety:

  • Report-only unless armed. A beat run is coerced to report regardless of policy unless AA_DISCORD_AUDIT_BEAT_ALLOW_DESTRUCTIVE is set — releasing the one-shot first-run lock for a manual CLI run does not arm the beat. A boot-time warning fires when the policy is destructive but the beat is not opted in.
  • Attributed. Every beat run writes an accepted system-actor AuditInvocation (triggered_by=BEAT), so the audit-the-auditor trail covers unattended runs too.
  • Self-healing. A run stranded by a soft-time-limit hit or a worker crash is reconciled to the resumable INTERRUPTED state on the next tick. AA_DISCORD_AUDIT_BEAT_MIN_INTERVAL_MINUTES is a coarse floor against a misconfigured fast schedule.

Celery tasks

The package registers five tasks under the aa_discord_audit.* name prefix. Only the beat tasks need a schedule; the rest are event-driven or run on demand. None are scheduled for you.

To schedule the beat tasks, add them to CELERYBEAT_SCHEDULE in your local.py, for example:

root@kitploit:~
CELERYBEAT_SCHEDULE["aa_discord_audit_sample_guild_presence"] = {
    "task": "aa_discord_audit.sample_guild_presence",
    "schedule": 600,  # seconds; honoured no finer than the sampler floor
}

Operator dashboard

Mounted under the Auth main nav as Discord Audit. Read-only views; each list carries a server-side search box that matches across every row — not just the page in view — alongside its dropdown filters:

  • Audit runs — a summary card with fleet-wide audit posture above a badged, sortable list; per-run detail splits into cards around the per-finding table.
  • Per-finding explain — classification, resolved action (with the resolved policy as JSON and an as-of marker), and which override layer (per-uid override, per-group / per-state policy, ProtectedDiscordMember, first-run lock) decided the outcome.
  • Invocations — every CLI / beat trigger including refused attempts, with the run state surfaced and the full argv expandable. Surfaces rate-limit usage.
  • Config change log — every operator edit to the operator-managed config tables, rendered as a field-level before/after diff for updates (pretty-printed JSON for create / delete) with a case-insensitive actor filter. The audit-the-auditor trail.

Launching an audit from the web UI

Operators with the aa_discord_audit.run_audit permission see a Launch Audit button on the Audit runs page. Clicking it opens a Bootstrap modal that surfaces the configured policy mode (report-only or destructive), the initial-acknowledgement state, and a Confirm Launch button. Submitting POSTs to /run-launch/, which creates a PENDING AuditRun, enqueues process_pending_run via Celery, and redirects to the run-detail page.

The web path mirrors the CLI's safety gates:

  • First-run lock. If audit_acknowledge_initial has not been run, the modal shows a refusal block (no submit button) instead of the Confirm Launch action. A POST that bypasses the modal (e.g. curl) is refused server-side, the refusal is recorded in AuditInvocation, and the operator is redirected with a flash message.
  • Destructive intent. Whether a launch may strip roles or kick members is decided server-side from the operator's permissions and the policy: was_confirmation_bypassed = user.has_perm(run_audit_destructive) and policy_has_destructive(policy). The UI has no toggle — the destructive permission alone determines intent. A run_audit-only operator who triggers a launch against a destructive policy gets a silent REPORT-only run (the same coercion the CLI applies without --yes).
  • Per-user rate limit. The same daily quota as the CLI; the modal still opens but the POST returns a flash + redirect once the quota is spent.

While a run is in a non-terminal state (PENDING, RUNNING, or INTERRUPTED), the run-detail page polls /runs/<pk>/state.json every five seconds and updates the State card in place. Polling pauses on hidden tabs and stops as soon as the run reaches a terminal state.

Observability

Optional Prometheus instrumentation behind the [metrics] extra — absent, every metric call resolves to a no-op stub at near-zero cost. The module ships a cooperate-don't-depend layer: when django-prometheus is installed the audit's counters and histograms register into the default prometheus_client.REGISTRY and django-prometheus' /metrics view exports them alongside its own series.

root@kitploit:~
pip install aa-discord-audit[metrics]

The snapshot gauges — guild presence and the per-group / per-role membership aggregates — take a second path: they live in a dedicated registry exported by the module's own /audit/discord/metrics endpoint. django-prometheus' multiprocess collector reads only mmap files and skips custom collectors, so these gauges need their own scrape target. Like any /metrics target it is unauthenticated — restrict it at the reverse-proxy or network layer. Both surfaces stay inert without the [metrics] extra.

Metric catalog, label vocabulary, and Grafana recipes live in docs/METRICS.md (Russian translation: docs/METRICS.ru.md).

Limitations

  • Single guild. The audit reconciles the one Discord guild AA is configured against; it does not span multiple guilds.
  • AA-managed roles only. Strip / kick act on AA-named roles and guild membership; roles AA does not manage are never touched.
  • No nickname auto-discovery. Bot accounts are recognised only via the explicit BotAccountUid table / AA_DISCORD_AUDIT_BOT_UIDS — AA_DISCORD_AUDIT_AUTO_DISCOVER_BY_NICKNAME is reserved for v2 and not implemented.
  • Alpha surface. Settings names and the public API may change before 1.0.

Documentation

  • docs/runbook.md — operator runbook: bot Discord permissions, pre-flight checklist, releasing the first-run lock, incident playbooks, diagnostic toggles.
  • docs/performance.md — audit_benchmark reference numbers and sizing implications.
  • docs/METRICS.md / docs/METRICS.ru.md — Prometheus metric catalog, label vocabulary, Grafana recipes.
  • Source code: https://gitlab.com/eveo7/aa-discord-audit
  • Issue tracker: https://gitlab.com/eveo7/aa-discord-audit/-/issues
  • Changelog: CHANGELOG.md

Development

root@kitploit:~
make dev         # uv sync --all-groups + pre-commit install
make tests       # uv run nox -s tests
make lint        # uv run nox -s lint
make typecheck   # mypy + basedpyright
make coverage    # term + html + xml report
make package     # uv build

Toolchain is uv-only. Line length is 79 (Python) / 120 (Markdown).

Translations

  • Русский

License

MIT — see LICENSE.

Download Tool
CodenameGates
aa_discord_audit.run_auditmanagement command, beat task, run delete
aa_discord_audit.run_audit_destructiveweb-launch gate for strip / strip_kick (separate from run_audit)
aa_discord_audit.acknowledge_initial_auditrelease the first-run dry-run lock
aa_discord_audit.manage_discord_identityDiscordIdentity admin
aa_discord_audit.manage_role_exceptionManagedRoleException admin
aa_discord_audit.manage_protected_memberProtectedDiscordMember admin
aa_discord_audit.manage_bot_account_uidBotAccountUid admin
aa_discord_audit.manage_finding_overrideFindingActionOverride admin
aa_discord_audit.view_auditrun (and friends)read-only audit-trail in the Auth dashboard
CommandPurpose
audit_discord_rolesPrimary entry point. --action {report,strip,strip_kick}.
audit_discord_roles --resume <run_id>Re-walk PENDING findings of an existing run.
audit_discord_roles --abandon <run_id>Mark a stuck run as ABANDONED.
audit_discord_roles --diff <run_id>Compare current state to a historical run.
audit_discord_roles --explain <member_id>Per-member classification (read-only).
audit_discord_roles --policy-preview <json>Project a hypothetical policy.
audit_discord_roles --from-fixture <path>Replay against a JSON snapshot.
audit_acknowledge_initialRelease the first-run dry-run lock from the console.
audit_benchmarkSynthetic-load sizing benchmark (see docs/performance.md).
prune_audit_runsRetention pruning.
audit_abandon_stuck_runsReconcile stuck PENDING runs leaked by a crashed worker — flips them to ABANDONED (see the runbook; a stuck RUNNING run uses audit_discord_roles --abandon).
TaskHow it runsWhat it does
aa_discord_audit.audit_orphan_membersCelery beat — you schedule itThe unattended build + apply audit described above. Report-only unless armed; shares the discord.user_actions.<uid> lock with AA's update_groups.
aa_discord_audit.retry_pending_kicksCelery beat — you schedule itBounded sweep that re-dispatches kicks deferred by a transient Discord failure, honouring a per-row cooldown so a hard-failing member is not hammered.
aa_discord_audit.process_pending_runEvent-driven — enqueued by a web launchPicks up the PENDING run a web launch created, flips it to RUNNING, and drives the pipeline against the run's frozen confirmation flag.
aa_discord_audit.prune_audit_runsCelery beat / cron — you schedule it (also a management command)Retention: idempotency-key expiry then row deletion (see Management commands and the runbook).
aa_discord_audit.sample_guild_presenceCelery beat — you schedule itLight, REST-only sample of guild member / online / boost counts into the latest-only presence snapshot for the Prometheus presence gauges. Gated on AA_DISCORD_AUDIT_PRESENCE_ENABLED; a skip-guard bounds a misconfigured fast schedule (floor AA_DISCORD_AUDIT_PRESENCE_SAMPLE_INTERVAL_MINUTES, default 10). Inert until both the [metrics] extra and a beat entry are present (see docs/METRICS.md).