skip-trace 0.1.0__py3-none-any.whl → 0.1.1__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.
@@ -7,7 +7,7 @@ from typing import IO
7
7
  from rich.console import Console
8
8
  from rich.table import Table
9
9
 
10
- from ..schemas import PackageResult
10
+ from ..schemas import EvidenceKind, EvidenceSource, PackageResult
11
11
 
12
12
 
13
13
  def render(result: PackageResult, file: IO[str] = sys.stdout):
@@ -28,6 +28,12 @@ def render(result: PackageResult, file: IO[str] = sys.stdout):
28
28
  )
29
29
  console.print("-" * 80)
30
30
 
31
+ # --- Pre-process to find Sigstore evidence ---
32
+ sigstore_evidence = [
33
+ ev for ev in result.evidence if ev.source == EvidenceSource.SIGSTORE
34
+ ]
35
+ sigstore_evidence_ids = {ev.id for ev in sigstore_evidence}
36
+
31
37
  # --- OWNERS TABLE ---
32
38
  if not result.owners:
33
39
  console.print("\n[bold]## 🕵️ Owner Candidates[/bold]")
@@ -40,7 +46,7 @@ def render(result: PackageResult, file: IO[str] = sys.stdout):
40
46
  owner_table = Table(
41
47
  show_header=True,
42
48
  header_style="bold magenta",
43
- title="Top Owner Candidates",
49
+ title="Top Owner Candidates (by inferred score)",
44
50
  title_style="bold",
45
51
  )
46
52
  owner_table.add_column("Owner", style="cyan", width=30, no_wrap=True)
@@ -81,14 +87,45 @@ def render(result: PackageResult, file: IO[str] = sys.stdout):
81
87
  else "[italic]No notes.[/italic]"
82
88
  )
83
89
 
90
+ # Check if this owner is backed by Sigstore evidence
91
+ is_verified = bool(sigstore_evidence_ids.intersection(owner.evidence))
92
+ owner_display_name = f"🛡️ {owner.name}" if is_verified else owner.name
93
+
84
94
  owner_table.add_row(
85
- owner.name,
95
+ owner_display_name,
86
96
  owner.kind.value,
87
97
  f"[{score_style}]{score_str}[/]",
88
98
  contacts_str,
89
99
  key_evidence_str,
90
100
  )
91
101
  console.print(owner_table)
102
+ console.print(
103
+ "[dim]Owners marked with 🛡️ are supported by cryptographic evidence.[/dim]"
104
+ )
105
+
106
+ # --- CRYPTOGRAPHIC EVIDENCE (SIGSTORE) ---
107
+ if sigstore_evidence:
108
+ console.print("\n[bold]## 🛡️ Cryptographic Evidence (from Sigstore)[/bold]")
109
+ sig_table = Table(
110
+ show_header=True,
111
+ header_style="bold green",
112
+ title="Verified Signatures and Provenance",
113
+ title_style="bold",
114
+ )
115
+ sig_table.add_column("Kind", style="cyan", width=25)
116
+ sig_table.add_column("Identity or Source")
117
+ sig_table.add_column("Notes")
118
+
119
+ for ev in sigstore_evidence:
120
+ kind_str = ev.kind.value.replace("sigstore-", "").replace("-", " ").title()
121
+ primary_value = ""
122
+ if ev.kind == EvidenceKind.SIGSTORE_SIGNER_IDENTITY:
123
+ primary_value = ev.value.get("identity", "[unknown]")
124
+ elif ev.kind == EvidenceKind.SIGSTORE_BUILD_PROVENANCE:
125
+ primary_value = ev.value.get("repo_uri", "[unknown]")
126
+
127
+ sig_table.add_row(kind_str, primary_value, ev.notes)
128
+ console.print(sig_table)
92
129
 
93
130
  # --- MAINTAINERS TABLE ---
94
131
  if result.maintainers:
@@ -96,7 +133,7 @@ def render(result: PackageResult, file: IO[str] = sys.stdout):
96
133
  maintainer_table = Table(
97
134
  show_header=True,
98
135
  header_style="bold cyan",
99
- title="Directly Listed Maintainers",
136
+ title="Directly Listed Maintainers (from PyPI)",
100
137
  title_style="bold",
101
138
  )
102
139
  maintainer_table.add_column("Name", style="cyan")
@@ -112,4 +149,31 @@ def render(result: PackageResult, file: IO[str] = sys.stdout):
112
149
 
113
150
  console.print(maintainer_table)
114
151
 
