A tool for parsing traffic on the jetstream and applying a moderation workstream based on regexp based rules
1# Developing Moderation Checks 2 3This guide explains how to configure moderation rules for skywatch-automod. 4 5## Overview 6 7Moderation checks are defined in TypeScript files in the `rules/` directory. Each check uses regular expressions to match content and specifies what action to take when a match is found. 8 9## Check Types 10 11### Post Content Checks 12 13File: `rules/posts.ts` 14 15Monitors post text and embedded URLs for matches. 16 17```typescript 18import type { Checks } from "../src/types.js"; 19 20export const POST_CHECKS: Checks[] = [ 21 { 22 label: "spam", 23 comment: "Spam content detected in post", 24 reportAcct: false, 25 commentAcct: false, 26 toLabel: true, 27 check: new RegExp("buy.*followers", "i"), 28 }, 29]; 30``` 31 32### Handle Checks 33 34File: `rules/handles.ts` 35 36Monitors user handles for pattern matches. 37 38```typescript 39export const HANDLE_CHECKS: Checks[] = [ 40 { 41 label: "impersonation", 42 comment: "Potential impersonation detected", 43 reportAcct: true, 44 commentAcct: false, 45 toLabel: false, 46 check: new RegExp("official.*support", "i"), 47 }, 48]; 49``` 50 51### Profile Checks 52 53File: `rules/profiles.ts` 54 55Monitors profile display names and descriptions. 56 57```typescript 58export const PROFILE_CHECKS: Checks[] = [ 59 { 60 label: "spam-profile", 61 comment: "Spam content in profile", 62 reportAcct: false, 63 commentAcct: false, 64 toLabel: true, 65 displayName: true, // Check display name 66 description: true, // Check description 67 check: new RegExp("follow.*back", "i"), 68 }, 69]; 70``` 71 72### Account Age Checks 73 74File: `rules/accountAge.ts` 75 76Labels accounts created after a specific date when they interact with monitored content. 77 78```typescript 79import type { AccountAgeCheck } from "../src/types.js"; 80 81export const ACCOUNT_AGE_CHECKS: AccountAgeCheck[] = [ 82 { 83 monitoredDIDs: ["did:plc:abc123"], 84 anchorDate: "2025-01-15", 85 maxAgeDays: 7, 86 label: "new-account-spam", 87 comment: "New account replying to monitored user", 88 expires: "2025-02-15", // Optional expiration 89 }, 90]; 91``` 92 93### Account Threshold Checks 94 95File: `rules/accountThreshold.ts` 96 97Applies account-level labels when an account accumulates multiple post-level violations within a time window. 98 99```typescript 100import type { AccountThresholdConfig } from "../src/types.js"; 101 102export const ACCOUNT_THRESHOLD_CONFIGS: AccountThresholdConfig[] = [ 103 { 104 labels: ["spam", "scam"], // Trigger on either label 105 threshold: 3, 106 accountLabel: "repeat-offender", 107 accountComment: "Account exceeded spam threshold", 108 window: 7, 109 windowUnit: "days", // Options: "minutes", "hours", "days" 110 reportAcct: true, 111 commentAcct: false, 112 toLabel: true, 113 }, 114]; 115``` 116 117### Starter Pack Threshold Checks 118 119File: `rules/starterPackThreshold.ts` 120 121Applies account-level labels when an account creates too many starter packs within a time window. Useful for detecting follow-farming and coordinated campaign behaviour. 122 123```typescript 124import type { StarterPackThresholdConfig } from "../src/types.js"; 125 126export const STARTER_PACK_THRESHOLD_CONFIGS: StarterPackThresholdConfig[] = [ 127 { 128 threshold: 10, // Account action triggered after 10 starter packs 129 window: 7, // Within this duration 130 windowUnit: "days", // Options: "minutes", "hours", "days" 131 accountLabel: "follow-farming", 132 accountComment: "Account created multiple starter packs in short period", 133 toLabel: true, // Whether to apply the label (default: true) 134 reportAcct: true, // Whether to report the account 135 commentAcct: false, // Whether to comment on the account 136 allowlist: [], // DIDs to exempt from this check 137 }, 138]; 139``` 140 141## Check Configuration Fields 142 143### Basic Fields (Required) 144 145- `label` - Label to apply (string) 146- `comment` - Comment for the moderation action (string) 147- `reportAcct` - Create account report (boolean) 148- `commentAcct` - Add comment to account (boolean) 149- `toLabel` - Apply the label (boolean) 150- `check` - Regular expression pattern (RegExp) 151 152### Optional Fields 153 154- `language` - Language codes to restrict check to (string[]) 155- `description` - Check profile descriptions (boolean) 156- `displayName` - Check profile display names (boolean) 157- `reportPost` - Create post report instead of just labeling (boolean) 158- `duration` - Label duration in hours (number) 159- `whitelist` - RegExp to exclude from matching (RegExp) 160- `ignoredDIDs` - DIDs to skip checking (string[]) 161- `starterPacks` - Filter by starter pack membership (string[]) 162- `knownVectors` - Known attack vectors for tracking (string[]) 163- `trackOnly` - Track without applying label (boolean) 164- `unlabel` - Remove existing label if content no longer matches (boolean) 165 166### Threshold Configuration Fields 167 168#### Account Threshold 169 170- `labels` - Single label or array of labels to aggregate (string | string[]) 171- `threshold` - Number of labeled posts required to trigger account action (number) 172- `window` - Rolling window duration (number) 173- `windowUnit` - Unit for the rolling window: "minutes", "hours", or "days" (WindowUnit) 174- `accountLabel` - Label to apply to the account (string) 175- `accountComment` - Comment for the account action (string) 176- `toLabel` - Whether to apply the label, defaults to true (boolean) 177- `reportAcct` - Whether to report the account (boolean) 178- `commentAcct` - Whether to comment on the account (boolean) 179 180#### Starter Pack Threshold 181 182- `threshold` - Number of starter packs required to trigger account action (number) 183- `window` - Rolling window duration (number) 184- `windowUnit` - Unit for the rolling window: "minutes", "hours", or "days" (WindowUnit) 185- `accountLabel` - Label to apply to the account (string) 186- `accountComment` - Comment for the account action (string) 187- `toLabel` - Whether to apply the label, defaults to true (boolean) 188- `reportAcct` - Whether to report the account (boolean) 189- `commentAcct` - Whether to comment on the account (boolean) 190- `allowlist` - DIDs to exempt from this check (string[]) 191 192## Examples 193 194### Language-Specific Check 195 196```typescript 197{ 198 language: ["spa"], 199 label: "spam-es", 200 comment: "Spanish spam detected", 201 reportAcct: false, 202 commentAcct: false, 203 toLabel: true, 204 check: new RegExp("comprar seguidores", "i"), 205} 206``` 207 208### Temporary Label 209 210```typescript 211{ 212 label: "review-needed", 213 comment: "Content flagged for review", 214 reportAcct: true, 215 commentAcct: false, 216 toLabel: false, 217 duration: 24, // Label expires after 24 hours 218 check: new RegExp("suspicious.*pattern", "i"), 219} 220``` 221 222### Whitelist Exception 223 224```typescript 225{ 226 label: "blocked-term", 227 comment: "Blocked term used", 228 reportAcct: false, 229 commentAcct: false, 230 toLabel: true, 231 check: new RegExp("\\bterm\\b", "i"), 232 whitelist: new RegExp("legitimate.*context", "i"), 233} 234``` 235 236### Ignored DIDs 237 238```typescript 239{ 240 label: "blocked-term", 241 comment: "Blocked term used", 242 reportAcct: false, 243 commentAcct: false, 244 toLabel: true, 245 check: new RegExp("\\bterm\\b", "i"), 246 ignoredDIDs: [ 247 "did:plc:trusted123", 248 "did:plc:verified456", 249 ], 250} 251``` 252 253## Global Configuration 254 255### Allowlist 256 257File: `rules/constants.ts` 258 259DIDs in the global allowlist bypass all checks. 260 261```typescript 262export const GLOBAL_ALLOW: string[] = [ 263 "did:plc:trusted123", 264 "did:plc:verified456", 265]; 266``` 267 268### Link Shorteners 269 270Pattern to match URL shorteners for special handling. 271 272```typescript 273export const LINK_SHORTENER = new RegExp( 274 "bit\\.ly|tinyurl\\.com|goo\\.gl", 275 "i" 276); 277``` 278 279## Best Practices 280 281### Regular Expressions 282 283- Use word boundaries (`\\b`) to avoid partial matches 284- Test patterns thoroughly to minimize false positives 285- Use case-insensitive matching (`i` flag) when appropriate 286- Escape special regex characters 287 288### Action Selection 289 290- `toLabel: true` - Apply label immediately (use for clear violations) 291- `reportAcct: true` - Create report for manual review (use for ambiguous cases) 292- `commentAcct: true` - Create comment on account (probably can be depreciated) 293 294### Performance 295 296- Keep regex patterns simple and efficient 297- Use language filters to reduce unnecessary checks 298- Leverage whitelists instead of complex negative lookaheads 299 300### Testing 301 302After modifying rules: 303 304```bash 305bun test:run 306``` 307 308Test specific rule modules: 309 310```bash 311bun test src/rules/posts/tests/ 312``` 313 314## Deployment 315 316Rules are mounted as a volume in docker compose: 317 318```yaml 319volumes: 320 - ./rules:/app/rules 321``` 322 323Changes require automod rebuild: 324 325```bash 326docker compose up -d --build automod 327```