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

add a "skip next line" pragma #367

Closed
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
48 changes: 47 additions & 1 deletion detect_secrets/filters/allowlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
from typing import List
from typing import Pattern

from ..util.code_snippet import CodeSnippet

def is_line_allowlisted(filename: str, line: str) -> bool:

def is_line_allowlisted(filename: str, line: str, context: CodeSnippet) -> bool:
regexes = _get_allowlist_regexes()

_, ext = os.path.splitext(filename)
Expand All @@ -17,6 +19,16 @@ def is_line_allowlisted(filename: str, line: str) -> bool:
if regex.search(line):
return True

previous_line = context.previous_line
regexes = _get_allowlist_nextline_regexes()

if ext[1:] in _get_file_based_allowlist_nextline_regexes():
regexes = _get_file_based_allowlist_nextline_regexes()[ext[1:]]

for regex in regexes:
if regex.search(previous_line):
return True
domanchi marked this conversation as resolved.
Show resolved Hide resolved

return False


Expand Down Expand Up @@ -53,3 +65,37 @@ def _get_allowlist_regexes() -> List[Pattern]:
)
]
]


@lru_cache(maxsize=1)
def _get_file_based_allowlist_nextline_regexes() -> Dict[str, List[Pattern]]:
# Add to this mapping (and ALLOWLIST_REGEXES if applicable) lazily,
# as more language specific file parsers are implemented.
# Discussion: https://github.com/Yelp/detect-secrets/pull/105
return {
'yaml': [_get_allowlist_nextline_regexes()[0]],
}


@lru_cache(maxsize=1)
def _get_allowlist_nextline_regexes() -> List[Pattern]:
return [
re.compile(r)
for r in [
r'^[ \t]*{} *pragma: ?allowlist[ -]nextline[ -]secret.*?{}[ \t]*$'.format(start, end)
for start, end in (
('#', ''), # e.g. python or yaml
('//', ''), # e.g. golang
(r'/\*', r' *\*/'), # e.g. c
('\'', ''), # e.g. visual basic .net
('--', ''), # e.g. sql
(r'<!--[# \t]*?', ' *?-->'), # e.g. xml
# many other inline comment syntaxes are not included,
# because we want to be performant for
# any(regex.search(line) for regex in ALLOWLIST_REGEXES)
# calls. of course, this won't be a concern if detect-secrets
# switches over to implementing file plugins for each supported
# filetype.
)
nickiaconis marked this conversation as resolved.
Show resolved Hide resolved
]
]
6 changes: 6 additions & 0 deletions detect_secrets/util/code_snippet.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ def __init__(self, snippet: List[str], start_line: int, target_index: int) -> No
def target_line(self) -> str:
return self.lines[self.target_index]

@property
def previous_line(self) -> str:
if self.target_index == 0 or len(self.lines) < self.target_index:
return ''
return self.lines[self.target_index - 1]

@target_line.setter
def target_line(self, value: str) -> None:
self.lines[self.target_index] = value
Expand Down
68 changes: 65 additions & 3 deletions tests/filters/allowlist_filter_test.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest

from detect_secrets.filters.allowlist import is_line_allowlisted
from detect_secrets.util.code_snippet import CodeSnippet


@pytest.mark.parametrize(
Expand All @@ -22,16 +23,57 @@
),
)
def test_basic(prefix, suffix):
line = f'AKIAEXAMPLE {prefix}pragma: allowlist secret{suffix}'
assert is_line_allowlisted(
'filename',
f'AKIAEXAMPLE {prefix}pragma: allowlist secret{suffix}',
line,
CodeSnippet([line], 0, 0),
)


@pytest.mark.parametrize(
'prefix, suffix',
(
('#', ''),
('# ', ' more text'),

('//', ''),
('// ', ' more text'),

('/*', '*/'),
('/* ', ' */'),

('--', ''),
('-- ', ' more text'),

('<!--', '-->'),
),
nickiaconis marked this conversation as resolved.
Show resolved Hide resolved
)
def test_nextline(prefix, suffix):
comment = f'{prefix}pragma: allowlist nextline secret{suffix}'
line = 'AKIAEXAMPLE'
assert is_line_allowlisted(
'filename',
line,
CodeSnippet([comment, line], 0, 1),
)


def test_nextline_exclusivity():
line = 'AKIAEXAMPLE # pragma: allowlist nextline secret'
assert is_line_allowlisted(
'filename',
line,
CodeSnippet([line], 0, 0),
) is False


def test_backwards_compatibility():
line = 'AKIAEXAMPLE # pragma: whitelist secret'
assert is_line_allowlisted(
'filename',
'AKIAEXAMPLE # pragma: whitelist secret',
line,
CodeSnippet([line], 0, 0),
)


Expand All @@ -43,4 +85,24 @@ def test_backwards_compatibility():
),
)
def test_file_based_regexes(line, expected_result):
assert is_line_allowlisted('filename.yaml', line) is expected_result
assert is_line_allowlisted(
'filename.yaml',
line,
CodeSnippet([line], 0, 0),
) is expected_result


@pytest.mark.parametrize(
'comment, expected_result',
(
('# pragma: allowlist nextline secret', True),
('// pragma: allowlist nextline secret', False),
),
)
def test_file_based_nextline_regexes(comment, expected_result):
line = 'key: value'
assert is_line_allowlisted(
'filename.yaml',
line,
CodeSnippet([comment, line], 0, 1),
) is expected_result
8 changes: 8 additions & 0 deletions tests/util/code_snippet_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,11 @@ def test_basic(line_number, expected):
assert ''.join(
list(get_code_snippet(list('abcde'), line_number, lines_of_context=2)),
) == expected


def test_target_line():
assert get_code_snippet(list('abcde'), 3, lines_of_context=2).target_line == 'c'


def test_previous_line():
assert get_code_snippet(list('abcde'), 3, lines_of_context=2).previous_line == 'b'