Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
f484bf1
Bump ruff from 0.14.14 to 0.15.12
dependabot[bot] Apr 26, 2026
8cbe6d0
Bump pydantic from 2.12.5 to 2.13.3
dependabot[bot] Apr 26, 2026
669efa0
build(deps): bump the github-actions group across 1 directory with 8 …
dependabot[bot] May 3, 2026
3c67968
build(deps-dev): bump maturin from 1.11.5 to 1.13.1
dependabot[bot] May 5, 2026
726a35d
Merge pull request #258 from blacklanternsecurity/dependabot/uv/dev/p…
liquidsec Jun 10, 2026
94eee2a
Merge pull request #257 from blacklanternsecurity/dependabot/uv/dev/r…
liquidsec Jun 10, 2026
1d3dcf7
Merge pull request #254 from blacklanternsecurity/dependabot/uv/dev/m…
liquidsec Jun 10, 2026
f92930e
Merge pull request #252 from blacklanternsecurity/dependabot/github_a…
liquidsec Jun 10, 2026
1ffd3b3
build(deps-dev): bump maturin from 1.13.1 to 1.14.0
dependabot[bot] Jun 14, 2026
bac1aa9
build(deps-dev): bump pydantic from 2.13.3 to 2.13.4
dependabot[bot] Jun 14, 2026
010648f
build(deps-dev): bump ruff from 0.15.12 to 0.15.17
dependabot[bot] Jun 14, 2026
f86e4e1
build(deps): bump the github-actions group with 8 updates
dependabot[bot] Jun 14, 2026
a4a9497
replace httpx with blasthttp, bump version to 11.0.0
liquidsec Jun 17, 2026
f420799
bump minimum python version to 3.10
liquidsec Jun 17, 2026
5bc001c
Merge pull request #267 from blacklanternsecurity/dependabot/uv/dev/r…
liquidsec Jun 17, 2026
ea236cf
Merge pull request #265 from blacklanternsecurity/dependabot/uv/dev/m…
liquidsec Jun 17, 2026
c78f0e9
Merge pull request #266 from blacklanternsecurity/dependabot/uv/dev/p…
liquidsec Jun 17, 2026
b46dad0
Merge pull request #268 from blacklanternsecurity/dependabot/github_a…
liquidsec Jun 17, 2026
667c3ca
add automatic release tagging on publish
liquidsec Jun 17, 2026
cfd6f8c
use github app token for daily update push
liquidsec Jun 17, 2026
f68543f
regenerate uv.lock for blasthttp
liquidsec Jun 17, 2026
756180f
use app token for release tag push
liquidsec Jun 17, 2026
22400ae
Merge remote-tracking branch 'origin/dev' into blasthttp-update
liquidsec Jun 17, 2026
b982b31
add ASN fallbacks for Korean providers, fix Microsoft IP ranges URL
liquidsec Jun 17, 2026
9e2fa0a
add Vultr, UpCloud, and BunnyCDN providers
liquidsec Jun 17, 2026
2fec200
Merge pull request #270 from blacklanternsecurity/blasthttp-update
liquidsec Jun 17, 2026
83ef3a7
move blasthttp import into request() to fix clean install import error
liquidsec Jun 17, 2026
2bc52de
fix blasthttp headers type and event loop, fix fetch_asns list/set bu…
liquidsec Jun 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cla.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
steps:
- name: Generate token from GitHub App
id: app-token
uses: actions/create-github-app-token@v1
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/daily-update.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,18 @@ jobs:
contents: write

steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.CLOUDCHECK_APP_ID }}
private-key: ${{ secrets.CLOUDCHECK_APP_PRIVATE_KEY }}

- name: Checkout stable branch
uses: actions/checkout@v6
with:
ref: stable
token: ${{ steps.app-token.outputs.token }}

- name: Set up Python
uses: actions/setup-python@v6
Expand Down
16 changes: 8 additions & 8 deletions .github/workflows/docker-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ jobs:
pip install requests

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4

- name: Run Docker tests
run: |
Expand All @@ -42,30 +42,30 @@ jobs:
uses: actions/checkout@v6

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4

