Skip to content
created by Aha00aAha00a at 2026-05-27
last modified by Aha00aAha00a at 2026-09-10
revision: 8

Blame of Dev BotDetection

older newer
DateTime Nickname Rev # Content Comment
2026-05-27T02:56:56 Aha00aAha00a 1 0 = [Dev] BotDetection initial
2026-05-27T02:56:56 Aha00aAha00a 1 1 initial
2026-05-27T02:56:56 Aha00aAha00a 1 2 크롤러·공격 요청을 탐지해 자동으로 차단하는 시스템 전체를 설명한다. initial
2026-05-27T02:56:56 Aha00aAha00a 1 3 initial
2026-05-27T02:56:56 Aha00aAha00a 1 4 == 전체 흐름 initial
2026-05-27T02:56:56 Aha00aAha00a 1 5 initial
2026-05-27T02:56:56 Aha00aAha00a 1 6 `FilterAccessLog`가 모든 요청을 가로채 아래 순서로 판정한다. initial
2026-05-27T02:56:56 Aha00aAha00a 1 7 initial
2026-05-27T02:56:56 Aha00aAha00a 1 8 [[[#!Text initial
2026-05-27T02:56:56 Aha00aAha00a 1 9 요청 도착 initial
2026-05-27T02:56:56 Aha00aAha00a 1 10 │ initial
2026-09-08T09:05:15 Aha00aAha00a 5 11 ├─[0] ipWhitelist 에 있나? ────────YES─► 아래 전부 건너뛰고 정상 처리 Whitelist is step 0 of the flow, and retention points at the constant instead of a number that moved
2026-09-08T09:05:15 Aha00aAha00a 5 12 │ Whitelist is step 0 of the flow, and retention points at the constant instead of a number that moved
2026-05-27T02:56:56 Aha00aAha00a 1 13 ├─[1] IpRateLimiter.isKnownBanned? ─YES─► tarpit(1~6분) + 403 initial
2026-05-27T02:56:56 Aha00aAha00a 1 14 │ (인메모리, DB 없음) initial
2026-05-27T02:56:56 Aha00aAha00a 1 15 │ initial
2026-09-08T09:05:15 Aha00aAha00a 5 16 ├─[·] IpRateLimiter.isKnownClean? ─YES─► [2] 를 건너뛴다 (60초 TTL) Whitelist is step 0 of the flow, and retention points at the constant instead of a number that moved
2026-09-08T09:05:15 Aha00aAha00a 5 17 │ Whitelist is step 0 of the flow, and retention points at the constant instead of a number that moved
2026-05-27T02:56:56 Aha00aAha00a 1 18 ├─[2] IpDeny DB 조회 ──────────────YES─► ban() 캐시 + tarpit(1~6분) + 403 initial
2026-09-08T09:05:15 Aha00aAha00a 5 19 │ (0·1 이 false이고 clean 캐시도 없을 때만 실행) Whitelist is step 0 of the flow, and retention points at the constant instead of a number that moved
2026-05-27T02:56:56 Aha00aAha00a 1 20 │ initial
2026-08-24T16:50:37 Aha00aAha00a 3 21 ├─[3] UriAttackDetector.isAttack? ─YES─► ban() + IpDeny 등록(async) + tarpit(1~11분) + 403 Verify documented numbers against the code; link the timezone plan and the convention (AhaWiki@1b0af0a3)
2026-05-27T02:56:56 Aha00aAha00a 1 22 │ initial
2026-05-27T02:56:56 Aha00aAha00a 1 23 ├─[4] IpRateLimiter.recordAndCheck? YES─► ban() + IpDeny 등록(async) + 즉시 403 initial
2026-05-27T02:56:56 Aha00aAha00a 1 24 │ initial
2026-05-27T02:56:56 Aha00aAha00a 1 25 └─[5] 정상 처리 initial
2026-05-27T02:56:56 Aha00aAha00a 1 26 ]]] initial
2026-05-27T02:56:56 Aha00aAha00a 1 27 initial
2026-05-27T02:56:56 Aha00aAha00a 1 28 === 단계별 설명 initial
2026-05-27T02:56:56 Aha00aAha00a 1 29 initial
2026-09-08T09:05:15 Aha00aAha00a 5 30 * '''`[0]` 화이트리스트''': `AhaWiki.ipWhitelist` 에 있는 주소는 `[1]`~`[4]` 를 모두 건너뛴다. '''`IpDeny` 행이 남아 있어도 그렇다''' — 그래서 차단을 푸는 가장 빠른 방법은 행을 지우는 것이 아니라 여기에 넣는 것이다. 예전에는 `[2]` 가 이 검사보다 먼저라 한 번 등록된 주소는 화이트리스트로도 풀 수 없었고, 서버 자신의 EIP 가 두 달 막혀 있었다. 루프백 세 주소는 설정이 아니라 `logics.ApplicationConf.AlwaysWhitelisted` 에 있어서 사이트 conf 가 지울 수 없다. Whitelist is step 0 of the flow, and retention points at the constant instead of a number that moved
2026-08-24T14:30:49 Aha00aAha00a 2 31 * '''`[1]` 인메모리 ban 확인''': `IpRateLimiter.knownBanned` 셋을 조회한다. DB 없음. `[2]`~`[4]`에서 차단된 IP가 재요청하면 여기서 바로 잡힌다. Fix accidental page links and two renamed symbols (AhaWiki@fec07d7d, @4a0f5b21)
2026-09-08T09:58:46 Aha00aAha00a 6 32 * '''`[·]` 인메모리 clean 캐시''': `knownClean` 에 있으면 `[2]` 의 DB 조회를 건너뛴다. TTL 은 `cleanTtlMs`(현재 60초). 정상 방문자가 매 요청마다 `IpDeny` 를 조회하지 않게 하는 것이 목적이다. Name where each tunable value lives, beside the value itself
2026-09-10T04:20:42 Aha00aAha00a 7 33 * '''`[2]` IpDeny DB 조회''': 서버 재시작 전에 등록된 영구 차단 IP를 처리한다. 조회 결과가 있으면 `ban()`을 호출해 이후 요청은 `[1]`에서 처리되도록 캐시한다. 결과가 없으면 `markClean()` 으로 60초 동안 `knownClean` 에 넣는다 — `[·]` 는 여기서만 채워진다. Bring the Dev pages back in step with the code: claims about behavior and structure checked against the source (2026-09-10 audit)
2026-09-10T04:20:42 Aha00aAha00a 7 34 * '''`[3]` UriAttackDetector''': WordPress·phpinfo 등 알려진 공격 URI를 차단한다. 탐지 즉시 `ban()`을 호출해 actor가 DB에 쓰기 전 같은 IP의 재요청을 `[1]`에서 막는다. `IpDeny` 등록 메시지는 tarpit 이 끝난 뒤(1~11분 뒤)에야 actor 로 간다 — 그 사이 재시작하면 행이 남지 않는다. `/public/`·`/assets/` 요청에서 잡힌 경우는 AccessLog 와 함께 IpDeny 등록도 건너뛴다(`shouldSkipAccessLogUri`). Bring the Dev pages back in step with the code: claims about behavior and structure checked against the source (2026-09-10 audit)
2026-09-10T04:20:42 Aha00aAha00a 7 35 * '''`[4]` IpRateLimiter.recordAndCheck''': 고속 요청 또는 페이지만 긁는 패턴을 탐지한다. tarpit 없이 즉시 응답한다(고볼륨 봇에 tarpit을 걸면 서버 커넥션이 쌓임). 등록 메시지의 조건은 `[3]` 과 같다. Bring the Dev pages back in step with the code: claims about behavior and structure checked against the source (2026-09-10 audit)
2026-09-10T04:20:42 Aha00aAha00a 7 36 * '''tarpit''': `after(delay, scheduler)` + 지연 응답. 스레드는 잡히지 않지만 TCP 커넥션은 열려 있다. `[1]``[2]` 와 `[3]` 에 쓰고, `[4]` 만 tarpit 없이 바로 403 이다. Bring the Dev pages back in step with the code: claims about behavior and structure checked against the source (2026-09-10 audit)
2026-05-27T02:56:56 Aha00aAha00a 1 37 initial
2026-05-27T02:56:56 Aha00aAha00a 1 38 == IpRateLimiter initial
2026-05-27T02:56:56 Aha00aAha00a 1 39 initial
2026-05-27T02:56:56 Aha00aAha00a 1 40 `logics.security.IpRateLimiter` (`@Singleton`) initial
2026-05-27T02:56:56 Aha00aAha00a 1 41 initial
2026-05-27T02:56:56 Aha00aAha00a 1 42 === knownBanned initial
2026-05-27T02:56:56 Aha00aAha00a 1 43 initial
2026-08-24T14:30:49 Aha00aAha00a 2 44 인메모리 셋(`ConcurrentHashMap.newKeySet`). 세 경로에서 추가된다. Fix accidental page links and two renamed symbols (AhaWiki@fec07d7d, @4a0f5b21)
2026-05-27T02:56:56 Aha00aAha00a 1 45 initial
2026-05-27T02:56:56 Aha00aAha00a 1 46 * `recordAndCheck` 탐지 시 initial
2026-05-27T02:56:56 Aha00aAha00a 1 47 * `UriAttackDetector` 탐지 시 (`FilterAccessLog`에서 직접 호출) initial
2026-05-27T02:56:56 Aha00aAha00a 1 48 * `IpDeny` DB 조회 결과가 있을 때 (`FilterAccessLog`에서 직접 호출) initial
2026-05-27T02:56:56 Aha00aAha00a 1 49 initial
2026-08-24T14:30:49 Aha00aAha00a 2 50 서버 재시작 시 초기화된다. 이후 요청은 `[2]` DB 조회를 통해 다시 캐시된다. Fix accidental page links and two renamed symbols (AhaWiki@fec07d7d, @4a0f5b21)
2026-05-27T02:56:56 Aha00aAha00a 1 51 initial
2026-05-27T02:56:56 Aha00aAha00a 1 52 === URI 분류 initial
2026-05-27T02:56:56 Aha00aAha00a 1 53 initial
2026-05-27T02:56:56 Aha00aAha00a 1 54 * `WikiPage`: `/w/*` initial
2026-05-27T02:56:56 Aha00aAha00a 1 55 * `HumanSignal`: 그 외 전부 (`/public/`, `/assets/`, `/api/links/`, `/api/me`, `/search` 등) initial
2026-05-27T02:56:56 Aha00aAha00a 1 56 initial
2026-05-27T02:56:56 Aha00aAha00a 1 57 실제 브라우저는 `/w/*`와 함께 반드시 다른 요청을 섞어 보낸다. initial
2026-05-27T02:56:56 Aha00aAha00a 1 58 순수 스크레이퍼는 `/w/*`만 요청하므로 HumanSignal이 누적되지 않는다. initial
2026-05-27T02:56:56 Aha00aAha00a 1 59 initial
2026-05-27T02:56:56 Aha00aAha00a 1 60 === 탐지 패턴 initial
2026-05-27T02:56:56 Aha00aAha00a 1 61 initial
2026-09-08T09:58:46 Aha00aAha00a 6 62 임계값 넷은 `IpRateLimiter` 의 상수다. 괄호 안은 현재 값이다. Name where each tunable value lives, beside the value itself
2026-05-27T02:56:56 Aha00aAha00a 1 63 initial
2026-09-08T09:58:46 Aha00aAha00a 6 64 * '''고속 요청''': `windowMs` 안에 `/w/*` 가 `rateThreshold` 회 이상 (30초 / 30회) Name where each tunable value lives, beside the value itself
2026-09-08T09:58:46 Aha00aAha00a 6 65 * '''페이지만 긁기''': 같은 창에서 `/w/*` 가 `botPageMin` 회 이상이고 HumanSignal 이 `botHumanSignalMin` 회 미만 (5회 / 3회) Name where each tunable value lives, beside the value itself
2026-09-08T09:58:46 Aha00aAha00a 6 66 Name where each tunable value lives, beside the value itself
2026-09-05T12:16:02 Aha00aAha00a 4 67 둘째 패턴은 사람이 만든 도구도 문다. 배포 헬스체크가 `/w/FrontPage` 만 3초 간격으로 폴링해서 다섯 번째에 '''자기를 차단'''한 적이 있다 — [Dev Deploying] 의 "왜 이런 모양인가" 참고. 자동 요청을 짜는 쪽에서는 `/w/` 밖의 URL 을 쓰거나 화이트리스트에 오르는 편이 낫다. Match the docs to the code: /hc health check, multi-class Schema blocks, and two links that pointed at Dev
2026-09-05T12:16:02 Aha00aAha00a 4 68 Match the docs to the code: /hc health check, multi-class Schema blocks, and two links that pointed at Dev
2026-05-27T02:56:56 Aha00aAha00a 1 69 === cleanup initial
2026-05-27T02:56:56 Aha00aAha00a 1 70 initial
2026-09-10T04:20:42 Aha00aAha00a 7 71 `recordAndCheck` 가 낮은 확률로 `cleanup()`을 실행해 만료된 `windows` 엔트리와 만료된 `knownClean` 엔트리를 제거한다. 확률은 그 함수 안의 `Random.nextInt` 인자(현재 1000번에 1번)다. Bring the Dev pages back in step with the code: claims about behavior and structure checked against the source (2026-09-10 audit)
2026-05-27T02:56:56 Aha00aAha00a 1 72 initial
2026-05-27T02:56:56 Aha00aAha00a 1 73 == UriAttackDetector initial
2026-05-27T02:56:56 Aha00aAha00a 1 74 initial
2026-05-27T02:56:56 Aha00aAha00a 1 75 `logics.security.UriAttackDetector` initial
2026-05-27T02:56:56 Aha00aAha00a 1 76 initial
2026-05-27T02:56:56 Aha00aAha00a 1 77 알려진 공격·탐색 URI 패턴을 in-memory로 판정한다. initial
2026-05-27T02:56:56 Aha00aAha00a 1 78 initial
2026-05-27T02:56:56 Aha00aAha00a 1 79 * `startsWith` 목록: `/wp`, `/wordpress`, `/backup`, `/.env`, `/.git` 등 initial
2026-05-27T02:56:56 Aha00aAha00a 1 80 * `contains` 목록: `/wp-admin`, `/wp-login.php`, `/phpinfo.php`, `/xmlrpc.php` 등 initial
2026-05-27T02:56:56 Aha00aAha00a 1 81 * 정규식: `.php` 파일 경로 전반 (`(?i)^/[a-z0-9._=-]{1,64}\.php...`) initial
2026-05-27T02:56:56 Aha00aAha00a 1 82 initial
2026-05-27T02:56:56 Aha00aAha00a 1 83 == IpDeny (DB) initial
2026-05-27T02:56:56 Aha00aAha00a 1 84 initial
2026-05-27T02:56:56 Aha00aAha00a 1 85 `models.tables.IpDeny` initial
2026-05-27T02:56:56 Aha00aAha00a 1 86 initial
2026-05-27T02:56:56 Aha00aAha00a 1 87 * `insert(ip, accessLog, reason)`: actor(`ActorAccessLog`)가 비동기로 호출한다. initial
2026-09-10T04:20:42 Aha00aAha00a 7 88 * `selectLatest(ip)`: 최신 차단 레코드 조회. `FilterAccessLog`에서 `[0]``[1]` 이 모두 false 이고 `knownClean` 에도 없을 때만 실행된다. Bring the Dev pages back in step with the code: claims about behavior and structure checked against the source (2026-09-10 audit)
2026-09-08T09:05:15 Aha00aAha00a 5 89 * `deleteExpired`: `Retention` 보다 오래된 레코드를 정리한다. 값과 그 이유는 상수 옆에 있다. Whitelist is step 0 of the flow, and retention points at the constant instead of a number that moved
2026-05-27T02:56:56 Aha00aAha00a 1 90 initial
2026-09-08T09:05:15 Aha00aAha00a 5 91 '''보관 기간이 곧 차단 기간이다.''' `selectLatest` 는 행의 나이를 보지 않으므로, 행이 남아 있는 동안 그 주소는 계속 403 이다. 이 문서는 2026-08-03 에 값이 바뀐 뒤로도 "5년" 이라고 적고 있었다 — 숫자를 산문에 옮겨 적으면 이렇게 갈라진다. 차단을 당장 풀려면 `ipWhitelist` 에 넣는 편이 빠르다(맨 위 표에서 whitelist 가 `IpDeny` 조회 자체를 건너뛴다). Whitelist is step 0 of the flow, and retention points at the constant instead of a number that moved
2026-09-08T09:05:15 Aha00aAha00a 5 92 Whitelist is step 0 of the flow, and retention points at the constant instead of a number that moved
2026-09-10T19:26:15 Aha00aAha00a 8 93 == 인스턴스가 둘이다 Bot detection: what two serving instances do to thresholds and to the ban reaching the other one
2026-09-10T19:26:15 Aha00aAha00a 8 94 Bot detection: what two serving instances do to thresholds and to the ban reaching the other one
2026-09-10T19:26:15 Aha00aAha00a 8 95 운영은 인스턴스 둘이 동시에 요청을 받는다(측정은 [Dev Deploying]). `IpRateLimiter` 의 창·`knownBanned`·`knownClean` 은 인스턴스마다 따로라서, 이 페이지의 흐름은 인스턴스 하나가 보는 것이다. Bot detection: what two serving instances do to thresholds and to the ban reaching the other one
2026-09-10T19:26:15 Aha00aAha00a 8 96 Bot detection: what two serving instances do to thresholds and to the ban reaching the other one
2026-09-10T19:26:15 Aha00aAha00a 8 97 * '''임계값은 인스턴스마다 센다.''' 프록시가 요청을 나눠 보내므로 한 인스턴스가 보는 것은 그 주소 요청의 일부다. 두 인스턴스에 고르게 가면, 위 탐지 패턴의 수보다 대략 두 배를 보내야 걸린다. Bot detection: what two serving instances do to thresholds and to the ban reaching the other one
2026-09-10T19:26:15 Aha00aAha00a 8 98 * '''한쪽이 잡은 주소를 다른 쪽이 아는 길은 `IpDeny` 행뿐이다.''' `[4]` 는 곧바로 행을 쓰지만(actor, 비동기), 다른 인스턴스가 그 주소를 `knownClean` 에 넣어 두었다면 그것이 끝날 때까지(`cleanTtlMs`) DB 를 다시 보지 않는다. `[3]` 은 tarpit 이 끝난 뒤에야 행을 쓰므로, 그 몇 분 동안 다른 인스턴스는 그 주소의 공격 URI 가 아닌 요청을 정상으로 처리한다. Bot detection: what two serving instances do to thresholds and to the ban reaching the other one
2026-09-10T19:26:15 Aha00aAha00a 8 99 Bot detection: what two serving instances do to thresholds and to the ban reaching the other one
2026-09-10T19:26:15 Aha00aAha00a 8 100 행이 쓰이고 나면 양쪽이 모두 막으므로, 이 틈은 분 단위다. 차단 기간과 견줄 크기는 아니다. Bot detection: what two serving instances do to thresholds and to the ban reaching the other one
2026-09-10T19:26:15 Aha00aAha00a 8 101 Bot detection: what two serving instances do to thresholds and to the ban reaching the other one
2026-05-27T02:56:56 Aha00aAha00a 1 102 == 관련 파일 initial
2026-05-27T02:56:56 Aha00aAha00a 1 103 initial
2026-05-27T02:56:56 Aha00aAha00a 1 104 * `app/filters/FilterAccessLog.scala` initial
2026-05-27T02:56:56 Aha00aAha00a 1 105 * `app/logics/security/IpRateLimiter.scala` initial
2026-05-27T02:56:56 Aha00aAha00a 1 106 * `app/logics/security/UriAttackDetector.scala` initial
2026-05-27T02:56:56 Aha00aAha00a 1 107 * `app/models/tables/IpDeny.scala` initial
2026-05-27T02:56:56 Aha00aAha00a 1 108 * `app/actors/ActorAccessLog.scala` initial