152
+ # --- URL STATUS TABLE ---
153
+ url_status_evidence = [
154
+ ev for ev in result.evidence if ev.kind == EvidenceKind.URL_STATUS
155
+ ]
156
+ if url_status_evidence:
157
+ console.print("\n[bold]## 🔗 URL Analysis[/bold]")
158
+ url_table = Table(
159
+ show_header=True, header_style="bold blue", title="Checked URLs"
160
+ )
161
+ url_table.add_column("URL", style="cyan", no_wrap=True)
162
+ url_table.add_column("HTTP Status", justify="center")
163
+
164
+ for ev in sorted(url_status_evidence, key=lambda x: x.locator):
165
+ status = ev.value.get("status_code", "N/A")
166
+ status_str = str(status)
167
+ if status == -1:
168
+ status_str = "Connection Error"
169
+ style = "bold red"
170
+ elif 200 <= status < 300:
171
+ style = "green"
172
+ elif 300 <= status < 400:
173
+ style = "yellow"
174
+ else:
175
+ style = "red"
176
+ url_table.add_row(ev.locator, f"[{style}]{status_str}[/]")
177
+ console.print(url_table)
178
+
115
179
  console.print("-" * 80)
skip_trace/schemas.py CHANGED
@@ -2,10 +2,13 @@
2
2
  from __future__ import annotations
3
3
 
4
4
  import datetime
5
+ import logging
5
6
  from dataclasses import dataclass, field
6
7
  from enum import Enum
7
8
  from typing import Any, List, Optional
8
9
 
10
+ logger = logging.getLogger(__name__)
11
+
9
12
  # --- Enums for controlled vocabularies ---
10
13
 
11
14
 
@@ -44,6 +47,8 @@ class EvidenceSource(str, Enum):
44
47
  WHOIS = "whois"
45
48
  VENV_SCAN = "venv-scan"
46
49
  LLM_NER = "llm-ner"
50
+ URL = "url" # For evidence from scanned homepages
51
+ PYPI_ATTESTATION = "pypi-attestation" # New source
47
52
 
48
53
 
49
54
  class EvidenceKind(str, Enum):
@@ -66,6 +71,12 @@ class EvidenceKind(str, Enum):
66
71
  # GitHub profile-specific evidence kinds
67
72
  USER_PROFILE = "user-profile"
68
73
  USER_COMPANY = "user-company"
74
+ URL_STATUS = "url-status" # To track HTTP status of found URLs
75
+ # Sigstore-specific evidence kinds
76
+ SIGSTORE_SIGNER_IDENTITY = "sigstore-signer-identity"
77
+ SIGSTORE_BUILD_PROVENANCE = "sigstore-build-provenance"
78
+ # PyPI Attestation-specific evidence kind
79
+ PYPI_PUBLISHER_ATTESTATION = "pypi-publisher-attestation"
69
80
 
70
81
 
71
82
  # --- Core Data Schemas ---
@@ -94,6 +105,16 @@ class EvidenceRecord:
94
105
  confidence: float = 0.0
95
106
  notes: str = ""
96
107
 
108
+ def __post_init__(self) -> None:
109
+ """Logs every instantiation."""
110
+ logger.info(
111
+ "EvidenceRecord created: id=%s source=%s kind=%s notes=%r",
112
+ self.id,
113
+ self.source,
114
+ self.kind,
115
+ self.notes,
116
+ )
117
+
97
118
 
98
119
  @dataclass
99
120
  class OwnerCandidate:
@@ -1,6 +1,7 @@
1
1
  # skip_trace/utils/http_client.py
2
2
  from __future__ import annotations
3
3
 
4
+ import logging
4
5
  from typing import Optional
5
6
 
6
7
  import httpx
@@ -9,6 +10,7 @@ from ..config import CONFIG
9
10
  from ..exceptions import NetworkError
10
11
 
11
12
  _client: Optional[httpx.Client] = None
13
+ logger = logging.getLogger(__name__)
12
14
 
13
15
 
14
16
  def get_client() -> httpx.Client:
@@ -32,6 +34,7 @@ def make_request(url: str) -> httpx.Response:
32
34
  :raises NetworkError: If the request fails due to network issues or an error status code.
33
35
  :return: The httpx.Response object.
