guarddog 2.8.4__py3-none-any.whl → 2.9.0__py3-none-any.whl

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,13 +1,7 @@
1
- import json
2
1
  import logging
3
- import os
4
- from datetime import datetime, timedelta
5
2
  from typing import Optional
6
3
 
7
- import requests
8
-
9
4
  from guarddog.analyzer.metadata.typosquatting import TyposquatDetector
10
- from guarddog.utils.config import TOP_PACKAGES_CACHE_LOCATION
11
5
 
12
6
  log = logging.getLogger("guarddog")
13
7
 
@@ -23,60 +17,17 @@ class RubyGemsTyposquatDetector(TyposquatDetector):
23
17
  """
24
18
 
25
19
  def _get_top_packages(self) -> set:
26
- popular_packages_url = (
27
- "https://packages.ecosyste.ms/api/v1/registries/rubygems.org/"
28
- "package_names?critical=true&per_page=1000"
20
+ """
21
+ Gets the top 1000 critical RubyGems packages.
22
+ Uses the base class implementation with RubyGems-specific parameters.
23
+ """
24
+ url = "https://packages.ecosyste.ms/api/v1/registries/rubygems.org/package_names?critical=true&per_page=1000"
25
+ return self._get_top_packages_with_refresh(
26
+ packages_filename="top_rubygems_packages.json",
27
+ popular_packages_url=url,
28
+ refresh_days=30,
29
29
  )
30
30
 
31
- top_packages_filename = "top_rubygems_packages.json"
32
- resources_dir = TOP_PACKAGES_CACHE_LOCATION
33
- if resources_dir is None:
34
- resources_dir = os.path.abspath(
35
- os.path.join(os.path.dirname(__file__), "..", "resources")
36
- )
37
-
38
- top_packages_path = os.path.join(resources_dir, top_packages_filename)
39
- top_packages_information = self._get_top_packages_local(top_packages_path)
40
-
41
- if self._file_is_expired(top_packages_path, days=30):
42
- new_information = self._get_top_packages_network(popular_packages_url)
43
- if new_information is not None:
44
- top_packages_information = new_information
45
-
46
- with open(top_packages_path, "w+") as f:
47
- json.dump(new_information, f, ensure_ascii=False, indent=4)
48
-
49
- if top_packages_information is None:
50
- return set()
51
- return set(top_packages_information)
52
-
53
- def _file_is_expired(self, path: str, days: int) -> bool:
54
- try:
55
- update_time = datetime.fromtimestamp(os.path.getmtime(path))
56
- return datetime.now() - update_time > timedelta(days=days)
57
- except FileNotFoundError:
58
- return True
59
-
60
- def _get_top_packages_local(self, path: str) -> list | None:
61
- try:
62
- with open(path, "r") as f:
63
- return json.load(f)
64
- except FileNotFoundError:
65
- log.debug(f"File not found: {path}")
66
- return None
67
-
68
- def _get_top_packages_network(self, url: str) -> list | None:
69
- try:
70
- response = requests.get(url)
71
- response.raise_for_status()
72
- return response.json()
73
- except json.JSONDecodeError:
74
- log.error(f'Could not parse JSON from: "{response.text}"')
75
- return None
76
- except requests.exceptions.RequestException as e:
77
- log.error(f"Network error: {e}")
78
- return None
79
-
80
31
  def detect(
81
32
  self,
82
33
  package_info,
@@ -1,7 +1,18 @@
1
1
  import abc
2
+ import json
3
+ import logging
4
+ import os
5
+ import time
6
+ from datetime import datetime, timedelta
2
7
  from itertools import permutations
8
+ from typing import Optional
9
+
10
+ import requests
3
11
 
4
12
  from guarddog.analyzer.metadata.detector import Detector
13
+ from guarddog.utils.config import TOP_PACKAGES_CACHE_LOCATION
14
+
15
+ log = logging.getLogger("guarddog")
5
16
 
6
17
 
7
18
  class TyposquatDetector(Detector):
@@ -19,8 +30,215 @@ class TyposquatDetector(Detector):
19
30
 
20
31
  @abc.abstractmethod
21
32
  def _get_top_packages(self) -> set:
33
+ """
34
+ Subclasses should implement this to return a set of top package names.
35
+
36
+ For simple implementations without network refresh, override this directly.
37
+ For implementations with network refresh, use _get_top_packages_with_refresh().
38
+ """
22
39
  pass
23
40
 
41
+ def _get_top_packages_with_refresh(
42
+ self,
43
+ packages_filename: str,
44
+ popular_packages_url: Optional[str] = None,
45
+ refresh_days: int = 30,
46
+ ) -> set:
47
+ """
48
+ Common implementation for getting top packages with optional network refresh.
49
+
50
+ Args:
51
+ packages_filename: Name of the JSON file (e.g., "top_pypi_packages.json")
52
+ popular_packages_url: URL to fetch fresh package data. If None, refresh is disabled.
53
+ refresh_days: Number of days before file is considered expired
54
+
55
+ Returns:
56
+ set: Set of package names
57
+ """
58
+ resources_dir = TOP_PACKAGES_CACHE_LOCATION
59
+ if resources_dir is None:
60
+ resources_dir = os.path.abspath(
61
+ os.path.join(os.path.dirname(__file__), "resources")
62
+ )
63
+
64
+ top_packages_path = os.path.join(resources_dir, packages_filename)
65
+ log.debug(f"Loading cache from: {top_packages_path}")
66
+
67
+ cache_data = self._load_cache_file(top_packages_path)
68
+
69
+ if cache_data:
70
+ log.debug(f"Cache loaded successfully with keys: {list(cache_data.keys())}")
71
+ else:
72
+ log.debug("Cache is empty or invalid")
73
+
74
+ top_packages_information = cache_data.get("packages") if cache_data else None
75
+
76
+ # Enable refresh if URL is provided
77
+ enable_refresh = popular_packages_url is not None
78
+ is_expired = self._cache_is_expired(cache_data, days=refresh_days)
79
+ log.debug(
80
+ f"Cache expired check: {is_expired} (refresh enabled: {enable_refresh})"
81
+ )
82
+
83
+ if enable_refresh and is_expired and popular_packages_url is not None:
84
+ log.info(
85
+ f"Cache is expired, attempting to refresh from: {popular_packages_url}"
86
+ )
87
+ new_response_data = self._get_top_packages_network_raw(popular_packages_url)
88
+ if new_response_data is not None:
89
+ log.debug("Downloaded new data, extracting package names")
90
+ top_packages_information = self._extract_package_names(
91
+ new_response_data
92
+ )
93
+
94
+ # Save with new standardized format
95
+ cache_data = {
96
+ "downloaded_timestamp": int(time.time()),
97
+ "packages": top_packages_information,
98
+ }
99
+
100
+ if top_packages_information is not None:
101
+ log.info(
102
+ f"Saving refreshed cache with {len(top_packages_information)} packages to {top_packages_path}"
103
+ )
104
+ with open(top_packages_path, "w+") as f:
105
+ json.dump(cache_data, f, ensure_ascii=False, indent=4)
106
+ else:
107
+ log.warning(
108
+ f"Failed to download new cache data from {popular_packages_url}"
109
+ )
110
+
111
+ if top_packages_information is None:
112
+ return set()
113
+
114
+ return set(top_packages_information)
115
+
116
+ def _cache_is_expired(self, cache_data: dict | None, days: int) -> bool:
117
+ """
118
+ Check if cache data is expired based on downloaded_timestamp.
119
+
120
+ Args:
121
+ cache_data: Cache dictionary with 'downloaded_timestamp' key
122
+ days: Number of days before cache is considered expired
123
+
124
+ Returns:
125
+ bool: True if expired or timestamp missing, False otherwise
126
+ """
127
+ if cache_data is None:
128
+ log.debug("Cache is expired: cache_data is None")
129
+ return True
130
+
131
+ timestamp = cache_data.get("downloaded_timestamp")
132
+ if timestamp is None:
133
+ # Missing timestamp, consider expired
134
+ log.debug("Cache is expired: missing 'downloaded_timestamp' field")
135
+ return True
136
+
137
+ try:
138
+ download_time = datetime.fromtimestamp(timestamp)
139
+ age = datetime.now() - download_time
140
+ is_expired = age > timedelta(days=days)
141
+ log.debug(
142
+ f"Cache age: {age.days} days, threshold: {days} days, expired: {is_expired}"
143
+ )
144
+ return is_expired
145
+ except (ValueError, OSError) as e:
146
+ # Invalid timestamp
147
+ log.debug(f"Cache is expired: invalid timestamp {timestamp} - {e}")
148
+ return True
149
+
150
+ def _load_cache_file(self, path: str) -> dict | None:
151
+ """
152
+ Load cache data from local JSON file.
153
+
154
+ Expected format: {"downloaded_timestamp": epoch, "packages": [...]}
155
+
156
+ If the file doesn't match this format, it will be considered invalid
157
+ and trigger a refresh to download data in the correct format.
158
+
159
+ Args:
160
+ path: Path to the JSON file
161
+
162
+ Returns:
163
+ dict: Cache data with 'packages' and 'downloaded_timestamp', or None if invalid
164
+ """
165
+ try:
166
+ with open(path, "r") as f:
167
+ result = json.load(f)
168
+
169
+ # Validate new format structure
170
+ if (
171
+ isinstance(result, dict)
172
+ and "packages" in result
173
+ and "downloaded_timestamp" in result
174
+ ):
175
+ # Validate that packages is a list
176
+ if isinstance(result["packages"], list):
177
+ return result
178
+ else:
179
+ log.warning(
180
+ f"Invalid cache format in {path}: 'packages' must be a list. Will trigger refresh."
181
+ )
182
+ return None
183
+
184
+ # File doesn't have the correct format - invalidate it
185
+ log.info(
186
+ f"Cache file {path} has old or invalid format. Will trigger refresh to new format."
187
+ )
188
+ return None
189
+
190
+ except FileNotFoundError:
191
+ log.debug(f"Cache file not found: {path}")
192
+ return None
193
+ except json.JSONDecodeError:
194
+ log.error(f"Invalid JSON in file: {path}")
195
+ return None
196
+
197
+ def _get_top_packages_network_raw(self, url: str) -> dict | list | None:
198
+ """
199
+ Fetch the complete response data from the network.
200
+ Returns the full JSON structure to preserve format when saving.
201
+
202
+ Args:
203
+ url: URL to fetch package data from
204
+
205
+ Returns:
206
+ dict | list: Full response data or None on error
207
+ """
208
+ try:
209
+ response = requests.get(url)
210
+ response.raise_for_status()
211
+ return response.json()
212
+ except json.JSONDecodeError:
213
+ log.error(f'Couldn\'t convert to json: "{response.text}"')
214
+ return None
215
+ except requests.exceptions.RequestException as e:
216
+ log.error(f"Network error: {e}")
217
+ return None
218
+
219
+ def _extract_package_names(self, data: dict | list | None) -> list | None:
220
+ """
221
+ Extract package names from the raw data structure.
222
+
223
+ Override this method in subclasses if the data format is specific to the ecosystem.
224
+ Default implementation assumes data is already a list of package names.
225
+
226
+ Args:
227
+ data: Raw data from JSON file or network response
228
+
229
+ Returns:
230
+ list: List of package names or None
231
+ """
232
+ if data is None:
233
+ return None
234
+
235
+ # Default: assume data is already a list
236
+ if isinstance(data, list):
237
+ return data
238
+
239
+ # If it's a dict, subclasses should override this method
240
+ return None
241
+
24
242
  def _is_distance_one_Levenshtein(self, name1, name2) -> bool:
25
243
  """
