pyupio / safety

Safety checks Python dependencies for known security vulnerabilities and suggests the proper remediations for vulnerabilities detected.
https://safetycli.com/product/safety-cli
MIT License
1.68k stars 142 forks source link

Support scanning setup.py files #108

Open voidus opened 6 years ago

voidus commented 6 years ago

It would be nice if it was possible to pass setup.py files to safety check --file

calve commented 6 years ago

The following script might be a starting point, just pipe the output to safety. Be careful with the input tho as this use ast.parse() and could happily execute malicious code in the setup.py.

#!/usr/bin/env python3                                                                                                                                                                                                                                                            
"""                                                                                                                                                                                                                                                                               
Returns a safety-readable list of requirements from a setup.py python script                                                                                                                                                                                                      

Code heavily inspired by https://stackoverflow.com/a/24237067                                                                                                                                                                                                                     

Usage:                                                                                                                                                                                                                                                                            

    $ ./parse_setup.py /path/to/setup.py                                                                                                                                                                                                                                          
"""
import ast
import sys
import textwrap

def parse_setup(setup_filename):
    """Parse setup.py and return args and keywords args to its setup                                                                                                                                                                                                              
    function call                                                                                                                                                                                                                                                                 

    """
    mock_setup = textwrap.dedent('''\                                                                                                                                                                                                                                             
    def setup(*args, **kwargs):                                                                                                                                                                                                                                                   
        __setup_calls__.append((args, kwargs))                                                                                                                                                                                                                                    
    ''')
    parsed_mock_setup = ast.parse(mock_setup, filename=setup_filename)
    with open(setup_filename, 'rt') as setup_file:
        parsed = ast.parse(setup_file.read())
        for index, node in enumerate(parsed.body[:]):
            if (
                not isinstance(node, ast.Expr) or
                not isinstance(node.value, ast.Call) or
                node.value.func.id != 'setup'
            ):
                continue
            parsed.body[index:index] = parsed_mock_setup.body
            break

    fixed = ast.fix_missing_locations(parsed)
    codeobj = compile(fixed, setup_filename, 'exec')
    local_vars = {}
    global_vars = {'__setup_calls__': []}
    exec(codeobj, global_vars, local_vars)
    return global_vars['__setup_calls__'][0]

if __name__ == '__main__':
    filename = sys.argv[1]
    _, kwargs = parse_setup(filename)
    requires = kwargs['install_requires']

    print('\n'.join(requires))
rafaelpivato commented 4 years ago

Apparently #218 might solve your issue here. Well, at least to integrate safety with your setup.py. Now, to literally scan a setup.py file "from outside" as a static analyzer, then we might need dparse support and a better use of dparse by Safety.