@stacksjs/ts-cloud 0.9.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (255) hide show
  1. package/dist/aws/index.js +1 -269
  2. package/dist/bin/chunk-08c0pzkb.js +1 -0
  3. package/dist/bin/chunk-12210qvj.js +1 -0
  4. package/dist/bin/chunk-1v6bt68w.js +6 -0
  5. package/dist/bin/chunk-35ttwn26.js +70 -0
  6. package/dist/bin/chunk-3r3be05z.js +1 -0
  7. package/dist/bin/chunk-41yqpdv7.js +2 -0
  8. package/dist/bin/chunk-5rv31chh.js +1 -0
  9. package/dist/bin/chunk-6eat8ezk.js +2 -0
  10. package/dist/bin/chunk-6gktf2m2.js +1 -0
  11. package/dist/bin/chunk-6zjy949y.js +141 -0
  12. package/dist/bin/chunk-7wzdaqh6.js +2 -0
  13. package/dist/bin/chunk-80ar3aty.js +1 -0
  14. package/dist/bin/chunk-8rwhsp5b.js +2 -0
  15. package/dist/bin/chunk-936vp7dx.js +44 -0
  16. package/dist/bin/chunk-9px3rg4a.js +4 -0
  17. package/dist/bin/chunk-anm3ae6z.js +7364 -0
  18. package/dist/bin/{chunk-ynjvbphf.js → chunk-axvr7dwa.js} +6 -6
  19. package/dist/bin/chunk-b0hj9m5e.js +2 -0
  20. package/dist/bin/{chunk-mh0kdzqp.js → chunk-b2ymh8k0.js} +1 -1
  21. package/dist/bin/chunk-b35989e3.js +50 -0
  22. package/dist/bin/chunk-c2b6nk0q.js +37 -0
  23. package/dist/bin/chunk-ckxpgwxw.js +3 -0
  24. package/dist/bin/chunk-d6zzgvtn.js +92 -0
  25. package/dist/bin/chunk-da8zpf82.js +1 -0
  26. package/dist/bin/chunk-ddmjjstg.js +1 -0
  27. package/dist/bin/chunk-dhhz4zyj.js +1 -0
  28. package/dist/bin/chunk-dj9a67c3.js +9 -0
  29. package/dist/bin/chunk-ds1z880f.js +1 -0
  30. package/dist/bin/chunk-dvkj4k6h.js +111 -0
  31. package/dist/bin/chunk-eh40fpvj.js +1 -0
  32. package/dist/bin/chunk-ey9fagrr.js +6 -0
  33. package/dist/bin/chunk-ezftw2q4.js +1 -0
  34. package/dist/bin/chunk-g6a4n3rh.js +1 -0
  35. package/dist/bin/chunk-g90nqfgv.js +585 -0
  36. package/dist/bin/chunk-gs40j75w.js +1 -0
  37. package/dist/bin/chunk-jjjts1vt.js +1 -0
  38. package/dist/bin/{chunk-ry030a6b.js → chunk-jnsbzqzz.js} +30 -30
  39. package/dist/bin/chunk-jnx4jgw8.js +1 -0
  40. package/dist/bin/chunk-jt1tndm2.js +3 -0
  41. package/dist/bin/chunk-kybdb0ws.js +2 -0
  42. package/dist/bin/chunk-m3nqw76h.js +3 -0
  43. package/dist/bin/chunk-mn6j22zn.js +6 -0
  44. package/dist/bin/chunk-nb0yjkv5.js +2 -0
  45. package/dist/bin/chunk-pjpa4x52.js +2 -0
  46. package/dist/bin/chunk-pkgkfe6s.js +2 -0
  47. package/dist/bin/chunk-pnz5hk8n.js +60 -0
  48. package/dist/bin/chunk-q46zr2tv.js +1 -0
  49. package/dist/bin/chunk-qtdngvs8.js +1 -0
  50. package/dist/bin/chunk-sbejksrh.js +2 -0
  51. package/dist/bin/chunk-svyd4v6c.js +34 -0
  52. package/dist/bin/chunk-tna7ay8d.js +1 -0
  53. package/dist/bin/chunk-tp4c2mv1.js +1 -0
  54. package/dist/bin/chunk-w5d6tr2x.js +1 -0
  55. package/dist/bin/chunk-wewcjam5.js +1 -0
  56. package/dist/bin/chunk-xv4bscrm.js +1 -0
  57. package/dist/bin/chunk-y9eyc5pw.js +1 -0
  58. package/dist/bin/chunk-y9f8649w.js +512 -0
  59. package/dist/bin/cli.js +428 -418
  60. package/dist/bin/dashboard-server.js +1 -1
  61. package/dist/chunk-06jk7ba9.js +7355 -0
  62. package/dist/chunk-0e16qt6k.js +5 -0
  63. package/dist/chunk-13hxhzaz.js +34 -0
  64. package/dist/chunk-2p89vt3b.js +2 -0
  65. package/dist/chunk-77t2ctwn.js +2 -0
  66. package/dist/chunk-88z37ey4.js +114 -0
  67. package/dist/chunk-8c4715hz.js +2 -0
  68. package/dist/chunk-9a22225r.js +60 -0
  69. package/dist/chunk-as6by62d.js +1 -0
  70. package/dist/chunk-cd0zpxhf.js +44 -0
  71. package/dist/chunk-cp54a0qn.js +6 -0
  72. package/dist/chunk-d67xbk4v.js +141 -0
  73. package/dist/chunk-dckz9xve.js +2 -0
  74. package/dist/chunk-e1666vjv.js +2 -0
  75. package/dist/chunk-hhdx5k96.js +442 -0
  76. package/dist/chunk-kv2ztkp4.js +1617 -0
  77. package/dist/chunk-nrz6zypg.js +1 -0
  78. package/dist/chunk-q816js1y.js +1 -0
  79. package/dist/chunk-qh9ep11z.js +704 -0
  80. package/dist/chunk-rg720cqg.js +1 -0
  81. package/dist/chunk-snak8s7m.js +92 -0
  82. package/dist/chunk-vq737g82.js +1 -0
  83. package/dist/chunk-xd6w0c11.js +50 -0
  84. package/dist/chunk-xtwjt370.js +37 -0
  85. package/dist/chunk-y26yh5zp.js +1 -0
  86. package/dist/chunk-zmxffmmq.js +4 -0
  87. package/dist/deploy/attach-credentials.d.ts +4 -8
  88. package/dist/deploy/index.js +1 -168
  89. package/dist/dns/cloudflare.d.ts +27 -0
  90. package/dist/dns/godaddy.d.ts +18 -1
  91. package/dist/dns/index.d.ts +2 -0
  92. package/dist/dns/index.js +1 -41
  93. package/dist/dns/zone-migration.d.ts +92 -0
  94. package/dist/dns/zone-migration.test.d.ts +1 -0
  95. package/dist/drivers/hetzner/driver.d.ts +8 -1
  96. package/dist/drivers/index.js +1 -154
  97. package/dist/drivers/shared/compute-provision.d.ts +14 -2
  98. package/dist/drivers/shared/db-provision.d.ts +3 -2
  99. package/dist/drivers/shared/mail-provision.d.ts +116 -0
  100. package/dist/drivers/shared/mail-provision.test.d.ts +1 -0
  101. package/dist/drivers/shared/managed-services-probe.d.ts +47 -0
  102. package/dist/drivers/shared/managed-services-probe.test.d.ts +1 -0
  103. package/dist/drivers/shared/package-manager.d.ts +1 -0
  104. package/dist/drivers/shared/remote-failure.d.ts +21 -0
  105. package/dist/drivers/shared/remote-failure.test.d.ts +1 -0
  106. package/dist/drivers/shared/sqlite-shared-path.d.ts +50 -0
  107. package/dist/drivers/shared/sqlite-shared-path.test.d.ts +1 -0
  108. package/dist/index.d.ts +2 -0
  109. package/dist/index.js +100 -6129
  110. package/dist/operations/plan.d.ts +136 -0
  111. package/dist/operations/plan.test.d.ts +1 -0
  112. package/dist/operations/server-rename.d.ts +84 -0
  113. package/dist/operations/server-rename.test.d.ts +1 -0
  114. package/dist/operations/site-move.d.ts +263 -0
  115. package/dist/operations/site-move.test.d.ts +1 -0
  116. package/dist/protection/index.js +1 -89
  117. package/dist/push/index.js +1 -509
  118. package/dist/spend/index.js +1 -173
  119. package/dist/ui/404.html +1 -1
  120. package/dist/ui/accept-invitation.html +1 -1
  121. package/dist/ui/access-denied.html +1 -1
  122. package/dist/ui/account/automation.html +5 -5
  123. package/dist/ui/account/security.html +2 -2
  124. package/dist/ui/applications/compose.html +6 -6
  125. package/dist/ui/applications/new.html +2 -2
  126. package/dist/ui/data/backups.html +80 -80
  127. package/dist/ui/data/services.html +78 -78
  128. package/dist/ui/data/volumes.html +50 -50
  129. package/dist/ui/index.html +50 -50
  130. package/dist/ui/infrastructure/topology.html +118 -118
  131. package/dist/ui/integrations.html +1 -1
  132. package/dist/ui/operations/alerts.html +47 -47
  133. package/dist/ui/operations/configuration.html +5 -5
  134. package/dist/ui/operations/jobs.html +76 -76
  135. package/dist/ui/operations/maintenance.html +6 -6
  136. package/dist/ui/operations/observability.html +82 -82
  137. package/dist/ui/operations/previews.html +5 -5
  138. package/dist/ui/operations/queue.html +6 -6
  139. package/dist/ui/operations/regions.html +6 -6
  140. package/dist/ui/operations/releases.html +6 -6
  141. package/dist/ui/operations/spend.html +54 -54
  142. package/dist/ui/operations/workloads.html +104 -104
  143. package/dist/ui/security.html +2 -2
  144. package/dist/ui/server/actions.html +56 -56
  145. package/dist/ui/server/activity.html +1 -1
  146. package/dist/ui/server/capacity.html +6 -6
  147. package/dist/ui/server/database.html +106 -106
  148. package/dist/ui/server/deployments.html +50 -50
  149. package/dist/ui/server/diagnostics.html +2 -2
  150. package/dist/ui/server/firewall.html +76 -76
  151. package/dist/ui/server/fleet.html +34 -34
  152. package/dist/ui/server/logs.html +24 -24
  153. package/dist/ui/server/metrics.html +112 -112
  154. package/dist/ui/server/security.html +1 -1
  155. package/dist/ui/server/services.html +27 -27
  156. package/dist/ui/server/sites.html +120 -120
  157. package/dist/ui/server/ssh-keys.html +51 -51
  158. package/dist/ui/server/team.html +144 -144
  159. package/dist/ui/server/terminal.html +15 -15
  160. package/dist/ui/serverless/alarms.html +52 -52
  161. package/dist/ui/serverless/assets.html +27 -27
  162. package/dist/ui/serverless/cost.html +2 -2
  163. package/dist/ui/serverless/data.html +36 -36
  164. package/dist/ui/serverless/deployments.html +27 -27
  165. package/dist/ui/serverless/firewall.html +1 -1
  166. package/dist/ui/serverless/functions.html +40 -40
  167. package/dist/ui/serverless/logs.html +24 -24
  168. package/dist/ui/serverless/metrics.html +2 -2
  169. package/dist/ui/serverless/queues.html +47 -47
  170. package/dist/ui/serverless/secrets.html +52 -52
  171. package/dist/ui/serverless/traces.html +6 -6
  172. package/dist/ui/serverless.html +54 -54
  173. package/dist/ui-src/src/charts.ts +17499 -12662
  174. package/dist/ui-src/src/topology.ts +2 -2
  175. package/package.json +3 -3
  176. package/dist/bin/chunk-09nwf2gy.js +0 -34
  177. package/dist/bin/chunk-14cwr6eh.js +0 -70
  178. package/dist/bin/chunk-271gk1n2.js +0 -6
  179. package/dist/bin/chunk-38pq971s.js +0 -2
  180. package/dist/bin/chunk-3gykfyq4.js +0 -37
  181. package/dist/bin/chunk-3r4fb59r.js +0 -3
  182. package/dist/bin/chunk-41x17hw1.js +0 -1
  183. package/dist/bin/chunk-4vrqm9h0.js +0 -2
  184. package/dist/bin/chunk-57y1zxbf.js +0 -3
  185. package/dist/bin/chunk-598yf4bs.js +0 -1
  186. package/dist/bin/chunk-8v7ddyvf.js +0 -1
  187. package/dist/bin/chunk-92mea34a.js +0 -1
  188. package/dist/bin/chunk-9pvsff8g.js +0 -1
  189. package/dist/bin/chunk-9v39615t.js +0 -1
  190. package/dist/bin/chunk-avm7tnwe.js +0 -1
  191. package/dist/bin/chunk-ax9mr6wm.js +0 -44
  192. package/dist/bin/chunk-axhnkn5g.js +0 -2
  193. package/dist/bin/chunk-b5gd39s8.js +0 -512
  194. package/dist/bin/chunk-b9qbp8j1.js +0 -1
  195. package/dist/bin/chunk-bz828v8q.js +0 -3
  196. package/dist/bin/chunk-c1vcdgvr.js +0 -1
  197. package/dist/bin/chunk-ctd2vg9w.js +0 -2
  198. package/dist/bin/chunk-dbgffky8.js +0 -1
  199. package/dist/bin/chunk-dwr30vbv.js +0 -1
  200. package/dist/bin/chunk-e3xrwh9z.js +0 -1
  201. package/dist/bin/chunk-fhc6ntvd.js +0 -2
  202. package/dist/bin/chunk-fpm59y25.js +0 -6
  203. package/dist/bin/chunk-fvbxvq6b.js +0 -1
  204. package/dist/bin/chunk-fy8j3y8p.js +0 -92
  205. package/dist/bin/chunk-fz21b57w.js +0 -1
  206. package/dist/bin/chunk-gavvf0pg.js +0 -141
  207. package/dist/bin/chunk-ggn7ax8p.js +0 -4
  208. package/dist/bin/chunk-gpe2prkm.js +0 -1
  209. package/dist/bin/chunk-gqf6jsmy.js +0 -1
  210. package/dist/bin/chunk-h5pa5men.js +0 -2
  211. package/dist/bin/chunk-hm08xdxf.js +0 -2
  212. package/dist/bin/chunk-hzgz13nf.js +0 -1
  213. package/dist/bin/chunk-hzqw8xm5.js +0 -111
  214. package/dist/bin/chunk-j703btc5.js +0 -50
  215. package/dist/bin/chunk-jpjamjey.js +0 -6935
  216. package/dist/bin/chunk-m2zwtapr.js +0 -1
  217. package/dist/bin/chunk-m669wtgr.js +0 -1
  218. package/dist/bin/chunk-mr0atd2s.js +0 -2
  219. package/dist/bin/chunk-ngcyftm2.js +0 -1
  220. package/dist/bin/chunk-p66ptpmr.js +0 -2
  221. package/dist/bin/chunk-p8q9bq9g.js +0 -1
  222. package/dist/bin/chunk-r3kv0vm9.js +0 -6
  223. package/dist/bin/chunk-r9hjwcj9.js +0 -6
  224. package/dist/bin/chunk-rtczra13.js +0 -1
  225. package/dist/bin/chunk-rytk89az.js +0 -60
  226. package/dist/bin/chunk-sgyffpgm.js +0 -581
  227. package/dist/bin/chunk-vek8a0w6.js +0 -1
  228. package/dist/bin/chunk-waayf9ay.js +0 -2
  229. package/dist/bin/chunk-yv9sth30.js +0 -1
  230. package/dist/chunk-01d86gt1.js +0 -1324
  231. package/dist/chunk-050r7ycy.js +0 -46516
  232. package/dist/chunk-1c8dbv57.js +0 -7278
  233. package/dist/chunk-29rse4p1.js +0 -12203
  234. package/dist/chunk-32e7ya18.js +0 -642
  235. package/dist/chunk-4cjrg98a.js +0 -597
  236. package/dist/chunk-50jpda9q.js +0 -371
  237. package/dist/chunk-6a1t7fnq.js +0 -12121
  238. package/dist/chunk-728jvrr0.js +0 -1701
  239. package/dist/chunk-7m60qnc8.js +0 -1762
  240. package/dist/chunk-aa2dkn7b.js +0 -205
  241. package/dist/chunk-d48vtdj4.js +0 -433
  242. package/dist/chunk-f55nkpvb.js +0 -8
  243. package/dist/chunk-fgk8cqew.js +0 -152
  244. package/dist/chunk-he9a874b.js +0 -769
  245. package/dist/chunk-k2yyatfw.js +0 -10
  246. package/dist/chunk-k4px0bnv.js +0 -23
  247. package/dist/chunk-nsgqsmrh.js +0 -1779
  248. package/dist/chunk-q0st3se8.js +0 -42033
  249. package/dist/chunk-qr803fye.js +0 -2122
  250. package/dist/chunk-rds0sy87.js +0 -1771
  251. package/dist/chunk-ttz2ra5j.js +0 -12
  252. package/dist/chunk-v0bahtg2.js +0 -4
  253. package/dist/chunk-va2yd85b.js +0 -200
  254. package/dist/chunk-x9s0cqfx.js +0 -13
  255. package/dist/chunk-zqtpg06c.js +0 -1752
