A free, self-hostable news aggregator…
This report summarizes scanner evidence for FreshRSS/FreshRSS, a PHP project with 16,469 GitHub stars. SynthScan examined 192,718 lines of code across 932 source files, recording 243 pattern matches distributed across 7 syntactic categories. The legacy adjusted pattern signal is 1.5 (Low pattern signal); it is not an authorship estimate.
Use the findings to ask whether maintainers can explain intent, verify dependencies, simplify unnecessary scaffolding, and focus review effort. Risk Payload v1 adds ownership, supply-chain, over-engineering, and review-effort vectors when available. These are prompts for human verification, not probabilities, security findings, or measured maintenance costs.
Use each vector to guide manual verification of the underlying findings.
Payload schema: 1.0.0 · Engine: 2.0.0 · Scoring: heuristic-density-v1 · Scan date: 2026-10-31.
Can maintainers explain intent, assumptions, and trade-offs? 236 finding(s).
Are imports declared and package/API names verified? 7 finding(s).
Are wrappers, guards, and repeated structures necessary? 214 finding(s).
Which changes need focused human review? 243 finding(s).
This chart tracks the legacy adjusted pattern score across scan runs. Changes can reflect source changes, scan coverage, or detector/scoring versions; they do not measure AI adoption or prove that a team refactored code. Compare runs only when their scoring versions are compatible. The dashed line tracks the raw pattern-hit count.
Groups findings by configured severity weight. Severity is not a confidence estimate, security rating, or proof that a package is missing. Verify imports against the project's manifests and inspect each finding in its local design context.
This horizontal bar chart shows where configured pattern scores accumulate across directories. Treat the bars as locations to inspect, not as evidence of authorship or code quality. Use the file-level findings to decide whether a directory needs a focused ownership, dependency, complexity, or review check.
The scanner identified 243 distinct pattern matches across 7 syntactic categories. Each entry below represents a discrete location in the source code where the engine recorded a detector match to inspect in context. Expand any category row to inspect the individual file paths, line numbers, code snippets, and the lexical context (CODE, COMMENT, or STRING) in which each match was detected.
Reading the findings table: The Severity column indicates the configured finding weight (CRITICAL / HIGH / MEDIUM / LOW). The Context column identifies whether the match occurred inside executable code, an inline comment, or a string literal — legacy comment-context matches receive a ×1.5 score weight. The ⚡ bolt icon marks clustered matches: three or more patterns within a 10-line window, each receiving an additional ×1.5 density multiplier under the legacy scoring model.
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | app/Models/Feed.php | 425 | public function newestArticleReceivedDate(): int { | CODE |
| LOW | app/Models/Feed.php | 435 | public function newestArticlePublicationDate(): int { | CODE |
| LOW | app/Models/Feed.php | 974 | private function dotNotationForStandardJsonFeed(): array { | CODE |
| LOW | app/Models/StatsDAOSQLite.php | 29 | protected function calculateEntryRepartitionPerFeedPerPeriod(string $period, ?int $feed = null): array { | CODE |
| LOW | app/Models/Search.php | 1323 | private function parseNotModifiedDateSearch(string $input): string { | CODE |
| LOW | app/Models/CategoryDAO.php | 361 | public function listCategoriesOrderUpdate(int $defaultCacheDuration = 86400, int $limit = 0): array { | CODE |
| LOW | app/Models/CategoryDAO.php | 488 | private static function daoToCategoriesPrepopulated(array $listDAO): array { | CODE |
| LOW⚡ | app/Models/StatsDAOPGSQL.php | 27 | public function calculateEntryRepartitionPerFeedPerHour(?int $feed = null): array { | CODE |
| LOW⚡ | app/Models/StatsDAOPGSQL.php | 36 | public function calculateEntryRepartitionPerFeedPerDayOfWeek(?int $feed = null): array { | CODE |
| LOW⚡ | app/Models/StatsDAOPGSQL.php | 45 | public function calculateEntryRepartitionPerFeedPerMonth(?int $feed = null): array { | CODE |
| LOW⚡ | app/Models/StatsDAOPGSQL.php | 55 | protected function calculateEntryRepartitionPerFeedPerPeriod(string $period, ?int $feed = null): array { | CODE |
| LOW | app/Models/StatsDAO.php | 41 | public function calculateEntryRepartition(): array { | CODE |
| LOW | app/Models/StatsDAO.php | 58 | public function calculateEntryRepartitionPerFeed(?int $feed = null, bool $only_main = false): array|false { | CODE |
| LOW⚡ | app/Models/StatsDAO.php | 126 | public function calculateEntryRepartitionPerFeedPerHour(?int $feed = null): array { | CODE |
| LOW⚡ | app/Models/StatsDAO.php | 134 | public function calculateEntryRepartitionPerFeedPerDayOfWeek(?int $feed = null): array { | CODE |
| LOW⚡ | app/Models/StatsDAO.php | 142 | public function calculateEntryRepartitionPerFeedPerMonth(?int $feed = null): array { | CODE |
| LOW | app/Models/StatsDAO.php | 154 | protected function calculateEntryRepartitionPerFeedPerPeriod(string $period, ?int $feed = null): array { | CODE |
| LOW⚡ | app/Models/StatsDAO.php | 190 | public function calculateEntryAveragePerFeedPerHour(?int $feed = null): float { | CODE |
| LOW⚡ | app/Models/StatsDAO.php | 197 | public function calculateEntryAveragePerFeedPerDayOfWeek(?int $feed = null): float { | CODE |
| LOW⚡ | app/Models/StatsDAO.php | 204 | public function calculateEntryAveragePerFeedPerMonth(?int $feed = null): float { | CODE |
| LOW⚡ | app/Models/StatsDAO.php | 212 | public static function calculateEntryAverageFromRepartition(array $repartition): float { | CODE |
| LOW⚡ | app/Models/StatsDAO.php | 220 | protected function calculateEntryAveragePerFeedPerPeriod(float $period, ?int $feed = null): float { | CODE |
| LOW | app/Models/FeedDAO.php | 541 | public function newestArticleReceivedDate(int $feedId): int { | CODE |
| LOW | app/Models/FeedDAO.php | 549 | public function newestArticlePublicationDate(int $feedId): int { | CODE |
| LOW | app/Controllers/userController.php | 658 | public function sendValidationEmailAction(): void { | CODE |
| LOW | app/Controllers/feedController.php | 1309 | public function contentSelectorPreviewAction(): void { | CODE |
| LOW | app/Controllers/extensionController.php | 47 | protected function getAvailableExtensionList(): array { | CODE |
| LOW | tests/app/Utils/dotNotationUtilTest.php | 50 | public static function testConvertJsonToRssEscapesCategories(): void { | CODE |
| LOW | tests/app/Utils/dotNotationUtilTest.php | 76 | public static function testConvertJsonToRssKeepsAttachments(): void { | CODE |
| LOW | tests/app/Models/SimplePieCustomTest.php | 93 | public static function provideIncompleteTagsOrEntities(): Traversable { | CODE |
| LOW | tests/app/Models/BooleanSearchTest.php | 50 | public static function provideTooLongOrTooDeepSearches(): array { | CODE |
| LOW | tests/app/Models/FeedTest.php | 38 | public static function provideStoredAndRequestedUrls(): array { | CODE |
| LOW | tests/app/Models/ContextTest.php | 37 | public static function testAutomaticStateSurvivesUrlEncoding(): void { | CODE |
| LOW | tests/app/Models/SearchTest.php | 220 | public static function provideModifiedDateSearch(): array { | CODE |
| LOW | tests/app/Models/SearchTest.php | 297 | public static function provideSavedQueriesExpansion(): array { | CODE |
| LOW | tests/app/Models/SearchTest.php | 504 | public static function provideconsistentOrParentheses(): array { | CODE |
| LOW | tests/app/Models/SearchTest.php | 1156 | public static function testBooleanSearchToString(string $input, string $expected): void { | CODE |
| LOW | tests/app/Models/SearchTest.php | 1164 | public static function provideBooleanSearchToString(): array { | CODE |
| LOW | tests/app/Models/SearchTest.php | 1213 | public static function testBooleanSearchToStringExpansion(array $queries, string $input, | CODE |
| LOW | tests/app/Models/SearchTest.php | 1232 | public static function provideBooleanSearchToStringExpansion(): array { | CODE |
| LOW | tests/app/Models/SearchTest.php | 1303 | public static function provideBooleanSearchEnforce(): array { | CODE |
| LOW | tests/app/Models/SearchTest.php | 1337 | public static function provideBooleanSearchRemove(): array { | CODE |
| LOW | tests/app/Models/FormAuthTest.php | 11 | public function testReqsCheckReturnsFalseIfEmpty(): void { | CODE |
| LOW | tests/app/Models/FormAuthTest.php | 67 | public function testReqsCheckAcceptsOtherMultibyteBoundary(): void { | CODE |
| LOW | tests/app/Models/FormAuthTest.php | 91 | public function testAuthWithInvalidUsernameAndCorrectCredentialsFail(): void { | CODE |
| LOW | tests/app/Models/FormAuthTest.php | 102 | public function testAuthWithValidUsernameAndWrongCredentialsFail(): void { | CODE |
| LOW | tests/app/Models/FormAuthTest.php | 113 | public function testAuthWithValidUsernameAndCorrectCredentialsOk(): void { | CODE |
| LOW | tests/app/Models/FormAuthTest.php | 255 | public function testAuthWithValidUsernameAndCorrectCredentialsAndEmptyPasswordFail(): void { | CODE |
| LOW | tests/app/Models/FormAuthTest.php | 266 | public function testAuthWithValidUsernameAndCorrectCredentialsAndTooLongPasswordFail(): void { | CODE |
| LOW | tests/app/Models/FormAuthTest.php | 296 | public function testAuthWithValidUsernameAndCorrectCredentialsAndWrongPassAlgorithmFail(): void { | CODE |
| LOW | tests/app/Models/FormAuthTest.php | 307 | public function testAuthWithValidUsernameAndAnyCredentialsAndMalformedBcryptHashFail(): void { | CODE |
| LOW | tests/app/Models/FormAuthTest.php | 318 | public function testAuthWithValidUsernameAndAnyCredentialsAndEmptyHashFail(): void { | CODE |
| LOW | tests/app/Models/FormAuthTest.php | 329 | public function testAuthWithValidUsernameAndCorrectHashAsPasswordFail(): void { | CODE |
| LOW⚡ | tests/app/Models/StatsDAOTest.php | 7 | public static function testCalculateEntryAverageFromRepartition(): void { | CODE |
| LOW⚡ | tests/app/Models/StatsDAOTest.php | 11 | public static function testCalculateEntryAverageFromEmptyRepartition(): void { | CODE |
| LOW⚡ | tests/app/Models/StatsDAOTest.php | 15 | public static function testCalculateEntryAverageFromZeroRepartition(): void { | CODE |
| LOW | tests/app/Models/DatabaseDAOTest.php | 46 | public static function provideStrilikeAccentsCasing(): array { | CODE |
| LOW | tests/app/Models/DatabaseDAOTest.php | 56 | public static function provideStrilikeUnicodeCasing(): array { | CODE |
| LOW | tests/app/Models/DatabaseDAOTest.php | 83 | public static function provideStrilikeNoUnicodeCasing(): array { | CODE |
| LOW⚡ | tests/app/Models/UserDAOTest.php | 10 | public function testExistsRejectsPathTraversal(string $payload): void { | CODE |
| 154 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | config.default.php | 61 | # `none` is without password and shows only the default user; | COMMENT |
| LOW | config.default.php | 81 | COMMENT | |
| LOW | config.default.php | 161 | COMMENT | |
| LOW | config.default.php | 261 | # - 10.0.0.0/8 | COMMENT |
| LOW | Docker/README.md | 361 | LISTEN: 0.0.0.0:80 | COMMENT |
| LOW | Docker/freshrss/docker-compose.yml | 21 | - extensions:/var/www/FreshRSS/extensions | COMMENT |
| LOW | app/Models/Search.php | 181 | COMMENT | |
| LOW | cli/README.md | 41 | # --base-url should be a public (routable) URL if possible, and is used for push (WebSub), for some API functions (e.g. | COMMENT |
| LOW | cli/README.md | 61 | ### User | COMMENT |
| LOW | cli/README.md | 161 | COMMENT | |
| LOW | cli/README.md | 181 | # -o, --origin-language selects the origin language (only used with add language action). | COMMENT |
| LOW | lib/phpmailer/phpmailer/src/PHPMailer.php | 1901 | //A space after `-f` is optional, but there is a long history of its presence | COMMENT |
| LOW | lib/phpmailer/phpmailer/src/PHPMailer.php | 5261 | //Unfold header lines | COMMENT |
| LOW | lib/Minz/FrontController.php | 1 | <?php | COMMENT |
| LOW | lib/simplepie/simplepie/src/Parse/Date.php | 181 | 'août' => 8, | COMMENT |
| LOW | p/i/index.php | 1 | <?php | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | app/Models/SimplePieCustom.php | 183 | 'textarea' => ['cols', 'disabled', 'maxlength', 'minlength', 'placeholder', 'readonly', 'rows', 'wrap'], | CODE |
| LOW | lib/phpmailer/phpmailer/README.md | 111 | $mail->Username = 'user@example.com'; //SMTP username | CODE |
| LOW | lib/phpmailer/phpmailer/src/PHPMailer.php | 1498 | * PHPMailer::validateAddress('user@example.com', function($address) { | COMMENT |
| LOW | lib/phpmailer/phpmailer/src/PHPMailer.php | 1532 | * * quoted elements: `'"test blah"@example.org'` | COMMENT |
| LOW⚡ | data/tos.example.html | 3 | <h2>Article 1: Lorem ipsum</h2> | CODE |
| LOW⚡ | data/tos.example.html | 6 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fringilla, | CODE |
| LOW⚡ | data/tos.example.html | 6 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fringilla, | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | app/Models/Feed.php | 623 | // Retry-After is stored for the URL as requested, not as stored for the feed | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | config.default.php | 250 | # Will be checked against CONN_REMOTE_ADDR (if available, to be robust even when using Apache mod_remoteip) | COMMENT |
| MEDIUM | app/Controllers/feedController.php | 1340 | //Get first entry (syntax robust for Generator or Array) | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | config.default.php | 265 | # Here you can add overrides for particular IP/domain:port combinations | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | cli/access-permissions.sh | 40 | # Read files, and directory traversal | COMMENT |
| LOW | cli/translation-update.sh | 15 | # Check if po4a is installed | COMMENT |