Compare commits

...

58 Commits
3.0.0 ... 3.1.1

Author SHA1 Message Date
Richard Mwewa
342a54caf1 Update config.py 2023-08-01 02:33:13 +02:00
Richard Mwewa
1c8583132b Merge pull request #18 from bellingcat/dev
Update octosuite.py
2023-06-05 16:58:10 +02:00
Richard Mwewa
61c0b12d8b Update octosuite.py 2023-05-22 14:55:02 +02:00
Richard Mwewa
9d88b66005 Merge pull request #17 from bellingcat/dev
Dev
2023-04-21 18:53:45 +02:00
Richard Mwewa
d1e9932967 Update octosuite.py
Added f-string to the Organisation profile Tree.
2023-04-21 18:42:53 +02:00
Richard Mwewa
595dcc5aac Update pyproject.toml
Added line `[tool.setuptools]`. Fixes error that might be returned during compilation if there's more than one directory in the project's root directory:

error: Multiple top-level packages discovered in a flat-layout
2023-04-16 01:12:47 +02:00
Richard Mwewa
6a535ae856 Created a setup_readline function for the code used to setup readline. The function has been moved to config.py, and will be called from main.py 2023-04-12 16:17:51 +02:00
Richard Mwewa
61188fe29e Update octosuite.py
Using subprocess.call() instead of os.system()
2023-04-12 16:00:48 +02:00
Richard Mwewa
cd6155b553 Update octosuite.py
Using subprocess.call() to list directories instead of os.system()
2023-04-12 15:52:50 +02:00
Richard Mwewa
f064f80e47 Delete images directory 2023-03-07 18:38:43 +02:00
Richard Mwewa
dae2226905 Delete images directory 2023-03-07 01:45:15 +02:00
Richard Mwewa
2a92723dc8 Delete setup.py 2023-03-01 23:25:42 +02:00
Richard Mwewa
af975a4e7a Update README.md 2023-02-25 03:09:50 +02:00
Richard Mwewa
4f96419fbd Update octosuite.py 2023-02-25 03:04:01 +02:00
Richard Mwewa
774c8d19a8 Update octosuite 2023-02-25 02:59:46 +02:00
Richard Mwewa
04f3bd89cf Update pyproject.toml 2023-02-24 03:29:50 +02:00
Richard Mwewa
3fbb8d8cbe Update pyproject.toml 2023-02-24 03:03:00 +02:00
Richard Mwewa
e836413719 Update pyproject.toml 2023-02-24 02:40:31 +02:00
Richard Mwewa
2ba4438f64 Update pyproject.toml 2023-02-23 01:32:37 +02:00
Richard Mwewa
6c76b8fb39 Update pyproject.toml 2023-02-22 21:49:52 +02:00
Richard Mwewa
803e3b8ebd Create pyproject.toml 2023-02-22 16:54:30 +02:00
Richard Mwewa
6f29f633ea Update octosuite.py 2023-02-22 16:53:58 +02:00
Richard Mwewa
a9c62ebf36 Merge pull request #14 from bellingcat/dev
Dev
2023-02-22 16:30:04 +02:00
Richard Mwewa
73bdc542fa Update banner.py 2023-02-22 16:29:05 +02:00
Richard Mwewa
386e77f375 Update setup.py 2023-02-22 16:28:32 +02:00
Richard Mwewa
033e758e9e Update octosuite.py 2023-02-22 16:27:56 +02:00
Richard Mwewa
796e8e943c Update octosuite.py 2023-02-22 16:20:26 +02:00
Richard Mwewa
3125c45efb Update octosuite.py 2023-02-22 16:13:07 +02:00
Richard Mwewa
01d6115540 Update octosuite.py 2023-02-22 16:05:20 +02:00
Richard Mwewa
a7da4f442d Update config.py 2023-02-22 16:01:42 +02:00
Richard Mwewa
f967134210 Merge pull request #13 from Weltolk/patch-1
bug fix
2023-02-22 15:46:41 +02:00
Weltolk
47b28b45e0 Merge branch 'bellingcat:master' into patch-1 2023-02-22 20:10:21 +08:00
Richard Mwewa
ca5245e0db Update README.md 2023-02-22 14:03:17 +02:00
Weltolk
d2c7ba7cd9 bug fix 2023-02-22 19:11:45 +08:00
Richard Mwewa
619ef8cf9f Update 2023-02-03 17:54:37 +02:00
Richard Mwewa
8364053f97 Update README.md 2023-01-26 21:33:29 +02:00
Richard Mwewa
1f288d503f Update octosuite.py 2023-01-25 23:00:05 +02:00
Richard Mwewa
57911bd68f Update octosuite.py 2023-01-25 22:50:19 +02:00
Richard Mwewa
264048e4bf Update README.md 2023-01-25 14:38:34 +02:00
Richard Mwewa
671fbdcaa4 Update README.md 2023-01-25 14:36:45 +02:00
Richard Mwewa
5fc5af4157 Update octosuite.py 2023-01-25 14:29:13 +02:00
Richard Mwewa
8bc799c829 Update README.md 2023-01-25 14:28:14 +02:00
Richard Mwewa
85cadefd50 v3.1.0 2023-01-25 14:11:25 +02:00
Richard Mwewa
646ea3cb51 Update README.md 2023-01-23 03:08:49 +02:00
Richard Mwewa
3e18a3301a Merge pull request from #9
fix: bad indentation leading to reference before assignment error #9
2023-01-22 17:02:39 +02:00
Richard Mwewa
d4b595d79e Merge pull request from #9
fix: bad indentation leading to reference before assignment error #9
2023-01-22 17:01:34 +02:00
Richard Mwewa
0f247d1dd8 Merge pull request from issue #9
fix: bad identation leading to reference before assigment error #9
2023-01-22 16:55:55 +02:00
Richard Mwewa
1b2c441237 Merge pull request #9 from albertollamaso/fix-var-assigment
fix: bad identation leading to `reference before assigment` error
2023-01-22 16:50:36 +02:00
albertollamaso
2723c2f8dd fix: bad identation leading to reference before assigment error 2023-01-22 13:59:26 +01:00
Richard Mwewa
7576fa64a1 Update setup.py 2023-01-22 03:17:01 +02:00
Richard Mwewa
79a97a7883 Update setup.py 2023-01-22 02:48:11 +02:00
Richard Mwewa
ac6ebc6d72 Update octosuite.py 2023-01-22 02:45:05 +02:00
Richard Mwewa
a12210cfa2 Fixed bug in octosuite.py 2023-01-22 01:17:50 +02:00
Richard Mwewa
47e0b4c64a Update banner.py 2023-01-16 21:02:30 +02:00
Richard Mwewa
c4847773a0 Update setup.py 2023-01-16 21:01:19 +02:00
Richard Mwewa
832e887302 Update main.py 2023-01-16 21:00:46 +02:00
Richard Mwewa
8487898f93 Merge pull request #8 from bellingcat/dev
Update octosuite.py
2023-01-02 22:32:13 +02:00
Richard Mwewa
13efce4f9c Update octosuite.py 2022-12-31 23:43:28 +02:00
20 changed files with 217 additions and 160 deletions

