endpointscanner 7.2.2__tar.gz → 7.2.4__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: endpointscanner
3
- Version: 7.2.2
3
+ Version: 7.2.4
4
4
  Summary: Website endpoint reconnaissance tool and rate limit tester that can bypass simple captchas and WAFs.
5
5
  Project-URL: Homepage, https://github.com/SphericalFlower52811/endpointscanner
6
6
  Project-URL: Issues, https://github.com/SphericalFlower52811/endpointscanner/issues
@@ -14,9 +14,10 @@ Requires-Dist: beautifulsoup4
14
14
  Requires-Dist: playwright
15
15
  Requires-Dist: playwright-stealth
16
16
  Requires-Dist: httpx[http2]>=0.27.0
17
+ Requires-Dist: colorama>=0.4.6
17
18
  Dynamic: license-file
18
19
 
19
- # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.2.2)
20
+ # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.2.4)
20
21
 
21
22
  A fast automated website reconnaissance tool that extracts endpoints, files, and even external links from websites. Automates IDOR and broken access control vulnerability testing through replacing variables with 1 in endpoints. Has a built in rate limit tester that can test on any endpoint, and can bypass simple WAFs/captchas and client-side SPAs.
22
23
 
@@ -189,11 +190,20 @@ Version 7.2.1 (patch update) added:
189
190
 
190
191
  Version 7.2.2 just changed wording and description of the tool to be more clear.
191
192
 
193
+ Version 7.2.3 added one more sensitive endpoint and fixed bug where some paths would be / from extra files.
194
+
195
+ Version 7.2.4 added:
196
+
197
+ - fixing pdf keys like /Btn and /Widget used in jsPDF, as they were previously detected as endpoints
198
+ - better SPA separation
199
+ - added more asset formats (.avif, .mp3, .mp4, .webm, .wav)
200
+ - Added text at start saying Endpointscanner 7.2.4 (i wanted it to have the vibe of most open source tools but the 3d text was just too much)
201
+
192
202
  ## Plans for next version and the future
193
203
 
194
204
  Version 7.3 is planned to have:
195
205
 
196
- - More JS Stacks to detect
206
+ - More JS Stacks to detect (and more accurate)
197
207
  - Detecting what type of captcha was used if the script is blocked.
198
208
 
199
209
  Future plans (May be added in the next version):
@@ -1,4 +1,4 @@
1
- # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.2.2)
1
+ # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.2.4)
2
2
 
3
3
  A fast automated website reconnaissance tool that extracts endpoints, files, and even external links from websites. Automates IDOR and broken access control vulnerability testing through replacing variables with 1 in endpoints. Has a built in rate limit tester that can test on any endpoint, and can bypass simple WAFs/captchas and client-side SPAs.
4
4
 
@@ -171,11 +171,20 @@ Version 7.2.1 (patch update) added:
171
171
 
172
172
  Version 7.2.2 just changed wording and description of the tool to be more clear.
173
173
 
174
+ Version 7.2.3 added one more sensitive endpoint and fixed bug where some paths would be / from extra files.
175
+
176
+ Version 7.2.4 added:
177
+
178
+ - fixing pdf keys like /Btn and /Widget used in jsPDF, as they were previously detected as endpoints
179
+ - better SPA separation
180
+ - added more asset formats (.avif, .mp3, .mp4, .webm, .wav)
181
+ - Added text at start saying Endpointscanner 7.2.4 (i wanted it to have the vibe of most open source tools but the 3d text was just too much)
182
+
174
183
  ## Plans for next version and the future
175
184
 
176
185
  Version 7.3 is planned to have:
177
186
 
178
- - More JS Stacks to detect
187
+ - More JS Stacks to detect (and more accurate)
179
188
  - Detecting what type of captcha was used if the script is blocked.
180
189
 
181
190
  Future plans (May be added in the next version):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: endpointscanner
3
- Version: 7.2.2
3
+ Version: 7.2.4
4
4
  Summary: Website endpoint reconnaissance tool and rate limit tester that can bypass simple captchas and WAFs.
5
5
  Project-URL: Homepage, https://github.com/SphericalFlower52811/endpointscanner
6
6
  Project-URL: Issues, https://github.com/SphericalFlower52811/endpointscanner/issues