- name: Log in to Docker Hub
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
username: blacklanternsecurity
password: ${{ secrets.DOCKER_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: blacklanternsecurity/cloudcheck
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix=sha-
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=v9
type=raw,value=v9.2
type=raw,value=v9.2.0
type=raw,value=v11
type=raw,value=v11.0
type=raw,value=v11.0.0

- name: Build and push Docker image
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile
Expand Down
50 changes: 42 additions & 8 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v6
Expand Down Expand Up @@ -47,8 +47,19 @@ jobs:
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/stable'
permissions:
contents: write
steps:
- name: Generate app token
id: app-token
uses: actions/create-github-app-token@v3
with:
app-id: ${{ secrets.CLOUDCHECK_APP_ID }}
private-key: ${{ secrets.CLOUDCHECK_APP_PRIVATE_KEY }}
- uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Set up Python
uses: actions/setup-python@v6
with:
Expand All @@ -57,10 +68,33 @@ jobs:
uses: dtolnay/rust-toolchain@stable
- name: Set up uv
uses: astral-sh/setup-uv@v7
- name: Get current version
id: get_version
run: |
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
echo "VERSION=v${VERSION}" >> $GITHUB_OUTPUT
- name: Check for version change
id: version_check
run: |
git fetch --tags
LATEST_TAG=$(git describe --tags $(git rev-list --tags --max-count=1) 2>/dev/null || echo "none")
CURRENT="${{ steps.get_version.outputs.VERSION }}"
if [ "$LATEST_TAG" = "$CURRENT" ]; then
echo "changed=false" >> $GITHUB_OUTPUT
else
echo "changed=true" >> $GITHUB_OUTPUT
fi
- name: Build PyPi package
run: uv run maturin build --release --out dist
- name: Publish PyPi package
run: uv run maturin publish --skip-existing --username __token__ --password ${{ secrets.PYPI_TOKEN }}
- name: Tag release
if: steps.version_check.outputs.changed == 'true'
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "${{ steps.get_version.outputs.VERSION }}" -m "Release ${{ steps.get_version.outputs.VERSION }}"
git push origin "refs/tags/${{ steps.get_version.outputs.VERSION }}"
linux:
runs-on: ${{ matrix.platform.runner }}
needs: publish
Expand Down Expand Up @@ -107,7 +141,7 @@ jobs:
exit 1
fi
- name: Upload wheels
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: wheels-linux-${{ matrix.platform.target }}
path: dist
Expand Down Expand Up @@ -157,7 +191,7 @@ jobs:
fi

- name: Upload wheels
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: wheels-musllinux-${{ matrix.platform.target }}
path: dist
Expand Down Expand Up @@ -186,7 +220,7 @@ jobs:
args: --release --out dist --find-interpreter
sccache: false
- name: Upload wheels
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: wheels-windows-${{ matrix.platform.target }}
path: dist
Expand Down Expand Up @@ -214,7 +248,7 @@ jobs:
args: --release --out dist --find-interpreter
sccache: false
- name: Upload wheels
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: wheels-macos-${{ matrix.platform.target }}
path: dist
Expand All @@ -231,7 +265,7 @@ jobs:
command: sdist
args: --out dist
- name: Upload sdist
uses: actions/upload-artifact@v6
uses: actions/upload-artifact@v7
with:
name: wheels-sdist
path: dist
Expand All @@ -249,9 +283,9 @@ jobs:
# Used to generate artifact attestation
attestations: write
steps:
- uses: actions/download-artifact@v7
- uses: actions/download-artifact@v8
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v3
uses: actions/attest-build-provenance@v4
with:
subject-path: 'wheels-*/*'
- name: Publish to PyPI
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

# Python
__pycache__/
*.so
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "cloudcheck"
version = "10.0.1"
version = "11.0.0"
edition = "2024"
description = "CloudCheck is a simple Rust tool to check whether an IP address or hostname belongs to a cloud provider."
license = "GPL-3.0"
Expand Down
20 changes: 16 additions & 4 deletions cloudcheck/helpers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import ipaddress
import os
import sys
import httpx
from pathlib import Path
from typing import List, Set, Union

Expand Down Expand Up @@ -204,8 +203,16 @@ def strings_to_cidrs(
}


_client = None


def request(url, include_api_key=False, browser_headers=False, timeout=60, **kwargs):
global _warned_missing_api_key
import asyncio
import blasthttp

global _warned_missing_api_key, _client
if _client is None:
_client = blasthttp.BlastHTTP()
headers = kwargs.get("headers", {})
if browser_headers:
headers.update(browser_base_headers)
Expand All @@ -221,10 +228,15 @@ def request(url, include_api_key=False, browser_headers=False, timeout=60, **kwa
"running the update.",
file=sys.stderr,
)
kwargs["headers"] = headers
kwargs["headers"] = list(headers.items())
kwargs["timeout"] = timeout
kwargs.setdefault("follow_redirects", True)
return httpx.get(url, **kwargs)
kwargs.setdefault("verify_certs", True)

async def _send():
return await _client.request(url, **kwargs)

return asyncio.run(_send())


def parse_v2fly_domain_file(file_path: Path) -> Set[str]:
Expand Down
2 changes: 1 addition & 1 deletion cloudcheck/providers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ def fetch_org_ids(

def fetch_asns(self) -> List[Union[ipaddress.IPv4Network, ipaddress.IPv6Network]]:
"""Fetch CIDRs for a given list of ASNs from ASNDB."""
cidrs = []
cidrs = set()
errors = []
print(f"Fetching {len(self.asns)} ASNs for {self.name}")
for asn in self.asns:
Expand Down
13 changes: 13 additions & 0 deletions cloudcheck/providers/bunnycdn.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from cloudcheck.providers.base import BaseProvider
from typing import List


class Bunnycdn(BaseProvider):
tags: List[str] = ["cdn"]
short_description: str = "Bunny CDN"
long_description: str = "A global content delivery network and edge platform."
# {"org_id": "ORG-BISD2-RIPE", "org_name": "BUNNYWAY, informacijske storitve d.o.o.", "country": "SI", "asns": [200325]}
asns: List[int] = [200325]
org_ids: List[str] = [
"ORG-BISD2-RIPE",
]
1 change: 1 addition & 0 deletions cloudcheck/providers/gabia.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class Gabia(BaseProvider):
short_description: str = "Gabia (가비아)"
long_description: str = "A Korean cloud hosting and infrastructure provider."
# {"org_id": "@aut-17589-APNIC", "org_name": null, "country": null, "asns": [17589]}
asns: List[int] = [17589]
org_ids: List[str] = [
"@aut-17589-APNIC",
]
1 change: 1 addition & 0 deletions cloudcheck/providers/hostway.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Hostway(BaseProvider):
long_description: str = "A Korean cloud hosting and infrastructure provider."
# {"org_id": "@aut-9952-APNIC", "org_name": null, "country": null, "asns": [9952]}
# {"asn":9952,"asn_name":"HOSTWAY-AS-KR","org_id":"@aut-9952-APNIC"}
asns: List[int] = [9952]
org_ids: List[str] = [
"@aut-9952-APNIC",
]
1 change: 1 addition & 0 deletions cloudcheck/providers/kinx.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Kinx(BaseProvider):
"A Korean content delivery network and cloud infrastructure provider."
)
# {"org_id": "@aut-9286-APNIC", "org_name": null, "country": null, "asns": [9286,9957,17604]}
asns: List[int] = [9286, 9957, 17604]
org_ids: List[str] = [
"@aut-9286-APNIC",
]
1 change: 1 addition & 0 deletions cloudcheck/providers/ktcloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class Ktcloud(BaseProvider):
"A Korean cloud computing service provided by KT Corporation."
)
# {"asn":9947,"asn_name":"KTC-AS-KR","country":null,"org":null,"org_id":"@aut-152232-APNIC","rir":null,"subnets":["61.100.71.0/24","61.100.72.0/24"]}
asns: List[int] = [9947]
org_ids: List[str] = [
"@aut-152232-APNIC",
]
1 change: 1 addition & 0 deletions cloudcheck/providers/lgtelecom.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Lgtelecom(BaseProvider):
long_description: str = "A Korean telecommunications company offering CDN services."
# {"org_id": "@aut-17853-APNIC", "org_name": null, "country": null, "asns": [17853]}
# {"asn":17853,"asn_name":"LGTELECOM-AS-KR","org_id":"@aut-17853-APNIC"}
asns: List[int] = [17853]
org_ids: List[str] = [
"@aut-17853-APNIC",
]
21 changes: 15 additions & 6 deletions cloudcheck/providers/microsoft.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import re

from cloudcheck.providers.base import BaseProvider
from typing import List, Dict

Expand Down Expand Up @@ -27,13 +29,20 @@ class Microsoft(BaseProvider):
],
}