View File

@@ -1,6 +1,6 @@
![logo](https://user-images.githubusercontent.com/74001397/175805580-fffc96d4-e0ef-48bb-a55c-80b2da3e714d.png)
A framework for gathering open-source intelligence on GitHub users, repositories and organizations
A framework for gathering open-source intelligence on GitHub users, repositories and organisations
[![Upload Python Package](https://github.com/bellingcat/octosuite/actions/workflows/python-publish.yml/badge.svg)](https://github.com/bellingcat/octosuite/actions/workflows/python-publish.yml)
[![CodeQL](https://github.com/bellingcat/octosuite/actions/workflows/codeql.yml/badge.svg)](https://github.com/bellingcat/octosuite/actions/workflows/codeql.yml)
@@ -11,19 +11,19 @@ A framework for gathering open-source intelligence on GitHub users, repositories
![GitHub repo size](https://img.shields.io/github/repo-size/bellingcat/octosuite?style=flat&logo=github)
![octosuite_gui_exe (2)](https://user-images.githubusercontent.com/74001397/186889610-4530ee26-d3c6-46fc-8c92-8709f89617fd.png "Octosuite' about window")
![2023-01-23_01-01](https://user-images.githubusercontent.com/74001397/213950701-44b3f98b-89e1-443a-abb5-1be8969b611f.png "Octosuite about")
![octosuite_gui_exe (4)](https://user-images.githubusercontent.com/74001397/186889897-c1c17fac-fddc-4967-9084-39cfe2d1307f.png "Octosuite user profile window")
![Screen Shot 2023-01-26 at 9 27 22 PM](https://user-images.githubusercontent.com/74001397/214932206-40ec42ba-4fe8-4115-b2dd-52c4d7be9b5c.png)
# Wiki
[Refer to the Wiki](https://github.com/bellingcat/octosuite/wiki) for installation instructions, in addition to all other documentation.
# Features
- [x] Fetches an organization's profile information
- [x] Fetches an organisation's profile information
- [x] Fetches an oganization's events
- [x] Returns an organization's repositories
- [x] Returns an organization's public members
- [x] Returns an organisation's repositories
- [x] Returns an organisation's public members
- [x] Fetches a repository's information
- [x] Returns a repository's contributors
- [x] Returns a repository's languages
@@ -33,24 +33,28 @@ A framework for gathering open-source intelligence on GitHub users, repositories
- [x] Returns a list of files in a specified path of a repository
- [x] Fetches a user's profile information
- [x] Returns a user's gists
- [x] Returns organizations that a user owns/belongs to
- [x] Returns organisations that a user owns/belongs to
- [x] Fetches a user's events
- [x] Fetches a list of users followed by the target
- [x] Fetches a user's followers
- [x] Checks if user A follows user B
- [x] Checks if user is a public member of an organizations
- [x] Returns a user's subscriptions
- [x] Checks if user is a public member of an organisations
- [x] Gets a user's subscriptions
- [x] Gets a user's events
- [x] Searches users
- [x] Searches repositories
- [x] Searches topics
- [x] Searches issues
- [x] Searches commits
- [x] Automatically logs network activity (.logs folder)
- [x] User can view, read and delete logs
- [x] Automatically logs network/user activity (.logs folder)
- [x] User can manage logs (view, read, delete)
- [x] Results can be saved to a .csv file (varies)
- [x] User can manage csv files (view, read, delete)
- [x] All the above can be used with command-line arguments (PyPI Package only)
- [x] ...And more
- [x] And more...
# TODO
- [ ] Rewrite the GUI in Visual Basic .NET (in progress)
## Note
> Octosuite automatically logs network and user activity of each session, the logs are saved by date and time in the .logs folder
@@ -59,6 +63,10 @@ A framework for gathering open-source intelligence on GitHub users, repositories
# License
![license](https://user-images.githubusercontent.com/74001397/137917929-2f2cdb0c-4d1d-4e4b-9f0d-e01589e027b5.png)
# Credits
* The code used for finding emails from usernames is taken from [Somdev Sangwan](https://github.com/s0md3v)'s [Zen](https://github.com/s0md3v/zen)
# Donations
If you like OctoSuite and would like to show support, you can Buy A Coffee for the developer using the button below

View File

@@ -1 +0,0 @@

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

View File

@@ -4,7 +4,7 @@ from octosuite.config import red, white, green, reset, Tree
# banner.py
# This file holds the program's banner and version tag
version_tag = "3.0.0"
version_tag = "3.1.1"
def banner():
@@ -16,7 +16,7 @@ def banner():
| |.----.| |_.-----.| __|.--.--.|__| |_.-----.
| - || __|| _| _ ||__ || | || | _| -__|
|_______||____||____|_____||_______||_____||__|____|_____|
v{version_tag}#dev
v{version_tag}
{white}— Advanced Github {red}OSINT{white} Framework

View File

@@ -1,3 +1,4 @@
import os
import psutil
import platform
import argparse
@@ -6,6 +7,7 @@ from rich.text import Text
from rich.table import Table
from datetime import datetime
from rich import print as xprint
from rich.markdown import Markdown
from rich.prompt import Prompt, Confirm
@@ -24,14 +26,14 @@ def usage():
octosuite --method user_repos --username <username>
Get Organi[sz]ation Profile Info
Get Organisation Profile Info
-----------------------------
octosuite --method org_profile --organization <organization_name>
octosuite --method org_profile --organisation <organisation_name>
Get Organi[sz]ation Repos
-----------------------------
octosuite --method org_repos --organization <organization_name>
octosuite --method org_repos --organisation <organisation_name>
Get Repo Profile Info
@@ -124,7 +126,7 @@ def usage():
def create_parser():
parser = argparse.ArgumentParser(description='OCTOSUITE: Advanced GitHub osint framework — by Richard Mwewa | https://about.me/rly0nheart', usage=usage())
parser.add_argument('-m', '--method', help='method', choices=['user_profile', 'user_repos', 'user_gists', 'user_orgs', 'user_events',
parser.add_argument('-m', '--method', help='method', choices=['user_email', 'user_profile', 'user_repos', 'user_gists', 'user_orgs', 'user_events',
'user_subscriptions', 'user_following', 'user_followers', 'user_follows',
'org_profile', 'org_repos', 'org_events', 'org_member',
'repo_profile', 'repo_contributors', 'repo_stargazers', 'repo_forks',
@@ -133,7 +135,7 @@ def create_parser():
'clear_logs', 'view_csv', 'read_csv', 'delete_csv', 'clear_csv', 'about', 'author'])
parser.add_argument('-u', '--username', help='username')
parser.add_argument('-uB', '--username_b', help='username_B (used with user_follows)')
parser.add_argument('-o', '--organization', '--organisation', help='organi[sz]ation name')
parser.add_argument('-o', '--organisation', '--organization', help='organisation name')
parser.add_argument('-r', '--repository', help='repository name')
parser.add_argument('-p', '--path_name', help='path name (used with repo_path_contents)')
parser.add_argument('-q', '--query', help='query (used with search methods)')
@@ -145,6 +147,28 @@ def create_parser():
return parser
# Setup readline
def setup_readline():
if os.name == "nt":
try:
from pyreadline3 import Readline
except ImportError:
subprocess.run(['pip3', 'install', 'pyreadline3'], shell=False)
readline = Readline()
else:
import readline
def completer(text, state):
options = [i for i in commands if i.startswith(text)]
if state < len(options):
return options[state]
else:
return None
readline.parse_and_bind("tab: complete")
readline.set_completer(completer)
parser = create_parser()
args = parser.parse_args()

View File

@@ -11,14 +11,14 @@ from octosuite.message_prefixes import PROMPT, WARNING, POSITIVE, NEGATIVE, INFO
def log_org_profile(response):
org_profile_fields = ['Profile photo', 'Name', 'Username', 'ID', 'Node ID', 'Email', 'About', 'Location', 'Blog',
'Followers', 'Following', 'Twitter handle', 'Gists', 'Repositories', 'Account type',
'Is verified?', 'Has organization projects?', 'Has repository projects?', 'Created at',
'Is verified?', 'Has organisation projects?', 'Has repository projects?', 'Created at',
'Updated at']
org_profile_row = [response.json()['avatar_url'], response.json()['name'], response.json()['login'],
response.json()['id'], response.json()['node_id'], response.json()['email'],
response.json()['description'], response.json()['location'], response.json()['blog'],
response.json()['followers'], response.json()['following'], response.json()['twitter_username'],
response.json()['public_gists'], response.json()['public_repos'], response.json()['type'],
response.json()['is_verified'], response.json()['has_organization_projects'],
response.json()['is_verified'], response.json()['has_organisation_projects'],
response.json()['has_repository_projects'], response.json()['created_at'],
response.json()['updated_at']]
@@ -34,7 +34,7 @@ def log_org_profile(response):
# Creating a .csv file of a user' profile
def log_user_profile(response):
user_profile_fields = ['Profile photo', 'Name', 'Username', 'ID', 'Node ID', 'Bio', 'Blog', 'Location', 'Followers',
'Following', 'Twitter handle', 'Gists', 'Repositories', 'Organization', 'Is hireable?',
'Following', 'Twitter handle', 'Gists', 'Repositories', 'organisation', 'Is hireable?',
'Is site admin?', 'Joined at', 'Updated at']
user_profile_row = [response.json()['avatar_url'], response.json()['name'], response.json()['login'],
response.json()['id'], response.json()['node_id'], response.json()['bio'],
@@ -184,12 +184,12 @@ def log_repo_contributors(contributor, repo_name):
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
# Create .csv for organization' events
def log_repo_events(event, organization):
# Create .csv for organisation' events
def log_repo_events(event, organisation):
org_event_fields = ['ID', 'Type', 'Created at', 'Payload']
org_event_row = [event['id'], event['type'], event['created_at'], event['payload']]
with open(os.path.join("output", f"{organization}_event_{event['id']}.csv"), 'w') as file:
with open(os.path.join("output", f"{organisation}_event_{event['id']}.csv"), 'w') as file:
write_csv = csv.writer(file)
write_csv.writerow(org_event_fields)
write_csv.writerow(org_event_row)
@@ -198,8 +198,8 @@ def log_repo_events(event, organization):
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
# Create .csv for organization' repositories
def log_org_repos(repository, organization):
# Create .csv for organisation' repositories
def log_org_repos(repository, organisation):
org_repo_fields = ['Name', 'ID', 'About', 'Forks', 'Stars', 'Watchers', 'License', 'Branch', 'Visibility',
'Language(s)', 'Open issues', 'Topics', 'Homepage', 'Clone URL', 'SSH URL', 'Is fork?',
'Is forkable?', 'Is private?', 'Is archived?', 'Is template?', 'Has wiki?', 'Has pages?',
@@ -213,7 +213,7 @@ def log_org_repos(repository, organization):
repository['has_projects'], repository['has_issues'], repository['has_downloads'],
repository['pushed_at'], repository['created_at'], repository['updated_at']]
with open(os.path.join("output", f"{repository['name']}_repository_of_{organization}.csv"), 'w') as file:
with open(os.path.join("output", f"{repository['name']}_repository_of_{organisation}.csv"), 'w') as file:
write_csv = csv.writer(file)
write_csv.writerow(org_repo_fields)
write_csv.writerow(org_repo_row)
@@ -335,13 +335,13 @@ def log_user_subscriptions(repository, username):
xprint(f"{POSITIVE} {logged_to_csv.format(file.name)}")
# .csv for user organizations
def log_user_orgs(organization, username):
# .csv for user organisations
def log_user_orgs(organisation, username):
user_org_fields = ['Profile photo', 'Name', 'ID', 'Node ID', 'URL', 'About']
user_org_row = [organization['avatar_url'], organization['login'], organization['id'], organization['node_id'],
organization['url'], organization['description']]
user_org_row = [organisation['avatar_url'], organisation['login'], organisation['id'], organisation['node_id'],
organisation['url'], organisation['description']]
with open(os.path.join("output", f"{organization['login']}_{username}.csv"), 'w') as file:
with open(os.path.join("output", f"{organisation['login']}_{username}.csv"), 'w') as file:
write_csv = csv.writer(file)
write_csv.writerow(user_org_fields)
write_csv.writerow(user_org_row)

View File

@@ -67,9 +67,10 @@ def user_command():
user_cmd_table = Table(show_header=True, header_style=header_title)
user_cmd_table.add_column("Command", style="dim")
user_cmd_table.add_column("Description")
user_cmd_table.add_row("email", "Return a target's email")
user_cmd_table.add_row("profile", "Get a target's profile info")
user_cmd_table.add_row("gists", "Return a users's gists")
user_cmd_table.add_row("org", "Return organizations that a target belongs to/owns")
user_cmd_table.add_row("orgs", "Return organisations that a target belongs to/owns")
user_cmd_table.add_row("repos", "Return a target's repositories")
user_cmd_table.add_row("events", "Return a target's events")
user_cmd_table.add_row("follows", "Check if user(A) follows user(B)")
@@ -86,13 +87,13 @@ def org_command():
org_cmd_table = Table(show_header=True, header_style=header_title)
org_cmd_table.add_column("Command", style="dim")
org_cmd_table.add_column("Description")
org_cmd_table.add_row("profile", "Get a target organization' profile info")
org_cmd_table.add_row("repos", "Return a target organization' repositories")
org_cmd_table.add_row("events", "Return a target organization' events")
org_cmd_table.add_row("member", "Check if a specified user is a public member of the target organization")
org_cmd_table.add_row("profile", "Get a target organisation' profile info")
org_cmd_table.add_row("repos", "Return a target organisation' repositories")
org_cmd_table.add_row("events", "Return a target organisation' events")
org_cmd_table.add_row("member", "Check if a specified user is a public member of the target organisation")
syntax = f"{green}org:<command>{reset}"
xprint(f"{usage_text.format(syntax, 'organization investigation(s)')}")
xprint(f"{usage_text.format(syntax, 'organisation investigation(s)')}")
xprint(org_cmd_table)
@@ -158,7 +159,7 @@ def help_command():
help_sub_cmd_table.add_column("Description")
help_sub_cmd_table.add_row("csv", "List all csv management commands")
help_sub_cmd_table.add_row("logs", "List all logs management commands")
help_sub_cmd_table.add_row("org", "List all organization investigation commands")
help_sub_cmd_table.add_row("org", "List all organisation investigation commands")
help_sub_cmd_table.add_row("user", "List all users investigation commands")
help_sub_cmd_table.add_row("repo", "List all repository investigation commands")
help_sub_cmd_table.add_row("search", "List all target discovery commands")

View File

@@ -14,7 +14,7 @@ file_downloading = "Downloading: {}"
file_downloaded = "Downloaded: downloads/{}"
info_not_found = "Information not found: {}, {}, {}"
user_not_found = "User not found: @{}"
org_not_found = "Organization not found: @{}"
org_not_found = "organisation not found: @{}"
repo_or_user_not_found = "Repository or User not found: {}, @{}"
prompt_log_csv = "Would you like to log this output to a .csv file?"
logged_to_csv = "Output logged: {}"

View File

@@ -3,10 +3,10 @@ from octosuite.octosuite import * # I drifted away from the 'pythonic way' here
def octosuite():
setup_readline()
try:
run = Octosuite()
path_finder()
clear_screen()
configure_logging()
check_updates()
if args.method:

View File

@@ -1,5 +1,6 @@
#!usr/bin/python
import re
import os
import sys
import shutil
@@ -9,10 +10,10 @@ import requests
import platform
import subprocess
from datetime import datetime
from requests.auth import HTTPBasicAuth
from octosuite.banner import version_tag, banner
from octosuite.config import Tree, Text, Table, Prompt, Confirm, xprint, create_parser, args, red, white, green, yellow, header_title, reset
from octosuite.message_prefixes import ERROR, WARNING, PROMPT, POSITIVE, NEGATIVE, INFO # wondering why I name all the variables instead of just using the * wildcard?, because it's the pythonic way lol
# seriously now, the reason why I am doing this, is so that you know exactly what I am importing from a named module :)
from octosuite.config import Tree, Text, Table, Prompt, Confirm, Markdown, xprint, create_parser, setup_readline, args, red, white, green, yellow, header_title, reset
from octosuite.message_prefixes import ERROR, WARNING, PROMPT, POSITIVE, NEGATIVE, INFO
from octosuite.helper import help_command, source_command, search_command, user_command, repo_command, \
logs_command, csv_command, org_command, source, org, repo, user, search, logs, csv
from octosuite.log_roller import ctrl_c, error, session_opened, session_closed, viewing_logs, viewing_csv, \
@@ -25,26 +26,6 @@ from octosuite.csv_loggers import log_org_profile, log_user_profile, log_repo_pr
log_commits_search
if os.name == "nt":
try:
from pyreadline3 import Readline
except ImportError:
subprocess.run(['pip3', 'install', 'pyreadline3'])
readline = Readline()
else:
import readline
def completer(text, state):
options = [i for i in commands if i.startswith(text)]
if state < len(options):
return options[state]
else:
return None
readline.parse_and_bind("tab: complete")
readline.set_completer(completer)
# path_finder()
# This function is responsible for creating/checking the availability of the (.logs, output, downloads) folders,
# enabling logging to automatically log network/user activity to a file, and logging the start of a session.
@@ -72,15 +53,20 @@ def configure_logging():
# if it does, it means the program is up-to-date.
# If it doesn't match, notify the user about a new release
def check_updates():
global markdown_release_notes
response = requests.get("https://api.github.com/repos/bellingcat/octosuite/releases/latest").json()
if response['tag_name'] == version_tag:
pass
else:
xprint(f"[{green}UPDATE{reset}] A new release of Octosuite is available ({response['tag_name']}). Run 'pip install --upgrade octosuite' to get the updates.\n")
raw_release_notes = response['body']
markdown_release_notes = Markdown(raw_release_notes)
xprint(f"[{green}UPDATE{reset}] A new release of Octosuite is available ({response['tag_name']}).\n")
xprint(markdown_release_notes)
def list_dir_and_files():
os.system('dir' if os.name == 'nt' else 'ls')
subprocess.call('cmd.exe /c dir' if os.name == "nt" else 'ls')
# Delete a specified csv file
@@ -188,30 +174,37 @@ def exit_session():
def clear_screen():
# Using 'cls' on Windows machines to clear the screen,
# otherwise, use 'clear'
os.system('cls' if os.name == 'nt' else 'clear')
subprocess.call('cmd.exe /c cls' if os.name == "nt" else 'clear')
def about():
about_text = f"""
OCTOSUITE © 2023 Richard Mwewa
An advanced and lightning fast framework for gathering open-source intelligence on GitHub users and organizations.
Whats new in v{version_tag}?
[{green}IMPROVED{reset}] Octosuite will now also run with command line arguments (use 'octosuite --help', to show usage)
[{green}IMPROVED{reset}] Users will now be able to view previous commands with the ↑ ↓ arrow keys (for Windows systems)
[{green}IMPROVED{reset}] Removed width from tables, so that they can auto adjust
[{green}ADDED{reset}] Added the 'ls' command, which will be used to list all files and directories of the specified directory (beta)
[{green}ADDED{reset}] Added the 'cd' command, which will be used to move to a specified directory (beta)
[{green}ADDED{reset}] Added the 'log:clear' command, which will be used to clear all logs
[{green}ADDED{reset}] Added the 'csv:clear' command, which will be used to clear all csv files
[{green}GUI{reset}] The GUI will now come as a standalone executable
An advanced and lightning fast framework for gathering open-source intelligence on GitHub users and organisations.
Read the wiki: https://github.com/bellingcat/octosuite/wiki
GitHub REST API documentation: https://docs.github.com/rest
"""
xprint(about_text)
xprint(markdown_release_notes)
def get_email_from_contributor(username, repo, contributor):
response = requests.get(f"https://github.com/{username}/{repo}/commits?author={contributor}",
auth=HTTPBasicAuth(username, '')).text
latest_commit = re.search(rf'href="/{username}/{repo}/commit/(.*?)"', response)
if latest_commit:
latest_commit = latest_commit.group(1)
else:
latest_commit = 'dummy'
commit_details = requests.get(f"https://github.com/{username}/{repo}/commit/{latest_commit}.patch",
auth=HTTPBasicAuth(username, '')).text
email = re.search(r'<(.*)>', commit_details)
if email:
email = email.group(1)
return email
class Octosuite:
@@ -250,6 +243,7 @@ class Octosuite:
("repo:issues", self.repo_issues),
("repo:releases", self.repo_releases),
("user", user),
("user:email", self.get_user_email),
("user:repos", self.user_repos),
("user:gists", self.user_gists),
("user:orgs", self.user_orgs),
@@ -278,6 +272,7 @@ class Octosuite:
# Arguments map will be used to run Octosuite with argparse
self.argument_map = [("user_profile", self.user_profile),
("user_email", self.get_user_email),
("user_repos", self.user_repos),
("user_gists", self.user_gists),
("user_orgs", self.user_orgs),
@@ -322,12 +317,12 @@ class Octosuite:
'sha': 'SHA',
'html_url': 'URL'}
# Organization attributes
# organisation attributes
self.org_attrs = ['avatar_url', 'login', 'id', 'node_id', 'email', 'description', 'blog', 'location',
'followers',
'following', 'twitter_username', 'public_gists', 'public_repos', 'type', 'is_verified',
'has_organization_projects', 'has_repository_projects', 'created_at', 'updated_at']
# Organization attribute dictionary
'has_organisation_projects', 'has_repository_projects', 'created_at', 'updated_at']
# organisation attribute dictionary
self.org_attr_dict = {'avatar_url': 'Profile Photo',
'login': 'Username',
'id': 'ID',
@@ -343,7 +338,7 @@ class Octosuite:
'public_repos': 'Repositories',
'type': 'Account type',
'is_verified': 'Is verified?',
'has_organization_projects': 'Has organization projects?',
'has_organisation_projects': 'Has organisation projects?',
'has_repository_projects': 'Has repository projects?',
'created_at': 'Created at',
'updated_at': 'Updated at'}
@@ -419,7 +414,7 @@ class Octosuite:
'twitter_username': 'Twitter Handle',
'public_gists': 'Gists (public)',
'public_repos': 'Repositories (public)',
'company': 'Organization',
'company': 'organisation',
'hireable': 'Is hireable?',
'site_admin': 'Is site admin?',
'created_at': 'Joined at',
@@ -503,7 +498,7 @@ class Octosuite:
'created_at': 'Created at',
'updated_at': 'Updated at'}
# User organizations attributes
# User organisations attributes
self.user_orgs_attrs = ['avatar_url', 'id', 'node_id', 'url', 'description']
self.user_orgs_attr_dict = {'avatar_url': 'Profile Photo',
'id': 'ID',
@@ -517,17 +512,39 @@ class Octosuite:
'About.me': 'https://about.me/rly0nheart',
'Buy Me A Coffee': 'https://buymeacoffee.com/189381184'}
# Fetching organization info
def org_profile(self):
if args.organization:
organization = args.organization
def get_repos_from_username(self, username):
response = requests.get(f"{self.endpoint}/users/{username}/repos?per_page=100&sort=pushed",
auth=HTTPBasicAuth(username, '')).text
repositories = re.findall(rf'"full_name":"{username}/(.*?)",.*?"fork":(.*?),', response)
unforked_repos = []
for repository in repositories:
if repository[1] == 'false':
unforked_repos.append(repository[0])
return unforked_repos
def get_user_email(self):
if args.username:
username = args.username
else:
organization = Prompt.ask(f"{white}@{green}Organi[sz]ation{reset}")
response = requests.get(f"{self.endpoint}/orgs/{organization}")
username = Prompt.ask(f"{white}@{green}Username{reset}")
repos = self.get_repos_from_username(username)
for repo in repos:
email = get_email_from_contributor(username, repo, username)
if email:
xprint(f"{username}: {email}")
break
# Fetching organisation info
def org_profile(self):
if args.organisation:
organisation = args.organisation
else:
organisation = Prompt.ask(f"{white}@{green}Organisation{reset}")
response = requests.get(f"{self.endpoint}/orgs/{organisation}")
if response.status_code == 404:
xprint(f"{NEGATIVE} {org_not_found.format(organization)}")
xprint(f"{NEGATIVE} {org_not_found.format(organisation)}")
elif response.status_code == 200:
org_profile_tree = Tree("\n" + response.json()['name'])
org_profile_tree = Tree(f"\n{response.json()['name']}")
for attr in self.org_attrs:
org_profile_tree.add(f"{self.org_attr_dict[attr]}: {response.json()[attr]}")
xprint(org_profile_tree)
@@ -547,7 +564,7 @@ class Octosuite:
if response.status_code == 404:
xprint(f"{NEGATIVE} {user_not_found.format(username)}")
elif response.status_code == 200:
user_profile_tree = Tree("\n" + response.json()['name'])
user_profile_tree = Tree(f"\n{response.json()['name']}")
for attr in self.profile_attrs:
user_profile_tree.add(f"{self.profile_attr_dict[attr]}: {response.json()[attr]}")
xprint(user_profile_tree)
@@ -570,7 +587,7 @@ class Octosuite:
if response.status_code == 404:
xprint(f"{NEGATIVE} {repo_or_user_not_found.format(repo_name, username)}")
elif response.status_code == 200:
repo_profile_tree = Tree("\n" + response.json()['full_name'])
repo_profile_tree = Tree(f"\n{response.json()['full_name']}")
for attr in self.repo_attrs:
repo_profile_tree.add(f"{self.repo_attr_dict[attr]}: {response.json()[attr]}")
xprint(repo_profile_tree)
@@ -737,17 +754,17 @@ class Octosuite:
else:
xprint(response.json())
# Fetching organization repositories
# Fetching organisation repositories
def org_repos(self):
if args.organization and args.limit:
organization = args.organization
if args.organisation and args.limit:
organisation = args.organisation
limit = args.limit
else:
organization = Prompt.ask(f"{white}@{green}Organi[sz]ation{reset}")
limit = Prompt.ask(limit_output.format("organization repositories"))
response = requests.get(f"{self.endpoint}/orgs/{organization}/repos?per_page={limit}")
organisation = Prompt.ask(f"{white}@{green}Organisation{reset}")
limit = Prompt.ask(limit_output.format("organisation repositories"))
response = requests.get(f"{self.endpoint}/orgs/{organisation}/repos?per_page={limit}")
if response.status_code == 404:
xprint(f"{NEGATIVE} {org_not_found.format(organization)}")
xprint(f"{NEGATIVE} {org_not_found.format(organisation)}")
elif response.status_code == 200:
for repository in response.json():
repos_tree = Tree("\n" + repository['full_name'])
@@ -756,43 +773,43 @@ class Octosuite:
xprint(repos_tree)
if args.log_csv or Prompt.ask(f"{PROMPT} {prompt_log_csv}") == "yes":
log_org_repos(repository, organization)
log_org_repos(repository, organisation)
else:
xprint(response.json())
# organization events
# organisation events
def org_events(self):
if args.organization and args.limit:
organization = args.organization
if args.organisation and args.limit:
organisation = args.organisation
limit = args.limit
else:
organization = Prompt.ask(f"{white}@{green}Organi[sz]ation{reset}")
limit = Prompt.ask(limit_output.format("organization events"))
response = requests.get(f"{self.endpoint}/orgs/{organization}/events?per_page={limit}")
organisation = Prompt.ask(f"{white}@{green}Organisation{reset}")
limit = Prompt.ask(limit_output.format("organisation events"))
response = requests.get(f"{self.endpoint}/orgs/{organisation}/events?per_page={limit}")
if response.status_code == 404:
xprint(f"{NEGATIVE} {org_not_found.format(organization)}")
xprint(f"{NEGATIVE} {org_not_found.format(organisation)}")
elif response.status_code == 200:
for event in response.json():
events_tree = Tree("\n" + event['id'])
events_tree.add(f"Type: {event['type']}")
events_tree.add(f"Created at: {event['created_at']}")
xprint(events_tree)
xprint(event['payload'])
# log_org_events(event, organization)
xprint(events_tree)
xprint(event['payload'])
# log_org_events(event, organisation)
else:
xprint(response.json())
# organization member
# organisation member
def org_member(self):
if args.organization and args.username:
organization = args.organization
if args.organisation and args.username:
organisation = args.organisation
username = args.username
else:
organization = Prompt.ask(f"{white}@{green}Organi[sz]ation{reset}")
organisation = Prompt.ask(f"{white}@{green}Organisation{reset}")
username = Prompt.ask(f"{white}@{green}Username{reset}")
response = requests.get(f"{self.endpoint}/orgs/{organization}/public_members/{username}")
response = requests.get(f"{self.endpoint}/orgs/{organisation}/public_members/{username}")
if response.status_code == 204:
xprint(f"{POSITIVE} User ({username}) is a public member of the organization -> ({organization})")
xprint(f"{POSITIVE} User ({username}) is a public member of the organisation -> ({organisation})")
else:
xprint(f"{NEGATIVE} {response.json()['message']}")
@@ -844,28 +861,28 @@ class Octosuite:
else:
xprint(response.json())
# Fetching a list of organizations that a user owns or belongs to
# Fetching a list of organisations that a user owns or belongs to
def user_orgs(self):
if args.username and args.limit:
username = args.username
limit = args.limit
else:
username = Prompt.ask(f"{white}@{green}Username{reset}")
limit = Prompt.ask(limit_output.format("user organizations"))
limit = Prompt.ask(limit_output.format("user organisations"))
response = requests.get(f"{self.endpoint}/users/{username}/orgs?per_page={limit}")
if not response.json():
xprint(f"{NEGATIVE} User ({username}) does not (belong to/own) any organizations.")
xprint(f"{NEGATIVE} User ({username}) does not (belong to/own) any organisations.")
elif response.status_code == 404:
xprint(f"{NEGATIVE} {user_not_found.format(username)}")
elif response.status_code == 200:
for organization in response.json():
org_tree = Tree("\n" + organization['login'])
for organisation in response.json():
org_tree = Tree("\n" + organisation['login'])
for attr in self.user_orgs_attrs:
org_tree.add(f"{self.user_orgs_attr_dict[attr]}: {organization[attr]}")
org_tree.add(f"{self.user_orgs_attr_dict[attr]}: {organisation[attr]}")
xprint(org_tree)
if args.log_csv or Confirm.ask(f"\n{PROMPT} {prompt_log_csv}"):
log_user_orgs(organization, username)
log_user_orgs(organisation, username)
else:
xprint(response.json())

39
pyproject.toml Normal file
View File

@@ -0,0 +1,39 @@
[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"
[tool.setuptools]
packages = ["octosuite"]
[project]
name = "octosuite"
version = "3.1.1"
description = "Advanced GitHub OSINT Framework"
readme = "README.md"
requires-python = ">=3.8"
license = {file = "LICENSE"}
keywords = ["github", "python", "github-api", "framework", "osint", "osint-framework", "osint-python", "osint-tool"]
authors = [{name = "Richard Mwewa", email = "rly0nheart@duck.com"}]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3",
"Intended Audience :: Information Technology",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
"Operating System :: OS Independent",
"Natural Language :: English"
]
dependencies = [
"rich",
"psutil",
"requests",
"pyreadline3",
]
[project.urls]
homepage = "https://github.com/bellingcat/octosuite"
documentation = "https://github.com/bellingcat/octosuite/wiki"
repository = "https://github.com/bellingcat/octosuite.git"
[project.scripts]
octosuite = "octosuite.main:octosuite"

View File

@@ -1,31 +0,0 @@
import setuptools
with open("README.md", "r", encoding="utf-8") as file:
long_description = file.read()
setuptools.setup(
name="octosuite",
version="3.0.0",
author="Richard Mwewa",
author_email="rly0nheart@duck.com",
packages=["octosuite"],
description="Advanced Github OSINT Framework",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/bellingcat/octosuite",
license="GNU General Public License v3 (GPLv3)",
install_requires=["requests", "rich", "psutil", "pyreadline3"],
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Information Technology',
'License :: OSI Approved :: GNU General Public License v3 (GPLv3)',
'Operating System :: OS Independent',
'Natural Language :: English',
'Programming Language :: Python :: 3'
],
entry_points={
"console_scripts": [
"octosuite=octosuite.main:octosuite",
]
},
)