#1812·apitable

Unauthenticated premature account destruction via /api/v1/internal/users/{userId}/close

Author: geo-chenCreated Jul 1, 2026Updated Jul 1, 2026
Labelsbug

reported via email on 25 May 2026 - no response:

I am reporting a missing authentication vulnerability in APITable (all versions through commit 88b24ce / docker tag latest) that allows an unauthenticated remote attacker to permanently and irreversibly destroy user accounts.

Background on the intended flow: APITable implements a two-step account deletion process. When a logged-in user submits POST /api/v1/user/applyForClosing, their account is placed in a "cooling-off" state (is_paused = 1). After 30 days, a scheduled task permanently closes the account by erasing the user's email, phone, and personal data, cancelling space subscriptions, removing space memberships, and deleting OAuth bindings.

The vulnerability: Two endpoints in InternalUserController are annotated with requiredLogin = false and are served through the public nginx gateway (location /api proxies to backend-server), making them accessible to any unauthenticated HTTP client:

Endpoint 1 -- Enumerate accounts in cooling-off state:

POST /api/v1/internal/getUserHistories HTTP/1.1
Host: [target.example.com](http://target.example.com/)
Content-Type: application/json

{"limitDays":30}

Response: {"success":true,"code":200,"message":"SUCCESS","data":[{"userId":2058870343465140225,...}]}

Endpoint 2 -- Immediately and permanently destroy the account (no credentials required):

POST /api/v1/internal/users/2058870343465140225/close HTTP/1.1
Host: [target.example.com](http://target.example.com/)
Content-Length: 0

Response: {"success":true,"code":200,"message":"SUCCESS","data":true}

After this call, the victim's email, phone number, and nick name are cleared from the database, all space memberships are removed, all OAuth bindings are deleted, and the account cannot be recovered. The 30-day cooling-off window -- which exists as a safety mechanism -- is bypassed entirely.

The root cause is in InternalUserController.java. Both closePausedUserAccount() and getUserHistories() carry the requiredLogin = false annotation. This annotation causes the ResourceInterceptor to return immediately (line: if (!resourceDef.getRequiredLogin()) { return true; }) without checking for a session or API key. The endpoints were designed for internal service-to-service use but are reachable from the public internet because the nginx gateway forwards all /api/** requests to the backend.

I reproduced this against docker tag apitable/backend-server:latest (commit 88b24ce) running at localhost:9157 on 2026-05-25. The fix should either require authentication (a shared secret or network-level restriction) on all /api/v1/internal/** routes, or move internal endpoints to a port that is not exposed through the public gateway.

CVSS 3.1 base score: AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:H = 8.2 High CWE-306: Missing Authentication for Critical Function