Skip to content

Commit dbfda2e

Browse files
committed
Fix issue #17.
Add support for `--exclude-rules` option
1 parent 9ab7ef8 commit dbfda2e

File tree

2 files changed

+63
-47
lines changed

2 files changed

+63
-47
lines changed

bin/php-sl.php

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
* Displays command line help information for PHP Security Linter.
3232
*
3333
* Outputs formatted usage instructions, available options, and examples
34-
* for runningsecurity linter fromcommand line.
34+
* for running security linter from command line.
3535
*
3636
* @return void Outputs directly to STDOUT
3737
*/
@@ -42,19 +42,21 @@ function showHelp(): void
4242
Usage: php bin/php-sl.php [options]
4343
4444
Options:
45-
-p, --path=PATH Path to scan (required)
46-
--exclude=LIST Comma-separated exclusions (dirs/files)
47-
--help Show this help message
45+
-p, --path=PATH Path to scan (required).
46+
--exclude=LIST Comma-separated paths to exclude.
47+
--exclude-rules=LIST Comma-separated rule IDs to ignore.
48+
--help Show this help message.
4849
4950
Examples:
5051
php bin/php-sl.php --path ./src
51-
php bin/php-sl.php -p ./app --exclude vendor,tests
52+
php bin/php-sl.php -p ./app --exclude .git,storage,vendor,tests
53+
php bin/php-sl.php -p ./src --exclude-rules CIS-003,OWASP-001
5254
5355
HELP;
5456
}
5557

