The scanner resolves predefined targets to file globs:
// scanner.go:227-250func targetGlobs(target, root string) []string { switch target { case "php-files": return []string{filepath.Join(root, "*.php"), filepath.Join(root, "app", "*.php")} case "blade-files": return []string{filepath.Join(root, "resources", "views", "*.blade.php")} case "config-files": return []string{filepath.Join(root, "config", "*.php")} case "env-files": return []string{filepath.Join(root, ".env"), filepath.Join(root, ".env.*")} case "routes-files": return []string{filepath.Join(root, "routes", "*.php")} case "migration-files": return []string{filepath.Join(root, "database", "migrations", "*.php")} case "js-files": return []string{filepath.Join(root, "resources", "js", "*.js"), filepath.Join(root, "resources", "js", "*.ts")} default: // Custom glob pattern if strings.ContainsAny(target, "*?[") { return []string{filepath.Join(root, target)} } return nil }}
For php-files, blade-files, and js-files, the scanner recursively walks subdirectories while skipping vendor, node_modules, .git, and other common exclusions.
Negative patterns trigger when a pattern is absent:
// scanner.go:118-127if pat.Negative { // Finding if pattern was NOT found in this file if len(matches) == 0 { findings = append(findings, s.buildFinding(rule, rel, 0, "")) }} else { for _, m := range matches { findings = append(findings, s.buildFinding(rule, rel, m.line, m.text)) }}
Example use case:
- id: AUTH-005 title: "Missing @csrf directive in form" patterns: - type: contains target: blade-files pattern: '<form' negative: true # Trigger if <form> exists but @csrf does NOT - type: contains target: blade-files pattern: '@csrf' negative: true
- id: INJECT-001 title: "Potential SQL injection via DB::raw with variable interpolation" description: "DB::raw() is being called with what looks like variable interpolation. This can lead to SQL injection if user input reaches the query." severity: high category: Injection enabled: true patterns: - type: regex target: php-files pattern: 'DB::raw\(.*\$' remediation: | Use parameter binding instead: DB::raw('SELECT * FROM users WHERE id = ?', [$userId]) Or use query builder methods: DB::table('users')->where('id', $userId)->get() references: - https://owasp.org/www-community/attacks/SQL_Injection
- id: XSS-001 title: "Unescaped Blade output" description: "The {!! !!} syntax outputs raw HTML without escaping. If the variable contains user input, this creates an XSS vulnerability." severity: high category: XSS enabled: true patterns: - type: regex target: blade-files pattern: '\{!!.*\$.*!!\}' remediation: | Use {{ }} for automatic HTML escaping: {{ $variable }} Only use {!! !!} for trusted HTML content, never user input.