26
244
  Returns True if two names have a Levenshtein distance of one
@@ -0,0 +1,38 @@
1
+ rules:
2
+ - id: screenshot
3
+ languages:
4
+ - python
5
+ message: This package is taking screenshots, which can be used to steal sensitive information displayed on screen
6
+ metadata:
7
+ description: Identify when a package captures screenshots of the user's display
8
+ patterns:
9
+ - pattern-either:
10
+ # PIL ImageGrab
11
+ - pattern: ImageGrab.grab(...)
12
+ - pattern: PIL.ImageGrab.grab(...)
13
+
14
+ # pyscreenshot library
15
+ - pattern: pyscreenshot.grab(...)
16
+
17
+ # pyautogui library
18
+ - pattern: pyautogui.screenshot(...)
19
+
20
+ # mss library - various patterns
21
+ - pattern: mss.mss().grab(...)
22
+ - pattern: |
23
+ with mss.mss() as $SCT:
24
+ ...
25
+ $SCT.grab(...)
26
+ - pattern: |
27
+ $SCT = mss.mss()
28
+ ...
29
+ $SCT.grab(...)
30
+ - pattern: $MSS.grab(...)
31
+
32
+ # D3DShot (Windows DirectX screenshots)
33
+ - pattern: d3dshot.create(...).screenshot(...)
34
+ - pattern: |
35
+ $D3D = d3dshot.create(...)
36
+ ...
37
+ $D3D.screenshot(...)
38
+ severity: WARNING
@@ -5,7 +5,7 @@ import re
5
5
  from typing import List
