Rebuild as "AntiLink Guard OSS" - TypeScript monorepo (Phases 1-8 of 9)#8
Merged
Merged
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
Sourced from the official docs (docs.antil.ink). Keeps this repo's feature list limited to what the open-source bot actually does (automatic link filtering) while accurately describing the separate, hosted AntiLink 2.0 product rather than implying it ships here. - Add 'This repo vs. hosted AntiLink' comparison and an 'Official links' table - Add an 'Add AntiLink 2.0 to Discord' link (invite.antil.ink) - Replace placeholder platform names with the real hosted product set (dashboard, Member Defense, Verify, Automod, Honeypot, Emergency Lockdown, custom bot, Free/Premium/AntiLink Premium), all marked as separate + hosted - Point the Commands 'Planned' note and FAQ at the hosted slash-command suite Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
Starts the rebuild of this repo into "AntiLink Guard OSS", a self-hostable Discord anti-phishing/link-moderation framework. This is a ground-up rewrite: the old single-file index.js bot is replaced by a pnpm/TypeScript monorepo, though its core idea (detect links, exempt roles/channels, log actions) lives on in packages/core's policy engine. Foundation: - pnpm workspace (packages/*, apps/*), tsconfig.base.json (strict TS, ES2022) - ESLint flat config (typescript-eslint + prettier), Prettier, Vitest - Root package.json targets Node >=20, pnpm 10 packages/core (new): the reusable detection engine, fully unit tested (58 tests, 4 suites): - extraction/normalize.ts - zero-width char stripping, hxxp:// and example[.]com defang reversal, tracking-param stripping, hostname lowercasing - extraction/extract-links.ts - pulls URLs, markdown links, bare www. domains, and discord.gg/discord.com invite links out of message text, flagging which ones were defanged/obfuscated - classification/classify.ts + homoglyph.ts - scores a link against domain allow/blocklists, a known-phishing list, URL shorteners, punycode hostnames, mixed-script (Cyrillic/Greek vs Latin) homoglyphs, and user-supplied regex rules - policy/policy-engine.ts - ties it together: role/user bypass, per-channel exemptions or mode overrides, mass-mention escalation, and score thresholds that resolve to an ALLOW/WARN/BLOCK/QUARANTINE verdict plus a NONE/LOG/DELETE/TIMEOUT action for the configured enforcement mode No known-phishing domains are hardcoded - that list is entirely policy-supplied, so the package never claims a threat-intel database it doesn't have. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
…dapters The persistence layer for AntiLink Guard OSS. All four adapters implement the same StorageAdapter interface and are validated against a shared behavioral contract test suite (94 tests total, 12 per adapter): - MemoryStorageAdapter - in-process, no dependencies, used by default in tests and the CLI - SqliteStorageAdapter (better-sqlite3) - the default for self-hosting; zero external services required - PostgresStorageAdapter (pg) - contract-tested against a live local Postgres instance in this session, not just written and assumed correct - MysqlStorageAdapter (mysql2) - same schema/logic shape as the others; its contract tests are honestly skipped (not faked passing) unless MYSQL_TEST_URL points at a reachable server Data models: GuildConfig, AllowlistEntry, BlocklistEntry, InviteRule, AuditLogEntry, ScanResultRecord, ModerationActionRecord. AuditLogEntry intentionally has no message-content field - only the metadata needed for a moderation log (user, channel, normalized URL, verdict, reasons, score, action, timestamp). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
packages/cli - a standalone CLI usable without ever touching Discord:
- antilink scan "<message>" - run the full policy engine on message text
- antilink test-url <url> - classify a single URL
- antilink init - scaffold .env.example + antilink.config.json
- antilink export-config - dump a guild's config/allowlist/blocklist/
invite rules (from SQLite) to JSON
- antilink import-config - load that JSON back into a (possibly fresh)
database
- antilink doctor - offline health checks: Node version, .env
presence, DISCORD_TOKEN sanity, config file
validity, the better-sqlite3 native binding,
and DB directory write access
Local policy is a zod-validated antilink.config.json mapping directly to
@antilink-guard/core's PolicyConfig. Exit codes are meaningful (scan/test-url
exit 1 on BLOCK/QUARANTINE, doctor exits 1 on any failed check) so the CLI
is scriptable in CI.
packages/storage gains a shared config-bundle module (export/import/parse,
zod-validated) so the CLI's export-config/import-config and the upcoming
discord-bot's /config export//import commands operate on the exact same
bundle format instead of duplicating it.
35 new tests, run against the actual compiled CLI (spawned via commander's
parseAsync, not just the underlying functions) plus a real export -> JSON ->
import round trip through a live SQLite file. One real bug was caught and
fixed during this work: a test helper's mock-teardown order was clearing
`.mock.calls` before the assertions read them.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
The Discord runtime for AntiLink Guard OSS: all 7 required slash commands plus the messageCreate moderation pipeline. Commands (all admin ones gated to Manage Server via setDefaultMemberPermissions; /testlink is open to everyone): - /antilink status|enable|disable|mode <block|warn|log> - /allowlist add|remove domain:<domain> - /blocklist add|remove domain:<domain> - /invites allow invite:<url> | block-all [enabled:<bool>] - /logs set-channel channel:<channel> - /testlink url:<url> - /config export|import (JSON file attachment) Runtime pipeline (events/message-create.ts): - ignores bots and DMs; skips disabled guilds - builds a PolicyConfig from the guild's stored config + allow/block lists + invite rules, runs it through @antilink-guard/core's evaluateMessage - gates message deletion/timeout on discord.js's own permission signals (message.deletable, member.moderatable) - moderation/enforce.ts depends on a narrow structural interface rather than the full discord.js Message class, specifically so it's unit-testable with plain fakes - records an AuditLogEntry (metadata only - no message content) and a ModerationActionRecord for every non-ALLOW verdict, and posts a mod-log embed (user, channel, normalized URL, action, reason, score, timestamp) to the configured log channel - a per-guild token-bucket rate limiter caps enforcement actions so a spam wave can't drive the bot into Discord's own API rate limits Also extends @antilink-guard/core's EnforcementMode/ModerationActionType with a `warn` tier (log/warn/delete/timeout), which /antilink mode's block/warn/log choices need - `warn` mode flags BLOCK/QUARANTINE-severity content without deleting it. All existing core tests still pass unchanged; 3 new policy-engine tests cover the new tier. /config import always overwrites the imported bundle's guildId with the guild the command was run in, so an admin can never accidentally (or maliciously) write another server's data into their own database. 122 new tests (68 in discord-bot, plus 3 core + 1 storage-schema addition), all against fakes/mocks of discord.js's Message/Interaction/Client shapes - there's no live Discord connection in this environment (no bot token, no gateway), so this is unit coverage of the actual logic, not an end-to-end test against real Discord. 186 tests pass across the whole workspace. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
…ompose A minimal, real bot assembled entirely from the published @antilink-guard/* packages: SqliteStorageAdapter + createBot from discord-bot, nothing else. - src/index.ts - loads .env, creates the data directory, opens SQLite, logs in - src/register-commands.ts - one-shot script to register the 7 slash command groups, guild-scoped (instant) or global - .env.example - DISCORD_TOKEN, DISCORD_CLIENT_ID, DISCORD_GUILD_ID, DATABASE_SQLITE_PATH, LOG_LEVEL - README.md - Discord Developer Portal setup (bot permissions, Message Content Intent), env var table, command registration, Docker Compose usage - Dockerfile - multi-stage pnpm workspace build, non-root user, a named volume for the SQLite file - root docker-compose.yml - builds and runs it with `docker compose up` Verified end-to-end in this session (not just written): with no token it exits with a clear error; with a token it creates the data directory, initializes a real SQLite database (confirmed via the WAL files it wrote), and proceeds all the way to a genuine Discord gateway request - which only fails here because this sandbox's network policy blocks discord.com, not because of anything in the code. `docker compose config` fully resolves the compose file (build context, env vars, volume) with no errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
A dependency-free (plain node:http, no framework) local dashboard that reads directly from the same storage a self-hosted bot uses: - Bot status per guild (enabled, mode, log channel, list counts, last audit entry time) - Full guild configuration - Allowlist and blocklist entries (with blocklist reasons) - The 50 most recent audit log entries No paid features, and explicitly no Discord OAuth or login of any kind - it's a local, unauthenticated, read-only tool by design, with that limitation called out prominently in both the UI banner and the README rather than left implicit. Real Discord OAuth is noted as a documented, opt-in future enhancement, not something bolted on half-finished here. Verified end-to-end: 8 integration tests start the actual HTTP server on an ephemeral port and hit it with real fetch() calls (not mocks), plus a manual smoke test running the built server and curling both the HTML page and a JSON API route. 194 tests pass across the whole workspace. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
…al DB driver selection CI (.github/workflows/ci.yml): full rewrite for pnpm - install, build (all packages/apps, topologically ordered by pnpm), typecheck, lint, format check, test. Runs on Node 20 and 22 (dropped 18, matching the package.json engines field). Adds live postgres:16 and mysql:8 service containers so those storage adapter contract tests actually run in CI instead of being skipped - only mysql.test.ts still skips locally when no MYSQL_TEST_URL is set. New: .github/workflows/codeql.yml (JS/TS static analysis on push, PR, and a weekly schedule). dependabot.yml: added a docker ecosystem entry for the example bot's base image, alongside the existing npm and github-actions entries. New root docs: GOVERNANCE.md (maintainer-led model, decision-making, scope boundary) and ROADMAP.md (shipped v0.1.0 items, near-term, later/exploratory, and an explicit "out of scope" section distinguishing this OSS framework from a hosted/paid product). Updated CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md, the PR template, and the issue templates for the pnpm monorepo - removed stale references to the old single-file bot and to an unrelated hosted platform's support/docs links (CODE_OF_CONDUCT.md's enforcement contact no longer points at a different product's support server; issue template links no longer reference a GitHub Discussions tab this repo doesn't have enabled). Root .env.example and package.json's `homepage` field also updated - env config is per-app now (apps/example-bot, apps/dashboard-lite each have their own .env.example), and homepage points at this repository instead of an unrelated hosted product's website. Real feature, not just docs: apps/example-bot can now select its storage backend via DATABASE_DRIVER (sqlite/mysql/postgres) instead of hardcoding SqliteStorageAdapter, so the self-hosting story for MySQL/Postgres is actually implemented, not aspirational. 8 new tests for the selection logic. 202 tests pass across the whole workspace. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
|
You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool. What Enabling Code Scanning Means:
For more information about GitHub Code Scanning, check out the documentation. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This supersedes PR #1 (already merged, but only through its early docs-only commit). This PR carries every commit made since then: the full rebuild of this repo into AntiLink Guard OSS - a self-hostable, TypeScript, open-source Discord anti-phishing and link-moderation framework, restructured as a pnpm monorepo.
What's done (Phases 1-8 of 9)
packages/core- URL extraction (defanged links, punycode, zero-width-character obfuscation, markdown, Discord invites), classification (domain allow/blocklists, URL shorteners, homoglyphs, custom regex rules), and a policy engine with alog → warn → delete → timeoutenforcement ladder.packages/storage- four adapters behind oneStorageAdapterinterface, validated by a shared contract test suite. The Postgres adapter is tested against a live Postgres instance, not just written and assumed correct; MySQL is honestly skipped locally (no server available) but runs for real in CI via a service container.packages/discord-bot-/antilink,/allowlist,/blocklist,/invites,/logs,/testlink,/config(export/import). The message pipeline gates deletion/timeout on discord.js's own permission signals, logs only metadata (no message content), posts mod-log embeds, and rate-limits enforcement actions.packages/cli- theantilinkcommand, independently testable without any Discord connection.apps/example-bot- a real, working bot assembled from the published packages, with Docker Compose and aDATABASE_DRIVERswitch (sqlite/mysql/postgres) that's actually implemented, not just documented.apps/dashboard-lite- dependency-free (node:http, no framework), explicitly local/unauthenticated by design, not half-built OAuth.212 tests pass across the workspace (typecheck, lint, and format all clean) - see the CI run on this PR for the live matrix.
What's left (Phase 9, follow-up PR)
docs/*.md(9 pages: getting-started, configuration, rules-engine, discord-setup, privacy, self-hosting, threat-model, api-reference, migration-from-old-antilink)README.mdrewrite (architecture diagram, quick start, badges)CHANGELOG.mdentries for this rewrite and v0.1.0 release notesNotes for reviewers
EnforcementMode/ModerationActionTypegained awarntier (log/warn/delete/timeout) to match the required/antilink mode block|warn|logcommand surface - existing tests were unaffected, 3 new ones added.antilink-guard(fromAnti-Links-Discord-Bot) -package.jsonURLs already point at the new name; the actual GitHub rename is a separate manual step for the repo owner.🤖 Generated with Claude Code
https://claude.ai/code/session_01MivRFgabRtQs6GhpNM3wie
Generated by Claude Code