Methodology: what a safe Salesforce app should be
This page has two parts: the safe-Salesforce-app standard as Salesforce
publishes it, and the Vulkro coverage map against it. Every detector traces
back to a row in the coverage matrix at the bottom, and every gap is labelled
covered, partial, planned, or out of scope. You can audit our coverage instead
of trusting our marketing.
Detector implementations are proprietary (closed source). What is public is
the methodology, the rule IDs, and the coverage status.
Why publish this
A security tool that will not name its standard cannot be audited against.
Most commercial tools in this space keep their rule taxonomy proprietary, so
the customer is left trusting a vendor demo. Vulkro puts the standard we measure against, the rule IDs we emit,
and the exact coverage gap on this page. If you can read it and reproduce our
coverage decision for a finding, the product is doing its job.
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
OwaspCategory enum 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 platform's own code analyzer rule set (and the open-source Apex rule
catalog it wraps) is the industry baseline for code-level coverage. It is not part of the
"standard" in the safe-app sense, but every Salesforce SAST tool is compared
against it, 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 (the platform's own code analyzer plus an enterprise SAST tool).
- 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=true in SecuritySettings
(Vulkro flags enableCSRFOnPost=false at High).
- REST Apex endpoints that change state must verify the request origin or
rely on the
Authorization header (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, or staticResource content 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; ERROR for 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
@isTest and a TestDataFactory pattern (no SeeAllData).
System.runAs is restricted to test paths.
ApexUnitTestClassShouldHaveAsserts (platform code analyzer rule): no test methods without Assert.*.
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).
eval with user-controlled input.
- Inline
<script> or <style> tags (CSP-blocked).
- Bypassing the namespace boundary via
window.parent / top.
@wire-to-innerHTML taint 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 direct innerHTML from external data = DOM XSS.
@wire adapters returning user-controllable data must be sanitised before
templating.
LightningMessageService channels must have isExposed=false unless the
channel is genuinely cross-package (the Top-20 vulnerability #15).
- CSS must stay inside the component file (
absolute / fixed / float
positioning that breaks encapsulation fails review).
4.3 Aura legacy considerations
.cmp markup: aura:unescapedHtml is forbidden on user content.
Locker.SecureDocument access 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 set escape="false" on user content.
<apex:outputField> uses Field-Level Security for rendering.
<apex:includeScript> must point at a StaticResource, never an external URL.
<apex:repeat> over user data: the rendering child must escape.
The Salesforce Metadata API exposes 200+ types. About 25 are security-relevant.
Each holds an org's configuration in code form, so 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 (for example a Web-to-Lead
Description field) can exfiltrate CRM data when it is 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
breach class carries that 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. |
The Apex rule set behind the platform's own code analyzer is the de-facto
industry baseline for Apex code-quality coverage. Vulkro does not aim to
replace it (the wrapper integration exists for users who want its output
in the Vulkro format), but the security-category rules are the comparison
point.
8.1 Apex security rules (the security category)
| 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 Apex error-prone (the security-adjacent subset)
| 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. |
| 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 Apex best practices
| 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 rule catalog is documented with the
platform's own code analyzer.
Vulkro is licensed for Wave 1 rule 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 the CLI (scheduled multi-org
scans, UEBA, DAST). Scheduled multi-org scans are covered by
Vulkro Cloud; see section 11.
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 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. |
| 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 (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 for the CLI (by architectural choice)
The vulkro-sf CLI is point-in-time, offline, local-first by design.
The following are excluded from the CLI on purpose, not by neglect. Where a
class is covered elsewhere, the last column says so.
| Class | Why out of scope for the CLI | What covers it |
|---|
| Scheduled scans and change history | The CLI runs when you run it and keeps no standing connection into the org. A standing third-party connection is itself an integration to secure: the 2025-26 breach class abused exactly that kind of trusted access. | Vulkro Cloud: scheduled scans of every org you connect, read-only, with the history kept across scans. It is scan-based and runs on a schedule, so it is not real-time monitoring. |
| Multi-org drift | Same as above: comparing many orgs over time needs scans kept somewhere shared. | Vulkro Cloud: multi-org scans on a schedule, with changes between scans tracked per org. |
| DAST | Requires a running org to probe. Vulkro is a static / metadata tool. | Out of scope permanently; integrate with a dynamic testing tool externally if needed. |
| UEBA / behavioural identity | Requires login-event telemetry over time: activity-monitoring territory. | Out of scope; pair Vulkro with an activity-monitoring platform for that workflow. |
| 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 |
| Apex security rules | 10 | 0 | 0 | 0 |
| Apex error-prone (security-adjacent) | 4 | 0 | 0 | 2 |
| 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