mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-09-03 15:00:50 +03:00
Add 55 new skills across 3 new domains + 6 undercovered areas (762 -> 817)
Demand-driven expansion targeting the fastest-growing 2025-2026 threat and
skills categories (ISC2/WEF/CrowdStrike/Mandiant signals):
- AI Security (NEW domain, 12 skills): LLM red-teaming with garak/PyRIT,
prompt injection (direct/indirect/RAG), MCP tool-poisoning, agentic tool
invocation, guardrails, model/data poisoning, system-prompt leakage,
embedding/vector weaknesses, model extraction, continuous red-teaming
- Supply Chain Security (NEW domain, 5 skills): SBOMs, dependency confusion,
malicious-npm triage, typosquatting, SLSA/Sigstore provenance
- Hardware & Firmware Security (NEW domain, 4 skills): CHIPSEC/UEFI audit,
Secure Boot bypass, TPM measured-boot attestation, ESP bootkit hunting
- Identity (10): Entra ID/ROADtools, GraphRunner, AADInternals, ADCS/Certipy,
shadow credentials, coercion, BloodHound CE, device-code phishing, SSO abuse
- Cloud-native (8): Stratus, Pacu, CloudFox, container escape, K8s RBAC,
Falco, Trivy, kube-bench
- Offensive C2 (6): Sliver, Havoc, NetExec, DPAPI, NTLM relay ESC8, redirectors
- DFIR (6): Hayabusa, Chainsaw, KAPE, Velociraptor, EZ Tools, Plaso
- Backfill (4): OpenCTI, MISP, honeytokens, post-quantum crypto migration
Each skill follows the repo taxonomy (SKILL.md + references/{standards,api-reference}.md
+ scripts/agent.py + LICENSE), with researched real tool commands (no placeholders),
complete frontmatter, and ATT&CK/ATLAS + NIST CSF mappings. Updates README domain
table, skill count, and index.json.
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Typosquatting screening helper.
|
||||
|
||||
Generates the standard typogard/typomania mutation set for a candidate package
|
||||
name, screens names against a popular-name corpus, and enriches suspected
|
||||
squats with live registry metadata (npm / PyPI) to support triage.
|
||||
|
||||
No third-party dependencies required (stdlib only). Network calls use urllib.
|
||||
|
||||
Examples:
|
||||
# Screen a single name against a PyPI corpus
|
||||
python agent.py screen --ecosystem pypi --corpus top-pypi-packages.json --name reqeusts
|
||||
|
||||
# Screen names from stdin (e.g., a diff of requirements.txt)
|
||||
git diff | python agent.py screen --ecosystem npm --corpus npm-top.json --stdin
|
||||
|
||||
# Show the generated mutation set for a legit name
|
||||
python agent.py mutate --name requests
|
||||
|
||||
# Pull registry metadata for triage
|
||||
python agent.py enrich --ecosystem pypi --name reqeusts
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
QWERTY_ADJ = {
|
||||
"q": "wa", "w": "qeas", "e": "wrsd", "r": "etdf", "t": "ryfg",
|
||||
"y": "tugh", "u": "yihj", "i": "uojk", "o": "ipkl", "p": "ol",
|
||||
"a": "qwsz", "s": "awedxz", "d": "serfcx", "f": "drtgvc",
|
||||
"g": "ftyhbv", "h": "gyujnb", "j": "huikmn", "k": "jiolm",
|
||||
"l": "kop", "z": "asx", "x": "zsdc", "c": "xdfv", "v": "cfgb",
|
||||
"b": "vghn", "n": "bhjm", "m": "njk",
|
||||
"0": "9", "1": "2", "2": "13", "3": "24", "4": "35",
|
||||
}
|
||||
DELIMS = ["-", "_", ".", ""]
|
||||
|
||||
|
||||
def mutations(name):
|
||||
"""Reproduce the typogard/typomania squat primitives for `name`."""
|
||||
out = set()
|
||||
n = name.lower()
|
||||
# 1. omission
|
||||
for i in range(len(n)):
|
||||
out.add(n[:i] + n[i + 1:])
|
||||
# 2. repetition
|
||||
for i in range(len(n)):
|
||||
out.add(n[:i] + n[i] + n[i:])
|
||||
# 3. transposition / swap of adjacent chars
|
||||
for i in range(len(n) - 1):
|
||||
out.add(n[:i] + n[i + 1] + n[i] + n[i + 2:])
|
||||
# 4. keyboard-adjacency 1-edit substitution
|
||||
for i, ch in enumerate(n):
|
||||
for repl in QWERTY_ADJ.get(ch, ""):
|
||||
out.add(n[:i] + repl + n[i + 1:])
|
||||
# 5. delimiter swaps
|
||||
for d in DELIMS:
|
||||
for d2 in DELIMS:
|
||||
if d and d in n:
|
||||
out.add(n.replace(d, d2))
|
||||
# 6. word-order swap for compound names
|
||||
for d in ("-", "_", "."):
|
||||
if d in n:
|
||||
parts = n.split(d)
|
||||
if len(parts) == 2:
|
||||
out.add(d.join(reversed(parts)))
|
||||
# 7. common suffixes
|
||||
for suf in ("js", "py", "lib", "cli", "2", "-ng"):
|
||||
out.add(n + suf)
|
||||
out.add(n + "-" + suf)
|
||||
out.discard(n)
|
||||
return {m for m in out if m}
|
||||
|
||||
|
||||
def levenshtein(a, b):
|
||||
if len(a) < len(b):
|
||||
a, b = b, a
|
||||
prev = list(range(len(b) + 1))
|
||||
for i, ca in enumerate(a, 1):
|
||||
cur = [i]
|
||||
for j, cb in enumerate(b, 1):
|
||||
cur.append(min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (ca != cb)))
|
||||
prev = cur
|
||||
return prev[-1]
|
||||
|
||||
|
||||
def load_corpus(path):
|
||||
with open(path, "r", encoding="utf-8") as fh:
|
||||
data = json.load(fh)
|
||||
names = set()
|
||||
# Accept several common corpus shapes
|
||||
if isinstance(data, dict) and "rows" in data: # top-pypi-packages
|
||||
names = {r["project"] for r in data["rows"]}
|
||||
elif isinstance(data, dict) and "objects" in data: # npm search
|
||||
names = {o["package"]["name"] for o in data["objects"]}
|
||||
elif isinstance(data, list):
|
||||
names = {(x if isinstance(x, str) else x.get("name", "")) for x in data}
|
||||
elif isinstance(data, dict):
|
||||
names = set(data.keys())
|
||||
return {n.lower() for n in names if n}
|
||||
|
||||
|
||||
def http_json(url):
|
||||
req = urllib.request.Request(url, headers={"User-Agent": "typosquat-screen/1.0"})
|
||||
with urllib.request.urlopen(req, timeout=20) as resp:
|
||||
return json.loads(resp.read().decode("utf-8"))
|
||||
|
||||
|
||||
def enrich(ecosystem, name):
|
||||
"""Pull triage metadata from the live registry."""
|
||||
try:
|
||||
if ecosystem == "npm":
|
||||
d = http_json(f"https://registry.npmjs.org/{name}")
|
||||
latest = d.get("dist-tags", {}).get("latest")
|
||||
ver = d.get("versions", {}).get(latest, {})
|
||||
return {
|
||||
"name": name,
|
||||
"created": d.get("time", {}).get("created"),
|
||||
"maintainers": [m.get("name") for m in d.get("maintainers", [])],
|
||||
"scripts": ver.get("scripts", {}),
|
||||
"repository": ver.get("repository"),
|
||||
}
|
||||
elif ecosystem == "pypi":
|
||||
d = http_json(f"https://pypi.org/pypi/{name}/json")
|
||||
info = d.get("info", {})
|
||||
return {
|
||||
"name": name,
|
||||
"author": info.get("author"),
|
||||
"home_page": info.get("home_page"),
|
||||
"releases": list(d.get("releases", {}).keys()),
|
||||
"project_urls": info.get("project_urls"),
|
||||
}
|
||||
except urllib.error.HTTPError as exc:
|
||||
return {"name": name, "error": f"HTTP {exc.code} (name may not exist)"}
|
||||
except Exception as exc: # noqa: BLE001
|
||||
return {"name": name, "error": str(exc)}
|
||||
return {"name": name, "error": "unsupported ecosystem"}
|
||||
|
||||
|
||||
def screen_name(name, corpus, threshold=2):
|
||||
"""Return list of (legit_name, distance) the candidate squats on."""
|
||||
name = name.lower().strip()
|
||||
hits = []
|
||||
if name in corpus:
|
||||
return [] # exact legit match is not a squat
|
||||
for legit in corpus:
|
||||
d = levenshtein(name, legit)
|
||||
if 0 < d <= threshold and abs(len(name) - len(legit)) <= threshold:
|
||||
hits.append((legit, d))
|
||||
hits.sort(key=lambda x: x[1])
|
||||
return hits
|
||||
|
||||
|
||||
def cmd_mutate(args):
|
||||
for m in sorted(mutations(args.name)):
|
||||
print(m)
|
||||
|
||||
|
||||
def cmd_screen(args):
|
||||
corpus = load_corpus(args.corpus)
|
||||
candidates = []
|
||||
if args.stdin:
|
||||
for line in sys.stdin:
|
||||
for tok in line.replace('"', " ").replace(",", " ").split():
|
||||
tok = tok.strip("+-=:'`@^~>< \t")
|
||||
if tok and tok[0].isalpha():
|
||||
candidates.append(tok)
|
||||
if args.name:
|
||||
candidates.append(args.name)
|
||||
flagged = 0
|
||||
for cand in dict.fromkeys(candidates):
|
||||
hits = screen_name(cand, corpus, args.threshold)
|
||||
if hits:
|
||||
flagged += 1
|
||||
top = ", ".join(f"{l}(d={d})" for l, d in hits[:3])
|
||||
print(f"[FLAG] {cand} -> resembles {top}")
|
||||
print(f"\nScreened {len(set(candidates))} name(s), flagged {flagged}.", file=sys.stderr)
|
||||
sys.exit(1 if flagged else 0)
|
||||
|
||||
|
||||
def cmd_enrich(args):
|
||||
print(json.dumps(enrich(args.ecosystem, args.name), indent=2))
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description="Typosquatting screening helper")
|
||||
sub = p.add_subparsers(dest="cmd", required=True)
|
||||
|
||||
m = sub.add_parser("mutate", help="Print squat mutations of a name")
|
||||
m.add_argument("--name", required=True)
|
||||
m.set_defaults(func=cmd_mutate)
|
||||
|
||||
s = sub.add_parser("screen", help="Screen names against a popular-name corpus")
|
||||
s.add_argument("--corpus", required=True)
|
||||
s.add_argument("--ecosystem", choices=["npm", "pypi", "cargo"], default="pypi")
|
||||
s.add_argument("--name")
|
||||
s.add_argument("--stdin", action="store_true")
|
||||
s.add_argument("--threshold", type=int, default=2)
|
||||
s.set_defaults(func=cmd_screen)
|
||||
|
||||
e = sub.add_parser("enrich", help="Pull registry metadata for triage")
|
||||
e.add_argument("--ecosystem", choices=["npm", "pypi"], required=True)
|
||||
e.add_argument("--name", required=True)
|
||||
e.set_defaults(func=cmd_enrich)
|
||||
|
||||
args = p.parse_args()
|
||||
args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user