semantic-types PyPI Solana Keypair Monkey Patch
Socket reported that semantic-types became malicious at version 0.1.5 and 0.1.6, with five Solana-themed PyPI packages pulling it transitively. The payload monkey-patched solders.keypair.Keypair constructors, encrypted Solana private keys with an RSA-2048 public key, and exfiltrated ciphertext through Solana Devnet SPL memo transactions.
On this page 0% read
Executive Summary
Socket reported a PyPI campaign by the alias cappership in which semantic-types carried the malicious payload and five Solana-themed packages pulled it transitively: solana-keypair, solana-publickey, solana-mev-agent-py, solana-trading-bot, and soltrade [Source 1].
The malicious semantic-types update landed at 0.1.5 on January 26, 2025; 0.1.6 repackaged the same payload on January 28, 2025 [Source 1]. The payload monkey-patched solders.keypair.Keypair.from_seed, from_bytes, and from_base58_string, encrypted captured private key bytes with a hardcoded RSA-2048 public key, and sent the ciphertext as an SPL memo transaction through Solana Devnet RPC at api.devnet.solana.com [Source 1].
There is no conventional attacker C2 domain to block for the key theft path. The hard indicators are package names/versions, PyPI publisher metadata from the Socket report, the Solana Devnet RPC endpoint, the SPL Memo program ID, the actor public key D782zqWjgSvy4hQoqzY1ySrGrotnXm1suJeXFur8sAko, the RSA public-key fingerprint 5a4d8480c9d1e82ba102f200258882fb9e694e8fc0343b6982c5540beccdca62, and code paths that generate Solana keypairs while the malicious package is present [Source 1].
Key Facts
event_type: "malicious PyPI package with transitive dependency delivery"
ecosystem: "PyPI"
publisher:
alias: "cappership"
email: "[email protected]"
malicious_payload_package:
name: "semantic-types"
malicious_versions:
- "0.1.5"
- "0.1.6"
transitive_carrier_packages:
- "solana-keypair"
- "solana-publickey"
- "solana-mev-agent-py"
- "solana-trading-bot"
- "soltrade"
execution_trigger: "Python import that registers monkey patches, followed by solders Keypair constructor use"
patched_methods:
- "solders.keypair.Keypair.from_seed"
- "solders.keypair.Keypair.from_bytes"
- "solders.keypair.Keypair.from_base58_string"
collection_window_utc:
start: "2025-01-26T00:00:00Z"
end: "2025-05-29T23:59:59Z"
network_iocs:
- "api.devnet.solana.com"
solana_iocs:
memo_program_id: "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
actor_public_key: "D782zqWjgSvy4hQoqzY1ySrGrotnXm1suJeXFur8sAko"
crypto_iocs:
rsa_public_key_fingerprint_sha256: "5a4d8480c9d1e82ba102f200258882fb9e694e8fc0343b6982c5540beccdca62"
credentials_at_risk:
- "Solana private keys created or imported through patched solders Keypair constructors"
Source Confidence & Evidence Mapping
- confirmed: Socket identified
semantic-typesas the core malicious package andsolana-keypair,solana-publickey,solana-mev-agent-py,solana-trading-bot, andsoltradeas dependent carrier packages [Source 1]. - confirmed: Socket states
semantic-types 0.1.5introduced the malicious payload on January 26, 2025 and0.1.6repackaged it on January 28, 2025 [Source 1]. - confirmed: Socket identified the monkey-patched methods
Keypair.from_seed,Keypair.from_bytes, andKeypair.from_base58_string, the Solana Devnet RPC endpoint, the threat actor public key, the PyPI alias/email, and RSA public-key fingerprint [Source 1]. - unclear: Public sources do not name victim wallets or prove which Devnet memo transactions correspond to real production keypairs.
- not_observed: No evidence indicates compromise of the legitimate
solderspackage itself.
Impact Determination
| Classification | Criteria | Evidence to collect | Handling decision |
|---|---|---|---|
| Confirmed compromise | A malicious package was installed and a patched solders.keypair.Keypair constructor generated or imported a real Solana keypair. | Installed package/version evidence, code path invoking Keypair.from_seed, Keypair.from_bytes, or Keypair.from_base58_string, Solana Devnet RPC sendTransaction, memo program use, actor public key D782zqWjgSvy4hQoqzY1ySrGrotnXm1suJeXFur8sAko. | Treat keypairs created or imported through the process as exposed; move funds and authority to keys generated in a clean environment. |
| Presumed exposed | semantic-types==0.1.5 or 0.1.6, or any carrier package, was installed in an environment that runs Solana key generation/import code, but runtime telemetry is missing. | pip freeze, lockfile, package cache, source grep for patched methods, notebook history, CI output. | Inventory all keypairs generated or imported by that environment after January 26, 2025 and replace them from clean tooling. |
| Potentially exposed | A carrier package appears in manifests or lockfiles, but installation or malicious dependency resolution is incomplete. | Dependency graph, package proxy records, lockfile history, virtualenv/container inventory. | Collect package-cache and environment evidence until the asset is classified. |
| Not exposed | No malicious package, carrier package, affected version, Solana keypair constructor, Devnet RPC, memo program, or actor key appears in source, environments, caches, or telemetry. | Negative repository search, package inventory, dependency lock, virtualenv/container search, and network telemetry search. | Keep negative evidence with the case record and close this event for the asset. |
| Unknown | Package inventory, dependency resolution history, endpoint data, source history, or Solana RPC telemetry is unavailable. | Named telemetry gap with system, owner, and retention status. | Keep Solana key material in scope until the missing evidence is recovered or key replacement is accepted as the closure path. |
Minimum Evidence To Collect
package_evidence:
- "semantic-types==0.1.5"
- "semantic-types==0.1.6"
- "solana-keypair"
- "solana-publickey"
- "solana-mev-agent-py"
- "solana-trading-bot"
- "soltrade"
code_evidence:
- "solders.keypair.Keypair.from_seed"
- "solders.keypair.Keypair.from_bytes"
- "solders.keypair.Keypair.from_base58_string"
network_evidence:
- "api.devnet.solana.com"
- "sendTransaction"
- "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
- "D782zqWjgSvy4hQoqzY1ySrGrotnXm1suJeXFur8sAko"
crypto_evidence:
- "5a4d8480c9d1e82ba102f200258882fb9e694e8fc0343b6982c5540beccdca62"
Timeline
- 2024-12-22: Socket reports benign
semantic-types 0.1.2andsolana-trading-bot 0.1.0were published [Source 1]. - 2024-12-23: Socket reports updates for
semantic-types 0.1.4,solana-trading-bot 0.1.1, andsoltrade 0.1.1with dependencies still benign [Source 1]. - 2025-01-26: Socket reports
semantic-types 0.1.5introduced the malicious monkey-patching payload;solana-mev-agent-py 0.1.0andsolana-keypair 0.1.0were also published [Source 1]. - 2025-01-28: Socket reports
semantic-types 0.1.6repackaged the same malicious payload [Source 1]. - 2025-02-04: Socket reports
solana-keypair 0.2.1andsolana-publickey 0.2.1were released and importedsemantic-types[Source 1]. - 2025-05-29: Socket published the public analysis with IOCs and the package cluster [Source 1].
What Happened
The actor used dependency relationships rather than a one-package-only lure. semantic-types contained the payload, while five Solana-themed packages depended on it and served as delivery paths [Source 1].
Once imported, the payload modified methods on solders.keypair.Keypair at runtime. Calls to from_seed, from_bytes, and from_base58_string still returned a usable keypair, but the wrapper also sent key material to the attacker’s collection path through a background thread [Source 1].
The exfiltration path used normal Solana Devnet RPC. Captured private key bytes were encrypted with the actor’s RSA public key, base64 encoded, embedded in an SPL Memo transaction, and broadcast to Devnet. The attacker could later read memo transactions associated with the actor public key and decrypt ciphertext offline [Source 1].
Technical Analysis
Package Manipulation
payload_package: "semantic-types"
malicious_versions:
- "0.1.5"
- "0.1.6"
carrier_packages:
- "solana-keypair"
- "solana-publickey"
- "solana-mev-agent-py"
- "solana-trading-bot"
- "soltrade"
dependency_trigger: "pip resolves semantic-types from carrier package dependencies"
Runtime Hook
The hook targets the solders keypair class object already used by Solana Python developers. Monkey patching means source code that imports solders can look normal while runtime method dispatch has changed inside the interpreter [Source 1].
Exfiltration
transport:
protocol: "Solana JSON-RPC over HTTPS"
endpoint: "https://api.devnet.solana.com"
rpc_method: "sendTransaction"
program: "SPL Memo"
memo_program_id: "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
actor_public_key: "D782zqWjgSvy4hQoqzY1ySrGrotnXm1suJeXFur8sAko"
payload_encoding:
key_material: "64-byte private key material from Keypair bytes"
encryption: "RSA-2048 public key"
encoding: "Base64 ciphertext in memo data"
Affected Assets and Blast Radius
affected_assets:
ecosystems:
- "PyPI"
packages:
- "semantic-types==0.1.5"
- "semantic-types==0.1.6"
- "solana-keypair"
- "solana-publickey"
- "solana-mev-agent-py"
- "solana-trading-bot"
- "soltrade"
environments:
- "developer virtualenvs"
- "CI jobs"
- "Jupyter notebooks"
- "container images"
- "Solana bots and trading tools"
secret_material:
- "Solana private keys generated by patched methods"
- "Solana private keys imported through patched methods"
not_currently_known_to_affect:
- "the solders package itself"
- "Solana keypairs generated in clean environments without the malicious packages"
Indicators of Compromise
package_versions:
- "semantic-types==0.1.5"
- "semantic-types==0.1.6"
- "solana-keypair"
- "solana-publickey"
- "solana-mev-agent-py"
- "solana-trading-bot"
- "soltrade"
domains:
- "api.devnet.solana.com"
urls:
- "https://api.devnet.solana.com"
solana:
memo_program_id: "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
actor_public_key: "D782zqWjgSvy4hQoqzY1ySrGrotnXm1suJeXFur8sAko"
crypto:
rsa_public_key_fingerprint_sha256: "5a4d8480c9d1e82ba102f200258882fb9e694e8fc0343b6982c5540beccdca62"
python_symbols:
- "solders.keypair.Keypair.from_seed"
- "solders.keypair.Keypair.from_bytes"
- "solders.keypair.Keypair.from_base58_string"
Detection and Hunting
Script: local repository and exported telemetry scope
#!/usr/bin/env python3
import os
import sys
import json
import subprocess
from pathlib import Path
ROOT = sys.argv[1] if len(sys.argv) > 1 else "."
LOG_ROOT = os.environ.get("LOG_ROOT", "")
OUT = Path(os.environ.get("OUT", "hp-semantic-types-pypi-solana-monkey-patch-scope"))
SINCE = "2025-01-26T00:00:00Z"
UNTIL = "2025-05-29T23:59:59Z"
PACKAGES = [
]
VERSIONS = [
"0.1.5",
"0.1.6",
"semantic-types==0.1.5",
"semantic-types==0.1.6",
"solana-keypair",
"solana-publickey",
"solana-mev-agent-py",
"solana-trading-bot",
"soltrade",
]
FILES = [
]
DOMAINS = [
"api.devnet.solana.com",
"solders.keypair.Keypair",
]
URLS = [
"https://api.devnet.solana.com",
]
IPS = [
]
HASHES = [
"5a4d8480c9d1e82ba102f200258882fb9e694e8fc0343b6982c5540beccdca62",
]
PROCESS_PATTERNS = [
]
NETWORK_PATTERNS = [
]
# Positive signal: repository, lockfile, artifact, process, or network telemetry contains one of the exact incident selectors above.
# Escalation: any match tied to a production build, CI run, deployed asset, or secret-bearing host moves the asset to presumed exposed.
OUT.mkdir(parents=True, exist_ok=True)
indicators_file = OUT / "indicators.txt"
# Collect unique indicators
indicators = set()
for group in [PACKAGES, VERSIONS, FILES, DOMAINS, URLS, IPS, HASHES, PROCESS_PATTERNS, NETWORK_PATTERNS]:
for val in group:
if val:
indicators.add(val)
with open(indicators_file, "w") as f:
for ind in sorted(indicators):
f.write(ind + "\n")
print(f"[+] Written unique selectors to {indicators_file}")
# Walk local directory
print(f"[+] Scanning directory: {ROOT} for selectors...")
matches = []
exclude_dirs = {"node_modules", "vendor", "dist", ".git"}
for root, dirs, filenames in os.walk(ROOT):
dirs[:] = [d for d in dirs if d not in exclude_dirs]
for filename in filenames:
filepath = Path(root) / filename
try:
content = filepath.read_text(errors="ignore")
for ind in indicators:
if ind in content:
matches.append(f"{filepath}: found '{ind}'")
except Exception:
pass
if matches:
(OUT / "repository-indicator-matches.txt").write_text("\n".join(matches) + "\n")
print(f"[!] Found {len(matches)} matches in codebase!")
# Optional Log Scanning
if LOG_ROOT and os.path.exists(LOG_ROOT):
print(f"[+] Scanning telemetry log directory: {LOG_ROOT}...")
log_matches = []
for root, _, filenames in os.walk(LOG_ROOT):
for filename in filenames:
filepath = Path(root) / filename
try:
content = filepath.read_text(errors="ignore")
for ind in indicators:
if ind in content:
log_matches.append(f"{filepath}: found '{ind}'")
except Exception:
pass
if log_matches:
(OUT / "exported-telemetry-indicator-matches.txt").write_text("\n".join(log_matches) + "\n")
print(f"[!] Found {len(log_matches)} matches in logs!")
if PACKAGES:
registry_dir = OUT / "registry"
registry_dir.mkdir(exist_ok=True)
for package in PACKAGES:
if not package: continue
safe_name = package.replace("/", "__")
print(f"[+] Querying pip index for {package}...")
res = subprocess.run(["python3", "-m", "pip", "index", "versions", package], capture_output=True, text=True)
if res.returncode == 0:
(registry_dir / f"pypi-{safe_name}-versions.txt").write_text(res.stdout)
subprocess.run(["python3", "-m", "pip", "download", "--no-deps", package, "-d", str(registry_dir)], capture_output=True)
print(f"[+] Wrote scope artifacts under {OUT}")
Downstream Abuse Audits
Script: GitHub organization run, release, secret, and workflow audit
#!/usr/bin/env python3
import os
import sys
import json
import subprocess
from pathlib import Path
if "ORG" not in os.environ:
print("ERROR: Set ORG environment variable to the GitHub organization to audit", file=sys.stderr)
sys.exit(1)
ORG = os.environ["ORG"]
SINCE = "2025-01-26T00:00:00Z"
UNTIL = "2025-05-29T23:59:59Z"
OUT = Path(os.environ.get("OUT", "hp-semantic-types-pypi-solana-monkey-patch-github-audit"))
SELECTORS = [
"0.1.5",
"0.1.6",
"semantic-types==0.1.5",
"semantic-types==0.1.6",
"solana-keypair",
"solana-publickey",
"solana-mev-agent-py",
"solana-trading-bot",
"soltrade",
"api.devnet.solana.com",
"solders.keypair.Keypair",
"https://api.devnet.solana.com",
"5a4d8480c9d1e82ba102f200258882fb9e694e8fc0343b6982c5540beccdca62",
]
# Positive signal: a workflow run, release, secret, key, package, or workflow change overlaps the exposure window and references an incident selector.
# Remediation trigger: unauthorized post-exposure write activity or a secret-bearing run matching an incident selector requires token revocation and downstream cloud/registry review.
OUT.mkdir(parents=True, exist_ok=True)
(OUT / "runs").mkdir(exist_ok=True)
(OUT / "logs").mkdir(exist_ok=True)
(OUT / "repos").mkdir(exist_ok=True)
# 1. Write incident-selectors file
selectors_file = OUT / "incident-selectors.txt"
with open(selectors_file, "w") as sf:
for s in SELECTORS:
if s:
sf.write(s + "\n")
# 2. Get list of repos
print(f"[+] Fetching repositories for organization: {ORG}")
repo_res = subprocess.run(["gh", "repo", "list", ORG, "--limit", "1000", "--json", "nameWithOwner"], capture_output=True, text=True)
if repo_res.returncode != 0:
print(f"[-] Failed to fetch repos: {repo_res.stderr}", file=sys.stderr)
sys.exit(1)
repos = [r["nameWithOwner"] for r in json.loads(repo_res.stdout)]
for repo in repos:
safe_repo = repo.replace("/", "__")
print(f"[+] Auditing repository: {repo}")
# Check runs in the window
runs_res = subprocess.run([
"gh", "api", f"/repos/{repo}/actions/runs",
"-f", "per_page=100",
"-f", f"created=>={SINCE}",
"--paginate"
], capture_output=True, text=True)
if runs_res.returncode == 0:
try:
all_runs = json.loads(runs_res.stdout).get("workflow_runs", [])
filtered_runs = [r for r in all_runs if r["created_at"] <= UNTIL]
if filtered_runs:
with open(OUT / "runs" / f"{safe_repo}-runs.jsonl", "w") as rf:
for run in filtered_runs:
rf.write(json.dumps(run) + "\n")
# Fetch log dynamically
run_id = str(run["id"])
log_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--log"], capture_output=True, text=True)
if log_res.returncode == 0:
(OUT / "logs" / f"{safe_repo}-{run_id}.log").write_text(log_res.stdout)
# Fetch details
view_res = subprocess.run(["gh", "run", "view", run_id, "--repo", repo, "--json", "databaseId,workflowName,headSha,event,createdAt,jobs"], capture_output=True, text=True)
if view_res.returncode == 0:
(OUT / "runs" / f"{safe_repo}-{run_id}.json").write_text(view_res.stdout)
except Exception as e:
print(f"[-] Error parsing runs for {repo}: {e}")
# Check releases in window
subprocess.run(["gh", "api", f"/repos/{repo}/releases", "-f", "per_page=100", "--paginate"], capture_output=True)
# Check repo secrets updated in window
subprocess.run(["gh", "api", f"/repos/{repo}/actions/secrets", "-f", "per_page=100", "--paginate"], capture_output=True)
# Check deploy keys
subprocess.run(["gh", "api", f"/repos/{repo}/keys", "-f", "per_page=100", "--paginate"], capture_output=True)
# Scan output directory for any indicator selector matches
print("[+] Scanning gathered telemetry for indicator matches...")
subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(selectors_file), str(OUT)], capture_output=False)
print(f"[+] Wrote GitHub audit artifacts under {OUT}")
Script: cloud OIDC and deployment credential follow-on audit
#!/usr/bin/env python3
import os
import json
import subprocess
from pathlib import Path
SINCE = "2025-01-26T00:00:00Z"
UNTIL = "2025-05-29T23:59:59Z"
OUT = Path(os.environ.get("OUT", "hp-semantic-types-pypi-solana-monkey-patch-cloud-audit"))
AWS_REGIONS = os.environ.get("AWS_REGIONS", "us-east-1").split(",")
# Positive signal: token exchange or privileged write activity occurs in the exposure window from GitHub, CI/CD, package registry, or deployment automation identity.
# Remediation trigger: unexpected write, deploy, IAM, secret, or registry activity tied to an exposed CI/CD path requires trust-policy disablement and credential rotation.
OUT.mkdir(parents=True, exist_ok=True)
# 1. AWS CloudTrail Audit
print("[+] Querying AWS CloudTrail for Web Identity token exchanges...")
aws_events = []
for region in AWS_REGIONS:
res = subprocess.run([
"aws", "cloudtrail", "lookup-events",
"--region", region,
"--start-time", SINCE,
"--end-time", UNTIL,
"--lookup-attributes", "AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity",
"--output", "json"
], capture_output=True, text=True)
if res.returncode == 0:
try:
events = json.loads(res.stdout).get("Events", [])
for event in events:
ct = json.loads(event.get("CloudTrailEvent", "{}"))
ct["region"] = region
aws_events.append(ct)
except Exception as e:
print(f"[-] Error parsing AWS CloudTrail events: {e}")
if aws_events:
with open(OUT / "aws-assume-role-with-web-identity.jsonl", "w") as f:
for ev in aws_events:
f.write(json.dumps(ev) + "\n")
# Audit follow-on events for returned access keys
for ev in aws_events:
access_key = ev.get("responseElements", {}).get("credentials", {}).get("accessKeyId")
region = ev.get("region", "us-east-1")
if access_key:
print(f"[+] Enumerating AWS events for AccessKey: {access_key}")
f_res = subprocess.run([
"aws", "cloudtrail", "lookup-events",
"--region", region,
"--start-time", SINCE,
"--end-time", UNTIL,
"--lookup-attributes", f"AttributeKey=AccessKeyId,AttributeValue={access_key}",
"--output", "json"
], capture_output=True, text=True)
if f_res.returncode == 0:
try:
f_events = json.loads(f_res.stdout).get("Events", [])
with open(OUT / "aws-follow-on-api-calls.jsonl", "a") as ff:
for fe in f_events:
ff.write(fe.get("CloudTrailEvent", "{}") + "\n")
except Exception as e:
print(f"[-] Error writing follow-on events: {e}")
# 2. Azure Activity Log Audit
print("[+] Querying Azure activity logs...")
az_res = subprocess.run([
"az", "monitor", "activity-log", "list",
"--start-time", SINCE,
"--end-time", UNTIL,
"--query", "[?contains(operationName.value, 'write') || contains(operationName.value, 'delete') || contains(operationName.value, 'Microsoft.ManagedIdentity')]",
"-o", "json"
], capture_output=True, text=True)
if az_res.returncode == 0:
(OUT / "azure-write-delete-activity.json").write_text(az_res.stdout)
# 3. GCP Logging Audit
print("[+] Querying GCP Cloud Logging...")
gcp_filter = f'timestamp>="{SINCE}" AND timestamp<="{UNTIL}" AND (protoPayload.methodName="google.sts.v1.SecurityTokenService.ExchangeToken" OR protoPayload.methodName:"GenerateAccessToken" OR protoPayload.methodName:"CreateServiceAccountKey" OR protoPayload.methodName:"SetIamPolicy")'
gcp_res = subprocess.run([
"gcloud", "logging", "read", gcp_filter,
"--format", "json"
], capture_output=True, text=True)
if gcp_res.returncode == 0:
(OUT / "gcp-token-and-iam-activity.json").write_text(gcp_res.stdout)
print(f"[+] Wrote cloud audit artifacts under {OUT}")
Script: registry metadata and artifact audit
#!/usr/bin/env python3
import os
import json
import subprocess
from pathlib import Path
SINCE = "2025-01-26T00:00:00Z"
OUT = Path(os.environ.get("OUT", "hp-semantic-types-pypi-solana-monkey-patch-registry-audit"))
PACKAGES = [
]
VERSIONS = [
"0.1.5",
"0.1.6",
"semantic-types==0.1.5",
"semantic-types==0.1.6",
"solana-keypair",
"solana-publickey",
"solana-mev-agent-py",
"solana-trading-bot",
"soltrade",
]
# Positive signal: registry metadata, package tarballs, or cached artifacts contain the exact affected package/version values.
# Remediation trigger: any internal package cache, build artifact, or deployment using these package/version values requires exposure scoping.
OUT.mkdir(parents=True, exist_ok=True)
with open(OUT / "affected-versions.txt", "w") as av:
for version in VERSIONS:
if version:
av.write(version + "\n")
# 1. Audit PyPI dependencies in project files
print("[+] Scanning PyPI dependency files...")
for file in ["requirements.txt", "poetry.lock", "Pipfile.lock", "pyproject.toml", "setup.py"]:
if Path(file).exists():
subprocess.run(["rg", "-n", "--hidden", "--fixed-strings", "-f", str(OUT / "affected-versions.txt"), file])
# 2. Query registry metadata and download packages for local analysis
packages_dir = OUT / "packages"
metadata_dir = OUT / "metadata"
packages_dir.mkdir(exist_ok=True)
metadata_dir.mkdir(exist_ok=True)
for package in PACKAGES:
if not package: continue
print(f"[+] Querying pip index for {package}...")
res = subprocess.run(["python3", "-m", "pip", "index", "versions", package], capture_output=True, text=True)
if res.returncode == 0:
(metadata_dir / f"{package}-versions.txt").write_text(res.stdout)
subprocess.run(["python3", "-m", "pip", "download", "--no-deps", package, "-d", str(packages_dir)], capture_output=True)
# 3. HOW TO REVOKE AND ROTATE EXPOSED PYPI PUBLISHING TOKENS:
# PyPI does not support token revocation via CLI. Follow these exact steps:
# 1. Log in to https://pypi.org/manage/account/
# 2. Scroll to the "API tokens" section and click "Remove" on any compromised tokens.
# 3. Generate a new API token limited to the specific project scope.
# 4. Update your CI/CD secrets using the GitHub CLI:
# subprocess.run(["gh", "secret", "set", "PYPI_API_TOKEN", "--body", "pypi-AgEIcHlwaS5vcm...", "--repo", "my-org/my-repo"])
print(f"[+] Wrote registry audit artifacts under {OUT}")
Sources
- Socket: Monkey-Patched PyPI Packages Use Transitive Dependencies to Steal Solana Private Keys - Role: PRIMARY_RESEARCH - Impact: Package cluster, malicious versions, payload behavior, patched methods, Solana Devnet exfiltration, actor key, RSA fingerprint, and PyPI publisher identity.
IOC Clipboard
4 IOCsapi.devnet.solana.com api[.]devnet[.]solana[.]com solders.keypair.Keypair solders[.]keypair[.]Keypair https://api.devnet.solana.com hxxps://api[.]devnet[.]solana[.]com 5a4d8480c9d1e82ba102f200258882fb9e694e8fc0343b6982c5540beccdca62 5a4d8480c9d1e82ba102f200258882fb9e694e8fc0343b6982c5540beccdca62