6
6
 
7
7
  import requests
8
- from semantic_version import NpmSpec, Version # type:ignore
8
+ from semantic_version import NpmSpec, Version # type: ignore
9
9
 
10
10
  from guarddog.scanners.npm_package_scanner import NPMPackageScanner
11
11
  from guarddog.scanners.scanner import Dependency, DependencyVersion, ProjectScanner
@@ -252,11 +252,9 @@ class ProjectScanner:
252
252
  user = os.getenv("GIT_USERNAME")
253
253
  personal_access_token = os.getenv("GH_TOKEN")
254
254
  if not user or not personal_access_token:
255
- log.error(
256
- """WARNING: Please set GIT_USERNAME (Github handle) and GH_TOKEN
255
+ log.error("""WARNING: Please set GIT_USERNAME (Github handle) and GH_TOKEN
257
256
  (generate a personal access token in Github settings > developer)
258
- as environment variables before proceeding."""
259
- )
257
+ as environment variables before proceeding.""")
260
258
  exit(1)
261
259
  return (user, personal_access_token)
262
260
 
@@ -4,7 +4,7 @@ import pathlib
4
4
  import stat
5
5
  import zipfile
6
6
 
7
- import tarsafe # type:ignore
7
+ import tarsafe # type: ignore
8
8
 
