Issues (3)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/GitPolicy/Application/GitPolicyAnalyser.php (3 issues)

Labels
Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace GitPolicy\Application;
4
5
use Symfony\Component\Console\Application;
6
use Symfony\Component\Console\Input;
7
use Symfony\Component\Console\Input\InputDefinition;
8
use Symfony\Component\Console\Input\InputOption;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Yaml\Parser;
11
use Symfony\Component\Console\Output\OutputInterface;
12
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
13
14
class GitPolicyAnalyser extends Application
15
{
16
    /**
17
     * @var array
18
     */
19
    protected $input;
20
21
    /**
22
     * @var OutputInterface
23
     */
24
    protected $output;
25
26
    /**
27
     * Name it.
28
     */
29
    public function __construct()
30
    {
31
        parent::__construct('GitPolicy hook analyser');
32
    }
33
34
    /**
35
     * GitPolicy' Brain.
36
     *
37
     * The following methods are a mix of functional patterns and OO patterns. It might take a bit to get the grasp
38
     * but once get the idea it will all be logical and show its immutable design.
39
     *
40
     * @param InputInterface  $input
41
     * @param OutputInterface $output
42
     *
43
     * @return int
44
     */
45
    public function doRun(InputInterface $input = null, OutputInterface $output = null)
46
    {
47
        // little prep, add some context to the set input: $this->input and "welcome" :)
48
        $this->prepEnvironment($input, $output);
0 ignored issues
show
It seems like $input defined by parameter $input on line 45 can be null; however, GitPolicy\Application\Gi...yser::prepEnvironment() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
It seems like $output defined by parameter $output on line 45 can be null; however, GitPolicy\Application\Gi...yser::prepEnvironment() does not accept null, maybe add an additional type check?

It seems like you allow that null is being passed for a parameter, however the function which is called does not seem to accept null.

We recommend to add an additional type check (or disallow null for the parameter):

function notNullable(stdClass $x) { }

// Unsafe
function withoutCheck(stdClass $x = null) {
    notNullable($x);
}

// Safe - Alternative 1: Adding Additional Type-Check
function withCheck(stdClass $x = null) {
    if ($x instanceof stdClass) {
        notNullable($x);
    }
}

// Safe - Alternative 2: Changing Parameter
function withNonNullableParam(stdClass $x) {
    notNullable($x);
}
Loading history...
49
        $this->analyseContext();
50
        $this->out('Checking GitPolicy', 'good');
51
52
        // get the right section of the config for this
53
        $config = $this->getRefSpecificConfiguration($this->loadConfig(), $this->input);
54
55
        // run the actual verification - if we are failing here - lets stop here.
56
        if ($this->verifyPolicy($config, $this->input)) {
57
            // print the messages and signal that we're done.
58
            $this->printMessages($config, $this->input);
59
            $this->out('Done :)', 'good');
60
61
            exit(0);
62
        }
63
64
        // I'm probably just too stupid to know how to exit a PHP application console in a git hook proper.
65
        $tempFile = fopen('./.tmp-endgp', 'w');
66
        fwrite($tempFile, 'failed :(');
67
        fclose($tempFile);
68
69
        exit(1);
70
    }
71
72
    /**
73
     * analyses the given attempt to push.
74
     */
75
    protected function analyseContext()
76
    {
77
        $this->input['context'] = [
78
            // type and the name of the remote ref as a structure for the following processes
79
            'ref_type' => (strpos($this->input['remote_ref'], 'refs/tags/') === 0) ? 'tag' : 'branch',
80
            'ref_name' => preg_replace('"refs/.+/"', '', $this->input['remote_ref']),
81
            'refs_different' => ($this->input['local_ref'] != $this->input['remote_ref']),
82
83
            // easy to match list of states
84
            'is' => [
85
                'tag' => (strpos($this->input['remote_ref'], 'refs/tags/') === 0),
86
                'branch' => (strpos($this->input['remote_ref'], 'refs/heads/') === 0),
87
                'create' => ($this->input['remote_sha'] == '0000000000000000000000000000000000000000'),
88
                'update' => (
89
                    $this->input['local_sha'] != '0000000000000000000000000000000000000000' &&
90
                    $this->input['remote_sha'] != '0000000000000000000000000000000000000000'
91
                ),
92
                'delete' => (
93
                    $this->input['local_ref'] == '(deleted)' ||
94
                    $this->input['local_sha'] == '0000000000000000000000000000000000000000'
95
                ),
96
            ],
97
        ];
98
99
        // @TODO add more extensive checks in here. For example go through the differences between the two hashs and
100
        //  verify all commit messages are fine by gitpolicy.
101
    }
102
103
    /**
104
     * Returns the specific section of the configuration for this push.
105
     *
106
     * E.g. if a tag is pushed/deleted returns the 'tag' section from the config.
107
     *
108
     * Fallback is always an empty array.
109
     *
110
     * @param array $config
111
     * @param array $push
112
     *
113
     * @return array
114
     */
115
    protected function getRefSpecificConfiguration(array $config, array $push)
116
    {
117
        return
118
            // no intention to process anything else than tags and branches for now ;)
119
            !(in_array($push['context']['ref_type'], ['tag', 'branch'])) ? [] :
120
121
            // get the right section of the config for this
122
            (array_key_exists($push['context']['ref_type'], $config)) ? $config[$push['context']['ref_type']] : [];
123
    }
124
125
    /**
126
     * verifies if any of the set policy parameters is/was violated.
127
     *
128
     * This method is used for some functional programming demonstration as well.
129
     *
130
     * @param array $config
131
     * @param array $push
132
     * @return boolean $passed
133
     */
134
    protected function verifyPolicy(array $config, array $push)
135
    {
136
        // We are merging the individual message parts together to get the complete array of messages
137
        $messages = implode("\n\n", array_merge(
138
            // Hint: A lot of the following comments are "written negated" compared to the actual
139
            // statement for better readability.
140
141
            // Are there any forbidden actions we should check?
142
            !array_key_exists('forbidden', $config) ? [] : array_intersect_key(
143
                // remove the elements which aren't applicable
144
                $config['forbidden'],
145
                array_filter($push['context']['is'], function ($state) { return $state; })
146
            ),
147
148
            // Should the name be validated?
149
            !(array_key_exists('name', $config) && isset($push['context']['ref_name'])) ? [] : array_merge(
150
                // simply check if the ref name is on the forbidden list
151
                isset($config['name']['forbidden'][$push['context']['ref_name']]) ?
152
                    [$config['name']['forbidden'][$push['context']['ref_name']]] : [],
153
154
                // check if the forbidden patterns brings any messages
155
                !isset($config['name']['forbidden_patterns']) ? [] : array_keys(array_filter(
156
                    array_flip($config['name']['forbidden_patterns']),
157
                    function ($pattern) use ($push) { return preg_match($pattern, $push['context']['ref_name']); }
158
                )),
159
160
                // do pretty much the same for the require pattern -
161
                //  but add the message if the pattern wasn't matched
162
                !isset($config['name']['required_patterns']) ? [] : array_keys(array_filter(
163
                    array_flip($config['name']['required_patterns']),
164
                    function ($pattern) use ($push) { return !preg_match($pattern, $push['context']['ref_name']); }
165
                ))
166
            )
167
        ));
168
169
        $this->out($messages, 'error');
170
        return (trim($messages) == '');
171
    }
172
173
    /**
174
     * prints notications (messages) after the push.
175
     *
176
     * Are there any messages which should be printed after the push has been confirmed to be accepted?
177
     *
178
     * This method is used for some functional programming demonstration as well.
179
     *
180
     * @param array $config
181
     * @param array $push
182
     */
183
    protected function printMessages(array $config, array $push)
184
    {
185
        // Pretty much the same again, just more wrapped into itself.
186
        $this->out(implode(
187
            "\n\n",
188
            !array_key_exists('after_push_messages', $config) ? [] : array_keys(array_filter(
189
                array_flip($config['after_push_messages']),
190
                function ($action) use ($push) {
191
                    // ensure we are displaying only the right messages for the context.
192
                    return isset($push['context']['is'][$action]) && $push['context']['is'][$action];
193
                }
194
            ))
195
        ), 'good');
196
    }
197
198
    /**
199
     * loads the configuration from the config.
200
     *
201
     * @param string $filename
202
     *
203
     * @return array
204
     *
205
     * @throws \Exception
206
     */
207
    protected function loadConfig($filename = '.gitpolicy.yml')
208
    {
209
        // parse the yml
210
        $yaml = new Parser();
211
212
        // check if the file exists
213
        if (!file_exists($filename)) {
214
            throw new \Exception($filename.' not found. Maybe you want to run the init command again?');
215
        }
216
217
        return $yaml->parse(file_get_contents($filename));
218
    }
219
220
    /**
221
     * Helps to set the environment for the application. Basically does these two things:.
222
     *
223
     *  * applies the input definition to the input and sets the result as property of the application.
224
     *  * prepares the outputinterface
225
     *
226
     * @param InputInterface  $input
227
     * @param OutputInterface $output
228
     */
229
    protected function prepEnvironment(InputInterface $input, OutputInterface $output)
230
    {
231
        $input->bind(new InputDefinition(array(
232
            new InputOption('local_ref', 'lref', InputOption::VALUE_REQUIRED, 'local ref'),
233
            new InputOption('local_sha', 'lsha', InputOption::VALUE_REQUIRED, 'local sha'),
234
            new InputOption('remote_ref', 'rref', InputOption::VALUE_REQUIRED, 'remote ref'),
235
            new InputOption('remote_sha', 'rsha', InputOption::VALUE_REQUIRED, 'remote sha'),
236
        )));
237
238
        $this->input = $input->getOptions();
239
240
        $output->getFormatter()->setStyle('error', new OutputFormatterStyle('white', 'red', array('bold')));
241
        $output->getFormatter()->setStyle('warning', new OutputFormatterStyle('black', 'yellow', array('bold')));
242
        $output->getFormatter()->setStyle('good', new OutputFormatterStyle('white', 'green', array('bold')));
243
244
        $this->output = $output;
245
    }
246
247
    /**
248
     * printing messages for lazy people like me.
249
     *
250
     * @param string $message
251
     * @param string $messageType
252
     */
253
    protected function out($message, $messageType = null)
254
    {
255
        // empty messages have no value for the user.
256
        if (trim($message) == '') {
257
            return;
258
        }
259
260
        // wrap message in tag to define the output
261
        if ($messageType != null) {
0 ignored issues
show
It seems like you are loosely comparing $messageType of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
262
            $message = "<{$messageType}>".trim($message)."</{$messageType}>";
263
        }
264
265
        // yay! print it :)
266
        $this->output->writeln($message."\n");
267
268
        // we really shouldn't continue if an error happens
269
        if ($messageType == 'error') {
270
            $this->output->writeln('<error>Stopping :/</error>');
271
        }
272
    }
273
}
274