Core Concepts
Security & code quality#
Every pull request into this package is scanned with Salesforce's own Code Analyzer. This page publishes what the latest scan found — including the findings that are still open — because a number you can check is worth more than an assurance you can't.
The short version#
| Critical findings | 0 — and 0 on every scan to date |
| Known-vulnerable JavaScript libraries | 0 — RetireJS returns nothing across the shipped package |
| Card data stored in Salesforce | None. Stripe.js tokenizes in the browser; only a payment-method ID reaches Apex |
| Public inbound endpoints | None. Salesforce asks Stripe for payment status — nothing is pushed into your org |
| API secrets in package fields | None. Stripe keys live only in a Named Credential your admin creates |
| Apex test coverage (packaged version) | 95%, measured by Salesforce, against a 75% requirement |
What removes risk before a scanner sees it#
A static analyzer can only find problems in code that exists. Four architectural decisions mean large categories of them are never written in the first place.
No cardholder data ever reaches your org. The donation form loads Stripe's Payment Element, and the card is tokenized in the donor's browser. What Apex receives is an opaque payment-method identifier. There is no field on any object in this package that could hold a card number, and nothing to leak if a record is over-shared. See Donations & payments.
No inbound webhook, so no public endpoint. Most payment integrations register a URL that the processor calls back into. This one inverts that: after a donor pays, a scheduled job in Salesforce asks Stripe what happened. There is no exposed endpoint to secure, no webhook signing secret to store or rotate, and no unauthenticated write path into your data.
Secrets are never packaged. The package ships no API key, in a custom field or anywhere else. Your admin creates a Named Credential during setup and Salesforce holds the key; the package references the credential by name. See Connecting Stripe.
The guest user can only insert. The public donation and event pages run as the Experience Cloud guest user, which in this package is granted the narrowest set of permissions the flow demonstrably needs. A guest submission is written as a staging row and picked up by a background job running in an authenticated context — a guest cannot create a Contact or an Opportunity, and cannot read one back.
The latest scan#
Scanned 1 September 2026 · Code Analyzer 0.53.0 · rule selector Recommended
Engines: PMD, ESLint (including the LWC and SLDS rule sets), CPD, RetireJS, and Code Analyzer's regex engine. Scope is the packaged source, the build and setup scripts, the widget source, and the documentation generators.
| Severity | Count |
|---|---|
| Critical | 0 |
| High | 48 |
| Moderate | 6,762 |
| Low | 17,510 |
| Info | 897 |
25,217 total — and that number needs explaining rather than burying, because five style rules account for more than four-fifths of it:
| Rule | Count | What it is |
|---|---|---|
slds/no-hardcoded-values-slds2 |
11,121 | Literal CSS values. Every one is the fallback half of the var(--token, value) pattern this package requires, so a campaign's own branding wins when it sets a token and the intended design renders when it doesn't. Auto-fixing these would break theming for every campaign that overrides a token. They are reported and deliberately kept. |
ApexUnitTestClassShouldHaveRunAs |
2,694 | A preference that every test method wrap itself in System.runAs. Test-only; no shipped behaviour. |
MethodNamingConventions |
2,601 | PMD's default naming pattern rejects the test_thing_doesThing convention used throughout the test suite. Test-only. |
ApexDoc |
2,255 | Missing doc blocks on individual members. Class-level doc blocks are what the published code reference is generated from, and those are required by the project's own rules. |
NoMixedIndentation |
2,037 | Whitespace. |
None of those five is a defect, and none of them can be. What matters is the severity split above, and the eight findings below.
Every security-tagged finding, and what it is#
The analyzer tags eight findings across the whole package as security-relevant. All eight are listed here with a verdict; each one is also recorded in the project's triage record or annotated in the code itself.
| Severity | Rule | Where | Verdict |
|---|---|---|---|
| High | VfUnescapeEl |
stripePayment.page |
The unescaped value is System.URL.getOrgDomainUrl() — your own org domain, from the platform. No user input is on the path. |
| High | VfUnescapeEl |
stripePaymentReturn.page |
Same value, same getter. |
| High | no-inner-html |
publicThankYou.js |
Writes a QR code onto the ticket page. The SVG is emitted by a QR encoder as a procedural matrix — it is not markup built from anything a donor typed. |
| Moderate | ApexSharingViolations |
DonationMonitorController |
An inner class. Apex inner classes don't inherit the outer class's sharing keyword, which is what the rule detects; this one runs under the enclosing admin-only controller action. |
| Moderate | ApexSharingViolations |
EventRegistrationController |
An inner class implementing the guest ticket flow, whose data access is gated by the guest action framework rather than by class-level sharing. |
| Moderate | ApexSharingViolations |
fflib_SObjectUnitOfWork |
Vendored FFLib. The access level is supplied by whoever constructs the Unit of Work, so the decision is made one level up, where the analyzer can't follow it. |
| Moderate | ApexDangerousMethods |
DonationDesignations |
Disables the FFLib trigger-layer CRUD re-check on a rollup that reads and writes only its own totals. The DML itself still runs under the platform's access rules. |
| Moderate | ApexDangerousMethods |
RecurringDonations |
The same, behind an explicit flag, for the same reason. |
The other 45 High findings are none of them security-tagged: four unused bindings that a bundler and a destructuring idiom actually require, and the remaining 41 inside vendored FFLib, which this package carries as source so that installing it does not force you to install a library first.
Every one of them is written up individually. The package publishes a Code Analyzer exceptions report listing each finding it does not fix, what the rule flags, why it is not a defect here, and how to check that for yourself against the code. That page is generated from a live scan rather than written by hand, and the build fails if a new finding appears without a justification or if a justification outlives the finding it explains — so it cannot quietly go out of date.
What this page does not claim
Code Analyzer is a static analyzer, not an audit, and a clean scan is not a security certification. This package has not completed Salesforce's AppExchange security review; when it does, that will be stated here as a separate fact. What the numbers above establish is that the code is scanned continuously, that the findings are published rather than filtered, and that every security-tagged one has a written verdict.
How it stays this way#
The scan is a required check on every pull request into the main branch, and runs again weekly so that a rule added in a new analyzer release surfaces against unchanged code.
- A Critical finding fails the build. The budget is zero, and the package has never had one, so any Critical is a genuine regression rather than an accepted baseline.
- High findings are capped at the current count. A pull request that adds one fails. Raising the cap is a deliberate, reviewed act that has to arrive with a written verdict for the new finding — it can't be nudged up to turn a build green.
- The analyzer version is pinned, so a red build always means new code, never a new rule quietly appearing.
- Auto-fixes are never applied wholesale — the SLDS rule above is the reason.
Alongside it, a separate workflow audits the package's shipped JavaScript dependencies weekly and on every pull request, and fails on any advisory at any severity.
Test coverage#
145 Apex test classes and over 2,300 test methods ship inside the package. Salesforce recomputes coverage itself when a package version is built and refuses to release one below 75%; the current version measured 95%.
Coverage is also verified across account models a development org can't reproduce. Before a version ships, the same packaged build is installed into four separate orgs — standard Contact/Account, Person Accounts, NPSP, and Person Accounts with NPSP — and the full test suite, a set of probes that push real records through the real packaged triggers, and the browser-level donation flow are all run in each. Results are diffed against the standard-account org, so a difference that only appears under Person Accounts or NPSP is visible instead of being buried in noise common to all four.