5658
/**
57-
* Outputs security scan results inspecified format.
59+
* Outputs security scan results in specified format.
5860
*
5961
* A human-readable summary is displayed with file-specific details.
6062
*
@@ -65,8 +67,7 @@ function outputResults(array $results): void
6567
{
6668
$scannedCount = $results['_meta']['scanned_count'] ?? 0;
6769
$issueCount = $results['_meta']['issue_count'] ?? 0;
68-
unset($results['_meta']); // Don't show meta data in file list
69-
70+
unset($results['_meta']); // Don't show meta data in file list.
7071
echo "Scan results\n";
7172
echo str_repeat("=", 40) . "\n\n";
7273

@@ -93,6 +94,7 @@ function runCli(array $argv): int
9394
$longOpts = [
9495
'path:',
9596
'exclude:',
97+
'exclude-rules:',
9698
'help',
9799
];
98100
$options = getopt($shortOpts, $longOpts);
@@ -102,20 +104,6 @@ function runCli(array $argv): int
102104
return 0;
103105
}
104106

105-
106-
$shortOpts = 'p:';
107-
$longOpts = [
108-
'path:',
109-
'exclude:',
110-
'help',
111-
];
112-
$options = getopt($shortOpts, $longOpts);
113-
114-
if (isset($options['help'])) {
115-
showHelp();
116-
exit(0);
117-
}
118-
119107
// Validate path.
120108
$path = $options['p'] ?? $options['path'] ?? null;
121109

@@ -124,23 +112,47 @@ function runCli(array $argv): int
124112
exit(0);
125113
}
126114

127-
// Process exclusions.
128-
$exclude = [];
115+
// Process file/dir exclusions.
116+
$excludePaths = [
117+
// Default exclusions.
118+
'vendor',
119+
'.git',
120+
'.github',
121+
'.gitlab',
122+
'.azure-pipelines',
123+
'.husky',
124+
'.circleci',
125+
'.vscode',
126+
'.idea',
127+
];
129128

130129
if (isset($options['exclude'])) {
131-
$exclude = is_array($options['exclude'])
130+
$userExclusions = is_array($options['exclude'])
132131
? $options['exclude']
133132
: explode(',', $options['exclude']);
133+
// Merge user exclusions into the default list, and ensure unique values.
134+
$excludePaths = array_unique(array_merge($excludePaths, $userExclusions));
135+
}
136+
137+
// Process rule exclusions.
138+
$excludeRules = [];
139+
140+
if (isset($options['exclude-rules'])) {
141+
$excludeRules = is_array($options['exclude-rules'])
142+
? $options['exclude-rules']
143+
: explode(',', $options['exclude-rules']);
144+
// Normalize and trim each rule ID.
145+
$excludeRules = array_map('trim', $excludeRules);
134146
}
135147

136148
try {
137-
$linter = new Linter();
138-
$results = $linter->scan($path, $exclude);
149+
// Pass rule exclusions to Linter constructor.
150+
$linter = new Linter($excludeRules);
151+
$results = $linter->scan($path, $excludePaths);
139152
outputResults($results);
140153
exit(0);
141154
} catch (LinterException $e) {
142155
fwrite(STDERR, sprintf('SCAN ERROR [%d]: %s%s', $e->getCode(), $e->getMessage(), PHP_EOL));
143-
144156
exit(2);
145157
} catch (Exception $e) {
146158
fwrite(STDERR, sprintf('FATAL ERROR: %s%s', $e->getMessage(), PHP_EOL));

src/Linter.php

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
* Class Linter
3434
*
3535
* This class is responsible for scanning PHP files within a directory for security vulnerabilities.
36-
* It applies predefined security rules to detect unsafe patterns and reportsfindings.
36+
* It applies predefined security rules to detect unsafe patterns and reports findings.
3737
*/
3838
final class Linter
3939
{
@@ -43,23 +43,32 @@ final class Linter
4343
private $rules = [];
4444

4545
/**
46-
* Constructor initializes security rules based on CIS and OWASP guidelines.
46+
* Constructor initializes security rules based on CIS and OWASP guidelines, applying exclusions.
47+
* * @param array $excludeRuleIds List of rule IDs to exclude.
4748
*/
48-
public function __construct()
49-
{
50-
$this->rules = array_merge(
49+
public function __construct(
50+
/**
51+
* @var array List of rule IDs to exclude.
52+
*/
53+
private readonly array $excludedRuleIds = []
54+
) {
55+
$allRules = array_merge(
5156
CisRules::getRules(),
5257
OwaspRules::getRules()
5358
);
59+
// Filter the rules based on the provided IDs
60+
$this->rules = array_filter($allRules, fn(array $rule): bool =>
61+
// Only keep the rule if its ID is NOT in the excludedRuleIds list
62+
!in_array($rule['id'], $this->excludedRuleIds, true));
5463
}
5564

5665
/**
57-
* Scans PHP files ingiven directory for security issues.
66+
* Scans PHP files in given directory for security issues.
5867
*
59-
* @param string $path Path todirectory to scan.
68+
* @param string $path Path to directory to scan.
6069
* @param array $exclude List of file patterns or paths to exclude from scanning.
6170
* @return array An associative array of detected security issues by file.
62-
* @throws LinterException Ifspecified path does not exist.
71+
* @throws LinterException If specified path does not exist.
6372
*/
6473
public function scan(string $path, array $exclude = []): array
6574
{
@@ -72,20 +81,17 @@ public function scan(string $path, array $exclude = []): array
7281
new RecursiveDirectoryIterator($path)
7382
);
7483
$phpFiles = new RegexIterator($iterator, '/^.+\.php$/i', RegexIterator::GET_MATCH);
75-
7684
$scannedCount = 0;
7785
$issueCount = 0;
7886

7987
foreach ($phpFiles as $file) {
8088
$filePath = $file[0];
81-
8289
if ($this->shouldExclude($filePath, $exclude)) {
8390
continue;
8491
}
8592

8693
$scannedCount++;
8794
$issues = $this->scanFile($filePath);
88-
8995
if (!empty($issues)) {
9096
$results[$filePath] = $issues;
9197
$issueCount += count($issues);
@@ -130,9 +136,7 @@ private function shouldExclude(string $filePath, array $exclude): bool
130136
continue;
131137
}
132138

133-
if (($this->isAbsolutePathMatch($filePath, $excludedPattern)) ||
134-
($this->isBasenameOrRelativePathMatch($filePath, $excludedPattern))
135-
) {
139+
if (($this->isAbsolutePathMatch($filePath, $excludedPattern)) || ($this->isBasenameOrRelativePathMatch($filePath, $excludedPattern))) {
136140
return true;
137141
}
138142
}
@@ -171,8 +175,8 @@ private function isAbsolutePathMatch(string $filePath, string $excludedPattern):
171175
*/
172176
private function isAbsolutePath(string $path): bool
173177
{
174-
return str_starts_with($path, '/') || // Unix
175-
preg_match('/^[A-Za-z]:[\/\\\\]/', $path); // Windows
178+
return str_starts_with($path, '/') || // Unix
179+
preg_match('/^[A-Za-z]:[\/\\\\]/', $path); // Windows
176180
}
177181

178182
/**
@@ -191,8 +195,8 @@ private function isBasenameOrRelativePathMatch(string $filePath, string $exclude
191195
/**
192196
* Scans a PHP file for security vulnerabilities based on predefined rules.
193197
*
194-
* @param string $filePath Path tofile to scan.
195-
* @return array List of detected security issues withinfile.
198+
* @param string $filePath Path to file to scan.
199+
* @return array List of detected security issues within file.
196200
*/
197201
private function scanFile(string $filePath): array
198202
{
@@ -208,7 +212,7 @@ private function scanFile(string $filePath): array
208212
foreach ($this->rules as $rule) {
209213
// Check full content first for better pattern matching.
210214
if (preg_match($rule['pattern'], $content)) {
211-
// Findline number where it occurs.
215+
// Find line number where it occurs.
212216
$lineNumber = 1;
213217
foreach ($lines as $line) {
214218
if (preg_match($rule['pattern'], $line)) {

0 commit comments

Comments
 (0)