Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert baseline file to match local filesystem path #573

Closed
3 changes: 2 additions & 1 deletion detect_secrets/core/potential_secret.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from ..util.color import AnsiColor
from ..util.color import colorize
from ..util.path import convert_local_os_path


class PotentialSecret:
Expand Down Expand Up @@ -75,7 +76,7 @@ def load_secret_from_dict(cls, data: Dict[str, Union[str, int, bool]]) -> 'Poten
"""Custom JSON decoder"""
kwargs: Dict[str, Any] = {
'type': str(data['type']),
'filename': str(data['filename']),
'filename': convert_local_os_path(str(data['filename'])),
'secret': 'will be replaced',
}

Expand Down
7 changes: 4 additions & 3 deletions detect_secrets/core/secrets_collection.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from typing import Tuple

from . import scan
from ..util.path import convert_local_os_path
from .potential_secret import PotentialSecret
from detect_secrets.settings import configure_settings_from_baseline
from detect_secrets.settings import get_settings
Expand Down Expand Up @@ -40,7 +41,7 @@ def load_from_baseline(cls, baseline: Dict[str, Any]) -> 'SecretsCollection':
for filename in baseline['results']:
for item in baseline['results'][filename]:
secret = PotentialSecret.load_secret_from_dict({'filename': filename, **item})
output[filename].add(secret)
output[convert_local_os_path(filename)].add(secret)

return output

Expand Down Expand Up @@ -72,8 +73,8 @@ def scan_files(self, *filenames: str, num_processors: Optional[int] = None) -> N
self[os.path.relpath(secret.filename, self.root)].add(secret)

def scan_file(self, filename: str) -> None:
for secret in scan.scan_file(os.path.join(self.root, filename)):
self[filename].add(secret)
for secret in scan.scan_file(convert_local_os_path(os.path.join(self.root, filename))):
self[convert_local_os_path(filename)].add(secret)

