diff --git a/bbot/core/event/base.py b/bbot/core/event/base.py index 60ec83970..8784ac484 100644 --- a/bbot/core/event/base.py +++ b/bbot/core/event/base.py @@ -393,7 +393,7 @@ def source(self, source): """ if is_event(source): self._source = source - hosts_are_same = self.host == source.host + hosts_are_same = self.host and (self.host == source.host) if source.scope_distance >= 0: new_scope_distance = int(source.scope_distance) # only increment the scope distance if the host changes @@ -752,6 +752,8 @@ class ASN(DictEvent): class CODE_REPOSITORY(DictHostEvent): + _always_emit = True + class _data_validator(BaseModel): url: str _validate_url = field_validator("url")(validators.validate_url) diff --git a/bbot/core/helpers/helper.py b/bbot/core/helpers/helper.py index 4f867775f..dbe19f20c 100644 --- a/bbot/core/helpers/helper.py +++ b/bbot/core/helpers/helper.py @@ -157,8 +157,12 @@ def __getattribute__(self, attr): # then try web return getattr(self.web, attr) except AttributeError: - # then die - raise AttributeError(f'Helper has no attribute "{attr}"') + try: + # then try validators + return getattr(self.validators, attr) + except AttributeError: + # then die + raise AttributeError(f'Helper has no attribute "{attr}"') class DummyModule(BaseModule): diff --git a/bbot/core/helpers/misc.py b/bbot/core/helpers/misc.py index 1d3e321e2..ebf9ef779 100644 --- a/bbot/core/helpers/misc.py +++ b/bbot/core/helpers/misc.py @@ -504,12 +504,13 @@ def is_port(p): return p and p.isdigit() and 0 <= int(p) <= 65535 -def is_dns_name(d): +def is_dns_name(d, include_local=True): """ Determines if the given string is a valid DNS name. Args: d (str): The string to be checked. + include_local (bool): Consider local hostnames to be valid (hostnames without periods) Returns: bool: True if the string is a valid DNS name, False otherwise. @@ -519,14 +520,17 @@ def is_dns_name(d): True >>> is_dns_name('localhost') True + >>> is_dns_name('localhost', include_local=False) + False >>> is_dns_name('192.168.1.1') False """ if is_ip(d): return False d = smart_decode(d) - if bbot_regexes.hostname_regex.match(d): - return True + if include_local: + if bbot_regexes.hostname_regex.match(d): + return True if bbot_regexes.dns_name_regex.match(d): return True return False diff --git a/bbot/core/helpers/validators.py b/bbot/core/helpers/validators.py index 969bc18a4..3b8dc77c0 100644 --- a/bbot/core/helpers/validators.py +++ b/bbot/core/helpers/validators.py @@ -280,3 +280,11 @@ def soft_validate(s, t): return True except ValueError: return False + + +def is_email(email): + try: + validate_email(email) + return True + except ValueError: + return False diff --git a/bbot/modules/github.py b/bbot/modules/github_codesearch.py similarity index 69% rename from bbot/modules/github.py rename to bbot/modules/github_codesearch.py index 25ef862ef..c98335722 100644 --- a/bbot/modules/github.py +++ b/bbot/modules/github_codesearch.py @@ -1,25 +1,19 @@ -from bbot.modules.templates.subdomain_enum import subdomain_enum_apikey +from bbot.modules.templates.github import github -class github(subdomain_enum_apikey): +class github_codesearch(github): watched_events = ["DNS_NAME"] - produced_events = ["URL_UNVERIFIED"] + produced_events = ["CODE_REPOSITORY", "URL_UNVERIFIED"] flags = ["passive", "subdomain-enum", "safe"] - meta = {"description": "Query Github's API for related repositories", "auth_required": True} - options = {"api_key": ""} - options_desc = {"api_key": "Github token"} + meta = {"description": "Query Github's API for code containing the target domain name", "auth_required": True} + options = {"api_key": "", "limit": 100} + options_desc = {"api_key": "Github token", "limit": "Limit code search to this many results"} - base_url = "https://api.github.com" + github_raw_url = "https://raw.githubusercontent.com/" async def setup(self): - ret = await super().setup() - self.headers = {"Authorization": f"token {self.api_key}"} - return ret - - async def ping(self): - url = f"{self.base_url}/zen" - response = await self.helpers.request(url) - assert getattr(response, "status_code", 0) == 200 + self.limit = self.config.get("limit", 100) + return await super().setup() async def handle_event(self, event): query = self.make_query(event) @@ -39,6 +33,7 @@ async def query(self, query): repos = {} url = f"{self.base_url}/search/code?per_page=100&type=Code&q={self.helpers.quote(query)}&page=" + "{page}" agen = self.helpers.api_page_iter(url, headers=self.headers, json=False) + num_results = 0 try: async for r in agen: if r is None: @@ -47,6 +42,8 @@ async def query(self, query): if status_code == 429: "Github is rate-limiting us (HTTP status: 429)" break + if status_code != 200: + break try: j = r.json() except Exception as e: @@ -64,10 +61,14 @@ async def query(self, query): repos[repo_url].append(raw_url) except KeyError: repos[repo_url] = [raw_url] + num_results += 1 + if num_results >= self.limit: + break + if num_results >= self.limit: + break finally: agen.aclose() return repos - @staticmethod - def raw_url(url): - return url.replace("https://github.com/", "https://raw.githubusercontent.com/").replace("/blob/", "/") + def raw_url(self, url): + return url.replace("https://github.com/", self.github_raw_url).replace("/blob/", "/") diff --git a/bbot/modules/github_org.py b/bbot/modules/github_org.py new file mode 100644 index 000000000..66182a2a6 --- /dev/null +++ b/bbot/modules/github_org.py @@ -0,0 +1,200 @@ +from bbot.modules.templates.github import github + + +class github_org(github): + watched_events = ["ORG_STUB", "SOCIAL"] + produced_events = ["CODE_REPOSITORY"] + flags = ["passive", "subdomain-enum", "safe"] + meta = {"description": "Query Github's API for organization and member repositories"} + options = {"api_key": "", "include_members": True, "include_member_repos": False} + options_desc = { + "api_key": "Github token", + "include_members": "Enumerate organization members", + "include_member_repos": "Also enumerate organization members' repositories", + } + + scope_distance_modifier = 2 + + async def setup(self): + self.include_members = self.config.get("include_members", True) + self.include_member_repos = self.config.get("include_member_repos", False) + return await super().setup() + + async def filter_event(self, event): + if event.type == "SOCIAL": + if event.data.get("platform", "") != "github": + return False, "event is not a github profile" + # reject org members if the setting isn't enabled + # this prevents gathering of org member repos + if (not self.include_member_repos) and ("github-org-member" in event.tags): + return False, "include_member_repos is False" + return True + + async def handle_event(self, event): + # handle github profile + if event.type == "SOCIAL": + user = event.data.get("profile_name", "") + in_scope = False + if "github-org-member" in event.tags: + is_org = False + elif "github-org" in event.tags: + is_org = True + in_scope = True + else: + is_org, in_scope = await self.validate_org(user) + + # find repos from user/org (SOCIAL --> CODE_REPOSITORY) + repos = [] + if is_org: + if in_scope: + self.verbose(f"Searching for repos belonging to organization {user}") + repos = await self.query_org_repos(user) + else: + self.verbose(f"Organization {user} does not appear to be in-scope") + elif "github-org-member" in event.tags: + self.verbose(f"Searching for repos belonging to user {user}") + repos = await self.query_user_repos(user) + for repo_url in repos: + repo_event = self.make_event({"url": repo_url}, "CODE_REPOSITORY", source=event) + repo_event.scope_distance = event.scope_distance + self.emit_event(repo_event) + + # find members from org (SOCIAL --> SOCIAL) + if is_org and self.include_members: + self.verbose(f"Searching for any members belonging to {user}") + org_members = await self.query_org_members(user) + for member in org_members: + event_data = {"platform": "github", "profile_name": member, "url": f"https://github.com/{member}"} + member_event = self.make_event(event_data, "SOCIAL", tags="github-org-member", source=event) + self.emit_event(member_event) + + # find valid orgs from stub (ORG_STUB --> SOCIAL) + elif event.type == "ORG_STUB": + user = event.data + self.verbose(f"Validating whether the organization {user} is within our scope...") + is_org, in_scope = await self.validate_org(user) + if not is_org or not in_scope: + self.verbose(f"Unable to validate that {user} is in-scope, skipping...") + return + + event_data = {"platform": "github", "profile_name": user, "url": f"https://github.com/{user}"} + github_org_event = self.make_event(event_data, "SOCIAL", tags="github-org", source=event) + github_org_event.scope_distance = event.scope_distance + self.emit_event(github_org_event) + + async def query_org_repos(self, query): + repos = [] + url = f"{self.base_url}/orgs/{self.helpers.quote(query)}/repos?per_page=100&page=" + "{page}" + agen = self.helpers.api_page_iter(url, headers=self.headers, json=False) + try: + async for r in agen: + if r is None: + break + status_code = getattr(r, "status_code", 0) + if status_code == 403: + self.warning("Github is rate-limiting us (HTTP status: 403)") + break + if status_code != 200: + break + try: + j = r.json() + except Exception as e: + self.warning(f"Failed to decode JSON for {r.url} (HTTP status: {status_code}): {e}") + break + if not j: + break + for item in j: + html_url = item.get("html_url", "") + repos.append(html_url) + finally: + agen.aclose() + return repos + + async def query_org_members(self, query): + members = [] + url = f"{self.base_url}/orgs/{self.helpers.quote(query)}/members?per_page=100&page=" + "{page}" + agen = self.helpers.api_page_iter(url, headers=self.headers, json=False) + try: + async for r in agen: + if r is None: + break + status_code = getattr(r, "status_code", 0) + if status_code == 403: + self.warning("Github is rate-limiting us (HTTP status: 403)") + break + if status_code != 200: + break + try: + j = r.json() + except Exception as e: + self.warning(f"Failed to decode JSON for {r.url} (HTTP status: {status_code}): {e}") + break + if not j: + break + for item in j: + login = item.get("login", "") + members.append(login) + finally: + agen.aclose() + return members + + async def query_user_repos(self, query): + repos = [] + url = f"{self.base_url}/users/{self.helpers.quote(query)}/repos?per_page=100&page=" + "{page}" + agen = self.helpers.api_page_iter(url, headers=self.headers, json=False) + try: + async for r in agen: + if r is None: + break + status_code = getattr(r, "status_code", 0) + if status_code == 403: + self.warning("Github is rate-limiting us (HTTP status: 403)") + break + if status_code != 200: + break + try: + j = r.json() + except Exception as e: + self.warning(f"Failed to decode JSON for {r.url} (HTTP status: {status_code}): {e}") + break + if not j: + break + for item in j: + html_url = item.get("html_url", "") + repos.append(html_url) + finally: + agen.aclose() + return repos + + async def validate_org(self, org): + is_org = False + in_scope = False + url = f"{self.base_url}/orgs/{org}" + r = await self.helpers.request(url, headers=self.headers) + if r is None: + return is_org, in_scope + status_code = getattr(r, "status_code", 0) + if status_code == 403: + self.warning("Github is rate-limiting us (HTTP status: 403)") + return is_org, in_scope + if status_code == 200: + is_org = True + try: + json = r.json() + except Exception as e: + self.warning(f"Failed to decode JSON for {r.url} (HTTP status: {status_code}): {e}") + return is_org, in_scope + for k, v in json.items(): + if ( + isinstance(v, str) + and ( + self.helpers.is_dns_name(v, include_local=False) + or self.helpers.is_url(v) + or self.helpers.is_email(v) + ) + and self.scan.in_scope(v) + ): + self.verbose(f'Found in-scope key "{k}": "{v}" for {org}, it appears to be in-scope') + in_scope = True + break + return is_org, in_scope diff --git a/bbot/modules/internal/speculate.py b/bbot/modules/internal/speculate.py index 0a506393b..e37ba9e08 100644 --- a/bbot/modules/internal/speculate.py +++ b/bbot/modules/internal/speculate.py @@ -19,8 +19,10 @@ class speculate(BaseInternalModule): "IP_ADDRESS", "HTTP_RESPONSE", "STORAGE_BUCKET", + "SOCIAL", + "AZURE_TENANT", ] - produced_events = ["DNS_NAME", "OPEN_TCP_PORT", "IP_ADDRESS", "FINDING"] + produced_events = ["DNS_NAME", "OPEN_TCP_PORT", "IP_ADDRESS", "FINDING", "ORG_STUB"] flags = ["passive"] meta = {"description": "Derive certain event types from others by common sense"} @@ -37,6 +39,7 @@ async def setup(self): self.portscanner_enabled = any(["portscan" in m.flags for m in self.scan.modules.values()]) self.range_to_ip = True self.dns_resolution = self.scan.config.get("dns_resolution", True) + self.org_stubs_seen = set() port_string = self.config.get("ports", "80,443") @@ -120,6 +123,31 @@ async def handle_event(self, event): # storage buckets etc. self.helpers.cloud.speculate(event) + # ORG_STUB from TLD, SOCIAL, AZURE_TENANT + org_stubs = set() + if event.type == "DNS_NAME" and event.scope_distance == 0: + tldextracted = self.helpers.tldextract(event.data) + registered_domain = getattr(tldextracted, "registered_domain", "") + if registered_domain: + tld_stub = getattr(tldextracted, "domain", "") + if tld_stub: + org_stubs.add(tld_stub) + elif event.type == "SOCIAL": + stub = event.data.get("stub", "") + if stub: + org_stubs.add(stub.lower()) + elif event.type == "AZURE_TENANT": + tenant_names = event.data.get("tenant-names", []) + org_stubs.update(set(tenant_names)) + for stub in org_stubs: + stub_hash = hash(stub) + if stub_hash not in self.org_stubs_seen: + self.org_stubs_seen.add(stub_hash) + stub_event = self.make_event(stub, "ORG_STUB", source=event) + if event.scope_distance > 0: + stub_event.scope_distance = event.scope_distance + self.emit_event(stub_event) + async def filter_event(self, event): # don't accept IP_RANGE --> IP_ADDRESS events from self if str(event.module) == "speculate": diff --git a/bbot/modules/secretsdb.py b/bbot/modules/secretsdb.py index b5115c1fa..83f305c61 100644 --- a/bbot/modules/secretsdb.py +++ b/bbot/modules/secretsdb.py @@ -18,6 +18,8 @@ class secretsdb(BaseModule): "signatures": "File path or URL to YAML signatures", } deps_pip = ["pyyaml~=6.0"] + # accept any HTTP_RESPONSE including out-of-scope ones (such as from github_codesearch) + scope_distance_modifier = 3 async def setup(self): self.rules = [] diff --git a/bbot/modules/social.py b/bbot/modules/social.py index 14af427d1..cb9311d45 100644 --- a/bbot/modules/social.py +++ b/bbot/modules/social.py @@ -6,18 +6,18 @@ class social(BaseModule): watched_events = ["URL_UNVERIFIED"] produced_events = ["SOCIAL"] meta = {"description": "Look for social media links in webpages"} - flags = ["active", "safe", "social-enum"] + flags = ["passive", "safe", "social-enum"] social_media_regex = { - "linkedin": r"(?:https?:\/\/)?(?:www\.)?linkedin\.com\/(?:in|company)\/[a-zA-Z0-9-]+\/?", - "facebook": r"(?:https?:\/\/)?(?:www\.)?facebook\.com\/[a-zA-Z0-9\.]+\/?", - "twitter": r"(?:https?:\/\/)?(?:www\.)?twitter\.com\/[a-zA-Z0-9_]{1,15}\/?", - "github": r"(?:https?:\/\/)?(?:www\.)?github\.com\/[a-zA-Z0-9_-]+\/?", - "instagram": r"(?:https?:\/\/)?(?:www\.)?instagram\.com\/[a-zA-Z0-9_\.]+\/?", - "youtube": r"(?:https?:\/\/)?(?:www\.)?youtube\.com\/[a-zA-Z0-9_]+\/?", - "bitbucket": r"(?:https?:\/\/)?(?:www\.)?bitbucket\.org\/[a-zA-Z0-9_-]+\/?", - "gitlab": r"(?:https?:\/\/)?(?:www\.)?gitlab\.com\/[a-zA-Z0-9_-]+\/?", - "discord": r"(?:https?:\/\/)?(?:www\.)?discord\.gg\/[a-zA-Z0-9_-]+\/?", + "linkedin": r"(?:https?://)?(?:www.)?linkedin.com/(?:in|company)/([a-zA-Z0-9-]+)/?", + "facebook": r"(?:https?://)?(?:www.)?facebook.com/([a-zA-Z0-9.]+)/?", + "twitter": r"(?:https?://)?(?:www.)?twitter.com/([a-zA-Z0-9_]{1,15})/?", + "github": r"(?:https?://)?(?:www.)?github.com/([a-zA-Z0-9_-]+)/?", + "instagram": r"(?:https?://)?(?:www.)?instagram.com/([a-zA-Z0-9_.]+)/?", + "youtube": r"(?:https?://)?(?:www.)?youtube.com/([a-zA-Z0-9_]+)/?", + "bitbucket": r"(?:https?://)?(?:www.)?bitbucket.org/([a-zA-Z0-9_-]+)/?", + "gitlab": r"(?:https?://)?(?:www.)?gitlab.com/([a-zA-Z0-9_-]+)/?", + "discord": r"(?:https?://)?(?:www.)?discord.gg/([a-zA-Z0-9_-]+)/?", } scope_distance_modifier = 1 @@ -28,6 +28,11 @@ async def setup(self): async def handle_event(self, event): for platform, regex in self.compiled_regexes.items(): - for match in regex.findall(event.data): - social_media_links = {"platform": platform, "url": match} - self.emit_event(social_media_links, "SOCIAL", source=event) + for match in regex.finditer(event.data): + url = match.group() + if not url.startswith("http"): + url = f"https://{url}" + profile_name = match.groups()[0] + self.emit_event( + {"platform": platform, "url": url, "profile_name": profile_name}, "SOCIAL", source=event + ) diff --git a/bbot/modules/templates/github.py b/bbot/modules/templates/github.py new file mode 100644 index 000000000..bf5b43c6f --- /dev/null +++ b/bbot/modules/templates/github.py @@ -0,0 +1,38 @@ +from bbot.modules.templates.subdomain_enum import subdomain_enum + + +class github(subdomain_enum): + """ + A template module for use of the GitHub API + Inherited by several other github modules. + """ + + _qsize = 1 + base_url = "https://api.github.com" + + async def setup(self): + await super().setup() + self.api_key = None + self.headers = {} + for module_name in ("github", "github_codesearch", "github_org"): + module_config = self.scan.config.get("modules", {}).get(module_name, {}) + api_key = module_config.get("api_key", "") + if api_key: + self.api_key = api_key + self.headers = {"Authorization": f"token {self.api_key}"} + break + try: + await self.ping() + self.hugesuccess(f"API is ready") + return True + except Exception as e: + return None, f"Error with API ({str(e).strip()})" + if not self.api_key: + if self.auth_required: + return None, "No API key set" + return True + + async def ping(self): + url = f"{self.base_url}/zen" + response = await self.helpers.request(url, headers=self.headers) + assert getattr(response, "status_code", 0) == 200, response.text diff --git a/bbot/test/test_step_1/test_events.py b/bbot/test/test_step_1/test_events.py index 48d1d43d0..902320e23 100644 --- a/bbot/test/test_step_1/test_events.py +++ b/bbot/test/test_step_1/test_events.py @@ -153,6 +153,18 @@ async def test_events(events, scan, helpers, bbot_config): event5 = scan.make_event("4.5.6.7", source=event4) assert event5._scope_distance == 3 + url_1 = scan.make_event("https://127.0.0.1/asdf", "URL_UNVERIFIED", source=scan.root_event) + assert url_1.scope_distance == 1 + url_2 = scan.make_event("https://127.0.0.1/test", "URL_UNVERIFIED", source=url_1) + assert url_2.scope_distance == 1 + url_3 = scan.make_event("https://127.0.0.2/asdf", "URL_UNVERIFIED", source=url_1) + assert url_3.scope_distance == 2 + + org_stub_1 = scan.make_event("STUB1", "ORG_STUB", source=scan.root_event) + org_stub_1.scope_distance == 1 + org_stub_2 = scan.make_event("STUB2", "ORG_STUB", source=org_stub_1) + org_stub_2.scope_distance == 2 + # internal event tracking root_event = scan.make_event("0.0.0.0", dummy=True) internal_event1 = scan.make_event("1.2.3.4", source=root_event, internal=True) diff --git a/bbot/test/test_step_1/test_helpers.py b/bbot/test/test_step_1/test_helpers.py index b27ab4577..59cbbf87b 100644 --- a/bbot/test/test_step_1/test_helpers.py +++ b/bbot/test/test_step_1/test_helpers.py @@ -85,6 +85,7 @@ async def test_helpers_misc(helpers, scan, bbot_scanner, bbot_config, bbot_https assert not helpers.is_ip("127.0.0.0.1") assert helpers.is_dns_name("evilcorp.com") assert helpers.is_dns_name("evilcorp") + assert not helpers.is_dns_name("evilcorp", include_local=False) assert helpers.is_dns_name("ドメイン.テスト") assert not helpers.is_dns_name("127.0.0.1") assert not helpers.is_dns_name("dead::beef") diff --git a/bbot/test/test_step_2/module_tests/test_module_github.py b/bbot/test/test_step_2/module_tests/test_module_github.py deleted file mode 100644 index 220bf019d..000000000 --- a/bbot/test/test_step_2/module_tests/test_module_github.py +++ /dev/null @@ -1,100 +0,0 @@ -from .base import ModuleTestBase - - -class TestGithub(ModuleTestBase): - config_overrides = {"modules": {"github": {"api_key": "asdf"}}, "omit_event_types": [], "scope_report_distance": 1} - - async def setup_before_prep(self, module_test): - module_test.httpx_mock.add_response(url="https://api.github.com/zen") - module_test.httpx_mock.add_response( - url="https://api.github.com/search/code?per_page=100&type=Code&q=blacklanternsecurity.com&page=1", - json={ - "total_count": 214, - "incomplete_results": False, - "items": [ - { - "name": "main.go", - "path": "v2/cmd/cve-annotate/main.go", - "sha": "4aa7c9ec68acb4c603d4b9163bf7ed42de1939fe", - "url": "https://api.github.com/repositories/252813491/contents/v2/cmd/cve-annotate/main.go?ref=06f242e5fce3439b7418877676810cbf57934875", - "git_url": "https://api.github.com/repositories/252813491/git/blobs/4aa7c9ec68acb4c603d4b9163bf7ed42de1939fe", - "html_url": "https://github.com/projectdiscovery/nuclei/blob/06f242e5fce3439b7418877676810cbf57934875/v2/cmd/cve-annotate/main.go", - "repository": { - "id": 252813491, - "node_id": "MDEwOlJlcG9zaXRvcnkyNTI4MTM0OTE=", - "name": "nuclei", - "full_name": "projectdiscovery/nuclei", - "private": False, - "owner": { - "login": "projectdiscovery", - "id": 50994705, - "node_id": "MDEyOk9yZ2FuaXphdGlvbjUwOTk0NzA1", - "avatar_url": "https://avatars.githubusercontent.com/u/50994705?v=4", - "gravatar_id": "", - "url": "https://api.github.com/users/projectdiscovery", - "html_url": "https://github.com/projectdiscovery", - "followers_url": "https://api.github.com/users/projectdiscovery/followers", - "following_url": "https://api.github.com/users/projectdiscovery/following{/other_user}", - "gists_url": "https://api.github.com/users/projectdiscovery/gists{/gist_id}", - "starred_url": "https://api.github.com/users/projectdiscovery/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/projectdiscovery/subscriptions", - "organizations_url": "https://api.github.com/users/projectdiscovery/orgs", - "repos_url": "https://api.github.com/users/projectdiscovery/repos", - "events_url": "https://api.github.com/users/projectdiscovery/events{/privacy}", - "received_events_url": "https://api.github.com/users/projectdiscovery/received_events", - "type": "Organization", - "site_admin": False, - }, - "html_url": "https://github.com/projectdiscovery/nuclei", - "description": "Fast and customizable vulnerability scanner based on simple YAML based DSL.", - "fork": False, - "url": "https://api.github.com/repos/projectdiscovery/nuclei", - "forks_url": "https://api.github.com/repos/projectdiscovery/nuclei/forks", - "keys_url": "https://api.github.com/repos/projectdiscovery/nuclei/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/projectdiscovery/nuclei/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/projectdiscovery/nuclei/teams", - "hooks_url": "https://api.github.com/repos/projectdiscovery/nuclei/hooks", - "issue_events_url": "https://api.github.com/repos/projectdiscovery/nuclei/issues/events{/number}", - "events_url": "https://api.github.com/repos/projectdiscovery/nuclei/events", - "assignees_url": "https://api.github.com/repos/projectdiscovery/nuclei/assignees{/user}", - "branches_url": "https://api.github.com/repos/projectdiscovery/nuclei/branches{/branch}", - "tags_url": "https://api.github.com/repos/projectdiscovery/nuclei/tags", - "blobs_url": "https://api.github.com/repos/projectdiscovery/nuclei/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/projectdiscovery/nuclei/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/projectdiscovery/nuclei/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/projectdiscovery/nuclei/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/projectdiscovery/nuclei/statuses/{sha}", - "languages_url": "https://api.github.com/repos/projectdiscovery/nuclei/languages", - "stargazers_url": "https://api.github.com/repos/projectdiscovery/nuclei/stargazers", - "contributors_url": "https://api.github.com/repos/projectdiscovery/nuclei/contributors", - "subscribers_url": "https://api.github.com/repos/projectdiscovery/nuclei/subscribers", - "subscription_url": "https://api.github.com/repos/projectdiscovery/nuclei/subscription", - "commits_url": "https://api.github.com/repos/projectdiscovery/nuclei/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/projectdiscovery/nuclei/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/projectdiscovery/nuclei/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/projectdiscovery/nuclei/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/projectdiscovery/nuclei/contents/{+path}", - "compare_url": "https://api.github.com/repos/projectdiscovery/nuclei/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/projectdiscovery/nuclei/merges", - "archive_url": "https://api.github.com/repos/projectdiscovery/nuclei/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/projectdiscovery/nuclei/downloads", - "issues_url": "https://api.github.com/repos/projectdiscovery/nuclei/issues{/number}", - "pulls_url": "https://api.github.com/repos/projectdiscovery/nuclei/pulls{/number}", - "milestones_url": "https://api.github.com/repos/projectdiscovery/nuclei/milestones{/number}", - "notifications_url": "https://api.github.com/repos/projectdiscovery/nuclei/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/projectdiscovery/nuclei/labels{/name}", - "releases_url": "https://api.github.com/repos/projectdiscovery/nuclei/releases{/id}", - "deployments_url": "https://api.github.com/repos/projectdiscovery/nuclei/deployments", - }, - "score": 1.0, - } - ], - }, - ) - - def check(self, module_test, events): - assert any( - e.data - == "https://raw.githubusercontent.com/projectdiscovery/nuclei/06f242e5fce3439b7418877676810cbf57934875/v2/cmd/cve-annotate/main.go" - for e in events - ), "Failed to detect URL" diff --git a/bbot/test/test_step_2/module_tests/test_module_github_codesearch.py b/bbot/test/test_step_2/module_tests/test_module_github_codesearch.py new file mode 100644 index 000000000..16826b6c0 --- /dev/null +++ b/bbot/test/test_step_2/module_tests/test_module_github_codesearch.py @@ -0,0 +1,84 @@ +from .base import ModuleTestBase + + +class TestGithub_Codesearch(ModuleTestBase): + config_overrides = { + "modules": {"github_codesearch": {"api_key": "asdf", "limit": 1}}, + "omit_event_types": [], + "scope_report_distance": 1, + } + modules_overrides = ["github_codesearch", "httpx", "secretsdb"] + + github_file_endpoint = ( + "/projectdiscovery/nuclei/06f242e5fce3439b7418877676810cbf57934875/v2/cmd/cve-annotate/main.go" + ) + github_file_url = f"http://127.0.0.1:8888{github_file_endpoint}" + + async def setup_before_prep(self, module_test): + expect_args = {"method": "GET", "uri": self.github_file_endpoint} + respond_args = {"response_data": "-----BEGIN PGP PRIVATE KEY BLOCK-----"} + module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args) + + module_test.httpx_mock.add_response(url="https://api.github.com/zen") + module_test.httpx_mock.add_response( + url="https://api.github.com/search/code?per_page=100&type=Code&q=blacklanternsecurity.com&page=1", + json={ + "total_count": 214, + "incomplete_results": False, + "items": [ + { + "html_url": "https://github.com/projectdiscovery/nuclei/blob/06f242e5fce3439b7418877676810cbf57934875/v2/cmd/cve-annotate/main.go", + "repository": { + "html_url": "https://github.com/projectdiscovery/nuclei", + }, + }, + { + "html_url": "https://github.com/projectdiscovery/nuclei/blob/06f242e5fce3439b7418877676810cbf57934875/v2/cmd/cve-annotate/main.go2", + "repository": { + "html_url": "https://github.com/projectdiscovery/nuclei", + }, + }, + { + "html_url": "https://github.com/projectdiscovery/nuclei/blob/06f242e5fce3439b7418877676810cbf57934875/v2/cmd/cve-annotate/main.go3", + "repository": { + "html_url": "https://github.com/projectdiscovery/nuclei", + }, + }, + ], + }, + ) + + async def setup_after_prep(self, module_test): + module_test.module.github_raw_url = "http://127.0.0.1:8888/" + + def check(self, module_test, events): + assert 1 == len([e for e in events if e.type == "URL_UNVERIFIED"]) + assert 1 == len( + [ + e + for e in events + if e.type == "URL_UNVERIFIED" and e.data == self.github_file_url and e.scope_distance == 1 + ] + ), "Failed to emit URL_UNVERIFIED" + assert 1 == len( + [ + e + for e in events + if e.type == "CODE_REPOSITORY" + and e.data["url"] == "https://github.com/projectdiscovery/nuclei" + and e.scope_distance == 1 + ] + ), "Failed to emit CODE_REPOSITORY" + assert 1 == len( + [e for e in events if e.type == "URL" and e.data == self.github_file_url and e.scope_distance == 1] + ), "Failed to visit URL" + assert 1 == len( + [ + e + for e in events + if e.type == "HTTP_RESPONSE" and e.data["url"] == self.github_file_url and e.scope_distance == 1 + ] + ), "Failed to visit URL" + assert [ + e for e in events if e.type == "FINDING" and str(e.module) == "secretsdb" + ], "Failed to find secret in repo file" diff --git a/bbot/test/test_step_2/module_tests/test_module_github_org.py b/bbot/test/test_step_2/module_tests/test_module_github_org.py new file mode 100644 index 000000000..94e09ed68 --- /dev/null +++ b/bbot/test/test_step_2/module_tests/test_module_github_org.py @@ -0,0 +1,367 @@ +from .base import ModuleTestBase + + +class TestGithub_Org(ModuleTestBase): + config_overrides = {"modules": {"github_org": {"api_key": "asdf"}}} + modules_overrides = ["github_org", "speculate"] + + async def setup_before_prep(self, module_test): + module_test.httpx_mock.add_response(url="https://api.github.com/zen") + module_test.httpx_mock.add_response( + url="https://api.github.com/orgs/blacklanternsecurity", + json={ + "login": "blacklanternsecurity", + "id": 25311592, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI1MzExNTky", + "url": "https://api.github.com/orgs/blacklanternsecurity", + "repos_url": "https://api.github.com/orgs/blacklanternsecurity/repos", + "events_url": "https://api.github.com/orgs/blacklanternsecurity/events", + "hooks_url": "https://api.github.com/orgs/blacklanternsecurity/hooks", + "issues_url": "https://api.github.com/orgs/blacklanternsecurity/issues", + "members_url": "https://api.github.com/orgs/blacklanternsecurity/members{/member}", + "public_members_url": "https://api.github.com/orgs/blacklanternsecurity/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/25311592?v=4", + "description": "Security Organization", + "name": "Black Lantern Security", + "company": None, + "blog": "www.blacklanternsecurity.com", + "location": "Charleston, SC", + "email": None, + "twitter_username": None, + "is_verified": False, + "has_organization_projects": True, + "has_repository_projects": True, + "public_repos": 70, + "public_gists": 0, + "followers": 415, + "following": 0, + "html_url": "https://github.com/blacklanternsecurity", + "created_at": "2017-01-24T00:14:46Z", + "updated_at": "2022-03-28T11:39:03Z", + "archived_at": None, + "type": "Organization", + }, + ) + module_test.httpx_mock.add_response( + url="https://api.github.com/orgs/blacklanternsecurity/repos?per_page=100&page=1", + json=[ + { + "id": 459780477, + "node_id": "R_kgDOG2exfQ", + "name": "test_keys", + "full_name": "blacklanternsecurity/test_keys", + "private": False, + "owner": { + "login": "blacklanternsecurity", + "id": 79229934, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjc5MjI5OTM0", + "avatar_url": "https://avatars.githubusercontent.com/u/79229934?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/blacklanternsecurity", + "html_url": "https://github.com/blacklanternsecurity", + "followers_url": "https://api.github.com/users/blacklanternsecurity/followers", + "following_url": "https://api.github.com/users/blacklanternsecurity/following{/other_user}", + "gists_url": "https://api.github.com/users/blacklanternsecurity/gists{/gist_id}", + "starred_url": "https://api.github.com/users/blacklanternsecurity/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/blacklanternsecurity/subscriptions", + "organizations_url": "https://api.github.com/users/blacklanternsecurity/orgs", + "repos_url": "https://api.github.com/users/blacklanternsecurity/repos", + "events_url": "https://api.github.com/users/blacklanternsecurity/events{/privacy}", + "received_events_url": "https://api.github.com/users/blacklanternsecurity/received_events", + "type": "Organization", + "site_admin": False, + }, + "html_url": "https://github.com/blacklanternsecurity/test_keys", + "description": None, + "fork": False, + "url": "https://api.github.com/repos/blacklanternsecurity/test_keys", + "forks_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/forks", + "keys_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/teams", + "hooks_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/hooks", + "issue_events_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/issues/events{/number}", + "events_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/events", + "assignees_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/assignees{/user}", + "branches_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/branches{/branch}", + "tags_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/tags", + "blobs_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/statuses/{sha}", + "languages_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/languages", + "stargazers_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/stargazers", + "contributors_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/contributors", + "subscribers_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/subscribers", + "subscription_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/subscription", + "commits_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/contents/{+path}", + "compare_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/merges", + "archive_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/downloads", + "issues_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/issues{/number}", + "pulls_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/pulls{/number}", + "milestones_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/milestones{/number}", + "notifications_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/labels{/name}", + "releases_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/releases{/id}", + "deployments_url": "https://api.github.com/repos/blacklanternsecurity/test_keys/deployments", + "created_at": "2022-02-15T23:10:51Z", + "updated_at": "2023-09-02T12:20:13Z", + "pushed_at": "2023-10-19T02:56:46Z", + "git_url": "git://github.com/blacklanternsecurity/test_keys.git", + "ssh_url": "git@github.com:blacklanternsecurity/test_keys.git", + "clone_url": "https://github.com/blacklanternsecurity/test_keys.git", + "svn_url": "https://github.com/blacklanternsecurity/test_keys", + "homepage": None, + "size": 2, + "stargazers_count": 2, + "watchers_count": 2, + "language": None, + "has_issues": True, + "has_projects": True, + "has_downloads": True, + "has_wiki": True, + "has_pages": False, + "has_discussions": False, + "forks_count": 32, + "mirror_url": None, + "archived": False, + "disabled": False, + "open_issues_count": 2, + "license": None, + "allow_forking": True, + "is_template": False, + "web_commit_signoff_required": False, + "topics": [], + "visibility": "public", + "forks": 32, + "open_issues": 2, + "watchers": 2, + "default_branch": "main", + "permissions": {"admin": False, "maintain": False, "push": False, "triage": False, "pull": True}, + } + ], + ) + module_test.httpx_mock.add_response( + url="https://api.github.com/orgs/blacklanternsecurity/members?per_page=100&page=1", + json=[ + { + "login": "TheTechromancer", + "id": 20261699, + "node_id": "MDQ6VXNlcjIwMjYxNjk5", + "avatar_url": "https://avatars.githubusercontent.com/u/20261699?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/TheTechromancer", + "html_url": "https://github.com/TheTechromancer", + "followers_url": "https://api.github.com/users/TheTechromancer/followers", + "following_url": "https://api.github.com/users/TheTechromancer/following{/other_user}", + "gists_url": "https://api.github.com/users/TheTechromancer/gists{/gist_id}", + "starred_url": "https://api.github.com/users/TheTechromancer/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/TheTechromancer/subscriptions", + "organizations_url": "https://api.github.com/users/TheTechromancer/orgs", + "repos_url": "https://api.github.com/users/TheTechromancer/repos", + "events_url": "https://api.github.com/users/TheTechromancer/events{/privacy}", + "received_events_url": "https://api.github.com/users/TheTechromancer/received_events", + "type": "User", + "site_admin": False, + } + ], + ) + module_test.httpx_mock.add_response( + url="https://api.github.com/users/TheTechromancer/repos?per_page=100&page=1", + json=[ + { + "id": 688270318, + "node_id": "R_kgDOKQYr7g", + "name": "websitedemo", + "full_name": "TheTechromancer/websitedemo", + "private": False, + "owner": { + "login": "TheTechromancer", + "id": 20261699, + "node_id": "MDQ6VXNlcjIwMjYxNjk5", + "avatar_url": "https://avatars.githubusercontent.com/u/20261699?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/TheTechromancer", + "html_url": "https://github.com/TheTechromancer", + "followers_url": "https://api.github.com/users/TheTechromancer/followers", + "following_url": "https://api.github.com/users/TheTechromancer/following{/other_user}", + "gists_url": "https://api.github.com/users/TheTechromancer/gists{/gist_id}", + "starred_url": "https://api.github.com/users/TheTechromancer/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/TheTechromancer/subscriptions", + "organizations_url": "https://api.github.com/users/TheTechromancer/orgs", + "repos_url": "https://api.github.com/users/TheTechromancer/repos", + "events_url": "https://api.github.com/users/TheTechromancer/events{/privacy}", + "received_events_url": "https://api.github.com/users/TheTechromancer/received_events", + "type": "User", + "site_admin": False, + }, + "html_url": "https://github.com/TheTechromancer/websitedemo", + "description": None, + "fork": False, + "url": "https://api.github.com/repos/TheTechromancer/websitedemo", + "forks_url": "https://api.github.com/repos/TheTechromancer/websitedemo/forks", + "keys_url": "https://api.github.com/repos/TheTechromancer/websitedemo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/TheTechromancer/websitedemo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/TheTechromancer/websitedemo/teams", + "hooks_url": "https://api.github.com/repos/TheTechromancer/websitedemo/hooks", + "issue_events_url": "https://api.github.com/repos/TheTechromancer/websitedemo/issues/events{/number}", + "events_url": "https://api.github.com/repos/TheTechromancer/websitedemo/events", + "assignees_url": "https://api.github.com/repos/TheTechromancer/websitedemo/assignees{/user}", + "branches_url": "https://api.github.com/repos/TheTechromancer/websitedemo/branches{/branch}", + "tags_url": "https://api.github.com/repos/TheTechromancer/websitedemo/tags", + "blobs_url": "https://api.github.com/repos/TheTechromancer/websitedemo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/TheTechromancer/websitedemo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/TheTechromancer/websitedemo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/TheTechromancer/websitedemo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/TheTechromancer/websitedemo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/TheTechromancer/websitedemo/languages", + "stargazers_url": "https://api.github.com/repos/TheTechromancer/websitedemo/stargazers", + "contributors_url": "https://api.github.com/repos/TheTechromancer/websitedemo/contributors", + "subscribers_url": "https://api.github.com/repos/TheTechromancer/websitedemo/subscribers", + "subscription_url": "https://api.github.com/repos/TheTechromancer/websitedemo/subscription", + "commits_url": "https://api.github.com/repos/TheTechromancer/websitedemo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/TheTechromancer/websitedemo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/TheTechromancer/websitedemo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/TheTechromancer/websitedemo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/TheTechromancer/websitedemo/contents/{+path}", + "compare_url": "https://api.github.com/repos/TheTechromancer/websitedemo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/TheTechromancer/websitedemo/merges", + "archive_url": "https://api.github.com/repos/TheTechromancer/websitedemo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/TheTechromancer/websitedemo/downloads", + "issues_url": "https://api.github.com/repos/TheTechromancer/websitedemo/issues{/number}", + "pulls_url": "https://api.github.com/repos/TheTechromancer/websitedemo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/TheTechromancer/websitedemo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/TheTechromancer/websitedemo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/TheTechromancer/websitedemo/labels{/name}", + "releases_url": "https://api.github.com/repos/TheTechromancer/websitedemo/releases{/id}", + "deployments_url": "https://api.github.com/repos/TheTechromancer/websitedemo/deployments", + "created_at": "2023-09-07T02:18:28Z", + "updated_at": "2023-09-07T02:20:18Z", + "pushed_at": "2023-09-07T02:34:45Z", + "git_url": "git://github.com/TheTechromancer/websitedemo.git", + "ssh_url": "git@github.com:TheTechromancer/websitedemo.git", + "clone_url": "https://github.com/TheTechromancer/websitedemo.git", + "svn_url": "https://github.com/TheTechromancer/websitedemo", + "homepage": None, + "size": 1, + "stargazers_count": 0, + "watchers_count": 0, + "language": "HTML", + "has_issues": True, + "has_projects": True, + "has_downloads": True, + "has_wiki": True, + "has_pages": True, + "has_discussions": False, + "forks_count": 0, + "mirror_url": None, + "archived": False, + "disabled": False, + "open_issues_count": 0, + "license": None, + "allow_forking": True, + "is_template": False, + "web_commit_signoff_required": False, + "topics": [], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + } + ], + ) + + def check(self, module_test, events): + assert len(events) == 6 + assert 1 == len( + [ + e + for e in events + if e.type == "DNS_NAME" and e.data == "blacklanternsecurity.com" and e.scope_distance == 0 + ] + ), "Failed to emit target DNS_NAME" + assert 1 == len( + [e for e in events if e.type == "ORG_STUB" and e.data == "blacklanternsecurity" and e.scope_distance == 1] + ), "Failed to find ORG_STUB" + assert 1 == len( + [ + e + for e in events + if e.type == "SOCIAL" + and e.data["platform"] == "github" + and e.data["profile_name"] == "blacklanternsecurity" + and "github-org" in e.tags + and e.scope_distance == 1 + ] + ), "Failed to find blacklanternsecurity github" + assert 1 == len( + [ + e + for e in events + if e.type == "SOCIAL" + and e.data["platform"] == "github" + and e.data["profile_name"] == "TheTechromancer" + and "github-org-member" in e.tags + and e.scope_distance == 2 + ] + ), "Failed to find TheTechromancer github" + assert 1 == len( + [ + e + for e in events + if e.type == "CODE_REPOSITORY" + and e.data["url"] == "https://github.com/blacklanternsecurity/test_keys" + and e.scope_distance == 1 + ] + ), "Failed to find blacklanternsecurity github repo" + + +class TestGithub_Org_No_Members(TestGithub_Org): + config_overrides = {"modules": {"github_org": {"include_members": False}}} + + def check(self, module_test, events): + assert len(events) == 5 + assert 1 == len( + [ + e + for e in events + if e.type == "SOCIAL" + and e.data["platform"] == "github" + and e.data["profile_name"] == "blacklanternsecurity" + and "github-org" in e.tags + and e.scope_distance == 1 + ] + ), "Failed to find blacklanternsecurity github" + assert 0 == len( + [ + e + for e in events + if e.type == "SOCIAL" + and e.data["platform"] == "github" + and e.data["profile_name"] == "TheTechromancer" + ] + ), "Found TheTechromancer github" + + +class TestGithub_Org_MemberRepos(TestGithub_Org): + config_overrides = {"modules": {"github_org": {"include_member_repos": True}}} + + def check(self, module_test, events): + assert len(events) == 7 + assert 1 == len( + [ + e + for e in events + if e.type == "CODE_REPOSITORY" + and e.data["url"] == "https://github.com/TheTechromancer/websitedemo" + and e.scope_distance == 2 + ] + ), "Found to find TheTechromancer github repo" diff --git a/bbot/test/test_step_2/module_tests/test_module_social.py b/bbot/test/test_step_2/module_tests/test_module_social.py index 6771b1715..dbe07ffdb 100644 --- a/bbot/test/test_step_2/module_tests/test_module_social.py +++ b/bbot/test/test_step_2/module_tests/test_module_social.py @@ -7,8 +7,14 @@ class TestSocial(ModuleTestBase): async def setup_after_prep(self, module_test): expect_args = {"method": "GET", "uri": "/"} - respond_args = {"response_data": ''} + respond_args = { + "response_data": '' + } module_test.set_expect_requests(expect_args=expect_args, respond_args=respond_args) def check(self, module_test, events): assert any(e.type == "SOCIAL" and e.data["platform"] == "discord" for e in events) + assert any( + e.type == "SOCIAL" and e.data["platform"] == "github" and e.data["profile_name"] == "blacklanternsecurity" + for e in events + )