9
9
  from guarddog.utils.config import (
10
10
  MAX_UNCOMPRESSED_SIZE,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: guarddog
3
- Version: 2.8.4
3
+ Version: 2.9.0
4
4
  Summary: GuardDog is a CLI tool for identifying malicious open source packages
5
5
  License: Apache-2.0
6
6
  License-File: LICENSE
@@ -17,7 +17,7 @@ Classifier: Programming Language :: Python :: 3.13
17
17
  Classifier: Programming Language :: Python :: 3.14
18
18
  Requires-Dist: click (>=8.1.3,<9.0.0)
19
19
  Requires-Dist: configparser (>=5.3,<8.0)
20
- Requires-Dist: disposable-email-domains (>=0.0.103,<0.0.121)
20
+ Requires-Dist: disposable-email-domains (>=0.0.103,<0.0.160)
21
21
  Requires-Dist: prettytable (>=3.6.0,<4.0.0)
22
22
  Requires-Dist: pygit2 (>=1.11,<1.19)
23
23
  Requires-Dist: python-dateutil (>=2.8.2,<3.0.0)
@@ -27,7 +27,7 @@ Requires-Dist: requests (>=2.29.0,<3.0.0)
27
27
  Requires-Dist: semantic-version (>=2.10.0,<3.0.0)
28
28
  Requires-Dist: semgrep (>=1.147.0,<2.0.0)
29
29
  Requires-Dist: tarsafe (>=0.0.5,<0.0.6)
30
- Requires-Dist: termcolor (>=2.1.0,<3.0.0)
30
+ Requires-Dist: termcolor (>=3.3.0,<4.0.0)
31
31
  Requires-Dist: urllib3 (>=2.5.0,<3.0.0)
32
32
  Requires-Dist: yara-python (>=4.5.1,<5.0.0)
33
33
  Project-URL: Repository, https://github.com/DataDog/guarddog
@@ -9,16 +9,16 @@ guarddog/analyzer/metadata/detector.py,sha256=dFhmoPVtxoed-Lz3Bd8GXZhorOVajvo5sI
9
9
  guarddog/analyzer/metadata/empty_information.py,sha256=qswYDOrL3MxJ4VYq893Eiev5y56fgXrk5-CTKVXbkeA,1199
10
10
  guarddog/analyzer/metadata/github_action/__init__.py,sha256=hOtiXKW-v5slzYW2M3k35M_YFfuLm8CNv5MwNSdFYMM,311
11
11
  guarddog/analyzer/metadata/go/__init__.py,sha256=apwPnP9D4WEqgtR4RY0YIuFN7oNJXxJE_vYlp0ffRvQ,391
12
- guarddog/analyzer/metadata/go/typosquatting.py,sha256=ZouptuX7a015XGjvYP6TFYgsOfYOxEm9jxYHKoCqwA4,4208
12
+ guarddog/analyzer/metadata/go/typosquatting.py,sha256=PP_xsgJOnZMfbA7DhCDh5jX0VQxoI-cJmkLn_dAsB_M,3623
13
13
  guarddog/analyzer/metadata/npm/__init__.py,sha256=j1Ng74bb1yD9XHFoYmJPzWL7vYMmLt6c2Lbc8lCqnUI,1326
14
14
  guarddog/analyzer/metadata/npm/bundled_binary.py,sha256=vxJLhaTS7wymbktvmfJsF3whz3DWisjdD4wqHlNXvhg,392
15
15
  guarddog/analyzer/metadata/npm/deceptive_author.py,sha256=RIBCWK3NjZiTf7tiz2V0ECy2Zr6Uwb69RQwIcWku380,366
16
- guarddog/analyzer/metadata/npm/direct_url_dependency.py,sha256=hjjTLIT0UVudSf9A9Hory2OAqUkdxoXfKGXDgMtnNso,2449
16
+ guarddog/analyzer/metadata/npm/direct_url_dependency.py,sha256=XdUhrrWiuunybagRVBSlkgCpi5ey2jAmAZAYTjfY_HM,2448
17
17
  guarddog/analyzer/metadata/npm/empty_information.py,sha256=Vpjr5Xe8JB4RIPzM0-BNmiqYiuY42LUbml7Yjig7Rcs,791
18
18
  guarddog/analyzer/metadata/npm/npm_metadata_mismatch.py,sha256=Fj9MT7XlO2iXis4Da-_0CmM0weQiv8bVzKUoSm8ntYU,4428
19
19
  guarddog/analyzer/metadata/npm/potentially_compromised_email_domain.py,sha256=Sm7fBfzayrbYXOpU5XzeCGKNfcX40hMOCSjKjhKwz-g,1719
20
20
  guarddog/analyzer/metadata/npm/release_zero.py,sha256=FNHYfxl52i0V3HydccspcfF82T5L9d3ZyE-_J-UVoS0,633
21
- guarddog/analyzer/metadata/npm/typosquatting.py,sha256=0vazzHPJOhjoxlv1O3-qVH-yQKLKefkXlK90D4JoPCQ,4143
21
+ guarddog/analyzer/metadata/npm/typosquatting.py,sha256=lR5Ym4XWGgnr1wOj7bxrawSVSN7-tnO8luz_oPo-kjU,2959
22
22
  guarddog/analyzer/metadata/npm/unclaimed_maintainer_email_domain.py,sha256=B8olfxXiaSM8c47dyftIgxmMVuwg4s7dMuVxrMS0GNE,953
23
23
  guarddog/analyzer/metadata/npm/utils.py,sha256=QirjkoXhDcrGfoteh-V717TGq1xpXmvuC9dEp_5bt2s,454
24
24
  guarddog/analyzer/metadata/potentially_compromised_email_domain.py,sha256=p2KCIByv4dBNM8h_1xPJgAOL197LLDvSLalZQpsvadg,2960
@@ -30,23 +30,23 @@ guarddog/analyzer/metadata/pypi/potentially_compromised_email_domain.py,sha256=8
30
30
  guarddog/analyzer/metadata/pypi/release_zero.py,sha256=UNUAFeB34B88N0LRe-tnmoZ3Y4x4AVZbe7m0i9Q3W7U,789
31
31
  guarddog/analyzer/metadata/pypi/repository_integrity_mismatch.py,sha256=yW7TPpA-g5XNAhiDtijYiBFhOo6ilBLseuJdwvIMRHI,7670
32
32
  guarddog/analyzer/metadata/pypi/single_python_file.py,sha256=L-YlmlP1TYA9XBeTHBPfECWgVIPTenUWRRnqwCMyh-o,1402
33
- guarddog/analyzer/metadata/pypi/typosquatting.py,sha256=Bzx_vjIiuugeRdyR-ABg7S-6J7Udm7stM8CUU8WUafg,6042
33
+ guarddog/analyzer/metadata/pypi/typosquatting.py,sha256=6BBkyqoq8Li9qg5eqI2mkpX1Ie0G86KCRfs7O9W2j9c,4146
34
34
  guarddog/analyzer/metadata/pypi/unclaimed_maintainer_email_domain.py,sha256=zfT0qTyN83O-7Yevc6tYIjmCAgh8Y_dhE6oZlKdNmm0,421
35
35
  guarddog/analyzer/metadata/pypi/utils.py,sha256=UtG2JVep8bSOMz5LkrhXqS5Oy7Na19nHVct4IQMsQik,177
36
36
  guarddog/analyzer/metadata/release_zero.py,sha256=F0I8coYivya7zns0XI1xNY4LLtTDQXoBUmze1wjwW4g,439
37
37
  guarddog/analyzer/metadata/repository_integrity_mismatch.py,sha256=R77YB2ANTALGfS8jbe7KhLJ3a3U_uPQV6VqgzFELEwM,7586
38
38
  guarddog/analyzer/metadata/resources/placeholder_email_domains.txt,sha256=o3mm9u6vuaVeN4wRgDTidR5oL6ufLTCrE9ISVYbOGUc,11
39
- guarddog/analyzer/metadata/resources/top_go_packages.json,sha256=HHOTcuWTGqlpXDOUgF7ejgmr8sGF_T5l7NQYdXmHcKQ,104044
40
- guarddog/analyzer/metadata/resources/top_npm_packages.json,sha256=eeqVkFNW8ltYcGbjAJBzZrdxBEKezxa6AVVYoEpFazs,192960
41
- guarddog/analyzer/metadata/resources/top_pypi_packages.json,sha256=hDQZUuG6BS4h7yNTAQ191l8_N6jaqezO9n2vdsZPKhA,1474298
42
- guarddog/analyzer/metadata/resources/top_rubygems_packages.json,sha256=75GrX0mB_nnH-0reUQYdOvTsduELrb63yX6xvHwoSmM,20152
39
+ guarddog/analyzer/metadata/resources/top_go_packages.json,sha256=rgdm_Pg6rC9JRBcK0ttoglJm7P1cm6BVnEAHP4dinnU,121633
40
+ guarddog/analyzer/metadata/resources/top_npm_packages.json,sha256=zrr4f1Q8daIXYTHqBibDIsf-bW9qIC7zEeoHNxB8xaA,225024
41
+ guarddog/analyzer/metadata/resources/top_pypi_packages.json,sha256=grxSRmS13DlOWGidgqvxx4jK0ryerxdWRLVfo-Jxrj4,385775
42
+ guarddog/analyzer/metadata/resources/top_rubygems_packages.json,sha256=Xhm5gu6KnB0ebFS4s3EFf59F7N02TD9ftwZAHIon82c,24112
43
43
  guarddog/analyzer/metadata/rubygems/__init__.py,sha256=DHLvlpbRdynWCIHG2caDEa3f7q0I8WsfbmrlTe-fmwM,947
44
44
  guarddog/analyzer/metadata/rubygems/bundled_binary.py,sha256=eOeZXDAoDuwhUfB9W5CesnMWyVpR6BePPzNkGRKZqkc,397
45
45
  guarddog/analyzer/metadata/rubygems/empty_information.py,sha256=8Dxa0Y8tbcv7lV3d3LUj_QO97lpUq2kTTY2JhQE2kw8,700
46
46
  guarddog/analyzer/metadata/rubygems/release_zero.py,sha256=7rk1vxp6OxJkBTOD9kOYEEPLE7YgbApFbOVhjECnIgw,671
47
47
  guarddog/analyzer/metadata/rubygems/repository_integrity_mismatch.py,sha256=pFNMdT0VpiHL6kEkj_2eOH0BwLCbQT2XLqyWJY67tr4,1582
48
- guarddog/analyzer/metadata/rubygems/typosquatting.py,sha256=k9xu8mdAiyNFEGmWFgSql-E-fjch6rS6CXSObQ3G4-c,4745
49
- guarddog/analyzer/metadata/typosquatting.py,sha256=6xmqsB0oKwrsXfTJ9DE1z8-nUW8ukgk1ZSS32iJmapk,4587
48
+ guarddog/analyzer/metadata/rubygems/typosquatting.py,sha256=Y-jrIQEQYkvKMxsXeHJI6gNI5NQjU5DP_21NGPcqqME,2950
49
+ guarddog/analyzer/metadata/typosquatting.py,sha256=_xtseQA8B_g5Et2FiPpM1t2BZ52fACnfNCiCSiGIuEU,12580
50
50
  guarddog/analyzer/metadata/unclaimed_maintainer_email_domain.py,sha256=e-K9mSdph3y33fP_W7LNOlC7AnUVk28a1VfQJtG9vxo,2375
51
51
  guarddog/analyzer/metadata/utils.py,sha256=lMzwC41_-XuUa4d6E5AMyX8XZgXqrQD_gmpQCHrIzXI,2458
52
52
  guarddog/analyzer/sourcecode/__init__.py,sha256=_xt-xn2lfpu-iQhe853eTbbTSN8ULxTutyYiBQ97NtM,4723
@@ -77,6 +77,7 @@ guarddog/analyzer/sourcecode/rubygems-exfiltrate-sensitive-data.yml,sha256=Tup4P
77
77
  guarddog/analyzer/sourcecode/rubygems-install-hook.yml,sha256=sjv3YXeERrZDaTu0pS28rl_B8z5BAMjohntzVLE6Fm8,1636
78
78
  guarddog/analyzer/sourcecode/rubygems-network-on-require.yml,sha256=kmuzYXcuPvarKSl5V5k5CBoPSeuvsLdFaf2_-GHYYmo,2095
79
79
  guarddog/analyzer/sourcecode/rubygems-serialize-environment.yml,sha256=qnZaV-aj9-eSZJeeF6XCi5JrnKkXNqj9xSqGjqP6AAY,1329
80
+ guarddog/analyzer/sourcecode/screenshot.yml,sha256=KYpVuzHBqc8lKy7P2Q30NVFQ6u0LrnBMiMUhY5wo2j0,1165
80
81
  guarddog/analyzer/sourcecode/shady-links.yml,sha256=H-QdBfK30PK9JRWJXk2SlFFiSSkMkuKZF0mWoCjwQ5w,3222
81
82
  guarddog/analyzer/sourcecode/silent-process-execution.yml,sha256=b6RjenMv7si7lXGak3uMmD7PMtQRuKPeJFggPW6UDNI,418
82
83
  guarddog/analyzer/sourcecode/steganography.yml,sha256=3ceO6SJhu4XpZEjfwelLdOxeZ4Ho1OgUjbcacwtOhR0,606
@@ -96,21 +97,21 @@ guarddog/scanners/github_action_scanner.py,sha256=6lriTel3U7vNmCWBf0SWti9sLCv88R
96
97
  guarddog/scanners/go_package_scanner.py,sha256=OdCbwtjJow9AxEv34z7WBfgTamqKj5DxJh7dly_1NuY,2926
97
98
  guarddog/scanners/go_project_scanner.py,sha256=2suZJWvYBhiiBMIQXs38SR04E_Ast50jO44X27gEG10,3349
98
99
  guarddog/scanners/npm_package_scanner.py,sha256=ciOvpRViMIQvNFupe5-hdXv65QLU5ObmacRkR2pgp18,2056
99
- guarddog/scanners/npm_project_scanner.py,sha256=liz5Fyscab53IiSPg0T21Z0vT5eotcHPc_W5Xam4A88,4957
100
+ guarddog/scanners/npm_project_scanner.py,sha256=DHPu_m_G4jkLoT61sNcYb6GdiRUqMwWZDRYf_TGSvb4,4958
100
101
  guarddog/scanners/pypi_package_scanner.py,sha256=ZkuRRbNejnpfFpIHJJ42GH34khiG8CUKWEPvVh_M_uk,2449
101
102
  guarddog/scanners/pypi_project_scanner.py,sha256=O91c1UP2iZju84_N7cSE7pWGrY6rKapeUqXEVyKld3A,6435
102
103
  guarddog/scanners/rubygems_package_scanner.py,sha256=NqqkKj3aeVKj32a6dxz87ZjEu6lNDXR5BX_Zgoc3Ow4,4162
103
104
  guarddog/scanners/rubygems_project_scanner.py,sha256=v6cjkngdfChLDdW8XYqm9na48jSzeYQMsVKmo2UrniA,2277
104
- guarddog/scanners/scanner.py,sha256=a8Hgt5_xtQFrB2LokNOSvwqI_JzESsWQ_ZnLn3kJCAQ,14746
105
+ guarddog/scanners/scanner.py,sha256=YuuLu1cPjeysc1YlRUpePI5e9TeNwIVRHUZXp-qxo7w,14716
105
106
  guarddog/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
- guarddog/utils/archives.py,sha256=s1AXn6r8kD8hy5JxEOTzdHTgDpgSTImj_mFHfAI34IM,7417
107
+ guarddog/utils/archives.py,sha256=tKSq4OtjzH_0GiucU-5gg83d191Oyk0xlTPjpdxVa-I,7418
107
108
  guarddog/utils/config.py,sha256=LfXghBsSeB_UH333C3zvh46UpG0doOW6MaZ7GG7_0Z8,2047
108
109
  guarddog/utils/exceptions.py,sha256=23Kzl3exqYK6X-bcGUeb8wPmSglWNX3GIDPkJ6lQzo4,54
109
110
  guarddog/utils/package_info.py,sha256=6fHJPeLn6-tHhKHw0Soedfv2ruPd8zhW2kbhlc3Aem0,975
110
- guarddog-2.8.4.dist-info/METADATA,sha256=SgaZvtbCAfOnr2PagJwdp4E-a4rXRKRG2aNO8mubQgM,22762
111
- guarddog-2.8.4.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
112
- guarddog-2.8.4.dist-info/entry_points.txt,sha256=vX2fvhnNdkbEL4pDzrH2NqjWVxeOaEYi0sJYmNgS2-s,45
113
- guarddog-2.8.4.dist-info/licenses/LICENSE,sha256=w1aNZxHyoyOPJ4fSdiyrr06tCJZbTjCsH9K1uqeDVyU,11377
114
- guarddog-2.8.4.dist-info/licenses/LICENSE-3rdparty.csv,sha256=cS61ONZL_xlXaTMvQXyBEi3J3es-40Gg6G-6idoa5Qk,314
115
- guarddog-2.8.4.dist-info/licenses/NOTICE,sha256=nlyNt2IjG8IBoQkb7n6jszwAvmREpKAx0POzFO1s2JM,140
116
- guarddog-2.8.4.dist-info/RECORD,,
111
+ guarddog-2.9.0.dist-info/METADATA,sha256=gogd3llQE03AT9lovoWH_k-cIqGSY0A9jisemAnCgTI,22762
112
+ guarddog-2.9.0.dist-info/WHEEL,sha256=kJCRJT_g0adfAJzTx2GUMmS80rTJIVHRCfG0DQgLq3o,88
113
+ guarddog-2.9.0.dist-info/entry_points.txt,sha256=vX2fvhnNdkbEL4pDzrH2NqjWVxeOaEYi0sJYmNgS2-s,45
114
+ guarddog-2.9.0.dist-info/licenses/LICENSE,sha256=w1aNZxHyoyOPJ4fSdiyrr06tCJZbTjCsH9K1uqeDVyU,11377
115
+ guarddog-2.9.0.dist-info/licenses/LICENSE-3rdparty.csv,sha256=cS61ONZL_xlXaTMvQXyBEi3J3es-40Gg6G-6idoa5Qk,314
116
+ guarddog-2.9.0.dist-info/licenses/NOTICE,sha256=nlyNt2IjG8IBoQkb7n6jszwAvmREpKAx0POzFO1s2JM,140
117
+ guarddog-2.9.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.3.0
2
+ Generator: poetry-core 2.3.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any