_ips_url = "https://download.microsoft.com/download/0/1/8/018E208D-54F8-44CD-AA26-CD7BC9524A8C/PublicIPs_20200824.xml"
_ips_confirmation_url = (
"https://www.microsoft.com/en-us/download/confirmation.aspx?id=56519"
)

def fetch_cidrs(self):
response = self.request(self._ips_url)
confirmation = self.request(self._ips_confirmation_url, browser_headers=True)
match = re.search(
r'https://download\.microsoft\.com/download/[^"]+\.json', confirmation.text
)
if not match:
raise ValueError("Could not find Azure IP ranges download URL")
response = self.request(match.group(0))
ranges = set()
for line in response.text.splitlines():
if "IpRange Subnet" in line:
ip_range = line.split('"')[1]
ranges.add(ip_range)
for entry in response.json().get("values", []):
for prefix in entry.get("properties", {}).get("addressPrefixes", []):
ranges.add(prefix)
return list(ranges)
1 change: 1 addition & 0 deletions cloudcheck/providers/navercloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Navercloud(BaseProvider):
)
# "org_id": "@aut-23576-APNIC", "org_name": null, "country": null, "asns": [23576,23982]}
# {"asn":23576,"asn_name":"nhn-AS-KR","org_id":"@aut-23576-APNIC"}
asns: List[int] = [23576, 23982]
org_ids: List[str] = [
"@aut-23576-APNIC",
]
1 change: 1 addition & 0 deletions cloudcheck/providers/nhncloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Nhncloud(BaseProvider):
)
# {"org_id": "@aut-10038-APNIC", "org_name": null, "country": null, "asns": [10038,45974,152291]}
# {"asn":45974,"asn_name":"NHN-AS-KR","org_id":"@aut-10038-APNIC"}
asns: List[int] = [10038, 45974, 152291]
org_ids: List[str] = [
"@aut-10038-APNIC",
]
1 change: 1 addition & 0 deletions cloudcheck/providers/skbroadband.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Skbroadband(BaseProvider):
long_description: str = "A Korean telecommunications company offering CDN services."
# {"org_id": "@aut-10049-APNIC", "org_name": null, "country": null, "asns": [9705,10049]}
# {"asn":10049,"asn_name":"SKNET-AS","country":null,"org":null,"org_id":"@aut-10049-APNIC"}
asns: List[int] = [9705, 10049]
org_ids: List[str] = [
"@aut-10049-APNIC",
]
15 changes: 15 additions & 0 deletions cloudcheck/providers/upcloud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from cloudcheck.providers.base import BaseProvider
from typing import List


class Upcloud(BaseProvider):
tags: List[str] = ["cloud"]
short_description: str = "UpCloud"
long_description: str = "A Finnish cloud infrastructure provider offering high-performance cloud servers."
# {"org_id": "ORG-UL87-RIPE", "org_name": "UpCloud Ltd", "country": "FI", "asns": [202053]}
# {"org_id": "UU-7-ARIN", "org_name": "UpCloud USA Inc", "country": "US", "asns": [25697]}
asns: List[int] = [202053, 25697]
org_ids: List[str] = [
"ORG-UL87-RIPE",
"UU-7-ARIN",
]
Loading
Loading