34
36
  """
37
+ logger.info(f"Looking at {url}")
35
38
  client = get_client()
36
39
  try:
37
40
  response = client.get(url)
@@ -43,3 +46,18 @@ def make_request(url: str) -> httpx.Response:
43
46
  raise NetworkError(
44
47
  f"Request to {e.request.url} failed with status {e.response.status_code}"
45
48
  ) from e
49
+
50
+
51
+ def make_request_safe(url: str) -> Optional[httpx.Response]:
52
+ """
53
+ Makes a GET request but returns the response even on HTTP error codes,
54
+ or None if a connection-level error occurs.
55
+ """
56
+ logger.info(f"Looking at {url}")
57
+ client = get_client()
58
+ try:
59
+ response = client.get(url)
60
+ return response
61
+ except httpx.RequestError as e:
62
+ logger.warning(f"Network request to {e.request.url} failed: {e}")
63
+ return None # Indicate a connection-level error
@@ -1,12 +1,15 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: skip-trace
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: Ownership Attribution for Python Packages
5
5
  Project-URL: Homepage, https://github.com/matthewdeanmartin/skip-trace
6
6
  Project-URL: Issues, https://github.com/matthewdeanmartin/skip-trace/issues
7
7
  Author-email: Matthew Dean Martin <matthewdeanmartin@gmail.com>
8
+ License-Expression: MIT
8
9
  License-File: LICENSE
9
- Classifier: Development Status :: 1 - Planning
10
+ Keywords: PEP 541,PyPI maintainers,package owners,package provenance,software supply chain
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: License :: OSI Approved :: MIT License
10
13
  Classifier: Operating System :: OS Independent
11
14
  Classifier: Programming Language :: Python :: 3
12
15
  Classifier: Programming Language :: Python :: 3.9
@@ -16,7 +19,7 @@ Classifier: Programming Language :: Python :: 3.12
16
19
  Classifier: Programming Language :: Python :: 3.13
17
20
  Classifier: Topic :: Security
18
21
  Classifier: Topic :: Software Development :: Quality Assurance
19
- Requires-Python: >=3.8
22
+ Requires-Python: >=3.13
20
23
  Requires-Dist: beautifulsoup4>=4.12.0
21
24
  Requires-Dist: email-validator>=2.0.0
22
25
  Requires-Dist: en-core-web-sm
@@ -27,6 +30,7 @@ Requires-Dist: pygithub>=1.59.0
27
30
  Requires-Dist: python-dotenv
28
31
  Requires-Dist: python-dotenv>=1.0.0
29
32
  Requires-Dist: python-whois>=0.8.0
33
+ Requires-Dist: pyyaml>=6.0
30
34
  Requires-Dist: rich-argparse
31
35
  Requires-Dist: rich>=13.0.0
32
36
  Requires-Dist: sigstore>=1.0.0
@@ -0,0 +1,39 @@
1
+ skip_trace/__about__.py,sha256=FsjvZ9_bL7XRX3JSfSw-8MGl8izBdAyC2zBiG2sCT-Q,660
2
+ skip_trace/__init__.py,sha256=t4sAc1NJDDj90Z1o1FwYnPMqpJgV1pDt4hlPuTlpykE,106
3
+ skip_trace/__main__.py,sha256=auCXP2BqRl6sPSrYr-_c5jA8fFKcOKTBTL2J9gMeFl0,144
4
+ skip_trace/cli.py,sha256=kHgr_jXkWWo6rthXU18c4qE5ur0Upc1wywkfzeTg9VA,5492
5
+ skip_trace/config.py,sha256=BHM8TuJ_jWDhr1uz9hAAwA3eJwu_m-pYmAvnx_TcCcw,4735
6
+ skip_trace/exceptions.py,sha256=MnMlTjPYrvtB_1j-CAHwmlggjfNCDiRiThjXyuX70Js,581
7
+ skip_trace/m.py,sha256=pmASLEmxc_V5snLxW90SXdmCQMphuCdcsZ6C399LLLo,10536
8
+ skip_trace/main.py,sha256=pmASLEmxc_V5snLxW90SXdmCQMphuCdcsZ6C399LLLo,10536
9
+ skip_trace/py.typed.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ skip_trace/schemas.py,sha256=jyK4HfJLNJTFfdXnSOKbRX_KQH1kxsLlhpeJ3izFEhA,3874
11
+ skip_trace/analysis/__init__.py,sha256=7bOZj8CbG7Ezl9w_Z9gZCuO456psxecJCmPLxljFiuo,135
12
+ skip_trace/analysis/content_scanner.py,sha256=B22moAVvPrl_IgdILYHVggIuUBYrGuvlqXL8xLIuYJg,6414
13
+ skip_trace/analysis/evidence.py,sha256=OSUS2SAlQCn6V6BJpzCKk_jxU_3Fzqv0EIH5AyRHln4,11661
14
+ skip_trace/analysis/ner.py,sha256=Ckcbyeza7fkDV-oGWTfakowLeWcybTzUpWjJ5bMlIwA,1607
15
+ skip_trace/analysis/scoring.py,sha256=Cfs54CjHjMwrC3y_Ab5CzmGyouf_wqepvGfNrxhfBAM,12312
16
+ skip_trace/analysis/source_scanner.py,sha256=CF3q5hXjSLMQHpQIzjSYlEMnTIJCMnWy24sK6RZN2w8,15272
17
+ skip_trace/collectors/__init__.py,sha256=R3ZEXSqLvecVghzxQ3c-pbTulgVddPVUSEwqFb3hLbU,193
18
+ skip_trace/collectors/github.py,sha256=CIwapS1UO6qLnoVyTHe7vLi159d6qRqCk9YRRk7jb7g,8575
19
+ skip_trace/collectors/github_files.py,sha256=APCavmVJ4ux3pUdSm2Ak2Fmdd6VplISqxqiFI_qj7so,13866
20
+ skip_trace/collectors/package_files.py,sha256=3rCtDMROF25N8anHy1UWLG1tPgSsNhR_zBYeKZ-MuoE,12936
21
+ skip_trace/collectors/pypi.py,sha256=nZVl7sDrnUBTKLsinU1TWbMm_CES6oGVUtQUKS21ues,6200
22
+ skip_trace/collectors/pypi_attestations.py,sha256=gc9_tPgEgyEr29nB0v2qSYGKUmEWsvi-qyukAVaz85Y,5807
23
+ skip_trace/collectors/sigstore.py,sha256=WWzRtx28pSQD-emqu-m6yAKiudOM6F0o49hSRWrwEao,6267
24
+ skip_trace/collectors/urls.py,sha256=XQ-2u48qTxQk175xFUBB4k6XA0t95rr34FBfmUBgV-E,3327
25
+ skip_trace/collectors/whois.py,sha256=tQYI30IbRMlHEwn3FRSdKYX_c2TJLyMUyhLdIxpXU4o,6953
26
+ skip_trace/reporting/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
+ skip_trace/reporting/json_reporter.py,sha256=VInt34k3Zo41y7SKkOnjxRj_gsB932GaAZsBj93Fvfc,610
28
+ skip_trace/reporting/md_reporter.py,sha256=ZAZW9xm1VxKjgfgei_sFFFEfu5csMTU1ry9Qg75k3Dc,6819
29
+ skip_trace/utils/__init__.py,sha256=Wm-u-aQCpDEGL5wiRHWQ1ZwNQvA2iSLRpDNeb46_2P8,126
30
+ skip_trace/utils/cache.py,sha256=K6k0MXuf3fDr1ecjThV3qhZFHYhgCi0K69i27SoJmAY,2415
31
+ skip_trace/utils/cli_suggestions.py,sha256=pDNqfLhcYWEqvFM4jiOsUGXlqvTZxpWHR_O1hoCvzyM,2886
32
+ skip_trace/utils/http_client.py,sha256=kZwloPE-kXguOL9ZoQ_joT7OB8-AAin17AnqVHX7PPw,1939
33
+ skip_trace/utils/safe_targz.py,sha256=ETOJc5VA9A5KDhlaV9N15tLbTFABA0zSQob9eXnKQPc,5233
34
+ skip_trace/utils/validation.py,sha256=cgPcmkVEoHZ9vkvntycckxpQM5UbrVpUvUCb0S2FZrs,1427
35
+ skip_trace-0.1.1.dist-info/METADATA,sha256=KiJqLaPCUgxXlVHKgflAiN3t-toCcPt_MzaOXNsvNEA,8571
36
+ skip_trace-0.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
37
+ skip_trace-0.1.1.dist-info/entry_points.txt,sha256=zR7je0FG4CxaLIfaONVMi9LgmKfWtbYkrTVLclrTwhg,51
38
+ skip_trace-0.1.1.dist-info/licenses/LICENSE,sha256=CDE_-1UnY5KRaDCIuCdNg7j7lkGCJbtZWTbhB10QeLk,1071
39
+ skip_trace-0.1.1.dist-info/RECORD,,
@@ -1,33 +0,0 @@
1
- skip_trace/__about__.py,sha256=aouD5xOQKs7wYRs0yiZysx-VWoCOqWX915NaDSofMR4,468
2
- skip_trace/__init__.py,sha256=UkTfkLRuPO5XBc9_6IYHDXqGB_t8AfCZSzAdMS8AuyE,129
3
- skip_trace/__main__.py,sha256=auCXP2BqRl6sPSrYr-_c5jA8fFKcOKTBTL2J9gMeFl0,144
4
- skip_trace/cli.py,sha256=ylcR_PMRcaCDf2pmrEGbna-q_3DJVlbB0ard1R86yaM,5483
5
- skip_trace/config.py,sha256=BHM8TuJ_jWDhr1uz9hAAwA3eJwu_m-pYmAvnx_TcCcw,4735
6
- skip_trace/exceptions.py,sha256=MnMlTjPYrvtB_1j-CAHwmlggjfNCDiRiThjXyuX70Js,581
7
- skip_trace/main.py,sha256=zcO1KaHB3nWq5oq4vOTraxuSSID9Ey78qYCjPbB0b9E,9907
8
- skip_trace/py.typed.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
- skip_trace/schemas.py,sha256=otxLkn56HtVGlycgOlzI6cTv_Iu_0ICfYYClb2WtPD0,3103
10
- skip_trace/analysis/__init__.py,sha256=7bOZj8CbG7Ezl9w_Z9gZCuO456psxecJCmPLxljFiuo,135
11
- skip_trace/analysis/evidence.py,sha256=z8-4AcoxTRTMzr4NhilijfVifjWmybTlBsvGR47evHc,11661
12
- skip_trace/analysis/ner.py,sha256=Ckcbyeza7fkDV-oGWTfakowLeWcybTzUpWjJ5bMlIwA,1607
13
- skip_trace/analysis/scoring.py,sha256=K_pyUVwkGUasYOdveNtvrBrTjZ03VbovjMbM0mbA7_s,10328
14
- skip_trace/analysis/source_scanner.py,sha256=nBE--gCaWfrXxsc-OdU9uhi4deYuF6AodW4w9b64sB8,15277
15
- skip_trace/collectors/__init__.py,sha256=7wRLLntBWFs-lik7Rr-oPv426zweFyCSKbwit1gXUdU,141
16
- skip_trace/collectors/github.py,sha256=CIwapS1UO6qLnoVyTHe7vLi159d6qRqCk9YRRk7jb7g,8575
17
- skip_trace/collectors/package_files.py,sha256=WT1I-WN6MclGGb02SaAE3kUTM5TImDdb9qXvVB_uIPc,6026
18
- skip_trace/collectors/pypi.py,sha256=OeqKIsg-s7oKxqTfcquqYVjyecmjNSupXVFcqBmKMFU,6205
19
- skip_trace/collectors/whois.py,sha256=tQYI30IbRMlHEwn3FRSdKYX_c2TJLyMUyhLdIxpXU4o,6953
20
- skip_trace/reporting/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
- skip_trace/reporting/json_reporter.py,sha256=VInt34k3Zo41y7SKkOnjxRj_gsB932GaAZsBj93Fvfc,610
22
- skip_trace/reporting/md_reporter.py,sha256=V0tR6srTVe4_PVB2J0TrC3B_tB_ZuDTAZ2xLrpMMaRo,4081
23
- skip_trace/utils/__init__.py,sha256=Wm-u-aQCpDEGL5wiRHWQ1ZwNQvA2iSLRpDNeb46_2P8,126
24
- skip_trace/utils/cache.py,sha256=K6k0MXuf3fDr1ecjThV3qhZFHYhgCi0K69i27SoJmAY,2415
25
- skip_trace/utils/cli_suggestions.py,sha256=pDNqfLhcYWEqvFM4jiOsUGXlqvTZxpWHR_O1hoCvzyM,2886
26
- skip_trace/utils/http_client.py,sha256=3n0c4QBMCXHnIZ6Jn30HZWtFaWYvrq0SNr5Mp2ll_1E,1350
27
- skip_trace/utils/safe_targz.py,sha256=ETOJc5VA9A5KDhlaV9N15tLbTFABA0zSQob9eXnKQPc,5233
28
- skip_trace/utils/validation.py,sha256=cgPcmkVEoHZ9vkvntycckxpQM5UbrVpUvUCb0S2FZrs,1427
29
- skip_trace-0.1.0.dist-info/METADATA,sha256=hij-sg6Pgci3LycgEJpLTZI17RULvlzr22E5cbB53uY,8380
30
- skip_trace-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
- skip_trace-0.1.0.dist-info/entry_points.txt,sha256=zR7je0FG4CxaLIfaONVMi9LgmKfWtbYkrTVLclrTwhg,51
32
- skip_trace-0.1.0.dist-info/licenses/LICENSE,sha256=CDE_-1UnY5KRaDCIuCdNg7j7lkGCJbtZWTbhB10QeLk,1071
33
- skip_trace-0.1.0.dist-info/RECORD,,