Where your findings come from
Every finding kipmox shows comes from one of these layers. They run together and are merged and de-duplicated into a single set of warnings.Severity levels
kipmox maps every finding — whichever engine produced it — to one of three levels:Salesforce Code Analyzer engines
kipmox runs the Salesforce Code Analyzer as its primary engine. It bundles several scanners, each covering a different area:kipmox.analysis.ruleSelector setting.
code-analyzer plugin isn’t installed, kipmox automatically falls back to its own built-in rules below so analysis still works.kipmox’s built-in rules
On top of the Code Analyzer, kipmox adds its own Salesforce-specific rules. These are the ones with the richest explanations and one-click fixes — the same rules kipmox falls back to when the Code Analyzer isn’t available. New rules are added regularly.Apex
🔴 SOQL query inside a loop
🔴 SOQL query inside a loop
soql-in-loopA SOQL query is being executed inside a for loop. Each iteration counts as a separate query against Salesforce’s governor limit of 100 SOQL queries per transaction.Why it matters: In production, this throws System.LimitException: Too many SOQL queries: 101 when the loop runs more than 100 times.Fix: Move the SOQL query outside the loop. Collect the IDs you need first, then query with a WHERE Id IN :idSet pattern.🔴 DML statement inside a loop
🔴 DML statement inside a loop
dml-in-loopA DML statement (insert, update, delete, upsert, or undelete) is being executed inside a for loop. Each iteration counts against Salesforce’s governor limit of 150 DML statements per transaction.Why it matters: In production, this throws System.LimitException: Too many DML statements: 151 when the loop runs more than 150 times.Fix: Collect records in a list inside the loop, then perform a single bulk DML operation after the loop.🔴 @future call inside a loop
🔴 @future call inside a loop
future-in-loopA @future method is being called inside a for loop. Each call counts against Salesforce’s governor limit of 50 future calls per transaction.Why it matters: In production, this throws System.LimitException: Too many future calls: 51 when the loop runs more than 50 times.Fix: Refactor the @future method to accept a collection of IDs and call it once outside the loop.🔴 Hardcoded Salesforce record ID
🔴 Hardcoded Salesforce record ID
hardcoded-idA Salesforce record ID is hardcoded directly in the code. Record IDs are org-specific and differ across every sandbox, scratch org, and production environment.Why it matters: Code with hardcoded IDs fails silently or throws errors when deployed to a different org — a common source of bugs when promoting from sandbox to production.Fix: Use a Custom Label, Custom Setting, or Custom Metadata Type to store org-specific IDs and reference them instead.🟡 Missing sharing keyword
🟡 Missing sharing keyword
missing-sharing-keywordAn Apex class is declared without an explicit with sharing, without sharing, or inherited sharing keyword.Why it matters: Without an explicit declaration, the class inherits its caller’s sharing context — which may be without sharing — potentially exposing records the running user shouldn’t access.Fix: Add an explicit sharing keyword to every Apex class.🟡 SOQL mode unspecified
🟡 SOQL mode unspecified
soql-mode-unspecifiedA SOQL query does not explicitly specify USER_MODE or SYSTEM_MODE.Why it matters: Without an explicit mode, SOQL runs in system mode by default — ignoring the running user’s field-level security and object permissions.Fix: Add WITH USER_MODE to enforce the running user’s permissions, or WITH SYSTEM_MODE to make the intent explicit.🟡 Unsafe single-record SOQL
🟡 Unsafe single-record SOQL
unsafe-single-soqlA SOQL query expected to return a single record is assigned directly to a variable without null protection or a try-catch block.Why it matters: If the query returns no records, Salesforce throws System.QueryException: List has no rows for assignment to SObject, crashing the transaction.Fix: Wrap the query in a try-catch block, or query into a list and check for results first.🟡 Direct trigger logic
🟡 Direct trigger logic
direct-trigger-logicBusiness logic is written directly in the trigger body instead of delegating to a handler class.Why it matters: Triggers with inline logic are hard to test, maintain, and extend. Best practice is to keep trigger bodies thin and delegate all logic to a dedicated handler class.Fix: Create a trigger handler class and call it from the trigger body.🔵 System.debug statement left in code
🔵 System.debug statement left in code
apex-system-debugA System.debug() statement is present in the code.Why it matters: Debug statements left in production clutter debug logs, add minor overhead, and can expose sensitive data.Fix: Remove System.debug() statements before deploying to production.LWC JavaScript
🔴 Direct innerHTML assignment
🔴 Direct innerHTML assignment
lwc-inner-htmlA component uses direct innerHTML assignment to render content.Why it matters: Direct innerHTML is an XSS vulnerability — user-supplied content can inject malicious scripts. Lightning Locker also blocks this pattern.Fix: Use LWC template directives (lwc:if, for:each) to render dynamic content safely.🔴 @api property mutated directly
🔴 @api property mutated directly
lwc-api-mutatedA component directly mutates an @api property.Why it matters: @api properties are owned by the parent. Mutating them directly breaks LWC’s unidirectional data flow and can cause unpredictable rendering.Fix: Copy the @api value to a tracked internal property and mutate the copy instead.🟡 Direct document access
🟡 Direct document access
lwc-document-accessThe component uses document.querySelector() or similar DOM APIs directly.Why it matters: Direct document access is blocked by Lightning Locker. Each component can only access its own DOM, not the full page.Fix: Use this.template.querySelector() to access elements within the component’s own shadow DOM.🟡 Wire adapter error unchecked
🟡 Wire adapter error unchecked
lwc-wire-error-uncheckedA wire adapter result is used without checking the .error property.Why it matters: Wire adapters return both data and error. If the call fails and .error isn’t handled, the component silently fails or throws when accessing .data.Fix: Always check both .data and .error when using wire adapters.🔵 event.target.value on a Lightning base component
🔵 event.target.value on a Lightning base component
lwc-event-target-valueThe component uses event.target.value to read input values.Why it matters: For Lightning base components (lightning-input, lightning-combobox, etc.), the correct property is event.detail.value. event.target.value returns undefined for these.Fix: Use event.detail.value for Lightning base components.🔵 Imperative Apex call missing .catch()
🔵 Imperative Apex call missing .catch()
lwc-missing-catchAn imperative Apex call uses .then() without a .catch() block.Why it matters: Without a .catch(), any error from the Apex method is silently swallowed — the component stops working with no visible error.Fix: Always add a .catch() block to imperative Apex calls.🔵 Unnecessary @track on a primitive & console.log left in code
🔵 Unnecessary @track on a primitive & console.log left in code
lwc-track-unnecessary, console-log-production@track on a primitive (string, number, boolean) is unnecessary since API v46 — all fields are reactive by default; remove the decorator. console.log() / console.error() statements left in a component clutter dev tools and can expose data — remove them before deploying.LWC HTML
🟡 String onclick handler
🟡 String onclick handler
lwc-string-onclickAn onclick attribute uses string function-call syntax.Why it matters: String-based handlers like onclick="handleClick()" aren’t supported in LWC templates — a common mistake coming from Aura or plain HTML.Fix: Use curly-brace expression syntax to reference the handler.🟡 for:each missing key attribute
🟡 for:each missing key attribute
lwc-for-each-no-keyA for:each directive is missing a key attribute on the repeated element.Why it matters: LWC requires a unique key on for:each elements to track and re-render list items — without it, LWC throws a template rendering error.Fix: Add a key attribute with a unique value — typically the record ID.Shared (Apex + LWC)
🟡 Empty catch block
🟡 Empty catch block
empty-catchA catch block is present but empty — exceptions are being silently swallowed.Why it matters: Empty catch blocks hide errors completely — no log, no user feedback, no way to diagnose.Fix: Always handle caught exceptions — at minimum, log them or surface an error message.Add your own rules
Your team can layer in its own PMD rules on top of everything above — kipmox merges them into the same findings list.Write a PMD ruleset (XML)
.xml file with your custom Apex rules.Reference it from a Code Analyzer config file
.yml / .yaml) that points to your ruleset(s). A workspace-relative path such as config/code-analyzer.yml is best.Point kipmox at the config file
.yml file. kipmox stores it in the kipmox.analysis.configFile setting and automatically infers the right rule selector from your rulesets.code-analyzer plugin installed.