diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/Makefile b/Makefile index 5f40ba1..bb8e329 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,9 @@ sbom: clean ## generate CycloneDX from NPM for this project deployments: ## FOR DOCO ONLY npx wrangler pages deployment list --project-name vulnetix +deploy: ## FOR DOCO ONLY + npx wrangler pages deployment create ./dist --project-name vulnetix --branch demo --upload-source-maps=true + run: ## FOR DOCO ONLY - Run these one at a time, do not call this target directly lsof -i tcp:8788 npm run preview diff --git a/del-cloudflare-deployments.py b/del-cloudflare-deployments.py new file mode 100644 index 0000000..0fe7d94 --- /dev/null +++ b/del-cloudflare-deployments.py @@ -0,0 +1,74 @@ +import pathlib +import tomllib +import time +from datetime import datetime, timedelta, timezone + +from httpx import Client, Response, HTTPError + +config = tomllib.loads(pathlib.Path('.dev.vars').read_text()) +ACCOUNT_ID = 'e302589106b4c4287aebe65763eb4f80' +PROJECT_NAME = 'vulnetix' +ENDPOINT = f'https://api.cloudflare.com/client/v4/accounts/{ACCOUNT_ID}/pages/projects/{PROJECT_NAME}/deployments' +CF_API_TOKEN = config['CLOUDFLARE_API_TOKEN'] +EXPIRATION_BEFORE = datetime.now(timezone.utc) - timedelta(days=7) +HEADERS = { + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {CF_API_TOKEN}', +} +print(HEADERS) + +def delete_deployments(client: Client): + page = 1 + total_deleted = 0 + i = 0 + while True: + i += 1 + r = client.get(ENDPOINT, headers=HEADERS, params={'page': page}) + check_status(r) + deployments = r.json() + + results = deployments['result'] + print(f'{i}: page {page}, got {len(results)} deployments, total deleted {total_deleted}') + deleted = 0 + + for deployment in results: + created_on = datetime.fromisoformat(deployment['created_on']) + if created_on < EXPIRATION_BEFORE: + deployment_id = deployment['id'] + start = time.time() + delete(client, deployment_id) + time_taken = time.time() - start + print(f' deleted {deployment_id} (created {created_on:%Y-%m-%d}), took {time_taken:.2f}s') + + # see if this is long enough to avoid rate limiting? + time.sleep(0.25) + deleted += 1 + + total_deleted += deleted + if deleted == 0: + # only increment the page number if we didn't delete anything + page += 1 + + +def delete(client: Client, deployment_id: str) -> int: + last_error = None + for retry in range(4): + try: + r = client.delete(f'{ENDPOINT}/{deployment_id}', headers=HEADERS, params={'force': 'true'}) + check_status(r) + except HTTPError as e: + print(f' retry {retry} after {e}') + last_error = e + else: + return retry + + raise last_error + + +def check_status(response: Response): + if not (200 <= response.status_code < 300): + raise HTTPError(f'HTTP status {response.status_code}: {response.text}') + +if __name__ == "__main__": + with Client(timeout=10) as c: + delete_deployments(c) diff --git a/functions/v1/github/[installation_id]/install/[code].js b/functions/v1/github/[installation_id]/install/[code].js index 4b670a8..f067b12 100644 --- a/functions/v1/github/[installation_id]/install/[code].js +++ b/functions/v1/github/[installation_id]/install/[code].js @@ -35,6 +35,7 @@ export async function onRequestGet(context) { const headers = { 'Accept': 'application/json' } const resp = await fetch(url, { headers, method }) oauthData = await resp.json() + console.log('oauthData', JSON.stringify(oauthData)) if (oauthData?.error) { return Response.json({ ok: false, error: { message: oauthData.error } }) } diff --git a/public/_headers b/public/_headers new file mode 100644 index 0000000..a524037 --- /dev/null +++ b/public/_headers @@ -0,0 +1,6 @@ +/* + X-Frame-Options: DENY + X-Content-Type-Options: nosniff + Referrer-Policy: no-referrer + Permissions-Policy: document-domain=() + Content-Security-Policy: script-src 'self'; frame-ancestors 'none'; diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..0ba8381 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,10 @@ +[project] +name = "vulnetix" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "cloudflare>=3.1.0", + "httpx>=0.27.2", +] diff --git a/src/pages/GitHubInstall.vue b/src/pages/GitHubInstall.vue deleted file mode 100644 index 2716817..0000000 --- a/src/pages/GitHubInstall.vue +++ /dev/null @@ -1,1182 +0,0 @@ - - - - - diff --git a/src/pages/GitHubLogin.vue b/src/pages/GitHubLogin.vue index e1ed04a..c202fc3 100644 --- a/src/pages/GitHubLogin.vue +++ b/src/pages/GitHubLogin.vue @@ -4,56 +4,90 @@ import { useMemberStore } from '@/stores/member'; import { Client, octodex } from '@/utils'; import { default as axios } from 'axios'; import { reactive } from 'vue'; -//TODO https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#check-if-vulnerability-alerts-are-enabled-for-a-repository -//TODO https://docs.github.com/en/rest/dependabot/alerts?apiVersion=2022-11-28#list-dependabot-alerts-for-a-repository -//TODO https://docs.github.com/en/rest/secret-scanning/secret-scanning?apiVersion=2022-11-28#list-secret-scanning-alerts-for-a-repository const client = new Client() const Member = useMemberStore() const initialState = { + error: '', + activity: 'Verifying', showEmptyState: false, loadingBar: false, octodexImageUrl: `https://octodex.github.com/images/${octodex[Math.floor(Math.random() * octodex.length)]}`, } -const state = reactive({ +const pageState = reactive({ ...initialState, }) class Controller { constructor() { - this.urlQuery = Object.fromEntries(location.search.substring(1).split('&').map(item => item.split('=').map(decodeURIComponent))) - - if (this.urlQuery?.code) { + const { code, installation_id, setup_action, state } = Object.fromEntries(location.search.substring(1).split('&').map(item => item.split('=').map(decodeURIComponent))) + if (code && installation_id && setup_action === 'install') { + Member.logout() + this.install(code, installation_id, state) + } else if (code) { Member.logout() - this.login(this.urlQuery.code) + this.login(code, state) } } - login = async code => { - state.showEmptyState = true - state.loadingBar = true - const { data } = await axios.get(`/v1/login/github/${code}`) - console.log(data) - state.loadingBar = false - if (data?.error?.message) { - if (data?.app?.installationId) { - data.error.message = `[Installation ID ${data.app.installationId}] ${data.error.message}` + install = async (code, installation_id, state = 'integrations') => { + pageState.error = '' + pageState.activity = 'Installing' + pageState.showEmptyState = true + pageState.loadingBar = true + try { + const { data } = await axios.get(`/v1/github/${installation_id}/install/${code}`) + if (!data.ok || data?.error?.message) { + pageState.error = data?.error?.message ? data.error.message : 'GitHub error, please check the integration logs or try again.' + pageState.loadingBar = false + pageState.showEmptyState = false + return } - if (data?.app?.login) { - data.error.message = `${data.error.message} (${data.app.login})` + pageState.loadingBar = false + persistData(data) + await client.storeKey(`session`, { + kid: data.session.kid, + secret: data.session.secret, + expiry: data.session.expiry, + }) + + return router.push(`/${state}`) + } catch (e) { + console.error(e) + pageState.loadingBar = false + pageState.showEmptyState = false + pageState.error = typeof e === "string" ? e : `${e.code} ${e.message}` + } + } + login = async (code, state = 'dashboard') => { + pageState.error = '' + pageState.activity = 'Verifying' + pageState.showEmptyState = true + pageState.loadingBar = true + try { + const { data } = await axios.get(`/v1/login/github/${code}`) + if (!data.ok || data?.error?.message) { + pageState.error = data?.error?.message ? data.error.message : 'GitHub error, please check the integration logs or try again.' + pageState.loadingBar = false + pageState.showEmptyState = false + return } - state.error = data.error.message - return + pageState.loadingBar = false + persistData(data) + await client.storeKey(`session`, { + kid: data.session.kid, + secret: data.session.secret, + expiry: data.session.expiry, + }) + + return router.push(`/${state}`) + } catch (e) { + console.error(e) + pageState.loadingBar = false + pageState.showEmptyState = false + pageState.error = typeof e === "string" ? e : `${e.code} ${e.message}` } - persistData(data) - await client.storeKey(`session`, { - kid: data.session.kid, - secret: data.session.secret, - expiry: data.session.expiry, - }) - state.showEmptyState = false - return router.replace(`/dashboard`) } } @@ -92,15 +126,24 @@ const controller = reactive(new Controller()) + @@ -118,13 +161,3 @@ const controller = reactive(new Controller()) - - diff --git a/src/pages/Projects.vue b/src/pages/Projects.vue index 19bf6c6..019950a 100644 --- a/src/pages/Projects.vue +++ b/src/pages/Projects.vue @@ -3,8 +3,6 @@ import router from "@/router"; import { Client, isJSON } from '@/utils'; import { computed, reactive } from 'vue'; import { useTheme } from 'vuetify'; -//TODO https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#check-if-vulnerability-alerts-are-enabled-for-a-repository -//TODO https://docs.github.com/en/rest/secret-scanning/secret-scanning?apiVersion=2022-11-28#list-secret-scanning-alerts-for-a-repository const client = new Client() const { global } = useTheme() diff --git a/uv.lock b/uv.lock new file mode 100644 index 0000000..2bb7d29 --- /dev/null +++ b/uv.lock @@ -0,0 +1,192 @@ +version = 1 +requires-python = ">=3.12" +resolution-markers = [ + "python_full_version < '3.13'", + "python_full_version >= '3.13'", +] + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "anyio" +version = "4.6.2.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, +] + +[[package]] +name = "cloudflare" +version = "3.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/97/3634f35fb6c7d1de0a7e690d5605e51ca064d94c0c95b28a9ca52530df02/cloudflare-3.1.0.tar.gz", hash = "sha256:fcb72f22803071054820a152ff403834c5df27f731f732ac26fcf681e909ef58", size = 1368813 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/a3/8394580b64c021ae070c1c2a1acab862bb58f16c84cee16cb3b09b8bdd1a/cloudflare-3.1.0-py3-none-any.whl", hash = "sha256:e93ddafa85ab34f0dc4e38b2e6807da77dedbf41d34a9019f3ad5569879a6f37", size = 2913936 }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, +] + +[[package]] +name = "h11" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, +] + +[[package]] +name = "httpcore" +version = "1.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b6/44/ed0fa6a17845fb033bd885c03e842f08c1b9406c86a2e60ac1ae1b9206a6/httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f", size = 85180 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/89/b161908e2f51be56568184aeb4a880fd287178d176fd1c860d2217f41106/httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f", size = 78011 }, +] + +[[package]] +name = "httpx" +version = "0.27.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, + { name = "sniffio" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "pydantic" +version = "2.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 }, +] + +[[package]] +name = "pydantic-core" +version = "2.23.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 }, + { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 }, + { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 }, + { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 }, + { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 }, + { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 }, + { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 }, + { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 }, + { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 }, + { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 }, + { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 }, + { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 }, + { url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 }, + { url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 }, + { url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 }, + { url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 }, + { url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 }, + { url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 }, + { url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 }, + { url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 }, + { url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 }, + { url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 }, + { url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 }, + { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, +] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, +] + +[[package]] +name = "vulnetix" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "cloudflare" }, + { name = "httpx" }, +] + +[package.metadata] +requires-dist = [ + { name = "cloudflare", specifier = ">=3.1.0" }, + { name = "httpx", specifier = ">=0.27.2" }, +]