@@ -14,9 +14,10 @@ Requires-Dist: beautifulsoup4
14
14
  Requires-Dist: playwright
15
15
  Requires-Dist: playwright-stealth
16
16
  Requires-Dist: httpx[http2]>=0.27.0
17
+ Requires-Dist: colorama>=0.4.6
17
18
  Dynamic: license-file
18
19
 
19
- # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.2.2)
20
+ # Website Endpoint Scanner and Rate Limit Tester For Websites (Version 7.2.4)
20
21
 
21
22
  A fast automated website reconnaissance tool that extracts endpoints, files, and even external links from websites. Automates IDOR and broken access control vulnerability testing through replacing variables with 1 in endpoints. Has a built in rate limit tester that can test on any endpoint, and can bypass simple WAFs/captchas and client-side SPAs.
22
23
 
@@ -189,11 +190,20 @@ Version 7.2.1 (patch update) added:
189
190
 
190
191
  Version 7.2.2 just changed wording and description of the tool to be more clear.
191
192
 
193
+ Version 7.2.3 added one more sensitive endpoint and fixed bug where some paths would be / from extra files.
194
+
195
+ Version 7.2.4 added:
196
+
197
+ - fixing pdf keys like /Btn and /Widget used in jsPDF, as they were previously detected as endpoints
198
+ - better SPA separation
199
+ - added more asset formats (.avif, .mp3, .mp4, .webm, .wav)
200
+ - Added text at start saying Endpointscanner 7.2.4 (i wanted it to have the vibe of most open source tools but the 3d text was just too much)
201
+
192
202
  ## Plans for next version and the future
193
203
 
194
204
  Version 7.3 is planned to have:
195
205
 
196
- - More JS Stacks to detect
206
+ - More JS Stacks to detect (and more accurate)
197
207
  - Detecting what type of captcha was used if the script is blocked.
198
208
 
199
209
  Future plans (May be added in the next version):
@@ -3,3 +3,4 @@ beautifulsoup4
3
3
  playwright
4
4
  playwright-stealth
5
5
  httpx[http2]>=0.27.0
6
+ colorama>=0.4.6
@@ -8,6 +8,7 @@ import time
8
8
  import json
9
9
  from playwright.sync_api import sync_playwright #headless browser to solve captcha
10
10
  from playwright_stealth import Stealth #ensure strict firewalls do not block the playwright browser
11
+ from colorama import Fore, Style, init
11
12
 
