Repository navigation
fix(auth): rate-limit login per IP, trusted proxies, 12-character passwords - #150
Merged
Merged
Conversation
…default
- POST /api/auth/login: 10 attempts per minute per TCP peer IP
(X-Forwarded-For ignored), then 429 with Retry-After. The block starts
at 1 minute and doubles on each attempt while blocked and on each new
block, capped at 15 minutes. A successful login clears the IP. No
per-account lockout. In-memory, idle entries swept after 15 minutes,
at most 10,000 IPs.
- New passwords (init, change-password, /setup page) need 12 characters.
Login does not check length, so existing 8-character passwords work.
- The binary's -addr default is 127.0.0.1:8787. The Docker image passes
-addr :8787 explicitly and is unchanged.
- GET /api/health returns {"ok":true} without a valid token; with one it
still returns service and version for the web UI top bar.
… reset-password.sh - ARTEX_TRUSTED_PROXIES (comma-separated CIDRs, empty by default). When the TCP peer is listed, the login limit reads X-Forwarded-For from the right and keys on the first hop that is not a trusted proxy; otherwise the header is ignored. Without it every client behind a reverse proxy shares one bucket and an attacker can block the admin. Passed through by docker-compose.yml and listed in .env.example. - README: nginx example sends X-Forwarded-For; document the variable; the binary's default listen address is 127.0.0.1:8787. - reset-password.sh refuses passwords under 12 characters, counted in characters like the server.
- README: ARTEX_TRUSTED_PROXIES for nginx and ARTEX on the host (127.0.0.1) and for nginx on the host with ARTEX in Docker (the bridge gateway, found with docker network inspect). - CI shell job: run shellcheck on reset-password.sh too.
Merged
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.
Login rate limit, 12-character passwords, loopback default, quieter /api/health
Deployment condition: reverse proxies
If ARTEX runs behind a reverse proxy, you must do both of the following before upgrading.
X-Forwarded-For. For nginx:proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;.ARTEX_TRUSTED_PROXIESto the address ARTEX sees the proxy connect from:127.0.0.1/32;docker network inspect artex_default --format '{{(index .IPAM.Config 0).Gateway}}', set in.env.Without both, every client reaches ARTEX from the proxy's single IP and shares one rate-limit bucket. Anyone who fails 10 logins in a minute then blocks every user, the admin included, for 1 to 15 minutes, and the block keeps extending while they continue. Direct and SSH-tunnel setups need no change.
Changes
POST /api/auth/login)Retry-Afterheader in seconds, and a JSON{"error": ...}body.X-Forwarded-Foris ignored.ARTEX_TRUSTED_PROXIESset (comma-separated CIDRs, empty by default), a request from a listed peer is handled differently. The header is read from the right, and the first hop that is not a trusted proxy is the client. Entries the client added further left are never used.docker-compose.ymlpasses the variable through, and.env.examplelists it.POST /api/auth/init,POST /api/auth/change-password, the/setuppage check and text, andreset-password.sh.-addrdefault is now127.0.0.1:8787(was:8787). The Docker image passes-addr :8787explicitly, so containers are unchanged.GET /api/health: without a valid token it returns{"ok":true}only. With a token it returnsok,serviceandversionas before; the web UI's top bar readsversionand sends its token.X-Forwarded-Forand both proxy layouts, and gives the default listen address.shelljob also runs shellcheck onreset-password.sh.Tests
These run without PostgreSQL: the handler logic is reached through
login(w, r, getSetting)with a stub settings read.TestLoginRateLimitReturns429WithRetryAfterTestLoginSuccessResetsCounterTestLoginRateLimitIsPerIPTestLoginRateLimitIgnoresForwardedForTestLoginRateLimitBehindTrustedProxyTestClientIPTestParseTrustedProxiesTestEightCharPasswordLogsInButIsRejectedAsNewTestLoginLimiterSweepsIdleEntriesTestLoginLimiterCapsEntriesTestHealthHidesVersionWithoutTokenTestValidatePassword(updated)Results:
go build ./...andgo vet ./server/ ./cmd/artex/pass.-race.go test ./server/...without PostgreSQL fails only in the tests that need a database. That is the same list as onmain.Not verified locally
/setupchange is a literal change from 8 to 12.reset-password.sh, because shellcheck is not installed on the machine used.