def scan_diff(self, diff: str) -> None:
"""
Expand Down
10 changes: 10 additions & 0 deletions detect_secrets/util/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,13 @@ def get_relative_path_if_in_cwd(path: str) -> Optional[str]:
return filepath

return None


def convert_local_os_path(path: str) -> str:
# Linux filesystem, replace \\ with /
if os.sep == '/':
path = path.replace('\\', '/')
return path
else:
path = path.replace('/', '\\')
return path
3 changes: 2 additions & 1 deletion tests/audit/audit_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import os
import random
from typing import List
from typing import Optional
Expand Down Expand Up @@ -135,7 +136,7 @@ def test_ensure_file_transformers_are_used(printer):
run_logic(secrets, 'y')
assert not m.called

line_number = list(secrets['test_data/config.env'])[0].line_number
line_number = list(secrets[os.path.join('test_data', 'config.env')])[0].line_number
assert lines[line_number - 1] in printer.message


Expand Down
14 changes: 10 additions & 4 deletions tests/core/baseline_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
import os
import subprocess
import tempfile
from pathlib import Path
Expand Down Expand Up @@ -84,16 +85,21 @@ def test_scan_all_files():

def test_load_and_output():
with open('.secrets.baseline') as f:
data = json.loads(f.read())
filedata = f.read()

secrets = baseline.load(data, filename='.secrets.baseline')
if os.sep == '\\':
# Replace Linux path seperators for Windows ones
filedata = filedata.replace('/', '\\\\')

filedata_json = json.loads(filedata)
secrets = baseline.load(filedata_json, filename='.secrets.baseline')
output = baseline.format_for_output(secrets)

for item in [data, output]:
for item in [filedata_json, output]:
item.pop('generated_at')

# We perform string matching because we want to ensure stable sorts.
assert json.dumps(output) == json.dumps(data)
assert json.dumps(output) == json.dumps(filedata_json)

# We need to make sure that default values carry through, for future backwards compatibility.
for plugin in output['plugins_used']:
Expand Down
83 changes: 44 additions & 39 deletions tests/core/secrets_collection_test.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
from unittest import mock

import pytest
Expand Down Expand Up @@ -41,9 +42,10 @@ def test_error_reading_file(mock_log_warning):
'detect_secrets.core.scan.open',
side_effect=IOError,
):
SecretsCollection().scan_file('test_data/config.env')
SecretsCollection().scan_file(os.path.join('test_data', 'config.env'))

assert 'Unable to open file: test_data/config.env' in mock_log_warning.warning_messages
file_assertion = str(os.path.join('test_data', 'config.env'))
assert 'Unable to open file: ' + file_assertion in mock_log_warning.warning_messages

@staticmethod
def test_line_based_success():
Expand All @@ -62,11 +64,11 @@ def test_line_based_success():
])

secrets = SecretsCollection()
secrets.scan_file('test_data/each_secret.py')
secrets.scan_file(os.path.join('test_data', 'each_secret.py'))

secret = next(iter(secrets['test_data/each_secret.py']))
secret = next(iter(secrets[os.path.join('test_data', 'each_secret.py')]))
assert secret.secret_value.startswith('c2VjcmV0IG1lc')
assert len(secrets['test_data/each_secret.py']) == 1
assert len(secrets[os.path.join('test_data', 'each_secret.py')]) == 1

@staticmethod
def test_file_based_success_config():
Expand All @@ -77,14 +79,15 @@ def test_file_based_success_config():
},
])
secrets = SecretsCollection()
secrets.scan_file('test_data/config.ini')
test_config = os.path.join('test_data', 'config.ini')
secrets.scan_file(test_config)

assert [str(secret).splitlines()[1] for _, secret in secrets] == [
'Location: test_data/config.ini:2',
'Location: test_data/config.ini:10',
'Location: test_data/config.ini:21',
'Location: test_data/config.ini:22',
'Location: test_data/config.ini:32',
'Location: %s:2' % test_config,
'Location: %s:10' % test_config,
'Location: %s:21' % test_config,
'Location: %s:22' % test_config,
'Location: %s:32' % test_config,
]

@staticmethod
Expand All @@ -96,12 +99,13 @@ def test_file_based_success_yaml():
},
])
secrets = SecretsCollection()
secrets.scan_file('test_data/config.yaml')
test_yaml = os.path.join('test_data', 'config.yaml')
secrets.scan_file(test_yaml)

assert [str(secret).splitlines()[1] for _, secret in secrets] == [
'Location: test_data/config.yaml:3',
'Location: test_data/config.yaml:5',
'Location: test_data/config.yaml:13',
'Location: %s:3' % test_yaml,
'Location: %s:5' % test_yaml,
'Location: %s:13' % test_yaml,
]

@staticmethod
Expand All @@ -115,7 +119,7 @@ def test_file_based_yaml_only_comments():
@pytest.mark.parametrize(
'filename',
(
'test_data/config.env',
os.path.join('test_data', 'config.env'),

# Markdown files with colons and unicode characters preceding the colon on the line
# would have caused the scanner to fail and exit on python2.7.
Expand Down Expand Up @@ -172,7 +176,7 @@ def test_success():

def test_merge():
old_secrets = SecretsCollection()
old_secrets.scan_file('test_data/each_secret.py')
old_secrets.scan_file(os.path.join('test_data', 'each_secret.py'))
assert len(list(old_secrets)) >= 4 # otherwise, this test won't work.

index = 0
Expand All @@ -187,7 +191,7 @@ def test_merge():
index += 1

new_secrets = SecretsCollection()
new_secrets.scan_file('test_data/each_secret.py')
new_secrets.scan_file(os.path.join('test_data', 'each_secret.py'))
list(new_secrets)[-2][1].is_secret = True

new_secrets.merge(old_secrets)
Expand All @@ -214,25 +218,25 @@ class TestTrim:
@staticmethod
def test_deleted_secret():
secrets = SecretsCollection()
secrets.scan_file('test_data/each_secret.py')
secrets.scan_file(os.path.join('test_data', 'each_secret.py'))

results = SecretsCollection.load_from_baseline({'results': secrets.json()})
results.data['test_data/each_secret.py'].pop()
results.data[os.path.join('test_data', 'each_secret.py')].pop()

original_size = len(secrets['test_data/each_secret.py'])
original_size = len(secrets[os.path.join('test_data', 'each_secret.py')])
secrets.trim(results)

assert len(secrets['test_data/each_secret.py']) < original_size
assert len(secrets[os.path.join('test_data', 'each_secret.py')]) < original_size

@staticmethod
def test_deleted_secret_file():
secrets = SecretsCollection()
secrets.scan_file('test_data/each_secret.py')
secrets.scan_file(os.path.join('test_data', 'each_secret.py'))

secrets.trim(SecretsCollection())
assert secrets

secrets.trim(SecretsCollection(), filelist=['test_data/each_secret.py'])
secrets.trim(SecretsCollection(), filelist=[os.path.join('test_data', 'each_secret.py')])
assert not secrets

@staticmethod
Expand Down Expand Up @@ -285,24 +289,25 @@ def test_no_modifications(base_state, scanned_results):
@staticmethod
def test_remove_non_existent_files():
secrets = SecretsCollection()
secrets.scan_file('test_data/each_secret.py')
secrets.scan_file(os.path.join('test_data', 'each_secret.py'))
assert bool(secrets)

secrets.data['does-not-exist'] = secrets.data.pop('test_data/each_secret.py')
file_pop = os.path.join('test_data', 'each_secret.py')
secrets.data['does-not-exist'] = secrets.data.pop(file_pop)
secrets.trim()

assert not bool(secrets)

@staticmethod
def test_maintains_labels():
labelled_secrets = SecretsCollection()
labelled_secrets.scan_file('test_data/each_secret.py')
labelled_secrets.scan_file(os.path.join('test_data', 'each_secret.py'))
for _, secret in labelled_secrets:
secret.is_secret = True
break

secrets = SecretsCollection()
secrets.scan_file('test_data/each_secret.py')
secrets.scan_file(os.path.join('test_data', 'each_secret.py'))

labelled_secrets.trim(scanned_results=secrets)

Expand All @@ -313,18 +318,18 @@ def test_bool():
secrets = SecretsCollection()
assert not secrets

secrets.scan_file('test_data/each_secret.py')
secrets.scan_file(os.path.join('test_data', 'each_secret.py'))
assert secrets

secrets['test_data/each_secret.py'].clear()
secrets[os.path.join('test_data', 'each_secret.py')].clear()
assert not secrets


class TestEqual:
@staticmethod
def test_mismatch_files():
secretsA = SecretsCollection()
secretsA.scan_file('test_data/each_secret.py')
secretsA.scan_file(os.path.join('test_data', 'each_secret.py'))

secretsB = SecretsCollection()
secretsB.scan_file('test_data/files/file_with_secrets.py')
Expand All @@ -350,7 +355,7 @@ class TestSubtraction:
def test_basic(configure_plugins):
with transient_settings({**configure_plugins, 'filters_used': []}):
secrets = SecretsCollection()
secrets.scan_file('test_data/each_secret.py')
secrets.scan_file(os.path.join('test_data', 'each_secret.py'))

# This baseline will have less secrets, since it filtered out some.
with transient_settings({
Expand All @@ -365,24 +370,24 @@ def test_basic(configure_plugins):
],
}):
baseline = SecretsCollection()
baseline.scan_file('test_data/each_secret.py')
baseline.scan_file(os.path.join('test_data', 'each_secret.py'))

# This tests the != operator for same file, different number of secrets.
# It's hidden in a different test, but I didn't want to set up the boilerplate
# again.
assert secrets != baseline

result = secrets - baseline
assert len(result['test_data/each_secret.py']) == 3
assert len(secrets['test_data/each_secret.py']) == 5
assert len(result[os.path.join('test_data', 'each_secret.py')]) == 3
assert len(secrets[os.path.join('test_data', 'each_secret.py')]) == 5

@staticmethod
def test_no_overlapping_files(configure_plugins):
secrets_a = SecretsCollection()
secrets_b = SecretsCollection()
with transient_settings({**configure_plugins, 'filters_used': []}):
secrets_a.scan_file('test_data/each_secret.py')
secrets_b.scan_file('test_data/config.env')
secrets_a.scan_file(os.path.join('test_data', 'each_secret.py'))
secrets_b.scan_file(os.path.join('test_data', 'config.env'))

assert (secrets_a - secrets_b).files == {'test_data/each_secret.py'}
assert (secrets_b - secrets_a).files == {'test_data/config.env'}
assert (secrets_a - secrets_b).files == {os.path.join('test_data', 'each_secret.py')}
assert (secrets_b - secrets_a).files == {os.path.join('test_data', 'config.env')}
Loading