endpointscanner 7.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SphericalFlower52811
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.4
2
+ Name: endpointscanner
3
+ Version: 7.0.2
4
+ Summary: Website endpoint scanner and rate limit tester that bypasses simple captchas.
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: curl_cffi
9
+ Requires-Dist: beautifulsoup4
10
+ Requires-Dist: playwright
11
+ Requires-Dist: playwright-stealth
12
+ Dynamic: license-file
13
+
14
+ # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.0.2)
15
+
16
+ For Installation, please go to the Installation section below!
17
+
18
+ ## How it works
19
+
20
+ - Uses curl_cffi and playwright-stealth to bypass simple captchas
21
+ - Uses a fake path to test which are real paths and which are shells. (websites like SPAs give a lot of trouble to current tools)
22
+ - Scrapes all `.js` files and `<script>` tags inside the html with a regex to find paths
23
+ - Differentiates paths by website endpoints, assets, redirects etc.
24
+ - Autofills {id} variables in endpoints as '1' to test the endpoints (can reveal potential IDORs)
25
+ - Checks server uptime and prints out JS Stack of the website
26
+ - Has a rate limit tester by sending n requests to a certain endpoint
27
+ - Can scan extra files like robots.txt for more endpoints
28
+ - Also scans for assets like images with a flag to disable showing them
29
+
30
+ ## How to run
31
+
32
+ Command to run after installing **(For installation, look for the 'Installation' section.)**:
33
+
34
+ Example commands to run:
35
+
36
+ ```bash
37
+ endpointscanner https://example.com(or the website you want to test) --ratelimit 100 --testpath /app --show-404s --show-assets
38
+ ```
39
+
40
+ Passable arguments:
41
+
42
+ ```bash
43
+ --ratelimit
44
+ --testpath
45
+ --show-404s
46
+ --disable-extra-files
47
+ --show-assets
48
+ ```
49
+
50
+ | Argument | What the argument does |
51
+ | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
+ | `--ratelimit` | how many requests to send to server to test. Without this argument, the rate limit test is not performed. |
53
+ | `--testpath` | which endpoint to test for rate limiting. Without this argument, the rate limit test will happen on the root directory ('/'). If an endpoint here returns a 404, it also defaults to the root directory. |
54
+ | `--show-404s` | Show inaccessible endpoints. |
55
+ | `--disable-extra-files` | The script won't scan through extra map files like robots.txt for extra endpoints. |
56
+ | `--show-assets` | Show assets like images that the script finds. |
57
+
58
+ Example use:
59
+
60
+ ```bash
61
+ endpointscanner example.com --ratelimit 100 --testpath /api/v1
62
+ ```
63
+
64
+ ## Installation
65
+
66
+ You MUST have python 3.9 or above to use this!!
67
+ To install endpointscanner, run the command:
68
+
69
+ ```bash
70
+ python3 -m pip install endpointscanner
71
+ ```
72
+
73
+ After that, install chromium on playwright (playwright will be installed when you install endpointscanner):
74
+
75
+ ```bash
76
+ playwright install chromium
77
+ ```
78
+
79
+ ### You may need to create a virtual environment if there is PEP 668. (For the endpointscanner installation, not playwright install chromium.)
80
+
81
+ To create a virtual environment named 'myvenv':
82
+
83
+ ```bash
84
+ python3 -m venv myvenv
85
+ ```
86
+
87
+ To activate virtual environment on Mac/Linux:
88
+
89
+ ```bash
90
+ source myvenv/bin/activate
91
+ ```
92
+
93
+ To activate virtual environment on Windows Command Prompt:
94
+
95
+ ```text
96
+ myvenv\Scripts\activate
97
+ ```
98
+
99
+ To activate virtual environment on Windows PowerShell:
100
+
101
+ ```powershell
102
+ myvenv\Scripts\Activate.ps1
103
+ ```
104
+
105
+ ### Alternative (Not Recommended)
106
+
107
+ If you do not want to create a virtual environment, you can run:
108
+
109
+ ```bash
110
+ python3 -m pip install endpointscanner --break-system-packages
111
+ ```
112
+
113
+ to install it without PEP 668.
114
+
115
+ **Warning**: Using `--break-system-packages` may corrupt your OS-managed python environment. Proceed entirely at your own risk. The author is not liable for any system damage if you run this.
116
+
117
+ ### Updating script
118
+
119
+ To update the script, you can run:
120
+
121
+ ```bash
122
+ python3 -m pip install --upgrade endpointscanner
123
+ ```
124
+
125
+ ## Weaknesses
126
+
127
+ - If there is a login page, the script will either show that all of the pages require login, or label all of them as 403.
128
+ - If there are shells in the page, it may give false positives for sensitive endpoints. If you see sensitive endpoints in the scan, they may not actually be exposed on the website if the website has a shell. (E.g. .gitignore, .env.local)
129
+
130
+ ## What was added
131
+
132
+ Version 7 (point 0 point two) added:
133
+
134
+ - Patches for more accuracy in scanning
135
+ - Showing original endpoints (replaces with 1, but also prints out the endpoint with variables in the actual website code)
136
+
137
+ ## Plans for next version
138
+
139
+ Version 7.1 is planned to have:
140
+
141
+ - Flag to display endpoints as it finds them to show progress
142
+ - Flag to export endpoints found to a text file
143
+ - Flag to disable showing the original endpoints to reduce cluttering (So it just replaces the endpoint with 1 and doesn't show end original endpoint with variables, makes terminal neater but loses original endpoint)
144
+
145
+ ai assisted code btw
146
+
147
+ # Legal Disclaimer
148
+
149
+ Note that this tool is strictly meant for **authorised** testing and security research. Running this script on websites where you are not permitted to do so can result in legal action. The author of this script assumes no responsibility for any misuse or legal consequences from running this script. Ensure you have received permission from the owner of the target owner before performing tests or scans on their website.
@@ -0,0 +1,136 @@
1
+ # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.0.2)
2
+
3
+ For Installation, please go to the Installation section below!
4
+
5
+ ## How it works
6
+
7
+ - Uses curl_cffi and playwright-stealth to bypass simple captchas
8
+ - Uses a fake path to test which are real paths and which are shells. (websites like SPAs give a lot of trouble to current tools)
9
+ - Scrapes all `.js` files and `<script>` tags inside the html with a regex to find paths
10
+ - Differentiates paths by website endpoints, assets, redirects etc.
11
+ - Autofills {id} variables in endpoints as '1' to test the endpoints (can reveal potential IDORs)
12
+ - Checks server uptime and prints out JS Stack of the website
13
+ - Has a rate limit tester by sending n requests to a certain endpoint
14
+ - Can scan extra files like robots.txt for more endpoints
15
+ - Also scans for assets like images with a flag to disable showing them
16
+
17
+ ## How to run
18
+
19
+ Command to run after installing **(For installation, look for the 'Installation' section.)**:
20
+
21
+ Example commands to run:
22
+
23
+ ```bash
24
+ endpointscanner https://example.com(or the website you want to test) --ratelimit 100 --testpath /app --show-404s --show-assets
25
+ ```
26
+
27
+ Passable arguments:
28
+
29
+ ```bash
30
+ --ratelimit
31
+ --testpath
32
+ --show-404s
33
+ --disable-extra-files
34
+ --show-assets
35
+ ```
36
+
37
+ | Argument | What the argument does |
38
+ | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
39
+ | `--ratelimit` | how many requests to send to server to test. Without this argument, the rate limit test is not performed. |
40
+ | `--testpath` | which endpoint to test for rate limiting. Without this argument, the rate limit test will happen on the root directory ('/'). If an endpoint here returns a 404, it also defaults to the root directory. |
41
+ | `--show-404s` | Show inaccessible endpoints. |
42
+ | `--disable-extra-files` | The script won't scan through extra map files like robots.txt for extra endpoints. |
43
+ | `--show-assets` | Show assets like images that the script finds. |
44
+
45
+ Example use:
46
+
47
+ ```bash
48
+ endpointscanner example.com --ratelimit 100 --testpath /api/v1
49
+ ```
50
+
51
+ ## Installation
52
+
53
+ You MUST have python 3.9 or above to use this!!
54
+ To install endpointscanner, run the command:
55
+
56
+ ```bash
57
+ python3 -m pip install endpointscanner
58
+ ```
59
+
60
+ After that, install chromium on playwright (playwright will be installed when you install endpointscanner):
61
+
62
+ ```bash
63
+ playwright install chromium
64
+ ```
65
+
66
+ ### You may need to create a virtual environment if there is PEP 668. (For the endpointscanner installation, not playwright install chromium.)
67
+
68
+ To create a virtual environment named 'myvenv':
69
+
70
+ ```bash
71
+ python3 -m venv myvenv
72
+ ```
73
+
74
+ To activate virtual environment on Mac/Linux:
75
+
76
+ ```bash
77
+ source myvenv/bin/activate
78
+ ```
79
+
80
+ To activate virtual environment on Windows Command Prompt:
81
+
82
+ ```text
83
+ myvenv\Scripts\activate
84
+ ```
85
+
86
+ To activate virtual environment on Windows PowerShell:
87
+
88
+ ```powershell
89
+ myvenv\Scripts\Activate.ps1
90
+ ```
91
+
92
+ ### Alternative (Not Recommended)
93
+
94
+ If you do not want to create a virtual environment, you can run:
95
+
96
+ ```bash
97
+ python3 -m pip install endpointscanner --break-system-packages
98
+ ```
99
+
100
+ to install it without PEP 668.
101
+
102
+ **Warning**: Using `--break-system-packages` may corrupt your OS-managed python environment. Proceed entirely at your own risk. The author is not liable for any system damage if you run this.
103
+
104
+ ### Updating script
105
+
106
+ To update the script, you can run:
107
+
108
+ ```bash
109
+ python3 -m pip install --upgrade endpointscanner
110
+ ```
111
+
112
+ ## Weaknesses
113
+
114
+ - If there is a login page, the script will either show that all of the pages require login, or label all of them as 403.
115
+ - If there are shells in the page, it may give false positives for sensitive endpoints. If you see sensitive endpoints in the scan, they may not actually be exposed on the website if the website has a shell. (E.g. .gitignore, .env.local)
116
+
117
+ ## What was added
118
+
119
+ Version 7 (point 0 point two) added:
120
+
121
+ - Patches for more accuracy in scanning
122
+ - Showing original endpoints (replaces with 1, but also prints out the endpoint with variables in the actual website code)
123
+
124
+ ## Plans for next version
125
+
126
+ Version 7.1 is planned to have:
127
+
128
+ - Flag to display endpoints as it finds them to show progress
129
+ - Flag to export endpoints found to a text file
130
+ - Flag to disable showing the original endpoints to reduce cluttering (So it just replaces the endpoint with 1 and doesn't show end original endpoint with variables, makes terminal neater but loses original endpoint)
131
+
132
+ ai assisted code btw
133
+
134
+ # Legal Disclaimer
135
+
136
+ Note that this tool is strictly meant for **authorised** testing and security research. Running this script on websites where you are not permitted to do so can result in legal action. The author of this script assumes no responsibility for any misuse or legal consequences from running this script. Ensure you have received permission from the owner of the target owner before performing tests or scans on their website.
@@ -0,0 +1,149 @@
1
+ Metadata-Version: 2.4
2
+ Name: endpointscanner
3
+ Version: 7.0.2
4
+ Summary: Website endpoint scanner and rate limit tester that bypasses simple captchas.
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: curl_cffi
9
+ Requires-Dist: beautifulsoup4
10
+ Requires-Dist: playwright
11
+ Requires-Dist: playwright-stealth
12
+ Dynamic: license-file
13
+
14
+ # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.0.2)
15
+
16
+ For Installation, please go to the Installation section below!
17
+
18
+ ## How it works
19
+
20
+ - Uses curl_cffi and playwright-stealth to bypass simple captchas
21
+ - Uses a fake path to test which are real paths and which are shells. (websites like SPAs give a lot of trouble to current tools)
22
+ - Scrapes all `.js` files and `<script>` tags inside the html with a regex to find paths
23
+ - Differentiates paths by website endpoints, assets, redirects etc.
24
+ - Autofills {id} variables in endpoints as '1' to test the endpoints (can reveal potential IDORs)
25
+ - Checks server uptime and prints out JS Stack of the website
26
+ - Has a rate limit tester by sending n requests to a certain endpoint
27
+ - Can scan extra files like robots.txt for more endpoints
28
+ - Also scans for assets like images with a flag to disable showing them
29
+
30
+ ## How to run
31
+
32
+ Command to run after installing **(For installation, look for the 'Installation' section.)**:
33
+
34
+ Example commands to run:
35
+
36
+ ```bash
37
+ endpointscanner https://example.com(or the website you want to test) --ratelimit 100 --testpath /app --show-404s --show-assets
38
+ ```
39
+
40
+ Passable arguments:
41
+
42
+ ```bash
43
+ --ratelimit
44
+ --testpath
45
+ --show-404s
46
+ --disable-extra-files
47
+ --show-assets
48
+ ```
49
+
50
+ | Argument | What the argument does |
51
+ | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
52
+ | `--ratelimit` | how many requests to send to server to test. Without this argument, the rate limit test is not performed. |
53
+ | `--testpath` | which endpoint to test for rate limiting. Without this argument, the rate limit test will happen on the root directory ('/'). If an endpoint here returns a 404, it also defaults to the root directory. |
54
+ | `--show-404s` | Show inaccessible endpoints. |
55
+ | `--disable-extra-files` | The script won't scan through extra map files like robots.txt for extra endpoints. |
56
+ | `--show-assets` | Show assets like images that the script finds. |
57
+
58
+ Example use:
59
+
60
+ ```bash
61
+ endpointscanner example.com --ratelimit 100 --testpath /api/v1
62
+ ```
63
+
64
+ ## Installation
65
+
66
+ You MUST have python 3.9 or above to use this!!
67
+ To install endpointscanner, run the command:
68
+
69
+ ```bash
70
+ python3 -m pip install endpointscanner
71
+ ```
72
+
73
+ After that, install chromium on playwright (playwright will be installed when you install endpointscanner):
74
+
75
+ ```bash
76
+ playwright install chromium
77
+ ```
78
+
79
+ ### You may need to create a virtual environment if there is PEP 668. (For the endpointscanner installation, not playwright install chromium.)
80
+
81
+ To create a virtual environment named 'myvenv':
82
+
83
+ ```bash
84
+ python3 -m venv myvenv
85
+ ```
86
+
87
+ To activate virtual environment on Mac/Linux:
88
+
89
+ ```bash
90
+ source myvenv/bin/activate
91
+ ```
92
+
93
+ To activate virtual environment on Windows Command Prompt:
94
+
95
+ ```text
96
+ myvenv\Scripts\activate
97
+ ```
98
+
99
+ To activate virtual environment on Windows PowerShell:
100
+
101
+ ```powershell
102
+ myvenv\Scripts\Activate.ps1
103
+ ```
104
+
105
+ ### Alternative (Not Recommended)
106
+
107
+ If you do not want to create a virtual environment, you can run:
108
+
109
+ ```bash
110
+ python3 -m pip install endpointscanner --break-system-packages
111
+ ```
112
+
113
+ to install it without PEP 668.
114
+
115
+ **Warning**: Using `--break-system-packages` may corrupt your OS-managed python environment. Proceed entirely at your own risk. The author is not liable for any system damage if you run this.
116
+
117
+ ### Updating script
118
+
119
+ To update the script, you can run:
120
+
121
+ ```bash
122
+ python3 -m pip install --upgrade endpointscanner
123
+ ```
124
+
125
+ ## Weaknesses
126
+
127
+ - If there is a login page, the script will either show that all of the pages require login, or label all of them as 403.
128
+ - If there are shells in the page, it may give false positives for sensitive endpoints. If you see sensitive endpoints in the scan, they may not actually be exposed on the website if the website has a shell. (E.g. .gitignore, .env.local)
129
+
130
+ ## What was added
131
+
132
+ Version 7 (point 0 point two) added:
133
+
134
+ - Patches for more accuracy in scanning
135
+ - Showing original endpoints (replaces with 1, but also prints out the endpoint with variables in the actual website code)
136
+
137
+ ## Plans for next version
138
+
139
+ Version 7.1 is planned to have:
140
+
141
+ - Flag to display endpoints as it finds them to show progress
142
+ - Flag to export endpoints found to a text file
143
+ - Flag to disable showing the original endpoints to reduce cluttering (So it just replaces the endpoint with 1 and doesn't show end original endpoint with variables, makes terminal neater but loses original endpoint)
144
+
145
+ ai assisted code btw
146
+
147
+ # Legal Disclaimer
148
+
149
+ Note that this tool is strictly meant for **authorised** testing and security research. Running this script on websites where you are not permitted to do so can result in legal action. The author of this script assumes no responsibility for any misuse or legal consequences from running this script. Ensure you have received permission from the owner of the target owner before performing tests or scans on their website.
@@ -0,0 +1,10 @@
1
+ LICENSE
2
+ README.md
3
+ enumerateendpoint.py
4
+ pyproject.toml
5
+ endpointscanner.egg-info/PKG-INFO
6
+ endpointscanner.egg-info/SOURCES.txt
7
+ endpointscanner.egg-info/dependency_links.txt
8
+ endpointscanner.egg-info/entry_points.txt
9
+ endpointscanner.egg-info/requires.txt
10
+ endpointscanner.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ endpointscanner = enumerateendpoint:main
@@ -0,0 +1,4 @@
1
+ curl_cffi
2
+ beautifulsoup4
3
+ playwright
4
+ playwright-stealth
@@ -0,0 +1 @@
1
+ enumerateendpoint
@@ -0,0 +1,648 @@
1
+ import asyncio
2
+ from curl_cffi import requests
3
+ import re
4
+ from bs4 import BeautifulSoup
5
+ from urllib.parse import urljoin, urlparse
6
+ import argparse
7
+ import time
8
+ from playwright.sync_api import sync_playwright #headless browser to solve captcha
9
+ from playwright_stealth import Stealth #Ensure strict firewalls do not block the playwright browser
10
+
11
+ #Will be fixed in version 8
12
+ '''
13
+ def isthere_captcha(response):
14
+ #me when captcha
15
+ html_content = response.text
16
+ html_lower = html_content.lower()
17
+
18
+ # cloudflare turnstile
19
+ if "challenge-platform" in html_content or "cf-challenge" in html_content:
20
+ return True, "Cloudflare Turnstile (Managed Challenge)"
21
+ if "window._cf_chl_opt" in html_content or "cf-ray:" in html_lower:
22
+ return True, "Cloudflare WAF Block Page"
23
+ if "cf-mitigated" in response.headers.get("Server", "").lower():
24
+ return True, "Cloudflare Edge Mitigation"
25
+
26
+ # perimeterx
27
+ if "window._pxappid" in html_lower or "px-captcha" in html_lower:
28
+ return True, "Human Security (PerimeterX) CAPTCHA"
29
+ if "captcha.px-cdn.net" in html_content or "client.perimeterx.net" in html_content:
30
+ return True, "Human Security (PerimeterX) Shield Active"
31
+
32
+ #recaptcha
33
+ if "google.com" in html_lower or "g-recaptcha" in html_lower:
34
+ return True, "Google reCAPTCHA Challenge"
35
+ if "recaptcha.js" in html_lower or "__recaptcha_api" in html_lower:
36
+ return True, "Google reCAPTCHA Script Loaded"
37
+
38
+ #h captcha
39
+ if "hcaptcha.com" in html_content or "h-captcha" in html_lower:
40
+ return True, "hCaptcha Verification Screen"
41
+
42
+ #akamai
43
+ if "akam_bm" in response.cookies or "bm_sz" in response.cookies:
44
+ return True, "Akamai Bot Manager Cookie Block"
45
+ if "_sec_challenge" in html_lower or "akamai-extension" in html_lower:
46
+ return True, "Akamai WAF Challenge Injection"
47
+
48
+ # aws/amazon captcha
49
+ if "aws-waf-token" in html_lower or "awswaf" in html_lower:
50
+ return True, "AWS WAF Token Challenge"
51
+ if "amazon captcha" in html_lower or "amzn-captcha" in html_lower:
52
+ return True, "Amazon Custom CAPTCHA Screen"
53
+
54
+ # incapsula or soemthing
55
+ if "incapsula" in html_lower or "_incap_" in html_lower:
56
+ return True, "Imperva Incapsula Bot Shield"
57
+ if "visid_incap" in response.cookies:
58
+ return True, "Imperva Session Interception"
59
+
60
+ # kasada
61
+ if "kpsdk" in html_lower or "ips.js" in html_lower:
62
+ return True, "Kasada Anti-Bot Handshake"
63
+
64
+ # generic captchas
65
+ if response.status_code in [403, 429]:
66
+ generic_signals = ["captcha", "robot", "automated access", "verify you are human", "checking your browser"]
67
+ for signal in generic_signals:
68
+ if signal in html_lower:
69
+ return True, f"Generic Firewall Block ({signal.title()})"
70
+ return True, f"Unidentified Security Drop (HTTP {response.status_code})"
71
+
72
+ return False, ""
73
+ '''
74
+ HEADER = {
75
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
76
+ "Accept-Language": "en-US, en;q=0.9"
77
+ } #browser header
78
+
79
+ USELESSSTUFF = {
80
+ "localhost", "127.0.0.1", "0.0.0.0",
81
+ "w3.org", "schema.org", "xml.org", "://microsoft.com"
82
+ }
83
+ def gethtmlafterload(url):
84
+ with sync_playwright() as p:
85
+ try:
86
+ browser = p.chromium.launch(
87
+ headless=True,
88
+ args=[
89
+ "--disable-blink-features=AutomationControlled",
90
+ "--no-sandbox",
91
+ "--disable-infobars"
92
+ ])
93
+ except Exception as e:
94
+ error_msg = str(e).lower()
95
+ error_type = type(e).__name__
96
+
97
+ # idiotproofing if people GENUINELY cannot read installation instructions
98
+ if "error" in error_type.lower() and ("executable" in error_msg.lower() or "install" in error_msg.lower()):
99
+ print("\nPlaywright installations are missing.")
100
+ print("Please read the installation instructions in the README of the repository.")
101
+ print("README link: https://github.com/SphericalFlower52811/endpointscanner/blob/main/README.md")
102
+ else:
103
+ print("Unexpected Issue:", e)
104
+ return "", {}
105
+
106
+ context = browser.new_context(
107
+ user_agent=HEADER['User-Agent'],
108
+ viewport={'width': 1920, 'height': 1080},
109
+ has_touch=True
110
+ )
111
+ page = context.new_page()
112
+ Stealth().apply_stealth_sync(page) #stealth
113
+
114
+ #go to page and get code, using stealth to bypass captchas
115
+ try:
116
+ page.goto(url, wait_until="domcontentloaded", timeout=60000) #wait for page content to download
117
+ page.mouse.move(100, 100)
118
+ page.mouse.move(200, 300)
119
+ page.evaluate("window.scrollTo(0, 500)")
120
+
121
+ page.wait_for_timeout(5000) #wait for page to load downloaded content, and cookie
122
+ mainhtml = page.content()
123
+ cookies = {c['name']: c['value'] for c in context.cookies()}
124
+
125
+ except Exception as e:
126
+ print("Unexpected Error:", e)
127
+ mainhtml, cookies = "", {}
128
+
129
+ browser.close()
130
+ return mainhtml, cookies
131
+
132
+
133
+ def identify_javascript_type(html, headers=None):
134
+ stack = []
135
+ #print(f"\n[DEBUG] HTML Snippet: {html[:1000]}\n")
136
+ # Next.js
137
+ if any(term in html for term in ['data-next-head', 'script id="__NEXT_DATA__"', 'next-head-count', '_next/', '_next/data']):
138
+ stack.append("Next.js")
139
+ # React
140
+ if 'data-reactroot' in html or 'id="root"' in html or 'react-dom' in html.lower():
141
+ stack.append("React")
142
+ # Vue / Angular
143
+ if 'id="app"' in html or 'v-bind' in html: stack.append("Vue.js")
144
+ if 'id="__nuxt"' in html or 'window.__NUXT__' in html: stack.append("Nuxt.js (Vue)")
145
+ if '<app-root' in html or 'ng-version' in html or '_nghost-' in html: stack.append("Angular")
146
+ # Node.js
147
+ if headers:
148
+ powered_by = headers.get('X-Powered-By', '').lower()
149
+ if 'express' in powered_by or 'node' in powered_by:
150
+ stack.append(f"Node.js ({powered_by.capitalize()})")
151
+ # vite and wekpack
152
+ if any(term in html.lower() for term in ['@vite/client', 'vite-plugin', 'src="/@vite', '__vite__', 'modulepreload']):
153
+ stack.append("Vite")
154
+ if 'webpack' in html.lower(): stack.append("Webpack")
155
+
156
+ return " + ".join(stack) if stack else "Unknown JS Stack"
157
+
158
+ async def async_rate_test(url, num_reqs=100):
159
+ print(f"\nStarting Rate Limit Test: {num_reqs} requests to {url}")
160
+
161
+ async with requests.AsyncSession(impersonate="chrome120") as session:
162
+ tasks = [session.get(url, headers=HEADER, timeout=10) for _ in range(num_reqs)]
163
+ responses = await asyncio.gather(*tasks, return_exceptions=True)
164
+
165
+ status_counts = {}
166
+ first_limit_at = None
167
+
168
+ for i, res in enumerate(responses):
169
+ request_number = i + 1
170
+ if isinstance(res, Exception):
171
+ status_counts['Error'] = status_counts.get('Error', 0) + 1
172
+ continue
173
+
174
+ code = res.status_code
175
+ status_counts[code] = status_counts.get(code, 0) + 1
176
+
177
+ if code != 200 and first_limit_at is None:
178
+ first_limit_at = (request_number, code)
179
+
180
+ print("\n--- Rate Limit Results ---")
181
+ for code, count in status_counts.items():
182
+ if code == 'Error': continue
183
+ label = "OK" if code == 200 else "LIMITED" if code == 429 else "WAF/FORBIDDEN" if code == 403 else "CRASHED" if code == 500 else "Other"
184
+ print(f" Status {code} ({label}): {count}")
185
+
186
+ if status_counts.get(200, 0) == num_reqs:
187
+ print(f"\nWebsite is potentially vulnerable to DoS or brute-forcing (No rate limit detected after {num_reqs} requests).")
188
+ elif first_limit_at:
189
+ req_num, code = first_limit_at
190
+ if code == 403:
191
+ print(f"\nA WAF (Firewall) likely intercepted the requests (403 Forbidden after {req_num} requests).")
192
+ elif code == 429:
193
+ print(f"\nServer-side rate limiting is active (429 Too Many Requests detected after {req_num} requests).")
194
+ else:
195
+ print(f"\nServer began responding with {code} after {req_num} requests.")
196
+
197
+ def main():
198
+ parser = argparse.ArgumentParser()
199
+ parser.add_argument("target", nargs='?', help="URL")
200
+ parser.add_argument("--ratelimit", nargs='?', const=100, type=int, default=None, help="Number of requests")
201
+ parser.add_argument("--testpath", nargs='?', const='/', type=str, help="Endpoint to test")
202
+ parser.add_argument("--show-404s", action="store_true", help="Show endpoints tested that returned a 404")
203
+ parser.add_argument("--disable-extra-files", action="store_true", help="Disable scanning of extra structural mapping files (robots, sitemaps, manifests, etc.)")
204
+ parser.add_argument("--show-assets", action="store_true", help="Include assets like images and fonts in scan results")
205
+ #add --show-org
206
+
207
+ args = parser.parse_args()
208
+ if args.testpath and args.ratelimit is None:
209
+ args.ratelimit = 100
210
+
211
+ ignored_extensions = ()
212
+
213
+ # error if the user somehow didn't read the instructions (i've idiotproofed the code ENOUGH)
214
+ if args.testpath and args.ratelimit is None:
215
+ parser.error("--testpath requires the --ratelimit flag.\nIf you want to do a rate limit test, use --ratelimit (number of requests) --testpath (path to test).\nIf not, don't use --ratelimit nor --testpath.")
216
+
217
+ show_dead = args.show_404s
218
+ target = args.target if args.target else input("Enter website (e.g. https://example.com): ").strip()
219
+
220
+ if not target.startswith(("http://", "https://")):
221
+ target = "https://" + target
222
+ try:
223
+ response = requests.get(target, headers=HEADER, timeout=5, impersonate="chrome120")
224
+ except requests.exceptions.SSLError:
225
+ if target.startswith("https://"):
226
+ print('HTTPS SSL Error. Trying HTTP...') #becuase later sum http noob
227
+ target = target.replace("https://", "http://")
228
+ try:
229
+ response = requests.get(target, headers=HEADER, timeout=5, impersonate="chrome120")
230
+ except Exception as e:
231
+ print(f'Target unreachable on HTTP: {e}')
232
+ exit()
233
+ except Exception as e:
234
+ print(f'Target unreachable: {e}')
235
+ exit()
236
+ try:
237
+ st = time.perf_counter()
238
+ uptimeres = requests.get(target, headers=HEADER, timeout=10, impersonate="chrome120")
239
+ et = time.perf_counter()
240
+ restime = et - st
241
+ print(f"Site responded in {round(restime, 2)} seconds.")
242
+
243
+ if restime < 0.5:
244
+ print("Server is very fast.")
245
+ elif restime < 1:
246
+ print("Server is fast.")
247
+ elif restime < 2.5:
248
+ print("Server is average speed.")
249
+ elif restime < 4:
250
+ print("Server is slow.")
251
+ else:
252
+ print("Server is very slow.")
253
+ except requests.exceptions.Timeout:
254
+ print("Server did not respond after 10 seconds.")
255
+
256
+ #hardcoded dangerous endpoints to test
257
+ SENSITIVE_ENDPOINT = {
258
+ "/.env", "/.env.local", "/.env.production", "/.env.development",
259
+ "/.git/config", "/.git/HEAD", "/robots.txt", "/sitemap.xml",
260
+ "/package.json", "/package-lock.json", "/.npmrc", "/.dockerenv",
261
+ "/.gitignore", "/api/health", "/admin", "/login", "/config",
262
+ "/.env.example", "/docker-compose.yml", "/.babelrc", "/.eslintrc.json",
263
+ "/wp-config.php", "/config.json", "/.aws/credentials", "/.git/index"
264
+ }
265
+
266
+ results_fromotherfiles = []
267
+ found_paths = set(SENSITIVE_ENDPOINT)
268
+ discovered_in_js = {}
269
+
270
+ if not args.disable_extra_files:
271
+ print("\nFinding paths from map files. (If they exist)")
272
+
273
+ # bobot.txt
274
+ try:
275
+ r_res = requests.get(urljoin(target, "/robots.txt"), headers=HEADER, impersonate="chrome120", timeout=4)
276
+ if r_res.status_code == 200 and "disallow" in r_res.text.lower():
277
+ rules = re.findall(r'(?:Disallow|Allow):\s*(/[a-zA-Z0-9_\-\./{}:|]*)', r_res.text, re.IGNORECASE)
278
+ for rule in rules:
279
+ clean_rule = rule.strip()
280
+ if clean_rule and clean_rule not in ["/", "/*"] and clean_rule not in found_paths:
281
+ found_paths.add(clean_rule)
282
+ results_fromotherfiles.append(f"{clean_rule} [Source: robots.txt]")
283
+ except: pass
284
+
285
+ # sitemap
286
+ try:
287
+ s_res = requests.get(urljoin(target, "/sitemap.xml"), headers=HEADER, impersonate="chrome120", timeout=4)
288
+ if s_res.status_code == 200 and "<loc" in s_res.text.lower():
289
+ locs = re.findall(r'<loc>https?://[^/]+(/[^<]+)</loc>', s_res.text, re.IGNORECASE)
290
+ for loc in locs:
291
+ clean_path = loc.strip()
292
+ if clean_path and clean_path != "/" and clean_path not in found_paths:
293
+ found_paths.add(clean_path)
294
+ results_fromotherfiles.append(f"{clean_path} [Source: sitemap.xml]")
295
+ except: pass
296
+
297
+ # me when manifesto without the o
298
+ try:
299
+ m_res = requests.get(urljoin(target, "/asset-manifest.json"), headers=HEADER, impersonate="chrome120", timeout=4)
300
+ if m_res.status_code == 200 and "{" in m_res.text:
301
+ paths = re.findall(r'["\'](/[a-zA-Z0-9_\-\./]+)["\']', m_res.text)
302
+ for path in paths:
303
+ if path not in found_paths:
304
+ found_paths.add(path)
305
+ results_fromotherfiles.append(f"{path} [Source: asset-manifest.json]")
306
+ except: pass
307
+ for manifest_path in ["/web-manifest.json", "/manifest.json"]:
308
+ try:
309
+ m_url = urljoin(target, manifest_path)
310
+ m_res = requests.get(m_url, headers=HEADER, impersonate="chrome120", timeout=4)
311
+ if m_res.status_code == 200 and "{" in m_res.text:
312
+ paths = re.findall(r'["\'](/[a-zA-Z0-9_\-\./]+)["\']', m_res.text)
313
+ for path in paths:
314
+ if path not in found_paths:
315
+ found_paths.add(path)
316
+ results_fromotherfiles.append(f"{path} [Source: {manifest_path.lstrip('/')}]")
317
+ except: pass
318
+ # service worker lol
319
+ for sw_path in ["/service-worker.js", "/sw.js"]:
320
+ try:
321
+ sw_res = requests.get(urljoin(target, sw_path), headers=HEADER, impersonate="chrome120", timeout=4)
322
+ if sw_res.status_code == 200:
323
+ paths = re.findall(r'["\'`](/[a-zA-Z0-9_\-\./{}:]+)["\'`]', sw_res.text)
324
+ for path in paths:
325
+ if path not in found_paths and not any(path.endswith(ext) for ext in ['.js', '.css']):
326
+ found_paths.add(path)
327
+ results_fromotherfiles.append(f"{path} [Source: {sw_path}]")
328
+ except: pass
329
+
330
+ #openid
331
+ try:
332
+ oidc_res = requests.get(urljoin(target, "/.well-known/openid-configuration"), headers=HEADER, impersonate="chrome120", timeout=4)
333
+ if oidc_res.status_code == 200 and "{" in oidc_res.text:
334
+ paths = re.findall(r'https?://[^/]+(/[^"\']*)', oidc_res.text)
335
+ for path in paths:
336
+ if path not in found_paths:
337
+ found_paths.add(path)
338
+ results_fromotherfiles.append(f"{path} [Source: openid-configuration]")
339
+ except: pass
340
+
341
+ print("\nStarting headless browser to bypass captchas and detect shells with a fake path.")
342
+ main_html, session_cookies = gethtmlafterload(target)
343
+
344
+ fake_path = "/very-fake-page-123456123456abcdefg"
345
+ fake_url = urljoin(target, fake_path)
346
+ try:
347
+ fake_res = requests.get(fake_url, cookies=session_cookies, headers=HEADER, impersonate="chrome120", timeout=10)
348
+ shell_content = fake_res.text
349
+ except:
350
+ shell_content = ""
351
+ try:
352
+ print(f"Detected JS Stack: {identify_javascript_type(main_html)}")
353
+ soup = BeautifulSoup(main_html, 'html.parser')
354
+
355
+ # next js code files
356
+ js_files = [urljoin(target, s.get('src')) for s in soup.find_all('script') if s.get('src')]
357
+ js_files.append(urljoin(target, "/_next/static/development/_buildManifest.js"))
358
+ js_files.append(urljoin(target, "/_next/static/runtime/_buildManifest.js"))
359
+
360
+ for script_tag in soup.find_all('script'):
361
+ src = script_tag.get('src')
362
+ if src and not src.startswith(('http://', 'https://')):
363
+ local_path = urlparse(src).path
364
+ if local_path:
365
+ clean_path = '/' + local_path.lstrip('/')
366
+ found_paths.add(clean_path)
367
+
368
+ targetthings = ['stylesheet', 'modulepreload', 'preload', 'prefetch', 'icon', 'shortcut icon', 'manifest']
369
+ #stylesheet like css
370
+ for link_tag in soup.find_all('link', rel=targetthings):
371
+ href = link_tag.get('href')
372
+ if href and not href.startswith(('http://', 'https://')):
373
+ local_path = urlparse(href).path
374
+ if local_path:
375
+ clean_path = '/' + local_path.lstrip('/')
376
+ found_paths.add(clean_path)
377
+ patterns = [
378
+ r'["\'`](/[a-zA-Z0-9_\-\./{}:]*)["\'`]',
379
+ r'(?:path|href|to|post|get|patch|put|delete|head|options)[\s]*[:=\(\|]+[\s]*["\'`](/?[a-zA-Z0-9_\-\./{}:\$]*[\./][a-zA-Z0-9_\-\./{}:\$]*)["\'`]',
380
+ r'["\'`](https?://[a-zA-Z0-9_\-\./{}:\$]+)["\'`]'
381
+ ]
382
+
383
+ for p in patterns:
384
+ respo = requests.get(target, headers=HEADER, timeout=5, impersonate="chrome120")
385
+ matches = re.findall(p, respo.text)
386
+ for m in matches:
387
+ m_clean = re.sub(r'(\$\{.*?\}|:[a-zA-Z0-9]+)', '1', m)
388
+ if m_clean != m:
389
+ m_display = f"{m_clean} [Original: {m}]"
390
+ else:
391
+ m_display = m_clean
392
+
393
+ if "://" not in m_clean:
394
+ if not m_clean.startswith('/'):
395
+ m_clean = '/' + m_clean
396
+ if m_clean != m:
397
+ m_display = '/' + m_display
398
+
399
+ if not m_clean.lower().endswith(ignored_extensions):
400
+ if m_clean in ["/", "//", "///", "/.", "/..", "/..."]:
401
+ continue
402
+ if any(term in m_clean.lower() for term in USELESSSTUFF):
403
+ continue
404
+ found_paths.add(m_clean)
405
+ if m_clean not in discovered_in_js:
406
+ discovered_in_js[m_clean] = m_display
407
+
408
+
409
+ # check <script> for endpoint too
410
+ inline_scripts = soup.find_all('script')
411
+ for script in inline_scripts:
412
+ if script.string:
413
+ chunks = re.findall(r'["\'](/?[a-zA-Z0-9_\-\./]*\.js)["\']', script.string)
414
+ for c in chunks:
415
+ clean_c = c if c.startswith('/') else '/' + c
416
+ js_files.append(urljoin(target, clean_c))
417
+ discovered_in_js[clean_c] = clean_c
418
+
419
+ for p in patterns:
420
+ matches = re.findall(p, script.string)
421
+ for m in matches:
422
+ m_clean = re.sub(r'(\$\{.*?\}|:[a-zA-Z0-9]+)', '1', m)
423
+ if m_clean != m:
424
+ m_display = f"{m_clean} [Original: {m}]"
425
+ else:
426
+ m_display = m_clean
427
+
428
+ if "://" not in m_clean:
429
+ if not m_clean.startswith('/'):
430
+ m_clean = '/' + m_clean
431
+ if m_clean != m:
432
+ m_display = '/' + m_display
433
+
434
+ if not m_clean.lower().endswith(ignored_extensions):
435
+ if m_clean in ["/", "//", "///", "/.", "/..", "/...", "/./", "localhost", "w3.org"]:
436
+ continue
437
+ if any(term in m_clean.lower() for term in USELESSSTUFF):
438
+ continue
439
+ found_paths.add(m_clean)
440
+ if m_clean not in discovered_in_js:
441
+ discovered_in_js[m_clean] = m_display
442
+
443
+
444
+ # scan all js files
445
+ #scan all js files
446
+ for path in list(found_paths):
447
+ if path.lower().endswith('.js') and urljoin(target, path) not in js_files:
448
+ js_files.append(urljoin(target, path))
449
+
450
+ for js_url in js_files:
451
+ try:
452
+ js_res = requests.get(js_url, headers=HEADER, cookies=session_cookies, timeout=5, impersonate="chrome120")
453
+ if js_res.status_code == 200:
454
+ if "index" not in js_url.lower() and "main" not in js_url.lower():
455
+ continue
456
+ for p in patterns:
457
+ matches = re.findall(p, js_res.text)
458
+ for m in matches:
459
+ m_clean = re.sub(r'(\$\{.*?\}|:[a-zA-Z0-9]+)', '1', m)
460
+
461
+ if m_clean != m:
462
+ m_display = f"{m_clean} [Original: {m}]"
463
+ else:
464
+ m_display = m_clean
465
+ if "://" not in m_clean:
466
+ if not m_clean.startswith('/'):
467
+ m_clean = '/' + m_clean
468
+ if m_clean != m:
469
+ m_display = '/' + m_display
470
+ if not m_clean.lower().endswith(ignored_extensions):
471
+ if m_clean in ["/", "//", "///", "/.", "/..", "/..."]:
472
+ continue
473
+ if any(term in m_clean.lower() for term in USELESSSTUFF):
474
+ continue
475
+ found_paths.add(m_clean)
476
+ if m_clean not in discovered_in_js:
477
+ discovered_in_js[m_clean] = m_display
478
+ except: continue
479
+
480
+
481
+ print(f"Total paths to test: {len(found_paths)} ({len(discovered_in_js)} scraped from JS).")
482
+ print("Testing endpoints...")
483
+ results_200, results_dead, results_30x = [], [], []
484
+ results_services, results_ext = [], []
485
+ results_frameworks, results_assets = [], []
486
+
487
+ for path in sorted(found_paths):
488
+ display_path = discovered_in_js.get(path, path)
489
+ # all external url cuz https: //blahblah
490
+ if "://" in path:
491
+ results_ext.append(display_path.lstrip('/'))
492
+ continue
493
+
494
+ try:
495
+ parsed_path = urlparse(path)
496
+ target_domain = urlparse(target).netloc
497
+
498
+ # get the base domain (efg.hijk from abcd.efg.hijk)
499
+ def get_base(domain):
500
+ parts = domain.split('.')
501
+ return ".".join(parts[-2:]) if len(parts) > 1 else domain
502
+
503
+ is_external = parsed_path.netloc and get_base(parsed_path.netloc) != get_base(target_domain)
504
+
505
+ if is_external:
506
+ results_ext.append(f"{path.lstrip('/')} [External Reference]")
507
+ continue
508
+
509
+
510
+ r = requests.get(
511
+ urljoin(target, path),
512
+ headers=HEADER,
513
+ cookies=session_cookies,
514
+ timeout=5,
515
+ allow_redirects=False,
516
+ impersonate="chrome120"
517
+ )
518
+
519
+ content_type = r.headers.get("Content-Type", "").lower()
520
+ is_shell = (r.text == shell_content)
521
+ is_home_redirect = (r.text == main_html)
522
+
523
+ # common service and api i think
524
+ service_markers = ["/api", "/v1", "/v2", "socket.io", "engine.io", "/graphql", "/webhook", "/rpc"]
525
+ is_machine_path = any(marker in path.lower() for marker in service_markers)
526
+
527
+ media_extensions = ('.png', '.jpg', '.jpeg', '.svg', '.webp', '.gif', '.ico', '.woff', '.woff2', '.ttf')
528
+ framework_extensions = ('.js', '.css', '.json', '.txt', '.xml', '.map')
529
+
530
+ is_media_asset = path.lower().endswith(media_extensions)
531
+ is_framework_asset = any(path.lower().endswith(ext) for ext in framework_extensions)
532
+
533
+ if r.status_code in [200, 304]: #304 got me
534
+ # if its a service/api path then like service and stuff ykyk
535
+ if is_machine_path:
536
+ results_services.append(f"{display_path} [Service/API]")
537
+ elif is_shell or is_home_redirect:
538
+ if path in discovered_in_js:
539
+ results_200.append(f"{display_path} [Client-Side Route, Requires Login]")
540
+ else:
541
+ results_dead.append(f"404 Not Found (React Shell): {path}")
542
+ else:
543
+ if "text/html" in content_type:
544
+ results_200.append(f"{display_path} [Access no matter what]")
545
+ elif is_media_asset:
546
+ results_assets.append(f"{display_path}")
547
+ elif is_framework_asset:
548
+ results_frameworks.append(f"{display_path}")
549
+ else:
550
+ #standard is like js and css
551
+ results_frameworks.append(f"{display_path} [Non-Standard File]")
552
+
553
+
554
+ elif r.status_code == 400:
555
+ if is_framework_asset:
556
+ results_frameworks.append(f"{display_path} [Asset Error - 400]")
557
+ # get machine services like socket.io that reject simple GET request. cuz socket stinky.
558
+ if "." in path or "/" in path:
559
+ results_services.append(f"{display_path} [Potential Service - 400]")
560
+ elif is_machine_path:
561
+ results_services.append(f"{display_path} [Service/API]") #cuz socket
562
+ else:
563
+ results_dead.append(f"400 Bad Request: {display_path}")
564
+
565
+ elif r.status_code in [403, 404]:
566
+ results_dead.append(f"{r.status_code} Error: {display_path}")
567
+ elif str(r.status_code).startswith('3'):
568
+ results_30x.append(f"{display_path} -> {r.headers.get('Location')}")
569
+ except: continue
570
+
571
+
572
+
573
+ if len(results_200) != 0:
574
+ print("\n---- ENDPOINTS FOUND ----")
575
+ for p in results_200: print(f" {p}")
576
+ else:
577
+ print("\n----NO ENDPOITNS FOUND----")
578
+ if len(results_services) != 0:
579
+ print("\n----SERVICES/APIS USED----")
580
+ for p in results_services: print(f" {p}")
581
+ else:
582
+ print("\n----NO SERVICES/APIS FOUND----")
583
+ if len(results_ext) != 0:
584
+ print("\n----EXTERNAL LINKS----")
585
+ for p in results_ext: print(f" {p}")
586
+ else:
587
+ print("\n----NO EXTERNAL LINKS FOUND----")
588
+ if len(results_frameworks) != 0:
589
+ print("\n----WEBSITE FRAMEWORKS----")
590
+ for p in results_frameworks: print(f" {p}")
591
+ else:
592
+ print("\n----NO WEBSITE FRAMEWORKS FOUND----")
593
+ if len(results_30x) != 0:
594
+ print("\n---- REDIRECTS (301/302/307) ----")
595
+ for p in results_30x: print(f" {p}")
596
+ else:
597
+ print("\n----NO REDIRECTS FOUND----")
598
+ # if result contains stuff
599
+ if not args.disable_extra_files:
600
+ if results_fromotherfiles:
601
+ print("\n---- PATHS FROM OTHER FILES ----")
602
+ for entry in results_fromotherfiles:
603
+ print(f" {entry}")
604
+ else:
605
+ print("\n----NO EXTRA PATHS FOUND FROM OTHER FILES----")
606
+ else:
607
+ pass
608
+ if args.show_assets:
609
+ if len(results_assets) != 0:
610
+ print("\n----WEBSITE ASSETS----")
611
+ for p in results_assets: print(f" {p}")
612
+ else:
613
+ print("\n----NO WEBSITE ASSETS FOUND----")
614
+ else:
615
+ pass
616
+ if show_dead:
617
+ if len(results_dead) != 0:
618
+ print("\n---- INACCESSIBLE (Confirmed 404/403) ----")
619
+ for p in results_dead: print(f" {p}")
620
+ else:
621
+ print("\n----NONE INACCESSIBLE 404/403----")
622
+
623
+ print(f"\n--- Scan Summary ---")
624
+ if args.show_assets:
625
+ print(f"Total Accessible Pages: {len(results_200)}\nTotal Services: {len(results_services)}\nTotal External References: {len(results_ext)}\nTotal Frameworks: {len(results_frameworks)}\nTotal Redirects: {len(results_30x)}\nTotal Assets: {len(results_assets)}\nTotal Inaccessible: {len(results_dead)}")
626
+ else:
627
+ print(f"Total Accessible Pages: {len(results_200)}\nTotal Services: {len(results_services)}\nTotal External References: {len(results_ext)}\nTotal Frameworks: {len(results_frameworks)}\nTotal Redirects: {len(results_30x)}\nTotal Assets: {len(results_assets)} (Hidden, use --show-assets to show)\nTotal Inaccessible: {len(results_dead)}")
628
+
629
+ if args.ratelimit is not None:
630
+ num = args.ratelimit
631
+ test_path = "/" #root dir
632
+ if args.testpath: #idiotproof
633
+ test_path = args.testpath if args.testpath.startswith('/') else '/' + args.testpath
634
+ try:
635
+ check_res = requests.get(urljoin(target, test_path), headers=HEADER, timeout=5, impersonate="chrome120")
636
+ if check_res.status_code in [301, 302, 307, 308, 403, 404]:
637
+ print(f"{test_path} is {check_res.status_code}. Testing on root domain.")
638
+ test_path = "/"
639
+ except:
640
+ test_path = "/"
641
+
642
+ asyncio.run(async_rate_test(urljoin(target, test_path), num))
643
+
644
+ except Exception as e:
645
+ print(f"Main Error: {e}")
646
+
647
+ if __name__ == "__main__":
648
+ main()
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "endpointscanner"
7
+ version = "7.0.2"
8
+ readme = "README.md"
9
+ description = "Website endpoint scanner and rate limit tester that bypasses simple captchas."
10
+ requires-python = ">=3.9"
11
+ dependencies = [
12
+ "curl_cffi",
13
+ "beautifulsoup4",
14
+ "playwright",
15
+ "playwright-stealth"
16
+ ]
17
+
18
+ [project.scripts]
19
+ # pyprojectthing
20
+ endpointscanner = "enumerateendpoint:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+