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

Keyword regexes improvement #418

Merged
merged 8 commits into from
Mar 6, 2021
Merged

Keyword regexes improvement #418

merged 8 commits into from
Mar 6, 2021

Conversation

pablosnt
Copy link
Contributor

@pablosnt pablosnt commented Mar 4, 2021

This Pull Request solves some issues detected in #414 and includes some improvements described in #396 to the latest version of detect-secrets.

This code includes:

  • Support to secret detection in comparisons. You can see it in FOLLOWED_BY_EQUAL_SIGNS_REGEX and FOLLOWED_BY_EQUAL_SIGNS_QUOTES_REQUIRED_REGEX regexes. For example:
if (password == "value") {}
else if (password === "value") {}
else if (password != "value") {}
else if (password !== "value") {}
  • Support to secret detection in reverse comparisons. You can see it in the PRECEDED_BY_EQUAL_COMPARISON_SIGNS_QUOTES_REQUIRED_REGEX regex. For example:
if ("value" == password) {}
else if ("value" === password) {}
else if ("value" != password) {}
else if ("value" !== password) {}
  • Corrections in the SECRET regex to avoid some problems detected in Bugfix of Yaml exception with simple quotes #414 and reduce the false positive percentage. The regex can be decomposed in the following sections:

    • [^\r\n]*: this section match with every character except line breaks. This allows to find secrets that starts with symbols or alphanumeric characters.
    • [a-zA-Z0-9]+: this section match only with alphanumeric characters, and at least one is required. This allows to reduce the false positives number.
    • [^\r\n]*: this section match with every character except line breaks. This allows to find secrets with symbols at the end.
    • [^\r\n,\'"]: this section match with the last secret character that can be everything except line breaks, comma or quotes. This allows to reduce the false positives number and to prevent errors in the code snippet highlighting. Next you can see an example:

last_comma

The following image shows a test with this new regex:

regex_example

Of course, keyword_test.py has been updated to check all this changes.

Copy link
Contributor

@domanchi domanchi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing this! I was going to work on a fix, but I'm so glad that you were able to fix these issues yourselves.

I like the detail that you have in your PR summary; I think that this should be captured in code as comments (especially your thorough breakdown of the new SECRET regex rationale).

With regards to test cases, I want to abandon the "old-style" tests, and (eventually) replace them with much more readable tests. After all, readable tests are a great way of documenting intended usage of functions. I think you have a good set of test cases, and maybe my interim test cases will help you get started?

def test_ignore_case():
    secret = next(scan_line('os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"'))
    assert secret.secret_value == 'testing'


def test_secret_with_whitespace():
    secret = next(scan_line('password = "not a secret"'))
    assert secret.secret_value == 'not a secret'


def test_comparison_operator():
    secret = next(scan_line('api_key == "hunter2"'))
    assert secret.secret_value == 'hunter2'


def test_bounds():
    for index, secret in scan_line(
        'detect-secrets scan '
        '--exclude-lines "password = blah" '
        '--exclude-lines "password = fake"',
    ):
        assert secret.secret_value in {'blah', 'fake'}

    # TODO(2021-03-03): This should probably yield `fake` as well.
    # assert index == 1


def test_backticks():
    secret = next(scan_line('api_key = `cat password`'))

    assert secret.secret_value == 'cat password'


@pytest.mark.parametrize(
    'line',
    (
        'if ("value" == password)',
        'else if ("value" === password)',
        ...
    ),
)
def test_preceded_by_equals_sign(line):
    pass


@pytest.fixture(autouse=True)
def use_keyword_detector():
    with transient_settings({
        'plugins_used': [{
            'name': 'KeywordDetector',
        }],
    }):
        yield

detect_secrets/plugins/keyword.py Outdated Show resolved Hide resolved
killuazhu pushed a commit to IBM/detect-secrets that referenced this pull request Mar 4, 2021
* feat: add auth keyword to detectors

Another common word for secrets that isn't currently picked up.

* feat: tests for new db2 keywords too

* test: test case for user reported false negative
@pablosnt
Copy link
Contributor Author

pablosnt commented Mar 5, 2021

With regards to test cases, I want to abandon the "old-style" tests, and (eventually) replace them with much more readable tests. After all, readable tests are a great way of documenting intended usage of functions. I think you have a good set of test cases, and maybe my interim test cases will help you get started?

We implement a new keyword_test.py. I think it is much more readable and simpler, so it should be very easy to add new test cases. I hope you would like it.

We obtain a 98% of coverage with the new keyword_test, so I think that the included test cases are enough:

keyword_coverage

Thank you very much!

Copy link
Contributor

@domanchi domanchi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow! What you've done with the KeywordDetector test cases is amazing! So much better, and more than I expected. Thanks!

Comment on lines 155 to 169
'''
Secret regex details:
[^\r\n]* -> this section match with every character except line breaks.
This allows to find secrets that starts with symbols or
alphanumeric characters.
[a-zA-Z0-9]+ -> this section match only with alphanumeric characters, and at
least one is required. This allows to reduce the false positives
number.
[^\r\n]* -> this section match with every character except line breaks.
This allows to find secrets with symbols at the end.
[^\r\n,\'"`] -> this section match with the last secret character that can be
everything except line breaks, comma, backticks or quotes. This
allows to reduce the false positives number and to prevent
errors in the code snippet highlighting.
'''
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, are you running pre-commit? I'm hitting this error:

$ pre-commit run --all-files
Check builtin type constructor use.......................................Passed
Check docstring is first.................................................Failed
hookid: check-docstring-first

detect_secrets/plugins/keyword.py:53 Multiple module docstrings (first docstring on line 1).

Debug Statements (Python)................................................Passed
Fix double quoted strings................................................Passed
Fix End of Files.........................................................Passed
Tests should end in _test.py.............................................Passed
Flake8...................................................................Passed
Trim Trailing Whitespace.................................................Passed
Reorder python imports...................................................Passed
Add trailing commas......................................................Passed
autopep8.................................................................Passed
Detect secrets...........................................................Failed
hookid: detect-secrets

Files were modified by this hook. Additional output:

The baseline file was updated.
Probably to keep line numbers of secrets up-to-date.
Please `git add .secrets.baseline`, thank you.


Your baseline file (.secrets.baseline) is unstaged.
`git add .secrets.baseline` to fix this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we are running pre-commit. This error is in the line 53 that is the comment of FALSE_POSITIVES variable. The details of the SECRET regex can be added using one line comments (#) instead of multiple line comments ('''). What do you prefer?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, let's just do multiple line comments, so pre-commit will be happy. Then I can merge this in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I think that pre-commit is working fine now

@pablosnt
Copy link
Contributor Author

pablosnt commented Mar 6, 2021

Wow! What you've done with the KeywordDetector test cases is amazing! So much better, and more than I expected. Thanks!

Thank you very much!!!

@domanchi domanchi merged commit deb1705 into Yelp:master Mar 6, 2021
# everything except line breaks, comma, backticks or quotes. This
# allows to reduce the false positives number and to prevent
# errors in the code snippet highlighting.
SECRET = r'[^\r\n]*[a-zA-Z0-9]+[^\r\n]*[^\r\n,\'"`]'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pablosantiagolopez : I think you might have introduced an accidental exponential performance degradation here, with files with long lines.

(stable_version) $ time detect-secrets scan detect_secrets/filters/gibberish/rfc.model
...
real    0m0.286s
user    0m0.243s
sys     0m0.036s

(version_on_master) $ time detect-secrets scan detect_secrets/filters/gibberish/rfc.model
...
real    29m28.225s
user    29m24.396s
sys     0m2.074s


(version_on_master) $ time detect-secrets scan detect_secrets/filters/gibberish/rfc.model --disable-plugin KeywordDetector
...
real    0m0.385s
user    0m0.322s
sys     0m0.054s

Please look into this.

@domanchi
Copy link
Contributor

domanchi commented Apr 1, 2021

@pablosantiagolopez : sorry it's been a while since I was able to look at your other pending PRs. I realized that since the KeywordDetector regexes are becoming increasingly complicated, the only way to properly test them is to stress test them against a subset of our internal code. As such, I've been retooling internal tools to perform this analysis, and have been trying to use that to approach testing these proposed changes with much more rigor.

In my investigations, I have found that accidental exponential performance degradation is still there, on the master branch. Here is the example:

<img src="data:image/png;base64,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
"
>

On the latest published version, this payload works. However, on the current master (465902312), it takes LONG time. Please investigate.

Maybe we should just add a max char limit to the regex?

@pablosnt
Copy link
Contributor Author

pablosnt commented Apr 1, 2021

Hi @domanchi, how are you? About our pending PRs, no problem, we understand that reviewing them suppose very much time and effort, so don't worry.

About the performance problem of the KeywordDetector, I was working in that and I think that the problem of the SECRET regex is the called Catastrophic backtracking. I found this interesting articles about the problem:

Based in this links, I have changed the SECRET regex to the following:

SECRET = r'(?=[^\v\'\"]*)(?=\w+)[^\v\'\"]*[^\v,\'\"`]'

The regex content is the same, but the first two sub-groups are moved into (?= ... ). I have tested it with your example, and detect-secrets finish the scan in similar times when KeywordDetector is enabled and when it is disabled. I have also runned the tests and everything works, so I think that it could be a good solution.

Please, test this regex and tell me what do you think about this. If it works for you, I can add it into one of our pending PRs because they are also related to the KeywordDetector plugin. Thank you very much for your feedback!

@domanchi
Copy link
Contributor

domanchi commented Apr 1, 2021

Thanks for your quick response! I'll test it out, and let you know.

@syn-4ck
Copy link
Contributor

syn-4ck commented Apr 1, 2021

Hi! I totally agree with the backtracking performance problem that @pablosantiagolopez has mentioned.

I would like to comment that it might be interesting to create a filter for these cases, in addition to fixing the backtracking problem (if the @domanchi 's tests confirm it). I think that a <img> HTML tag with a base64 source content will never contain secrets, so implement a HTML tags filter can be a good feature for detect-secrets... This filter will improve performance and reduce the rate of false positives.

Also, other good idea can be include more non-code files extensions into the IGNORED_FILE_EXTENSIONS (binary files, compiled packages...). We contributed adding some extensions in #419, but I think this list should grow as the version is used.

This tips can improve the KeywordDetector plugin performance. What do you think @pablosantiagolopez @domanchi?

@domanchi
Copy link
Contributor

domanchi commented Apr 1, 2021

@pablosantiagolopez : I can confirm it performs a lot better. Let's cut out a PR for this change alone, so that I can merge it independently of other modifications to the KeywordDetector.

EDIT: Feel free to use the example payload as a test case for long lines, so that we can add regression tests.

@pablosnt
Copy link
Contributor Author

pablosnt commented Apr 1, 2021

Perfect, it's comming...Thank you!

@pablosnt pablosnt mentioned this pull request Apr 1, 2021
@KevinHock
Copy link
Collaborator

Amazing PR @pablosnt 😮

@pablosnt
Copy link
Contributor Author

Amazing PR @pablosnt 😮

Thank you very much @KevinHock!!! Everything is easier with the help of @syn-4ck ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants