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.
- guarddog/analyzer/metadata/go/typosquatting.py +11 -28
- guarddog/analyzer/metadata/npm/direct_url_dependency.py +0 -1
- guarddog/analyzer/metadata/npm/typosquatting.py +24 -59
- guarddog/analyzer/metadata/pypi/typosquatting.py +20 -77
- guarddog/analyzer/metadata/resources/top_go_packages.json +2926 -2923
- guarddog/analyzer/metadata/resources/top_npm_packages.json +8005 -8002
- guarddog/analyzer/metadata/resources/top_pypi_packages.json +15003 -60021
- guarddog/analyzer/metadata/resources/top_rubygems_packages.json +979 -976
- guarddog/analyzer/metadata/rubygems/typosquatting.py +9 -58
- guarddog/analyzer/metadata/typosquatting.py +218 -0
- guarddog/analyzer/sourcecode/screenshot.yml +38 -0
- guarddog/scanners/npm_project_scanner.py +1 -1
- guarddog/scanners/scanner.py +2 -4
- guarddog/utils/archives.py +1 -1
- {guarddog-2.8.4.dist-info → guarddog-2.9.0.dist-info}/METADATA +3 -3
- {guarddog-2.8.4.dist-info → guarddog-2.9.0.dist-info}/RECORD +21 -20
- {guarddog-2.8.4.dist-info → guarddog-2.9.0.dist-info}/WHEEL +1 -1
- {guarddog-2.8.4.dist-info → guarddog-2.9.0.dist-info}/entry_points.txt +0 -0
- {guarddog-2.8.4.dist-info → guarddog-2.9.0.dist-info}/licenses/LICENSE +0 -0
- {guarddog-2.8.4.dist-info → guarddog-2.9.0.dist-info}/licenses/LICENSE-3rdparty.csv +0 -0
- {guarddog-2.8.4.dist-info → guarddog-2.9.0.dist-info}/licenses/NOTICE +0 -0
|
@@ -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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
guarddog/scanners/scanner.py
CHANGED
|
@@ -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
|
|
guarddog/utils/archives.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: guarddog
|
|
3
|
-
Version: 2.
|
|
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.
|
|
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 (>=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
40
|
-
guarddog/analyzer/metadata/resources/top_npm_packages.json,sha256=
|
|
41
|
-
guarddog/analyzer/metadata/resources/top_pypi_packages.json,sha256=
|
|
42
|
-
guarddog/analyzer/metadata/resources/top_rubygems_packages.json,sha256=
|
|
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=
|
|
49
|
-
guarddog/analyzer/metadata/typosquatting.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
111
|
-
guarddog-2.
|
|
112
|
-
guarddog-2.
|
|
113
|
-
guarddog-2.
|
|
114
|
-
guarddog-2.
|
|
115
|
-
guarddog-2.
|
|
116
|
-
guarddog-2.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|