NOW MANAGED ON CODEBERG
This report summarizes scanner evidence for BookStackApp/BookStack, a PHP project with 19,117 GitHub stars. SynthScan examined 296,455 lines of code across 2281 source files, recording 314 pattern matches distributed across 7 syntactic categories. The legacy adjusted pattern signal is 1.1 (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? 285 finding(s).
Are imports declared and package/API names verified? 29 finding(s).
Are wrappers, guards, and repeated structures necessary? 254 finding(s).
Which changes need focused human review? 314 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 314 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/Settings/AppSettingsStore.php | 88 | protected function destroyExistingSettingImage(string $settingKey): void | CODE |
| LOW | app/App/Providers/EventServiceProvider.php | 49 | protected function configureEmailVerification(): void | CODE |
| LOW | app/Access/LdapService.php | 361 | protected function extractGroupNamesFromLdapGroupDns(array $groupDNs): array | CODE |
| LOW | app/Access/LdapService.php | 440 | protected function extractGroupsFromSearchResponseEntry(array $ldapEntry): array | CODE |
| LOW | app/Access/SocialAuthService.php | 56 | public function handleRegistrationCallback(string $socialDriver, SocialUser $socialUser): SocialUser | CODE |
| LOW | app/Access/LoginService.php | 97 | protected function getLastLoginAttemptDetails(): array | CODE |
| LOW | app/Access/LoginService.php | 120 | protected function setLastLoginAttemptedForUser(User $user, string $method, bool $remember): void | CODE |
| LOW | app/Access/LoginService.php | 147 | public function awaitingEmailConfirmation(User $user): bool | CODE |
| LOW | app/Access/RegistrationService.php | 28 | public function ensureRegistrationAllowed() | CODE |
| LOW | app/Access/Saml2Service.php | 202 | protected function loadOneloginServiceProviderDetails(): array | CODE |
| LOW | app/Access/GroupSyncService.php | 29 | protected function externalIdMatchesGroupNames(string $externalId, array $groupNames): bool | CODE |
| LOW | app/Access/GroupSyncService.php | 56 | protected function matchGroupsToSystemsRoles(array $groupNames): Collection | CODE |
| LOW | app/Access/SocialDriverManager.php | 51 | public function isAutoConfirmEmailEnabled(string $driver): bool | CODE |
| LOW | app/Access/SocialDriverManager.php | 79 | public function getConfigureForRedirectCallback(string $driver): callable | CODE |
| LOW | app/Access/Oidc/OidcProviderSettings.php | 114 | protected function loadSettingsFromIssuerDiscovery(ClientInterface $httpClient): array | CODE |
| LOW | app/Access/Oidc/OidcService.php | 177 | protected function processAccessTokenCallback(OidcAccessToken $accessToken, OidcProviderSettings $settings): User | CODE |
| LOW | app/Access/Oidc/OidcJwtWithClaims.php | 62 | public function validateCommonTokenDetails(string $clientId): bool | CODE |
| LOW | app/Access/Oidc/OidcOAuthProvider.php | 50 | public function getResourceOwnerDetailsUrl(AccessToken $token): string | CODE |
| LOW | app/Access/Controllers/LoginController.php | 167 | protected function sendLoginAttemptExceptionResponse(LoginAttemptException $exception, Request $request) | CODE |
| LOW | app/Access/Controllers/LoginController.php | 186 | protected function updateIntendedFromPrevious(): void | CODE |
| LOW | app/Access/Controllers/HandlesPartialLogins.php | 14 | protected function currentOrLastAttemptedUser(): User | CODE |
| LOW | app/Access/Guards/LdapSessionGuard.php | 112 | protected function createNewFromLdapAndCreds(array $ldapUserDetails, array $credentials): User | CODE |
| LOW | app/Util/HtmlContentFilter.php | 51 | protected function filterOutScriptsFromDocument(HtmlDocument $doc): void | CODE |
| LOW | app/Util/HtmlContentFilter.php | 89 | protected function filterOutFormElementsFromDocument(HtmlDocument $doc): void | CODE |
| LOW | app/Util/HtmlContentFilter.php | 116 | protected function filterOutBadHtmlElementsFromDocument(HtmlDocument $doc): void | CODE |
| LOW | app/Util/HtmlContentFilter.php | 123 | protected function filterOutNonContentElementsFromDocument(HtmlDocument $doc): void | CODE |
| LOW | app/Util/HtmlContentFilter.php | 174 | public static function removeScriptsFromDocument(HtmlDocument $doc): void | CODE |
| LOW | app/Util/HtmlContentFilter.php | 189 | public static function removeScriptsFromHtmlString(string $html): string | CODE |
| LOW | app/Util/CspService.php | 62 | public function allowedIFrameHostsConfigured(): bool | CODE |
| LOW | app/Util/HtmlPurifier/ConfiguredHtmlPurifier.php | 56 | protected function createCacheFolderIfNeeded(string $cachePath): void | CODE |
| LOW | app/References/ReferenceFetcher.php | 36 | public function getReferenceCountToEntity(Entity $entity): int | CODE |
| LOW | app/References/CrossLinkParser.php | 87 | public static function createWithEntityResolvers(): self | CODE |
| LOW | app/References/ReferenceStore.php | 76 | protected function dropReferencesFromEntities(array $entities): void | CODE |
| LOW | app/References/ReferenceUpdater.php | 35 | public function changeReferencesUsingContext(ReferenceChangeContext $context): void | CODE |
| LOW | app/References/ReferenceUpdater.php | 101 | protected function updateReferencesWithinEntity(Entity $entity, string $oldLink, string $newLink): void | CODE |
| LOW | app/References/ReferenceUpdater.php | 112 | protected function updateReferencesWithinDescription(Entity&HasDescriptionInterface $entity, string $oldLink, string | CODE |
| LOW | app/References/ReferenceUpdater.php | 120 | protected function updateReferencesWithinPage(Page $page, string $oldLink, string $newLink): void | CODE |
| LOW | app/Activity/Tools/WebhookFormatter.php | 73 | public function addDefaultModelFormatters(): void | CODE |
| LOW | …ity/Notifications/Handlers/BaseNotificationHandler.php | 19 | protected function sendNotificationToUserIds(string $notification, array $userIds, User $initiator, string|Loggable | CODE |
| LOW | …cations/Handlers/CommentMentionNotificationHandler.php | 77 | protected function getPreviouslyNotifiedUserIds(Comment $comment): array | CODE |
| LOW | app/Uploads/ImageStorage.php | 33 | public function usingSecureRestrictedImages(): bool | CODE |
| LOW | app/Uploads/ImageStorageDisk.php | 108 | public function destroyAllMatchingNameFromPath(string $path): void | CODE |
| LOW | app/Uploads/UserAvatars.php | 45 | public function assignToUserFromExistingData(User $user, string $imageData, string $extension): void | CODE |
| LOW | app/Uploads/UserAvatars.php | 123 | protected function createAvatarImageFromData(User $user, string $imageData, string $extension): Image | CODE |
| LOW | app/Uploads/AttachmentService.php | 21 | public function streamAttachmentFromStorage(Attachment $attachment) | CODE |
| LOW | app/Uploads/AttachmentService.php | 105 | public function updateFileOrderWithinPage(array $attachmentOrder, int $pageId) | CODE |
| LOW | app/Uploads/ImageResizer.php | 32 | public function loadGalleryThumbnailsForMany(iterable $images, bool $shouldCreate = false): void | CODE |
| LOW | app/Uploads/ImageResizer.php | 42 | public function loadGalleryThumbnailsForImage(Image $image, bool $shouldCreate): void | CODE |
| LOW | app/Uploads/ImageResizer.php | 161 | protected function interventionFromImageData(string $imageData, ?string $fileType): InterventionImage | CODE |
| LOW | app/Uploads/ImageResizer.php | 195 | protected function orientImageToOriginalExif(InterventionImage $image, string $originalData): void | CODE |
| LOW | app/Uploads/FaviconHandler.php | 51 | public function restoreOriginalIfNotExists(): bool | CODE |
| LOW | app/Uploads/ImageService.php | 119 | public function replaceExistingFromUpload(string $path, string $type, UploadedFile $file): void | CODE |
| LOW | app/Uploads/ImageService.php | 252 | public function pathAccessibleInLocalSecure(string $imagePath): bool | CODE |
| LOW | app/Uploads/ImageService.php | 315 | protected function checkUserHasAccessToRelationOfImageAtPath(string $path): bool | CODE |
| LOW | app/Uploads/ImageService.php | 340 | protected function checkUserHasAccessToRelationOfImage(Image $image): bool | CODE |
| LOW | app/Uploads/ImageService.php | 368 | public function streamImageFromStorageResponse(string $imageType, string $path): StreamedResponse | CODE |
| LOW | app/Permissions/JointPermissionBuilder.php | 127 | protected function buildJointPermissionsForBooks(EloquentCollection $books, array $roles, bool $deleteOld = false): | CODE |
| LOW | app/Permissions/JointPermissionBuilder.php | 152 | protected function buildJointPermissionsForEntities(array $entities): void | CODE |
| LOW | app/Permissions/JointPermissionBuilder.php | 164 | protected function deleteManyJointPermissionsForEntities(array $entities): void | CODE |
| LOW | app/Permissions/JointPermissionBuilder.php | 202 | protected function createManyJointPermissions(array $originalEntities, array $roles): void | CODE |
| 194 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | app/Console/Commands/CreateAdminCommand.php | 124 | $details['email'] = 'admin@example.com'; | CODE |
| LOW | …rces/views/settings/categories/customization.blade.php | 139 | 'placeholder' => trans('settings.app_homepage_select'), | CODE |
| LOW | resources/views/auth/register.blade.php | 34 | @include('form.password', ['name' => 'password', 'placeholder' => trans('auth.password_hint')]) | CODE |
| LOW | resources/views/auth/invite-set-password.blade.php | 15 | @include('form.password', ['name' => 'password', 'placeholder' => trans('auth.password_hint')]) | CODE |
| LOW | resources/views/attachments/manager-edit-form.blade.php | 39 | 'placeholder' => trans('entities.attachments_edit_drop_upload'), | CODE |
| LOW | resources/views/pages/parts/form.blade.php | 28 | @include('form.text', ['name' => 'name', 'model' => $model, 'placeholder' => trans('entities.pages_t | CODE |
| LOW | resources/views/entities/template-selector.blade.php | 9 | 'placeholder' => trans('entities.default_template_select'), | CODE |
| LOW | tests/Auth/AuthTest.php | 124 | $this->post('/login', ['email' => 'admin@example.com', 'password' => 'cattreedog']); | CODE |
| LOW | tests/Auth/Saml2Test.php | 99 | 'email' => 'user@example.com', | CODE |
| LOW | tests/Auth/Saml2Test.php | 367 | $this->assertDatabaseMissing('users', ['email' => 'user@example.com']); | CODE |
| LOW | tests/Auth/Saml2Test.php | 406 | 'email' => 'user@example.com', | CODE |
| LOW | tests/Auth/Saml2Test.php | 415 | 'email' => 'user@example.com', | CODE |
| LOW | tests/Auth/Saml2Test.php | 439 | 'email' => 'user@example.com', | CODE |
| LOW | tests/Auth/Saml2Test.php | 441 | $this->assertEquals('user@example.com', user()->email); | CODE |
| LOW | tests/Auth/Saml2Test.php | 524 | * 0 => "user@example.com" | COMMENT |
| LOW⚡ | tests/Entity/PageContentFilteringTest.php | 320 | '<div>Lorem ipsum dolor sit amet.</div><p onclick="console.log(\'test\')">Hello</p>', | CODE |
| LOW⚡ | tests/Entity/PageContentFilteringTest.php | 320 | '<div>Lorem ipsum dolor sit amet.</div><p onclick="console.log(\'test\')">Hello</p>', | CODE |
| LOW⚡ | tests/Entity/PageContentFilteringTest.php | 321 | '<div>Lorem ipsum dolor sit amet.<p onclick="console.log(\'test\')">Hello</p></div>', | CODE |
| LOW⚡ | tests/Entity/PageContentFilteringTest.php | 321 | '<div>Lorem ipsum dolor sit amet.<p onclick="console.log(\'test\')">Hello</p></div>', | CODE |
| LOW⚡ | tests/Entity/PageContentFilteringTest.php | 322 | '<div><div><div><div>Lorem ipsum dolor sit amet.<p onclick="console.log(\'test\')">Hello</p></div></div></di | CODE |
| LOW⚡ | tests/Entity/PageContentFilteringTest.php | 322 | '<div><div><div><div>Lorem ipsum dolor sit amet.<p onclick="console.log(\'test\')">Hello</p></div></div></di | CODE |
| LOW⚡ | tests/Entity/PageContentFilteringTest.php | 323 | '<div onclick="console.log(\'test\')">Lorem ipsum dolor sit amet.</div><p onclick="console.log(\'test\')">He | CODE |
| LOW⚡ | tests/Entity/PageContentFilteringTest.php | 323 | '<div onclick="console.log(\'test\')">Lorem ipsum dolor sit amet.</div><p onclick="console.log(\'test\')">He | CODE |
| LOW | tests/Commands/CreateAdminCommandTest.php | 207 | 'email' => 'admin@example.com', | CODE |
| LOW⚡ | public/libs/tinymce/tinymce.min.js | 4 | !function(){"use strict";var e=function(e){if(null===e)return"null";if(void 0===e)return"undefined";var t=typeof e;retur | CODE |
| LOW | public/libs/tinymce/themes/silver/theme.min.js | 4 | !function(){"use strict";const e=Object.getPrototypeOf,t=(e,t,o)=>{var n;return!!o(e,t.prototype)||(null===(n=e.construc | CODE |
| LOW | dev/api/responses/shelves-read.json | 70 | "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", | CODE |
| LOW | dev/api/responses/shelves-read.json | 70 | "description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | jest.config.ts | 21 | // Indicates whether the coverage information should be collected while executing the test | COMMENT |
| LOW | jest.config.ts | 41 | // "text", | COMMENT |
| LOW | jest.config.ts | 61 | // forceCoverageMatch: [], | COMMENT |
| LOW | jest.config.ts | 81 | COMMENT | |
| LOW | jest.config.ts | 121 | // Use this configuration option to add custom reporters to Jest | COMMENT |
| LOW | jest.config.ts | 141 | "./resources/js" | COMMENT |
| LOW | jest.config.ts | 161 | COMMENT | |
| LOW | jest.config.ts | 181 | // This option allows the use of a custom results processor | COMMENT |
| LOW | app/Config/oidc.php | 41 | // A false value force-disables RP-Initiated Logout. | COMMENT |
| LOW | app/Config/app.php | 21 | // Shows much more verbose error messages. Has potential to show | COMMENT |
| LOW | app/Config/app.php | 41 | // This setting is a string of characters which represent different available filters: | COMMENT |
| LOW | app/Config/app.php | 61 | // and used by BookStack in URL generation. | COMMENT |
| LOW | app/Config/app.php | 81 | // Space separated if multiple. BookStack host domain is auto-inferred, in addition to | COMMENT |
| LOW | app/Config/session.php | 41 | // When using the "apc" or "memcached" session drivers, you may specify a | COMMENT |
| LOW | app/Config/session.php | 61 | // your application but you are free to change this when necessary. | COMMENT |
| LOW | app/Config/session.php | 81 | COMMENT | |
| LOW | app/Config/debugbar.php | 21 | COMMENT | |
| LOW | app/Config/debugbar.php | 121 | COMMENT | |
| LOW | app/Config/saml2.php | 61 | // SAML protocol binding to be used when returning the <Response> | COMMENT |
| LOW | app/Config/logging.php | 21 | // messages to the logs. The name specified in this option should match | COMMENT |
| LOW | resources/js/wysiwyg/ui/defaults/toolbars.ts | 201 | COMMENT | |
| LOW | resources/js/wysiwyg/lexical/core/LexicalReconciler.ts | 721 | // We also want to make sure we clear them down, otherwise we | COMMENT |
| LOW | …xical/core/nodes/__tests__/unit/LexicalTabNode.test.ts | 61 | invariant($isRangeSelection(selection), 'isRangeSelection(selection)'); | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | app/Config/logging.php | 37 | // you a variety of powerful log handlers / formatters to utilize. | COMMENT |
| MEDIUM | resources/js/wysiwyg/lexical/core/LexicalUpdates.ts | 252 | // We leverage editor._dirtyLeaves to track the new dirty leaves after the transforms | COMMENT |
| MEDIUM | …iwyg/lexical/core/__tests__/unit/LexicalEditor.test.ts | 529 | // Add transform makes everything dirty the first time (let's not leverage this here) | COMMENT |
| MEDIUM⚡ | public/libs/tinymce/tinymce.min.js | 4 | !function(){"use strict";var e=function(e){if(null===e)return"null";if(void 0===e)return"undefined";var t=typeof e;retur | CODE |
| MEDIUM⚡ | public/libs/tinymce/tinymce.min.js | 4 | !function(){"use strict";var e=function(e){if(null===e)return"null";if(void 0===e)return"undefined";var t=typeof e;retur | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| CRITICAL | public/libs/tinymce/themes/silver/theme.min.js | 4 | !function(){"use strict";const e=Object.getPrototypeOf,t=(e,t,o)=>{var n;return!!o(e,t.prototype)||(null===(n=e.construc | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | …xical/core/__tests__/unit/LexicalSerialization.test.ts | 110 | const expectedStringifiedEditorState = `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal"," | CODE |
| LOW | …xical/core/__tests__/unit/LexicalSerialization.test.ts | 119 | `{"root":{"children":[{"children":[{"detail":0,"format":0,"mode":"normal","style":"","text":"Welcome to the play | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | resources/js/wysiwyg/lexical/utils/index.ts | 426 | // TODO: implement this functionality | COMMENT |