diff options
| author | Leitwolf | 2026-11-10 00:44:57 +0300 |
|---|---|---|
| committer | GitHub | 2026-11-10 00:44:57 +0300 |
| commit | 80d46cbffc87da9bf2665935e563ab556c1a267d (patch) | |
| tree | 7496e782c0a82a9ff5ea9b59b85e3607fd7794d6 | |
| parent | 3ac256e69f10a3bb78d394c77d03a88a72261597 (diff) | |
| parent | c0bafe9050161ea6ca0c49f54897f908ca16ce84 (diff) | |
| download | progmaweb-main.tar.gz | |
Rate-limit sign-in by visitor behind Cloudflare and stop forwarding h2c
| -rw-r--r-- | .github/workflows/codeql.yml | 4 | ||||
| -rw-r--r-- | ops/README.md | 14 | ||||
| -rw-r--r-- | ops/nginx/cloudflare-real-ip.conf | 37 | ||||
| -rw-r--r-- | ops/nginx/progmaweb.conf | 37 | ||||
| -rw-r--r-- | pnpm-lock.yaml | 38 | ||||
| -rw-r--r-- | pnpm-workspace.yaml | 8 |
6 files changed, 106 insertions, 32 deletions
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1c6d4bd..6d11636 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -35,7 +35,7 @@ jobs: if: matrix.language == 'csharp' with: dotnet-version: 10.0.x - - uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4 + - uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build_mode }} @@ -46,7 +46,7 @@ jobs: run: | dotnet restore Progmaweb.slnx --locked-mode dotnet build Progmaweb.slnx --configuration Release --no-restore - - uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4 + - uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: category: /language:${{ matrix.language }} diff --git a/ops/README.md b/ops/README.md index 3fe1b4c..0457499 100644 --- a/ops/README.md +++ b/ops/README.md @@ -104,9 +104,17 @@ could be registered by someone else. Delete the dump once the deletion is confir The Privacy Policy says that IP addresses are kept only with ViGet uploads. On both servers the `main` log format of `/etc/nginx/nginx.conf` therefore has no `$remote_addr` and no `$http_x_forwarded_for`, and `error_log` keeps only -`crit` entries, because a lower level names the client of a failed request. The change is -`ops/aisupport/nginx-no-ip.pl` (applied by hand on the first server on 2026-11-06, with a copy of the previous file -in `/root/policy-20261106`). A new site in `conf.d` uses the `main` format or one of its own without an address. +`crit` entries, because a lower level names the client of a failed request. The change was applied by hand on the +first server on 2026-11-06, with a copy of the previous file in `/root/policy-20261106`. A new site in `conf.d` uses the `main` format or one of its own without an address. + +### Visitor addresses behind Cloudflare + +Cloudflare proxies the hosts, so Nginx is reached by Cloudflare edges, and one visitor's requests arrive from +many of them. `nginx/cloudflare-real-ip.conf`, installed as `/etc/nginx/conf.d/00-cloudflare-real-ip.conf`, takes +the visitor from `CF-Connecting-IP` when, and only when, the peer is in a Cloudflare range. Without it the API +rate-limits sign-in by edge: one visitor never reaches the limit and unrelated visitors share one. Compare its ranges +with <https://www.cloudflare.com/ips/> when Cloudflare announces a change. The address is used for the limit +alone; the logs above still keep none. ### Releases and rollback diff --git a/ops/nginx/cloudflare-real-ip.conf b/ops/nginx/cloudflare-real-ip.conf new file mode 100644 index 0000000..2162842 --- /dev/null +++ b/ops/nginx/cloudflare-real-ip.conf @@ -0,0 +1,37 @@ +# SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> +# SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 + +# Installed as /etc/nginx/conf.d/00-cloudflare-real-ip.conf on the first server. +# +# Cloudflare proxies the Progmasoft, Tala and Tazel hosts, so the peer of +# Nginx is a Cloudflare edge, and each request may come from another edge. +# Without this file the address the API rate-limits by is that edge: the +# sign-in limit then never fills for one visitor and is shared by unrelated +# ones. A connection from a Cloudflare range takes the visitor from +# CF-Connecting-IP; any other peer keeps its own address, so the header cannot +# be forged by a direct connection to the server. The ranges are those of +# https://www.cloudflare.com/ips/ (checked 2026-11-09). + +set_real_ip_from 173.245.48.0/20; +set_real_ip_from 103.21.244.0/22; +set_real_ip_from 103.22.200.0/22; +set_real_ip_from 103.31.4.0/22; +set_real_ip_from 141.101.64.0/18; +set_real_ip_from 108.162.192.0/18; +set_real_ip_from 190.93.240.0/20; +set_real_ip_from 188.114.96.0/20; +set_real_ip_from 197.234.240.0/22; +set_real_ip_from 198.41.128.0/17; +set_real_ip_from 162.158.0.0/15; +set_real_ip_from 104.16.0.0/13; +set_real_ip_from 104.24.0.0/14; +set_real_ip_from 172.64.0.0/13; +set_real_ip_from 131.0.72.0/22; +set_real_ip_from 2400:cb00::/32; +set_real_ip_from 2606:4700::/32; +set_real_ip_from 2803:f800::/32; +set_real_ip_from 2405:b500::/32; +set_real_ip_from 2405:8100::/32; +set_real_ip_from 2a06:98c0::/29; +set_real_ip_from 2c0f:f248::/32; +real_ip_header CF-Connecting-IP; diff --git a/ops/nginx/progmaweb.conf b/ops/nginx/progmaweb.conf index f5bdddc..f752b80 100644 --- a/ops/nginx/progmaweb.conf +++ b/ops/nginx/progmaweb.conf @@ -1,6 +1,19 @@ # SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com> # SPDX-License-Identifier: AGPL-3.0-or-later WITH AdditionRef-Progmasoft-Patent-Grant-1.1 +# Only a WebSocket upgrade reaches the applications. Forwarding any Upgrade +# header with "Connection: upgrade" on every request would let a client ask an +# upstream for another protocol (h2c) behind Nginx. +map $http_upgrade $progmaweb_upgrade { + default ""; + ~*^websocket$ websocket; +} + +map $progmaweb_upgrade $progmaweb_connection { + default ""; + websocket upgrade; +} + server { listen 80; listen [::]:80; @@ -87,8 +100,8 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; + proxy_set_header Upgrade $progmaweb_upgrade; + proxy_set_header Connection $progmaweb_connection; proxy_read_timeout 60s; } } @@ -122,8 +135,8 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; + proxy_set_header Upgrade $progmaweb_upgrade; + proxy_set_header Connection $progmaweb_connection; proxy_read_timeout 60s; } } @@ -206,8 +219,8 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; + proxy_set_header Upgrade $progmaweb_upgrade; + proxy_set_header Connection $progmaweb_connection; proxy_read_timeout 60s; } } @@ -271,8 +284,8 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; + proxy_set_header Upgrade $progmaweb_upgrade; + proxy_set_header Connection $progmaweb_connection; proxy_read_timeout 60s; } } @@ -337,8 +350,8 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; + proxy_set_header Upgrade $progmaweb_upgrade; + proxy_set_header Connection $progmaweb_connection; proxy_read_timeout 60s; } } @@ -399,8 +412,8 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; + proxy_set_header Upgrade $progmaweb_upgrade; + proxy_set_header Connection $progmaweb_connection; proxy_read_timeout 60s; } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6d6f591..42ca720 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,8 @@ settings: overrides: brace-expansion@>=5.0.0 <5.0.12: 5.0.12 zod@<4.6.6: 4.6.7 + glob@>=9.0.0 <14.0.2: 14.0.4 + minimatch@>=7.0.0 <11.0.1: 11.0.5 importers: @@ -360,6 +362,9 @@ packages: cpu: [x64] os: [win32] + '@isaacs/nonrecursive-glob-match@1.1.0': + resolution: {integrity: sha512-GPm/Dbvk5RLmhRnxXIa7nTpK0xtl4E0wj0MslsuNiJ1FIROU+2+9lgUC/dgF7Ey4QCWhfEsgxilMRMCdHPfDfw==} + '@istanbuljs/schema@0.1.6': resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} engines: {node: '>=8'} @@ -1173,8 +1178,8 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@13.0.6: - resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + glob@14.0.4: + resolution: {integrity: sha512-mMiwNKoJZ4HAIRVJxotO6LsNRReA30gQwP9E2/p94tfLv+jygCNpKDjhhSL6OLY6LE371EZm5EAf7RpYuUqlsw==} engines: {node: 18 || 20 || >=22} globals@14.0.0: @@ -1460,9 +1465,9 @@ packages: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} - minimatch@10.2.6: - resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} - engines: {node: 18 || 20 || >=22} + minimatch@11.0.5: + resolution: {integrity: sha512-snPlVppFcYWfLLFQGhDxUyBcHNtMlWmn3jtj7pEaO+DP8pqL0IvAzXhMYmn1z9ASNtgmwLBdFxSf4tMUB0OTKA==} + engines: {node: 20 || >=22} minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} @@ -1582,8 +1587,8 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@2.0.2: - resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + path-scurry@3.0.0: + resolution: {integrity: sha512-1pTbQqH1wIwMi/eleQ5leFTTCkzFsPcgXNjwOA8YGGW/W4/kHT3dssE7Z6NH+zuqNLOzOB0uaD61yl7cAcVhgg==} engines: {node: 18 || 20 || >=22} picocolors@1.1.1: @@ -2250,6 +2255,8 @@ snapshots: '@img/sharp-win32-x64@0.35.5': optional: true + '@isaacs/nonrecursive-glob-match@1.1.0': {} + '@istanbuljs/schema@0.1.6': {} '@jridgewell/gen-mapping@0.3.13': @@ -2423,7 +2430,7 @@ snapshots: '@typescript-eslint/types': 8.68.0 '@typescript-eslint/visitor-keys': 8.68.0 debug: 4.4.3(supports-color@7.2.0) - minimatch: 10.2.6 + minimatch: 11.0.5 semver: 7.8.5 tinyglobby: 0.2.17 ts-api-utils: 2.5.0(typescript@6.0.3) @@ -3208,11 +3215,11 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@13.0.6: + glob@14.0.4: dependencies: - minimatch: 10.2.6 + minimatch: 11.0.5 minipass: 7.1.3 - path-scurry: 2.0.2 + path-scurry: 3.0.0 globals@14.0.0: {} @@ -3485,8 +3492,9 @@ snapshots: braces: 3.0.3 picomatch: 2.3.2 - minimatch@10.2.6: + minimatch@11.0.5: dependencies: + '@isaacs/nonrecursive-glob-match': 1.1.0 brace-expansion: 5.0.12 minimatch@3.1.5: @@ -3615,7 +3623,7 @@ snapshots: path-parse@1.0.7: {} - path-scurry@2.0.2: + path-scurry@3.0.0: dependencies: lru-cache: 11.5.3 minipass: 7.1.3 @@ -3908,8 +3916,8 @@ snapshots: test-exclude@8.0.0: dependencies: '@istanbuljs/schema': 0.1.6 - glob: 13.0.6 - minimatch: 10.2.6 + glob: 14.0.4 + minimatch: 11.0.5 tinyglobby@0.2.17: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ac1f58f..74a255c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -16,6 +16,14 @@ overrides: # validating oversized constrained collections (fixed in 4.6.6). The plugin # accepts any zod 4; remove this once it resolves a fixed release itself. "zod@<4.6.6": 4.6.7 + # c8 reaches glob 13 and minimatch 10 through test-exclude 8, the latest + # release. glob before 14.0.2 keeps walking the file system after its + # AbortSignal fired, and minimatch before 11.0.1 can be made to expand a + # pattern without bound. test-exclude uses only their stable glob and match + # calls, which the fixed majors keep. Remove these once test-exclude + # depends on fixed releases itself. + "glob@>=9.0.0 <14.0.2": 14.0.4 + "minimatch@>=7.0.0 <11.0.1": 11.0.5 auditConfig: ignoreGhsas: # braces <= 3.0.3: stack exhaustion on deeply nested brace patterns. No |