Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 96 additions & 54 deletions gitshield/cli.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,114 @@
import os
import re

import click
import openai
from utils import check_for_git, install_git_hook, uninstall_git_hook
import sys

class GitHookInstaller:
@staticmethod
def install():
"""Install the git hook"""
click.secho("Installing the git hook...", fg="green")
GitHookInstaller.check_for_git()
GitHookInstaller._install_git_hook()

@staticmethod
def uninstall():
"""Uninstall the git hook"""
click.secho("Uninstall the git hook...", fg="green")
GitHookInstaller.check_for_git()
GitHookInstaller._uninstall_git_hook()

@staticmethod
def check_for_git():
"""Check if we are in a git repository"""
try:
subprocess.check_call(
["git", "rev-parse", "--is-inside-work-tree"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except subprocess.CalledProcessError:
GitHookInstaller._print_not_a_git_repo_and_exit()

@staticmethod
def _install_git_hook():
"""Actual implementation of installing the git hook"""
# Implementation details...
pass

@staticmethod
def _uninstall_git_hook():
"""Actual implementation of uninstalling the git hook"""
# Implementation details...
pass

@staticmethod
def _print_not_a_git_repo_and_exit():
"""Print a message that we are not in a git repository"""
click.secho(
"fatal: not a git repository (or any of the parent directories): .git",
fg="red",
)
sys.exit(1)

class CodeAnalyzer:
@staticmethod
def analyze_files(changed_file_paths):
"""Analyze code for secrets"""
api_key = os.getenv("OPENAI_API_KEY")
if api_key:
openai.api_key = api_key

for file_path in changed_file_paths:
click.secho(f"Checking {file_path} for secrets...", fg="green")
with open(file_path, "r") as f:
file_string = f.read()

CodeAnalyzer._regex_check(file_string, file_path)

if not api_key:
continue

message = (
"are there any secrets like passwords or personal credentials in this code?\n" # noqa: E501
+ file_string
)
messages = [{"role": "user", "content": message}]
chat = openai.ChatCompletion.create(
model="gpt-3.5-turbo", messages=messages
)

reply = chat.choices[0].message.content
click.echo(f"ChatGPT reply from analyzing {file_path}: {reply}")

@staticmethod
def _regex_check(file_data, file_path):
"""Check for secrets using regex"""
secret_pattern = r"(?:[^a-zA-Z0-9])(secret|password)\s*(=|:\s*)\s*([A-Za-z0-9_'-]{10,})"
secret_match = re.findall(secret_pattern, file_data)
if len(secret_match):
click.secho(
f"There are secrets present in {file_path} secrets - {secret_match}",
fg="red",
)
sys.exit(1)
else:
click.secho(f"No secrets were detected in {file_path} by the regex matcher!", fg="green")

@click.group()
def cli():
pass


@cli.command()
def install():
"""Install the git hook"""
click.secho("Installing the git hook...", fg="green")
check_for_git()
install_git_hook()

GitHookInstaller.install()

@cli.command()
def uninstall():
"""Uninstall the git hook"""
click.secho("Uninstall the git hook...", fg="green")
check_for_git()
uninstall_git_hook()

GitHookInstaller.uninstall()

@cli.command()
@click.option(
Expand All @@ -37,49 +120,8 @@ def uninstall():
)
def pre_commit(changed_file_path):
"""Run on the pre-commit hook"""
api_key = os.getenv("OPENAI_API_KEY")
if api_key:
openai.api_key = api_key

for file in changed_file_path:
click.secho(f"Checking {file} for secrets...", fg="green")
with open(file, "r") as f:
fileString = f.read()

regexCheck(fileString, file)

if not api_key:
continue
CodeAnalyzer.analyze_files(changed_file_path)

message = (
"are there any secrets like passwords or personal credentials in this code?\n" # noqa: E501
+ fileString
)
messages = []

messages.append(
{"role": "user", "content": message},
)
chat = openai.ChatCompletion.create(
model="gpt-3.5-turbo", messages=messages
)

reply = chat.choices[0].message.content
click.echo(f"ChatGPT reply from analyzing {file}: {reply}")

def regexCheck(file_data, file):
secret_pattern = r"(?:[^a-zA-Z0-9])(secret|password)\s*(=|:\s*)\s*([A-Za-z0-9_'-]{10,})"
secret_match = re.findall(secret_pattern, file_data)
if len(secret_match):
click.secho(
f"There are secrets present in {file} secrets - \
{secret_match}",
fg="red",
)
import sys

sys.exit(1)
else:
click.secho(f"No secrets were detected in {file} by the regex matcher!", fg="green")
if __name__ == "__main__":
cli()

152 changes: 79 additions & 73 deletions gitshield/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,80 +5,86 @@

import click


def check_for_git():
"""Check if we are in a git repository"""
try:
subprocess.check_call(
["git", "rev-parse", "--is-inside-work-tree"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except subprocess.CalledProcessError:
print_not_a_git_repo_and_exit()


def get_git_dir():
"""Get git repository directory"""
try:
git_root = subprocess.check_output(
["git", "rev-parse", "--git-dir"], stderr=subprocess.PIPE
)
except subprocess.CalledProcessError:
print_not_a_git_repo_and_exit()

return git_root.decode("utf-8").strip()


def print_not_a_git_repo_and_exit():
"""Print a message that we are not in a git repository"""
click.secho(
"fatal: not a git repository (or any of the parent directories): .git",
fg="red",
)
sys.exit(1)


def install_git_hook():
"""Install the git hook"""
pre_commit_file_path = resource_path("pre-commit")
hook_path = get_hook_path()
if hook_path.exists():
class GitHookUtils:
@staticmethod
def check_for_git():
"""Check if we are in a git repository"""
try:
subprocess.check_call(
["git", "rev-parse", "--is-inside-work-tree"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
except subprocess.CalledProcessError:
GitHookUtils.print_not_a_git_repo_and_exit()

@staticmethod
def get_git_dir():
"""Get git repository directory"""
try:
git_root = subprocess.check_output(
["git", "rev-parse", "--git-dir"], stderr=subprocess.PIPE
)
except subprocess.CalledProcessError:
GitHookUtils.print_not_a_git_repo_and_exit()

return git_root.decode("utf-8").strip()

@staticmethod
def print_not_a_git_repo_and_exit():
"""Print a message that we are not in a git repository"""
click.secho(
"Git hook already exists, skipping installation",
fg="yellow",
"fatal: not a git repository (or any of the parent directories): .git",
fg="red",
)
return
sys.exit(1)

shutil.copy(pre_commit_file_path, hook_path)
hook_path.chmod(0o755)


def uninstall_git_hook():
"""Uninstall the git hook"""
hook_path = get_hook_path()
hook_path.unlink()


def get_hook_path():
"""Get the path to the git hook"""
git_dir = get_git_dir()
return Path(git_dir) / "hooks" / "pre-commit"


def get_pre_commit_content():
"""Get the content of the prepare-commit-msg hook"""
return """#!/bin/sh
@staticmethod
def get_pre_commit_content():
"""Get the content of the prepare-commit-msg hook"""
return """#!/bin/sh
gitshield prepare-commit-msg --commit-msg-file "$1" --commit-source "$2" --commit-sha "$3"
""" # noqa: E501


def resource_path(relative_path):
"""Get absolute path to resource, works for dev and for PyInstaller"""
if getattr(sys, "frozen", False):
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = Path(sys._MEIPASS)
else:
base_path = Path(__file__).parent.parent.absolute()

return base_path / relative_path
""" # noqa: E501

class GitHookInstaller:
@staticmethod
def install_git_hook():
"""Install the git hook"""
pre_commit_file_path = GitHookUtils.resource_path("pre-commit")
hook_path = GitHookInstaller.get_hook_path()
if hook_path.exists():
click.secho(
"Git hook already exists, skipping installation",
fg="yellow",
)
return

shutil.copy(pre_commit_file_path, hook_path)
hook_path.chmod(0o755)

@staticmethod
def uninstall_git_hook():
"""Uninstall the git hook"""
hook_path = GitHookInstaller.get_hook_path()
hook_path.unlink()

@staticmethod
def get_hook_path():
"""Get the path to the git hook"""
git_dir = GitHookUtils.get_git_dir()
return Path(git_dir) / "hooks" / "pre-commit"

@staticmethod
def resource_path(relative_path):
"""Get absolute path to resource, works for dev and for PyInstaller"""
if getattr(sys, "frozen", False):
# PyInstaller creates a temp folder and stores path in _MEIPASS
base_path = Path(sys._MEIPASS)
else:
base_path = Path(__file__).parent.parent.absolute()

return base_path / relative_path

if __name__ == "__main__":
GitHookUtils.check_for_git()
GitHookInstaller.install_git_hook()