@@ -1,205 +0,0 @@
1
- // src/protection/ddos.ts
2
- var DEFAULT_DDOS_THRESHOLDS = {
3
- newConnectionsPerSecond: 50,
4
- concurrentPerSource: 100,
5
- synPerSecond: 2000,
6
- burst: 100,
7
- icmpPerSecond: 5,
8
- banSeconds: 600
9
- };
10
- function resolveThresholds(config) {
11
- return { ...DEFAULT_DDOS_THRESHOLDS, ...config.thresholds };
12
- }
13
- var CIDR_PATTERN = /^(?:\d{1,3}\.){3}\d{1,3}(?:\/\d{1,2})?$|^[0-9a-f:]+(?:\/\d{1,3})?$/i;
14
- function isValidCidr(value) {
15
- if (!CIDR_PATTERN.test(value))
16
- return false;
17
- const [address, prefix] = value.split("/");
18
- if (address.includes(":"))
19
- return prefix == null || Number(prefix) >= 0 && Number(prefix) <= 128;
20
- const octets = address.split(".");
21
- if (octets.length !== 4 || octets.some((octet) => Number(octet) > 255 || octet === ""))
22
- return false;
23
- return prefix == null || Number(prefix) >= 0 && Number(prefix) <= 32;
24
- }
25
- function validCidrs(values) {
26
- return [...new Set((values ?? []).map((value) => value.trim()).filter(isValidCidr))];
27
- }
28
- function validPorts(values) {
29
- const ports = (values ?? [80, 443]).filter((port) => Number.isInteger(port) && port >= 1 && port <= 65535);
30
- return [...new Set(ports)].sort((a, b) => a - b);
31
- }
32
- function sysctlHardening(config = {}) {
33
- const thresholds = resolveThresholds(config);
34
- return {
35
- "net.ipv4.tcp_syncookies": "1",
36
- "net.ipv4.tcp_max_syn_backlog": String(Math.max(4096, thresholds.synPerSecond * 4)),
37
- "net.ipv4.tcp_synack_retries": "2",
38
- "net.ipv4.tcp_abort_on_overflow": "1",
39
- "net.ipv4.tcp_fin_timeout": "15",
40
- "net.ipv4.tcp_keepalive_time": "300",
41
- "net.ipv4.tcp_keepalive_intvl": "30",
42
- "net.ipv4.tcp_keepalive_probes": "3",
43
- "net.core.somaxconn": "4096",
44
- "net.core.netdev_max_backlog": "16384",
45
- "net.ipv4.conf.all.rp_filter": "1",
46
- "net.ipv4.conf.default.rp_filter": "1",
47
- "net.ipv4.conf.all.accept_source_route": "0",
48
- "net.ipv4.conf.all.accept_redirects": "0",
49
- "net.ipv4.conf.all.send_redirects": "0",
50
- "net.ipv6.conf.all.accept_redirects": "0",
51
- "net.ipv6.conf.all.accept_source_route": "0",
52
- "net.ipv4.icmp_echo_ignore_broadcasts": "1",
53
- "net.ipv4.icmp_ignore_bogus_error_responses": "1",
54
- "net.netfilter.nf_conntrack_max": "262144",
55
- "net.netfilter.nf_conntrack_tcp_timeout_established": "3600"
56
- };
57
- }
58
- function renderSysctlFile(config = {}) {
59
- const lines = [
60
- "# Managed by ts-cloud. Edits are overwritten on the next deploy.",
61
- "# Network hardening against L3/L4 floods.",
62
- ""
63
- ];
64
- for (const [key, value] of Object.entries(sysctlHardening(config)))
65
- lines.push(`${key} = ${value}`);
66
- return `${lines.join(`
67
- `)}
68
- `;
69
- }
70
- function renderNftablesRuleset(config = {}) {
71
- const thresholds = resolveThresholds(config);
72
- const ports = validPorts(config.ports);
73
- const allowlist = validCidrs(config.allowlist);
74
- const blocklist = validCidrs(config.blocklist);
75
- const verdict = config.monitorOnly ? "counter" : "counter drop";
76
- const cdnRanges = config.originProtection?.enabled ? validCidrs(config.originProtection.cdnRanges) : [];
77
- const ipv4Allow = allowlist.filter((cidr) => !cidr.includes(":"));
78
- const ipv6Allow = allowlist.filter((cidr) => cidr.includes(":"));
79
- const ipv4Block = blocklist.filter((cidr) => !cidr.includes(":"));
80
- const lines = [
81
- "#!/usr/sbin/nft -f",
82
- "# Managed by ts-cloud. Edits are overwritten on the next deploy.",
83
- "",
84
- "table inet ts_cloud_ddos",
85
- "delete table inet ts_cloud_ddos",
86
- "",
87
- "table inet ts_cloud_ddos {",
88
- " # Sources that tripped a limit, with a timeout so a ban self-expires.",
89
- ` set banned4 { type ipv4_addr; flags timeout; timeout ${thresholds.banSeconds}s; }`,
90
- ` set banned6 { type ipv6_addr; flags timeout; timeout ${thresholds.banSeconds}s; }`
91
- ];
92
- if (ipv4Allow.length > 0)
93
- lines.push(` set allow4 { type ipv4_addr; flags interval; elements = { ${ipv4Allow.join(", ")} } }`);
94
- if (ipv6Allow.length > 0)
95
- lines.push(` set allow6 { type ipv6_addr; flags interval; elements = { ${ipv6Allow.join(", ")} } }`);
96
- if (ipv4Block.length > 0)
97
- lines.push(` set block4 { type ipv4_addr; flags interval; elements = { ${ipv4Block.join(", ")} } }`);
98
- if (cdnRanges.length > 0)
99
- lines.push(` set cdn4 { type ipv4_addr; flags interval; elements = { ${cdnRanges.filter((cidr) => !cidr.includes(":")).join(", ")} } }`);
100
- lines.push("", " chain input {", " type filter hook input priority -150; policy accept;", "", " # Established traffic is already paid for; never re-inspect it.", " ct state established,related accept", " iif lo accept", "", " # Invalid packets are never legitimate and are cheap to drop early.", " ct state invalid counter drop");
101
- if (ipv4Allow.length > 0)
102
- lines.push(" ip saddr @allow4 accept");
103
- if (ipv6Allow.length > 0)
104
- lines.push(" ip6 saddr @allow6 accept");
105
- if (ipv4Block.length > 0)
106
- lines.push(" ip saddr @block4 counter drop");
107
- lines.push(" ip saddr @banned4 counter drop", " ip6 saddr @banned6 counter drop", "");
108
- lines.push(" # Nonsense TCP flag combinations: scanners and stack fingerprinting.", " tcp flags & (fin|syn) == (fin|syn) counter drop", " tcp flags & (syn|rst) == (syn|rst) counter drop", " tcp flags & (fin|rst) == (fin|rst) counter drop", " tcp flags & (fin|ack) == fin counter drop", " tcp flags & (syn|fin|ack|rst|psh|urg) == 0x0 counter drop", "", ` # Ping stays usable, ping floods do not.`, ` ip protocol icmp icmp type echo-request limit rate ${thresholds.icmpPerSecond}/second burst ${thresholds.burst} packets accept`, " ip protocol icmp icmp type echo-request counter drop", "");
109
- if (ports.length > 0) {
110
- const portSet = ports.join(", ");
111
- lines.push(` # Platform-wide SYN ceiling. Above it the kernel falls back to cookies.`, ` tcp dport { ${portSet} } ct state new limit rate ${thresholds.synPerSecond}/second burst ${thresholds.burst} packets accept`, "", ` # Per-source new-connection rate. This is what stops one host hammering.`, ` tcp dport { ${portSet} } ct state new meter conn_rate4 { ip saddr limit rate over ${thresholds.newConnectionsPerSecond}/second burst ${thresholds.burst} packets } add @banned4 { ip saddr } ${verdict}`, ` tcp dport { ${portSet} } ct state new meter conn_rate6 { ip6 saddr limit rate over ${thresholds.newConnectionsPerSecond}/second burst ${thresholds.burst} packets } add @banned6 { ip6 saddr } ${verdict}`, "", ` # Concurrency ceiling per source: the slow-loris defence.`, ` tcp dport { ${portSet} } ct state new meter conn_count4 { ip saddr ct count over ${thresholds.concurrentPerSource} } ${verdict}`, ` tcp dport { ${portSet} } ct state new meter conn_count6 { ip6 saddr ct count over ${thresholds.concurrentPerSource} } ${verdict}`, "");
112
- if (cdnRanges.length > 0)
113
- lines.push(" # Origin protection: reject traffic that skipped the CDN and came", " # straight to the box, which is how an attacker bypasses the edge.", ` tcp dport { ${portSet} } ip saddr != @cdn4 counter drop`, "");
114
- }
115
- lines.push(" }", "}", "");
116
- return lines.join(`
117
- `);
118
- }
119
- function renderDdosInstallScript(config = {}) {
120
- return `#!/usr/bin/env bash
121
- # Managed by ts-cloud. Installs L3/L4 flood mitigation.
122
- set -euo pipefail
123
-
124
- if ! command -v nft >/dev/null 2>&1; then
125
- export DEBIAN_FRONTEND=noninteractive
126
- apt-get update -qq
127
- apt-get install -y -qq nftables
128
- fi
129
-
130
- install -d -m 0755 /etc/ts-cloud /etc/nftables.d
131
- cat > /etc/sysctl.d/90-ts-cloud-ddos.conf <<'SYSCTL_EOF'
132
- ${renderSysctlFile(config)}SYSCTL_EOF
133
- sysctl --system >/dev/null
134
-
135
- cat > /etc/nftables.d/ts-cloud-ddos.nft.new <<'NFT_EOF'
136
- ${renderNftablesRuleset(config)}NFT_EOF
137
-
138
- # Syntax-check before applying: a bad ruleset would leave the box unprotected.
139
- if ! nft -c -f /etc/nftables.d/ts-cloud-ddos.nft.new; then
140
- echo "ts-cloud: generated nftables ruleset failed validation, keeping the previous one" >&2
141
- rm -f /etc/nftables.d/ts-cloud-ddos.nft.new
142
- exit 1
143
- fi
144
-
145
- mv /etc/nftables.d/ts-cloud-ddos.nft.new /etc/nftables.d/ts-cloud-ddos.nft
146
- nft -f /etc/nftables.d/ts-cloud-ddos.nft
147
-
148
- # Applying the ruleset does not survive a reboot on its own: nftables.service
149
- # loads /etc/nftables.conf, so the file has to be included from there or the box
150
- # comes back up unprotected and nothing says so.
151
- touch /etc/nftables.conf
152
- if ! grep -q 'ts-cloud-ddos.nft' /etc/nftables.conf; then
153
- printf '
154
- include "/etc/nftables.d/ts-cloud-ddos.nft"
155
- ' >> /etc/nftables.conf
156
- fi
157
- systemctl enable nftables >/dev/null 2>&1 || true
158
- echo "ts-cloud: L3/L4 mitigation active"
159
- `;
160
- }
161
- var LEVEL_ORDER = ["off", "monitor", "rate_limit", "challenge", "lockdown"];
162
- function planMitigation(signals, topSources = []) {
163
- const reasons = [];
164
- const baseline = Math.max(1, signals.baselineRequestsPerSecond);
165
- const ratio = signals.requestsPerSecond / baseline;
166
- let levelIndex = 0;
167
- const escalate = (next, reason) => {
168
- reasons.push(reason);
169
- levelIndex = Math.max(levelIndex, LEVEL_ORDER.indexOf(next));
170
- };
171
- const surging = ratio >= 3;
172
- if (surging)
173
- escalate("monitor", `Traffic is ${ratio.toFixed(1)}x the baseline.`);
174
- const concentrated = signals.topSourceShare >= 0.25;
175
- const failing = signals.errorRate >= 0.25;
176
- const scripted = (signals.suspiciousAgentShare ?? 0) >= 0.5;
177
- const thinlySpread = signals.uniqueSources > 0 && signals.requestsPerSecond / signals.uniqueSources >= 50;
178
- if (surging && concentrated)
179
- escalate("rate_limit", `One source accounts for ${(signals.topSourceShare * 100).toFixed(0)}% of traffic.`);
180
- if (surging && thinlySpread)
181
- escalate("rate_limit", "Request rate per unique source is far above what a browser produces.");
182
- if (surging && failing)
183
- escalate("challenge", `Error rate is ${(signals.errorRate * 100).toFixed(0)}% under load.`);
184
- if (surging && scripted)
185
- escalate("challenge", "Most requests carry no recognisable browser agent.");
186
- if (ratio >= 20 && (concentrated || failing || scripted || thinlySpread))
187
- escalate("lockdown", `Traffic is ${ratio.toFixed(0)}x the baseline with attack-shaped characteristics.`);
188
- if (!surging && signals.topSourceShare >= 0.6 && signals.requestsPerSecond > baseline)
189
- escalate("rate_limit", "A single source dominates traffic without an overall surge.");
190
- const level = LEVEL_ORDER[levelIndex];
191
- const factor = { off: 1, monitor: 1, rate_limit: 0.5, challenge: 0.25, lockdown: 0.1 }[level];
192
- return {
193
- level,
194
- reasons,
195
- rateLimitFactor: factor,
196
- challengeEnabled: level === "challenge" || level === "lockdown",
197
- staticOnly: level === "lockdown",
198
- banCandidates: level === "off" || level === "monitor" ? [] : validCidrs(topSources)
199
- };
200
- }
201
- function applyMitigationFactor(limit, plan) {
202
- return Math.max(1, Math.floor(limit * plan.rateLimitFactor));
203
- }
204
-
205
- export { DEFAULT_DDOS_THRESHOLDS, isValidCidr, sysctlHardening, renderSysctlFile, renderNftablesRuleset, renderDdosInstallScript, planMitigation, applyMitigationFactor };
@@ -1,433 +0,0 @@
1
- import {
2
- isValidCidr
3
- } from "./chunk-aa2dkn7b.js";
4
- // src/protection/ratelimit.ts
5
- var RATE_LIMIT_KEY_SEPARATOR = "\x00";
6
- var GLOB_PLACEHOLDER = "\x01";
7
- function globMatches(pattern, value) {
8
- if (pattern === "*" || pattern === "**")
9
- return true;
10
- const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, GLOB_PLACEHOLDER).replace(/\*/g, "[^/]*").replace(/\u0001/g, ".*");
11
- return new RegExp(`^${escaped}$`).test(value);
12
- }
13
- function hostMatches(pattern, host) {
14
- if (pattern.startsWith("*.")) {
15
- const suffix = pattern.slice(1);
16
- return host.endsWith(suffix) && host.length > suffix.length;
17
- }
18
- return pattern === host;
19
- }
20
- function ruleMatches(rule, request) {
21
- if (rule.enabled === false)
22
- return false;
23
- if (rule.methods?.length && !rule.methods.some((method) => method.toUpperCase() === request.method.toUpperCase()))
24
- return false;
25
- if (rule.path && !globMatches(rule.path, request.path))
26
- return false;
27
- if (rule.host && !hostMatches(rule.host, request.host ?? ""))
28
- return false;
29
- return true;
30
- }
31
- function rateLimitKey(rule, request) {
32
- const spec = rule.key ?? { source: "ip" };
33
- const fallback = spec.fallbackToIp !== false;
34
- let identity;
35
- if (spec.source === "ip")
36
- identity = request.ip;
37
- else if (spec.source === "global")
38
- identity = "global";
39
- else if (spec.source === "host")
40
- identity = request.host ?? "";
41
- else if (spec.source === "path")
42
- identity = request.path;
43
- else if (spec.source === "header")
44
- identity = spec.name ? request.headers?.[spec.name.toLowerCase()] : undefined;
45
- else if (spec.source === "cookie")
46
- identity = spec.name ? request.cookies?.[spec.name] : undefined;
47
- if (!identity && spec.source !== "global")
48
- identity = fallback ? request.ip : "__missing__";
49
- return `${rule.id}${RATE_LIMIT_KEY_SEPARATOR}${identity}`;
50
- }
51
-
52
- class RateLimiter {
53
- clock;
54
- maxTrackedKeys;
55
- buckets = new Map;
56
- windows = new Map;
57
- rules;
58
- constructor(rules, clock = () => Date.now(), maxTrackedKeys = 1e5) {
59
- this.clock = clock;
60
- this.maxTrackedKeys = maxTrackedKeys;
61
- this.rules = [...rules].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));
62
- }
63
- check(request) {
64
- const now = this.clock();
65
- const matching = this.rules.filter((rule) => ruleMatches(rule, request));
66
- let blocked;
67
- for (const rule of matching) {
68
- const test = this.test(rule, request, now);
69
- if (test.allowed)
70
- continue;
71
- if (!blocked || severity(test.action) > severity(blocked.action))
72
- blocked = test;
73
- }
74
- if (blocked)
75
- return blocked;
76
- let tightest = { action: "allow", allowed: true };
77
- for (const rule of matching) {
78
- const decision = this.consume(rule, request, now);
79
- if (decision.remaining != null && (tightest.remaining ?? Infinity) > decision.remaining)
80
- tightest = decision;
81
- }
82
- return tightest;
83
- }
84
- test(rule, request, now) {
85
- const key = rateLimitKey(rule, request);
86
- if ((rule.algorithm ?? "token_bucket") === "token_bucket") {
87
- const capacity = rule.burst ?? rule.limit;
88
- const state = this.buckets.get(key);
89
- const tokens = state ? Math.min(capacity, state.tokens + this.refill(rule, now - state.updatedAt)) : capacity;
90
- if (tokens >= 1)
91
- return { action: "allow", allowed: true };
92
- return this.blocked(rule, key, 0, this.msPerToken(rule) * (1 - tokens), now);
93
- }
94
- const hits = (this.windows.get(key)?.hits ?? []).filter((at) => at > now - rule.windowMs);
95
- if (hits.length < rule.limit)
96
- return { action: "allow", allowed: true };
97
- return this.blocked(rule, key, 0, hits[0] + rule.windowMs - now, now);
98
- }
99
- peek(request) {
100
- const now = this.clock();
101
- for (const rule of this.rules) {
102
- if (!ruleMatches(rule, request))
103
- continue;
104
- const decision = this.test(rule, request, now);
105
- if (!decision.allowed)
106
- return decision;
107
- }
108
- return { action: "allow", allowed: true };
109
- }
110
- msPerToken(rule) {
111
- return rule.windowMs / Math.max(1, rule.limit);
112
- }
113
- refill(rule, elapsedMs) {
114
- return Math.max(0, elapsedMs) / this.msPerToken(rule);
115
- }
116
- blocked(rule, key, remaining, retryAfterMs, now) {
117
- const action = rule.action ?? "deny";
118
- return {
119
- action,
120
- allowed: action === "allow" || action === "log",
121
- ruleId: rule.id,
122
- limit: rule.limit,
123
- remaining,
124
- retryAfterMs: Math.max(0, Math.ceil(retryAfterMs)),
125
- resetAt: now + Math.max(0, retryAfterMs),
126
- key
127
- };
128
- }
129
- consume(rule, request, now) {
130
- const key = rateLimitKey(rule, request);
131
- const algorithm = rule.algorithm ?? "token_bucket";
132
- if (algorithm === "token_bucket") {
133
- const capacity = rule.burst ?? rule.limit;
134
- const state2 = this.buckets.get(key) ?? { tokens: capacity, updatedAt: now };
135
- const tokens = Math.min(capacity, state2.tokens + this.refill(rule, now - state2.updatedAt));
136
- if (tokens < 1) {
137
- this.buckets.set(key, { tokens, updatedAt: now });
138
- return this.blocked(rule, key, 0, this.msPerToken(rule) * (1 - tokens), now);
139
- }
140
- this.evictIfNeeded(this.buckets);
141
- this.buckets.set(key, { tokens: tokens - 1, updatedAt: now });
142
- return {
143
- action: "allow",
144
- allowed: true,
145
- ruleId: rule.id,
146
- limit: capacity,
147
- remaining: Math.floor(tokens - 1),
148
- resetAt: now + this.msPerToken(rule) * (capacity - (tokens - 1)),
149
- key
150
- };
151
- }
152
- const state = this.windows.get(key) ?? { hits: [] };
153
- const cutoff = now - rule.windowMs;
154
- const hits = state.hits.filter((at) => at > cutoff);
155
- if (hits.length >= rule.limit) {
156
- this.windows.set(key, { hits });
157
- return this.blocked(rule, key, 0, hits[0] + rule.windowMs - now, now);
158
- }
159
- hits.push(now);
160
- this.evictIfNeeded(this.windows);
161
- this.windows.set(key, { hits });
162
- return {
163
- action: "allow",
164
- allowed: true,
165
- ruleId: rule.id,
166
- limit: rule.limit,
167
- remaining: rule.limit - hits.length,
168
- resetAt: hits[0] + rule.windowMs,
169
- key
170
- };
171
- }
172
- evictIfNeeded(map) {
173
- if (map.size < this.maxTrackedKeys)
174
- return;
175
- const excess = map.size - this.maxTrackedKeys + 1;
176
- let removed = 0;
177
- for (const key of map.keys()) {
178
- map.delete(key);
179
- if (++removed >= excess)
180
- break;
181
- }
182
- }
183
- sweep() {
184
- const now = this.clock();
185
- let removed = 0;
186
- for (const [key, state] of this.windows) {
187
- const ruleId = key.split(RATE_LIMIT_KEY_SEPARATOR)[0];
188
- const rule = this.rules.find((candidate) => candidate.id === ruleId);
189
- if (!rule) {
190
- this.windows.delete(key);
191
- removed++;
192
- continue;
193
- }
194
- if (state.hits.every((at) => at <= now - rule.windowMs)) {
195
- this.windows.delete(key);
196
- removed++;
197
- }
198
- }
199
- for (const [key, state] of this.buckets) {
200
- const ruleId = key.split(RATE_LIMIT_KEY_SEPARATOR)[0];
201
- const rule = this.rules.find((candidate) => candidate.id === ruleId);
202
- if (!rule || now - state.updatedAt > rule.windowMs * 2) {
203
- this.buckets.delete(key);
204
- removed++;
205
- }
206
- }
207
- return removed;
208
- }
209
- get trackedKeys() {
210
- return this.buckets.size + this.windows.size;
211
- }
212
- }
213
- function severity(action) {
214
- return { allow: 0, log: 1, throttle: 2, challenge: 3, deny: 4 }[action];
215
- }
216
- function rateLimitHeaders(decision) {
217
- const headers = {};
218
- if (decision.limit != null)
219
- headers["ratelimit-limit"] = String(decision.limit);
220
- if (decision.remaining != null)
221
- headers["ratelimit-remaining"] = String(decision.remaining);
222
- if (decision.resetAt != null)
223
- headers["ratelimit-reset"] = String(Math.max(0, Math.ceil((decision.resetAt - Date.now()) / 1000)));
224
- if (!decision.allowed && decision.retryAfterMs != null)
225
- headers["retry-after"] = String(Math.max(1, Math.ceil(decision.retryAfterMs / 1000)));
226
- return headers;
227
- }
228
- function defaultRateLimitRules() {
229
- return [
230
- { id: "global-ip", limit: 600, windowMs: 60000, burst: 100, key: { source: "ip" }, action: "deny", priority: 100 },
231
- {
232
- id: "api-ip",
233
- limit: 120,
234
- windowMs: 60000,
235
- burst: 30,
236
- path: "/api/**",
237
- key: { source: "ip" },
238
- action: "deny",
239
- priority: 50
240
- },
241
- {
242
- id: "auth-ip",
243
- limit: 10,
244
- windowMs: 60000,
245
- algorithm: "sliding_window",
246
- path: "/auth/**",
247
- methods: ["POST"],
248
- key: { source: "ip" },
249
- action: "challenge",
250
- priority: 10
251
- }
252
- ];
253
- }
254
- // src/protection/controls.ts
255
- var PROTECTION_SETTING = "protection.controls";
256
- var MAX_CONTROL_HOURS = 24;
257
- var DEFAULT_ATTACK_MODE_HOURS = 4;
258
- var EMPTY = { ipRules: { allow: [], block: [] } };
259
- function activeControl(control, now) {
260
- if (!control?.enabled)
261
- return;
262
- return new Date(control.expiresAt).getTime() > now.getTime() ? control : undefined;
263
- }
264
- function clampHours(hours, fallback) {
265
- const value = Number.isFinite(hours) ? Number(hours) : fallback;
266
- return Math.min(MAX_CONTROL_HOURS, Math.max(0.25, value));
267
- }
268
- var MAX_IP_RULES = 500;
269
-
270
- class ProtectionControlStore {
271
- controlPlane;
272
- options;
273
- constructor(controlPlane, options = {}) {
274
- this.controlPlane = controlPlane;
275
- this.options = options;
276
- }
277
- now() {
278
- return this.options.now?.() ?? new Date;
279
- }
280
- read() {
281
- const raw = this.controlPlane.getSetting(PROTECTION_SETTING);
282
- if (!raw || typeof raw !== "object" || Array.isArray(raw))
283
- return { ...EMPTY, ipRules: { allow: [], block: [] } };
284
- const record = raw;
285
- return {
286
- attackMode: record.attackMode,
287
- mitigationPause: record.mitigationPause,
288
- ipRules: {
289
- allow: Array.isArray(record.ipRules?.allow) ? record.ipRules.allow : [],
290
- block: Array.isArray(record.ipRules?.block) ? record.ipRules.block : []
291
- },
292
- updatedAt: record.updatedAt
293
- };
294
- }
295
- write(next) {
296
- const stored = { ...next, updatedAt: this.now().toISOString() };
297
- this.controlPlane.setSetting(PROTECTION_SETTING, stored);
298
- return stored;
299
- }
300
- raw() {
301
- return this.read();
302
- }
303
- current() {
304
- const now = this.now();
305
- const stored = this.read();
306
- return {
307
- attackMode: activeControl(stored.attackMode, now),
308
- mitigationPause: activeControl(stored.mitigationPause, now),
309
- ipRules: stored.ipRules
310
- };
311
- }
312
- enableAttackMode(input) {
313
- const now = this.now();
314
- const control = {
315
- enabled: true,
316
- startedAt: now.toISOString(),
317
- expiresAt: new Date(now.getTime() + clampHours(input.hours, DEFAULT_ATTACK_MODE_HOURS) * 3600000).toISOString(),
318
- reason: input.reason.trim() || "Attack mode enabled by an operator.",
319
- actorId: input.actorId
320
- };
321
- this.write({ ...this.read(), attackMode: control });
322
- return control;
323
- }
324
- disableAttackMode() {
325
- const stored = this.read();
326
- if (!stored.attackMode?.enabled)
327
- return false;
328
- this.write({ ...stored, attackMode: { ...stored.attackMode, enabled: false } });
329
- return true;
330
- }
331
- pauseMitigations(input) {
332
- const reason = input.reason.trim();
333
- if (!reason)
334
- throw new Error("Pausing mitigations requires a reason: you are liable for the traffic it admits.");
335
- const now = this.now();
336
- const control = {
337
- enabled: true,
338
- startedAt: now.toISOString(),
339
- expiresAt: new Date(now.getTime() + clampHours(input.hours, MAX_CONTROL_HOURS) * 3600000).toISOString(),
340
- reason,
341
- actorId: input.actorId
342
- };
343
- this.write({ ...this.read(), mitigationPause: control });
344
- return control;
345
- }
346
- resumeMitigations() {
347
- const stored = this.read();
348
- if (!stored.mitigationPause?.enabled)
349
- return false;
350
- this.write({ ...stored, mitigationPause: { ...stored.mitigationPause, enabled: false } });
351
- return true;
352
- }
353
- addIpRule(list, cidr) {
354
- const value = cidr.trim();
355
- if (!isValidCidr(value))
356
- throw new Error(`Not a valid IP or CIDR: ${cidr}`);
357
- const stored = this.read();
358
- const next = { ...stored.ipRules, [list]: [...new Set([...stored.ipRules[list], value])] };
359
- if (next[list].length > MAX_IP_RULES)
360
- throw new Error(`At most ${MAX_IP_RULES} ${list} rules are supported.`);
361
- const other = list === "allow" ? "block" : "allow";
362
- next[other] = next[other].filter((entry) => entry !== value);
363
- this.write({ ...stored, ipRules: next });
364
- return next;
365
- }
366
- removeIpRule(list, cidr) {
367
- const stored = this.read();
368
- const next = { ...stored.ipRules, [list]: stored.ipRules[list].filter((entry) => entry !== cidr.trim()) };
369
- this.write({ ...stored, ipRules: next });
370
- return next;
371
- }
372
- }
373
- function describePosture(controls) {
374
- if (controls.mitigationPause)
375
- return {
376
- attackMode: !!controls.attackMode,
377
- mitigationPaused: true,
378
- summary: `Automatic mitigation is paused until ${controls.mitigationPause.expiresAt}. All traffic is being served and billed.`,
379
- expiresAt: controls.mitigationPause.expiresAt
380
- };
381
- if (controls.attackMode)
382
- return {
383
- attackMode: true,
384
- mitigationPaused: false,
385
- summary: `Attack mode is on until ${controls.attackMode.expiresAt}. Every visitor is being challenged.`,
386
- expiresAt: controls.attackMode.expiresAt
387
- };
388
- return { attackMode: false, mitigationPaused: false, summary: "Protection is in its normal posture." };
389
- }
390
- function applyControlsToRateLimits(rules, controls) {
391
- if (controls.mitigationPause)
392
- return rules.map((rule) => ({ ...rule, enabled: false }));
393
- if (!controls.attackMode)
394
- return [...rules];
395
- return rules.map((rule) => ({
396
- ...rule,
397
- action: rule.action === "deny" ? "deny" : "challenge",
398
- burst: Math.max(1, Math.min(rule.burst ?? rule.limit, 5))
399
- }));
400
- }
401
- function applyControlsToDdos(config, controls) {
402
- const allowlist = [...new Set([...config.allowlist ?? [], ...controls.ipRules.allow])];
403
- const blocklist = [...new Set([...config.blocklist ?? [], ...controls.ipRules.block])];
404
- if (controls.mitigationPause)
405
- return { ...config, allowlist, blocklist, monitorOnly: true };
406
- if (!controls.attackMode)
407
- return { ...config, allowlist, blocklist };
408
- const thresholds = config.thresholds ?? {};
409
- return {
410
- ...config,
411
- allowlist,
412
- blocklist,
413
- thresholds: {
414
- ...thresholds,
415
- newConnectionsPerSecond: Math.max(1, Math.floor((thresholds.newConnectionsPerSecond ?? 50) / 5)),
416
- concurrentPerSource: Math.max(1, Math.floor((thresholds.concurrentPerSource ?? 100) / 5))
417
- }
418
- };
419
- }
420
- function mergeControlsIntoConfig(config, controls) {
421
- const compute = config?.infrastructure?.compute;
422
- if (!compute || compute.ddos === false)
423
- return config;
424
- const base = typeof compute.ddos === "object" && compute.ddos !== null ? compute.ddos : {};
425
- return {
426
- ...config,
427
- infrastructure: {
428
- ...config.infrastructure,
429
- compute: { ...compute, ddos: applyControlsToDdos(base, controls) }
430
- }
431
- };
432
- }
433
- export { RATE_LIMIT_KEY_SEPARATOR, globMatches, ruleMatches, rateLimitKey, RateLimiter, rateLimitHeaders, defaultRateLimitRules, PROTECTION_SETTING, MAX_CONTROL_HOURS, DEFAULT_ATTACK_MODE_HOURS, ProtectionControlStore, describePosture, applyControlsToRateLimits, applyControlsToDdos, mergeControlsIntoConfig };
@@ -1,8 +0,0 @@
1
- import {
2
- RDSClient
3
- } from "./chunk-he9a874b.js";
4
- import"./chunk-zqtpg06c.js";
5
- import"./chunk-v0bahtg2.js";
6
- export {
7
- RDSClient
8
- };