12
13
  HEADER = {
13
14
  "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",
@@ -18,6 +19,21 @@ USELESSSTUFF = {
18
19
  "localhost", "127.0.0.1", "0.0.0.0",
19
20
  "w3.org", "schema.org", "xml.org", "://microsoft.com", "/../"
20
21
  }
22
+ #jspdf makes all those for no reason
23
+ JSPDF_SIGNATURE_KEYS = {
24
+ "/ASCII85Decode", "/ASCII85Encode", "/ASCIIHexDecode", "/ASCIIHexEncode",
25
+ "/Annot", "/Btn", "/CIDSystemInfo", "/Ch", "/FlateDecode", "/FlateEncode",
26
+ "/Form", "/I", "/Image", "/Outlines", "/Pattern", "/Sig", "/Tx", "/Widget", "/XObject"
27
+ }
28
+
29
+ S_HEADER = {
30
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...",
31
+ "Cache-Control": "no-store",
32
+ "Pragma": "no-cache",
33
+ "If-None-Match": "",
34
+ "If-Modified-Since": ""
35
+ }
36
+
21
37
  unsorted_paths = []
22
38
  e_files = []
23
39
  start_test_time = None
@@ -225,9 +241,7 @@ async def async_rate_test(url, num_reqs=100, method="GET", rb=None, rv=None, coo
225
241
  print("[+] Streaming requests into the background network pipeline...")
226
242
  await asyncio.sleep(45.0)
227
243
 
228
- #after network is gone
229
- #Fancy label below
230
- #----AFTER NETWORK IS GONE----
244
+ #label every single request using enumerate() to find out exactly when the first request timed out, or hit a non-200.
231
245
  status_counts = {}
232
246
  first_limit_at = None
233
247
 
@@ -349,6 +363,13 @@ def main():
349
363
  parser.add_argument("-ro", "--raw-output", action="store_true", help="Do not sort out endpoints after finding them. Will leave out sensitive endpoints whether they are exposed or not.")
350
364
  parser.add_argument("-rh", "--ratelimit-header", type=str, default=None, help="Custom headers. Must be seperated by a pipe(|), or newlines. Example use: Cookies: {ExampleCookie: example} | Accept: application/json, text/plain, */*. If the custom header contains double quotes, please use single quotes instead of double quotes to pass this flag.")
351
365
  args = parser.parse_args()
366
+ if not args.only_res:
367
+ init(autoreset=True)
368
+ print()
369
+ print("-" * 40)
370
+ print(f"{Style.BRIGHT}Endpointscanner {Fore.LIGHTMAGENTA_EX}v7.2.4")
371
+ print("-" * 40)
372
+ print()
352
373
  #ratelimit type always defaults to get
353
374
  if (args.ratelimit_type != 'GET' or args.ratelimit_var or args.ratelimit_body or args.ratelimit_header) and args.ratelimit is None:
354
375
  passedrateargs = []
@@ -404,7 +425,7 @@ def main():
404
425
  exit(1)
405
426
 
406
427
  if args.ratelimit_body and args.ratelimit_var:
407
- # bracket escaping
428
+ # bracket escaping, so if someone puts {{X}} or X, it will end up as {X}.
408
429
  expected_bracket_token = f"{{{args.ratelimit_var.strip('{}')}}}"
409
430
 
410
431
  if expected_bracket_token not in args.ratelimit_body:
@@ -473,7 +494,7 @@ def main():
473
494
  start_test_time = time.perf_counter()
474
495
  #hardcoded dangerous endpoints to test
475
496
  SENSITIVE_ENDPOINT = {
476
- "/.env", "/.env.local", "/.env.production", "/.env.development",
497
+ "/.env", "/.env.local", "/.env.production", "/.env.development", ".env.dev",
477
498
  "/.git/config", "/.git/HEAD", "/package.json", "/package-lock.json", "/.npmrc", "/.dockerenv",
478
499
  "/.gitignore", "/api/health", "/admin", "/login", "/config",
479
500
  "/.env.example", "/docker-compose.yml", "/.babelrc", "/.eslintrc.json",
@@ -504,6 +525,8 @@ def main():
504
525
  for rule in rules:
505
526
  clean_rule = rule.strip()
506
527
  if clean_rule and clean_rule not in ["/", "/*"] and clean_rule not in found_paths:
528
+ if clean_rule.strip() in ["/", "/*"]:
529
+ continue
507
530
  found_paths.add(clean_rule)
508
531
  results_fromotherfiles.append(f"{clean_rule} [Source: robots.txt]")
509
532
  if args.show_prog:
@@ -519,6 +542,8 @@ def main():
519
542
  for loc in locs:
520
543
  clean_path = loc.strip()
521
544
  if clean_path and clean_path != "/" and clean_path not in found_paths:
545
+ if clean_path.strip() == "/":
546
+ continue
522
547
  found_paths.add(clean_path)
523
548
  if not args.tidy:
524
549
  results_fromotherfiles.append(f"{clean_path} [Source: sitemap.xml]")
@@ -536,6 +561,8 @@ def main():
536
561
  paths = re.findall(r'["\'](/[a-zA-Z0-9_\-\./]+)["\']', m_res.text)
537
562
  for path in paths:
538
563
  if path not in found_paths:
564
+ if path.strip() == "/":
565
+ continue
539
566
  found_paths.add(path)
540
567
  if not args.tidy:
541
568
  results_fromotherfiles.append(f"{path} [Source: asset-manifest.json]")
@@ -553,6 +580,8 @@ def main():
553
580
  paths = re.findall(r'["\'](/[a-zA-Z0-9_\-\./]+)["\']', m_res.text)
554
581
  for path in paths:
555
582
  if path not in found_paths:
583
+ if path.strip() == "/":
584
+ continue
556
585
  found_paths.add(path)
557
586
  if not args.tidy:
558
587
  results_fromotherfiles.append(f"{path} [Source: {manifest_path.lstrip('/')}]")
@@ -572,6 +601,8 @@ def main():
572
601
  paths = re.findall(r'["\'`](/[a-zA-Z0-9_\-\./{}:]+)["\'`]', sw_res.text)
573
602
  for path in paths:
574
603
  if path not in found_paths and not any(path.endswith(ext) for ext in ['.js', '.css']):
604
+ if path.strip() == "/":
605
+ continue
575
606
  found_paths.add(path)
576
607
  if not args.tidy:
577
608
  results_fromotherfiles.append(f"{path} [Source: {sw_path}]")
@@ -584,11 +615,13 @@ def main():
584
615
  #openid
585
616
  try:
586
617
  oidc_res = requests.get(urljoin(target, "/.well-known/openid-configuration"), headers=E_HEADER, impersonate="chrome120", timeout=4)
587
- if oidc_res.status_code == 200 and "{" in oidc_res.text:
618
+ if oidc_res.status_code == 200 and "authorization_endpoint" in oidc_res.text and "issuer" in oidc_res.text:
588
619
  e_files.append("/.well-known/openid-configuration")
589
620
  paths = re.findall(r'https?://[^/]+(/[^"\']*)', oidc_res.text)
590
621
  for path in paths:
591
622
  if path not in found_paths:
623
+ if path.strip() == "/":
624
+ continue
592
625
  found_paths.add(path)
593
626
  if not args.tidy:
594
627
  results_fromotherfiles.append(f"{path} [Source: openid-configuration]")
@@ -651,6 +684,10 @@ def main():
651
684
  start_test_time = time.perf_counter()
652
685
  args.scan_timeout = 5.0 #5min more
653
686
  matches = re.findall(p, respo.text)
687
+ detected_library_keys = set(matches).intersection(JSPDF_SIGNATURE_KEYS)
688
+ if len(detected_library_keys) >= 3:
689
+ matches = [m for m in matches if m not in JSPDF_SIGNATURE_KEYS]
690
+
654
691
  for m in matches:
655
692
  m_clean = re.sub(r'(\$\{.*?\}|:[a-zA-Z0-9]+)', '1', m)
656
693
  m_clean = m_clean.strip()
@@ -772,6 +809,9 @@ def main():
772
809
  current_filename = js_url
773
810
  for p in patterns:
774
811
  matches = re.findall(p, js_res.text)
812
+ detected_library_keys = set(matches).intersection(JSPDF_SIGNATURE_KEYS)
813
+ if len(detected_library_keys) >= 3:
814
+ matches = [m for m in matches if m not in JSPDF_SIGNATURE_KEYS]
775
815
  for m in matches:
776
816
  m_clean = re.sub(r'(\$\{.*?\}|:[a-zA-Z0-9]+)', '1', m)
777
817
  m_clean = m_clean.strip()
@@ -821,8 +861,13 @@ def main():
821
861
  start_test_time = time.perf_counter()
822
862
  args.scan_timeout = 5.0 #5min more
823
863
  try:
864
+ DOWNLOAD_XML_HEADERS = HEADER.copy()
865
+ DOWNLOAD_XML_HEADERS["Cache-Control"] = "no-cache"
866
+ DOWNLOAD_XML_HEADERS["Pragma"] = "no-cache"
867
+ DOWNLOAD_XML_HEADERS["If-None-Match"] = ""
868
+ DOWNLOAD_XML_HEADERS["If-Modified-Since"] = ""
824
869
  target_xml_url = urljoin(target, xmlfile)
825
- x_res = requests.get(target_xml_url, headers=HEADER, impersonate="chrome120", timeout=4)
870
+ x_res = requests.get(target_xml_url, headers=DOWNLOAD_XML_HEADERS, impersonate="chrome120", timeout=4)
826
871
 
827
872
  if x_res.status_code == 200 and "<loc" in x_res.text.lower():
828
873
  locs = re.findall(r'<loc>https?://[^/]+(/[^<]+)</loc>', x_res.text, re.IGNORECASE)
@@ -852,9 +897,11 @@ def main():
852
897
  results_services, results_ext, results_subd = [], [], []
853
898
  results_frameworks, results_assets = [], []
854
899
  unsorted = []
900
+ assets_suffix = "" if args.show_assets else " (Hidden, use --show-assets to show)"
901
+ dead_suffix = "" if args.show_404s else " (Hidden, use --show-404s to show)"
855
902
  if not args.raw_output:
856
903
  if not args.only_res:
857
- print(f"Total paths to test: {len(found_paths)}")
904
+ print(f"Total paths to test: {len(found_paths)} (Scraped: {len(found_paths) - len(SENSITIVE_ENDPOINT)} | Built-in: {len(SENSITIVE_ENDPOINT)})")
858
905
  print("Testing endpoints...")
859
906
  for path in sorted(found_paths):
860
907
  display_path = discovered_in_js.get(path, path)
@@ -900,7 +947,7 @@ def main():
900
947
 
901
948
  r = requests.get(
902
949
  urljoin(target, path),
903
- headers=HEADER,
950
+ headers=S_HEADER,
904
951
  cookies=session_cookies,
905
952
  timeout=5,
906
953
  allow_redirects=False,
@@ -915,8 +962,8 @@ def main():
915
962
  service_markers = ["/api", "/v1", "/v2", "socket.io", "engine.io", "/graphql", "/webhook", "/rpc", "/actuator", "/swagger", "/v3/api-docs", "/rest/", "/ws", "/metrics"]
916
963
  is_machine_path = any(marker in path.lower() for marker in service_markers)
917
964
 
918
- media_extensions = ('.png', '.jpg', '.jpeg', '.svg', '.webp', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.swf')
919
- framework_extensions = ('.js', '.css', '.json', '.txt', '.xml', '.map')
965
+ media_extensions = ('.png', '.jpg', '.jpeg', '.svg', '.webp', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.swf', '.mp4', '.mp3', '.avif', '.webm', '.wav')
966
+ framework_extensions = ('.js', '.css', '.json', '.txt', '.xml', '.map', '.rels', '.md')
920
967
 
921
968
  is_media_asset = path.lower().endswith(media_extensions)
922
969
  is_framework_asset = any(path.lower().endswith(ext) for ext in framework_extensions)
@@ -927,7 +974,13 @@ def main():
927
974
  if not args.tidy and r.status_code == 405:
928
975
  statag = ' [405]'
929
976
  is_transport = any(p in path.lower() for p in ["socket.io", "engine.io", "/rpc", "/webhook", "/graphql"])
930
- if (is_shell or is_home_redirect) and not is_transport:
977
+ if (is_shell or is_home_redirect) and (is_framework_asset or is_media_asset or "." in path) and not is_transport:
978
+ if not args.tidy:
979
+ results_dead.append(f"404 Not Found (React Shell): {path}{statag}")
980
+ else:
981
+ results_dead.append(f"404 Not Found: {path}{statag}")
982
+ continue
983
+ if (is_shell or is_home_redirect) and not is_transport and not is_framework_asset and not is_media_asset:
931
984
  if path in discovered_in_js:
932
985
  if not args.tidy:
933
986
  results_200.append(f"{display_path}{statag} [Client-Side Route, Requires Login]")
@@ -950,7 +1003,7 @@ def main():
950
1003
  results_services.append(f"{display_path}{statag}")
951
1004
 
952
1005
  else:
953
- if "text/html" in content_type:
1006
+ if "text/html" in content_type and not ((is_shell or is_home_redirect) and (is_framework_asset or "." in path)):
954
1007
  if not args.tidy:
955
1008
  results_200.append(f"{display_path}{statag} [Access no matter what]")
956
1009
  else:
@@ -958,7 +1011,13 @@ def main():
958
1011
  elif is_media_asset:
959
1012
  results_assets.append(f"{display_path}{statag}")
960
1013
  elif is_framework_asset:
961
- results_frameworks.append(f"{display_path}{statag}")
1014
+ if is_shell or is_home_redirect:
1015
+ if not args.tidy:
1016
+ results_dead.append(f"404 Not Found (React Shell Fake File): {path}{statag}")
1017
+ else:
1018
+ results_dead.append(f"404 Not Found: {path}{statag}")
1019
+ else:
1020
+ results_frameworks.append(f"{display_path}{statag}")
962
1021
  else:
963
1022
  if not args.tidy:
964
1023
  results_frameworks.append(f"{display_path}{statag} [Non-Standard File]")
@@ -1005,6 +1064,7 @@ def main():
1005
1064
  start_test_time = time.perf_counter()
1006
1065
  args.scan_timeout = 5.0 #5min more
1007
1066
  else:
1067
+ #UNSORTED_PATHS IS FOR RAW OUTPUT FLAG. FOR UNSORTED AFTER SCAN TIMEOUT IT IS THE 'UNSORTED' LIST.
1008
1068
  for path in sorted(found_paths):
1009
1069
  display_path = discovered_in_js.get(path, path)
1010
1070
  pure_path = display_path.split(" [Original:")[0]
@@ -1075,17 +1135,23 @@ def main():
1075
1135
  print("WARNING: There should never be no inaccessble paths on a website.\nThis is most likely a false positive a fault on the script's end.\nReport this to the owner of the script immediately, whether it has found endpoints or not, and what site the script has been tested on.")
1076
1136
  if unsorted:
1077
1137
  print("\n----UNSORTED (Scan timed out)----")
1078
- for p in unsorted_paths: print(f" {p}")
1138
+ for p in unsorted: print(f" {p}")
1079
1139
  else:
1080
1140
  if args.scan_timeout:
1081
1141
  if not args.only_res:
1082
1142
  print("\nAll paths sorted out.")
1083
1143
 
1084
1144
  print(f"\n--- Scan Summary ---")
1085
- if args.show_assets:
1086
- print(f"Total Accessible Pages: {len(results_200)}\nTotal Services: {len(results_services)}\nTotal External References: {len(results_ext)}\nTotal Source Code/Files: {len(results_frameworks)}\nTotal Redirects: {len(results_30x)}\nTotal Assets: {len(results_assets)}\nTotal Inaccessible: {len(results_dead)}")
1087
- else:
1088
- print(f"Total Accessible Pages: {len(results_200)}\nTotal Services: {len(results_services)}\nTotal External References: {len(results_ext)}\nTotal Source Code/Files: {len(results_frameworks)}\nTotal Redirects: {len(results_30x)}\nTotal Assets: {len(results_assets)} (Hidden, use --show-assets to show)\nTotal Inaccessible: {len(results_dead)}")
1145
+ summary_report = (
1146
+ f"Total Accessible Pages: {len(results_200)}\n"
1147
+ f"Total Services: {len(results_services)}\n"
1148
+ f"Total External References: {len(results_ext)}\n"
1149
+ f"Total Source Code/Files: {len(results_frameworks)}\n"
1150
+ f"Total Redirects: {len(results_30x)}\n"
1151
+ f"Total Assets: {len(results_assets)}{assets_suffix}\n"
1152
+ f"Total Inaccessible: {len(results_dead)}{dead_suffix}"
1153
+ )
1154
+ print(summary_report)
1089
1155
  if unsorted:
1090
1156
  print(f"Total Unsorted: {len(unsorted)}")
1091
1157
  if args.show_source:
@@ -1158,12 +1224,7 @@ def main():
1158
1224
  f.write("All paths sorted out.\n")
1159
1225
 
1160
1226
  f.write(f"\n--- Scan Summary ---\n")
1161
- f.write(f"Total Accessible Pages: {len(results_200)}\n")
1162
- f.write(f"Total Services: {len(results_services)}\n")
1163
- f.write(f"Total External References: {len(results_ext)}\n")
1164
- f.write(f"Total Source Code/Files: {len(results_frameworks)}\n")
1165
- f.write(f"Total Redirects: {len(results_30x)}\n")
1166
- f.write(f"Total Inaccessible: {len(results_dead)}\n")
1227
+ f.write(summary_report)
1167
1228
  if unsorted:
1168
1229
  f.write(f"Total Unsorted: {len(unsorted)}")
1169
1230
  if args.show_source:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "endpointscanner"
7
- version = "7.2.2"
7
+ version = "7.2.4"
8
8
  readme = "README.md"
9
9
  description = "Website endpoint reconnaissance tool and rate limit tester that can bypass simple captchas and WAFs."
10
10
  requires-python = ">=3.9"
@@ -33,7 +33,8 @@ dependencies = [
33
33
  "beautifulsoup4",
34
34
  "playwright",
35
35
  "playwright-stealth",
36
- "httpx[http2]>=0.27.0"
36
+ "httpx[http2]>=0.27.0",
37
+ "colorama>=0.4.6"
37
38
  ]
38
39
 
39
40
  [project.scripts]
File without changes