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 */
3838final 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