mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-18 05:29:40 +03:00
Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
This commit is contained in:
@@ -1,61 +1,272 @@
|
||||
#!/usr/bin/env python3
|
||||
"""HashiCorp Vault secrets management audit."""
|
||||
import argparse, json, sys
|
||||
"""HashiCorp Vault secrets management agent.
|
||||
|
||||
Manages secrets lifecycle using the HashiCorp Vault KV v2 secrets engine
|
||||
via the hvac Python library. Supports reading, writing, listing, deleting
|
||||
secrets, and auditing secret access patterns.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
|
||||
try:
|
||||
import requests
|
||||
import hvac
|
||||
except ImportError:
|
||||
requests = None
|
||||
print("[!] 'hvac' library required: pip install hvac", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def audit_config(target, token):
|
||||
findings = []
|
||||
if not requests: return [{"error": "requests required"}]
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
try:
|
||||
resp = requests.get(f"{target}/api/v1/status", headers=headers, timeout=10)
|
||||
if resp.status_code == 200:
|
||||
data = resp.json()
|
||||
if not data.get("enabled", True):
|
||||
findings.append({"check": "Service Status", "status": "DISABLED", "severity": "CRITICAL"})
|
||||
elif resp.status_code == 401:
|
||||
findings.append({"check": "Authentication", "status": "UNAUTHORIZED", "severity": "HIGH"})
|
||||
except requests.RequestException as e:
|
||||
findings.append({"error": str(e)})
|
||||
return findings
|
||||
|
||||
def check_compliance(target, token):
|
||||
findings = []
|
||||
if not requests: return []
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
def get_vault_client(addr=None, token=None, namespace=None):
|
||||
"""Create and authenticate a Vault client."""
|
||||
vault_addr = addr or os.environ.get("VAULT_ADDR", "http://127.0.0.1:8200")
|
||||
vault_token = token or os.environ.get("VAULT_TOKEN", "")
|
||||
vault_ns = namespace or os.environ.get("VAULT_NAMESPACE")
|
||||
|
||||
if not vault_token:
|
||||
print("[!] Set VAULT_TOKEN env var or use --token", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
client = hvac.Client(url=vault_addr, token=vault_token, namespace=vault_ns)
|
||||
if not client.is_authenticated():
|
||||
print("[!] Vault authentication failed", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(f"[+] Connected to Vault at {vault_addr}")
|
||||
return client
|
||||
|
||||
|
||||
def write_secret(client, path, data, mount_point="secret"):
|
||||
"""Write or update a secret in KV v2."""
|
||||
print(f"[*] Writing secret to {mount_point}/{path}")
|
||||
response = client.secrets.kv.v2.create_or_update_secret(
|
||||
path=path, secret=data, mount_point=mount_point,
|
||||
)
|
||||
version = response.get("data", {}).get("version", "unknown")
|
||||
print(f"[+] Secret written (version: {version})")
|
||||
return {"path": path, "version": version, "action": "write"}
|
||||
|
||||
|
||||
def read_secret(client, path, mount_point="secret", version=None):
|
||||
"""Read a secret from KV v2."""
|
||||
print(f"[*] Reading secret from {mount_point}/{path}")
|
||||
kwargs = {"path": path, "mount_point": mount_point}
|
||||
if version:
|
||||
kwargs["version"] = version
|
||||
response = client.secrets.kv.v2.read_secret_version(**kwargs)
|
||||
secret_data = response.get("data", {}).get("data", {})
|
||||
metadata = response.get("data", {}).get("metadata", {})
|
||||
print(f"[+] Secret read (version: {metadata.get('version', '?')}, "
|
||||
f"created: {metadata.get('created_time', 'unknown')})")
|
||||
masked = {k: v[:3] + "***" if isinstance(v, str) and len(v) > 3 else "***"
|
||||
for k, v in secret_data.items()}
|
||||
return {
|
||||
"path": path,
|
||||
"keys": list(secret_data.keys()),
|
||||
"masked_values": masked,
|
||||
"version": metadata.get("version"),
|
||||
"created_time": metadata.get("created_time"),
|
||||
"deletion_time": metadata.get("deletion_time"),
|
||||
"destroyed": metadata.get("destroyed"),
|
||||
}
|
||||
|
||||
|
||||
def list_secrets(client, path="", mount_point="secret"):
|
||||
"""List secret paths under a given prefix."""
|
||||
print(f"[*] Listing secrets under {mount_point}/{path}")
|
||||
try:
|
||||
resp = requests.get(f"{target}/api/v1/compliance", headers=headers, timeout=10)
|
||||
if resp.status_code == 200:
|
||||
for item in resp.json().get("checks", []):
|
||||
if item.get("status") != "PASS":
|
||||
findings.append({"check": item.get("name"), "status": item.get("status"),
|
||||
"severity": item.get("severity", "MEDIUM")})
|
||||
except requests.RequestException:
|
||||
pass
|
||||
return findings
|
||||
response = client.secrets.kv.v2.list_secrets(
|
||||
path=path, mount_point=mount_point
|
||||
)
|
||||
keys = response.get("data", {}).get("keys", [])
|
||||
print(f"[+] Found {len(keys)} entries")
|
||||
for key in keys:
|
||||
marker = "/" if key.endswith("/") else " "
|
||||
print(f" {marker} {key}")
|
||||
return {"path": path, "entries": keys, "count": len(keys)}
|
||||
except hvac.exceptions.InvalidPath:
|
||||
print(f"[*] No secrets found at {mount_point}/{path}")
|
||||
return {"path": path, "entries": [], "count": 0}
|
||||
|
||||
|
||||
def delete_secret(client, path, mount_point="secret", versions=None, destroy=False):
|
||||
"""Delete or destroy a secret."""
|
||||
if destroy and versions:
|
||||
print(f"[*] Permanently destroying versions {versions} at {mount_point}/{path}")
|
||||
client.secrets.kv.v2.destroy_secret_versions(
|
||||
path=path, versions=versions, mount_point=mount_point
|
||||
)
|
||||
print(f"[+] Versions {versions} permanently destroyed")
|
||||
return {"path": path, "action": "destroy", "versions": versions}
|
||||
elif versions:
|
||||
print(f"[*] Soft-deleting versions {versions} at {mount_point}/{path}")
|
||||
client.secrets.kv.v2.delete_secret_versions(
|
||||
path=path, versions=versions, mount_point=mount_point
|
||||
)
|
||||
print(f"[+] Versions {versions} soft-deleted (recoverable)")
|
||||
return {"path": path, "action": "soft_delete", "versions": versions}
|
||||
else:
|
||||
print(f"[*] Deleting latest version at {mount_point}/{path}")
|
||||
client.secrets.kv.v2.delete_latest_version_of_secret(
|
||||
path=path, mount_point=mount_point
|
||||
)
|
||||
print(f"[+] Latest version deleted")
|
||||
return {"path": path, "action": "delete_latest"}
|
||||
|
||||
|
||||
def read_metadata(client, path, mount_point="secret"):
|
||||
"""Read secret metadata including version history."""
|
||||
print(f"[*] Reading metadata for {mount_point}/{path}")
|
||||
response = client.secrets.kv.v2.read_secret_metadata(
|
||||
path=path, mount_point=mount_point
|
||||
)
|
||||
meta = response.get("data", {})
|
||||
versions = meta.get("versions", {})
|
||||
print(f"[+] {len(versions)} version(s), max_versions: {meta.get('max_versions', 0)}")
|
||||
version_info = []
|
||||
for ver_num, ver_data in sorted(versions.items(), key=lambda x: int(x[0])):
|
||||
version_info.append({
|
||||
"version": int(ver_num),
|
||||
"created_time": ver_data.get("created_time"),
|
||||
"deletion_time": ver_data.get("deletion_time"),
|
||||
"destroyed": ver_data.get("destroyed", False),
|
||||
})
|
||||
return {
|
||||
"path": path,
|
||||
"current_version": meta.get("current_version"),
|
||||
"max_versions": meta.get("max_versions"),
|
||||
"cas_required": meta.get("cas_required"),
|
||||
"created_time": meta.get("created_time"),
|
||||
"updated_time": meta.get("updated_time"),
|
||||
"versions": version_info,
|
||||
}
|
||||
|
||||
|
||||
def audit_secrets(client, mount_point="secret", path_prefix=""):
|
||||
"""Audit all secrets under a path, reporting metadata and age."""
|
||||
print(f"[*] Auditing secrets under {mount_point}/{path_prefix}")
|
||||
audit_results = []
|
||||
|
||||
def _recurse(current_path):
|
||||
try:
|
||||
resp = client.secrets.kv.v2.list_secrets(
|
||||
path=current_path, mount_point=mount_point
|
||||
)
|
||||
except hvac.exceptions.InvalidPath:
|
||||
return
|
||||
keys = resp.get("data", {}).get("keys", [])
|
||||
for key in keys:
|
||||
full_path = f"{current_path}{key}" if current_path else key
|
||||
if key.endswith("/"):
|
||||
_recurse(full_path)
|
||||
else:
|
||||
try:
|
||||
meta_resp = client.secrets.kv.v2.read_secret_metadata(
|
||||
path=full_path, mount_point=mount_point
|
||||
)
|
||||
meta = meta_resp.get("data", {})
|
||||
audit_results.append({
|
||||
"path": full_path,
|
||||
"current_version": meta.get("current_version"),
|
||||
"created_time": meta.get("created_time"),
|
||||
"updated_time": meta.get("updated_time"),
|
||||
"version_count": len(meta.get("versions", {})),
|
||||
})
|
||||
except Exception as e:
|
||||
audit_results.append({"path": full_path, "error": str(e)})
|
||||
|
||||
_recurse(path_prefix)
|
||||
print(f"[+] Audited {len(audit_results)} secret(s)")
|
||||
for item in audit_results:
|
||||
if "error" in item:
|
||||
print(f" [ERR] {item['path']}: {item['error']}")
|
||||
else:
|
||||
print(f" {item['path']:40s} v{item['current_version']} "
|
||||
f"(updated: {str(item.get('updated_time', 'N/A'))[:19]})")
|
||||
return audit_results
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser(description="HashiCorp Vault secrets management audit")
|
||||
p.add_argument("--target", required=True, help="Target URL")
|
||||
p.add_argument("--token", required=True, help="API token")
|
||||
p.add_argument("--output", "-o", help="Output JSON report")
|
||||
p.add_argument("--verbose", "-v", action="store_true")
|
||||
a = p.parse_args()
|
||||
print("[*] HashiCorp Vault secrets management audit")
|
||||
report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": []}
|
||||
report["findings"].extend(audit_config(a.target, a.token))
|
||||
report["findings"].extend(check_compliance(a.target, a.token))
|
||||
high = sum(1 for f in report["findings"] if f.get("severity") in ("HIGH", "CRITICAL"))
|
||||
report["risk_level"] = "HIGH" if high else "MEDIUM" if report["findings"] else "LOW"
|
||||
print(f"[*] {len(report['findings'])} findings, risk: {report['risk_level']}")
|
||||
if a.output:
|
||||
with open(a.output, "w") as f: json.dump(report, f, indent=2)
|
||||
parser = argparse.ArgumentParser(
|
||||
description="HashiCorp Vault secrets management agent"
|
||||
)
|
||||
sub = parser.add_subparsers(dest="command", help="Action to perform")
|
||||
|
||||
p_write = sub.add_parser("write", help="Write a secret")
|
||||
p_write.add_argument("--path", required=True, help="Secret path")
|
||||
p_write.add_argument("--data", required=True, help='JSON data')
|
||||
p_write.add_argument("--mount", default="secret", help="KV mount point")
|
||||
|
||||
p_read = sub.add_parser("read", help="Read a secret")
|
||||
p_read.add_argument("--path", required=True)
|
||||
p_read.add_argument("--version", type=int, help="Specific version")
|
||||
p_read.add_argument("--mount", default="secret")
|
||||
|
||||
p_list = sub.add_parser("list", help="List secrets")
|
||||
p_list.add_argument("--path", default="")
|
||||
p_list.add_argument("--mount", default="secret")
|
||||
|
||||
p_del = sub.add_parser("delete", help="Delete a secret")
|
||||
p_del.add_argument("--path", required=True)
|
||||
p_del.add_argument("--versions", type=int, nargs="+")
|
||||
p_del.add_argument("--destroy", action="store_true")
|
||||
p_del.add_argument("--mount", default="secret")
|
||||
|
||||
p_meta = sub.add_parser("metadata", help="Read secret metadata")
|
||||
p_meta.add_argument("--path", required=True)
|
||||
p_meta.add_argument("--mount", default="secret")
|
||||
|
||||
p_audit = sub.add_parser("audit", help="Audit all secrets")
|
||||
p_audit.add_argument("--path", default="")
|
||||
p_audit.add_argument("--mount", default="secret")
|
||||
|
||||
parser.add_argument("--addr", help="Vault address (or VAULT_ADDR env)")
|
||||
parser.add_argument("--token", help="Vault token (or VAULT_TOKEN env)")
|
||||
parser.add_argument("--namespace", help="Vault namespace")
|
||||
parser.add_argument("--output", "-o", help="Output JSON report")
|
||||
parser.add_argument("--verbose", "-v", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
client = get_vault_client(args.addr, args.token, args.namespace)
|
||||
|
||||
if args.command == "write":
|
||||
secret_data = json.loads(args.data)
|
||||
result = write_secret(client, args.path, secret_data, args.mount)
|
||||
elif args.command == "read":
|
||||
result = read_secret(client, args.path, args.mount,
|
||||
getattr(args, "version", None))
|
||||
elif args.command == "list":
|
||||
result = list_secrets(client, args.path, args.mount)
|
||||
elif args.command == "delete":
|
||||
result = delete_secret(client, args.path, args.mount,
|
||||
getattr(args, "versions", None),
|
||||
getattr(args, "destroy", False))
|
||||
elif args.command == "metadata":
|
||||
result = read_metadata(client, args.path, args.mount)
|
||||
elif args.command == "audit":
|
||||
result = audit_secrets(client, args.mount, args.path)
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
report = {
|
||||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||||
"tool": "HashiCorp Vault",
|
||||
"command": args.command,
|
||||
"result": result,
|
||||
}
|
||||
|
||||
if args.output:
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(report, f, indent=2)
|
||||
print(f"\n[+] Report saved to {args.output}")
|
||||
elif args.verbose:
|
||||
print(json.dumps(report, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user