diff --git a/aspa-adoption-history.json b/aspa-adoption-history.json index a256bea..d2c1c01 100644 --- a/aspa-adoption-history.json +++ b/aspa-adoption-history.json @@ -859,12 +859,12 @@ }, { "date": "2026-07-16", - "ts": 1784174057994, - "aspa_objects": 2382, - "roa_count": 971831, - "atlas_asns_total": 4775, - "atlas_asns_with_aspa": 504, - "coverage_pct_atlas": 10.55, + "ts": 1784228666748, + "aspa_objects": 2397, + "roa_count": 972406, + "atlas_asns_total": 4774, + "atlas_asns_with_aspa": 508, + "coverage_pct_atlas": 10.64, "aspa_delta": 0, "method": "rpki_cloudflare_feed" } diff --git a/hijack-alerts.json b/hijack-alerts.json index 8f551c4..028e0e1 100644 --- a/hijack-alerts.json +++ b/hijack-alerts.json @@ -1,526 +1,4 @@ [ - { - "id": "1cd5436afebd1e80", - "asn": "199121", - "ts": "2026-06-15T11:00:12.656Z", - "severity": "MEDIUM", - "unexpected": [], - "missing": [ - "2001:67c:7a4::/48", - "91.244.180.0/24" - ], - "resolved": false, - "msg": "BGP anomaly for AS199121: 0 unexpected prefix(es), 2 missing" - }, - { - "id": "36a6622c24ed26d1", - "asn": "138699", - "ts": "2026-06-15T12:30:06.281Z", - "severity": "HIGH", - "unexpected": [ - "2404:9dc0:cd04::/48", - "101.45.200.0/23", - "101.45.198.0/24", - "2404:9dc0:c002::/48" - ], - "missing": [ - "118.26.132.0/24", - "103.136.222.0/24" - ], - "resolved": false, - "msg": "BGP anomaly for AS138699: 4 unexpected prefix(es), 2 missing" - }, - { - "id": "8bf8329d8e3cb77e", - "asn": "141805", - "ts": "2026-06-15T12:30:19.407Z", - "severity": "HIGH", - "unexpected": [ - "103.109.136.0/22", - "103.165.170.0/23" - ], - "missing": [], - "resolved": false, - "msg": "BGP anomaly for AS141805: 2 unexpected prefix(es), 0 missing" - }, - { - "id": "696d5fbb9d34e721", - "asn": "3491", - "ts": "2026-06-15T15:00:06.359Z", - "severity": "HIGH", - "unexpected": [ - "2400:8800:f8fb::/48", - "63.219.192.0/24", - "2400:8800:f8fa::/48", - "2400:8800:f8f0::/48" - ], - "missing": [ - "45.195.111.0/24", - "154.194.34.0/24", - "101.60.254.0/24", - "207.176.31.0/24" - ], - "resolved": false, - "msg": "BGP anomaly for AS3491: 4 unexpected prefix(es), 4 missing" - }, - { - "id": "62ff6f8f905a167f", - "asn": "15290", - "ts": "2026-06-15T15:00:07.886Z", - "severity": "HIGH", - "unexpected": [], - "missing": [ - "2001:4c8:1005:1::/64", - "209.112.10.0/24", - "2001:4c8:107f:100::/56", - "2001:4c8:1092::/48", - "192.135.87.0/24", - "2001:4c8:4:1::/64", - "2001:4c8:1089::/48", - "2001:4c8:107f:8000::/56", - "216.129.75.0/24", - "203.186.119.0/24", - "74.216.180.0/24", - "2001:4c8:108e::/48", - "2001:4c8:100f:2::/64", - "216.13.190.0/24", - "2001:4c8:108a::/48", - "2001:4c8:e:10::/64", - "216.13.26.0/24", - "2001:4c8:1090::/48", - "216.13.36.0/24", - "2001:4c8:107f:8200::/56", - "2001:4c8:107f:200::/56" - ], - "resolved": false, - "msg": "BGP anomaly for AS15290: 0 unexpected prefix(es), 21 missing" - }, - { - "id": "b9a408dd83c3f6b8", - "asn": "34702", - "ts": "2026-06-15T15:00:15.888Z", - "severity": "HIGH", - "unexpected": [], - "missing": [ - "38.180.230.0/24", - "38.244.154.0/24", - "46.22.208.0/20", - "80.79.112.0/20", - "38.244.216.0/24", - "185.114.116.0/24", - "38.180.44.0/23", - "114.129.9.0/24", - "77.83.28.0/22", - "38.244.155.0/24", - "185.46.20.0/22", - "185.246.184.0/22", - "38.180.248.0/24", - "38.180.163.0/24", - "37.252.4.0/23", - "2a01:97a0::/32", - "38.180.164.0/24", - "38.180.10.0/24", - "38.180.216.0/24", - "176.97.74.0/24" - ], - "resolved": false, - "msg": "BGP anomaly for AS34702: 0 unexpected prefix(es), 20 missing" - }, - { - "id": "849133ec654eb827", - "asn": "132372", - "ts": "2026-06-15T15:00:18.308Z", - "severity": "HIGH", - "unexpected": [ - "103.91.194.0/24", - "2a0d:9842::/48" - ], - "missing": [ - "172.94.65.0/24", - "172.94.34.0/24", - "172.94.97.0/24", - "172.94.29.0/24", - "172.94.99.0/24" - ], - "resolved": false, - "msg": "BGP anomaly for AS132372: 2 unexpected prefix(es), 5 missing" - }, - { - "id": "3298f8fa150e2b6f", - "asn": "24940", - "ts": "2026-06-15T16:00:04.851Z", - "severity": "MEDIUM", - "unexpected": [], - "missing": [ - "2a11:e980::/29" - ], - "resolved": false, - "msg": "BGP anomaly for AS24940: 0 unexpected prefix(es), 1 missing" - }, - { - "id": "7e133bf4bea21ffe", - "asn": "396986", - "ts": "2026-06-15T17:00:05.869Z", - "severity": "CRITICAL", - "unexpected": [ - "71.18.71.0/24", - "71.18.65.0/24", - "71.18.67.0/24", - "101.45.18.0/24", - "101.45.14.0/24", - "71.18.64.0/24", - "71.18.66.0/24", - "101.45.199.0/24", - "101.45.6.0/24", - "101.45.236.0/22" - ], - "missing": [ - "71.18.138.0/23" - ], - "resolved": false, - "msg": "BGP anomaly for AS396986: 10 unexpected prefix(es), 1 missing" - }, - { - "id": "f71900e135c3cc15", - "asn": "57099", - "ts": "2026-06-15T17:00:06.619Z", - "severity": "MEDIUM", - "unexpected": [ - "91.209.142.0/24" - ], - "missing": [], - "resolved": false, - "msg": "BGP anomaly for AS57099: 1 unexpected prefix(es), 0 missing" - }, - { - "id": "b5d22a1605943f06", - "asn": "22616", - "ts": "2026-06-15T17:00:07.942Z", - "severity": "HIGH", - "unexpected": [ - "137.31.114.0/23", - "137.31.101.0/24" - ], - "missing": [], - "resolved": false, - "msg": "BGP anomaly for AS22616: 2 unexpected prefix(es), 0 missing" - }, - { - "id": "8881c693d3a0d59a", - "asn": "133815", - "ts": "2026-06-15T17:30:14.856Z", - "severity": "HIGH", - "unexpected": [], - "missing": [ - "103.152.90.0/23", - "103.190.218.0/24", - "103.52.46.0/23", - "103.149.224.0/23", - "103.130.136.0/23", - "103.190.216.0/23", - "103.150.14.0/23", - "103.149.224.0/24", - "103.130.136.0/24", - "103.188.52.0/24", - "103.190.218.0/23", - "103.130.138.0/23", - "103.141.150.0/23", - "103.149.233.0/24", - "103.150.16.0/23", - "103.190.219.0/24", - "103.149.230.0/23", - "103.52.46.0/24", - "103.152.96.0/24", - "103.152.110.0/23", - "103.150.34.0/23", - "103.188.53.0/24", - "103.188.50.0/23", - "103.150.4.0/23", - "103.52.45.0/24", - "103.188.52.0/23", - "103.149.234.0/23", - "103.141.150.0/24", - "103.52.47.0/24", - "103.130.136.0/22", - "103.141.152.0/24", - "103.152.96.0/23", - "103.150.12.0/23", - "103.188.50.0/24", - "103.190.220.0/23", - "103.130.137.0/24", - "103.141.151.0/24", - "103.52.44.0/22", - "103.52.44.0/23", - "103.188.41.0/24", - "103.188.40.0/23", - "103.149.232.0/24", - "103.151.194.0/23", - "103.149.225.0/24", - "103.141.153.0/24", - "103.188.40.0/24", - "103.151.18.0/23", - "2403:bac0::/32", - "103.52.44.0/24", - "103.151.100.0/23", - "2403:57c0::/32", - "103.141.152.0/23", - "103.149.235.0/24", - "103.149.234.0/24", - "103.152.70.0/23", - "103.188.51.0/24", - "103.149.232.0/23" - ], - "resolved": false, - "msg": "BGP anomaly for AS133815: 0 unexpected prefix(es), 57 missing" - }, - { - "id": "fdce2c893da29056", - "asn": "138699", - "ts": "2026-06-15T18:30:13.293Z", - "severity": "HIGH", - "unexpected": [ - "2404:9dc0:cd04::/48", - "101.45.200.0/23", - "101.45.198.0/24", - "2404:9dc0:c002::/48" - ], - "missing": [ - "118.26.132.0/24", - "103.136.222.0/24" - ], - "resolved": false, - "msg": "BGP anomaly for AS138699: 4 unexpected prefix(es), 2 missing" - }, - { - "id": "c4feb1b96cb6f8b5", - "asn": "141805", - "ts": "2026-06-15T19:00:15.931Z", - "severity": "HIGH", - "unexpected": [ - "103.109.136.0/22", - "103.165.170.0/23" - ], - "missing": [], - "resolved": false, - "msg": "BGP anomaly for AS141805: 2 unexpected prefix(es), 0 missing" - }, - { - "id": "91f5b79d5d133efd", - "asn": "3491", - "ts": "2026-06-15T21:30:06.401Z", - "severity": "HIGH", - "unexpected": [ - "63.219.192.0/24", - "2400:8800:f8fa::/48", - "2400:8800:f8f0::/48", - "2400:8800:f8fb::/48" - ], - "missing": [ - "72.57.243.0/24", - "45.195.111.0/24", - "72.57.240.0/24", - "72.57.250.0/24", - "72.57.244.0/24", - "72.57.249.0/24", - "72.57.246.0/24", - "72.57.248.0/24", - "72.57.241.0/24", - "72.57.253.0/24", - "154.194.34.0/24", - "101.60.254.0/24", - "72.57.247.0/24", - "72.57.254.0/24", - "207.176.31.0/24", - "72.57.252.0/24", - "72.57.245.0/24", - "72.57.255.0/24" - ], - "resolved": false, - "msg": "BGP anomaly for AS3491: 4 unexpected prefix(es), 18 missing" - }, - { - "id": "1e80c4398f8117eb", - "asn": "15290", - "ts": "2026-06-15T21:30:06.806Z", - "severity": "HIGH", - "unexpected": [], - "missing": [ - "2001:4c8:1005:1::/64", - "209.112.10.0/24", - "2001:4c8:107f:100::/56", - "2001:4c8:1092::/48", - "192.135.87.0/24", - "2001:4c8:4:1::/64", - "2001:4c8:1089::/48", - "2001:4c8:107f:8000::/56", - "216.129.75.0/24", - "203.186.119.0/24", - "74.216.180.0/24", - "2001:4c8:108e::/48", - "2001:4c8:100f:2::/64", - "216.13.190.0/24", - "2001:4c8:108a::/48", - "2001:4c8:e:10::/64", - "216.13.26.0/24", - "2001:4c8:1090::/48", - "216.13.36.0/24", - "2001:4c8:107f:8200::/56", - "2001:4c8:107f:200::/56" - ], - "resolved": false, - "msg": "BGP anomaly for AS15290: 0 unexpected prefix(es), 21 missing" - }, - { - "id": "74b6571e2f059465", - "asn": "34702", - "ts": "2026-06-15T21:30:06.944Z", - "severity": "MEDIUM", - "unexpected": [ - "185.200.196.0/24" - ], - "missing": [], - "resolved": false, - "msg": "BGP anomaly for AS34702: 1 unexpected prefix(es), 0 missing" - }, - { - "id": "7449192d68c9a7e1", - "asn": "132372", - "ts": "2026-06-15T21:30:07.247Z", - "severity": "HIGH", - "unexpected": [ - "2a0d:9842::/48", - "103.91.194.0/24" - ], - "missing": [ - "172.94.65.0/24", - "172.94.34.0/24", - "172.94.97.0/24", - "172.94.29.0/24", - "172.94.99.0/24" - ], - "resolved": false, - "msg": "BGP anomaly for AS132372: 2 unexpected prefix(es), 5 missing" - }, - { - "id": "174210e8d5316ec1", - "asn": "24940", - "ts": "2026-06-15T22:00:05.807Z", - "severity": "MEDIUM", - "unexpected": [], - "missing": [ - "2a11:e980::/29" - ], - "resolved": false, - "msg": "BGP anomaly for AS24940: 0 unexpected prefix(es), 1 missing" - }, - { - "id": "957ea776f2308c94", - "asn": "396986", - "ts": "2026-06-15T23:00:06.155Z", - "severity": "CRITICAL", - "unexpected": [ - "101.45.18.0/24", - "71.18.64.0/24", - "71.18.65.0/24", - "71.18.71.0/24", - "71.18.67.0/24", - "101.45.14.0/24", - "71.18.66.0/24", - "101.45.199.0/24", - "101.45.6.0/24", - "101.45.236.0/22" - ], - "missing": [ - "71.18.138.0/23" - ], - "resolved": false, - "msg": "BGP anomaly for AS396986: 10 unexpected prefix(es), 1 missing" - }, - { - "id": "8618386e589465bf", - "asn": "57099", - "ts": "2026-06-15T23:00:07.558Z", - "severity": "MEDIUM", - "unexpected": [ - "91.209.142.0/24" - ], - "missing": [], - "resolved": false, - "msg": "BGP anomaly for AS57099: 1 unexpected prefix(es), 0 missing" - }, - { - "id": "8d2f409ec938982a", - "asn": "22616", - "ts": "2026-06-15T23:00:09.152Z", - "severity": "HIGH", - "unexpected": [ - "137.31.114.0/23", - "137.31.101.0/24" - ], - "missing": [], - "resolved": false, - "msg": "BGP anomaly for AS22616: 2 unexpected prefix(es), 0 missing" - }, - { - "id": "ed9f21b684f999c8", - "asn": "138699", - "ts": "2026-06-16T01:00:05.552Z", - "severity": "HIGH", - "unexpected": [ - "2404:9dc0:cd04::/48", - "101.45.200.0/23", - "101.45.198.0/24", - "2404:9dc0:c002::/48" - ], - "missing": [ - "118.26.132.0/24", - "103.136.222.0/24" - ], - "resolved": false, - "msg": "BGP anomaly for AS138699: 4 unexpected prefix(es), 2 missing" - }, - { - "id": "a4d2fe348e3337a8", - "asn": "199121", - "ts": "2026-06-16T01:30:12.664Z", - "severity": "MEDIUM", - "unexpected": [], - "missing": [ - "2001:67c:7a4::/48", - "91.244.180.0/24" - ], - "resolved": false, - "msg": "BGP anomaly for AS199121: 0 unexpected prefix(es), 2 missing" - }, - { - "id": "667336e4e7f8a075", - "asn": "141805", - "ts": "2026-06-16T01:30:29.042Z", - "severity": "HIGH", - "unexpected": [ - "103.109.136.0/22", - "103.165.170.0/23" - ], - "missing": [], - "resolved": false, - "msg": "BGP anomaly for AS141805: 2 unexpected prefix(es), 0 missing" - }, - { - "id": "03d0c309a2e9f14a", - "asn": "32", - "ts": "2026-06-16T03:30:12.819Z", - "severity": "HIGH", - "unexpected": [], - "missing": [ - "204.63.224.0/21", - "171.64.0.0/14", - "2607:f6d0::/32", - "128.12.0.0/16", - "2620:6c:40c0::/48" - ], - "resolved": false, - "msg": "BGP anomaly for AS32: 0 unexpected prefix(es), 5 missing" - }, { "id": "a49abae0bbdebba5", "asn": "3491", @@ -24546,5 +24024,733 @@ ], "resolved": false, "msg": "BGP anomaly for AS132372: 2 unexpected prefix(es), 5 missing" + }, + { + "id": "dabf3791c59c7ae3", + "asn": "133815", + "ts": "2026-07-16T07:29:59.505Z", + "severity": "HIGH", + "unexpected": [], + "missing": [ + "103.152.90.0/23", + "103.190.218.0/24", + "103.52.46.0/23", + "103.149.224.0/23", + "103.130.136.0/23", + "103.190.216.0/23", + "103.150.14.0/23", + "103.149.224.0/24", + "103.130.136.0/24", + "103.188.52.0/24", + "103.190.218.0/23", + "103.130.138.0/23", + "103.141.150.0/23", + "103.149.233.0/24", + "103.150.16.0/23", + "103.190.219.0/24", + "103.149.230.0/23", + "103.52.46.0/24", + "103.152.96.0/24", + "103.152.110.0/23", + "103.150.34.0/23", + "103.188.53.0/24", + "103.188.50.0/23", + "103.150.4.0/23", + "103.52.45.0/24", + "103.188.52.0/23", + "103.149.234.0/23", + "103.141.150.0/24", + "103.52.47.0/24", + "103.130.136.0/22", + "103.141.152.0/24", + "103.152.96.0/23", + "103.150.12.0/23", + "103.188.50.0/24", + "103.190.220.0/23", + "103.130.137.0/24", + "103.141.151.0/24", + "103.52.44.0/22", + "103.52.44.0/23", + "103.188.41.0/24", + "103.188.40.0/23", + "103.149.232.0/24", + "103.151.194.0/23", + "103.149.225.0/24", + "103.141.153.0/24", + "103.188.40.0/24", + "103.151.18.0/23", + "2403:bac0::/32", + "103.52.44.0/24", + "103.151.100.0/23", + "2403:57c0::/32", + "103.141.152.0/23", + "103.149.235.0/24", + "103.149.234.0/24", + "103.152.70.0/23", + "103.188.51.0/24", + "103.149.232.0/23" + ], + "resolved": false, + "msg": "BGP anomaly for AS133815: 0 unexpected prefix(es), 57 missing" + }, + { + "id": "2127cd300b93b16d", + "asn": "24940", + "ts": "2026-07-16T08:29:51.432Z", + "severity": "HIGH", + "unexpected": [ + "2.28.0.0/16", + "2.29.0.0/16", + "193.105.82.0/24" + ], + "missing": [ + "2a11:e980::/29" + ], + "resolved": false, + "msg": "BGP anomaly for AS24940: 3 unexpected prefix(es), 1 missing" + }, + { + "id": "b77e565aa3e251ae", + "asn": "138699", + "ts": "2026-07-16T08:29:52.126Z", + "severity": "HIGH", + "unexpected": [ + "2404:9dc0:cd04::/48", + "2404:9dc0:c002::/48", + "101.45.200.0/23", + "101.45.198.0/24" + ], + "missing": [ + "118.26.132.0/24", + "103.136.222.0/24" + ], + "resolved": false, + "msg": "BGP anomaly for AS138699: 4 unexpected prefix(es), 2 missing" + }, + { + "id": "b3295679b1906219", + "asn": "15290", + "ts": "2026-07-16T08:29:52.989Z", + "severity": "HIGH", + "unexpected": [], + "missing": [ + "2001:4c8:1005:1::/64", + "209.112.10.0/24", + "2001:4c8:107f:100::/56", + "2001:4c8:1097::/48", + "2001:4c8:1092::/48", + "192.135.87.0/24", + "2001:4c8:4:1::/64", + "216.123.66.0/24", + "2001:4c8:1089::/48", + "216.123.80.0/24", + "203.186.117.0/24", + "206.191.100.0/24", + "2001:4c8:107f:8000::/56", + "216.129.75.0/24", + "203.186.119.0/24", + "74.216.180.0/24", + "209.82.49.0/24", + "2001:4c8:108e::/48", + "159.18.219.0/24", + "209.112.42.0/24", + "2001:4c8:100f:2::/64", + "216.13.190.0/24", + "2001:4c8:108a::/48", + "2001:4c8:1080::/48", + "2001:4c8:101f::/48", + "204.138.183.0/24", + "2001:4c8:e:10::/64", + "216.13.26.0/24", + "2001:4c8:1090::/48", + "216.13.36.0/24", + "216.13.115.0/24", + "74.216.227.0/24", + "2001:4c8:107f:8200::/56", + "2001:4c8:107f:200::/56", + "203.186.116.0/24" + ], + "resolved": false, + "msg": "BGP anomaly for AS15290: 0 unexpected prefix(es), 35 missing" + }, + { + "id": "1d0f77f1730666ed", + "asn": "3491", + "ts": "2026-07-16T08:59:58.040Z", + "severity": "CRITICAL", + "unexpected": [ + "63.219.192.0/24", + "2400:8800:f8fa::/48", + "2400:8800:f8fb::/48", + "2400:8800:f8f0::/48", + "209.8.154.0/23" + ], + "missing": [ + "72.57.243.0/24", + "45.195.111.0/24", + "72.57.240.0/24", + "72.57.250.0/24", + "72.57.244.0/24", + "72.57.249.0/24", + "72.57.246.0/24", + "72.57.248.0/24", + "72.57.241.0/24", + "72.57.253.0/24", + "154.194.34.0/24", + "101.60.254.0/24", + "72.57.247.0/24", + "72.57.254.0/24", + "2400:8800:f827::/48", + "207.176.31.0/24", + "72.57.252.0/24", + "72.57.245.0/24", + "72.57.255.0/24" + ], + "resolved": false, + "msg": "BGP anomaly for AS3491: 5 unexpected prefix(es), 19 missing" + }, + { + "id": "cc48f388be2bb5a8", + "asn": "34702", + "ts": "2026-07-16T10:29:52.615Z", + "severity": "MEDIUM", + "unexpected": [ + "185.200.196.0/24" + ], + "missing": [], + "resolved": false, + "msg": "BGP anomaly for AS34702: 1 unexpected prefix(es), 0 missing" + }, + { + "id": "5c8fc49fb1d1b020", + "asn": "396986", + "ts": "2026-07-16T10:59:51.681Z", + "severity": "CRITICAL", + "unexpected": [ + "101.45.18.0/24", + "101.45.199.0/24", + "2605:340:f04a::/48", + "101.45.202.0/24", + "71.18.66.0/24", + "101.45.15.0/24", + "71.18.44.0/24", + "101.45.6.0/24", + "71.18.45.0/24", + "71.18.64.0/24", + "71.18.71.0/24", + "71.18.67.0/24", + "71.18.65.0/24", + "2605:340:f049::/48", + "101.45.14.0/24", + "101.45.23.0/24" + ], + "missing": [ + "71.18.138.0/23", + "2605:340:f082::/48", + "71.18.64.0/21", + "71.18.233.0/24", + "2605:340:f051::/48" + ], + "resolved": false, + "msg": "BGP anomaly for AS396986: 16 unexpected prefix(es), 5 missing" + }, + { + "id": "b3abfb2e73bab81d", + "asn": "22616", + "ts": "2026-07-16T10:59:54.728Z", + "severity": "CRITICAL", + "unexpected": [ + "137.31.24.0/23", + "137.31.101.0/24", + "205.220.14.0/23", + "137.31.120.0/23", + "137.31.114.0/23", + "137.31.119.0/24", + "2605:4300:e500::/40", + "137.31.26.0/23", + "137.31.126.0/23", + "137.31.122.0/23", + "140.232.189.0/24", + "137.31.128.0/23", + "140.232.190.0/24", + "137.31.124.0/23", + "155.95.86.0/24", + "165.225.99.0/24", + "205.220.12.0/23", + "140.232.188.0/24", + "89.18.89.0/24", + "89.18.90.0/24" + ], + "missing": [], + "resolved": false, + "msg": "BGP anomaly for AS22616: 20 unexpected prefix(es), 0 missing" + }, + { + "id": "a680d3be8885d390", + "asn": "132372", + "ts": "2026-07-16T12:59:53.209Z", + "severity": "HIGH", + "unexpected": [ + "2a0d:9842::/48", + "103.91.194.0/24" + ], + "missing": [ + "172.94.65.0/24", + "172.94.34.0/24", + "172.94.97.0/24", + "172.94.29.0/24", + "172.94.99.0/24" + ], + "resolved": false, + "msg": "BGP anomaly for AS132372: 2 unexpected prefix(es), 5 missing" + }, + { + "id": "43f7610739f4b0da", + "asn": "24940", + "ts": "2026-07-16T14:59:51.363Z", + "severity": "HIGH", + "unexpected": [ + "193.105.82.0/24", + "2.28.0.0/16", + "2.29.0.0/16" + ], + "missing": [ + "2a11:e980::/29" + ], + "resolved": false, + "msg": "BGP anomaly for AS24940: 3 unexpected prefix(es), 1 missing" + }, + { + "id": "90880af3d1bbaada", + "asn": "138699", + "ts": "2026-07-16T14:59:51.719Z", + "severity": "HIGH", + "unexpected": [ + "2404:9dc0:c002::/48", + "101.45.198.0/24", + "2404:9dc0:cd04::/48", + "101.45.200.0/23" + ], + "missing": [ + "118.26.132.0/24", + "103.136.222.0/24" + ], + "resolved": false, + "msg": "BGP anomaly for AS138699: 4 unexpected prefix(es), 2 missing" + }, + { + "id": "27f8a67414d226ff", + "asn": "15290", + "ts": "2026-07-16T14:59:52.699Z", + "severity": "HIGH", + "unexpected": [], + "missing": [ + "2001:4c8:1005:1::/64", + "209.112.10.0/24", + "2001:4c8:107f:100::/56", + "2001:4c8:1097::/48", + "2001:4c8:1092::/48", + "192.135.87.0/24", + "2001:4c8:4:1::/64", + "216.123.66.0/24", + "2001:4c8:1089::/48", + "216.123.80.0/24", + "203.186.117.0/24", + "206.191.100.0/24", + "2001:4c8:107f:8000::/56", + "216.129.75.0/24", + "203.186.119.0/24", + "74.216.180.0/24", + "209.82.49.0/24", + "2001:4c8:108e::/48", + "159.18.219.0/24", + "209.112.42.0/24", + "2001:4c8:100f:2::/64", + "216.13.190.0/24", + "2001:4c8:108a::/48", + "2001:4c8:1080::/48", + "2001:4c8:101f::/48", + "204.138.183.0/24", + "2001:4c8:e:10::/64", + "216.13.26.0/24", + "2001:4c8:1090::/48", + "216.13.36.0/24", + "216.13.115.0/24", + "74.216.227.0/24", + "2001:4c8:107f:8200::/56", + "2001:4c8:107f:200::/56", + "203.186.116.0/24" + ], + "resolved": false, + "msg": "BGP anomaly for AS15290: 0 unexpected prefix(es), 35 missing" + }, + { + "id": "69bb974506f0b391", + "asn": "3491", + "ts": "2026-07-16T15:29:53.658Z", + "severity": "CRITICAL", + "unexpected": [ + "2400:8800:f8f0::/48", + "63.219.192.0/24", + "2400:8800:f8fa::/48", + "2400:8800:f8fb::/48", + "209.8.154.0/23" + ], + "missing": [ + "72.57.243.0/24", + "45.195.111.0/24", + "72.57.240.0/24", + "72.57.250.0/24", + "72.57.244.0/24", + "72.57.249.0/24", + "72.57.246.0/24", + "72.57.248.0/24", + "72.57.241.0/24", + "72.57.253.0/24", + "154.194.34.0/24", + "101.60.254.0/24", + "72.57.247.0/24", + "72.57.254.0/24", + "2400:8800:f827::/48", + "207.176.31.0/24", + "72.57.252.0/24", + "72.57.245.0/24", + "72.57.255.0/24" + ], + "resolved": false, + "msg": "BGP anomaly for AS3491: 5 unexpected prefix(es), 19 missing" + }, + { + "id": "86bd727392b1748d", + "asn": "34702", + "ts": "2026-07-16T16:29:52.869Z", + "severity": "MEDIUM", + "unexpected": [ + "185.200.196.0/24" + ], + "missing": [], + "resolved": false, + "msg": "BGP anomaly for AS34702: 1 unexpected prefix(es), 0 missing" + }, + { + "id": "d281a294f6dc1c5f", + "asn": "396986", + "ts": "2026-07-16T16:59:51.691Z", + "severity": "CRITICAL", + "unexpected": [ + "101.45.6.0/24", + "2605:340:f049::/48", + "71.18.64.0/24", + "71.18.66.0/24", + "71.18.67.0/24", + "101.45.14.0/24", + "101.45.202.0/24", + "71.18.65.0/24", + "71.18.71.0/24", + "101.45.199.0/24", + "71.18.45.0/24", + "2605:340:f04a::/48", + "101.45.15.0/24", + "71.18.44.0/24", + "101.45.18.0/24", + "101.45.23.0/24" + ], + "missing": [ + "71.18.138.0/23", + "2605:340:f082::/48", + "71.18.64.0/21", + "71.18.233.0/24", + "2605:340:f051::/48" + ], + "resolved": false, + "msg": "BGP anomaly for AS396986: 16 unexpected prefix(es), 5 missing" + }, + { + "id": "ebc4a90f3acc05a0", + "asn": "22616", + "ts": "2026-07-16T16:59:56.161Z", + "severity": "CRITICAL", + "unexpected": [ + "137.31.119.0/24", + "155.95.86.0/24", + "140.232.189.0/24", + "137.31.101.0/24", + "2605:4300:e500::/40", + "137.31.26.0/23", + "140.232.188.0/24", + "137.31.122.0/23", + "137.31.124.0/23", + "140.232.190.0/24", + "137.31.128.0/23", + "205.220.12.0/23", + "137.31.24.0/23", + "205.220.14.0/23", + "165.225.99.0/24", + "137.31.114.0/23", + "137.31.126.0/23", + "137.31.120.0/23", + "89.18.89.0/24", + "89.18.90.0/24" + ], + "missing": [], + "resolved": false, + "msg": "BGP anomaly for AS22616: 20 unexpected prefix(es), 0 missing" + }, + { + "id": "d494ec52181c405a", + "asn": "132372", + "ts": "2026-07-16T19:00:03.899Z", + "severity": "HIGH", + "unexpected": [ + "2a0d:9842::/48", + "103.91.194.0/24" + ], + "missing": [ + "172.94.65.0/24", + "172.94.34.0/24", + "172.94.97.0/24", + "172.94.29.0/24", + "172.94.99.0/24" + ], + "resolved": false, + "msg": "BGP anomaly for AS132372: 2 unexpected prefix(es), 5 missing" + }, + { + "id": "2c9adcda2c4d4e55", + "asn": "199121", + "ts": "2026-07-16T19:34:04.009Z", + "severity": "MEDIUM", + "unexpected": [], + "missing": [ + "2001:67c:7a4::/48", + "91.244.180.0/24" + ], + "resolved": false, + "msg": "BGP anomaly for AS199121: 0 unexpected prefix(es), 2 missing" + }, + { + "id": "39312de4cf19618a", + "asn": "32", + "ts": "2026-07-16T19:34:04.099Z", + "severity": "HIGH", + "unexpected": [], + "missing": [ + "204.63.224.0/21", + "171.64.0.0/14", + "2607:f6d0::/32", + "128.12.0.0/16", + "2620:6c:40c0::/48" + ], + "resolved": false, + "msg": "BGP anomaly for AS32: 0 unexpected prefix(es), 5 missing" + }, + { + "id": "549c0cf08c99c20d", + "asn": "133815", + "ts": "2026-07-16T19:34:04.143Z", + "severity": "HIGH", + "unexpected": [], + "missing": [ + "103.152.90.0/23", + "103.190.218.0/24", + "103.52.46.0/23", + "103.149.224.0/23", + "103.130.136.0/23", + "103.190.216.0/23", + "103.150.14.0/23", + "103.149.224.0/24", + "103.130.136.0/24", + "103.188.52.0/24", + "103.190.218.0/23", + "103.130.138.0/23", + "103.141.150.0/23", + "103.149.233.0/24", + "103.150.16.0/23", + "103.190.219.0/24", + "103.149.230.0/23", + "103.52.46.0/24", + "103.152.96.0/24", + "103.152.110.0/23", + "103.150.34.0/23", + "103.188.53.0/24", + "103.188.50.0/23", + "103.150.4.0/23", + "103.52.45.0/24", + "103.188.52.0/23", + "103.149.234.0/23", + "103.141.150.0/24", + "103.52.47.0/24", + "103.130.136.0/22", + "103.141.152.0/24", + "103.152.96.0/23", + "103.150.12.0/23", + "103.188.50.0/24", + "103.190.220.0/23", + "103.130.137.0/24", + "103.141.151.0/24", + "103.52.44.0/22", + "103.52.44.0/23", + "103.188.41.0/24", + "103.188.40.0/23", + "103.149.232.0/24", + "103.151.194.0/23", + "103.149.225.0/24", + "103.141.153.0/24", + "103.188.40.0/24", + "103.151.18.0/23", + "2403:bac0::/32", + "103.52.44.0/24", + "103.151.100.0/23", + "2403:57c0::/32", + "103.141.152.0/23", + "103.149.235.0/24", + "103.149.234.0/24", + "103.152.70.0/23", + "103.188.51.0/24", + "103.149.232.0/23" + ], + "resolved": false, + "msg": "BGP anomaly for AS133815: 0 unexpected prefix(es), 57 missing" + }, + { + "id": "a455ad0006d2506a", + "asn": "57099", + "ts": "2026-07-16T19:34:04.324Z", + "severity": "HIGH", + "unexpected": [], + "missing": [ + "185.168.144.0/22", + "146.255.104.0/21", + "85.95.32.0/19", + "91.214.228.0/22", + "185.47.216.0/22", + "185.113.0.0/22", + "151.249.64.0/20", + "5.133.172.0/22", + "164.138.80.0/21", + "209.35.128.0/20", + "185.56.40.0/22", + "185.44.248.0/22", + "185.16.224.0/22", + "46.231.8.0/21", + "91.229.222.0/23" + ], + "resolved": false, + "msg": "BGP anomaly for AS57099: 0 unexpected prefix(es), 15 missing" + }, + { + "id": "23184850da2eb053", + "asn": "142631", + "ts": "2026-07-16T19:34:04.456Z", + "severity": "HIGH", + "unexpected": [], + "missing": [ + "103.171.104.0/23", + "103.171.105.0/24", + "160.191.18.0/24", + "103.171.104.0/24", + "103.229.232.0/24", + "103.208.66.0/24", + "2407:c2c0::/32", + "160.191.19.0/24" + ], + "resolved": false, + "msg": "BGP anomaly for AS142631: 0 unexpected prefix(es), 8 missing" + }, + { + "id": "a211075721768fe8", + "asn": "141805", + "ts": "2026-07-16T19:34:04.589Z", + "severity": "HIGH", + "unexpected": [], + "missing": [ + "103.165.171.0/24", + "103.165.170.0/24", + "103.109.136.0/24", + "2001:df6:6b80::/48", + "103.109.137.0/24", + "103.109.139.0/24", + "103.109.138.0/24" + ], + "resolved": false, + "msg": "BGP anomaly for AS141805: 0 unexpected prefix(es), 7 missing" + }, + { + "id": "bfc455b9a95ff44b", + "asn": "402062", + "ts": "2026-07-16T19:34:04.675Z", + "severity": "HIGH", + "unexpected": [], + "missing": [ + "38.81.252.0/24", + "38.81.251.0/24", + "2604:2420::/48", + "2604:2420:4::/48", + "2604:2420:1::/48", + "38.81.254.0/24", + "2604:2420:3::/48", + "38.81.253.0/24", + "38.81.255.0/24", + "2604:2420:2::/48", + "2604:2420:5::/48", + "38.81.250.0/24" + ], + "resolved": false, + "msg": "BGP anomaly for AS402062: 0 unexpected prefix(es), 12 missing" + }, + { + "id": "e547e29609bfc46e", + "asn": "58617", + "ts": "2026-07-16T19:34:04.719Z", + "severity": "MEDIUM", + "unexpected": [], + "missing": [ + "103.9.23.0/24", + "163.61.154.0/24" + ], + "resolved": false, + "msg": "BGP anomaly for AS58617: 0 unexpected prefix(es), 2 missing" + }, + { + "id": "ee63ab25565afd81", + "asn": "5482", + "ts": "2026-07-16T19:34:04.779Z", + "severity": "HIGH", + "unexpected": [], + "missing": [ + "51.179.192.0/18", + "2a0b:5f00::/29", + "141.195.128.0/19", + "2a05:87c7:2000::/36", + "2a05:87c5:2000::/36", + "188.74.72.0/23", + "2a05:87c3:8000::/36", + "2a0b:5f04:4000::/39", + "62.64.154.0/23", + "51.179.200.0/21", + "188.74.70.0/23", + "188.74.74.0/23", + "188.74.76.0/23", + "2a05:87c7:1000::/36", + "2a0b:5f04:1000::/39", + "2a05:87c3:2000::/36", + "51.179.208.0/21", + "62.64.152.0/23", + "141.195.144.0/21", + "2a05:87c3:1000::/36", + "141.195.152.0/21", + "2a0b:5f04:5000::/39", + "2a05:87c7:7000::/36", + "141.195.136.0/21", + "51.179.192.0/21", + "217.142.64.0/18", + "2a0b:5f04::/39", + "2a05:87c7:8000::/36", + "2a05:87c3:7000::/36", + "2a0a:5583::/32", + "2a05:87c5:1000::/36" + ], + "resolved": false, + "msg": "BGP anomaly for AS5482: 0 unexpected prefix(es), 31 missing" } ] \ No newline at end of file diff --git a/local-db-client.js b/local-db-client.js new file mode 100644 index 0000000..c3df0e7 --- /dev/null +++ b/local-db-client.js @@ -0,0 +1,437 @@ +/** + * Local Database Client for PeerCortex + * Replaces external API calls with local PostgreSQL queries + * BGP + RPKI + Threat Intel + RDAP caching + */ + +const { Pool } = require('pg'); + +const pool = new Pool({ + user: process.env.DB_USER || 'llm', + password: process.env.DB_PASSWORD || 'llm_secure_2026', + host: process.env.DB_HOST || '192.168.178.82', + port: parseInt(process.env.DB_PORT || '5432'), + database: process.env.DB_NAME || 'llm_gateway', +}); + +// RDAP Cache (in-memory for this session) +const rdapCache = new Map(); +const RDAP_CACHE_TTL = 3600000; // 1 hour + +// ══════════════════════════════════════════════════════════════ +// BGP FUNCTIONS +// ══════════════════════════════════════════════════════════════ + +async function getBgpStatus(prefix) { + try { + const result = await pool.query( + `SELECT DISTINCT origin_asn, MAX(visibility_percent) as visibility_percent, MAX(last_seen) as last_seen + FROM bgp_routes + WHERE prefix = $1::cidr + GROUP BY origin_asn`, + [prefix] + ); + + if (result.rows.length === 0) { + return { + announced: false, + origin_asns: [], + visibility_percent: 0, + last_seen: new Date().toISOString(), + source: 'local_bgp', + }; + } + + return { + announced: true, + origin_asns: result.rows.map(r => r.origin_asn), + visibility_percent: Math.max(...result.rows.map(r => parseFloat(r.visibility_percent) || 0)), + last_seen: result.rows[0].last_seen || new Date().toISOString(), + source: 'local_bgp', + }; + } catch (error) { + console.error('[Local DB] BGP Status Error:', error.message); + return null; + } +} + +async function getAnnouncedPrefixes(asn) { + try { + const result = await pool.query( + `SELECT prefix, origin_asn, visibility_percent, last_seen + FROM bgp_routes + WHERE origin_asn = $1 + ORDER BY visibility_percent DESC + LIMIT 100`, + [asn] + ); + return result.rows; + } catch (error) { + console.error('[Local DB] Announced Prefixes Error:', error.message); + return []; + } +} + +// Returns null on DB error (check could not run) vs [] for a genuine single/no-origin +// result -- a caller treating both as "no hijack" would silently disable hijack +// detection on every transient DB hiccup while still showing the check as clean. +async function checkBgpHijack(prefix) { + try { + const result = await pool.query( + `SELECT DISTINCT origin_asn FROM bgp_routes WHERE prefix = $1::cidr`, + [prefix] + ); + return result.rows.length > 1 ? result.rows.map(r => r.origin_asn) : []; + } catch (error) { + console.error('[Local DB] Hijack Check Error:', error.message); + return null; + } +} + +// ══════════════════════════════════════════════════════════════ +// RPKI FUNCTIONS +// ══════════════════════════════════════════════════════════════ + +async function validateRpki(prefix, originAsn) { + try { + const prefixParts = prefix.split('/'); + if (prefixParts.length !== 2) { + return { status: 'unknown', description: 'Invalid CIDR format' }; + } + + const prefixLength = parseInt(prefixParts[1]); + + // Query for covering ROAs + const result = await pool.query( + `SELECT * FROM rpki_roas + WHERE $1::cidr << (prefix || '/' || max_length)::cidr + AND origin_asn = $2 + AND expires > NOW() + LIMIT 10`, + [prefix, originAsn] + ); + + if (result.rows.length === 0) { + const anyRoa = await pool.query( + `SELECT 1 FROM rpki_roas WHERE $1::cidr << prefix AND expires > NOW() LIMIT 1`, + [prefix] + ); + + if (anyRoa.rows.length > 0) { + return { + status: 'invalid', + prefix, + asn: originAsn, + description: `RPKI INVALID: ROAs exist but origin ASN ${originAsn} not authorized`, + }; + } + + return { + status: 'not-found', + prefix, + asn: originAsn, + description: 'No matching ROA found (unprotected)', + }; + } + + const roa = result.rows[0]; + if (prefixLength > roa.max_length) { + return { + status: 'invalid', + prefix, + asn: originAsn, + max_length: roa.max_length, + description: `RPKI INVALID: Prefix length ${prefixLength} > max_length ${roa.max_length}`, + }; + } + + return { + status: 'valid', + prefix, + asn: originAsn, + max_length: roa.max_length, + expires: roa.expires, + description: `RPKI VALID: Origin ASN ${originAsn} authorized`, + }; + } catch (error) { + console.error('[Local DB] RPKI Validation Error:', error.message); + return { status: 'unknown', description: 'RPKI validation error' }; + } +} + +async function getRoasForAsn(asn) { + try { + const result = await pool.query( + `SELECT prefix, max_length, expires FROM rpki_roas + WHERE origin_asn = $1 AND expires > NOW() + ORDER BY prefix`, + [asn] + ); + return result.rows; + } catch (error) { + console.error('[Local DB] ROAs for ASN Error:', error.message); + return []; + } +} + +// ══════════════════════════════════════════════════════════════ +// THREAT INTEL FUNCTIONS +// ══════════════════════════════════════════════════════════════ + +async function getThreatIntel(ip) { + try { + const result = await pool.query( + `SELECT ip_address, threat_level, confidence_score, source, details, cached_at + FROM threat_intel + WHERE ip_address = $1::inet + AND expires_at > NOW() + LIMIT 1`, + [ip] + ); + + return result.rows.length > 0 ? result.rows[0] : null; + } catch (error) { + console.error('[Local DB] Threat Intel Error:', error.message); + return null; + } +} + +async function isMaliciousIp(ip) { + try { + const result = await pool.query( + `SELECT 1 FROM threat_intel + WHERE ip_address = $1::inet + AND threat_level IN ('CRITICAL', 'HIGH') + AND expires_at > NOW() + LIMIT 1`, + [ip] + ); + return result.rows.length > 0; + } catch (error) { + console.error('[Local DB] Malicious IP Check Error:', error.message); + return false; + } +} + +// ══════════════════════════════════════════════════════════════ +// RDAP CACHING (in-memory) +// ══════════════════════════════════════════════════════════════ + +function getRdapCached(resource) { + const cached = rdapCache.get(resource); + if (cached && Date.now() - cached.timestamp < RDAP_CACHE_TTL) { + console.log(`[RDAP Cache] HIT: ${resource}`); + return cached.data; + } + if (cached) rdapCache.delete(resource); + return null; +} + +function setRdapCached(resource, data) { + rdapCache.set(resource, { data, timestamp: Date.now() }); + console.log(`[RDAP Cache] SET: ${resource} (TTL: 1h)`); +} + +// ══════════════════════════════════════════════════════════════ +// RIPE STAT API WRAPPER (Drop-in replacements for external API calls) +// ══════════════════════════════════════════════════════════════ + +async function getRipeStatAnnouncedPrefixes(asn) { + try { + const prefixes = await getAnnouncedPrefixes(asn); + return { + status: 'ok', + cached: false, + data: { + resource: `AS${asn}`, + prefixes: prefixes.map(p => ({ + prefix: p.prefix, + origin_asn: p.origin_asn, + })), + }, + }; + } catch (error) { + console.error('[Local RIPE Stat] Announced Prefixes Error:', error.message); + return { status: 'error', error: error.message, data: { resource: `AS${asn}`, prefixes: [] } }; + } +} + +async function getRipeStatAsnNeighbours(asn) { + try { + const result = await pool.query( + `SELECT DISTINCT origin_asn FROM bgp_routes LIMIT 200` + ); + + const neighbours = result.rows.map(r => ({ + asn: r.origin_asn, + type: 'unknown', + prefixes: 0, + })); + + return { + status: 'ok', + cached: false, + data: { + resource: `AS${asn}`, + neighbours: neighbours, + }, + }; + } catch (error) { + console.error('[Local RIPE Stat] ASN Neighbours Error:', error.message); + return { status: 'error', error: error.message, data: { resource: `AS${asn}`, neighbours: [] } }; + } +} + +async function getRipeStatAsOverview(asn) { + try { + const prefixes = await getAnnouncedPrefixes(asn); + const roasCount = await pool.query( + `SELECT COUNT(*) as count FROM rpki_roas WHERE origin_asn = $1 AND expires > NOW()`, + [asn] + ); + + return { + status: 'ok', + cached: false, + data: { + resource: `AS${asn}`, + asn: asn, + holder: 'Unknown', + announced_prefixes_count: prefixes.length, + description: [{ descr: 'Local Database ASN Overview' }], + type: 'asn', + rpki_status: roasCount.rows[0].count > 0 ? 'signed' : 'not-signed', + }, + }; + } catch (error) { + console.error('[Local RIPE Stat] AS Overview Error:', error.message); + return { status: 'error', error: error.message, data: { resource: `AS${asn}`, announced_prefixes_count: 0 } }; + } +} + +async function getRipeStatVisibility(asn) { + try { + const result = await pool.query( + `SELECT COALESCE(AVG(visibility_percent), 0) as avg_visibility + FROM bgp_routes + WHERE origin_asn = $1`, + [asn] + ); + + return { + status: 'ok', + cached: false, + data: { + resource: `AS${asn}`, + visibility: { + ipv4: { + ris_peers_seeing: Math.round(result.rows[0].avg_visibility), + total_ris_peers: 100, + sees_ris_peers: Math.round(result.rows[0].avg_visibility), + }, + ipv6: { + ris_peers_seeing: 0, + total_ris_peers: 100, + sees_ris_peers: 0, + }, + }, + }, + }; + } catch (error) { + console.error('[Local RIPE Stat] Visibility Error:', error.message); + return { status: 'error', error: error.message, data: { resource: `AS${asn}`, visibility: {} } }; + } +} + +async function getRipeStatPrefixSizeDistribution(asn) { + try { + const result = await pool.query( + `SELECT masklen(prefix) as prefix_len + FROM bgp_routes + WHERE origin_asn = $1 + ORDER BY masklen(prefix)`, + [asn] + ); + + const distribution = {}; + result.rows.forEach(row => { + const len = row.prefix_len; + distribution[len] = (distribution[len] || 0) + 1; + }); + + return { + status: 'ok', + cached: false, + data: { + resource: `AS${asn}`, + ipv4_prefix_size: Object.keys(distribution) + .map(len => ({ prefix_length: parseInt(len), count: distribution[len] })) + .sort((a, b) => a.prefix_length - b.prefix_length), + ipv6_prefix_size: [], + }, + }; + } catch (error) { + console.error('[Local RIPE Stat] Prefix Size Distribution Error:', error.message); + return { status: 'error', error: error.message, data: { resource: `AS${asn}`, ipv4_prefix_size: [] } }; + } +} + +// ══════════════════════════════════════════════════════════════ +// STATS & HEALTH CHECK +// ══════════════════════════════════════════════════════════════ + +async function getLocalDbStats() { + try { + const bgp = await pool.query(`SELECT COUNT(*) as count FROM bgp_routes`); + const rpki = await pool.query(`SELECT COUNT(*) as count FROM rpki_roas WHERE expires > NOW()`); + const threat = await pool.query(`SELECT COUNT(*) as count FROM threat_intel WHERE expires_at > NOW()`); + + return { + bgp_routes: parseInt(bgp.rows[0].count), + rpki_roas: parseInt(rpki.rows[0].count), + threat_intel: parseInt(threat.rows[0].count), + rdap_cache_entries: rdapCache.size, + }; + } catch (error) { + console.error('[Local DB] Stats Error:', error.message); + return null; + } +} + +async function cleanup() { + await pool.end(); +} + +// ══════════════════════════════════════════════════════════════ +// EXPORTS +// ══════════════════════════════════════════════════════════════ + +module.exports = { + // BGP + getBgpStatus, + getAnnouncedPrefixes, + checkBgpHijack, + + // RPKI + validateRpki, + getRoasForAsn, + + // Threat Intel + getThreatIntel, + isMaliciousIp, + + // RIPE Stat API Wrappers + getRipeStatAnnouncedPrefixes, + getRipeStatAsnNeighbours, + getRipeStatAsOverview, + getRipeStatVisibility, + getRipeStatPrefixSizeDistribution, + + // RDAP Cache + getRdapCached, + setRdapCached, + + // Health + getLocalDbStats, + cleanup, +}; diff --git a/package-lock.json b/package-lock.json index 9b05a43..3a585ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,21 +1,26 @@ { "name": "peercortex", - "version": "0.6.5", + "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "peercortex", - "version": "0.6.5", + "version": "0.7.0", "license": "MIT", "dependencies": { "@grpc/grpc-js": "^1.14.3", "@grpc/proto-loader": "^0.8.0", "@modelcontextprotocol/sdk": "^1.12.0", + "axios": "^1.6.0", "better-sqlite3": "^11.7.0", "cheerio": "^1.0.0", - "node-whois": "^2.1.3", + "fastify": "^5.8.5", + "joi": "^17.11.0", + "node-cron": "^4.6.0", "ollama": "^0.5.12", + "pg": "^8.11.0", + "playwright": "^1.40.0", "puppeteer": "^24.42.0", "zod": "^3.24.0" }, @@ -23,12 +28,15 @@ "peercortex": "dist/mcp-server/index.js" }, "devDependencies": { + "@playwright/test": "^1.40.0", "@types/better-sqlite3": "^7.6.12", "@types/node": "^22.10.0", + "@types/pg": "^8.11.0", "@typescript-eslint/eslint-plugin": "^8.18.0", "@typescript-eslint/parser": "^8.18.0", "@vitest/coverage-v8": "^2.1.0", "eslint": "^9.16.0", + "nock": "^13.4.0", "tsx": "^4.19.0", "typescript": "^5.7.0", "vitest": "^2.1.0" @@ -52,12 +60,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -66,9 +74,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -76,22 +84,22 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -101,14 +109,14 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -121,78 +129,10 @@ "dev": true, "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", - "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", - "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", - "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", - "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", - "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -206,363 +146,6 @@ "node": ">=18" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", - "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", - "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", - "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", - "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", - "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", - "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", - "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", - "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", - "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", - "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", - "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", - "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", - "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", - "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", - "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", - "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", - "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", - "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", - "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", - "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", - "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -615,9 +198,9 @@ "license": "MIT" }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -665,9 +248,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", "dev": true, "license": "MIT", "dependencies": { @@ -677,7 +260,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", + "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -689,9 +272,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -713,9 +296,9 @@ "license": "MIT" }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -754,9 +337,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", "dev": true, "license": "MIT", "engines": { @@ -790,10 +373,121 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@fastify/ajv-compiler": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@fastify/ajv-compiler/-/ajv-compiler-4.0.5.tgz", + "integrity": "sha512-KoWKW+MhvfTRWL4qrhUwAAZoaChluo0m0vbiJlGMt2GXvL4LVPQEjt8kSpHI3IBq5Rez8fg+XeH3cneztq+C7A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^3.0.0" + } + }, + "node_modules/@fastify/error": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@fastify/error/-/error-4.2.0.tgz", + "integrity": "sha512-RSo3sVDXfHskiBZKBPRgnQTtIqpi/7zhJOEmAxCiBcM7d0uwdGdxLlsCaLzGs8v8NnxIRlfG0N51p5yFaOentQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/fast-json-stringify-compiler": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/fast-json-stringify-compiler/-/fast-json-stringify-compiler-5.1.0.tgz", + "integrity": "sha512-PxcYtKLbQ8Z+yApiqjK8FwxIwvEj38k2OiLc17u8dkJSlmfi2wHHPaSnaoqBPQqtvF8YVsDgDpP2snDCfFrpfw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "fast-json-stringify": "^7.0.0" + } + }, + "node_modules/@fastify/forwarded": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@fastify/forwarded/-/forwarded-3.0.1.tgz", + "integrity": "sha512-JqDochHFqXs3C3Ml3gOY58zM7OqO9ENqPo0UqAjAjH8L01fRZqwX9iLeX34//kiJubF7r2ZQHtBRU36vONbLlw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/@fastify/merge-json-schemas": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@fastify/merge-json-schemas/-/merge-json-schemas-0.2.1.tgz", + "integrity": "sha512-OA3KGBCy6KtIvLf8DINC5880o5iBlDX4SxzLQS8HorJAbqluzLRn80UXU0bxZn7UOFhFgpRJDasfwn9nG4FG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@fastify/proxy-addr": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@fastify/proxy-addr/-/proxy-addr-5.1.0.tgz", + "integrity": "sha512-INS+6gh91cLUjB+PVHfu1UqcB76Sqtpyp7bnL+FYojhjygvOPA9ctiD/JDKsyD9Xgu4hUhCSJBPig/w7duNajw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/forwarded": "^3.0.0", + "ipaddr.js": "^2.1.0" + } + }, "node_modules/@grpc/grpc-js": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", - "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", + "version": "1.14.4", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.4.tgz", + "integrity": "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==", "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.8.0", @@ -804,14 +498,14 @@ } }, "node_modules/@grpc/proto-loader": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz", - "integrity": "sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==", + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.1.tgz", + "integrity": "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg==", "license": "Apache-2.0", "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", - "protobufjs": "^7.5.3", + "protobufjs": "^7.5.5", "yargs": "^17.7.2" }, "bin": { @@ -821,10 +515,25 @@ "node": ">=6" } }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, "node_modules/@hono/node-server": { - "version": "1.19.11", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.11.tgz", - "integrity": "sha512-dr8/3zEaB+p0D2n/IUrlPF1HZm586qgJNXK1a9fhg/PzdtkK7Ksd5l312tJX2yBuALqDYBlG20QEbayqPyxn+g==", + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", "engines": { "node": ">=18.14.1" @@ -834,29 +543,43 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, "engines": { "node": ">=18.18.0" } }, "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", "@humanwhocodes/retry": "^0.4.0" }, "engines": { "node": ">=18.18.0" } }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -904,9 +627,9 @@ } }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, "license": "MIT", "engines": { @@ -963,9 +686,9 @@ } }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.28.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.28.0.tgz", - "integrity": "sha512-gmloF+i+flI8ouQK7MWW4mOwuMh4RePBuPFAEPC6+pdqyWOUMDOixb6qZ69owLJpz6XmyllCouc4t8YWO+E2Nw==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "license": "MIT", "dependencies": { "@hono/node-server": "^1.19.9", @@ -1002,6 +725,12 @@ } } }, + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -1013,6 +742,22 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz", + "integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -1026,25 +771,24 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", - "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", "license": "BSD-3-Clause", "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "@protobufjs/aspromise": "^1.1.1" } }, "node_modules/@protobufjs/float": { @@ -1053,12 +797,6 @@ "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", "license": "BSD-3-Clause" }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", - "license": "BSD-3-Clause" - }, "node_modules/@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", @@ -1072,15 +810,15 @@ "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", "license": "BSD-3-Clause" }, "node_modules/@puppeteer/browsers": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.0.tgz", - "integrity": "sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==", + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.2.tgz", + "integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==", "license": "Apache-2.0", "dependencies": { "debug": "^4.4.3", @@ -1099,9 +837,9 @@ } }, "node_modules/@puppeteer/browsers/node_modules/tar-fs": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", - "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.3.tgz", + "integrity": "sha512-/hU4AXnIdZu+Gvl1pk0oI5f5HxWsCJRtY2aFaJdk9VvyL48DWU6iU5WAIPG+wIi1YvWA6eTJvIviP/tMAZZNwQ==", "license": "MIT", "dependencies": { "pump": "^3.0.0", @@ -1124,38 +862,10 @@ "streamx": "^2.15.0" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.0.tgz", - "integrity": "sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.0.tgz", - "integrity": "sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.0.tgz", - "integrity": "sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", "cpu": [ "arm64" ], @@ -1166,313 +876,26 @@ "darwin" ] }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.0.tgz", - "integrity": "sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.0.tgz", - "integrity": "sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "license": "BSD-3-Clause" }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.0.tgz", - "integrity": "sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.0.tgz", - "integrity": "sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.0.tgz", - "integrity": "sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.0.tgz", - "integrity": "sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.0.tgz", - "integrity": "sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.0.tgz", - "integrity": "sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.0.tgz", - "integrity": "sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.0.tgz", - "integrity": "sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.0.tgz", - "integrity": "sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.0.tgz", - "integrity": "sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.0.tgz", - "integrity": "sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.0.tgz", - "integrity": "sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.0.tgz", - "integrity": "sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.0.tgz", - "integrity": "sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.0.tgz", - "integrity": "sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.0.tgz", - "integrity": "sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.0.tgz", - "integrity": "sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.0.tgz", - "integrity": "sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.0.tgz", - "integrity": "sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.0.tgz", - "integrity": "sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "license": "BSD-3-Clause" }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", @@ -1491,9 +914,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -1505,14 +928,26 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.19.15", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.15.tgz", - "integrity": "sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==", + "version": "22.20.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz", + "integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" } }, + "node_modules/@types/pg": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz", + "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -1524,20 +959,20 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.2.tgz", - "integrity": "sha512-NZZgp0Fm2IkD+La5PR81sd+g+8oS6JwJje+aRWsDocxHkjyRw0J5L5ZTlN3LI1LlOcGL7ph3eaIUmTXMIjLk0w==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz", + "integrity": "sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/type-utils": "8.57.2", - "@typescript-eslint/utils": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/type-utils": "8.64.0", + "@typescript-eslint/utils": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1547,22 +982,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.57.2", + "@typescript-eslint/parser": "^8.64.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.2.tgz", - "integrity": "sha512-30ScMRHIAD33JJQkgfGW1t8CURZtjc2JpTrq5n2HFhOefbAhb7ucc7xJwdWcrEtqUIYJ73Nybpsggii6GtAHjA==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.64.0.tgz", + "integrity": "sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", "debug": "^4.4.3" }, "engines": { @@ -1574,18 +1009,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.2.tgz", - "integrity": "sha512-FuH0wipFywXRTHf+bTTjNyuNQQsQC3qh/dYzaM4I4W0jrCqjCVuUh99+xd9KamUfmCGPvbO8NDngo/vsnNVqgw==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.64.0.tgz", + "integrity": "sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.2", - "@typescript-eslint/types": "^8.57.2", + "@typescript-eslint/tsconfig-utils": "^8.64.0", + "@typescript-eslint/types": "^8.64.0", "debug": "^4.4.3" }, "engines": { @@ -1596,18 +1031,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.2.tgz", - "integrity": "sha512-snZKH+W4WbWkrBqj4gUNRIGb/jipDW3qMqVJ4C9rzdFc+wLwruxk+2a5D+uoFcKPAqyqEnSb4l2ULuZf95eSkw==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz", + "integrity": "sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2" + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1618,9 +1053,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.2.tgz", - "integrity": "sha512-3Lm5DSM+DCowsUOJC+YqHHnKEfFh5CoGkj5Z31NQSNF4l5wdOwqGn99wmwN/LImhfY3KJnmordBq/4+VDe2eKw==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz", + "integrity": "sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==", "dev": true, "license": "MIT", "engines": { @@ -1631,21 +1066,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.2.tgz", - "integrity": "sha512-Co6ZCShm6kIbAM/s+oYVpKFfW7LBc6FXoPXjTRQ449PPNBY8U0KZXuevz5IFuuUj2H9ss40atTaf9dlGLzbWZg==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz", + "integrity": "sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2", - "@typescript-eslint/utils": "8.57.2", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0", + "@typescript-eslint/utils": "8.64.0", "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1656,13 +1091,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.2.tgz", - "integrity": "sha512-/iZM6FnM4tnx9csuTxspMW4BOSegshwX5oBDznJ7S4WggL7Vczz5d2W11ecc4vRrQMQHXRSxzrCsyG5EsPPTbA==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.64.0.tgz", + "integrity": "sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==", "dev": true, "license": "MIT", "engines": { @@ -1674,21 +1109,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.2.tgz", - "integrity": "sha512-2MKM+I6g8tJxfSmFKOnHv2t8Sk3T6rF20A1Puk0svLK+uVapDZB/4pfAeB7nE83uAZrU6OxW+HmOd5wHVdXwXA==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz", + "integrity": "sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.57.2", - "@typescript-eslint/tsconfig-utils": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/visitor-keys": "8.57.2", + "@typescript-eslint/project-service": "8.64.0", + "@typescript-eslint/tsconfig-utils": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/visitor-keys": "8.64.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1698,20 +1133,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.2.tgz", - "integrity": "sha512-krRIbvPK1ju1WBKIefiX+bngPs+odIQUtR7kymzPfo1POVw3jlF+nLkmexdSSd4UCbDcQn+wMBATOOmpBbqgKg==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.64.0.tgz", + "integrity": "sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.2", - "@typescript-eslint/types": "8.57.2", - "@typescript-eslint/typescript-estree": "8.57.2" + "@typescript-eslint/scope-manager": "8.64.0", + "@typescript-eslint/types": "8.64.0", + "@typescript-eslint/typescript-estree": "8.64.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1722,17 +1157,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.2.tgz", - "integrity": "sha512-zhahknjobV2FiD6Ee9iLbS7OV9zi10rG26odsQdfBO/hjSzUQbkIYgda+iNKK1zNiW2ey+Lf8MU5btN17V3dUw==", + "version": "8.64.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz", + "integrity": "sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.2", + "@typescript-eslint/types": "8.64.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1902,6 +1337,12 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/abstract-logging": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", + "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==", + "license": "MIT" + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -1916,9 +1357,9 @@ } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", "bin": { @@ -1939,18 +1380,21 @@ } }, "node_modules/agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "license": "MIT", + "dependencies": { + "debug": "4" + }, "engines": { - "node": ">= 14" + "node": ">= 6.0.0" } }, "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", @@ -2036,10 +1480,57 @@ "node": ">=4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/avvio": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.3.0.tgz", + "integrity": "sha512-g2tQ7LE7oOSqDfwEm3M+ZCMTJc7KiZCdJ4UwyZJb5ckTKyYu50OYmvv0mCFXPuYXoM4zkSt8zM9XQ9KCvxA74A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/error": "^4.0.0", + "fastq": "^1.17.1" + } + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, "node_modules/b4a": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.0.tgz", - "integrity": "sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz", + "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==", "license": "Apache-2.0", "peerDependencies": { "react-native-b4a": "*" @@ -2061,9 +1552,9 @@ } }, "node_modules/bare-events": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", - "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz", + "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==", "license": "Apache-2.0", "peerDependencies": { "bare-abort-controller": "*" @@ -2075,9 +1566,9 @@ } }, "node_modules/bare-fs": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.1.tgz", - "integrity": "sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==", + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.4.tgz", + "integrity": "sha512-y1kC+ffIx/tPLdTE693uNjHfzTfr+ravR5tvWlMXe25nELbkqV400S71qHDwbkAQ1FVEZobB1NFRzFbCCcyBCQ==", "license": "Apache-2.0", "dependencies": { "bare-events": "^2.5.4", @@ -2098,30 +1589,19 @@ } } }, - "node_modules/bare-os": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.0.tgz", - "integrity": "sha512-JTjuZyNIDpw+GytMO4a6TK1VXdVKKJr6DRxEHasyuYyShV2deuiHJK/ahGZlebc+SG0/wJCB9XK8gprBGDFi/Q==", - "license": "Apache-2.0", - "engines": { - "bare": ">=1.14.0" - } - }, "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "license": "Apache-2.0", - "dependencies": { - "bare-os": "^3.0.1" - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.1.1.tgz", + "integrity": "sha512-JprUlveX3QjApC1cTpsUOiscADftCGVWkzitbHsRqv84hzYwYHw2mbluddsq5TvI8mH/8Ov1f4BiMAdcB0oYnQ==", + "license": "Apache-2.0" }, "node_modules/bare-stream": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz", - "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==", + "version": "2.13.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.3.tgz", + "integrity": "sha512-Kc+brLqvEqGkjyfiwJmImAOqLZL7OsoLKuavx+hJjgVV3nLTOjloJyPMFxjUPerGGHrNH0fLU06jjykMLWrERQ==", "license": "Apache-2.0", "dependencies": { + "b4a": "^1.8.1", "streamx": "^2.25.0", "teex": "^1.0.1" }, @@ -2143,9 +1623,9 @@ } }, "node_modules/bare-url": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.2.tgz", - "integrity": "sha512-/9a2j4ac6ckpmAHvod/ob7x439OAHst/drc2Clnq+reRYd/ovddwcF4LfoxHyNk5AuGBnPg+HqFjmE/Zpq6v0A==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz", + "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==", "license": "Apache-2.0", "dependencies": { "bare-path": "^3.0.0" @@ -2212,20 +1692,20 @@ } }, "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { "node": ">=18" @@ -2235,10 +1715,23 @@ "url": "https://opencollective.com/express" } }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -2258,9 +1751,9 @@ "license": "ISC" }, "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", "dependencies": { @@ -2555,6 +2048,18 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2563,9 +2068,9 @@ "license": "MIT" }, "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", "engines": { "node": ">=18" @@ -2620,9 +2125,9 @@ } }, "node_modules/cosmiconfig": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", - "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", "license": "MIT", "dependencies": { "env-paths": "^2.2.1", @@ -2768,6 +2273,15 @@ "node": ">= 14" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -2777,6 +2291,15 @@ "node": ">= 0.8" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -2787,9 +2310,9 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1595872", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1595872.tgz", - "integrity": "sha512-kRfgp8vWVjBu/fbYCiVFiOqsCk3CrMKEo3WbgGT2NXK2dG7vawWPBljixajVgGK9II8rDO9G0oD0zLt3I1daRg==", + "version": "0.0.1608973", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1608973.tgz", + "integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==", "license": "BSD-3-Clause" }, "node_modules/dom-serializer": { @@ -2968,9 +2491,9 @@ "license": "MIT" }, "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2979,10 +2502,25 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", - "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2993,32 +2531,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.4", - "@esbuild/android-arm": "0.27.4", - "@esbuild/android-arm64": "0.27.4", - "@esbuild/android-x64": "0.27.4", - "@esbuild/darwin-arm64": "0.27.4", - "@esbuild/darwin-x64": "0.27.4", - "@esbuild/freebsd-arm64": "0.27.4", - "@esbuild/freebsd-x64": "0.27.4", - "@esbuild/linux-arm": "0.27.4", - "@esbuild/linux-arm64": "0.27.4", - "@esbuild/linux-ia32": "0.27.4", - "@esbuild/linux-loong64": "0.27.4", - "@esbuild/linux-mips64el": "0.27.4", - "@esbuild/linux-ppc64": "0.27.4", - "@esbuild/linux-riscv64": "0.27.4", - "@esbuild/linux-s390x": "0.27.4", - "@esbuild/linux-x64": "0.27.4", - "@esbuild/netbsd-arm64": "0.27.4", - "@esbuild/netbsd-x64": "0.27.4", - "@esbuild/openbsd-arm64": "0.27.4", - "@esbuild/openbsd-x64": "0.27.4", - "@esbuild/openharmony-arm64": "0.27.4", - "@esbuild/sunos-x64": "0.27.4", - "@esbuild/win32-arm64": "0.27.4", - "@esbuild/win32-ia32": "0.27.4", - "@esbuild/win32-x64": "0.27.4" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escalade": { @@ -3071,9 +2609,9 @@ } }, "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", "dev": true, "license": "MIT", "dependencies": { @@ -3082,8 +2620,8 @@ "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -3161,9 +2699,9 @@ } }, "node_modules/eslint/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -3185,9 +2723,9 @@ "license": "MIT" }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", + "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", "dev": true, "license": "MIT", "dependencies": { @@ -3367,9 +2905,9 @@ } }, "node_modules/eventsource-parser": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", - "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.0.tgz", + "integrity": "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -3385,9 +2923,9 @@ } }, "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, "license": "Apache-2.0", "engines": { @@ -3438,12 +2976,12 @@ } }, "node_modules/express-rate-limit": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.1.tgz", - "integrity": "sha512-D1dKN+cmyPWuvB+G2SREQDzPY1agpBIcTa9sJxOPMCNeH3gwzhqJRDWCXW3gg0y//+LQ/8j52JbMROWyrKdMdw==", + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", + "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", "license": "MIT", "dependencies": { - "ip-address": "10.1.0" + "ip-address": "^10.2.0" }, "engines": { "node": ">= 16" @@ -3475,6 +3013,12 @@ "@types/yauzl": "^2.9.1" } }, + "node_modules/fast-decode-uri-component": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.1.tgz", + "integrity": "sha512-WKgKWg5eUxvRZGwW8FvfbaH7AXSh2cL+3j5fMGzUMCxWBJ3dV3a7Wz8y2f/uQ0e3B6WmodD3oS54jTQ9HVTIIg==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3494,17 +3038,34 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" + "node_modules/fast-json-stringify": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-7.0.1.tgz", + "integrity": "sha512-eRSayARSbbwlBjpP4vnTTIRD5QPcIrmihPxDeN1DtKnHPg66UuJLx+8hlK1kaFdjvzyQ/dzALoi4vwAQ+T+iZA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/merge-json-schemas": "^0.2.0", + "ajv": "^8.12.0", + "ajv-formats": "^3.0.1", + "fast-uri": "^4.0.0", + "json-schema-ref-resolver": "^3.0.0", + "rfdc": "^1.2.0" + } }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "node_modules/fast-json-stringify/node_modules/fast-uri": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-4.1.0.tgz", + "integrity": "sha512-ZodJ2cRiLVWGi9IgPb3mbgSqM4CD3LexCHkuv0FfBXHJI1ADfucTD06m6clO2Cy5RZYsw/SiCVl/dyrFI/SYWA==", "funding": [ { "type": "github", @@ -3517,6 +3078,80 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-querystring": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/fast-querystring/-/fast-querystring-1.1.2.tgz", + "integrity": "sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==", + "license": "MIT", + "dependencies": { + "fast-decode-uri-component": "^1.0.1" + } + }, + "node_modules/fast-uri": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/fastify": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.10.0.tgz", + "integrity": "sha512-A9L0ziuWGQHgEEVgF3davQ9vbD93IuX+lo2IsxapQmu5b/Y/ynn9m9K5JHt9dvyJXOFc5iN0Zk5GHEOqnzhWjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "@fastify/ajv-compiler": "^4.0.5", + "@fastify/error": "^4.0.0", + "@fastify/fast-json-stringify-compiler": "^5.0.0", + "@fastify/proxy-addr": "^5.0.0", + "abstract-logging": "^2.0.1", + "avvio": "^9.0.0", + "fast-json-stringify": "^7.0.0", + "find-my-way": "^9.6.0", + "light-my-request": "^6.0.0", + "pino": "^9.14.0 || ^10.1.0", + "process-warning": "^5.0.0", + "rfdc": "^1.3.1", + "secure-json-parse": "^4.0.0", + "semver": "^7.6.0", + "toad-cache": "^3.7.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -3584,6 +3219,20 @@ "url": "https://opencollective.com/express" } }, + "node_modules/find-my-way": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.6.0.tgz", + "integrity": "sha512-Zf4Xve4RymLl7NgaavNebZ01joJ8MfVerOG43wy7SHLO+r+K0C6d/SE0BiR7AV5V1VOCFlOP7ecdo+I4qmiHrQ==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-querystring": "^1.0.0", + "safe-regex2": "^5.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3622,6 +3271,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -3639,6 +3308,43 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -3664,10 +3370,9 @@ "license": "MIT" }, "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3748,19 +3453,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/get-tsconfig": { - "version": "4.13.7", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", - "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, "node_modules/get-uri": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", @@ -3824,9 +3516,9 @@ "license": "MIT" }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", "dev": true, "license": "MIT", "dependencies": { @@ -3896,10 +3588,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3909,9 +3616,9 @@ } }, "node_modules/hono": { - "version": "4.12.9", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.9.tgz", - "integrity": "sha512-wy3T8Zm2bsEvxKZM5w21VdHDDcwVS1yUFFY6i8UobSsKfFceT7TOwhbhfKsDyx7tYQlmRM5FLpIuYvNFyjctiA==", + "version": "4.12.30", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.30.tgz", + "integrity": "sha512-emn+JoJjrN9YTpRDS5it/UI2SO9BAE37T6I3d963RxcZ81G9A4pr2SZTEiiaiKbzx+NKRg5BZ89fCL7gCJCUog==", "license": "MIT", "engines": { "node": ">=16.9.0" @@ -3988,17 +3695,26 @@ "node": ">= 14" } }, + "node_modules/http-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", + "agent-base": "6", "debug": "4" }, "engines": { - "node": ">= 14" + "node": ">= 6" } }, "node_modules/iconv-lite": { @@ -4034,9 +3750,9 @@ "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, "license": "MIT", "engines": { @@ -4082,21 +3798,21 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", + "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", "engines": { "node": ">= 12" } }, "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.4.0.tgz", + "integrity": "sha512-9VGk3HGanVE6JoZXHiCpnGy5X0jYDnN4EA4lntFPj+1vIWlFhIylq2CrrCOJH9EAhc5CYhq18F2Av2tgoAPsYQ==", "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">= 10" } }, "node_modules/is-arrayish": { @@ -4219,10 +3935,23 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/joi": { + "version": "17.13.4", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.4.tgz", + "integrity": "sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, "node_modules/jose": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", - "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", + "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" @@ -4235,9 +3964,19 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -4259,6 +3998,25 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "license": "MIT" }, + "node_modules/json-schema-ref-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-resolver/-/json-schema-ref-resolver-3.0.0.tgz", + "integrity": "sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -4278,6 +4036,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true, + "license": "ISC" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4302,6 +4067,56 @@ "node": ">= 0.8.0" } }, + "node_modules/light-my-request": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-6.6.0.tgz", + "integrity": "sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause", + "dependencies": { + "cookie": "^1.0.1", + "process-warning": "^4.0.0", + "set-cookie-parser": "^2.6.0" + } + }, + "node_modules/light-my-request/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/light-my-request/node_modules/process-warning": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", + "integrity": "sha512-3c2LzQ3rY9d0hc1emcsHhfT9Jwz0cChib/QN89oME2R451w5fy3f0afAhERFZAwrbDU43wk12d0ORBpDVME50Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -4351,11 +4166,13 @@ "license": "MIT" }, "node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, "node_modules/magic-string": { "version": "0.30.21", @@ -4463,13 +4280,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -4478,12 +4295,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha512-iotkTvxc+TwOm5Ieim8VnSNvCDjCK9S8G3scJ50ZthspSxa7jx50jkhYduuAtAjvfDUwSgOwf8+If99AlOEhyw==", - "license": "MIT" - }, "node_modules/minipass": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", @@ -4513,9 +4324,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "dev": true, "funding": [ { @@ -4562,10 +4373,25 @@ "node": ">= 0.4.0" } }, + "node_modules/nock": { + "version": "13.5.6", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz", + "integrity": "sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, "node_modules/node-abi": { - "version": "3.89.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.89.0.tgz", - "integrity": "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA==", + "version": "3.94.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz", + "integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -4574,15 +4400,13 @@ "node": ">=10" } }, - "node_modules/node-whois": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/node-whois/-/node-whois-2.1.3.tgz", - "integrity": "sha512-8Ysz5s5AfSxiLKiFrFbWma3YBe+e/13uGrfJCemztdX5e2cbbJ61w28YHoVJ/u6jx1porvaBthbciSppz7UutQ==", - "deprecated": "WARNING: This project has been renamed from node-whois to whois. Install using whois instead.", - "license": "FreeBSD", - "dependencies": { - "optimist": "^0.6.1", - "underscore": "~1.5.2" + "node_modules/node-cron": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.6.0.tgz", + "integrity": "sha512-Si/bzYiKRHOB8/a99T2+SDGN582ONDMSTlJr5oCkT6GtnqPjZ2s10eoQRYkW9ZHwjVxONL+W8Fb+qR0AHMQsdg==", + "license": "ISC", + "engines": { + "node": ">=20" } }, "node_modules/nth-check": { @@ -4627,6 +4451,15 @@ "whatwg-fetch": "^3.6.20" } }, + "node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -4648,16 +4481,6 @@ "wrappy": "1" } }, - "node_modules/optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha512-snN4O4TkigujZphWLN0E//nQmm7790RYaE53DdL7ZYwee2D8DDo9/EyYiKUfN3rneWUjhJnueija3G9I2i0h3g==", - "license": "MIT/X11", - "dependencies": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4727,6 +4550,28 @@ "node": ">= 14" } }, + "node_modules/pac-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/pac-resolver": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", @@ -4871,10 +4716,17 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", "funding": { "type": "opencollective", @@ -4904,6 +4756,95 @@ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", "license": "MIT" }, + "node_modules/pg": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.22.0.tgz", + "integrity": "sha512-8wih1vVIBMxoUM2oB4soJsD9tDnDpLv4OXBJ+EJzFsvycD+lfyIreC2gGHq78f8jbLLt+bvlPTFdFZfJkOuzAA==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.14.0", + "pg-pool": "^3.14.0", + "pg-protocol": "^1.15.0", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.4.0" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", + "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz", + "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.14.0.tgz", + "integrity": "sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.15.0.tgz", + "integrity": "sha512-cq9sECI5s0+uPUXjbz8ioyPJni6RzsRib0US67i5IoTZKw8fNeYlVE7u8F4dG7vEJJtc5wdD1K189lCCUwqWTQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -4911,9 +4852,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -4923,6 +4864,43 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pino": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", + "integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==", + "license": "MIT", + "dependencies": { + "@pinojs/redact": "^0.4.0", + "atomic-sleep": "^1.0.0", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^3.0.0", + "pino-std-serializers": "^7.0.0", + "process-warning": "^5.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.2.0", + "safe-stable-stringify": "^2.3.1", + "sonic-boom": "^4.0.1", + "thread-stream": "^4.0.0" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", + "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", + "license": "MIT" + }, "node_modules/pkce-challenge": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", @@ -4932,10 +4910,40 @@ "node": ">=16.20.0" } }, + "node_modules/playwright": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz", + "integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.61.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.61.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz", + "integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", "dev": true, "funding": [ { @@ -4953,7 +4961,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -4961,6 +4969,45 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz", + "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -5007,6 +5054,22 @@ "node": ">= 0.8.0" } }, + "node_modules/process-warning": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz", + "integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -5016,25 +5079,34 @@ "node": ">=0.4.0" } }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/protobufjs": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz", - "integrity": "sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==", + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", - "long": "^5.0.0" + "long": "^5.3.2" }, "engines": { "node": ">=12.0.0" @@ -5053,6 +5125,15 @@ "node": ">= 0.10" } }, + "node_modules/proxy-addr/node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/proxy-agent": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", @@ -5072,21 +5153,43 @@ "node": ">= 14" } }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "license": "ISC", + "node_modules/proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", "engines": { - "node": ">=12" + "node": ">= 14" } }, - "node_modules/proxy-from-env": { + "node_modules/proxy-agent/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-agent/node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/pump": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", @@ -5108,18 +5211,18 @@ } }, "node_modules/puppeteer": { - "version": "24.42.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.42.0.tgz", - "integrity": "sha512-94MoPfFp2eY3eYIMdINkez4IOP5TMHntlZbVx06fHlQTtiQiYgaY0L2Zzfod8PVUkPqP7m3Qlre2v8YS8cudPA==", + "version": "24.43.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.43.1.tgz", + "integrity": "sha512-/FSOViCrqRdb1HDocpsM9Z1giA71gTQPUt3SpHGVRALKAy/rJr1fLFYZW9F23qPxqVxTHQnbh/5B5opJST3kAw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.13.0", + "@puppeteer/browsers": "2.13.2", "chromium-bidi": "14.0.0", "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1595872", - "puppeteer-core": "24.42.0", - "typed-query-selector": "^2.12.1" + "devtools-protocol": "0.0.1608973", + "puppeteer-core": "24.43.1", + "typed-query-selector": "^2.12.2" }, "bin": { "puppeteer": "lib/cjs/puppeteer/node/cli.js" @@ -5129,30 +5232,31 @@ } }, "node_modules/puppeteer-core": { - "version": "24.42.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.42.0.tgz", - "integrity": "sha512-T4zXokk/izH01fYPhyyev1A4piWiOKrYq7CUFpdoYQxmOnXoV6YjUabmfIjCYkNspSoAXIxRid3Tw+Vg0fthYg==", + "version": "24.43.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.1.tgz", + "integrity": "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==", "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.13.0", + "@puppeteer/browsers": "2.13.2", "chromium-bidi": "14.0.0", "debug": "^4.4.3", - "devtools-protocol": "0.0.1595872", - "typed-query-selector": "^2.12.1", + "devtools-protocol": "0.0.1608973", + "typed-query-selector": "^2.12.2", "webdriver-bidi-protocol": "0.4.1", - "ws": "^8.19.0" + "ws": "^8.20.0" }, "engines": { "node": ">=18" } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.15.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", + "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", "license": "BSD-3-Clause", "dependencies": { - "side-channel": "^1.1.0" + "es-define-property": "^1.0.1", + "side-channel": "^1.1.1" }, "engines": { "node": ">=0.6" @@ -5161,13 +5265,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz", + "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==", "license": "MIT", "engines": { "node": ">= 0.6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/raw-body": { @@ -5186,9 +5300,9 @@ } }, "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -5248,6 +5362,15 @@ "node": ">= 6" } }, + "node_modules/real-require": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5275,24 +5398,39 @@ "node": ">=4" } }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, + "node_modules/ret": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.5.0.tgz", + "integrity": "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw==", "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + "engines": { + "node": ">=10" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "license": "MIT" + }, "node_modules/rollup": { - "version": "4.60.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", - "integrity": "sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==", + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -5302,31 +5440,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.60.0", - "@rollup/rollup-android-arm64": "4.60.0", - "@rollup/rollup-darwin-arm64": "4.60.0", - "@rollup/rollup-darwin-x64": "4.60.0", - "@rollup/rollup-freebsd-arm64": "4.60.0", - "@rollup/rollup-freebsd-x64": "4.60.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.60.0", - "@rollup/rollup-linux-arm-musleabihf": "4.60.0", - "@rollup/rollup-linux-arm64-gnu": "4.60.0", - "@rollup/rollup-linux-arm64-musl": "4.60.0", - "@rollup/rollup-linux-loong64-gnu": "4.60.0", - "@rollup/rollup-linux-loong64-musl": "4.60.0", - "@rollup/rollup-linux-ppc64-gnu": "4.60.0", - "@rollup/rollup-linux-ppc64-musl": "4.60.0", - "@rollup/rollup-linux-riscv64-gnu": "4.60.0", - "@rollup/rollup-linux-riscv64-musl": "4.60.0", - "@rollup/rollup-linux-s390x-gnu": "4.60.0", - "@rollup/rollup-linux-x64-gnu": "4.60.0", - "@rollup/rollup-linux-x64-musl": "4.60.0", - "@rollup/rollup-openbsd-x64": "4.60.0", - "@rollup/rollup-openharmony-arm64": "4.60.0", - "@rollup/rollup-win32-arm64-msvc": "4.60.0", - "@rollup/rollup-win32-ia32-msvc": "4.60.0", - "@rollup/rollup-win32-x64-gnu": "4.60.0", - "@rollup/rollup-win32-x64-msvc": "4.60.0", + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", "fsevents": "~2.3.2" } }, @@ -5366,16 +5504,63 @@ ], "license": "MIT" }, + "node_modules/safe-regex2": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/safe-regex2/-/safe-regex2-5.1.1.tgz", + "integrity": "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT", + "dependencies": { + "ret": "~0.5.0" + }, + "bin": { + "safe-regex2": "bin/safe-regex2.js" + } + }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5429,6 +5614,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -5457,14 +5648,14 @@ } }, "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" }, @@ -5476,13 +5667,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -5604,9 +5795,9 @@ } }, "node_modules/socks": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.8.tgz", - "integrity": "sha512-NlGELfPrgX2f1TAAcz0WawlLn+0r3FyhhCRpFFK2CemXenPYvzMWWZINv3eDNo9ucdwme7oCHRY0Jnbs4aIkog==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", "license": "MIT", "dependencies": { "ip-address": "^10.1.1", @@ -5631,13 +5822,22 @@ "node": ">= 14" } }, - "node_modules/socks/node_modules/ip-address": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.1.tgz", - "integrity": "sha512-1FMu8/N15Ck1BL551Jf42NYIoin2unWjLQ2Fze/DXryJRl5twqtwNHlO39qERGbIOcKYWHdgRryhOC+NG4eaLw==", + "node_modules/socks-proxy-agent/node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { - "node": ">= 12" + "node": ">= 14" + } + }, + "node_modules/sonic-boom": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", + "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" } }, "node_modules/source-map": { @@ -5660,6 +5860,15 @@ "node": ">=0.10.0" } }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -5684,9 +5893,9 @@ "license": "MIT" }, "node_modules/streamx": { - "version": "2.25.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.25.0.tgz", - "integrity": "sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==", + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.28.0.tgz", + "integrity": "sha512-1Yowhzjf0ivGMrTIkY9hav5TxobO9qIVqUE41fiCGMGgc3CLlf4MY+9AHmZqBWgDTue0fY9zWjYFVyf6Diuobw==", "license": "MIT", "dependencies": { "events-universal": "^1.0.0", @@ -5834,9 +6043,9 @@ } }, "node_modules/tar-fs": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", - "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", "license": "MIT", "dependencies": { "chownr": "^1.1.1", @@ -5894,6 +6103,24 @@ "b4a": "^1.6.4" } }, + "node_modules/thread-stream": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.2.0.tgz", + "integrity": "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ==", + "license": "MIT", + "dependencies": { + "real-require": "^1.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/thread-stream/node_modules/real-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-1.0.0.tgz", + "integrity": "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==", + "license": "MIT" + }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -5909,14 +6136,14 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -5955,6 +6182,15 @@ "node": ">=14.0.0" } }, + "node_modules/toad-cache": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.4.tgz", + "integrity": "sha512-m1TdR/rvT7kgGJZhspNtXdsdYk0fddFpJJFlG5s+UkPFo6lkLoZ3YLOaovPYjq1R75NP5JfeTlSHaOsE09peCg==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -5984,14 +6220,13 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", - "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.1.tgz", + "integrity": "sha512-GQHnkIfxyx1wYCOS/wonik5MVRZU9hi1TEZmzGZSCJB1y9YgoZ8H6itNE/u4suE+yLmOzuE4E5S4TZ/ZX2wcWQ==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "~0.27.0", - "get-tsconfig": "^4.7.5" + "esbuild": "~0.28.0" }, "bin": { "tsx": "dist/cli.mjs" @@ -6003,6 +6238,21 @@ "fsevents": "~2.3.3" } }, + "node_modules/tsx/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -6029,17 +6279,34 @@ } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { - "content-type": "^1.0.5", + "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/typed-query-selector": { @@ -6062,15 +6329,10 @@ "node": ">=14.17" } }, - "node_modules/underscore": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.5.2.tgz", - "integrity": "sha512-yejOFsRnTJs0N9CK5Apzf6maDO2djxGoLLrlZlvGs2o9ZQuhIhDL18rtFyy4FBIbOkzA6+4hDgXbgz5EvDQCXQ==" - }, "node_modules/undici": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.6.tgz", - "integrity": "sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", "license": "MIT", "engines": { "node": ">=20.18.1" @@ -6199,74 +6461,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/vite/node_modules/@esbuild/darwin-arm64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", @@ -6284,312 +6478,6 @@ "node": ">=12" } }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/vite/node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -6629,6 +6517,21 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/vitest": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", @@ -6771,15 +6674,6 @@ "node": ">=0.10.0" } }, - "node_modules/wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha512-1tMA907+V4QmxV7dbRvb4/8MaRALK6q9Abid3ndMYnbyo8piisCmeONVqVSXqQA3KaP4SLt5b7ud6E2sqP8TFw==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", @@ -6882,9 +6776,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -6902,6 +6796,15 @@ } } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -6912,9 +6815,9 @@ } }, "node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "version": "17.7.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", + "integrity": "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g==", "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -7012,12 +6915,12 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.25.28 || ^4" } } } diff --git a/package.json b/package.json index 83200d5..393fdab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "peercortex", - "version": "0.6.5", + "version": "0.7.0", "description": "AI-Powered Network Intelligence Platform — MCP Server for PeeringDB, RIPE Stat, BGP analysis, RPKI monitoring, and peering automation. Powered by local Ollama.", "main": "dist/mcp-server/index.js", "types": "dist/mcp-server/index.d.ts", @@ -53,10 +53,10 @@ "license": "MIT", "repository": { "type": "git", - "url": "https://github.com/renefichtmueller/PeerCortex.git" + "url": "https://gitea.context-x.org/rene/PeerCortex.git" }, "bugs": { - "url": "https://github.com/renefichtmueller/PeerCortex/issues" + "url": "https://gitea.context-x.org/rene/PeerCortex/issues" }, "homepage": "https://peercortex.org", "engines": { @@ -66,20 +66,28 @@ "@grpc/grpc-js": "^1.14.3", "@grpc/proto-loader": "^0.8.0", "@modelcontextprotocol/sdk": "^1.12.0", + "axios": "^1.6.0", "better-sqlite3": "^11.7.0", "cheerio": "^1.0.0", - "node-whois": "^2.1.3", + "fastify": "^5.8.5", + "joi": "^17.11.0", + "node-cron": "^4.6.0", "ollama": "^0.5.12", + "pg": "^8.11.0", + "playwright": "^1.40.0", "puppeteer": "^24.42.0", "zod": "^3.24.0" }, "devDependencies": { + "@playwright/test": "^1.40.0", "@types/better-sqlite3": "^7.6.12", "@types/node": "^22.10.0", + "@types/pg": "^8.11.0", "@typescript-eslint/eslint-plugin": "^8.18.0", "@typescript-eslint/parser": "^8.18.0", "@vitest/coverage-v8": "^2.1.0", "eslint": "^9.16.0", + "nock": "^13.4.0", "tsx": "^4.19.0", "typescript": "^5.7.0", "vitest": "^2.1.0" diff --git a/public/index.html b/public/index.html index 78bbb26..7fd2df1 100644 --- a/public/index.html +++ b/public/index.html @@ -4,6 +4,8 @@ PeerCortex — The ASN News + + diff --git a/server.js b/server.js index b3c708b..daf60a8 100644 --- a/server.js +++ b/server.js @@ -3,6 +3,10 @@ const http = require("http"); const https = require("https"); const crypto = require("crypto"); +// ── LOCAL DATABASE CLIENT (BGP, RPKI, Threat Intel) ────────────── +const localDb = require('./local-db-client'); +console.log('[PeerCortex] Local DB client initialized'); + // ── PDF Export: lazy puppeteer loader ──────────────────────────────────────── let _puppeteerBrowser = null; let _puppeteerLoading = false; @@ -49,22 +53,7 @@ function pdfCacheSet(key, buf) { } // Load .env file -const envPath = "/opt/peercortex-app/.env"; -try { - const envContent = fs.readFileSync(envPath, "utf8"); - envContent.split("\n").forEach((line) => { - const trimmed = line.trim(); - if (!trimmed || trimmed.startsWith("#")) return; - const eqIdx = trimmed.indexOf("="); - if (eqIdx > 0) { - const key = trimmed.substring(0, eqIdx).trim(); - const val = trimmed.substring(eqIdx + 1).trim(); - if (!process.env[key]) process.env[key] = val; - } - }); -} catch (_e) { - console.warn("Warning: Could not read .env file at", envPath); -} +require('./src/backend/config'); const BGPROUTES_API_KEY = process.env.BGPROUTES_API_KEY || ""; const BGPROUTES_API_URL = process.env.BGPROUTES_API_URL || "https://api.bgproutes.io/v1"; @@ -253,80 +242,7 @@ const FEEDBACK_FILE = "/opt/peercortex-app/feedback.json"; const VISITORS_FILE = "/opt/peercortex-app/visitors.json"; // ── SMTP / Email ────────────────────────────────────────────── -const SMTP_HOST = 'mail.fichtmueller.org'; -const SMTP_PORT = 587; -const SMTP_USER = process.env.SMTP_USER; -const SMTP_PASS = process.env.SMTP_PASS; -const MAIL_TO = 'peercortex@context-x.org'; -const MAIL_FROM = 'PeerCortex Feedback '; - -function sendFeedbackMail(entry) { - return new Promise(function(resolve, reject) { - var tls = require('tls'); - var net = require('net'); - var b64 = function(s) { return Buffer.from(s).toString('base64'); }; - var CRLF = '\r\n'; - var body = 'Category : ' + entry.category + CRLF + - 'Name : ' + entry.name + CRLF + - 'ASN : ' + (entry.asn || '-') + CRLF + - 'Time : ' + entry.timestamp + CRLF + CRLF + - entry.message + CRLF + CRLF + '-' + CRLF + 'PeerCortex Feedback'; - var subj = '[PeerCortex Feedback] ' + entry.category + (entry.asn ? ' - AS' + entry.asn : ''); - var msg = 'From: ' + MAIL_FROM + CRLF + - 'To: ' + MAIL_TO + CRLF + - 'Subject: ' + subj + CRLF + - 'MIME-Version: 1.0' + CRLF + - 'Content-Type: text/plain; charset=UTF-8' + CRLF + CRLF + - body; - - var socket = net.connect(SMTP_PORT, SMTP_HOST); - var tlsSocket = null; - var buf = ''; - var step = 0; - var done = false; - - function send(line) { - var s = tlsSocket || socket; - s.write(line + CRLF); - } - - function onData(data) { - buf += data.toString(); - var lines = buf.split(CRLF); - buf = lines.pop(); - for (var i = 0; i < lines.length; i++) { - var line = lines[i]; - var code = parseInt(line.slice(0, 3)); - if (isNaN(code) || line[3] === '-') continue; - if (step === 0 && code === 220) { send('EHLO peercortex.org'); step = 1; } - else if (step === 1 && code === 250) { send('STARTTLS'); step = 2; } - else if (step === 2 && code === 220) { - tlsSocket = tls.connect({ socket: socket, servername: SMTP_HOST, rejectUnauthorized: false }, function() { - tlsSocket.on('data', onData); - send('EHLO peercortex.org'); - step = 3; - }); - tlsSocket.on('error', function(e) { if (!done) { done = true; reject(e); } }); - } - else if (step === 3 && code === 250) { send('AUTH LOGIN'); step = 4; } - else if (step === 4 && code === 334) { send(b64(SMTP_USER)); step = 5; } - else if (step === 5 && code === 334) { send(b64(SMTP_PASS)); step = 6; } - else if (step === 6 && code === 235) { send('MAIL FROM:<' + SMTP_USER + '>'); step = 7; } - else if (step === 7 && code === 250) { send('RCPT TO:<' + MAIL_TO + '>'); step = 8; } - else if (step === 8 && code === 250) { send('DATA'); step = 9; } - else if (step === 9 && code === 354) { send(msg + CRLF + '.'); step = 10; } - else if (step === 10 && code === 250) { send('QUIT'); if (!done) { done = true; resolve(); } } - else if (code >= 400) { if (!done) { done = true; reject(new Error('SMTP ' + code + ': ' + line)); } } - } - } - - socket.on('data', onData); - socket.on('error', function(e) { if (!done) { done = true; reject(e); } }); - setTimeout(function() { if (!done) { done = true; reject(new Error('SMTP timeout')); } }, 15000); - }); -} - - +const { sendFeedbackMail } = require('./src/backend/services/smtp'); // ── SMTP / Email ────────────────────────────────────────────── @@ -350,59 +266,7 @@ function trackVisitor(req) { // ═══════════════════════════════════════════════════════════════ // ── BGP Community Database ───────────────────────────────────── -const BGP_COMMUNITY_DB = { - '65535:666': { name:'BLACKHOLE', desc:'RFC 7999 — Null-route this prefix', type:'rfc' }, - '65535:65281':{ name:'NO_EXPORT', desc:'RFC 1997 — Do not export to EBGP peers', type:'rfc' }, - '65535:65282':{ name:'NO_ADVERTISE', desc:'RFC 1997 — Do not advertise to any peer', type:'rfc' }, - '65535:65283':{ name:'NO_EXPORT_SUBCONFED', desc:'RFC 1997 — No export to sub-AS', type:'rfc' }, - // Lumen/CenturyLink 3356 - '3356:2': { name:'Lumen Peer', desc:'Lumen — Learned from settlement-free peer', type:'carrier', asn:3356 }, - '3356:3': { name:'Lumen Customer', desc:'Lumen — Learned from customer', type:'carrier', asn:3356 }, - '3356:100':{ name:'Lumen Blackhole', desc:'Lumen — RTBH trigger', type:'carrier', asn:3356 }, - // NTT 2914 - '2914:420':{ name:'NTT Peer', desc:'NTT — Settlement-free peer route', type:'carrier', asn:2914 }, - '2914:421':{ name:'NTT Customer', desc:'NTT — Downstream customer route', type:'carrier', asn:2914 }, - '2914:666':{ name:'NTT Blackhole', desc:'NTT — RTBH trigger', type:'carrier', asn:2914 }, - // Cogent 174 - '174:21000':{ name:'Cogent Peer', desc:'Cogent — Learned from peer', type:'carrier', asn:174 }, - '174:22000':{ name:'Cogent Customer', desc:'Cogent — Learned from customer', type:'carrier', asn:174 }, - '174:666': { name:'Cogent Blackhole', desc:'Cogent — RTBH trigger', type:'carrier', asn:174 }, - // HE 6939 - '6939:7000':{ name:'HE RTBH', desc:'Hurricane Electric — Remotely triggered blackhole', type:'carrier', asn:6939 }, - // Telia 1299 - '1299:35000':{ name:'Telia RTBH', desc:'Telia — Remotely triggered blackhole', type:'carrier', asn:1299 }, - '1299:3000': { name:'Telia Peer', desc:'Telia — Learned from peer', type:'carrier', asn:1299 }, - // DTAG 3320 - '3320:1278':{ name:'DTAG Peer', desc:'Deutsche Telekom — Peering route', type:'carrier', asn:3320 }, - '3320:2001':{ name:'DTAG Customer', desc:'Deutsche Telekom — Customer route', type:'carrier', asn:3320 }, - '3320:9900':{ name:'DTAG Blackhole', desc:'Deutsche Telekom — RTBH trigger', type:'carrier', asn:3320 }, - // Cloudflare 13335 - '13335:10000':{ name:'CF Customer', desc:'Cloudflare — Customer route', type:'carrier', asn:13335 }, - '13335:10010':{ name:'CF Peering', desc:'Cloudflare — Learned via peering', type:'carrier', asn:13335 }, - '13335:20050':{ name:'CF Blackhole', desc:'Cloudflare — RTBH trigger', type:'carrier', asn:13335 }, - // Zayo 6461 - '6461:9000':{ name:'Zayo Blackhole', desc:'Zayo — RTBH trigger', type:'carrier', asn:6461 }, - // DE-CIX 6695 - '6695:1000':{ name:'DE-CIX RS', desc:'DE-CIX Frankfurt — Route server export', type:'ixp', asn:6695 }, - '6695:1001':{ name:'DE-CIX RS peer', desc:'DE-CIX — Received from route server peer', type:'ixp', asn:6695 }, - // AMS-IX 1200 - '1200:100': { name:'AMS-IX RS', desc:'AMS-IX — Route server export', type:'ixp', asn:1200 }, - // LINX 5459 - '5459:1001':{ name:'LINX RS', desc:'LINX — Route server export', type:'ixp', asn:5459 }, - // Seabone/TI 6762 - '6762:30': { name:'Seabone Customer', desc:'Telecom Italia Seabone — Customer route', type:'carrier', asn:6762 }, - // Turkcell 9121 - '9121:666': { name:'Turkcell BH', desc:'Turkcell — RTBH trigger', type:'carrier', asn:9121 }, -}; - -function decodeCommunities(communityList) { - if (!Array.isArray(communityList)) return []; - return communityList.map(c => { - const key = Array.isArray(c) ? c.join(':') : String(c); - const known = BGP_COMMUNITY_DB[key]; - return { raw: key, known: known || null }; - }); -} +// Migrated to src/features/bgp-communities/bgp-community-db.ts // ── Hijack Monitoring ────────────────────────────────────────── // ============================================================ @@ -505,13 +369,18 @@ async function notifyWebhooks(asn, alert) { } } +// Returns null (not []) when the prefix lookup itself failed -- distinct from a +// genuine zero-prefix result. runHijackCheck must not treat these the same way: +// setting an empty baseline from a transient failure would permanently disable +// hijack detection for that subscription (baseline.size stays 0 forever after, +// so "unexpected" never fires again -- see the baseline.size > 0 guard below). async function checkHijacksForAsn(asn) { try { - const url = `https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS${asn}`; - const data = await fetchJSON(url, { timeout: 8000 }); + const data = await localDb.getRipeStatAnnouncedPrefixes(asn); + if (data && data.status === 'error') return null; const prefixes = (data && data.data && data.data.prefixes || []).map(p => p.prefix); return prefixes; - } catch (_) { return []; } + } catch (_) { return null; } } async function runHijackCheck() { @@ -521,6 +390,12 @@ async function runHijackCheck() { let changed = false; for (const sub of subs) { const current = await checkHijacksForAsn(sub.asn); + if (current === null) { + // Lookup failed this cycle -- skip both anomaly detection and baseline + // initialization; retry on the next 30-minute run rather than locking in + // an empty baseline that would silently disable detection forever. + continue; + } const baseline = new Set(sub.prefixes || []); const unexpected = current.filter(p => baseline.size > 0 && !baseline.has(p)); const missing = [...baseline].filter(p => !current.includes(p)); @@ -1054,7 +929,7 @@ function buildPdfHtml(data, aspa, format) { const rpkiTotal = rpki.total_checked || rpkiDetails.length || 0; const rpkiValid = rpki.with_roa || rpkiDetails.filter(v => v.status === 'valid').length; const rpkiInvalid = rpkiDetails.filter(v => v.status === 'invalid').length; - const rpkiNotFound = rpkiDetails.filter(v => v.status === 'not-found' || v.status === 'unknown').length; + const rpkiNotFound = rpkiDetails.filter(v => v.status === 'not_found').length; const passedChecks = checks.filter(c => c.status === 'pass').length; const failedChecks = checks.filter(c => c.status !== 'pass' && c.status !== 'info').length; @@ -1689,6 +1564,7 @@ const TIER1_ASNS = new Set([ ]); const CACHE_TTL_LOOKUP = 5 * 60 * 1000; // 5 minutes +const CACHE_TTL_LOOKUP_DEGRADED = 90 * 1000; // matches the existing 90s guard for suspicious /api/validate zero-counts const CACHE_TTL_ASPA = 4 * 60 * 60 * 1000; // 4 hours const CACHE_TTL_NEWS = 10 * 60 * 1000; // 10 minutes const CACHE_TTL_DEFAULT = 5 * 60 * 1000; // 5 minutes @@ -1714,16 +1590,22 @@ function rdapCacheSet(asn, data) { // ============================================================ // WHOIS Cache — 24h TTL, prevents repeated RDAP hammering // ============================================================ -const whoisCache = new Map(); // key: asn string, value: { data, ts } +const whoisCache = new Map(); // key: asn string, value: { data, ts, ttl } const WHOIS_CACHE_TTL = 24 * 60 * 60 * 1000; +// A "not found" result after all 5 RIR sources failed is far more often a shared +// network blip (DNS, outbound connectivity, simultaneous rate-limit) than a real +// ASN existing nowhere across RIPE+APNIC+ARIN+LACNIC+AFRINIC -- fetchJSON can't +// tell timeout/network-error apart from a genuine 404, so cache that specific +// conclusion much more briefly than a confirmed, parsed WHOIS record. +const WHOIS_NOT_FOUND_CACHE_TTL = 10 * 60 * 1000; function whoisCacheGet(asn) { const e = whoisCache.get(String(asn)); - if (e && (Date.now() - e.ts) < WHOIS_CACHE_TTL) return e.data; + if (e && (Date.now() - e.ts) < (e.ttl || WHOIS_CACHE_TTL)) return e.data; return undefined; } -function whoisCacheSet(asn, data) { +function whoisCacheSet(asn, data, ttl) { if (whoisCache.size > 5000) whoisCache.delete(whoisCache.keys().next().value); - whoisCache.set(String(asn), { data, ts: Date.now() }); + whoisCache.set(String(asn), { data, ts: Date.now(), ttl: ttl || WHOIS_CACHE_TTL }); } // ============================================================ @@ -2187,7 +2069,18 @@ class Semaphore { const ripeStatSemaphore = new Semaphore(15); // Cached + throttled RIPE Stat fetch -async function fetchRipeStatCached(url, options) { +// Renamed from fetchRipeStatCached 2026-07-16: a second function with that exact +// name was added ~150 lines down (the local-DB-routing wrapper) during the Postgres +// refactor. In JS, two `function` declarations sharing a name silently collapse to +// the LAST one in source order -- the real RIPE Stat fetch+cache+throttle +// implementation below was completely shadowed, and every call to +// fetchRipeStatCached() for any endpoint NOT in {announced-prefixes, asn-neighbours, +// as-overview, visibility, prefix-size-distribution} was unconditionally returning +// null instead of ever reaching this code. That silently broke blocklist, +// abuse-contact-finder, bgp-updates, routing-status, looking-glass, whois, +// reverse-dns-consistency, maxmind-geo-lite-pfx, and ixs lookups -- see the router's +// fallback call to this function for the fix. +async function fetchRipeStatCachedFromApi(url, options) { // Extract endpoint name from URL for TTL lookup const match = url.match(/\/data\/([^/]+)\//); const endpoint = match ? match[1] : "default"; @@ -2327,14 +2220,19 @@ async function ensureAspaCache() { } } -// Lookup ASPA object for a given ASN from the RPKI feed cache +// Lookup ASPA object for a given ASN from the RPKI feed cache. feedLoaded +// distinguishes "the feed loaded fine and this ASN genuinely has no ASPA object" +// from "the Cloudflare feed fetch has never succeeded, so we don't actually know" -- +// rpkiAspaLastFetch is only set on a successful parse (see fetchRpkiAspaFeed), so +// 0 means every attempt so far has failed and exists:false here is not trustworthy. function lookupAspaFromRpki(asn) { const asnNum = Number(asn); + const feedLoaded = rpkiAspaLastFetch > 0; if (rpkiAspaMap.has(asnNum)) { const providers = rpkiAspaMap.get(asnNum); - return { exists: true, providers: [...providers].sort((a, b) => a - b) }; + return { exists: true, providers: [...providers].sort((a, b) => a - b), feedLoaded }; } - return { exists: false, providers: [] }; + return { exists: false, providers: [], feedLoaded }; } @@ -2541,30 +2439,69 @@ async function resolveASNames(providers) { return providers; } -// RPKI per-prefix validation — uses local ROA store (instant, no API calls) -// Falls back to RIPE Stat only if ROA store is not yet loaded (cold start) -function fetchRPKIPerPrefix(asn, prefix) { - // Try local ROA store first (sub-millisecond) - const local = roaStore.validate(asn, prefix); - if (local !== null) return Promise.resolve(local); +// ── MASTER RIPE Stat API wrapper (Local-first, zero external API calls) ── +// Analyzes RIPE Stat URL and dispatches to appropriate localDb function +async function fetchRipeStatCached(url, options = {}) { + try { + // Detect which RIPE Stat endpoint this is and call local DB + if (url.includes('/announced-prefixes/')) { + const asnMatch = url.match(/resource=AS(\d+)/); + if (asnMatch) return await localDb.getRipeStatAnnouncedPrefixes(parseInt(asnMatch[1])); + } + if (url.includes('/asn-neighbours/')) { + const asnMatch = url.match(/resource=AS(\d+)/); + if (asnMatch) return await localDb.getRipeStatAsnNeighbours(parseInt(asnMatch[1])); + } + if (url.includes('/as-overview/')) { + const asnMatch = url.match(/resource=AS(\d+)/); + if (asnMatch) return await localDb.getRipeStatAsOverview(parseInt(asnMatch[1])); + } + if (url.includes('/visibility/')) { + const asnMatch = url.match(/resource=AS(\d+)/); + if (asnMatch) return await localDb.getRipeStatVisibility(parseInt(asnMatch[1])); + } + if (url.includes('/prefix-size-distribution/')) { + const asnMatch = url.match(/resource=AS(\d+)/); + if (asnMatch) return await localDb.getRipeStatPrefixSizeDistribution(parseInt(asnMatch[1])); + } - // Fallback: RIPE Stat API (only during cold start before first feed load) - return fetchRipeStatCached( - "https://stat.ripe.net/data/rpki-validation/data.json?resource=AS" + - asn + "&prefix=" + encodeURIComponent(prefix) - ).then((r) => { - const status = r?.data?.status || "not_found"; - const validating = r?.data?.validating_roas || []; - return { prefix, status, validating_roas: validating.length }; - }); + // For every other RIPE Stat endpoint (not mirrored into the local Postgres DB) -- + // e.g. blocklist, abuse-contact-finder, bgp-updates, routing-status, looking-glass, + // whois, reverse-dns-consistency, maxmind-geo-lite-pfx, ixs -- fall through to the + // real RIPE Stat API with its own cache/throttle/never-cache-null protections, + // instead of returning null unconditionally (see fetchRipeStatCachedFromApi's + // header comment for how this silently broke ~9 checks until 2026-07-16). + return fetchRipeStatCachedFromApi(url, options); + } catch (e) { + console.error("[fetchRipeStatCached] Error:", e.message); + return Promise.resolve(null); + } } -// Validate RPKI for a prefix — local ROA store (instant) or RIPE Stat fallback +// RPKI per-prefix validation — uses local ROA store (instant, no API calls) +// Falls back to RIPE Stat only if ROA store is not yet loaded (cold start) +// Validate RPKI for a prefix — uses local PostgreSQL database (sub-10ms, zero external API calls) +// Returns: { prefix, status: "valid"|"invalid"|"not_found"|"unavailable", validating_roas: N } +// "not_found" means the DB was queried and genuinely has no covering ROA -- a real result. +// "unavailable" means the DB query itself failed (connection/timeout/error) -- we don't know. +// Conflating these used to make DB hiccups masquerade as "no ROA published" (a real, worse- +// sounding finding). Callers computing coverage percentages should exclude "unavailable". async function validateRPKIWithCache(asn, prefix) { try { - return await fetchRPKIPerPrefix(asn, prefix); + const result = await localDb.validateRpki(prefix, asn); + if (result.status === 'valid') { + return { prefix, status: "valid", validating_roas: 1 }; + } else if (result.status === 'invalid') { + return { prefix, status: "invalid", validating_roas: 1 }; + } else if (result.status === 'unknown') { + return { prefix, status: "unavailable", validating_roas: 0 }; + } else { + // 'not-found': genuinely no covering ROA + return { prefix, status: "not_found", validating_roas: 0 }; + } } catch (_e) { - return { prefix, status: "not_found", validating_roas: 0 }; + console.error("[RPKI] Error validating " + prefix + ":", _e.message); + return { prefix, status: "unavailable", validating_roas: 0 }; } } @@ -2583,6 +2520,9 @@ function hasAsSet(asPath) { // Hop Check function (core of ASPA verification) // aspaStore = Map> (CAS -> provider set) +// hopCheck(asI, asJ): is asJ an attested provider of CUSTOMER asI? Caller must pass +// the customer first, provider second -- see draft-ietf-sidrops-aspa-verification +// section 5.3's authorized(A(I), A(I+1)), where A(I) is always the customer side. function hopCheck(asI, asJ, aspaStore) { const providers = aspaStore.get(asI); if (!providers) return "NoAttestation"; @@ -2604,7 +2544,9 @@ function verifyUpstream(asPath, aspaStore, rawPathStr) { let hasNoAttestation = false; for (let i = 1; i < collapsed.length; i++) { - const check = hopCheck(collapsed[i - 1], collapsed[i], aspaStore); + // collapsed[i] (closer to origin) is the customer; collapsed[i - 1] (closer to + // the validator) is the provider it's declaring -- customer goes first in hopCheck. + const check = hopCheck(collapsed[i], collapsed[i - 1], aspaStore); hops.push({ from: collapsed[i - 1], to: collapsed[i], @@ -2623,7 +2565,16 @@ function verifyUpstream(asPath, aspaStore, rawPathStr) { }; } -// Downstream Verification (RFC Section 6.2) +// Downstream Verification -- draft-ietf-sidrops-aspa-verification Section 5.5. +// Unlike upstream verification, a downstream path may legitimately contain both +// an up-ramp (customer->provider hops, near the origin) AND a down-ramp +// (provider->customer hops, near the validator), transitioning at most once -- +// e.g. origin -> its providers -> a peering exchange -> validator's providers -> +// validator. Section 5.3 defines four ramp lengths computed from two independent +// scans, verified 2026-07-16 against a worked example fetched from the draft +// (N=4 path with a single unattested/no-attestation "kink" correctly resolves +// to Valid, and a path with definite failures on both ends correctly resolves +// to Invalid, matching this implementation). function verifyDownstream(asPath, aspaStore, rawPathStr) { if (rawPathStr && hasAsSet(rawPathStr)) return { result: "Invalid", reason: "Path contains AS_SET" }; const collapsed = collapsePrepends(asPath); @@ -2635,58 +2586,57 @@ function verifyDownstream(asPath, aspaStore, rawPathStr) { hops.push({ from: collapsed[i - 1], to: collapsed[i], - result: hopCheck(collapsed[i - 1], collapsed[i], aspaStore), + result: hopCheck(collapsed[i], collapsed[i - 1], aspaStore), }); } - // Find u_min: first index where forward hop is NotProviderPlus - let uMin = N + 1; - for (let u = 1; u < N; u++) { - if (hopCheck(collapsed[u - 1], collapsed[u], aspaStore) === "NotProviderPlus") { - uMin = u; - break; - } + // Up-ramp scan: walk from the origin (collapsed[N-1]) toward the validator + // (collapsed[0]), i.e. authorized(A(I), A(I+1)) for I = 1..N-1 ascending. + // maxUpRamp stops at the first DEFINITE violation ("NotProviderPlus"); + // minUpRamp stops at the first hop that isn't confirmed ProviderPlus + // (including unattested "NoAttestation" hops, which don't disprove a leak + // but also don't prove the path is clean). + let maxUpRamp = N; + let minUpRamp = N; + for (let k = N - 1; k >= 1; k--) { + const check = hopCheck(collapsed[k], collapsed[k - 1], aspaStore); + const I = N - k; + if (check === "NotProviderPlus" && maxUpRamp === N) maxUpRamp = I; + if (check !== "ProviderPlus" && minUpRamp === N) minUpRamp = I; + if (maxUpRamp !== N && minUpRamp !== N) break; } - // Find v_max: last index where reverse hop is NotProviderPlus - let vMax = 0; - for (let v = N - 2; v >= 0; v--) { - if (hopCheck(collapsed[v + 1], collapsed[v], aspaStore) === "NotProviderPlus") { - vMax = v; - break; - } + // Down-ramp scan: the mirror image, walking from the validator (collapsed[0]) + // toward the origin, i.e. authorized(A(J), A(J-1)) for J = N..2 descending. + let maxDownRamp = N; + let minDownRamp = N; + for (let k = 0; k <= N - 2; k++) { + const check = hopCheck(collapsed[k], collapsed[k + 1], aspaStore); + const rampLen = k + 1; // = N - J + 1 for the corresponding J + if (check === "NotProviderPlus" && maxDownRamp === N) maxDownRamp = rampLen; + if (check !== "ProviderPlus" && minDownRamp === N) minDownRamp = rampLen; + if (maxDownRamp !== N && minDownRamp !== N) break; } - if (uMin <= vMax) { - return { result: "Invalid", reason: "uMin(" + uMin + ") <= vMax(" + vMax + "): valley detected", hops }; + if (maxUpRamp + maxDownRamp < N) { + return { + result: "Invalid", + reason: "max_up_ramp(" + maxUpRamp + ") + max_down_ramp(" + maxDownRamp + ") < " + N + ": no valid up/down split covers the path", + hops, + }; } - - // Compute up-ramp K - let K = 0; - for (let i = 1; i < N; i++) { - if (hopCheck(collapsed[i - 1], collapsed[i], aspaStore) === "ProviderPlus") { - K = i; - } else { - break; - } + if (minUpRamp + minDownRamp < N) { + return { + result: "Unknown", + reason: "min_up_ramp(" + minUpRamp + ") + min_down_ramp(" + minDownRamp + ") < " + N + ": unattested hops prevent full confirmation", + hops, + }; } - - // Compute down-ramp L - let L = N - 1; - for (let j = N - 2; j >= 0; j--) { - if (hopCheck(collapsed[j + 1], collapsed[j], aspaStore) === "ProviderPlus") { - L = j; - } else { - break; - } - } - - const gap = L - K; - if (gap <= 1) { - return { result: "Valid", reason: "Valid up-down path (K=" + K + ", L=" + L + ")", hops }; - } - - return { result: "Unknown", reason: "Gap between up-ramp and down-ramp (K=" + K + ", L=" + L + ", gap=" + gap + ")", hops }; + return { + result: "Valid", + reason: "max_up_ramp(" + maxUpRamp + ") + max_down_ramp(" + maxDownRamp + ") >= " + N + " and min_up_ramp + min_down_ramp >= " + N, + hops, + }; } // Valley Detection: scan path for up-down-up pattern (route leak indicator) @@ -2793,10 +2743,13 @@ async function fetchRipeRpkiValidator(asn, prefix) { } } -// Cross-check a sample of prefixes against RIPE RPKI Validator (max 5, in parallel) +// Cross-check a sample of prefixes against RIPE RPKI Validator (max 5, in parallel). +// agreement_pct only counts pairs where BOTH sources actually returned a real +// verdict -- a zero-sample or all-RIPE-lookups-failed result reports agreement_pct: +// null (not a fabricated 100), since nothing was actually cross-checked. async function crossCheckRpki(asn, prefixes, localResults) { const sample = prefixes.slice(0, 5); - if (sample.length === 0) return { cloudflare_valid: 0, ripe_valid: 0, agreement_pct: 100, disagreements: [], sample_size: 0 }; + if (sample.length === 0) return { cloudflare_valid: 0, ripe_valid: 0, agreement_pct: null, disagreements: [], sample_size: 0, comparable_size: 0 }; const ripeResults = await Promise.all( sample.map((pfx) => fetchRipeRpkiValidator(asn, pfx)) @@ -2810,6 +2763,7 @@ async function crossCheckRpki(asn, prefixes, localResults) { let cloudflareValid = 0; let ripeValid = 0; let agreements = 0; + let comparable = 0; const disagreements = []; for (let i = 0; i < sample.length; i++) { @@ -2823,12 +2777,13 @@ async function crossCheckRpki(asn, prefixes, localResults) { if (cfIsValid) cloudflareValid++; if (ripeIsValid) ripeValid++; - // Skip comparison if RIPE returned error/unknown + // A failed/unknown RIPE lookup isn't a comparison at all -- exclude it from + // the agreement percentage instead of counting it as a silent "agreement". if (ripeState === "error" || ripeState === "unknown") { - agreements++; // Don't count failed lookups as disagreements continue; } + comparable++; if (cfIsValid === ripeIsValid) { agreements++; } else { @@ -2840,8 +2795,8 @@ async function crossCheckRpki(asn, prefixes, localResults) { } } - const agreementPct = sample.length > 0 ? Math.round((agreements / sample.length) * 100) : 100; - return { cloudflare_valid: cloudflareValid, ripe_valid: ripeValid, agreement_pct: agreementPct, disagreements: disagreements, sample_size: sample.length }; + const agreementPct = comparable > 0 ? Math.round((agreements / comparable) * 100) : null; + return { cloudflare_valid: cloudflareValid, ripe_valid: ripeValid, agreement_pct: agreementPct, disagreements: disagreements, sample_size: sample.length, comparable_size: comparable }; } // ============================================================ @@ -3154,7 +3109,9 @@ async function fetchWhois(resource) { whoisCacheSet(asn, result.data); } else { result.error = "Not found in any RIR database (RIPE, APNIC, ARIN, LACNIC, AFRINIC)"; - whoisCacheSet(asn, null); // cache miss to avoid repeated hammering + // Short TTL: this "not found" likely reflects a transient failure across + // all 5 sources, not a confirmed non-existent ASN -- see WHOIS_NOT_FOUND_CACHE_TTL. + whoisCacheSet(asn, null, WHOIS_NOT_FOUND_CACHE_TTL); } } } else if (/[\/:]/.test(trimmed) || /^\d+\.\d+\.\d+/.test(trimmed)) { @@ -3208,6 +3165,40 @@ async function fetchWhois(resource) { return result; } +// ============================================================ +// Internal API Server Proxy +// ============================================================ +// Several routes were extracted into src/api/ + src/features/*/routes.ts +// (Fastify, dist/api/index.js, run as a separate PM2 process on +// API_SERVER_PORT) but were never re-wired here after that migration — +// they just silently 404'd. This forwards those specific paths through +// rather than reimplementing them inline a second time. +const API_SERVER_PORT = parseInt(process.env.API_SERVER_PORT || "3102", 10); + +function proxyToApiServer(req, res, targetUrl) { + const proxyReq = http.request( + { + hostname: "127.0.0.1", + port: API_SERVER_PORT, + path: targetUrl, + method: req.method, + headers: Object.assign({}, req.headers, { host: "127.0.0.1:" + API_SERVER_PORT }), + }, + (proxyRes) => { + res.writeHead(proxyRes.statusCode, proxyRes.headers); + proxyRes.pipe(res); + } + ); + proxyReq.on("error", (err) => { + console.error("[API Proxy] Error forwarding " + targetUrl + ":", err.message); + if (!res.headersSent) { + res.writeHead(502, { "Content-Type": "application/json" }); + res.end(JSON.stringify({ error: "API server unavailable", detail: err.message })); + } + }); + req.pipe(proxyReq); +} + // ============================================================ // HTTP Server // ============================================================ @@ -3540,16 +3531,20 @@ const server = http.createServer(async (req, res) => { res.setHeader("Content-Type", "application/json"); - // Health endpoint — extended with cache status and ASPA metrics + // Health endpoint — extended with cache status, ASPA metrics, and local DB stats if (reqPath === "/api/health") { const mem = process.memoryUsage(); - const roaAge = roaStore.lastBuild ? Math.floor((Date.now() - roaStore.lastBuild) / 60000) : -1; const aspaAge = rpkiAspaLastFetch ? Math.floor((Date.now() - rpkiAspaLastFetch) / 60000) : -1; const pdbTotal = pdbSourceCache.hits + pdbSourceCache.misses; - const status = roaStore.ready && aspaAge < 300 ? "ok" : "degraded"; - return res.end( - JSON.stringify({ + // Query local DB stats (async, but return partial if needed) + localDb.getLocalDbStats().then(function(dbStats) { + // Determine health status based on local DB data availability + const hasLocalBgp = dbStats && dbStats.bgp_routes > 100000; // should have >2M rows normally + const hasLocalRpki = dbStats && dbStats.rpki_roas > 100000; // should have >500k rows normally + const status = (hasLocalBgp && hasLocalRpki && aspaAge < 300) ? "ok" : "degraded"; + + const healthResponse = { status, service: "PeerCortex", version: "0.6.9", @@ -3558,7 +3553,6 @@ const server = http.createServer(async (req, res) => { memory_mb: Math.round(mem.heapUsed / 1024 / 1024), bgproutes_configured: !!BGPROUTES_API_KEY, caches: { - roa_store: { entries: roaStore.count, age_minutes: roaAge, ready: roaStore.ready }, aspa_map: { entries: rpkiAspaMap.size, age_minutes: aspaAge }, pdb_net: { entries: pdbSourceCache.net.size, hit_rate_pct: pdbTotal > 0 ? Math.round(pdbSourceCache.hits / pdbTotal * 100) : 0 }, pdb_netixlan: { entries: pdbSourceCache.netixlan.size }, @@ -3566,6 +3560,14 @@ const server = http.createServer(async (req, res) => { ripe_stat: { entries: ripeStatCache.size }, response_cache: { entries: responseCache.size }, }, + local_db: dbStats ? { + bgp_routes: dbStats.bgp_routes, + rpki_roas: dbStats.rpki_roas, + threat_intel: dbStats.threat_intel, + rdap_cache_entries: dbStats.rdap_cache_entries, + source: "PostgreSQL (local)", + healthy: hasLocalBgp && hasLocalRpki, + } : null, aspa_adoption: { total_objects: rpkiAspaMap.size, roa_count: roaStore.count, @@ -3574,8 +3576,42 @@ const server = http.createServer(async (req, res) => { ? aspaAdoptionDailyHistory[aspaAdoptionDailyHistory.length - 1].aspa_objects - aspaAdoptionDailyHistory[aspaAdoptionDailyHistory.length - 2].aspa_objects : 0, }, - }) - ); + }; + return res.end(JSON.stringify(healthResponse, null, 2)); + }).catch(function(e) { + console.error('[/api/health] Local DB stats error:', e.message); + // Return health without local DB stats on error + return res.end( + JSON.stringify({ + status, + service: "PeerCortex", + version: "0.6.9", + timestamp: new Date().toISOString(), + uptime_seconds: Math.floor(process.uptime()), + memory_mb: Math.round(mem.heapUsed / 1024 / 1024), + bgproutes_configured: !!BGPROUTES_API_KEY, + caches: { + roa_store: { entries: roaStore.count, age_minutes: roaAge, ready: roaStore.ready }, + aspa_map: { entries: rpkiAspaMap.size, age_minutes: aspaAge }, + pdb_net: { entries: pdbSourceCache.net.size, hit_rate_pct: pdbTotal > 0 ? Math.round(pdbSourceCache.hits / pdbTotal * 100) : 0 }, + pdb_netixlan: { entries: pdbSourceCache.netixlan.size }, + pdb_netfac: { entries: pdbSourceCache.netfac.size }, + ripe_stat: { entries: ripeStatCache.size }, + response_cache: { entries: responseCache.size }, + }, + local_db: { error: "Could not fetch local DB stats", message: e.message }, + aspa_adoption: { + total_objects: rpkiAspaMap.size, + roa_count: roaStore.count, + history_samples: aspaAdoptionDailyHistory.length, + delta_last: aspaAdoptionDailyHistory.length >= 2 + ? aspaAdoptionDailyHistory[aspaAdoptionDailyHistory.length - 1].aspa_objects - aspaAdoptionDailyHistory[aspaAdoptionDailyHistory.length - 2].aspa_objects + : 0, + }, + }, null, 2) + ); + }); + return; } // ============================================================ @@ -3677,15 +3713,17 @@ const server = http.createServer(async (req, res) => { const aspaObjectExists = aspaLookup.exists; const aspaDeclaredProviders = aspaLookup.providers; - // Build ASPA store from RPKI feed data (real ASPA objects) + // Build ASPA store from RPKI feed data (real ASPA objects only). If the + // target has no real ASPA object, its hops must show "NoAttestation" -- + // NOT a fabricated declaration built from heuristically-detected BGP + // neighbours, which would contradict the aspa_object_exists:false this + // same response reports and falsely present unattested hops as + // "ProviderPlus"/Valid. hopCheck() already returns "NoAttestation" for + // any ASN with no entry in aspaStore, so simply not setting one here is + // correct and sufficient -- no fallback needed. const aspaStore = new Map(); - // Add the target ASN's RPKI-declared providers if (aspaObjectExists) { aspaStore.set(targetAsn, new Set(aspaDeclaredProviders)); - } else { - // Fallback: use detected providers for path verification - const providerSet = new Set(detectedProviders.map((p) => p.asn)); - aspaStore.set(targetAsn, providerSet); } // Also populate store with all known ASPA objects from the RPKI feed // for providers that have their own ASPA objects (enables full path verification) @@ -3694,14 +3732,13 @@ const server = http.createServer(async (req, res) => { aspaStore.set(cas, provSet); } } - - // Also add reverse relationships for providers we know about - // (each provider has the target as customer) - detectedProviders.forEach((p) => { - if (!aspaStore.has(p.asn)) { - aspaStore.set(p.asn, new Set()); - } - }); + // Deliberately NOT adding empty Set() entries for detected-but-unattested + // providers here: aspaStore.get() returning undefined for them makes + // hopCheck() correctly report "NoAttestation". An empty (but present) Set + // would make providers.has(x) return false, misreporting "NotProviderPlus" + // (implying a real ASPA object that denies this hop) instead of "we simply + // don't know" -- see the 2026-07-16 audit for how this previously caused + // clean, unattested paths to be misclassified as route leaks. // Sample paths for verification (up to 50) const samplePaths = allPaths.slice(0, 50); @@ -3780,8 +3817,11 @@ const server = http.createServer(async (req, res) => { await ensureAspaCache(); const rpkiBatch = announcedPrefixes.map((p) => p.prefix); const rpkiResults = await Promise.all(rpkiBatch.map((pfx) => validateRPKIWithCache(rawAsn, pfx))); - const rpkiValid = rpkiResults.filter((r) => r.status === "valid").length; - const rpkiCoverage = rpkiResults.length > 0 ? Math.round((rpkiValid / rpkiResults.length) * 100) : 0; + // Exclude "unavailable" (DB error) from the coverage denominator -- a DB hiccup + // must not lower a network's displayed ASPA readiness score. + const rpkiChecked = rpkiResults.filter((r) => r.status !== "unavailable"); + const rpkiValid = rpkiChecked.filter((r) => r.status === "valid").length; + const rpkiCoverage = rpkiChecked.length > 0 ? Math.round((rpkiValid / rpkiChecked.length) * 100) : 0; // Calculate readiness score const readinessScore = calculateAspaReadinessScore({ @@ -3803,6 +3843,7 @@ const server = http.createServer(async (req, res) => { asn: targetAsn, readiness_score: readinessScore, aspa_object_exists: aspaObjectExists, + aspa_feed_healthy: aspaLookup.feedLoaded, detected_providers: detectedProviders.map((p) => ({ ...p, frequency: providerFrequency.get(p.asn) || 0, @@ -3945,6 +3986,7 @@ const server = http.createServer(async (req, res) => { detected_providers: detectedProviders, provider_count: detectedProviders.length, aspa_object_exists: aspaObjectExists, + aspa_feed_healthy: aspaLookup.feedLoaded, aspa_declared_providers: aspaDeclaredProviders.map((a) => ({ asn: a })), aspa_declared_count: aspaDeclaredProviders.length, recommended_aspa: recommendedAspa, @@ -3966,140 +4008,107 @@ const server = http.createServer(async (req, res) => { } // ============================================================ - // bgproutes.io endpoint: /api/bgproutes?asn=X (or prefix=X) + // BGP endpoint (LOCAL DB): /api/bgp?asn=X (or prefix=X) + // Queries local PostgreSQL bgp_routes table — zero external API calls // ============================================================ - if (reqPath === "/api/bgproutes") { + if (reqPath === "/api/bgp") { const rawAsn = (url.searchParams.get("asn") || "").replace(/[^0-9]/g, ""); const prefix = url.searchParams.get("prefix") || ""; if (!rawAsn && !prefix) { res.writeHead(400); return res.end(JSON.stringify({ error: "Need asn or prefix parameter" })); } - const cacheKeyBgr = rawAsn || prefix; - const cachedBgr = resultCacheGet(bgproutesResultCache, cacheKeyBgr); - if (cachedBgr !== undefined) { - res.writeHead(200, { "Content-Type": "application/json" }); - return res.end(JSON.stringify(cachedBgr)); + const cacheKey = rawAsn || prefix; + const cached = resultCacheGet(bgproutesResultCache, cacheKey); + if (cached !== undefined) { + res.writeHead(200, { "Content-Type": "application/json", "X-Cache": "HIT" }); + return res.end(JSON.stringify(cached)); } const start = Date.now(); try { - const result = { meta: { timestamp: new Date().toISOString() }, vantage_points: null, routes: null }; + const result = { + meta: { timestamp: new Date().toISOString(), source: "local_bgp_db" }, + bgp_status: null, + threat_intel: null, + }; - // Use module-level vantage_points cache (1h TTL) to prevent 429 flooding - let vpData = null; - if (bgproutesVpCache && (Date.now() - bgproutesVpCacheTs) < BGPROUTES_VP_TTL) { - vpData = bgproutesVpCache; - } else { - vpData = await fetchJSON(BGPROUTES_API_URL + "/vantage_points", { - headers: { "x-api-key": BGPROUTES_API_KEY }, - timeout: 10000, - }); - if (vpData && !vpData.error) { bgproutesVpCache = vpData; bgproutesVpCacheTs = Date.now(); } - } - - if (vpData && !vpData.error) { - const vpList = vpData?.data?.bgp || (Array.isArray(vpData) ? vpData : vpData.data || []); - const readyVPs = Array.isArray(vpList) ? vpList.filter((vp) => !vp.status || (Array.isArray(vp.status) && vp.status.includes("ready"))) : []; - result.vantage_points = { - count: readyVPs.length, - total: Array.isArray(vpList) ? vpList.length : 0, - list: readyVPs.slice(0, 20).map((vp) => ({ - id: vp.id, - asn: vp.asn, - ip: vp.ip, - source: vp.source || "", - org_name: vp.org_name || "", - country: vp.org_country || vp.country || "", - rib_v4: vp.rib_size_v4 || 0, - rib_v6: vp.rib_size_v6 || 0, - })), - }; - } else { - result.vantage_points = { count: 0, error: "Could not fetch vantage points" }; - } - - let ribSuccess = false; - const readyVPsForRib = result.vantage_points && result.vantage_points.list - ? result.vantage_points.list.filter((vp) => vp.rib_v4 > 500000).slice(0, 1) - : []; - - if (readyVPsForRib.length > 0) { - const vpId = readyVPsForRib[0].id; - const now = new Date().toISOString().replace(/\.\d+Z$/, ""); - const ribBody = { - vp_bgp_ids: String(vpId), - date: now, - return_aspath: true, - return_rov_status: true, - return_aspa_status: true, - }; - - if (prefix) { - ribBody.prefix_exact_match = prefix; - } else if (rawAsn) { - ribBody.aspath_regexp = rawAsn + "$"; - } - - try { - const ribData = await postJSON(BGPROUTES_API_URL + "/rib", ribBody, { - headers: { "x-api-key": BGPROUTES_API_KEY }, - timeout: 6000, - }); - - if (ribData && ribData.data) { - const bgpData = ribData.data.bgp || {}; - const vpRoutes = bgpData[String(vpId)] || {}; - const routeEntries = Object.entries(vpRoutes).map(([pfx, arr]) => { - const asPath = Array.isArray(arr) ? arr[0] || "" : ""; - const rovStatus = Array.isArray(arr) ? arr[2] || "" : ""; - const aspaStatus = Array.isArray(arr) ? arr[3] || "" : ""; - return { - prefix: pfx, - as_path: asPath, - rov_status: (function(rs) { - var parts = rs.split(",").map(function(s) { return s === "V" ? "valid" : s === "I" ? "invalid" : s === "U" ? "unknown" : s; }); - if (parts.indexOf("invalid") >= 0) return "invalid"; - if (parts.indexOf("unknown") >= 0) return "unknown"; - if (parts.indexOf("valid") >= 0) return "valid"; - return parts[0] || "unknown"; - })(rovStatus), - aspa_status: (function(as) { - var parts = as.split(",").map(function(s) { return s === "V" ? "valid" : s === "I" ? "invalid" : s === "U" ? "unknown" : s; }); - if (parts.indexOf("invalid") >= 0) return "invalid"; - if (parts.indexOf("unknown") >= 0) return "unknown"; - if (parts.indexOf("valid") >= 0) return "valid"; - return parts[0] || "unknown"; - })(aspaStatus), - }; - }); - - if (routeEntries.length > 0) { - result.routes = { - count: routeEntries.length, - vp_used: { id: vpId, org: readyVPsForRib[0].org_name, country: readyVPsForRib[0].country }, - sample: routeEntries.slice(0, 20), - }; - ribSuccess = true; - } + // ---- BGP Status (local DB lookup) ---- + if (prefix) { + // Prefix lookup: Get BGP status for this prefix + const bgpStatus = await localDb.getBgpStatus(prefix); + if (bgpStatus) { + result.bgp_status = { + prefix, + announced: bgpStatus.announced, + origin_asns: bgpStatus.origin_asns, + visibility_percent: bgpStatus.visibility_percent, + last_seen: bgpStatus.last_seen, + source: "local_bgp", + }; + // Check for hijack (multiple origin ASNs). null means the check itself + // failed (DB error) -- distinct from a genuine single/no-origin result. + const hijackAsns = await localDb.checkBgpHijack(prefix); + if (hijackAsns === null) { + result.bgp_status.hijack_check_unavailable = true; + } else if (hijackAsns.length > 1) { + result.bgp_status.hijack_warning = { + detected: true, + origin_asns: hijackAsns, + message: `Multiple origin ASNs detected for ${prefix}`, + }; } - } catch (_e) {} + } + } else if (rawAsn) { + // ASN lookup: Get all announced prefixes for this ASN + const prefixes = await localDb.getAnnouncedPrefixes(rawAsn); + if (prefixes && prefixes.length > 0) { + result.bgp_status = { + asn: rawAsn, + announced_count: prefixes.length, + prefixes: prefixes.slice(0, 50).map((p) => ({ + prefix: p.prefix, + origin_asn: p.origin_asn, + visibility_percent: p.visibility_percent, + last_seen: p.last_seen, + })), + source: "local_bgp", + }; + } else { + result.bgp_status = { + asn: rawAsn, + announced: false, + announced_count: 0, + message: "No prefixes found for this ASN in local BGP table", + source: "local_bgp", + }; + } } - if (!ribSuccess) { - result.routes = { - status: "unavailable", - message: readyVPsForRib.length === 0 - ? "No ready VPs with sufficient RIB size found" - : "bgproutes.io: VPs available but RIB query returned no data for this ASN", - }; + // ---- Threat Intelligence (local cache lookup) ---- + // If we have an IP context, look up threat intel + if (prefix && prefix.includes(".")) { + // Extract IP from prefix (e.g., "1.1.1.0/24" → "1.1.1.0") + const ipAddr = prefix.split("/")[0]; + const threat = await localDb.getThreatIntel(ipAddr); + if (threat) { + result.threat_intel = { + ip_address: threat.ip_address, + threat_level: threat.threat_level, + confidence_score: threat.confidence_score, + source: threat.source, + cached_at: threat.cached_at, + }; + } } result.meta.duration_ms = Date.now() - start; - resultCacheSet(bgproutesResultCache, cacheKeyBgr, result); + resultCacheSet(bgproutesResultCache, cacheKey, result); + res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(result, null, 2)); } catch (err) { + console.error("[/api/bgp] Error:", err.message); res.writeHead(500); - return res.end(JSON.stringify({ error: "bgproutes.io query failed", message: err.message })); + return res.end(JSON.stringify({ error: "BGP query failed", message: err.message })); } } @@ -4177,18 +4186,29 @@ const server = http.createServer(async (req, res) => { var bogonPrefixes = bogonPrefixResults.filter(function(r) { return r.is_bogon; }); var asnInPaths = neighbours.map(function(n) { return n.asn; }); var bogonAsns = asnInPaths.filter(checkBogonAsn); + // "pass" must mean prefixes/neighbours were actually fetched and checked clean -- + // not that the upstream RIPE Stat calls failed and left nothing to check. + var bogonDataUnavailable = !prefixData && !neighbourData; var bogonResult = { - status: bogonPrefixes.length === 0 && bogonAsns.length === 0 ? "pass" : "fail", + status: bogonDataUnavailable ? "info" : (bogonPrefixes.length === 0 && bogonAsns.length === 0 ? "pass" : "fail"), bogon_prefixes: bogonPrefixes, bogon_asns_in_paths: bogonAsns, total_prefixes_checked: allPrefixes.length, + prefixes_source_unavailable: !prefixData, + neighbours_source_unavailable: !neighbourData, }; // Phase 2: All API-dependent validations in parallel var validationPromises = {}; // 12. IRR Validation + // fetchJSON resolves(null) on timeout/network-error/parse-failure rather than + // rejecting, so the .catch() below never sees the common failure mode -- a + // dead upstream would otherwise silently read as "0 entries, no mismatches, pass". validationPromises.irr = fetchJSON("https://irrexplorer.nlnog.net/api/prefixes/asn/" + rawAsn).then(function(irrData) { + if (irrData === null) { + return { status: "info", message: "IRR Explorer unavailable (fetch failed)", total_entries: 0, mismatches: [], mismatch_count: 0 }; + } var entries = Array.isArray(irrData) ? irrData : []; var mismatches = entries.filter(function(e) { if (!e.bgpOrigins && !e.bgp_origins) return false; @@ -4214,16 +4234,22 @@ const server = http.createServer(async (req, res) => { validationPromises.rpki_completeness = Promise.all( allPrefixes.map(function(pfx) { return validateRPKIWithCache(rawAsn, pfx); }) ).then(function(rpkiResults) { - var withRoa = rpkiResults.filter(function(r) { return r.status === "valid"; }); - var coverage = rpkiResults.length > 0 ? Math.round((withRoa.length / rpkiResults.length) * 100) : 0; - var overSpecific = rpkiResults.filter(function(r) { + var checkedResults = rpkiResults.filter(function(r) { return r.status !== "unavailable"; }); + var unavailableCount = rpkiResults.length - checkedResults.length; + var withRoa = checkedResults.filter(function(r) { return r.status === "valid"; }); + var coverage = checkedResults.length > 0 ? Math.round((withRoa.length / checkedResults.length) * 100) : 0; + var overSpecific = checkedResults.filter(function(r) { var mask = parseInt((r.prefix || "").split("/")[1] || "0"); return !r.prefix.includes(":") && mask >= 25 && r.status !== "valid"; }); + // If the local RPKI DB was unreachable for every prefix, we haven't actually + // checked anything -- say so instead of reporting a misleading pass/fail. + var allUnavailable = rpkiResults.length > 0 && checkedResults.length === 0; return { - status: coverage >= 90 ? "pass" : coverage >= 50 ? "warning" : "fail", + status: allUnavailable ? "info" : coverage >= 90 ? "pass" : coverage >= 50 ? "warning" : "fail", coverage_pct: coverage, - total_checked: rpkiResults.length, + total_checked: checkedResults.length, + db_unavailable_count: unavailableCount, with_roa: withRoa.length, over_specific: overSpecific.map(function(r) { return r.prefix; }), details: rpkiResults, @@ -4232,6 +4258,7 @@ const server = http.createServer(async (req, res) => { // 14. Abuse Contact Validation validationPromises.abuse_contact = fetchRipeStatCached("https://stat.ripe.net/data/abuse-contact-finder/data.json?resource=AS" + rawAsn).then(function(data) { + if (data === null) return { status: "info", message: "abuse-contact-finder unavailable (fetch failed)", contacts: [], has_valid_email: false }; var contacts = data && data.data && data.data.abuse_contacts ? data.data.abuse_contacts : []; var hasEmail = contacts.length > 0 && contacts.some(function(c) { return c && c.includes("@"); }); return { status: hasEmail ? "pass" : "fail", contacts: contacts, has_valid_email: hasEmail }; @@ -4241,14 +4268,25 @@ const server = http.createServer(async (req, res) => { validationPromises.blocklist = Promise.all( samplePrefixes.slice(0, 5).map(function(pfx) { return fetchRipeStatCached("https://stat.ripe.net/data/blocklist/data.json?resource=" + encodeURIComponent(pfx)).then(function(data) { + // fetchRipeStatCached resolves(null) on timeout/network-error rather than + // throwing, so the .catch() below never sees that case -- distinguish it + // explicitly instead of letting a failed lookup read as "not listed". + if (data === null) return { prefix: pfx, listed: false, checked: false }; var sources = data && data.data && data.data.sources ? data.data.sources : []; var listed = sources.filter(function(s) { return s.prefix_count > 0 || (s.entries && s.entries.length > 0); }); - return { prefix: pfx, listed: listed.length > 0, sources: listed.map(function(s) { return s.source || s.name || "unknown"; }) }; - }).catch(function() { return { prefix: pfx, listed: false, error: true }; }); + return { prefix: pfx, listed: listed.length > 0, checked: true, sources: listed.map(function(s) { return s.source || s.name || "unknown"; }) }; + }).catch(function() { return { prefix: pfx, listed: false, checked: false }; }); }) ).then(function(results) { - var listedPrefixes = results.filter(function(r) { return r.listed; }); - return { status: listedPrefixes.length === 0 ? "pass" : "fail", checked: results.length, listed_prefixes: listedPrefixes }; + var checkedResults = results.filter(function(r) { return r.checked; }); + var listedPrefixes = checkedResults.filter(function(r) { return r.listed; }); + var allUnchecked = results.length > 0 && checkedResults.length === 0; + return { + status: allUnchecked ? "info" : (listedPrefixes.length === 0 ? "pass" : "fail"), + checked: checkedResults.length, + unavailable: results.length - checkedResults.length, + listed_prefixes: listedPrefixes, + }; }).catch(function(e) { return { status: "error", error: String(e) }; }); // 16. MANRS Compliance — scraped from public participants list (24h cache) @@ -4261,6 +4299,7 @@ const server = http.createServer(async (req, res) => { validationPromises.rdns = Promise.all( samplePrefixes.slice(0, rdnsSampleSize).map(function(pfx) { return fetchRipeStatCached("https://stat.ripe.net/data/reverse-dns-consistency/data.json?resource=" + encodeURIComponent(pfx), { timeout: 4000 }).then(function(data) { + if (data === null) return { prefix: pfx, has_rdns: false, details: [], error: true }; var pfxData = data && data.data && data.data.prefixes ? data.data.prefixes : {}; var hasDelegation = false; var details = []; @@ -4288,11 +4327,19 @@ const server = http.createServer(async (req, res) => { }).catch(function() { return { prefix: pfx, has_rdns: false, error: true }; }); }) ).then(function(results) { - var withRdns = results.filter(function(r) { return r.has_rdns; }); - var coverage = results.length > 0 ? Math.round((withRdns.length / results.length) * 100) : 0; - // Include details of what failed - var failedPrefixes = results.filter(function(r) { return !r.has_rdns && !r.error; }).map(function(r) { return r.prefix; }); - return { status: coverage >= 80 ? "pass" : coverage >= 50 ? "warning" : "fail", coverage_pct: coverage, checked: results.length, results: results, failed_prefixes: failedPrefixes }; + var checkedResults = results.filter(function(r) { return !r.error; }); + var withRdns = checkedResults.filter(function(r) { return r.has_rdns; }); + var coverage = checkedResults.length > 0 ? Math.round((withRdns.length / checkedResults.length) * 100) : 0; + var failedPrefixes = checkedResults.filter(function(r) { return !r.has_rdns; }).map(function(r) { return r.prefix; }); + var allUnavailable = results.length > 0 && checkedResults.length === 0; + return { + status: allUnavailable ? "info" : (coverage >= 80 ? "pass" : coverage >= 50 ? "warning" : "fail"), + coverage_pct: coverage, + checked: checkedResults.length, + unavailable: results.length - checkedResults.length, + results: results, + failed_prefixes: failedPrefixes, + }; }).catch(function(e) { return { status: "error", error: String(e) }; }); // 18. BGP Visibility (uses routing-status API which is more reliable than visibility API) @@ -4314,7 +4361,9 @@ const server = http.createServer(async (req, res) => { } return { status: score >= 80 ? "pass" : score >= 50 ? "warning" : "fail", visibility_score: score, total_ris_peers: totalPeers, seen_by: seeingPeers, v4_seeing: v4.ris_peers_seeing || 0, v4_total: v4.total_ris_peers || 0, v6_seeing: v6.ris_peers_seeing || 0, v6_total: v6.total_ris_peers || 0, observed_neighbours: observedNeighbours, source: "bgproutes.io_fallback" }; }).catch(function() { - return { status: "fail", visibility_score: 0, total_ris_peers: 0, seen_by: 0, source: "unavailable" }; + // Neither RIPE routing-status nor the bgproutes.io fallback returned data -- + // this network hasn't actually been measured, not confirmed at 0% visibility. + return { status: "info", message: "visibility could not be measured (both data sources unavailable)", visibility_score: 0, total_ris_peers: 0, seen_by: 0, source: "unavailable" }; }); } return { status: score >= 80 ? "pass" : score >= 50 ? "warning" : "fail", visibility_score: score, total_ris_peers: totalPeers, seen_by: seeingPeers, v4_seeing: v4.ris_peers_seeing || 0, v4_total: v4.total_ris_peers || 0, v6_seeing: v6.ris_peers_seeing || 0, v6_total: v6.total_ris_peers || 0, observed_neighbours: observedNeighbours, source: "ripe_routing_status" }; @@ -4433,8 +4482,10 @@ const server = http.createServer(async (req, res) => { validationPromises.resource_cert = Promise.all( allPrefixes.map(function(pfx) { return validateRPKIWithCache(rawAsn, pfx); }) ).then(function(results) { - var hasRoa = results.some(function(r) { return r.status === "valid" || r.validating_roas > 0; }); - return { status: hasRoa ? "pass" : "fail", has_roas: hasRoa, checked: results.length, roa_count: results.filter(function(r) { return r.status === "valid"; }).length }; + var checked = results.filter(function(r) { return r.status !== "unavailable"; }); + var hasRoa = checked.some(function(r) { return r.status === "valid" || r.validating_roas > 0; }); + var allUnavailable = results.length > 0 && checked.length === 0; + return { status: allUnavailable ? "info" : hasRoa ? "pass" : "fail", has_roas: hasRoa, checked: checked.length, roa_count: checked.filter(function(r) { return r.status === "valid"; }).length }; }).catch(function(e) { return { status: "error", error: String(e) }; }); // Geolocation cross-ref with PeeringDB facilities @@ -4647,14 +4698,14 @@ const server = http.createServer(async (req, res) => { ]).then(d => { rdapCacheSet(asn, d); return d; }); const promises = [ - timedFetch("RIPE Stat Prefixes", fetchRipeStatCached("https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS" + asn, { timeout: 8000 })), - timedFetch("RIPE Stat Neighbours", fetchRipeStatCached("https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + asn, { timeout: 8000 })), - timedFetch("RIPE Stat Overview", fetchRipeStatCached("https://stat.ripe.net/data/as-overview/data.json?resource=AS" + asn)), - timedFetch("RIPE Stat RIR", fetchRipeStatCached("https://stat.ripe.net/data/rir-stats-country/data.json?resource=AS" + asn)), - timedFetch("RIPE Atlas", fetchJSON("https://atlas.ripe.net/api/v2/probes/?asn_v4=" + asn + "&page_size=500")), - timedFetch("bgp.he.net", fetchBgpHeNet(asn)), - timedFetch("RIPE Stat Visibility", fetchRipeStatCached("https://stat.ripe.net/data/visibility/data.json?resource=AS" + asn, { timeout: 8000 })), - timedFetch("RIPE Stat PrefixSize", fetchRipeStatCached("https://stat.ripe.net/data/prefix-size-distribution/data.json?resource=AS" + asn)), + timedFetch("RIPE Stat Prefixes", localDb ? localDb.getRipeStatAnnouncedPrefixes(asn) : Promise.resolve(null)), + timedFetch("RIPE Stat Neighbours", localDb ? localDb.getRipeStatAsnNeighbours(asn) : Promise.resolve(null)), + timedFetch("RIPE Stat Overview", localDb ? localDb.getRipeStatAsOverview(asn) : Promise.resolve(null)), + timedFetch("RIPE Stat RIR", Promise.resolve(null)), + timedFetch("RIPE Atlas", Promise.resolve(null)), + timedFetch("bgp.he.net", Promise.resolve(null)), + timedFetch("RIPE Stat Visibility", localDb ? localDb.getRipeStatVisibility(asn) : Promise.resolve(null)), + timedFetch("RIPE Stat PrefixSize", localDb ? localDb.getRipeStatPrefixSizeDistribution(asn) : Promise.resolve(null)), timedFetch("PeeringDB IXLan", cachedIxlan ? Promise.resolve(cachedIxlan) : fetchPeeringDBWithRetry(ixQuery)), timedFetch("PeeringDB Facilities", cachedFac ? Promise.resolve(cachedFac) : (netId ? fetchPeeringDBWithRetry("/netfac?net_id=" + netId + "&limit=1000") : Promise.resolve(null))), timedFetch("PeeringDB Contacts", pocQuery ? fetchPeeringDB(pocQuery).catch(() => null) : Promise.resolve(null)), @@ -4666,6 +4717,16 @@ const server = http.createServer(async (req, res) => { if (!cachedIxlan && ixlanData) pdbSourceCache.set("netixlan", ixCacheKey, ixlanData); if (!cachedFac && facData) pdbSourceCache.set("netfac", String(netId), facData); + // local-db-client's getRipeStat* functions return status:"error" (not "ok") when + // the local Postgres DB query itself failed -- track which sources actually + // failed so we don't cache a DB hiccup's fake-empty result as if it were a + // genuine "this ASN has 0 prefixes" answer for the full 5-minute TTL. + const degradedSources = [ + ["RIPE Stat Prefixes", prefixData], ["RIPE Stat Neighbours", neighbourData], + ["RIPE Stat Overview", overviewData], ["RIPE Stat Visibility", visibilityData], + ["RIPE Stat PrefixSize", prefixSizeData], + ].filter(([, v]) => v && v.status === "error").map(([name]) => name); + const prefixes = prefixData?.data?.prefixes || []; const neighbours = neighbourData?.data?.neighbours || []; const overview = overviewData?.data || {}; @@ -4770,19 +4831,22 @@ const server = http.createServer(async (req, res) => { const rpkiStatuses = rpkiAllResults; const rpkiValid = rpkiStatuses.filter((r) => r.status === "valid").length; const rpkiInvalid = rpkiStatuses.filter((r) => r.status === "invalid").length; - const rpkiNotFound = rpkiStatuses.filter((r) => r.status !== "valid" && r.status !== "invalid").length; - const rpkiTotal = rpkiStatuses.length; + const rpkiUnavailable = rpkiStatuses.filter((r) => r.status === "unavailable").length; + const rpkiNotFound = rpkiStatuses.filter((r) => r.status !== "valid" && r.status !== "invalid" && r.status !== "unavailable").length; + // Exclude "unavailable" (DB error) from the coverage denominator -- a DB hiccup + // must not lower a network's displayed RPKI coverage percentage. + const rpkiTotal = rpkiStatuses.length - rpkiUnavailable; const rpkiCoverage = rpkiTotal > 0 ? Math.round((rpkiValid / rpkiTotal) * 100) : 0; - const upstreams = neighbours + let upstreams = neighbours .filter((n) => n.type === "left") .map((n) => ({ asn: n.asn, name: n.as_name || "", power: n.power || 0 })) .sort((a, b) => b.power - a.power); - const downstreams = neighbours + let downstreams = neighbours .filter((n) => n.type === "right") .map((n) => ({ asn: n.asn, name: n.as_name || "", power: n.power || 0 })) .sort((a, b) => b.power - a.power); - const peers = neighbours + let peers = neighbours .filter((n) => n.type === "uncertain" || n.type === "peer") .map((n) => ({ asn: n.asn, name: n.as_name || "", power: n.power || 0 })) .sort((a, b) => b.power - a.power); @@ -4800,6 +4864,57 @@ const server = http.createServer(async (req, res) => { await Promise.race([resolvePromise, new Promise(r => setTimeout(r, 3000))]); } + // ---- Threat Intelligence Enrichment for Neighbors ---- + // Enrich neighbor data with threat status from local threat_intel table + const threatEnrichNeighbors = async (neighbors) => { + const allNeighbors = [...neighbors]; + const threatMap = {}; + + // Batch threat intel lookups (cap at 50 to avoid overwhelming DB) + const toCheck = allNeighbors.slice(0, 50); + const threatPromises = toCheck.map(async (n) => { + try { + // Try to get threat intel by AS number or typical AS IP pattern + // For now, we'll mark neighbors without direct IP threat data + const asNum = String(n.asn); + const threat = await localDb.getThreatIntel(asNum); + if (threat) { + threatMap[n.asn] = { + threat_level: threat.threat_level, + confidence_score: threat.confidence_score, + source: threat.source, + cached_at: threat.cached_at, + }; + } + } catch (e) { + // Gracefully skip on error + console.error(`[Threat Lookup] Error checking ASN ${n.asn}:`, e.message); + } + }); + + // Run threat lookups with 4s timeout + await Promise.race([ + Promise.all(threatPromises), + new Promise(r => setTimeout(r, 4000)), + ]); + + return threatMap; + }; + + const threatMap = await threatEnrichNeighbors([...upstreams, ...downstreams, ...peers]); + + // Attach threat status to neighbor objects + const addThreatToNeighbor = (n) => ({ + ...n, + threat_level: threatMap[n.asn]?.threat_level || null, + threat_confidence: threatMap[n.asn]?.confidence_score || null, + threat_source: threatMap[n.asn]?.source || null, + }); + + upstreams = upstreams.map(addThreatToNeighbor); + downstreams = downstreams.map(addThreatToNeighbor); + peers = peers.map(addThreatToNeighbor); + let rir = ""; let country = ""; // RIPE Stat rir-stats-country uses 'location' field (not 'country' or 'rir') @@ -4919,7 +5034,7 @@ const server = http.createServer(async (req, res) => { // ============================================================ // Multi-source cross-checks (run in parallel, non-blocking) // ============================================================ - let rpkiCrossCheck = { cloudflare_valid: 0, ripe_valid: 0, agreement_pct: 100, disagreements: [], sample_size: 0 }; + let rpkiCrossCheck = { cloudflare_valid: 0, ripe_valid: 0, agreement_pct: null, disagreements: [], sample_size: 0, comparable_size: 0 }; let prefixCrossCheck = { ripe_stat: prefixes.length, bgp_he_net: null, agreement: null, note: "" }; let neighbourCrossCheck = { ripe_stat_total: neighbours.length, bgp_he_net_total: null }; @@ -4933,6 +5048,13 @@ const server = http.createServer(async (req, res) => { if (rpkiCrossResult) rpkiCrossCheck = rpkiCrossResult; } catch (_e) { /* cross-check failed, keep defaults */ } + // bgp.he.net fetching is deliberately disabled (see the hardcoded + // Promise.resolve(null) a few hundred lines up, "bgp.he.net" timedFetch) -- + // bgpHeData is therefore always null and this whole branch, along with the + // prefix/neighbour cross-checks below, never actually runs. Left in place + // (not deleted) in case bgp.he.net fetching is re-enabled later, but + // overall_confidence below must not claim a 2-source agreement that never + // happened -- see the sources:1/2 handling further down. // Prefix count cross-check: compare RIPE Stat vs bgp.he.net if (bgpHeData) { const heV4 = bgpHeData.prefixes_v4 || 0; @@ -4961,10 +5083,12 @@ const server = http.createServer(async (req, res) => { prefixCrossCheck.note = "bgp.he.net data unavailable"; } - // Compute overall data quality + // Compute overall data quality. Only push a score for a cross-check that + // actually ran (agreement_pct !== null) -- an unrun/inconclusive check must + // not silently count as a perfect 100 in the average, and confidence must + // reflect "nothing was actually cross-checked" rather than defaulting high. const crossCheckScores = []; - // RPKI agreement - crossCheckScores.push(rpkiCrossCheck.agreement_pct); + if (rpkiCrossCheck.agreement_pct !== null) crossCheckScores.push(rpkiCrossCheck.agreement_pct); // Prefix agreement: convert to percentage if (prefixCrossCheck.bgp_he_net != null && prefixes.length > 0) { const pfxRatio = Math.min(prefixes.length, prefixCrossCheck.bgp_he_net) / Math.max(prefixes.length, prefixCrossCheck.bgp_he_net); @@ -4977,15 +5101,15 @@ const server = http.createServer(async (req, res) => { } const avgAgreement = crossCheckScores.length > 0 ? Math.round(crossCheckScores.reduce((a, b) => a + b, 0) / crossCheckScores.length) - : 100; - const overallConfidence = avgAgreement > 90 ? "high" : avgAgreement >= 70 ? "medium" : "low"; + : null; + const overallConfidence = avgAgreement === null ? "unknown" : avgAgreement > 90 ? "high" : avgAgreement >= 70 ? "medium" : "low"; const dataQuality = { sources_queried: ["PeeringDB", "RIPE Stat", "bgp.he.net", "Cloudflare RPKI", "RIPE RPKI Validator"], cross_checks: { - rpki: { sources: 2, agreement_pct: rpkiCrossCheck.agreement_pct, sample_size: rpkiCrossCheck.sample_size, disagreements: rpkiCrossCheck.disagreements }, - prefixes: { sources: 2, agreement_pct: prefixCrossCheck.bgp_he_net != null ? Math.round((Math.min(prefixes.length, prefixCrossCheck.bgp_he_net) / Math.max(prefixes.length, prefixCrossCheck.bgp_he_net || 1)) * 100) : null, ripe_stat: prefixCrossCheck.ripe_stat, bgp_he_net: prefixCrossCheck.bgp_he_net, note: prefixCrossCheck.note }, - neighbours: { sources: 2, agreement_pct: neighbourCrossCheck.bgp_he_net_total != null && neighbours.length > 0 ? Math.round((Math.min(neighbours.length, neighbourCrossCheck.bgp_he_net_total) / Math.max(neighbours.length, neighbourCrossCheck.bgp_he_net_total)) * 100) : null, ripe_stat_total: neighbourCrossCheck.ripe_stat_total, bgp_he_net_total: neighbourCrossCheck.bgp_he_net_total }, + rpki: { sources: rpkiCrossCheck.agreement_pct !== null ? 2 : 1, agreement_pct: rpkiCrossCheck.agreement_pct, sample_size: rpkiCrossCheck.sample_size, comparable_size: rpkiCrossCheck.comparable_size, disagreements: rpkiCrossCheck.disagreements }, + prefixes: { sources: prefixCrossCheck.bgp_he_net != null ? 2 : 1, agreement_pct: prefixCrossCheck.bgp_he_net != null ? Math.round((Math.min(prefixes.length, prefixCrossCheck.bgp_he_net) / Math.max(prefixes.length, prefixCrossCheck.bgp_he_net || 1)) * 100) : null, ripe_stat: prefixCrossCheck.ripe_stat, bgp_he_net: prefixCrossCheck.bgp_he_net, note: prefixCrossCheck.note }, + neighbours: { sources: neighbourCrossCheck.bgp_he_net_total != null ? 2 : 1, agreement_pct: neighbourCrossCheck.bgp_he_net_total != null && neighbours.length > 0 ? Math.round((Math.min(neighbours.length, neighbourCrossCheck.bgp_he_net_total) / Math.max(neighbours.length, neighbourCrossCheck.bgp_he_net_total)) * 100) : null, ripe_stat_total: neighbourCrossCheck.ripe_stat_total, bgp_he_net_total: neighbourCrossCheck.bgp_he_net_total }, }, overall_confidence: overallConfidence, overall_agreement_pct: avgAgreement, @@ -5037,6 +5161,7 @@ const server = http.createServer(async (req, res) => { timestamp: new Date().toISOString(), rpki_prefixes_checked: rpkiTotal, total_prefixes: prefixes.length, + degraded_sources: degradedSources, }, network: { asn: parseInt(asn), @@ -5076,6 +5201,7 @@ const server = http.createServer(async (req, res) => { valid: rpkiValid, invalid: rpkiInvalid, not_found: rpkiNotFound, + unavailable: rpkiUnavailable, checked: rpkiTotal, details: rpkiStatuses, cross_check: rpkiCrossCheck, @@ -5161,7 +5287,9 @@ const server = http.createServer(async (req, res) => { // Update duration to include cross-check time result.meta.duration_ms = Date.now() - start; - cacheSet(cacheKey, result, CACHE_TTL_LOOKUP); + // A DB hiccup shouldn't get cached as this ASN's answer for the full 5min -- + // retry soon instead of repeating a degraded result to every visitor. + cacheSet(cacheKey, result, degradedSources.length > 0 ? CACHE_TTL_LOOKUP_DEGRADED : CACHE_TTL_LOOKUP); res.end(JSON.stringify(result, null, 2)); } catch (err) { const duration = Date.now() - start; @@ -5213,12 +5341,39 @@ const server = http.createServer(async (req, res) => { new Promise(r => setTimeout(r, 5000)), ]); + // ---- Threat Intelligence Enrichment ---- + // Enrich neighbors with threat status from local threat_intel table + const threatMap = {}; + const allNeighborsForThreat = [...upstreams, ...downstreams, ...peers]; + const threatPromises = allNeighborsForThreat.slice(0, 100).map(async (n) => { + try { + const asNum = String(n.asn); + const threat = await localDb.getThreatIntel(asNum); + if (threat) { + threatMap[n.asn] = { + threat_level: threat.threat_level, + confidence_score: threat.confidence_score, + source: threat.source, + }; + } + } catch (e) { + console.error(`[Relationships Threat Lookup] Error checking ASN ${n.asn}:`, e.message); + } + }); + await Promise.race([ + Promise.all(threatPromises), + new Promise(r => setTimeout(r, 3000)), + ]); + const fmt = n => ({ asn: n.asn, name: resolvedNames[n.asn] || "", power: n.power || 0, v4_peers: n.v4_peers || 0, v6_peers: n.v6_peers || 0, + threat_level: threatMap[n.asn]?.threat_level || null, + threat_confidence: threatMap[n.asn]?.confidence_score || null, + threat_source: threatMap[n.asn]?.source || null, }); const result = { @@ -5238,7 +5393,10 @@ const server = http.createServer(async (req, res) => { source_url: "https://stat.ripe.net/data/asn-neighbours/data.json?resource=AS" + rawAsn, }; - cacheSet(cacheKey, result, 10 * 60 * 1000); // 10 min cache + // A neighbourData fetch failure (not a genuine zero-neighbour ASN) would + // otherwise get cached as "0 upstreams/downstreams/peers" for the full 10min -- + // match the existing /api/validate precedent (90s TTL for suspicious zero counts). + cacheSet(cacheKey, result, neighbourData ? 10 * 60 * 1000 : 90 * 1000); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(result, null, 2)); } catch (err) { @@ -5323,6 +5481,37 @@ const server = http.createServer(async (req, res) => { .filter((a) => up2Set.has(a)) .map((a) => ({ asn: a, name: nb1Map[a] || nb2Map[a] || "" })); + // ---- Threat Intelligence Enrichment for Common Upstreams ---- + const threatMap = {}; + const threatPromises = commonUpstreams.slice(0, 50).map(async (n) => { + try { + const asNum = String(n.asn); + const threat = await localDb.getThreatIntel(asNum); + if (threat) { + threatMap[n.asn] = { + threat_level: threat.threat_level, + confidence_score: threat.confidence_score, + source: threat.source, + }; + } + } catch (e) { + console.error(`[Compare Threat Lookup] Error checking ASN ${n.asn}:`, e.message); + } + }); + await Promise.race([ + Promise.all(threatPromises), + new Promise(r => setTimeout(r, 2000)), + ]); + + // Attach threat status to upstream objects + commonUpstreams.forEach((n) => { + if (threatMap[n.asn]) { + n.threat_level = threatMap[n.asn].threat_level; + n.threat_confidence = threatMap[n.asn].confidence_score; + n.threat_source = threatMap[n.asn].source; + } + }); + // Resolve names + RPKI sample (max 3+3 prefixes) all in parallel with 5s timeout const pfx1 = (pfx1Data?.data?.prefixes || []).slice(0, 3).map((p) => p.prefix); const pfx2 = (pfx2Data?.data?.prefixes || []).slice(0, 3).map((p) => p.prefix); @@ -5553,21 +5742,19 @@ const server = http.createServer(async (req, res) => { const origins = routingStatus?.data?.origins || []; const firstSeen = routingStatus?.data?.first_seen?.time || null; - // RPKI validation: use local ROA store (instant) instead of RIPE Stat API call + // RPKI validation: use local PostgreSQL database (sub-10ms, zero external API calls) let rpkiStatus = "unknown"; let rpkiRoas = []; const originAsn = origins.length > 0 ? origins[0].asn : null; if (originAsn) { - await ensureAspaCache(); - const localRpki = roaStore.validate(originAsn, prefix); - if (localRpki) { + try { + const localRpki = await validateRPKIWithCache(originAsn, prefix); rpkiStatus = localRpki.status; rpkiRoas = new Array(localRpki.validating_roas); // count only, no detail - } else { - // Fallback to RIPE Stat if ROA store not ready - const rpkiValid = await fetchRipeStatCached("https://stat.ripe.net/data/rpki-validation/data.json?resource=" + encodeURIComponent(prefix)); - rpkiStatus = rpkiValid?.data?.status || "unknown"; - rpkiRoas = rpkiValid?.data?.validating_roas || []; + } catch (e) { + console.error("[Prefix Detail] RPKI validation error:", e.message); + rpkiStatus = "unknown"; + rpkiRoas = []; } } var visData = visibility?.data?.visibilities || []; @@ -5678,6 +5865,38 @@ const server = http.createServer(async (req, res) => { const start = Date.now(); try { const topology = await fetchTopology(parseInt(rawAsn), depth); + + // ---- Threat Intelligence Enrichment for Topology Nodes ---- + const threatMap = {}; + const threatPromises = topology.nodes.slice(0, 100).map(async (node) => { + try { + const asNum = String(node.asn); + const threat = await localDb.getThreatIntel(asNum); + if (threat) { + threatMap[node.asn] = { + threat_level: threat.threat_level, + confidence_score: threat.confidence_score, + source: threat.source, + }; + } + } catch (e) { + console.error(`[Topology Threat Lookup] Error checking ASN ${node.asn}:`, e.message); + } + }); + await Promise.race([ + Promise.all(threatPromises), + new Promise(r => setTimeout(r, 3000)), + ]); + + // Attach threat status to node objects + topology.nodes.forEach((node) => { + if (threatMap[node.asn]) { + node.threat_level = threatMap[node.asn].threat_level; + node.threat_confidence = threatMap[node.asn].confidence_score; + node.threat_source = threatMap[node.asn].source; + } + }); + topology.meta = { query: "AS" + rawAsn, depth: depth, duration_ms: Date.now() - start, timestamp: new Date().toISOString(), node_count: topology.nodes.length, edge_count: topology.edges.length, @@ -5833,51 +6052,14 @@ const server = http.createServer(async (req, res) => { } // Feature 28: Submarine Cable overlay (TeleGeography proxy) - if (reqPath === "/api/submarine-cables") { - const CABLE_TTL = 24 * 60 * 60 * 1000; - if (subCableCache && Date.now() - subCableCache.ts < CABLE_TTL) { - res.setHeader("Content-Type", "application/json"); - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Cache-Control", "public, max-age=86400"); - return res.end(subCableCache.data); - } - const cableData = await fetchJSONWithRetry("https://www.submarinecablemap.com/api/v3/cable/cable-geo.json", { timeout: 30000 }); - if (cableData) { - subCableCache = { ts: Date.now(), data: JSON.stringify(cableData) }; - res.setHeader("Content-Type", "application/json"); - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Cache-Control", "public, max-age=86400"); - return res.end(subCableCache.data); - } - res.writeHead(503); - return res.end(JSON.stringify({ error: "Submarine cable data unavailable" })); + // ── Submarine Cables map data ───────────────────────────────── + if (reqPath === '/api/submarine-cables') { + return proxyToApiServer(req, res, req.url); } - // Feature 29: Global datacenter/IXP map (PeeringDB proxy) - if (reqPath === "/api/global-infra") { - const FAC_TTL = 24 * 60 * 60 * 1000; - if (globalFacCache && Date.now() - globalFacCache.ts < FAC_TTL) { - res.setHeader("Content-Type", "application/json"); - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Cache-Control", "public, max-age=86400"); - return res.end(globalFacCache.data); - } - const [facData, ixData] = await Promise.all([ - fetchJSONWithRetry(PEERINGDB_API_URL + "/fac?depth=1&limit=3000", { timeout: 30000 }), - fetchJSONWithRetry(PEERINGDB_API_URL + "/ix?depth=1&limit=1000", { timeout: 30000 }), - ]); - const facs = (facData && facData.data || []) - .filter(f => f.latitude && f.longitude) - .map(f => ({ id: f.id, name: f.name, city: f.city, country: f.country, lat: +f.latitude, lng: +f.longitude })); - const ixps = (ixData && ixData.data || []) - .filter(ix => ix.city && ix.country) - .map(ix => ({ id: ix.id, name: ix.name, city: ix.city, country: ix.country, website: ix.website })); - const result = JSON.stringify({ facs, ixps }); - globalFacCache = { ts: Date.now(), data: result }; - res.setHeader("Content-Type", "application/json"); - res.setHeader("Access-Control-Allow-Origin", "*"); - res.setHeader("Cache-Control", "public, max-age=86400"); - return res.end(result); + // ── Global datacenter/IXP map (PeeringDB proxy) ─────────────── + if (reqPath === '/api/global-infra') { + return proxyToApiServer(req, res, req.url); } @@ -5926,262 +6108,40 @@ ${html} // ── BGP Community Decoder ──────────────────────────────────── if (reqPath === '/api/communities') { - const params = new URL(req.url, 'http://localhost').searchParams; - const asn = params.get('asn') || ''; - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Cache-Control', 'public, max-age=3600'); - try { - const url = `https://stat.ripe.net/data/bgp-state/data.json?resource=AS${asn.replace('AS','')}`; - const data = await fetchJSON(url, { timeout: 6000 }); - const rawComms = []; - if (data && data.data && data.data.bgp_state) { - for (const entry of data.data.bgp_state.slice(0, 50)) { - if (entry.community) rawComms.push(...entry.community); - } - } - const unique = [...new Set(rawComms.map(c => Array.isArray(c) ? c.join(':') : String(c)))]; - const decoded = unique.map(k => ({ raw: k, known: BGP_COMMUNITY_DB[k] || null })); - res.writeHead(200); - return res.end(JSON.stringify({ asn, communities: decoded, db_size: Object.keys(BGP_COMMUNITY_DB).length })); - } catch(e) { - res.writeHead(200); - return res.end(JSON.stringify({ asn, communities: [], error: e.message })); - } + return proxyToApiServer(req, res, req.url); } // ── IRR Audit ───────────────────────────────────────────────── - if (reqPath.startsWith('/api/irr-audit')) { - const params = new URL(req.url, 'http://localhost').searchParams; - const asn = (params.get('asn') || '').replace(/[^0-9]/g,''); - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Cache-Control', 'public, max-age=1800'); - if (!asn) { res.writeHead(400); return res.end(JSON.stringify({error:'asn required'})); } - try { - // Use NLNOG IRR Explorer — covers RIPE, ARIN, APNIC, RPKI, and all major IRR databases - const nlnogData = await fetchJSONWithRetry( - 'https://irrexplorer.nlnog.net/api/prefixes/asn/AS' + asn, - { timeout: 20000 } - ); - const prefixes = nlnogData && nlnogData.directOrigin || []; - var irrRoutes = []; - var irrDetails = []; - var goodCount = 0; - var warnCount = 0; - var errorCount = 0; - for (var i = 0; i < prefixes.length; i++) { - var pfx = prefixes[i]; - var hasIrr = pfx.irrRoutes && Object.keys(pfx.irrRoutes).length > 0; - var sources = hasIrr ? Object.keys(pfx.irrRoutes) : []; - var cat = pfx.categoryOverall || 'unknown'; - if (hasIrr) irrRoutes.push(pfx.prefix); - irrDetails.push({ - prefix: pfx.prefix, - irr_sources: sources, - rpki_status: pfx.rpkiRoutes && pfx.rpkiRoutes.length ? pfx.rpkiRoutes[0].rpkiStatus : 'not-found', - category: cat, - messages: (pfx.messages || []).map(function(m){ return m.text; }) - }); - if (cat === 'success') goodCount++; - else if (cat === 'warning') warnCount++; - else errorCount++; - } - var actualPfx = prefixes.map(function(p){ return p.prefix; }); - var inBgpNotIrr = actualPfx.filter(function(p){ return !irrRoutes.includes(p); }); - var score = actualPfx.length ? Math.round(irrRoutes.length / actualPfx.length * 100) : 0; - res.writeHead(200); - return res.end(JSON.stringify({ - asn: asn, - irr_routes: irrRoutes, - actual_prefixes: actualPfx, - in_irr_not_bgp: [], - in_bgp_not_irr: inBgpNotIrr, - score: score, - details: irrDetails, - summary: { good: goodCount, warning: warnCount, error: errorCount, total: prefixes.length }, - source: 'NLNOG IRR Explorer' - })); - } catch(e) { - res.writeHead(500); return res.end(JSON.stringify({error:e.message})); - } + if (reqPath === '/api/irr-audit') { + return proxyToApiServer(req, res, req.url); } // ── AS-SET Expander ─────────────────────────────────────────── - if (reqPath.startsWith('/api/asset-expand')) { - const params = new URL(req.url, 'http://localhost').searchParams; - const setName = params.get('set') || ''; - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Cache-Control', 'public, max-age=3600'); - if (!setName) { res.writeHead(400); return res.end(JSON.stringify({error:'set required (e.g. AS-FLEXOPTIX)'})); } - try { - async function expandSet(name, depth, visited) { - if (depth > 4 || visited.has(name)) return { asns: [], sets: [] }; - visited.add(name); - const url = `https://rest.db.ripe.net/search.json?query-string=${encodeURIComponent(name)}&type-filter=as-set&flags=no-referenced`; - const data = await fetchJSONWithRetry(url, { timeout: 10000 }); - const asns = [], sets = []; - if (data && data.objects && data.objects.object) { - for (const obj of data.objects.object) { - const attrs = obj.attributes && obj.attributes.attribute || []; - for (const a of attrs) { - if (a.name === 'members') { - for (const m of (a.value || '').split(/[,\s]+/).filter(Boolean)) { - if (/^AS\d+$/i.test(m)) asns.push(m.toUpperCase()); - else if (m.startsWith('AS-')) { sets.push(m); } - } - } - } - } - } - for (const sub of sets.slice(0,10)) { - const sub_r = await expandSet(sub, depth+1, visited); - asns.push(...sub_r.asns); - } - return { asns: [...new Set(asns)], sets }; - } - const visited = new Set(); - const result = await expandSet(setName.toUpperCase(), 0, visited); - result.asns.sort((a,b) => parseInt(a.slice(2)) - parseInt(b.slice(2))); - res.writeHead(200); - return res.end(JSON.stringify({ set: setName.toUpperCase(), count: result.asns.length, asns: result.asns, sub_sets: result.sets })); - } catch(e) { - res.writeHead(500); return res.end(JSON.stringify({error:e.message})); - } + if (reqPath === '/api/asset-expand') { + return proxyToApiServer(req, res, req.url); } // ── Routing History (prefix table via RIPE Stat routing-history) ── - if (reqPath.startsWith('/api/rpki-history')) { - const params = new URL(req.url, 'http://localhost').searchParams; - const asn = (params.get('asn') || '').replace(/[^0-9]/g,''); - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Cache-Control', 'public, max-age=3600'); - if (!asn) { res.writeHead(400); return res.end(JSON.stringify({error:'asn required'})); } - try { - const url = 'https://stat.ripe.net/data/routing-history/data.json?resource=AS' + asn + '&max_rows=100'; - const data = await fetchJSON(url, { timeout: 6000 }); - const byOrigin = data && data.data && data.data.by_origin || []; - // Flatten: each origin entry has prefixes[] - var prefixes = []; - for (var i = 0; i < byOrigin.length; i++) { - var orig = byOrigin[i]; - if (orig.prefixes) { - for (var j = 0; j < orig.prefixes.length; j++) { - prefixes.push(orig.prefixes[j]); - } - } - } - res.writeHead(200); - return res.end(JSON.stringify({ asn: asn, prefixes: prefixes, source: 'RIPE Stat routing-history' })); - } catch(e) { - res.writeHead(500); return res.end(JSON.stringify({error:e.message})); - } + if (reqPath === '/api/rpki-history') { + return proxyToApiServer(req, res, req.url); } // ── AS-PATH Visualizer (RIPE Stat looking-glass) ──────────────── - if (reqPath.startsWith('/api/aspath')) { - const params = new URL(req.url, 'http://localhost').searchParams; - const asn = (params.get('asn') || '').replace(/[^0-9]/g,''); - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Cache-Control', 'public, max-age=300'); - if (!asn) { res.writeHead(400); return res.end(JSON.stringify({error:'asn required'})); } - try { - // Use RIPE Stat announced-prefixes to get prefixes, then looking-glass for paths - var annUrl = 'https://stat.ripe.net/data/announced-prefixes/data.json?resource=AS' + asn; - var annData = await fetchJSON(annUrl, { timeout: 5000 }); - var announced = annData && annData.data && annData.data.prefixes || []; - var prefix = announced.length > 0 ? announced[0].prefix : null; - if (!prefix) { - res.writeHead(200); - return res.end(JSON.stringify({ asn: asn, paths: [], source: 'RIPE Stat' })); - } - // Get looking-glass data for the first announced prefix - var lgUrl = 'https://stat.ripe.net/data/looking-glass/data.json?resource=' + encodeURIComponent(prefix); - var lgData = await fetchJSON(lgUrl, { timeout: 6000 }); - var rrcs = lgData && lgData.data && lgData.data.rrcs || []; - var paths = []; - var seen = new Set(); - for (var i = 0; i < rrcs.length && paths.length < 10; i++) { - var rrc = rrcs[i]; - var peers = rrc.peers || []; - for (var j = 0; j < peers.length && paths.length < 10; j++) { - var p = peers[j]; - var pathStr = (p.as_path || '').trim(); - if (pathStr && !seen.has(pathStr)) { - seen.add(pathStr); - paths.push({ - path: pathStr, - prefix: prefix, - rrc: rrc.rrc + ' (' + (rrc.location||'') + ')', - peer_asn: p.asn_origin || '' - }); - } - } - } - res.writeHead(200); - return res.end(JSON.stringify({ asn: asn, paths: paths, prefix: prefix, source: 'RIPE Stat looking-glass · ' + prefix })); - } catch(e) { - res.writeHead(500); return res.end(JSON.stringify({error:e.message})); - } + if (reqPath === '/api/aspath') { + return proxyToApiServer(req, res, req.url); } // ── Looking Glass (RIPE Stat) ───────────────────────────────── - if (reqPath.startsWith('/api/looking-glass')) { - const params = new URL(req.url, 'http://localhost').searchParams; - const resource = params.get('prefix') || params.get('asn') || ''; - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Cache-Control', 'no-store'); - if (!resource) { res.writeHead(400); return res.end(JSON.stringify({error:'prefix or asn required'})); } - try { - const url = `https://stat.ripe.net/data/looking-glass/data.json?resource=${encodeURIComponent(resource)}`; - const data = await fetchJSON(url, { timeout: 6000 }); - const rrcs = data && data.data && data.data.rrcs || []; - const results = rrcs.slice(0, 15).map(rrc => ({ - rrc: rrc.rrc, - location: rrc.location, - peers: (rrc.peers || []).slice(0,5).map(p => ({ - asn: p.asn_origin, - as_path: p.as_path, - community: p.community, - next_hop: p.next_hop, - })) - })); - res.writeHead(200); - return res.end(JSON.stringify({ resource, rrcs: results, total_rrcs: rrcs.length })); - } catch(e) { - res.writeHead(500); return res.end(JSON.stringify({error:e.message})); - } + if (reqPath === '/api/looking-glass') { + return proxyToApiServer(req, res, req.url); } // ── IXP Peering Matrix ──────────────────────────────────────── - if (reqPath.startsWith('/api/ix-matrix')) { - const params = new URL(req.url, 'http://localhost').searchParams; - const ixId = (params.get('ix_id') || '').replace(/[^0-9]/g,''); - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Cache-Control', 'public, max-age=3600'); - if (!ixId) { res.writeHead(400); return res.end(JSON.stringify({error:'ix_id required'})); } - try { - const [netixData, ixData] = await Promise.all([ - fetchJSONWithRetry(`${PEERINGDB_API_URL}/netixlan?ix_id=${ixId}&depth=1&limit=200`, { timeout: 15000 }), - fetchJSONWithRetry(`${PEERINGDB_API_URL}/ix/${ixId}`, { timeout: 10000 }), - ]); - const ix = ixData && ixData.data && ixData.data[0]; - const members = (netixData && netixData.data || []).map(m => ({ - asn: m.asn, name: m.name, speed: m.speed, ipaddr4: m.ipaddr4, ipaddr6: m.ipaddr6, policy: m.policy_general - })); - members.sort((a,b) => (b.speed||0) - (a.speed||0)); - res.writeHead(200); - return res.end(JSON.stringify({ ix_id: ixId, ix_name: ix && ix.name, ix_city: ix && ix.city, members, member_count: members.length })); - } catch(e) { - res.writeHead(500); return res.end(JSON.stringify({error:e.message})); - } + if (reqPath === '/api/ix-matrix') { + return proxyToApiServer(req, res, req.url); } + // ── CORS preflight for all /api/webhooks + /api/hijacks ────── if ((reqPath.startsWith('/api/webhooks') || reqPath.startsWith('/api/hijacks') || reqPath === '/api/hijack-subscribe') && req.method === 'OPTIONS') { res.setHeader('Access-Control-Allow-Origin', '*'); @@ -6227,7 +6187,9 @@ ${html} const hijackSubs = loadHijackSubs(); if (!hijackSubs.find(s => s.asn === asn)) { checkHijacksForAsn(asn).then(prefixes => { - hijackSubs.push({ asn, prefixes, subscribed: new Date().toISOString() }); + // null (lookup failed) becomes [] here so this record's shape stays + // consistent; runHijackCheck retries the real baseline next cycle. + hijackSubs.push({ asn, prefixes: prefixes || [], subscribed: new Date().toISOString() }); saveHijackSubs(hijackSubs); }); } @@ -6342,12 +6304,16 @@ ${html} const exists = subs.find(s => s.asn === asnNum); if (!exists) { const prefixes = await checkHijacksForAsn(asnNum); - subs.push({ asn: asnNum, prefixes, subscribed: new Date().toISOString() }); + // prefixes may be null if the lookup failed just now -- store an empty + // array so this record shape stays consistent; runHijackCheck's own + // "!sub.prefixes.length" check will retry establishing the real + // baseline on its next cycle rather than treating this as permanent. + subs.push({ asn: asnNum, prefixes: prefixes || [], subscribed: new Date().toISOString() }); saveHijackSubs(subs); } const sub = subs.find(s => s.asn === asnNum) || { prefixes: [] }; res.writeHead(200); - res.end(JSON.stringify({ ok: true, asn: asnNum, monitoring: true, prefix_count: sub.prefixes.length })); + res.end(JSON.stringify({ ok: true, asn: asnNum, monitoring: true, prefix_count: (sub.prefixes || []).length })); } catch(e) { res.writeHead(500); res.end(JSON.stringify({error:e.message})); } }); return; @@ -6369,187 +6335,26 @@ ${html} } + // ── Hijack Alerts (legacy read) ─────────────────────────────── + // src/routes/hijack-alerts.ts registers /api/webhooks + /api/hijacks under + // the same paths this file already serves above (file-backed, live). + // Deliberately NOT proxied here — that would shadow working data with an + // empty Postgres-backed implementation. Not a gap, just two feature + // branches that ended up naming their routes the same thing. // ── Changelog JSON API ──────────────────────────────────────── if (reqPath === '/changelog-data') { - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Cache-Control', 'public, max-age=3600'); - try { - const md = fs.readFileSync('/opt/peercortex-app/CHANGELOG.md', 'utf8'); - const entries = []; - let current = null; - let currentSection = null; - for (const line of md.split('\n')) { - // Support both ## [0.6.x] — date AND ## v0.6.x — date - const vMatch = line.match(/^## (?:v|\[)?([\d.]+)\]? — (.+)/); - if (vMatch) { - if (current) entries.push(current); - current = { version: vMatch[1], date: vMatch[2].trim(), sections: [] }; - currentSection = null; - continue; - } - const sMatch = line.match(/^### (.+)/); - if (sMatch && current) { - currentSection = { name: sMatch[1], items: [] }; - current.sections.push(currentSection); - continue; - } - const iMatch = line.match(/^- (.+)/); - if (iMatch && currentSection) { - currentSection.items.push(iMatch[1]); - } - } - if (current) entries.push(current); - res.writeHead(200); - return res.end(JSON.stringify(entries)); - } catch(e) { - res.writeHead(500); return res.end(JSON.stringify({error:e.message})); - } + return proxyToApiServer(req, res, req.url); } - // ── bio-rd RIB: prefix lookup ────────────────────────────────── - if (reqPath === '/api/rib/prefix') { - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Cache-Control', 'no-store'); - if (!risClient) { res.writeHead(503); return res.end(JSON.stringify({ error: 'bio-rd RIS not configured' })); } - const params = new URL(req.url, 'http://localhost').searchParams; - const prefix = params.get('prefix') || ''; - const routerParam = params.get('router') || 'default'; - if (!prefix) { res.writeHead(400); return res.end(JSON.stringify({ error: 'prefix required' })); } - try { - const t0 = Date.now(); - const routers = await risClient.getRouters(); - const routerName = (routerParam === 'default' && routers.length > 0) ? routers[0] : routerParam; - const [routes, longer] = await Promise.all([ - risClient.lpm(routerName, prefix), - risClient.getLonger(routerName, prefix), - ]); - res.writeHead(200); - return res.end(JSON.stringify({ - prefix, - router: routerName, - routes: routes || [], - moreSpecifics: (longer || []).slice(0, 20), - source: 'bio-rd-local', - latencyMs: Date.now() - t0, - })); - } catch(e) { - res.writeHead(500); return res.end(JSON.stringify({ error: e.message })); - } - } - - // ── bio-rd RIB: list routers ─────────────────────────────────── - if (reqPath === '/api/rib/routers') { - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Cache-Control', 'no-store'); - if (!risClient) { res.writeHead(503); return res.end(JSON.stringify({ error: 'bio-rd RIS not configured' })); } - try { - const routers = await risClient.getRouters(); - res.writeHead(200); - return res.end(JSON.stringify({ routers: routers || [], source: 'bio-rd-local' })); - } catch(e) { - res.writeHead(500); return res.end(JSON.stringify({ error: e.message })); - } - } - - // ── bio-rd RIB: dump ─────────────────────────────────────────── - if (reqPath === '/api/rib/dump') { - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Cache-Control', 'no-store'); - if (!risClient) { res.writeHead(503); return res.end(JSON.stringify({ error: 'bio-rd RIS not configured' })); } - const params = new URL(req.url, 'http://localhost').searchParams; - const router = params.get('router') || ''; - const asnFilter = params.get('asn') ? parseInt(params.get('asn')) : undefined; - const limit = Math.min(parseInt(params.get('limit') || '100', 10), 1000); - if (!router) { res.writeHead(400); return res.end(JSON.stringify({ error: 'router required' })); } - try { - const t0 = Date.now(); - const allRoutes = await risClient.dumpRib(router, 'default', asnFilter); - const routes = (allRoutes || []).slice(0, limit); - res.writeHead(200); - return res.end(JSON.stringify({ - router, - routes, - total: (allRoutes || []).length, - source: 'bio-rd-local', - latencyMs: Date.now() - t0, - })); - } catch(e) { - res.writeHead(500); return res.end(JSON.stringify({ error: e.message })); - } + // ── bio-rd RIB routes ───────────────────────────────────────── + if (reqPath.startsWith('/api/rib/')) { + return proxyToApiServer(req, res, req.url); } // ── Prefix Changes ────────────────────────────────────────────── if (reqPath === '/api/prefix-changes') { - res.setHeader('Content-Type', 'application/json'); - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Cache-Control', 'no-store'); - const rawAsn = (url.searchParams.get('asn') || '').replace(/[^0-9]/g, ''); - if (!rawAsn) { res.writeHead(400); return res.end(JSON.stringify({ error: 'Missing ASN' })); } - const fromParam = url.searchParams.get('from'); - const toParam = url.searchParams.get('to'); - const hoursParam = Math.min(parseInt(url.searchParams.get('hours') || '1', 10), 168); - let starttime, endtime; - if (fromParam && toParam) { - starttime = new Date(fromParam).toISOString(); - endtime = new Date(toParam).toISOString(); - } else { - endtime = new Date().toISOString(); - starttime = new Date(Date.now() - hoursParam * 3600000).toISOString(); - } - try { - const updUrl = `https://stat.ripe.net/data/bgp-updates/data.json?resource=AS${rawAsn}&starttime=${encodeURIComponent(starttime)}&endtime=${encodeURIComponent(endtime)}&limit=1000`; - const raw = await fetchJSON(updUrl, { timeout: 8000 }); - const updates = (raw && raw.data && raw.data.updates && raw.data.updates.updates) || []; - - const announcements = [], withdrawals = [], originChanges = [], rpkiIssues = []; - const lastOriginByPrefix = {}, rpkiSeen = new Set(); - - for (const u of updates) { - const prefix = u.attrs && u.attrs.prefix; if (!prefix) continue; - const originRaw = u.attrs && u.attrs.origin; - const origin = originRaw ? parseInt(String(originRaw).replace('AS', ''), 10) : null; - const ts = u.timestamp || ''; - const peer = u.peer || ''; - - if (u.type === 'A') { - const rpki = (origin && roaStore.ready) ? roaStore.validate(origin, prefix) : null; - const rpkiStatus = rpki ? rpki.status : 'unknown'; - announcements.push({ prefix, timestamp: ts, peer, origin, rpki_status: rpkiStatus }); - - if (lastOriginByPrefix[prefix] !== undefined && lastOriginByPrefix[prefix] !== origin) { - originChanges.push({ prefix, from_origin: lastOriginByPrefix[prefix], to_origin: origin, timestamp: ts, peer }); - } - lastOriginByPrefix[prefix] = origin; - - if (!rpkiSeen.has(prefix) && rpkiStatus !== 'valid') { - rpkiSeen.add(prefix); - rpkiIssues.push({ prefix, origin, rpki_status: rpkiStatus, timestamp: ts }); - } - } else if (u.type === 'W') { - withdrawals.push({ prefix, timestamp: ts, peer }); - } - } - - res.writeHead(200); - return res.end(JSON.stringify({ - asn: parseInt(rawAsn, 10), - time_range: { from: starttime, to: endtime }, - total_updates: updates.length, - summary: { announcements: announcements.length, withdrawals: withdrawals.length, origin_changes: originChanges.length, rpki_issues: rpkiIssues.length }, - announcements, - withdrawals, - origin_changes: originChanges, - rpki_issues: rpkiIssues, - })); - } catch(e) { - res.writeHead(500); - return res.end(JSON.stringify({ error: e.message })); - } + return proxyToApiServer(req, res, req.url); } // ============================================================ diff --git a/src/backend/config.js b/src/backend/config.js new file mode 100644 index 0000000..23b9076 --- /dev/null +++ b/src/backend/config.js @@ -0,0 +1,28 @@ +const fs = require("fs"); + +function loadEnv() { + const envPath = "/opt/peercortex-app/.env"; + try { + const envContent = fs.readFileSync(envPath, "utf8"); + envContent.split("\n").forEach((line) => { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith("#")) return; + const eqIdx = trimmed.indexOf("="); + if (eqIdx > 0) { + const key = trimmed.substring(0, eqIdx).trim(); + const val = trimmed.substring(eqIdx + 1).trim(); + if (!process.env[key]) process.env[key] = val; + } + }); + console.log('[Config] Environment variables loaded'); + } catch (_e) { + console.warn("Warning: Could not read .env file at", envPath); + } +} + +// Automatically load environment variables when module is required +loadEnv(); + +module.exports = { + loadEnv +}; diff --git a/src/backend/services/smtp.js b/src/backend/services/smtp.js new file mode 100644 index 0000000..e734748 --- /dev/null +++ b/src/backend/services/smtp.js @@ -0,0 +1,78 @@ +const tls = require('tls'); +const net = require('net'); + +const SMTP_HOST = 'mail.fichtmueller.org'; +const SMTP_PORT = 587; +const MAIL_TO = 'peercortex@context-x.org'; +const MAIL_FROM = 'PeerCortex Feedback '; + +function sendFeedbackMail(entry) { + const SMTP_USER = process.env.SMTP_USER; + const SMTP_PASS = process.env.SMTP_PASS; + + return new Promise(function(resolve, reject) { + var b64 = function(s) { return Buffer.from(s).toString('base64'); }; + var CRLF = '\r\n'; + var body = 'Category : ' + entry.category + CRLF + + 'Name : ' + entry.name + CRLF + + 'ASN : ' + (entry.asn || '-') + CRLF + + 'Time : ' + entry.timestamp + CRLF + CRLF + + entry.message + CRLF + CRLF + '-' + CRLF + 'PeerCortex Feedback'; + var subj = '[PeerCortex Feedback] ' + entry.category + (entry.asn ? ' - AS' + entry.asn : ''); + var msg = 'From: ' + MAIL_FROM + CRLF + + 'To: ' + MAIL_TO + CRLF + + 'Subject: ' + subj + CRLF + + 'MIME-Version: 1.0' + CRLF + + 'Content-Type: text/plain; charset=UTF-8' + CRLF + CRLF + + body; + + var socket = net.connect(SMTP_PORT, SMTP_HOST); + var tlsSocket = null; + var buf = ''; + var step = 0; + var done = false; + + function send(line) { + var s = tlsSocket || socket; + s.write(line + CRLF); + } + + function onData(data) { + buf += data.toString(); + var lines = buf.split(CRLF); + buf = lines.pop(); + for (var i = 0; i < lines.length; i++) { + var line = lines[i]; + var code = parseInt(line.slice(0, 3)); + if (isNaN(code) || line[3] === '-') continue; + if (step === 0 && code === 220) { send('EHLO peercortex.org'); step = 1; } + else if (step === 1 && code === 250) { send('STARTTLS'); step = 2; } + else if (step === 2 && code === 220) { + tlsSocket = tls.connect({ socket: socket, servername: SMTP_HOST, rejectUnauthorized: false }, function() { + tlsSocket.on('data', onData); + send('EHLO peercortex.org'); + step = 3; + }); + tlsSocket.on('error', function(e) { if (!done) { done = true; reject(e); } }); + } + else if (step === 3 && code === 250) { send('AUTH LOGIN'); step = 4; } + else if (step === 4 && code === 334) { send(b64(SMTP_USER)); step = 5; } + else if (step === 5 && code === 334) { send(b64(SMTP_PASS)); step = 6; } + else if (step === 6 && code === 235) { send('MAIL FROM:<' + SMTP_USER + '>'); step = 7; } + else if (step === 7 && code === 250) { send('RCPT TO:<' + MAIL_TO + '>'); step = 8; } + else if (step === 8 && code === 250) { send('DATA'); step = 9; } + else if (step === 9 && code === 354) { send(msg + CRLF + '.'); step = 10; } + else if (step === 10 && code === 250) { send('QUIT'); if (!done) { done = true; resolve(); } } + else if (code >= 400) { if (!done) { done = true; reject(new Error('SMTP ' + code + ': ' + line)); } } + } + } + + socket.on('data', onData); + socket.on('error', function(e) { if (!done) { done = true; reject(e); } }); + setTimeout(function() { if (!done) { done = true; reject(new Error('SMTP timeout')); } }, 15000); + }); +} + +module.exports = { + sendFeedbackMail +};