Skip to content
Open
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
73 changes: 47 additions & 26 deletions cred_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,54 @@
import sys
import click

EXCLUDED_DIRS = {'.git', '__pycache__', 'venv'}
ALLOWED_EXTENSIONS = {'.py', '.txt', '.env', '.cfg', '.json', '.yaml', '.yml'}

AWS_KEY_PATTERN = r'(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])'
SECRET_KEY_PATTERN = r'(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])'

@click.command()
@click.option('--path', default='.', help='Path other than the local directory to scan')
@click.option('--secret', is_flag=True, help='Also look for Secret Key patterns. This may result in many false matches due to the nature of secret keys.')
def scan(path, secret):
@click.option('--path', default='.', help='Directory path to scan')
@click.option('--secret', is_flag=True, help='Scan for generic secret key patterns too')
@click.option('--log', type=click.Path(), help='Log results to a file')
def scan(path, secret, log):
fail = False
for dirname, dirnames, filenames in os.walk(path):
# print path to all subdirectories first.
for subdirname in dirnames:
print(os.path.join(dirname, subdirname))

# print path to all filenames.
for filename in filenames:
click.echo(os.path.join(dirname, filename))
f = open(os.path.join(dirname, filename))
if secret:
pattern = re.compile('(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])|(?<![A-Za-z0-9/+=])[A-Za-z0-9/+=]{40}(?![A-Za-z0-9/+=])')
else:
pattern = re.compile('(?<![A-Z0-9])[A-Z0-9]{20}(?![A-Z0-9])')
findings = []

for root, dirs, files in os.walk(path):
# Ignore excluded directories
dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS]

for file in files:
file_path = os.path.join(root, file)
_, ext = os.path.splitext(file)
if ext.lower() not in ALLOWED_EXTENSIONS:
continue

try:
for i, line in enumerate(f):
for match in re.finditer(pattern, line):
click.secho('Found AWS Access Key in ' + os.path.join(dirname, filename,), fg='red')
fail = True
except UnicodeDecodeError:
click.secho("Can't scan file due to type: " + os.path.join(dirname, filename), fg='red')
pass
if fail == True:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.readlines()
except (UnicodeDecodeError, PermissionError) as e:
click.secho(f"Skipped binary or unreadable file: {file_path}", fg='yellow')
continue

pattern = f"{AWS_KEY_PATTERN}|{SECRET_KEY_PATTERN}" if secret else AWS_KEY_PATTERN
regex = re.compile(pattern)

for i, line in enumerate(content):
for match in regex.finditer(line):
finding = f"Found possible secret in {file_path} at line {i+1}: {match.group()}"
click.secho(finding, fg='red')
findings.append(finding)
fail = True

if log and findings:
with open(log, 'w', encoding='utf-8') as logfile:
logfile.write("\n".join(findings))
click.secho(f"\nFindings written to {log}", fg='blue')

if fail:
sys.exit(1)
if __name__ == "__main__":
scan()

if __name__ == '__main__':
scan()