> */ private array $byVulnerability = []; /** @var array> */ private array $byPackage = []; /** * @param Collection $ignores */ private function __construct(Collection $ignores) { foreach ($ignores as $ignore) { if ($ignore->expires_at !== null && $ignore->expires_at->isPast()) { continue; } $vulnerabilityId = $this->normalize($ignore->vulnerability_id); $package = $this->normalizePackage($ignore->package); if ($vulnerabilityId !== null) { $this->byVulnerability[$vulnerabilityId][] = $ignore; } elseif ($package !== null) { $this->byPackage[$package][] = $ignore; } } } /** * @param Collection $ignores */ public static function fromIgnores(Collection $ignores): self { return new self($ignores); } public static function forResource(Model $resource): self { $teamId = method_exists($resource, 'team') ? $resource->team()?->id : null; if ($teamId === null) { return new self(collect()); } $ignores = ImageScanIgnore::query() ->active() ->where('team_id', $teamId) ->where(function (Builder $query) use ($resource) { $query->whereNull('resource_type') ->orWhere(function (Builder $query) use ($resource) { $query->where('resource_type', $resource->getMorphClass()) ->where('resource_id', $resource->getKey()); }); }) ->get(); return new self($ignores); } public function matches(Finding $finding): ?ImageScanIgnore { $exploitSignal = SecurityRules::exploitSignal($finding) !== null && ! SecurityRules::isCpeOnlyOsMatch($finding); foreach ([$finding->id, ...$finding->aliases] as $candidate) { foreach ($this->byVulnerability[$this->normalize($candidate)] ?? [] as $ignore) { $package = $this->normalizePackage($ignore->package); if (($package === null || $package === $finding->package) && $this->applies($ignore, $exploitSignal)) { return $ignore; } } } foreach ($this->byPackage[$finding->package] ?? [] as $ignore) { if ($this->applies($ignore, $exploitSignal)) { return $ignore; } } return null; } /** * An ignore of a problem with an exploit signal ends 90 days after it was made, also when it was set to never end * or the problem got the signal later (a team-wide or package ignore, a new KEV entry). */ private function applies(ImageScanIgnore $ignore, bool $exploitSignal): bool { if (! $exploitSignal || $ignore->created_at === null) { return true; } return $ignore->created_at->gte(now()->subDays(self::EXPLOIT_SIGNAL_IGNORE_DAYS)); } private function normalize(?string $value): ?string { $value = trim((string) $value); return $value === '' ? null : mb_strtolower($value); } private function normalizePackage(?string $value): ?string { return $value === null || $value === '' ? null : $value; } }