diff --git a/README.md b/README.md index 69ef0e3..4dec6fb 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,12 @@ It can: - `misp-modules` running and reachable (default: `http://127.0.0.1:6666`) - Python dependency: - `requests` +- Network access to `raw.githubusercontent.com` to fetch MISP's `describeTypes.json` + (`--describe-types-url`), used to validate/guess attribute types and for + `--list-supported-types`/`--list-active-modules`/`--verbose-types`. This fetch is + cached locally (see `--cache-file`/`--cache-ttl-seconds`) and is skipped entirely + when you pass an explicit `--type`, so fully-specified queries work without it + (e.g. in air-gapped environments). Install dependencies: diff --git a/bin/cli.py b/bin/cli.py index 92267b1..3f831ef 100644 --- a/bin/cli.py +++ b/bin/cli.py @@ -626,6 +626,27 @@ def purge_cache(cache_path: str) -> int: return 0 +def get_describe_types(describe_types_url: str, cache_path: str, cache_ttl_seconds: int) -> Dict[str, Any]: + """Fetch MISP's describeTypes.json, reusing a cached copy within the TTL to avoid + hitting raw.githubusercontent.com on every invocation.""" + cache_key = f"describe_types:{describe_types_url}" + now = int(time.time()) + try: + cache = load_cache(cache_path) + except Exception: + cache = {"entries": {}} + cached = get_cached_response(cache, cache_key, now, cache_ttl_seconds) + if cached is not None: + return cached["response"] + describe_types = fetch_describe_types(describe_types_url) + cache.setdefault("entries", {})[cache_key] = {"cached_at": now, "response": describe_types} + try: + save_cache(cache_path, cache) + except Exception: + pass + return describe_types + + def make_cache_key(base_url: str, module_name: str, attr_type: str, value: str, module_config: Dict[str, Any]) -> str: key_payload = { "base_url": base_url.rstrip("/"), @@ -848,9 +869,19 @@ def main() -> int: return 1 valid_types = set() - if args.list_supported_types or args.verbose_types or not args.list_active_modules: + need_describe_types = args.list_supported_types or args.verbose_types or not args.list_active_modules + # A fully explicit --type query doesn't need MISP's describeTypes.json (fetched from + # raw.githubusercontent.com) to know what to do, so skip that network dependency and + # trust the user-provided type in that case. + skip_for_explicit_type = ( + need_describe_types + and args.attr_type + and not args.list_supported_types + and not args.list_active_modules + ) + if need_describe_types and not skip_for_explicit_type: try: - describe_types = fetch_describe_types(args.describe_types_url) + describe_types = get_describe_types(args.describe_types_url, args.cache_file, args.cache_ttl_seconds) valid_types = get_valid_types(describe_types) except Exception as e: print(f"[!] Unable to fetch describeTypes.json: {e}", file=sys.stderr) @@ -921,10 +952,11 @@ def main() -> int: log("No likely MISP attribute type could be guessed from the input.") return 1 - candidate_types = [(t, r) for t, r in candidate_types if t in valid_types] - if not candidate_types: - log("No valid MISP attribute type found.") - return 1 + if valid_types: + candidate_types = [(t, r) for t, r in candidate_types if t in valid_types] + if not candidate_types: + log("No valid MISP attribute type found.") + return 1 if not args.attr_type and not args.all_guesses: candidate_types = candidate_types[:1]