attackmap 0.6.4__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.
- attackmap/__init__.py +2 -0
- attackmap/analyzer.py +449 -0
- attackmap/analyzer_contracts.py +194 -0
- attackmap/analyzers.py +1003 -0
- attackmap/anomalies.py +612 -0
- attackmap/asset_model.py +245 -0
- attackmap/attack_taxonomy.py +133 -0
- attackmap/auth_protocol.py +702 -0
- attackmap/authz.py +1695 -0
- attackmap/bench.py +467 -0
- attackmap/cli.py +1474 -0
- attackmap/config_scanner.py +389 -0
- attackmap/context_pack.py +82 -0
- attackmap/contracts.py +201 -0
- attackmap/control_model.py +280 -0
- attackmap/crossrepo.py +356 -0
- attackmap/crypto.py +240 -0
- attackmap/cve.py +717 -0
- attackmap/defensive_review.py +647 -0
- attackmap/detection_opportunities.py +1221 -0
- attackmap/detections/__init__.py +491 -0
- attackmap/detections/bola.yml +57 -0
- attackmap/detections/cloud.yml +609 -0
- attackmap/detections/injection.yml +167 -0
- attackmap/detections/secrets.yml +48 -0
- attackmap/detections/sigma.py +511 -0
- attackmap/detections/ssrf.yml +98 -0
- attackmap/detections/weakness.yml +62 -0
- attackmap/detections/webhook.yml +45 -0
- attackmap/detections/workflow.yml +121 -0
- attackmap/diagrams.py +237 -0
- attackmap/diff.py +301 -0
- attackmap/exploitability.py +493 -0
- attackmap/fingerprint.py +91 -0
- attackmap/fleet.py +370 -0
- attackmap/gitref.py +129 -0
- attackmap/graph.py +26 -0
- attackmap/handler_auth.py +639 -0
- attackmap/handler_span.py +62 -0
- attackmap/hunt_harness.py +505 -0
- attackmap/insights.py +701 -0
- attackmap/llm_review.py +846 -0
- attackmap/lockfiles.py +521 -0
- attackmap/mass_assignment.py +543 -0
- attackmap/md.py +175 -0
- attackmap/merge.py +333 -0
- attackmap/models.py +1284 -0
- attackmap/plugins_lock.py +114 -0
- attackmap/popular_packages.py +193 -0
- attackmap/progress.py +258 -0
- attackmap/recon_models.py +31 -0
- attackmap/recon_to_analysis.py +149 -0
- attackmap/redact.py +220 -0
- attackmap/report.py +424 -0
- attackmap/review_eval.py +335 -0
- attackmap/review_json.py +308 -0
- attackmap/review_prompts.py +796 -0
- attackmap/route_auth_fusion.py +474 -0
- attackmap/safe_fs.py +285 -0
- attackmap/sarif.py +373 -0
- attackmap/sbom.py +418 -0
- attackmap/scanner.py +1379 -0
- attackmap/sdk/__init__.py +278 -0
- attackmap/sdk/contracts.py +17 -0
- attackmap/sdk/fs.py +156 -0
- attackmap/sdk/models.py +59 -0
- attackmap/secrets_history.py +161 -0
- attackmap/security_overlay.py +71 -0
- attackmap/srcpaths.py +301 -0
- attackmap/suggest.py +394 -0
- attackmap/supply_chain.py +688 -0
- attackmap/suppress.py +740 -0
- attackmap/taint.py +1540 -0
- attackmap/taint_flow.py +1419 -0
- attackmap/taint_sinks.py +431 -0
- attackmap/taint_sinks.yaml +308 -0
- attackmap/taint_sources.py +396 -0
- attackmap/taxonomy.py +465 -0
- attackmap/threat_model.py +3441 -0
- attackmap/topology.py +316 -0
- attackmap/triage.py +151 -0
- attackmap/weaknesses.py +365 -0
- attackmap/webhardening.py +479 -0
- attackmap/webhook_verification.py +1067 -0
- attackmap/workflow_scanner.py +964 -0
- attackmap-0.6.4.dist-info/METADATA +226 -0
- attackmap-0.6.4.dist-info/RECORD +91 -0
- attackmap-0.6.4.dist-info/WHEEL +5 -0
- attackmap-0.6.4.dist-info/entry_points.txt +2 -0
- attackmap-0.6.4.dist-info/licenses/LICENSE +21 -0
- attackmap-0.6.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""Official analyzer plugins, pinned to immutable commits (#237).
|
|
2
|
+
|
|
3
|
+
Generated by scripts/update_plugin_lock.py — do not edit by hand. Only
|
|
4
|
+
these may be auto-installed by `-m` / `suggest --install`, and only at
|
|
5
|
+
the pinned commit.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
OFFICIAL_PLUGINS: list[dict] = [
|
|
9
|
+
{
|
|
10
|
+
'repo': 'attackmap-analyzer-python',
|
|
11
|
+
'package': 'attackmap-analyzer-python',
|
|
12
|
+
'version': '0.2.0',
|
|
13
|
+
'git_sha': '9888be5c8f8e9127686e0ed98af9425a34bc7b8c',
|
|
14
|
+
'entry_points': ['python'],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
'repo': 'attackmap-analyzer-rust',
|
|
18
|
+
'package': 'attackmap-analyzer-rust',
|
|
19
|
+
'version': '0.2.0',
|
|
20
|
+
'git_sha': 'ba6cce7ec1b8055b27c3aa2ddd8884ae410b8ba0',
|
|
21
|
+
'entry_points': ['rust'],
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
'repo': 'attackmap-analyzer-go',
|
|
25
|
+
'package': 'attackmap-analyzer-go',
|
|
26
|
+
'version': '0.2.0',
|
|
27
|
+
'git_sha': '8570338fc67198770b3185633bb0fc35d7db5deb',
|
|
28
|
+
'entry_points': ['go'],
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
'repo': 'attackmap-analyzer-java-spring',
|
|
32
|
+
'package': 'attackmap-analyzer-java-spring',
|
|
33
|
+
'version': '0.2.0',
|
|
34
|
+
'git_sha': '82e23461f6c075d17a709c29c9349a1076d1bafa',
|
|
35
|
+
'entry_points': ['java_spring'],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
'repo': 'attackmap-analyzer-dotnet',
|
|
39
|
+
'package': 'attackmap-analyzer-dotnet',
|
|
40
|
+
'version': '0.2.0',
|
|
41
|
+
'git_sha': 'fd08a2899f81c7173e257e892fb3914704244f3d',
|
|
42
|
+
'entry_points': ['dotnet'],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
'repo': 'attackmap-analyzer-terraform',
|
|
46
|
+
'package': 'attackmap-analyzer-terraform',
|
|
47
|
+
'version': '0.2.1',
|
|
48
|
+
'git_sha': '1dfbce9a32c9a2fd6fc1b80f976ea86f69d82cd1',
|
|
49
|
+
'entry_points': ['terraform'],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
'repo': 'attackmap-analyzer-c',
|
|
53
|
+
'package': 'attackmap-analyzer-c',
|
|
54
|
+
'version': '0.2.0',
|
|
55
|
+
'git_sha': '00f7615a75df47e4ea1a084af562b4cf2e037ef1',
|
|
56
|
+
'entry_points': ['c'],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
'repo': 'attackmap-analyzer-cpp',
|
|
60
|
+
'package': 'attackmap-analyzer-cpp',
|
|
61
|
+
'version': '0.2.0',
|
|
62
|
+
'git_sha': 'cae9511e8db3ae192af9189a5d15d889e7fd18a8',
|
|
63
|
+
'entry_points': ['cpp'],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
'repo': 'attackmap-analyzer-swift',
|
|
67
|
+
'package': 'attackmap-analyzer-swift',
|
|
68
|
+
'version': '0.2.0',
|
|
69
|
+
'git_sha': 'eb56ca09c642e580c5cd73391d602f555d429a4b',
|
|
70
|
+
'entry_points': ['objc', 'swift'],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
'repo': 'attackmap-analyzer-node-service',
|
|
74
|
+
'package': 'attackmap-analyzer-node-service',
|
|
75
|
+
'version': '0.3.0',
|
|
76
|
+
'git_sha': '949269777c020e7a6265fb1436cf1be2fe5004be',
|
|
77
|
+
'entry_points': ['node_service'],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
'repo': 'attackmap-analyzer-atproto',
|
|
81
|
+
'package': 'attackmap-analyzer-atproto',
|
|
82
|
+
'version': '0.2.0',
|
|
83
|
+
'git_sha': '34b7616d5428549d39848dc4db1af7a71d6e0b56',
|
|
84
|
+
'entry_points': ['atproto'],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
'repo': 'attackmap-analyzer-php-web',
|
|
88
|
+
'package': 'attackmap-analyzer-php-web',
|
|
89
|
+
'version': '0.2.0',
|
|
90
|
+
'git_sha': '65a5f098c40ae64d3ec3b5d20777eac640fb743b',
|
|
91
|
+
'entry_points': ['php_web'],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
'repo': 'attackmap-analyzer-php-laminas',
|
|
95
|
+
'package': 'attackmap-analyzer-php-laminas',
|
|
96
|
+
'version': '0.2.0',
|
|
97
|
+
'git_sha': '9046ff6256338850574ee781b69bd2e3f09d2ab2',
|
|
98
|
+
'entry_points': ['php_laminas'],
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
'repo': 'attackmap-analyzer-omeka-s',
|
|
102
|
+
'package': 'attackmap-analyzer-omeka-s',
|
|
103
|
+
'version': '0.2.0',
|
|
104
|
+
'git_sha': '9759c163e30c35256558188cff30dc1cf1331cd5',
|
|
105
|
+
'entry_points': ['omeka_s'],
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
'repo': 'attackmap-analyzer-iac',
|
|
109
|
+
'package': 'attackmap-analyzer-iac',
|
|
110
|
+
'version': '0.2.0',
|
|
111
|
+
'git_sha': 'ef7811cb500b40535740fc104a3d2b29ef627141',
|
|
112
|
+
'entry_points': ['iac'],
|
|
113
|
+
},
|
|
114
|
+
]
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Bundled popular-package names for offline typosquat detection (#247).
|
|
2
|
+
|
|
3
|
+
Versioned, offline and deterministic: no network lookup ever happens. The
|
|
4
|
+
lists are curated from widely used packages (PyPI download rankings and the
|
|
5
|
+
most-depended-upon npm packages), and also carry near-twins of popular names
|
|
6
|
+
(`boto`/`boto3`, `scipy`/`scapy`) so a legitimate package isn't flagged as a
|
|
7
|
+
squat of its neighbour. Bump ``POPULAR_PACKAGES_VERSION`` when editing.
|
|
8
|
+
|
|
9
|
+
PyPI names are PEP 503-normalized (lowercase, runs of ``-_.`` → ``-``).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
POPULAR_PACKAGES_VERSION = "2026.10.1"
|
|
15
|
+
|
|
16
|
+
PYPI: frozenset[str] = frozenset((
|
|
17
|
+
"accelerate", "adal", "aiobotocore", "aiohttp", "aiosignal", "alabaster",
|
|
18
|
+
"alembic", "altair", "amqp", "angr", "annotated-types", "ansible", "ansible-core",
|
|
19
|
+
"anthropic", "anyio", "apscheduler", "arcade", "argon2-cffi", "ariadne", "arrow",
|
|
20
|
+
"asgiref", "astroid", "asttokens", "async-timeout", "asyncpg", "attrs", "authlib",
|
|
21
|
+
"automat", "autopep8", "awscli", "azure-common", "azure-core", "azure-identity",
|
|
22
|
+
"azure-storage-blob", "babel", "backoff", "bandit", "bcrypt", "beautifulsoup4",
|
|
23
|
+
"bidict", "billiard", "binaryornot", "black", "bleach", "blinker", "bokeh", "boto",
|
|
24
|
+
"boto3", "botocore", "build", "cachecontrol", "cachetools", "capstone",
|
|
25
|
+
"cassandra-driver", "catboost", "cattrs", "celery", "certifi", "cffi", "cfgv",
|
|
26
|
+
"channels", "chardet", "charset-normalizer", "click", "cloudpickle", "cmake",
|
|
27
|
+
"colorama", "colorlog", "comm", "comtypes", "confluent-kafka", "constantly",
|
|
28
|
+
"contourpy", "cookiecutter", "coverage", "croniter", "cryptography", "cssselect",
|
|
29
|
+
"cx-freeze", "cycler", "cython", "cytoolz", "daphne", "dash", "dask",
|
|
30
|
+
"dataclasses-json", "datadog", "datasets", "ddtrace", "debugpy", "decorator",
|
|
31
|
+
"deepdiff", "defusedxml", "dill", "diskcache", "distlib", "distributed", "django",
|
|
32
|
+
"django-cors-headers", "djangorestframework", "dnspython", "docker", "docutils",
|
|
33
|
+
"duckdb", "dynaconf", "ecdsa", "elasticsearch", "email-validator", "emoji",
|
|
34
|
+
"environs", "et-xmlfile", "eventlet", "exceptiongroup", "execnet", "executing",
|
|
35
|
+
"fabric", "factory-boy", "faker", "fastapi", "feedparser", "filelock", "findspark",
|
|
36
|
+
"fiona", "flake8", "flask", "flask-cors", "flask-jwt-extended", "flask-login",
|
|
37
|
+
"flask-migrate", "flask-restful", "flask-sqlalchemy", "flask-wtf", "flit-core",
|
|
38
|
+
"fonttools", "freezegun", "frida", "frida-tools", "frozenlist", "fsspec", "ftfy",
|
|
39
|
+
"gensim", "geopandas", "gevent", "gnupg", "google-api-core",
|
|
40
|
+
"google-api-python-client", "google-auth", "google-auth-httplib2",
|
|
41
|
+
"google-auth-oauthlib", "google-cloud-core", "google-cloud-storage",
|
|
42
|
+
"google-crc32c", "google-resumable-media", "googleapis-common-protos", "gradio",
|
|
43
|
+
"graphene", "graphql-core", "greenlet", "grpcio", "grpcio-status", "gunicorn",
|
|
44
|
+
"h11", "h5py", "hatchling", "html5lib", "httpcore", "httplib2", "httptools",
|
|
45
|
+
"httpx", "huggingface-hub", "humanize", "hyperlink", "hypothesis", "identify",
|
|
46
|
+
"idna", "imageio", "imagesize", "impacket", "importlib-metadata", "incremental",
|
|
47
|
+
"iniconfig", "invoke", "ipaddress", "ipykernel", "ipython", "ipywidgets",
|
|
48
|
+
"isodate", "isort", "itemadapter", "itsdangerous", "jaraco-classes", "jedi",
|
|
49
|
+
"jeepney", "jinja2", "jinja2-time", "jmespath", "joblib", "jsonpatch",
|
|
50
|
+
"jsonpath-ng", "jsonpointer", "jsonschema", "jsonschema-specifications", "jupyter",
|
|
51
|
+
"jupyter-client", "jupyter-core", "jupyterlab", "jupyterlab-server", "jwcrypto",
|
|
52
|
+
"kafka-python", "keras", "keyring", "keystone-engine", "kivy", "kiwisolver",
|
|
53
|
+
"kombu", "kubernetes", "langchain", "langchain-community", "langchain-core",
|
|
54
|
+
"langdetect", "lief", "lightgbm", "llvmlite", "locket", "loguru", "lxml", "mako",
|
|
55
|
+
"markdown", "markdown-it-py", "markupsafe", "marshmallow", "matplotlib",
|
|
56
|
+
"matplotlib-inline", "mccabe", "mdurl", "meson", "mistune", "mkdocs",
|
|
57
|
+
"mkdocs-material", "mock", "more-itertools", "moto", "motor", "mpmath", "msal",
|
|
58
|
+
"msal-extensions", "msgpack", "msrest", "msrestazure", "multidict", "mypy",
|
|
59
|
+
"mypy-extensions", "mysqlclient", "nbclient", "nbconvert", "nbformat",
|
|
60
|
+
"nest-asyncio", "netaddr", "netcdf4", "netifaces", "networkx", "ninja", "nltk",
|
|
61
|
+
"nmap", "nodeenv", "nose", "nose2", "notebook", "nox", "numba", "numpy",
|
|
62
|
+
"oauth2client", "oauthlib", "openai", "opencv-python", "opencv-python-headless",
|
|
63
|
+
"openpyxl", "opentelemetry-api", "opentelemetry-sdk", "orjson", "packaging",
|
|
64
|
+
"pandas", "pandocfilters", "paramiko", "parsel", "parso", "partd", "passlib",
|
|
65
|
+
"patsy", "pdm", "peewee", "pefile", "pendulum", "pexpect", "phonenumbers", "pika",
|
|
66
|
+
"pillow", "pip", "pkginfo", "platformdirs", "playwright", "plotly", "pluggy",
|
|
67
|
+
"poetry", "poetry-core", "polars", "pony", "pre-commit", "prettytable",
|
|
68
|
+
"prometheus-client", "prompt-toolkit", "protobuf", "psutil", "psycopg", "psycopg2",
|
|
69
|
+
"psycopg2-binary", "ptyprocess", "pure-eval", "pwntools", "py2exe", "py4j",
|
|
70
|
+
"pyarrow", "pyasn1", "pyasn1-modules", "pybind11", "pycodestyle", "pycountry",
|
|
71
|
+
"pycparser", "pycryptodome", "pycryptodomex", "pydantic", "pydantic-core",
|
|
72
|
+
"pydantic-settings", "pyelftools", "pyflakes", "pygame", "pyglet", "pygments",
|
|
73
|
+
"pyhamcrest", "pyinstaller", "pyjwt", "pylint", "pymdown-extensions", "pymongo",
|
|
74
|
+
"pymysql", "pynacl", "pyobjc", "pyopengl", "pyopenssl", "pyotp", "pyparsing",
|
|
75
|
+
"pyppeteer", "pyproj", "pyproject-hooks", "pyqt5", "pyqt6", "pyquery",
|
|
76
|
+
"pyrsistent", "pyside6", "pysnmp", "pyspark", "pytest", "pytest-asyncio",
|
|
77
|
+
"pytest-cov", "pytest-mock", "pytest-timeout", "pytest-xdist", "python-dateutil",
|
|
78
|
+
"python-dotenv", "python-engineio", "python-gnupg", "python-jose",
|
|
79
|
+
"python-multipart", "python-nmap", "python-slugify", "python-socketio", "pytz",
|
|
80
|
+
"pyvmomi", "pywin32", "pywin32-ctypes", "pyyaml", "pyzmq", "qrcode", "r2pipe",
|
|
81
|
+
"readme-renderer", "redis", "redis-py-cluster", "referencing", "regex", "requests",
|
|
82
|
+
"requests-mock", "requests-oauthlib", "responses", "retrying", "rich", "ropgadget",
|
|
83
|
+
"rpds-py", "rsa", "ruff", "s3fs", "s3transfer", "safetensors", "salt", "scapy",
|
|
84
|
+
"schedule", "scikit-build", "scikit-image", "scikit-learn", "scipy", "scrapy",
|
|
85
|
+
"seaborn", "secretstorage", "selenium", "sentencepiece", "sentry-sdk",
|
|
86
|
+
"service-identity", "setuptools", "setuptools-scm", "shapely", "shellingham",
|
|
87
|
+
"shodan", "simplejson", "six", "sniffio", "snowballstemmer",
|
|
88
|
+
"snowflake-connector-python", "sortedcontainers", "soupsieve", "spacy", "sphinx",
|
|
89
|
+
"sphinx-rtd-theme", "sqlalchemy", "sqlalchemy-utils", "sqlparse", "stack-data",
|
|
90
|
+
"starlette", "statsmodels", "strawberry-graphql", "streamlit", "structlog",
|
|
91
|
+
"sympy", "tables", "tabulate", "tblib", "tenacity", "tensorboard", "tensorflow",
|
|
92
|
+
"termcolor", "text-unidecode", "threadpoolctl", "tifffile", "tiktoken", "tinycss2",
|
|
93
|
+
"tokenizers", "toml", "tomli", "tomlkit", "toolz", "torch", "torchaudio",
|
|
94
|
+
"torchvision", "tornado", "tortoise-orm", "tox", "tqdm", "traitlets",
|
|
95
|
+
"transformers", "twine", "twisted", "typer", "typing-extensions", "tzdata",
|
|
96
|
+
"ujson", "unicorn", "unidecode", "uritemplate", "urllib3", "uvicorn", "uvloop",
|
|
97
|
+
"vine", "virtualenv", "w3lib", "watchdog", "watchfiles", "wcwidth", "webencodings",
|
|
98
|
+
"websocket-client", "websockets", "werkzeug", "wheel", "wrapt", "wtforms",
|
|
99
|
+
"wxpython", "xarray", "xgboost", "xmltodict", "yapf", "yara-python", "yarl",
|
|
100
|
+
"z3-solver", "zict", "zipp", "zope-interface",
|
|
101
|
+
))
|
|
102
|
+
|
|
103
|
+
NPM: frozenset[str] = frozenset((
|
|
104
|
+
"@apollo/client", "@aws-sdk/client-s3", "@babel/core", "@babel/generator",
|
|
105
|
+
"@babel/parser", "@babel/preset-env", "@babel/preset-react",
|
|
106
|
+
"@babel/preset-typescript", "@babel/runtime", "@babel/traverse", "@babel/types",
|
|
107
|
+
"@emotion/react", "@emotion/styled", "@material-ui/core", "@mui/material",
|
|
108
|
+
"@nestjs/common", "@nestjs/core", "@prisma/client", "@reduxjs/toolkit",
|
|
109
|
+
"@tanstack/react-query", "@types/express", "@types/jest", "@types/lodash",
|
|
110
|
+
"@types/node", "@types/react", "@types/react-dom",
|
|
111
|
+
"@typescript-eslint/eslint-plugin", "@typescript-eslint/parser", "accepts",
|
|
112
|
+
"adm-zip", "ajv", "ajv-keywords", "amqplib", "angular", "ansi-regex",
|
|
113
|
+
"ansi-styles", "antd", "anymatch", "apollo-server", "archiver", "array-flatten",
|
|
114
|
+
"async", "asynckit", "autoprefixer", "aws-sdk", "axios", "babel-core",
|
|
115
|
+
"babel-loader", "balanced-match", "base64-js", "bcrypt", "bcryptjs",
|
|
116
|
+
"better-sqlite3", "big.js", "binary-extensions", "bindings", "bl", "bluebird",
|
|
117
|
+
"body-parser", "bootstrap", "boxen", "brace-expansion", "braces", "browserify",
|
|
118
|
+
"browserslist", "buffer", "buffer-from", "bull", "bullmq", "bunyan", "busboy",
|
|
119
|
+
"bytes", "call-bind", "callsites", "camelcase", "caniuse-lite", "canvas", "chai",
|
|
120
|
+
"chalk", "chart.js", "cheerio", "chokidar", "chownr", "class-transformer",
|
|
121
|
+
"class-validator", "classnames", "clean-css", "cli-spinners", "clipboardy",
|
|
122
|
+
"cliui", "clone", "color-convert", "color-name", "colors", "combined-stream",
|
|
123
|
+
"commander", "compression", "concat-map", "concurrently", "configstore",
|
|
124
|
+
"content-disposition", "content-type", "cookie", "cookie-parser",
|
|
125
|
+
"copy-webpack-plugin", "core-js", "core-util-is", "cors", "cosmiconfig", "cpy-cli",
|
|
126
|
+
"cross-env", "cross-fetch", "cross-spawn", "crypto-js", "css-loader", "cssnano",
|
|
127
|
+
"csv-parse", "d3", "date-fns", "dayjs", "debug", "decamelize", "deep-equal",
|
|
128
|
+
"deep-extend", "deepmerge", "define-properties", "delayed-stream", "depd",
|
|
129
|
+
"destroy", "dotenv", "duplexify", "ee-first", "ejs", "electron-to-chromium",
|
|
130
|
+
"emojis-list", "encodeurl", "end-of-stream", "engine.io", "enhanced-resolve",
|
|
131
|
+
"enquirer", "es-abstract", "esbuild", "escape-html", "escape-string-regexp",
|
|
132
|
+
"eslint", "eslint-config-airbnb", "eslint-config-prettier", "eslint-plugin-import",
|
|
133
|
+
"eslint-plugin-jsx-a11y", "eslint-plugin-prettier", "eslint-plugin-react",
|
|
134
|
+
"eslint-plugin-react-hooks", "etag", "eventemitter3", "events", "exceljs", "execa",
|
|
135
|
+
"express", "express-session", "fast-deep-equal", "fast-json-stable-stringify",
|
|
136
|
+
"fast-xml-parser", "fastify", "figures", "file-loader", "fill-range",
|
|
137
|
+
"finalhandler", "find-up", "firebase", "firebase-admin",
|
|
138
|
+
"fork-ts-checker-webpack-plugin", "form-data", "formidable", "formik", "forwarded",
|
|
139
|
+
"fresh", "fs-extra", "fs-minipass", "fsevents", "function-bind", "get-caller-file",
|
|
140
|
+
"get-intrinsic", "get-stream", "glob", "glob-parent", "googleapis", "got",
|
|
141
|
+
"graceful-fs", "graphql", "graphql-tag", "grunt", "gulp", "handlebars", "hapi",
|
|
142
|
+
"has", "has-flag", "hasown", "helmet", "highlight.js", "history",
|
|
143
|
+
"html-webpack-plugin", "http-errors", "http-proxy", "http-proxy-middleware",
|
|
144
|
+
"human-signals", "husky", "iconv-lite", "ieee754", "immer", "immutable",
|
|
145
|
+
"import-fresh", "inflight", "inherits", "ini", "inquirer", "ioredis", "ipaddr.js",
|
|
146
|
+
"is-core-module", "is-docker", "is-extglob", "is-glob", "is-number", "is-stream",
|
|
147
|
+
"is-wsl", "isarray", "isexe", "isomorphic-fetch", "jasmine", "jest", "jimp", "joi",
|
|
148
|
+
"jquery", "js-tokens", "js-yaml", "jsdom", "json-schema-traverse",
|
|
149
|
+
"json-stable-stringify", "json5", "jsonfile", "jsonwebtoken", "jszip", "kafkajs",
|
|
150
|
+
"karma", "kleur", "knex", "koa", "koa-router", "latest-version", "leaflet", "less",
|
|
151
|
+
"lint-staged", "listr2", "loader-utils", "locate-path", "lodash",
|
|
152
|
+
"lodash.debounce", "lodash.get", "lodash.isequal", "lodash.merge", "log-symbols",
|
|
153
|
+
"log4js", "loose-envify", "lru-cache", "luxon", "mapbox-gl", "markdown-it",
|
|
154
|
+
"marked", "media-typer", "memory-fs", "meow", "merge-descriptors", "merge-stream",
|
|
155
|
+
"methods", "micromatch", "mime", "mime-db", "mime-types", "mimic-fn",
|
|
156
|
+
"mini-css-extract-plugin", "minimatch", "minimist", "minipass", "mitt", "mkdirp",
|
|
157
|
+
"mobx", "mocha", "moment", "mongodb", "mongoose", "morgan", "ms", "multer",
|
|
158
|
+
"mustache", "mysql", "mysql2", "nan", "nanoid", "needle", "negotiator",
|
|
159
|
+
"neo-async", "next", "node-addon-api", "node-fetch", "node-gyp", "node-releases",
|
|
160
|
+
"nodemailer", "nodemon", "normalize-path", "npm-run-all", "npm-run-path",
|
|
161
|
+
"numeral", "nunjucks", "nuxt", "object-assign", "object-keys", "on-finished",
|
|
162
|
+
"once", "onetime", "open", "opn", "ora", "p-limit", "p-locate", "p-try",
|
|
163
|
+
"package-json", "papaparse", "parcel", "parent-module", "passport", "passport-jwt",
|
|
164
|
+
"passport-local", "path-exists", "path-is-absolute", "path-key", "path-parse",
|
|
165
|
+
"path-to-regexp", "pdfkit", "pg", "pg-promise", "picocolors", "picomatch", "pino",
|
|
166
|
+
"pkg-dir", "playwright", "postcss", "postcss-loader", "prebuild-install",
|
|
167
|
+
"prettier", "prisma", "process-nextick-args", "prompts", "prop-types",
|
|
168
|
+
"proxy-addr", "psl", "pug", "pump", "punycode", "puppeteer", "qs", "query-string",
|
|
169
|
+
"querystring", "range-parser", "raw-body", "rc", "react", "react-bootstrap",
|
|
170
|
+
"react-dom", "react-hook-form", "react-query", "react-redux", "react-router",
|
|
171
|
+
"react-router-dom", "readable-stream", "readdirp", "recoil", "redis", "redux",
|
|
172
|
+
"redux-saga", "redux-thunk", "reflect-metadata", "regenerator-runtime",
|
|
173
|
+
"registry-url", "request", "require-directory", "resolve", "resolve-from",
|
|
174
|
+
"restify", "rimraf", "rollup", "rxjs", "rxjs-compat", "safe-buffer", "sass",
|
|
175
|
+
"sass-loader", "schema-utils", "selenium-webdriver", "semver", "send", "sequelize",
|
|
176
|
+
"serve-favicon", "serve-static", "setprototypeof", "sharp", "shebang-command",
|
|
177
|
+
"shebang-regex", "shelljs", "showdown", "signal-exit", "sinon", "socket.io",
|
|
178
|
+
"socket.io-client", "source-map", "source-map-support", "sqlite3", "statuses",
|
|
179
|
+
"stream-shift", "string-width", "string_decoder", "strip-ansi",
|
|
180
|
+
"strip-final-newline", "strip-json-comments", "stripe", "style-loader",
|
|
181
|
+
"styled-components", "stylus", "superagent", "supports-color", "svelte", "swr",
|
|
182
|
+
"tailwindcss", "tapable", "tar", "tar-stream", "terser", "terser-webpack-plugin",
|
|
183
|
+
"three", "through2", "to-regex-range", "toidentifier", "tough-cookie", "ts-loader",
|
|
184
|
+
"ts-node", "tslib", "twilio", "type-is", "typeorm", "typescript", "uglify-js",
|
|
185
|
+
"underscore", "universalify", "unpipe", "unzipper", "update-browserslist-db",
|
|
186
|
+
"update-notifier", "uri-js", "url-loader", "url-parse", "util-deprecate",
|
|
187
|
+
"utils-merge", "uuid", "validator", "vary", "vite", "vue", "watchpack", "webpack",
|
|
188
|
+
"webpack-cli", "webpack-dev-server", "whatwg-fetch", "which", "winston",
|
|
189
|
+
"wrap-ansi", "wrappy", "ws", "xdg-basedir", "xlsx", "xml2js", "y18n", "yallist",
|
|
190
|
+
"yaml", "yargs", "yargs-parser", "yup", "zod", "zone.js", "zustand",
|
|
191
|
+
))
|
|
192
|
+
|
|
193
|
+
BY_ECOSYSTEM: dict[str, frozenset[str]] = {"pypi": PYPI, "npm": NPM}
|
attackmap/progress.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Live scan progress with ETA (#36), for terminals and for front-ends.
|
|
2
|
+
|
|
3
|
+
A repo scan can run for many minutes on a large tree, and a silent terminal
|
|
4
|
+
reads as "hung." This renders a single, self-updating status line to stderr:
|
|
5
|
+
a bar + percentage + count + ETA during the per-file pass, and an animated
|
|
6
|
+
spinner with elapsed time during the indeterminate tail analyzers (taint,
|
|
7
|
+
SBOM, authorization, anomalies) — the phase where a big monorepo spends most
|
|
8
|
+
of its time.
|
|
9
|
+
|
|
10
|
+
Zero third-party deps. TTY-aware: when stderr isn't a terminal (CI, piped to
|
|
11
|
+
a file) it stays silent so logs aren't polluted with carriage returns. The
|
|
12
|
+
scanner drives it through the small hook surface (`begin`/`advance`/`stage`/
|
|
13
|
+
`done`); when no progress object is passed, the scan runs exactly as before.
|
|
14
|
+
|
|
15
|
+
Two renderers implement that hook surface: :class:`ScanProgress` (the TTY
|
|
16
|
+
status line above) and :class:`JsonScanProgress`, which always emits NDJSON
|
|
17
|
+
events for the macOS GUI and other non-TTY front-ends.
|
|
18
|
+
:func:`create_progress` picks one from ``--progress-format``.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import sys
|
|
25
|
+
import threading
|
|
26
|
+
import time
|
|
27
|
+
from typing import TextIO
|
|
28
|
+
|
|
29
|
+
# NDJSON progress protocol version (consumed by the macOS GUI and other
|
|
30
|
+
# non-TTY front-ends). Bump only on a breaking change to the event shape.
|
|
31
|
+
PROGRESS_PROTOCOL_VERSION = 1
|
|
32
|
+
|
|
33
|
+
_SPINNER = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
|
|
34
|
+
_BAR_WIDTH = 24
|
|
35
|
+
_MIN_REDRAW_INTERVAL = 0.08 # seconds — cap redraws so we don't thrash the TTY
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _fmt_duration(seconds: float) -> str:
|
|
39
|
+
seconds = int(max(0, seconds))
|
|
40
|
+
if seconds >= 3600:
|
|
41
|
+
return f"{seconds // 3600}h{(seconds % 3600) // 60:02d}m"
|
|
42
|
+
return f"{seconds // 60:02d}:{seconds % 60:02d}"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ScanProgress:
|
|
46
|
+
"""Render scan progress to a TTY. All methods are no-ops when disabled."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, *, enabled: bool = True, stream: TextIO | None = None) -> None:
|
|
49
|
+
stream = stream if stream is not None else sys.stderr
|
|
50
|
+
self._stream = stream
|
|
51
|
+
self._enabled = enabled and hasattr(stream, "isatty") and stream.isatty()
|
|
52
|
+
self._total = 0
|
|
53
|
+
self._done = 0
|
|
54
|
+
self._start = 0.0
|
|
55
|
+
self._last_draw = 0.0
|
|
56
|
+
self._last_len = 0
|
|
57
|
+
self._label = ""
|
|
58
|
+
# Spinner thread state for indeterminate stages.
|
|
59
|
+
self._spin_stop: threading.Event | None = None
|
|
60
|
+
self._spin_thread: threading.Thread | None = None
|
|
61
|
+
self._lock = threading.Lock()
|
|
62
|
+
|
|
63
|
+
# --- per-file phase ---------------------------------------------------
|
|
64
|
+
|
|
65
|
+
def begin(self, total: int, label: str = "Scanning files") -> None:
|
|
66
|
+
if not self._enabled:
|
|
67
|
+
return
|
|
68
|
+
self._stop_spinner()
|
|
69
|
+
self._total = max(0, total)
|
|
70
|
+
self._done = 0
|
|
71
|
+
self._label = label
|
|
72
|
+
self._start = time.monotonic()
|
|
73
|
+
self._draw(force=True)
|
|
74
|
+
|
|
75
|
+
def advance(self, current: str = "") -> None:
|
|
76
|
+
if not self._enabled:
|
|
77
|
+
return
|
|
78
|
+
self._done += 1
|
|
79
|
+
self._draw(current=current)
|
|
80
|
+
|
|
81
|
+
# --- indeterminate tail phases ---------------------------------------
|
|
82
|
+
|
|
83
|
+
def stage(self, label: str) -> None:
|
|
84
|
+
"""Switch to an indeterminate phase with an animated spinner."""
|
|
85
|
+
if not self._enabled:
|
|
86
|
+
return
|
|
87
|
+
self._stop_spinner()
|
|
88
|
+
self._label = label
|
|
89
|
+
stop = threading.Event()
|
|
90
|
+
self._spin_stop = stop
|
|
91
|
+
started = time.monotonic()
|
|
92
|
+
|
|
93
|
+
def _spin() -> None:
|
|
94
|
+
i = 0
|
|
95
|
+
while not stop.is_set():
|
|
96
|
+
frame = _SPINNER[i % len(_SPINNER)]
|
|
97
|
+
with self._lock:
|
|
98
|
+
self._write(f"{frame} {label}… ({_fmt_duration(time.monotonic() - started)})")
|
|
99
|
+
i += 1
|
|
100
|
+
stop.wait(0.12)
|
|
101
|
+
|
|
102
|
+
thread = threading.Thread(target=_spin, daemon=True)
|
|
103
|
+
self._spin_thread = thread
|
|
104
|
+
thread.start()
|
|
105
|
+
|
|
106
|
+
def done(self, summary: str = "") -> None:
|
|
107
|
+
if not self._enabled:
|
|
108
|
+
return
|
|
109
|
+
self._stop_spinner()
|
|
110
|
+
with self._lock:
|
|
111
|
+
self._clear_line()
|
|
112
|
+
if summary:
|
|
113
|
+
self._stream.write(summary + "\n")
|
|
114
|
+
self._stream.flush()
|
|
115
|
+
|
|
116
|
+
# --- internals --------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
def _stop_spinner(self) -> None:
|
|
119
|
+
if self._spin_stop is not None:
|
|
120
|
+
self._spin_stop.set()
|
|
121
|
+
if self._spin_thread is not None:
|
|
122
|
+
self._spin_thread.join(timeout=0.5)
|
|
123
|
+
self._spin_stop = None
|
|
124
|
+
self._spin_thread = None
|
|
125
|
+
|
|
126
|
+
def _draw(self, *, current: str = "", force: bool = False) -> None:
|
|
127
|
+
now = time.monotonic()
|
|
128
|
+
if not force and (now - self._last_draw) < _MIN_REDRAW_INTERVAL and self._done < self._total:
|
|
129
|
+
return
|
|
130
|
+
self._last_draw = now
|
|
131
|
+
total = self._total or 1
|
|
132
|
+
frac = min(1.0, self._done / total)
|
|
133
|
+
filled = int(_BAR_WIDTH * frac)
|
|
134
|
+
bar = "█" * filled + "░" * (_BAR_WIDTH - filled)
|
|
135
|
+
elapsed = now - self._start
|
|
136
|
+
eta = (elapsed / frac - elapsed) if frac > 0 else 0.0
|
|
137
|
+
tail = f" {_truncate(current, 32)}" if current else ""
|
|
138
|
+
line = (
|
|
139
|
+
f"{self._label} [{bar}] {int(frac * 100):3d}% "
|
|
140
|
+
f"{self._done}/{self._total} ETA {_fmt_duration(eta)}{tail}"
|
|
141
|
+
)
|
|
142
|
+
with self._lock:
|
|
143
|
+
self._write(line)
|
|
144
|
+
|
|
145
|
+
def _write(self, text: str) -> None:
|
|
146
|
+
# Pad to erase any leftover from a previous longer line.
|
|
147
|
+
pad = max(0, self._last_len - len(text))
|
|
148
|
+
self._stream.write("\r" + text + " " * pad)
|
|
149
|
+
self._stream.flush()
|
|
150
|
+
self._last_len = len(text)
|
|
151
|
+
|
|
152
|
+
def _clear_line(self) -> None:
|
|
153
|
+
if self._last_len:
|
|
154
|
+
self._stream.write("\r" + " " * self._last_len + "\r")
|
|
155
|
+
self._last_len = 0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _truncate(text: str, width: int) -> str:
|
|
159
|
+
text = text.replace("\\", "/")
|
|
160
|
+
if len(text) <= width:
|
|
161
|
+
return text
|
|
162
|
+
return "…" + text[-(width - 1):]
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class JsonScanProgress:
|
|
166
|
+
"""Emit scan progress as newline-delimited JSON (one object per line).
|
|
167
|
+
|
|
168
|
+
Same hook surface as ``ScanProgress`` (``begin``/``advance``/``stage``/
|
|
169
|
+
``done``) so the scanner drives either interchangeably. Unlike the TTY
|
|
170
|
+
renderer this never gates on ``isatty`` — its whole purpose is to feed a
|
|
171
|
+
non-terminal consumer (the macOS GUI) reading the child process's stderr.
|
|
172
|
+
|
|
173
|
+
Event shapes (all carry ``"v": PROGRESS_PROTOCOL_VERSION``)::
|
|
174
|
+
|
|
175
|
+
{"v":1,"event":"begin","total":1240,"label":"Scanning files"}
|
|
176
|
+
{"v":1,"event":"advance","done":37,"total":1240,"current":"src/app.ts"}
|
|
177
|
+
{"v":1,"event":"stage","label":"Taint analysis"}
|
|
178
|
+
{"v":1,"event":"done","summary":"…","done":1240,"total":1240,"elapsed_s":92.4}
|
|
179
|
+
|
|
180
|
+
``advance`` events are throttled to at most one per ``min_interval`` seconds
|
|
181
|
+
to keep the stream sane on large trees; ``done`` (per-file count) stays
|
|
182
|
+
accurate because every advance still increments the internal counter and the
|
|
183
|
+
latest count rides on each emitted event.
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def __init__(self, *, stream: TextIO | None = None, min_interval: float = 0.1) -> None:
|
|
187
|
+
self._stream = stream if stream is not None else sys.stderr
|
|
188
|
+
self._min_interval = max(0.0, min_interval)
|
|
189
|
+
self._total = 0
|
|
190
|
+
self._done = 0
|
|
191
|
+
self._start = 0.0
|
|
192
|
+
self._last_emit = 0.0
|
|
193
|
+
|
|
194
|
+
def begin(self, total: int, label: str = "Scanning files") -> None:
|
|
195
|
+
self._total = max(0, total)
|
|
196
|
+
self._done = 0
|
|
197
|
+
self._start = time.monotonic()
|
|
198
|
+
self._last_emit = self._start # anchor throttle window to scan start
|
|
199
|
+
self._emit("begin", total=self._total, label=label)
|
|
200
|
+
|
|
201
|
+
def advance(self, current: str = "") -> None:
|
|
202
|
+
self._done += 1
|
|
203
|
+
now = time.monotonic()
|
|
204
|
+
# Always emit the final file; throttle the rest.
|
|
205
|
+
if self._done < self._total and (now - self._last_emit) < self._min_interval:
|
|
206
|
+
return
|
|
207
|
+
self._last_emit = now
|
|
208
|
+
self._emit("advance", done=self._done, total=self._total, current=current)
|
|
209
|
+
|
|
210
|
+
def stage(self, label: str) -> None:
|
|
211
|
+
self._emit("stage", label=label)
|
|
212
|
+
|
|
213
|
+
def done(self, summary: str = "") -> None:
|
|
214
|
+
elapsed = round(time.monotonic() - self._start, 3) if self._start else 0.0
|
|
215
|
+
self._emit(
|
|
216
|
+
"done",
|
|
217
|
+
summary=summary,
|
|
218
|
+
done=self._done,
|
|
219
|
+
total=self._total,
|
|
220
|
+
elapsed_s=elapsed,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def _emit(self, event: str, **fields: object) -> None:
|
|
224
|
+
payload = {"v": PROGRESS_PROTOCOL_VERSION, "event": event, **fields}
|
|
225
|
+
try:
|
|
226
|
+
self._stream.write(json.dumps(payload, ensure_ascii=False) + "\n")
|
|
227
|
+
self._stream.flush()
|
|
228
|
+
except (ValueError, OSError):
|
|
229
|
+
# Never let a broken/closed pipe take down a scan.
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def create_progress(
|
|
234
|
+
fmt: str = "auto",
|
|
235
|
+
*,
|
|
236
|
+
no_progress: bool = False,
|
|
237
|
+
stream: TextIO | None = None,
|
|
238
|
+
):
|
|
239
|
+
"""Build the right progress sink for ``--progress-format``.
|
|
240
|
+
|
|
241
|
+
- ``none`` (or ``no_progress=True``) → a disabled ``ScanProgress`` no-op.
|
|
242
|
+
- ``json`` → :class:`JsonScanProgress` (NDJSON, always emits).
|
|
243
|
+
- ``auto`` / ``tty`` → :class:`ScanProgress` (TTY bar, self-silences when
|
|
244
|
+
stderr isn't a terminal).
|
|
245
|
+
"""
|
|
246
|
+
if no_progress or fmt == "none":
|
|
247
|
+
return ScanProgress(enabled=False, stream=stream)
|
|
248
|
+
if fmt == "json":
|
|
249
|
+
return JsonScanProgress(stream=stream)
|
|
250
|
+
return ScanProgress(enabled=True, stream=stream)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
__all__ = [
|
|
254
|
+
"ScanProgress",
|
|
255
|
+
"JsonScanProgress",
|
|
256
|
+
"create_progress",
|
|
257
|
+
"PROGRESS_PROTOCOL_VERSION",
|
|
258
|
+
]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
# Phase-1 shared recon/result model exports.
|
|
4
|
+
# Keep ScanResult and recon signal models as the single source of truth via models.py.
|
|
5
|
+
from .models import (
|
|
6
|
+
AuthHint,
|
|
7
|
+
DatabaseHint,
|
|
8
|
+
EdgeHint,
|
|
9
|
+
EntrypointHint,
|
|
10
|
+
ExternalCall,
|
|
11
|
+
FrameworkHint,
|
|
12
|
+
ProtocolHint,
|
|
13
|
+
Route,
|
|
14
|
+
ScanResult,
|
|
15
|
+
SecretHint,
|
|
16
|
+
ServiceHint,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"Route",
|
|
21
|
+
"ExternalCall",
|
|
22
|
+
"DatabaseHint",
|
|
23
|
+
"AuthHint",
|
|
24
|
+
"ServiceHint",
|
|
25
|
+
"EdgeHint",
|
|
26
|
+
"EntrypointHint",
|
|
27
|
+
"ProtocolHint",
|
|
28
|
+
"FrameworkHint",
|
|
29
|
+
"SecretHint",
|
|
30
|
+
"ScanResult",
|
|
31
|
+
]
|