Methodology: what a safe Salesforce app should be
This page documents the safe-Salesforce-app standard as Salesforce publishes it, and the Vulkro coverage map against that standard. Every detector we ship traces back to a row in the coverage matrix at the bottom; every gap is named explicitly with a "covered / partial / planned / out of scope" label so customers can audit our coverage instead of trusting our marketing.
Detector implementations are proprietary (per the closed-source positioning). This document is the methodology, the rule IDs, and the coverage status - all of which are part of the public credibility play.
Why publish this
A Salesforce security tool that does not name its standard cannot be audited against. Every commercial competitor (CodeScan, Clayton, AppOmni, Obsidian, Adaptive Shield) keeps its rule taxonomy proprietary; the customer is left trusting a vendor demo. Vulkro flips that: the standard we measure against, the rule IDs we emit, and the precise coverage gap are all on this page.
If a customer can read this and reproduce our coverage decision for a finding, the product is trustworthy. If they cannot, we are not done.
The Salesforce safe-app standard (sources)
The standard is the union of four publicly-published bodies:
- The Salesforce Well-Architected Framework (architect.salesforce.com). Three pillars (Trusted, Easy, Adaptable), each with three dimensions, each with documented anti-patterns. Authoritative for architecture quality.
- The AppExchange Security Review checklist (Partners portal + the Dev Blog's Top 20 vulnerabilities and the Prepare for Security Review companion). Authoritative for ISV submissions: every managed package shipping through AppExchange must pass it.
- OWASP API Security Top 10 (owasp.org/API-Security).
Cross-industry standard; Vulkro's
OwaspCategoryenum maps to it directly. - The 2025-26 Salesforce breach class map. Not a Salesforce-published standard, but the empirically-grounded list of what actually compromised real orgs: Salesloft Drift, Gainsight, Experience Cloud guest-user (AuraInspector), ShinyHunters / UNC6040 vishing, ForcedLeak (Agentforce CVSS 9.4).
The PMD Apex rule catalog and the Salesforce Code Analyzer rule set are the industry baseline for code-level coverage. They are not part of the "standard" in the safe-app sense, but every Salesforce SAST tool is compared against them, so we include a parity check here.
1. Well-Architected Framework
The framework has three pillars: Trusted, Easy, Adaptable. Each pillar has three dimensions. The dimensions are the unit at which anti-patterns are catalogued by Salesforce Architects.
1.1 Trusted
"Trusted solutions protect stakeholders." Salesforce Architects.
| Dimension | What it requires | Notable anti-patterns |
|---|---|---|
| Secure | Authentication, authorization, encryption, secrets handling, secure communication, secure data storage. Every requirement in the AppExchange Security Review checklist sits here. | Hardcoded credentials in source / metadata; CRUD/FLS bypass; without sharing on user-reachable Apex; cleartext callbacks; broad OAuth scope; over-privileged profiles; guest-user object exposure. |
| Compliant | Compliance with the regulatory frameworks the data falls under (GDPR, HIPAA, PCI DSS, SOC 2, NIST). Mapping audit logs, retention, lawful basis, encryption-at-rest. | PII in debug logs; missing field-level encryption on sensitive fields; missing audit trail; cross-border data flow without DPA. |
| Reliable | Operates within governor limits, handles errors deliberately, recovers from transient failures, bulkifies. | SOQL in loop; DML in loop; @future in loop; non-bulkified triggers; recursive triggers without guards; empty catch blocks; reports with multiple purposes; logic in triggers without handler pattern; mixed-DML errors. |
1.2 Easy
"Easy solutions deliver value fast." Salesforce Architects.
| Dimension | What it requires | Notable anti-patterns |
|---|---|---|
| Intentional | Solutions that map to a clear business outcome; declarative-first; minimal custom code where standard features exist. | Custom code where Flow / standard automation suffices; duplicate fields / objects modelling the same concept; "kitchen-sink" controllers. |
| Automated | Manual processes replaced with automation; declarative tools (Flow) preferred for business logic. | Manual data movement; spreadsheet-based workflows; ungoverned imports; unoptimised SOQL that scans full objects. |
| Engaging | Lightning components and modern UI surface; consistent UX; fast page loads. | Visualforce-only UI on Lightning Experience; absolute-positioned CSS in components; uncontextualised dashboards. |
1.3 Adaptable
"Adaptable solutions evolve with the business." Salesforce Architects.
| Dimension | What it requires | Notable anti-patterns |
|---|---|---|
| Resilient | Production-like testing in lower environments; release pipeline with rollback; environment parity; observability for cross-component failures. | Tests pass in scratch but fail in prod (no parity); deploy without rollback plan; missing platform-event monitors on critical flows. |
| Composable | Integration via well-defined APIs (Named Credentials, External Services); separation of concerns; trigger handler frameworks. | Point-to-point integrations without an integration layer; ungoverned external callouts (hardcoded URLs); multiple triggers per object without orchestration. |
| Future-Proof (implied; sometimes folded into Composable) | Hardcoded record IDs removed; declarative configuration over hard-coded constants; named credentials over hardcoded URLs. | Hardcoded org-specific IDs; hardcoded URLs; tight coupling to a specific page layout. |
The exact list of officially-published anti-patterns evolves; the Salesforce Well-Architected Anti-Patterns catalog is the authoritative source. Vulkro tracks the high-value statically-detectable ones; see section 9.
2. AppExchange Security Review
The Security Review is the gating process for every ISV-distributed managed package on AppExchange. Salesforce's Product Security team runs:
- Static analysis (Checkmarx CxSAST + Salesforce Code Analyzer).
- Dynamic analysis (OWASP ZAP / Burp / Qualys; Chimera was retired June 2025).
- Manual review against eight categories.
2.1 The eight categories
| # | Category | What's required |
|---|---|---|
| 1 | Authentication & session management | Cookies over HTTPS with the SECURE flag; session invalidated on logout; SessionID never sent to external systems; OAuth or Integration Users for any external call. |
| 2 | Authorization (CRUD / FLS / sharing) | Explicit CRUD and FLS check before every DML / query. WITH USER_MODE (Spring '23+) or WITH SECURITY_ENFORCED (older) on SOQL. Every Apex class declares a sharing mode: with sharing, without sharing, or inherited sharing. |
| 3 | Input validation | No SOQL injection (use bind variables or String.escapeSingleQuotes). XSS-safe rendering (no escape="false" on user-controlled content). No CSRF on state-changing endpoints. |
| 4 | Output encoding | All user-rendered content encoded for its sink (HTML / JS / URL). CSP-safe Lightning component rendering. |
| 5 | Cryptography | No MD5 or SHA-1 for authentication; no static IV; no Math.random() for tokens; AES-256 minimum; key handled via Crypto.encryptWithManagedIV. |
| 6 | Communication security | All callouts over HTTPS; TLS 1.2 minimum; valid CA chain; CSP Trusted Sites for remote resources; Named Credentials for outbound auth. |
| 7 | Logging & error handling | No PII in debug logs; no stack traces to users; no application secrets in System.debug; error pages do not leak system data. |
| 8 | Secrets storage | Named Credentials, Protected Custom Settings, or Protected Custom Metadata. Never hardcoded in source. |
2.2 The Top 20 vulnerabilities (verbatim, ranked by failure rate)
Salesforce Dev Relations publishes the empirical ranking from real submission failures. Quoted verbatim from the official blog post:
| Rank | Vulnerability | Failure trigger |
|---|---|---|
| 1 | CRUD/FLS enforcement | Failure to check object and field-level accessibility before queries or DML. |
| 2 | Insecure software version | Outdated libraries or frameworks with known CVEs. |
| 3 | Sharing violation | Apex classes lacking with sharing; Flows in System Mode without sharing. |
| 4 | Insecure storage of sensitive data | Secrets hard-coded into source code. |
| 5 | TLS/SSL configuration | Connections not using TLS 1.2+. |
| 6 | Sensitive information in debug | Debug statements that expose secrets, system data, or PII. |
| 7 | CSRF | No anti-CSRF token on state-changing endpoints. |
| 8 | Stored & reflected XSS | Unvalidated input rendered as HTML. |
| 9 | JavaScript not in static resources | JS loaded externally rather than packaged. |
| 10 | SOQL injection | Unvalidated input concatenated into dynamic SOQL. |
| 11 | Lightning: improper CSS load | <link> / <style> instead of approved component loading. |
| 12 | JavaScript in Salesforce DOM (Classic only) | JS running in the Salesforce app context. |
| 13 | Information disclosure on errors | Stack traces and system data shown to users. |
| 14 | Aura components: CSS outside component | CSS that bypasses component encapsulation. |
| 15 | Message channel exposed | Lightning Message Channel with isExposed=true. |
| 16 | Sensitive info in URL | Credentials or tokens in query strings (server-logged). |
| 17 | Insecure endpoint | HTTP instead of HTTPS. |
| 18 | Username or email enumeration | Distinguishable error messages reveal valid accounts. |
| 19 | Password management | Password reuse, missing old-password verification, cleartext transmission. |
| 20 | Password Echo | Passwords rendered in plaintext in UI or API. |
vulkro-sf appexchange-report maps each Vulkro Salesforce finding to the
checklist section that gates it, and renders a PASS / FAIL / NOT-EVALUATED
HTML report - see
AppExchange Security Review readiness.
3. Apex security checklist (developer-facing rules)
This is the operational shape of the AppExchange Security Review for Apex.
3.1 CRUD / FLS / Sharing
| Rule | Apex pattern that satisfies it | Anti-pattern Vulkro flags |
|---|---|---|
| Declare sharing mode | public with sharing class Foo {} / inherited sharing | Class with no sharing declaration that handles user-supplied input. |
| Enforce CRUD/FLS on read | WITH USER_MODE (Spring '23+) / WITH SECURITY_ENFORCED; Schema.SObjectType.X.isAccessible() | Dynamic SOQL without enforcement; WITHOUT SHARING on user-reachable classes. |
| Enforce CRUD/FLS on write | Database.insert(records, AccessLevel.USER_MODE); insert as user records;; Schema.SObjectType.X.isCreateable() check | DML on a request-reachable class without any enforcement. |
| Strip inaccessible fields | Security.stripInaccessible(AccessType.READABLE, records) | Mass-assignment via JSON.deserialize directly into an SObject. |
3.2 Injection (SOQL / SOSL)
| Rule | Pattern | Anti-pattern Vulkro flags |
|---|---|---|
| Bind variables for user input | [SELECT Id FROM Account WHERE Name = :name] | Database.query('... WHERE Name = \'' + name + '\''). |
| Escape if dynamic required | String.escapeSingleQuotes(input) | Concatenated dynamic SOQL with no escape and no bind. |
Whitelist for WHERE field name choices | Enum / static map for the user-pickable column | User-supplied field name interpolated into dynamic SOQL. |
3.3 XSS / output encoding
| Sink | Safe | Anti-pattern |
|---|---|---|
Visualforce {!field} | Default escaping; do not set escape="false" on user content. | <apex:outputText escape="false" value="{!UserSuppliedHtml}" />. |
Visualforce {!URLFOR(...)} | URL-encoded. | Hand-rolled URL concatenation with user input. |
LWC lwc:html="value" | Sanitize via DOMPurify (or do not use). | Direct innerHTML from @wire data. |
Apex PageReference | Use safe constructors; do not construct from raw input. | new PageReference(userInput). |
3.4 CSRF
- All Visualforce
<apex:form>automatically inject a CSRF token; do not bypass with custom HTML forms. enableCSRFOnGet=true/enableCSRFOnPost=trueinSecuritySettings(Vulkro flagsenableCSRFOnPost=falseat High).- REST Apex endpoints that change state must verify the request origin or
rely on the
Authorizationheader (no cookie-only auth for state changes).
3.5 Cryptography
| Concern | Required | Anti-pattern |
|---|---|---|
| Hash for authentication | SHA-256+ or bcrypt | MD5 / SHA-1. |
| Symmetric encryption | AES-256 with managed IV | AES-128 / static IV / ECB mode. |
| Randomness for tokens | Crypto.getRandomLong() / Crypto.getRandomInteger() | Math.random(). |
| Key handling | Crypto.encryptWithManagedIV | Hardcoded keys in Apex or metadata. |
3.6 Secrets / Named Credentials
- Hardcoded secrets in Apex literals,
customLabel, orstaticResourcecontent fail review. - Named Credentials (recommended): credential lives in Setup; Apex references the named credential by API name; rotation is admin-controlled.
- External Credentials (newer): Named Credential + External Credential split; supports OAuth client-credentials, JWT, password-grant.
- Protected Custom Metadata Types: encryption-at-rest in the org; readable only by package code.
- Never use unprotected Custom Settings for secrets (visible to any admin).
3.7 Logging hygiene
System.debug()output is preserved across all logs; treat it as a public surface.LoggingLevel.DEBUG(or lower) for routine;ERRORfor failures.- Never log: passwords, OAuth tokens, session IDs, customer PII, account numbers, SSNs.
- Do not surface stack traces to end users; convert to a user-safe message and log the trace internally.
3.8 Test coverage (AppExchange gate: 75%)
- AppExchange Security Review requires 75% code coverage across the package.
- All test classes use
@isTestand aTestDataFactorypattern (no SeeAllData). System.runAsis restricted to test paths.ApexUnitTestClassShouldHaveAsserts(PMD): no test methods withoutAssert.*.
4. LWC / Aura / Visualforce surface
4.1 Lightning Web Security (LWS)
Salesforce evolved from Lightning Locker (which used Secure* wrapper
objects) to Lightning Web Security, which uses
distortion:
the JS sandbox transforms unsafe operations rather than wrapping APIs.
Each namespace gets its own detached sandbox.
The security model prohibits:
document.write()(distorted to a no-op).evalwith user-controlled input.- Inline
<script>or<style>tags (CSP-blocked). - Bypassing the namespace boundary via
window.parent/top. @wire-to-innerHTMLtaint without sanitisation.
Vulkro's sf_lws, sf_lwc, sf_aura_cmp detectors flag the LWS-violation
patterns directly.
4.2 LWC component-level rules
lwc:dom="manual"plus directinnerHTMLfrom external data = DOM XSS.@wireadapters returning user-controllable data must be sanitised before templating.LightningMessageServicechannels must haveisExposed=falseunless the channel is genuinely cross-package (the Top-20 vulnerability #15).- CSS must stay inside the component file (
absolute/fixed/floatpositioning that breaks encapsulation fails review).
4.3 Aura legacy considerations
.cmpmarkup:aura:unescapedHtmlis forbidden on user content.Locker.SecureDocumentaccess patterns differ from LWS; legacy Aura components running under Locker must not assume LWS semantics.- Aura external dependencies must be packaged as
StaticResource(Top-20 #9).
4.4 Visualforce escape rules
<apex:outputText>escapes by default; never setescape="false"on user content.<apex:outputField>uses Field-Level Security for rendering.<apex:includeScript>must point at aStaticResource, never an external URL.<apex:repeat>over user data: the rendering child must escape.
5. Metadata-type security coverage
The Salesforce Metadata API exposes 200+ types. The security-relevant subset is roughly 25 types. Each is the source of truth for an org's configuration in code form; an audit can scan them deterministically.
5.1 Identity & authentication
| Metadata type | Security concern | Vulkro detector |
|---|---|---|
Profile | Over-privileged base profile (Modify All / View All / API-enabled guest). | sf_metadata (covered) |
PermissionSet | Over-privileged perm set; permset-group overlapping. | sf_metadata (covered) |
PermissionSetGroup | Privilege aggregation across multiple perm sets. | Covered (large aggregation Medium, no-activation-required Medium). |
SamlSsoConfig | Missing signature validation, weak algorithm, redirect handling. | Covered (weak signature algorithm). |
AuthProvider | Hardcoded consumer secret; permissive scope. | Covered. |
IdentityProvider | Misconfigured org-as-IdP. | Out of scope for v1. |
LoginFlow | Custom login interception; possible MFA bypass. | Covered (active-inventory finding). |
LoginIpRange | Network-level restrictions on profile / org. | Covered (full-Internet range High, broad /8 Medium, missing-on-admin Medium). |
5.2 OAuth & external credentials
| Metadata type | Security concern | Vulkro detector |
|---|---|---|
ConnectedApp | Full OAuth scope; cleartext callback; hardcoded consumer secret / key; Full + RefreshToken (Drift/Gainsight class). | sf_metadata (covered, deep) |
NamedCredential | Hardcoded password; AnonymousUser principal with allowMergeFieldsInBody=true; HTTP endpoint. | sf_metadata (covered, deep: 4 checks - hardcoded password, cleartext endpoint, merge-body-true, anonymous principal). |
ExternalCredential | Hardcoded credentials in metadata; missing rotation. | Covered (hardcoded secret-like parameter values). |
CredentialUserMapping / PerUserCredentialMapping | Per-user credentials leaked at runtime. | Planned. |
5.3 Network & outbound
| Metadata type | Security concern | Vulkro detector |
|---|---|---|
RemoteSiteSetting | http:// (cleartext) outbound URL; disableProtocolSecurity HTTPS-to-HTTP downgrade. | Covered. |
CorsWhitelistOrigin | Wildcard origin or http:// cross-origin trust. | Covered. |
CspTrustedSite | Wildcard endpoint URL or http:// endpoint. | Covered. |
Certificate | Weak signature algorithm; weak key size; exportable private key; self-signed. | Covered (weak key size Medium, exportable private key High, self-signed Low). |
5.4 Sharing & org-wide defaults
| Metadata type | Security concern | Vulkro detector |
|---|---|---|
SharingRules / SharingCriteriaRule / SharingOwnerRule | Wildcard Public Read/Write on a sensitive object; broad criteria sharing. | Covered (Full Access High; Edit-to-allInternalUsers High; Edit-to-specific-role Medium). |
SharingSet (Experience Cloud) | Guest-user sharing-set abuse on sensitive standard objects. | Covered (Edit/Full to sensitive object High, Read to sensitive object Medium inventory; ten-object sensitive list). |
Object-level OrganizationWideDefault (under CustomObject) | OWD = Public Read/Write on PII-bearing custom objects. | Planned. |
Group | Public groups granting unintended access. | Out of scope for v1. |
5.5 Session & org-config
| Metadata type | Security concern | Vulkro detector |
|---|---|---|
SecuritySettings | Weak password policy; long session timeout; clickjack off; CSRF off; HTTPS not required; no-IP-lock; etc. | sf_metadata (covered, deep: 10 checks) |
MobileSettings | Mobile-specific session settings, jailbreak detection. | Out of scope for v1. |
PasswordPolicies (folded into SecuritySettings) | Length, complexity, expiration, lockout. | sf_metadata (covered). |
5.6 Public surfaces (Experience Cloud)
| Metadata type | Security concern | Vulkro detector |
|---|---|---|
Profile (Guest User License) | Guest can read sensitive object; guest can View All Records (AuraInspector class). | sf_metadata (covered, deep) |
Site (Force.com Sites) | Public site exposes unsecured Visualforce; public Apex methods reachable unauthenticated. | Covered (active inventory Medium, standard portal pages enabled High, browser cross-origin allowlist Medium). |
Network (Experience Cloud) | Self-registration; guest file access; guest Chatter; guest not gated to login. | Covered (4 checks: self-reg + profile, guest file access High, guest Chatter Medium, guest-not-gated Medium). |
ExperienceBundle | Component-level guest exposure. | Covered (inventory cue Medium; points admin at the matching Network + Profile guest checks). |
5.7 Workflow, async, and DML automation
| Metadata type | Security concern | Vulkro detector |
|---|---|---|
Flow | System-context DML; hardcoded record IDs; guest-context flows; missing entry conditions. | sf_flow (covered for the documented vectors). |
ApexTrigger | Logic in trigger body without handler; multiple triggers per object; missing bulk handling. | Planned. |
WorkflowRule / WorkflowOutboundMessage | Outbound message reveals SessionID; hardcoded URL; missing endpoint validation. | Covered (cleartext http:// endpoint High; includeSessionId=true Critical, multi-block iteration). |
ApprovalProcess | Auto-approve path that elevates privilege. | Out of scope for v1. |
AssignmentRule / AutoResponseRule | Email auto-response leaking record content. | Out of scope for v1. |
5.8 Content & external integration
| Metadata type | Security concern | Vulkro detector |
|---|---|---|
ExternalDataSource | Anonymous auth on a sensitive remote source; cleartext endpoint. | Covered (<protocol>NoAuthentication</> High, <endpoint>http:// High). |
ExternalServiceRegistration | External Service with no auth header / wildcard schema. | Covered (missing Named Credential reference). |
StaticResource | Vulnerable JS library packaged (CVE in static resource); hard-coded API key in JSON. | Covered (deep): RetireJS via sf_lwc plus six credential patterns (AWS key + secret, Slack, GitHub, generic API key, Bearer token) via sf_content. |
EmailTemplate | PII rendered in template; merge-field XSS through template. | Out of scope for v1. |
Document | Sensitive document attached publicly. | Out of scope for v1. |
CustomMetadataType | Plain-text secret stored in Custom Metadata (not protected). | Covered (sensitive field name + literal value + <protected>false</> -> Critical, with placeholder filter). |
WebLink | URL formula with XSS via {!URL} injection; javascript: scheme. | Covered (<linkType>javascript</> Critical, <url>javascript: Critical; standalone .weblink-meta.xml only, nested object weblinks planned). |
VisualforcePage / VisualforceComponent | Direct file scan (escape="false", includeScript). | sf_visualforce (covered). |
6. Agentforce / GenAI security (ForcedLeak class)
ForcedLeak (CVSS 9.4) is the Agentforce-specific class disclosed in 2025. An agent driven by indirect prompt injection (e.g. a Web-to-Lead Description field) can exfiltrate CRM data when reachable from untrusted input and backed by an over-permissioned user or actions.
The defensive controls Salesforce documents:
| Control | What it requires | Vulkro detector |
|---|---|---|
| Least-privilege agent user | Each agent runs as a dedicated user with only the perm sets the agent needs. | sf_agentforce (Medium surface finding nudges admin to audit). |
CspTrustedSite for Agentforce / Einstein | Outbound URLs the agent action can reach must be on the Trusted Sites allowlist. | Planned (CspTrustedSite scanner). |
| Action CRUD / FLS enforcement | Every Apex action enforces CRUD / FLS or runs in USER_MODE. | apex_crud_fls (covered). |
| Action sharing mode | Apex action class is with sharing or inherited sharing, never without sharing. | sf_agentforce (covered, High: cross-references the Apex class file). |
| Prompt-template grounding sanitization | Free-text fields (Lead.Description, Case.Description) must not be ground into prompts without sanitization. | Planned (needs verified GenAiPromptTemplate grounding schema). |
| Trusted URL enforcement | Agentforce setting requires every outbound URL to be on the Trusted Sites list. | Inventory only (sf_agentforce Medium surface finding). |
7. 2025-26 Salesforce breach class map
Not a Salesforce-published standard, but the empirically grounded list of what compromised real customer orgs in 2025-26. Every detector that maps to a real breach class carries the breach-class label in the finding message, so the finding cites the threat model.
| Breach class | What happened | Vector | Vulkro detector |
|---|---|---|---|
| Salesloft Drift OAuth token theft (2025, 700+ orgs) | OAuth refresh tokens for the Drift Connected App stolen at the vendor, replayed against customer orgs. | Connected App + Full scope + persistent refresh token + vendor-side token store breached. | sf_metadata::scan_connected_app (covered): flags Full + RefreshToken / OfflineAccess at High. |
| Gainsight OAuth abuse (2025, 200+ orgs) | Same architecture as Drift; refresh tokens at the vendor reused. | Same as Drift. | Same detector. |
| Experience Cloud guest-user exposure / AuraInspector campaign (2023-25, Loblaw 75M, ADT 10M+) | Mass-scanned Experience Cloud sites for guest profiles that granted object read on sensitive standard or custom objects, including View All. | Guest User License profile with <objectPermissions> granting read or View All Records. | sf_metadata guest-user (covered, High / Critical). |
| ShinyHunters / UNC6040 vishing (2025, Google 2.55M, Qantas 5.7M, Allianz 1.4M, ~1B records claimed) | Social-engineered help-desk into installing a malicious Connected App. | Malicious Connected App with broad scope; over-permissioned user accepts. | sf_metadata Connected App: Covered for the technical preconditions (broad scope, full + refresh token, hardcoded secret). The social-engineering vector itself is fundamentally a human-factor attack and falls under DAST / UEBA territory, both out of scope by architectural choice (section 11). |
| ForcedLeak (Agentforce, CVSS 9.4) (2025) | Indirect prompt injection via Web-to-Lead Description triggered Agentforce action that ran without sharing Apex. | GenAiFunction Apex action invoking a without sharing class + grounding on untrusted text. | sf_agentforce (covered, High): two-pass detection of Apex actions whose class is without sharing. Prompt-grounding portion is planned. |
8. PMD Apex rule parity
PMD Apex (which Salesforce Code Analyzer wraps) is the de-facto industry baseline for Apex code-quality coverage. Vulkro does not aim to be a PMD replacement (the wrapper integration exists for users who want PMD output in the Vulkro format), but the security-category rules are the comparison point.
8.1 PMD Apex security rules (the security category)
| PMD rule | What it catches | Vulkro coverage |
|---|---|---|
ApexBadCrypto | MD5, SHA-1, weak ciphers. | Covered (engine crypto checks). |
ApexCRUDViolation | DML / SOQL without CRUD/FLS enforcement. | Covered (apex_crud_fls). |
ApexDangerousMethods | EncodingUtil.urlEncode misuse, Crypto.encrypt with hardcoded key. | Covered. |
ApexInsecureEndpoint | http:// endpoint in callout. | Covered. |
ApexOpenRedirect | Redirect to user-controlled URL. | Covered. |
ApexSharingViolations | without sharing on user-reachable class. | Covered (apex_crud_fls). |
ApexSOQLInjection | Dynamic SOQL with input concatenation. | Covered. |
ApexSuggestUsingNamedCred | Hardcoded HTTP basic auth instead of Named Credential. | Covered (via AP-012: any HttpRequest.setEndpoint('http(s)://...') that is not a callout: Named Credential reference fires Adaptable). |
ApexXSSFromEscapeFalse | Visualforce escape="false" on user content. | Covered (sf_visualforce). |
ApexXSSFromURLParam | URL parameter rendered without encoding. | Covered. |
8.2 PMD Apex error-prone (the security-adjacent subset)
| PMD rule | Pattern | Vulkro coverage |
|---|---|---|
ApexCSRF | State change in constructor (CSRF-exploitable). | Covered (via sf_apex_csrf, Wave 9). |
AvoidDirectAccessTriggerMap | Trigger.new[i] / Trigger.old[i] indexed access. | Covered (via sf_apex_quality, Wave 10). |
AvoidHardcodingId | Hardcoded record ID. | Covered (AP-003). |
EmptyCatchBlock | Silenced exceptions. | Covered (AP-004). |
InaccessibleAuraEnabledGetter | @AuraEnabled annotation on a property whose getter lacks @AuraEnabled. | Out of scope for v1 (requires Apex AST property / getter analysis to distinguish field-level annotation from getter-level annotation; the existing engine grammar treats them uniformly). |
TestMethodsMustBeInTestClasses | Tests outside @isTest classes. | Out of scope for v1. |
8.3 PMD Apex performance (governor-limit anti-patterns)
| PMD rule | Pattern | Vulkro coverage |
|---|---|---|
OperationWithLimitsInLoop | SOQL / DML / async in a loop. | Covered (AP-001, AP-002). |
AvoidNonRestrictiveQueries | SELECT Id FROM Account (no LIMIT, no WHERE). | Covered (AP-013). |
EagerlyLoadedDescribeSObjectResult | Schema.SObjectType.X.fields.getMap() inside loop. | Covered (AP-008 via the more general Schema describe in loop detector). |
8.4 PMD Apex best practices
| PMD rule | Pattern | Vulkro coverage |
|---|---|---|
ApexAssertionsShouldIncludeMessage | Bare Assert.areEqual(a, b). | Out of scope. |
ApexUnitTestClassShouldHaveAsserts | Test methods with no Assert.*. | Planned (testability gate for AppExchange). |
ApexUnitTestShouldNotUseSeeAllDataTrue | @isTest(SeeAllData=true). | Planned. |
AvoidFutureAnnotation | @future instead of Queueable. | Planned (AP-005 candidate). |
AvoidGlobalModifier | global keyword on internal classes. | Out of scope. |
AvoidLogicInTrigger | Logic in trigger body. | Planned (AP-005 candidate). |
DebugsShouldUseLoggingLevel | System.debug('...') without LoggingLevel. | Planned. |
QueueableWithoutFinalizer | Queueable that does not implement Finalizer. | Planned. |
The full PMD Apex rules catalog is at
pmd.github.io/pmd/pmd_rules_apex.html.
Vulkro is licensed for Wave 1 PMD parity (the security + governor-limit
subset) and will iterate toward broader code-quality breadth in subsequent
waves.
9. Anti-pattern detector roadmap
Anti-patterns are tracked separately from security findings in Vulkro (CLI:
vulkro-sf antipatterns). Each is mapped to a Well-Architected pillar /
dimension.
| ID | Pillar / Dimension | Pattern | Status |
|---|---|---|---|
| AP-001 | Trusted / Reliable | SOQL inside a loop. | Covered. |
| AP-002 | Trusted / Reliable | DML inside a loop. | Covered. |
| AP-003 | Adaptable / (Future-Proof) | Hardcoded Salesforce record ID. | Covered. |
| AP-004 | Trusted / Reliable | Empty catch block. | Covered. |
| AP-005 | Adaptable / Composable | Logic in trigger body without handler. | Covered. |
| AP-006 | Trusted / Reliable | @future annotation inside trigger or loop. | Covered (trigger case; the loop case is queued). |
| AP-007 | Trusted / Reliable | Apex class with no apparent test class. | Covered (cross-file: sibling-test naming + substring reference fallback). |
| AP-008 | Easy / Automated | Schema.SObjectType describe call in a loop. | Covered. |
| AP-009 | Adaptable / Composable | Multiple triggers on the same SObject. | Covered (cross-file: group by on SObject header; cross-references siblings). |
| AP-010 | Trusted / Reliable | Recursive trigger without static guard. | Covered (heuristic: DML on Trigger.new / Trigger.old). |
| AP-011 | Trusted / Reliable | Mixed-DML transaction (setup + non-setup). | Covered (heuristic: file-level setup + non-setup new constructors, excludes @isTest and System.runAs). |
| AP-012 | Adaptable / Composable | Hardcoded URL in callout (use Named Credential). | Covered. |
| AP-013 | Trusted / Reliable | SELECT ... FROM ... without WHERE or LIMIT. | Covered. |
| AP-014 | Trusted / Reliable | @isTest(SeeAllData=true). | Covered. |
| AP-015 | Easy / Engaging | Reports / dashboards on multiple concepts (single-purpose violation). | Out of scope for v1 (requires report metadata parsing). |
10. Vulkro coverage matrix (the gap map)
The master matrix. Every safe-app requirement enumerated above maps to exactly one row here, with the current state.
Legend
- Covered : detector ships today and the corresponding finding exists.
- Partial : detector covers part of the requirement; the remaining surface is named in the "Gap" column.
- Planned : the requirement is in scope and named in the roadmap (Wave 1 or later).
- Out of scope : intentionally not in v1 (continuous monitoring, UEBA, DAST). May be re-evaluated for the self-hosted server tier.
10.1 AppExchange Top-20 vulnerability coverage
| Top-20 # | Vulnerability | State | Detector |
|---|---|---|---|
| 1 | CRUD / FLS enforcement | Covered | apex_crud_fls |
| 2 | Insecure software version | Covered (JS only) | sf_lwc RetireJS wrapper. Apex dep version is out of scope (no Apex package manager). |
| 3 | Sharing violation | Covered | apex_crud_fls + sf_agentforce (action-class sharing). |
| 4 | Insecure storage of sensitive data | Covered | sf_metadata (ConnectedApp secret); engine secret scanners. |
| 5 | TLS / SSL configuration | Covered (engine) | TLS / cipher-suite checks. |
| 6 | Sensitive information in debug | Covered | Engine secret-in-log heuristics plus sf_apex_logging: System.debug(...) of variables matching a sensitive-name heuristic (password, secret, token, api_key, session_id, etc.) fires Medium. |
| 7 | CSRF | Covered | SecuritySettings.enableCSRFOnPost=false (High) plus sf_apex_csrf: DML in a Visualforce-controller constructor (the PMD ApexCSRF pattern) fires Medium. |
| 8 | Stored & reflected XSS | Covered | sf_visualforce + sf_lwc. |
| 9 | JavaScript not in static resources | Covered | sf_apex_url_safety: Visualforce <apex:includeScript value="http(s)://..."> (not a {!$Resource.X} reference) fires High. |
| 10 | SOQL injection | Covered | Apex SOQL injection detector. |
| 11 | Lightning improper CSS load | Out of scope | Cosmetic, not security-impactful. |
| 12 | JavaScript in Salesforce DOM (Classic) | Out of scope | Classic is sunsetting. |
| 13 | Information disclosure on errors | Covered | sf_apex_url_safety: any call to Exception.getStackTraceString() fires Medium (the API itself is the smell - stack-trace strings should never reach a user-facing surface). |
| 14 | Aura CSS outside component | Out of scope | Cosmetic. |
| 15 | Message channel exposed (isExposed=true) | Covered | sf_message_channels: <isExposed>true</> on a LightningMessageChannel fires Medium. |
| 16 | Sensitive info in URL | Covered | sf_apex_url_safety: new PageReference(...) / HttpRequest.setEndpoint(...) whose argument concatenates a sensitive-named identifier (token, password, api_key, session_id, etc.) fires High. |
| 17 | Insecure endpoint | Covered | sf_metadata RemoteSiteSetting + engine HTTP detector. |
| 18 | Username / email enumeration | Out of scope | Requires DAST. |
| 19 | Password management | Covered | SecuritySettings covers every statically-detectable password concern (length, complexity, expiration, lockout). The runtime aspects (per-user password reuse, password echo in UI) belong to DAST, which is out of scope by architectural choice (see section 11). |
| 20 | Password echo | Out of scope | Requires DAST / UI inspection. |
10.2 Metadata-type coverage
| Metadata type | State | Detector / Plan |
|---|---|---|
Profile | Covered (deep) | over-privilege, guest-user exposure. |
PermissionSet | Covered (deep) | over-privilege, dormant admins (live-org). |
PermissionSetGroup | Covered | large aggregation (>=5 permsets) Medium, no-activation-required Medium. |
ConnectedApp | Covered (deep) | 5 checks incl. Drift/Gainsight token sprawl. |
NamedCredential | Covered (deep) | hardcoded password, cleartext endpoint, merge-body-true, anonymous principal (4 checks). |
ExternalCredential | Covered | hardcoded secret-like parameter values (Critical, CWE-798). |
SamlSsoConfig | Covered | weak signature algorithm (SHA-1 / RSA-SHA1) Medium. |
AuthProvider | Covered | hardcoded consumer secret (Critical), consumer key (Medium). |
LoginFlow | Covered | active LoginFlow inventory (Medium, admin audit cue). |
LoginIpRange | Covered | full-Internet range High, broad /8 Medium, missing-on-admin Medium. |
RemoteSiteSetting | Covered | cleartext http:// URL, disableProtocolSecurity=true. |
CorsWhitelistOrigin | Covered | wildcard origin, http:// cross-origin trust. |
CspTrustedSite | Covered | wildcard endpoint, http:// endpoint. |
Certificate | Covered | weak key size (Medium), exportable private key (High), self-signed (Low). |
SharingRules (all four) | Covered | Full Access (High), Edit to allInternalUsers (High), Edit narrower (Medium). |
SharingSet | Covered | Edit/Full to sensitive object High, Read to sensitive object Medium inventory. |
| Object OWD | Planned | Public R/W on PII custom object. |
SecuritySettings | Covered (deep) | 10 checks. |
Site (Force.com Sites) | Covered | active site Medium, standard portal pages High, browser X-origin Medium. |
Network (Experience Cloud) | Covered | self-registration + profile, guest file access (High), guest Chatter (Medium), guest-not-gated (Medium). |
ExperienceBundle | Covered | inventory cue Medium (points admin at the matching Network + Profile guest checks). |
Flow | Covered | system DML, hardcoded IDs. |
ApexTrigger | Covered | AP-005 logic-in-body + AP-009 multiple-triggers-per-SObject (cross-file). |
WorkflowRule / WorkflowOutboundMessage | Covered | cleartext http:// endpoint (High), <includeSessionId>true</> (Critical) per-block. |
ExternalDataSource | Covered | <protocol>NoAuthentication</> High, <endpoint>http:// High. |
ExternalServiceRegistration | Covered | missing Named Credential reference (Medium). |
StaticResource | Covered (deep) | RetireJS (via sf_lwc) plus six credential patterns: AWS key + secret, Slack, GitHub, generic API key, Bearer token. |
CustomMetadataType | Covered | sensitive field + literal value + <protected>false</> -> Critical (CWE-798, placeholder filter). |
WebLink | Covered | <linkType>javascript</> (Critical), <url>javascript: (Critical). |
VisualforcePage / VisualforceComponent | Covered | sf_visualforce. |
Bot / BotVersion / GenAiPlanner* / GenAiFunction / GenAiPromptTemplate | Covered (deep) | surface + ForcedLeak class-bypass. |
EmailTemplate, Document, ApprovalProcess, AssignmentRule, AutoResponseRule, Group, MobileSettings | Out of scope (v1) | low security-density per item. |
10.3 Code coverage parity (PMD Apex security + adjacent)
See section 8 for the rule-by-rule mapping.
10.4 Breach-class coverage
| Breach class | State |
|---|---|
| Drift / Gainsight OAuth token theft | Covered (Full + Refresh detector). |
| AuraInspector / Experience Cloud guest exposure | Covered (guest-user object exposure + View All). |
| ShinyHunters / UNC6040 vishing (malicious Connected App install) | Partial - cannot detect runtime social engineering, but the over-permissioned Connected App that becomes the attack pivot is flagged. |
| ForcedLeak (Agentforce class-bypass) | Covered (sf_agentforce deep detector). |
| ForcedLeak (Agentforce prompt-template grounding) | Planned (needs verified GenAiPromptTemplate schema). |
11. Out of scope (by architectural choice)
Vulkro is point-in-time, offline, local-first by design. The following are excluded from v1 on purpose, not by neglect:
| Class | Why out of scope | What would close it |
|---|---|---|
| Continuous monitoring | Requires a always-on agent in the customer org; that is the SSPM (AppOmni, Obsidian, Adaptive Shield) architecture, and the Salesloft Drift / Gainsight breach class is the cost of that architecture. | The deferred self-hosted server tier (kept in the customer perimeter). |
| DAST | Requires a running org to probe. Vulkro is a static / metadata tool. | Out of scope permanently; integrate with OWASP ZAP / Burp / Qualys externally if needed. |
| UEBA / behavioural identity | Requires login-event telemetry over time. SSPM territory (Obsidian leads). | Out of scope; complement Vulkro with Obsidian for that workflow. |
| Multi-org continuous drift | Same as continuous monitoring. | Self-hosted tier. |
| Per-user runtime password reuse | Requires login event stream. | Out of scope. |
| Username / email enumeration | Requires probing live endpoints (DAST). | Out of scope. |
12. Coverage status summary
| Group | Covered | Partial | Planned | Out of scope |
|---|---|---|---|---|
| AppExchange Top-20 | 15 | 0 | 0 | 5 |
| Metadata types (security-relevant subset, ~30) | 29 | 0 | 0 | 8 |
| PMD Apex security rules | 10 | 0 | 0 | 0 |
| PMD Apex error-prone (security-adjacent) | 4 | 0 | 0 | 2 |
| PMD Apex performance | 3 | 0 | 0 | 0 |
| Well-Architected anti-patterns (statically detectable subset) | 14 | 0 | 0 | 1 |
| Breach-class map | 5 | 0 | 0 | 0 |
The Wave 1 roadmap (12-15 additions) closes most of the Planned column; see the feature inventory for the live status.
13. References
Authoritative sources cited throughout:
- Salesforce Well-Architected Framework overview
- Salesforce Well-Architected Anti-Patterns catalog
- AppExchange Security Requirements Checklist (Partners)
- Top 20 vulnerabilities found in the AppExchange Security Review (the verbatim list in section 2.2)
- Prepare your app to pass the AppExchange Security Review (the 8 required-control categories in section 2.1)
- Salesforce Code Analyzer
- PMD Apex rules catalog
- Lightning Web Security architecture
- OWASP API Security Top 10
- Salesforce Metadata API types list
- Public breach write-ups (Salesloft Drift, Gainsight, AuraInspector campaign, ShinyHunters / UNC6040, ForcedLeak Agentforce CVSS 9.4).
This document is the source of truth for every Vulkro Salesforce detector roadmap decision. When a row in the matrix moves from Planned to Covered, the corresponding finding ID and detector module are added in the same commit, and this page is updated. The CHANGELOG records each transition.