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
+ )