LLM Frontend for Power Users.
This report summarizes scanner evidence for SillyTavern/SillyTavern, a JavaScript project with 35,148 GitHub stars. SynthScan examined 282,003 lines of code across 770 source files, recording 472 pattern matches distributed across 12 syntactic categories. The legacy adjusted pattern signal is 2.0 (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? 453 finding(s).
Are imports declared and package/API names verified? 17 finding(s).
Are wrappers, guards, and repeated structures necessary? 383 finding(s).
Which changes need focused human review? 472 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 472 distinct pattern matches across 12 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 | tests/util.test.js | 8 | function createMockExpressResponse() { | CODE |
| LOW | tests/frontend/MacroLexer.e2e.js | 1364 | async function runLexerGetTokensAndErrors(page, input) { | CODE |
| LOW | tests/frontend/MacroEngine.e2e.js | 3377 | async function evaluateWithEngineAndCaptureMacroLogs(page, input) { | CODE |
| LOW | tests/frontend/MacroEngine.e2e.js | 3413 | async function evaluateWithEngineAndVariables(page, input, variables) { | CODE |
| LOW | tests/frontend/MacroRegistry.e2e.js | 623 | async function registerMacroAndCaptureErrors(page, { macroName, options }) { | CODE |
| LOW | tests/frontend/MacroRegistry.e2e.js | 679 | async function registerAliasAndCaptureErrors(page, { targetMacroName, aliasName, options = {} }) { | CODE |
| LOW | tests/frontend/ReasoningHiddenBlocks.e2e.js | 4 | function createEditableReasoningMessageHtml({ detailsAttributes = 'data-has-content="false"', includeMessageContainers = | CODE |
| LOW | public/script.js | 1086 | function verifyCharactersSearchSortRule() { | CODE |
| LOW | public/script.js | 1364 | export async function deleteCharacterChatByName(characterId, fileName) { | CODE |
| LOW | public/script.js | 1614 | function getMessageDeletionStartId(id, deleteToolCalls = true) { | CODE |
| LOW | public/script.js | 2050 | export function ensureMessageMediaIsArray(mes) { | CODE |
| LOW | public/script.js | 2506 | function updateMessageItemizedPromptButton(message, { messageId = chat.indexOf(message), messageElement = chatElement.fi | CODE |
| LOW | public/script.js | 3203 | function addPersonaDescriptionExtensionPrompt() { | CODE |
| LOW | public/script.js | 3281 | export function getExtensionPromptMaxDepth() { | CODE |
| LOW | public/script.js | 3402 | export function getCharacterCardFieldsLazy({ chid = undefined } = {}) { | CODE |
| LOW | public/script.js | 7114 | export function setExternalAbortController(controller) { | CODE |
| LOW | public/script.js | 8121 | export function setGenerationParamsFromPreset(preset) { | CODE |
| LOW | public/script.js | 8632 | export function selectRightMenuWithAnimation(selectedMenuId) { | CODE |
| LOW | public/script.js | 8942 | export function getExtensionPromptRoleByName(roleName) { | CODE |
| LOW | public/script.js | 8996 | export async function setCharacterSettingsOverrides() { | CODE |
| LOW | public/script.js | 9481 | export function getFirstDisplayedMessageId() { | CODE |
| LOW | public/script.js | 9547 | function updateAlternateGreetingsHintVisibility(root) { | CODE |
| LOW | public/script.js | 9714 | function handleMoveAlternateGreeting(event, direction) { | CODE |
| LOW | public/script.js | 10658 | export async function renameGroupOrCharacterChat({ characterId, groupId, oldFileName, newFileName, loader: showLoader }) | CODE |
| LOW | public/global.d.ts | 232 | function convertVideoToAnimatedWebp(args: ConvertVideoArgs): Promise<Uint8Array>; | CODE |
| LOW | public/scripts/swipe-picker.js | 17 | export function canOpenSwipePickerForMessage(messageId) { | CODE |
| LOW | public/scripts/welcome-screen.js | 204 | export function getPermanentAssistantAvatar() { | CODE |
| LOW | public/scripts/welcome-screen.js | 255 | async function unshallowPermanentAssistant() { | CODE |
| LOW | public/scripts/welcome-screen.js | 534 | async function renameRecentCharacterChat(avatarId, fileName) { | CODE |
| LOW | public/scripts/welcome-screen.js | 599 | async function deleteRecentCharacterChat(avatarId, fileName) { | CODE |
| LOW | public/scripts/welcome-screen.js | 690 | async function openRecentChatsSettingsPopup() { | CODE |
| LOW | public/scripts/welcome-screen.js | 819 | export async function openPermanentAssistantChat({ tryCreate = true, created = false } = {}) { | CODE |
| LOW | public/scripts/welcome-screen.js | 883 | export async function openPermanentAssistantCard() { | CODE |
| LOW | public/scripts/welcome-screen.js | 898 | export function assignCharacterAsAssistant(characterId) { | CODE |
| LOW | public/scripts/variables.js | 895 | function closureDeserializeCallback(args, value) { | CODE |
| LOW | public/scripts/action-loader-slashcommands.js | 15 | export function registerActionLoaderSlashCommands() { | CODE |
| LOW | public/scripts/chat-templates.js | 120 | export async function deriveTemplatesFromChatTemplate(chat_template, hash) { | CODE |
| LOW | public/scripts/kai-settings.js | 134 | function loadKoboldSettingsFromPreset(preset) { | CODE |
| LOW | public/scripts/kai-settings.js | 229 | export async function generateKoboldWithStreaming(generate_data, signal) { | CODE |
| LOW | public/scripts/itemized-prompts.js | 65 | export async function replaceItemizedPromptText(mesId, promptText) { | CODE |
| LOW | public/scripts/itemized-prompts.js | 389 | export function deleteItemizedPromptForMessage(messageId) { | CODE |
| LOW | public/scripts/logprobs.js | 74 | function renderAlternativeTokensView() { | CODE |
| LOW | public/scripts/logprobs.js | 509 | export function saveLogprobsForActiveMessage(logprobs, continueFrom) { | CODE |
| LOW | public/scripts/logprobs.js | 559 | function getActiveMessageLogprobData() { | CODE |
| LOW | public/scripts/logprobs.js | 575 | function convertTokenIdLogprobsToText(input) { | CODE |
| LOW | public/scripts/personas.js | 207 | function verifyPersonaSearchSortRule() { | CODE |
| LOW | public/scripts/personas.js | 551 | export async function convertCharacterToPersona(characterId = null) { | CODE |
| LOW | public/scripts/personas.js | 689 | export function updatePersonaConnectionsAvatarList() { | CODE |
| LOW | public/scripts/personas.js | 965 | export function isPersonaConnectionLocked(connection) { | CODE |
| LOW | public/scripts/personas.js | 1206 | async function onPersonaDescriptionInput() { | CODE |
| LOW | public/scripts/personas.js | 1238 | async function onPersonaDescriptionDepthValueInput() { | CODE |
| LOW | public/scripts/personas.js | 1252 | async function onPersonaDescriptionDepthRoleInput() { | CODE |
| LOW | public/scripts/personas.js | 1316 | async function onPersonaDescriptionPositionInput() { | CODE |
| LOW | public/scripts/personas.js | 1334 | export function getOrCreatePersonaDescriptor() { | CODE |
| LOW | public/scripts/personas.js | 1334 | export function getOrCreatePersonaDescriptor() { | CODE |
| LOW | public/scripts/personas.js | 1518 | function getPersonaTemporaryLockInfo() { | CODE |
| LOW | public/scripts/personas.js | 1543 | async function loadPersonaForCurrentChat({ doRender = false } = {}) { | CODE |
| LOW | public/scripts/personas.js | 1875 | export async function retriggerFirstMessageOnEmptyChat() { | CODE |
| LOW | public/scripts/personas.js | 2463 | function userMessageNamesEnumProvider() { | CODE |
| LOW | public/scripts/personas.js | 2472 | function registerPersonaSlashCommands() { | CODE |
| 321 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | .github/workflows/pr-check-merge-conflicts.yaml | 23 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/on-close-handler.yml | 23 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/issues-updates-on-merge.yml | 23 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/on-open-handler.yml | 23 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/job-close-stale.yml | 23 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/job-close-stale.yml | 68 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/job-close-stale.yml | 106 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/issues-auto-manager.yml | 23 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/issues-auto-manager.yml | 57 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/issues-auto-manager.yml | 115 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/issues-auto-manager.yml | 142 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/pr-auto-manager.yml | 32 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/pr-auto-manager.yml | 69 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/pr-auto-manager.yml | 100 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/pr-auto-manager.yml | 134 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/pr-auto-manager.yml | 171 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/pr-auto-manager.yml | 250 | # Create a GitHub App token | COMMENT |
| MEDIUM | .github/workflows/pr-auto-manager.yml | 290 | # Create a GitHub App token | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| CRITICAL | public/lib/jquery-ui.min.js | 6 | !function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(V){"use strict" | CODE |
| CRITICAL | public/lib/jquery-3.5.1.min.js | 2 | !function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):f | CODE |
| CRITICAL⚡ | public/lib/epub.min.js | 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("JSZip")):"function"==typeof d | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | public/locales/it-it.json | 1400 | "Don't forget to save a snapshot of your settings before proceeding.": "Non dimenticare di salvare un'istantanea del | CODE |
| LOW | public/locales/th-th.json | 1413 | "Don't forget to save a snapshot of your settings before proceeding.": "อย่าลืมบันทึกสแนปช็อตการตั้งค่าของคุณก่อนดำเนิ | CODE |
| LOW | public/locales/ko-kr.json | 1417 | "Don't forget to save a snapshot of your settings before proceeding.": "계속하기 전에 설정의 스냅샷을 저장하는 것을 잊지 마세요.", | CODE |
| LOW | public/locales/es-es.json | 1400 | "Don't forget to save a snapshot of your settings before proceeding.": "No olvide guardar una instantánea de su conf | CODE |
| LOW | public/locales/ja-jp.json | 1402 | "Don't forget to save a snapshot of your settings before proceeding.": "続行する前に、設定のスナップショットを保存することを忘れないでください。", | CODE |
| LOW | public/locales/zh-cn.json | 3416 | "Don't forget to save a snapshot of your settings before proceeding.": "在继续之前,不要忘记保存您的设置快照。", | CODE |
| LOW | public/locales/ar-sa.json | 1400 | "Don't forget to save a snapshot of your settings before proceeding.": "لا تنس حفظ لقطة من إعداداتك قبل المتابعة.", | CODE |
| LOW | public/locales/pt-pt.json | 1398 | "Don't forget to save a snapshot of your settings before proceeding.": "Não se esqueça de salvar um instantâneo de s | CODE |
| LOW | public/locales/uk-ua.json | 1398 | "Don't forget to save a snapshot of your settings before proceeding.": "Перш ніж продовжити, не забудьте зберегти зн | CODE |
| LOW | public/locales/de-de.json | 1400 | "Don't forget to save a snapshot of your settings before proceeding.": "Vergessen Sie nicht, einen Schnappschuss Ihr | CODE |
| LOW | public/locales/fr-fr.json | 1322 | "Don't forget to save a snapshot of your settings before proceeding.": "N'oubliez pas d'enregistrer un instantané de | CODE |
| LOW | public/locales/zh-tw.json | 1400 | "Don't forget to save a snapshot of your settings before proceeding.": "在繼續之前,請不要忘記進行備份。", | CODE |
| LOW | public/locales/is-is.json | 1398 | "Don't forget to save a snapshot of your settings before proceeding.": "Ekki gleyma að vista mynd af stillingunum þí | CODE |
| LOW | public/locales/ru-ru.json | 1569 | "Don't forget to save a snapshot of your settings before proceeding.": "Настоятельно советуем перед этим сохранить с | CODE |
| LOW | public/locales/nl-nl.json | 1396 | "Don't forget to save a snapshot of your settings before proceeding.": "Vergeet niet een momentopname van uw instell | CODE |
| LOW | public/locales/vi-vn.json | 1398 | "Don't forget to save a snapshot of your settings before proceeding.": "Đừng quên sao lưu snapshot của cài đặt của b | CODE |
| LOW | public/scripts/templates/resetSettings.html | 5 | <div data-i18n="Don't forget to save a snapshot of your settings before proceeding."> | CODE |
| LOW | public/scripts/templates/resetSettings.html | 6 | Don't forget to save a snapshot of your settings before proceeding. | CODE |
| LOW | src/endpoints/vectors.js | 21 | // Don't forget to add new sources to the SOURCES array | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | public/script.js | 3406 | // For group chats, we need to check if group cards should be used | COMMENT |
| LOW⚡ | public/scripts/world-info.js | 2259 | // Step 1: Clean all <option> elements within <select> | COMMENT |
| LOW⚡ | public/scripts/world-info.js | 2267 | // Step 2: Clean all <select> elements | COMMENT |
| LOW | public/scripts/world-info.js | 2289 | // Step 3: Clean <div>, <span>, <input> | COMMENT |
| LOW⚡ | public/scripts/extensions/gallery/index.js | 630 | // Step 1: Clone the template content | COMMENT |
| LOW⚡ | public/scripts/extensions/gallery/index.js | 640 | // Step 2: Append the given image | COMMENT |
| LOW⚡ | public/scripts/extensions/gallery/index.js | 687 | // Step 3: Attach it to the movingDivs container | COMMENT |
| LOW⚡ | public/scripts/extensions/gallery/index.js | 690 | // Step 4: Call dragElement and loadMovingUIState | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | default/config.yaml | 21 | # Open the browser automatically on server startup. | COMMENT |
| LOW | default/config.yaml | 41 | # Interval in seconds to write a heartbeat file. Set to 0 to disable. | COMMENT |
| LOW | default/config.yaml | 101 | url: "socks5://username:password@example.com:1080" | COMMENT |
| LOW | default/config.yaml | 121 | # This will use auto login to an account with the same username | COMMENT |
| LOW | default/config.yaml | 141 | # Host whitelist configuration. Recommended if you're using a listen mode | COMMENT |
| LOW | default/config.yaml | 181 | # Disable startup security checks - NOT RECOMMENDED | COMMENT |
| LOW | default/config.yaml | 221 | chat: | COMMENT |
| LOW | default/config.yaml | 281 | - cdn.discordapp.com | COMMENT |
| LOW | default/config.yaml | 321 | enableDownloadableTokenizers: true | COMMENT |
| LOW | default/config.yaml | 341 | # -- OLLAMA API CONFIGURATION -- | COMMENT |
| LOW | default/config.yaml | 361 | # Enables caching of the message history at depth (if supported). | COMMENT |
| LOW | default/config.yaml | 381 | # Adds thought signatures to requests (if available). Only for Gemini 3 and above. | COMMENT |
| LOW | public/script.js | 1161 | ]; | COMMENT |
| LOW | public/scripts/dynamic-styles.js | 121 | COMMENT | |
| LOW | public/scripts/slash-commands/SlashCommandParser.js | 581 | * @returns Whether the next characters are the indicated symbol. | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW⚡ | public/scripts/world-info.js | 2259 | // Step 1: Clean all <option> elements within <select> | COMMENT |
| LOW⚡ | public/scripts/world-info.js | 2267 | // Step 2: Clean all <select> elements | COMMENT |
| LOW | public/scripts/world-info.js | 2289 | // Step 3: Clean <div>, <span>, <input> | COMMENT |
| LOW⚡ | public/scripts/extensions/gallery/index.js | 630 | // Step 1: Clone the template content | COMMENT |
| LOW⚡ | public/scripts/extensions/gallery/index.js | 640 | // Step 2: Append the given image | COMMENT |
| LOW⚡ | public/scripts/extensions/gallery/index.js | 687 | // Step 3: Attach it to the movingDivs container | COMMENT |
| LOW⚡ | public/scripts/extensions/gallery/index.js | 690 | // Step 4: Call dragElement and loadMovingUIState | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | public/scripts/variables.js | 2070 | <pre><code class="language-stscript">/len Lorem ipsum | /echo</code></pre> | CODE |
| LOW | public/scripts/RossAscends-mods.js | 327 | send_textarea.attr('placeholder', send_textarea.attr('no_connection_text')); //Input bar placeholder tells users | CODE |
| LOW | public/scripts/RossAscends-mods.js | 338 | send_textarea.attr('placeholder', send_textarea.attr('connected_text')); //on connect, placeholder tells use | CODE |
| LOW | public/scripts/secrets.js | 264 | $(input_selector).attr('placeholder', placeholderWithLabel); | CODE |
| LOW | public/scripts/openai.js | 5744 | $('#openai_reverse_proxy').attr('placeholder', 'https://api.anthropic.com/v1'); | CODE |
| LOW | public/scripts/openai.js | 5755 | $('#openai_reverse_proxy').attr('placeholder', 'https://api.openai.com/v1'); | CODE |
| LOW | public/scripts/world-info.js | 2970 | placeholder: input.attr('placeholder'), | CODE |
| LOW | public/scripts/world-info.js | 3353 | commentInput.attr('placeholder', (keys || t`Entry Title/Memo`)); | CODE |
| LOW | public/scripts/slash-commands.js | 2537 | name: 'placeholder', | CODE |
| LOW | public/scripts/extensions/tts/index.js | 94 | const fallbackPreview = 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet'; | CODE |
| LOW | public/scripts/extensions/tts/index.js | 94 | const fallbackPreview = 'Neque porro quisquam est qui dolorem ipsum quia dolor sit amet'; | CODE |
| LOW | public/lib/select2.min.js | 2 | !function(n){"function"==typeof define&&define.amd?define(["jquery"],n):"object"==typeof module&&module.exports?module.e | CODE |
| LOW | public/lib/jquery-ui.min.js | 6 | !function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery"],t):t(jQuery)}(function(V){"use strict" | CODE |
| LOW | public/lib/select2-search-placeholder.js | 19 | this.$search.attr('placeholder', this.options.get('searchInputPlaceholder')); | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | public/scripts/world-info.js | 2802 | // we gonna utilize the custom tokenizer that also handles the input. | COMMENT |
| MEDIUM⚡ | public/lib/epub.min.js | 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("JSZip")):"function"==typeof d | CODE |
| MEDIUM⚡ | public/lib/epub.min.js | 1 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("JSZip")):"function"==typeof d | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | src/endpoints/characters.js | 1330 | // ── Bulk mode: avatars array is present ────────────────── | COMMENT |
| MEDIUM | src/endpoints/characters.js | 1400 | // ── Single mode (default behavior) ─────────────────────── | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | public/script.js | 616 | export let amount_gen = 80; //default max length of AI generated responses | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | docker/docker-entrypoint.sh | 58 | # Check if PUID/PGID variables are provided | COMMENT |