CRUD / FLS enforcement
- All SOQL queries in
@AuraEnabledmethods useWITH SECURITY_ENFORCED - All DML operations in controllers are gated by
isCreateable(),isUpdateable(),isDeletable()checks CIO_SchemaHelpervalidatesisAccessible()before returning objects/fields to the UI- Subquery and relationship-traversal queries catch
QueryExceptionand degrade gracefully when the running user is missing FLS
Sharing model
- All controller classes use
with sharing(respects org-wide defaults and sharing rules) - Service classes that run in async context use
inherited sharing - No class uses
without sharing
SOQL injection prevention
- Admin-supplied filter text on
CIO_Scheduled_Sync__c.SOQL_Filter__cis parsed and validated byCIO_SoqlFilterValidatorbefore execution. Strict whitelist grammar — only safe operators, validated field names, escaped string literals, no sub-SELECTs, no comments, no semicolons. CIO_RecruiterDashboardServiceresolves the configurable owner field against a cached schema-validated allowlist; native SOQL with bind variables is dispatched over the canonical name returned bySchema.SObjectField.getDescribe().getName().CIO_SourceConfigController.saveSourceMappingsdeletes existing mappings via a hardcodedswitchover the five legitimate lookup-field branches — never via dynamicDatabase.query.- All other dynamic SOQL uses bind variables; user-supplied values are never string-concatenated.
API key security
API_Key__c(Pipelines CDP write key) andApp_API_Key__c(App API bearer token) areEncryptedTextfields. Salesforce encrypts them at rest with platform encryption.- Subscriber-org users see the masked form (
*****) unless they have "View Encrypted Data" permission. - The Settings UI returns a fully-asterisk-masked value to the LWC; the unmasked key is never sent over the wire to the browser.
CIO_SettingsController.saveSettingsonly updates the stored key when the submitted value does not contain*— i.e. the admin actually re-typed a new key.- API keys are scrubbed from any persisted activity-log body via
CIO_Logger.sanitizeBody().
Future migration to External Credentials
The package ships today with EncryptedText storage because that pattern is well-supported in 2GP managed packages. A future release will introduce External Credentials so that the auth header is injected by the platform and Apex never reads the key directly.
Webhook security
- Inbound webhooks at
/services/apexrest/cio/webhook/*verify HMAC-SHA256 signatures using a per-config secret stored onCIO_Webhook_Config__c.Secret_Key__c. - Signature comparison is constant-time (
CIO_WebhookReceiver.constantTimeBlobEquals) to defeat timing-side-channel attacks. - Malformed signatures are rejected without echoing parser errors back to the caller.
- Internal exceptions never leak to the response body — handler returns a generic
{"error": "Internal server error"}and logs detail server-side. - All response bodies are built with
JSON.serialize(Map<String, Object>), never with string concatenation.
Data retention & PII handling
CIO_Activity_Log__cretention is governed byLog_Body_Retention_Days__c(default 30 days, minimum 7).- The daily
CIO_LogPurgeBatch(Schedulable) deletes records older than the cutoff. - Set
Log_Bodies_Enabled__c = falseto skip body capture entirely. The log row is still created (operational visibility) but bodies arenull. - The on-demand purge endpoint enforces a 7-day floor, requires
isDeletable()on the log object, and writes an audit record before deletion. - Activity log list views do not expose request/response bodies (only available in detail view).
CIO_ReadOnlypermission set excludesRequest_Body__candResponse_Body__cfields.- Error messages returned to the client are generic (no stack traces or internal details).
No third-party data sharing
CIO Pipelines does not share PII with any third party other than Customer.io itself, which is the integration target. We do not:
- Send analytics events to external services
- Ship telemetry from subscriber orgs to Creative Round
- Contact any URL other than the configured Customer.io endpoints
- Ship usage reports off the customer's Salesforce org
The only outbound HTTP traffic is to:
cdp.customer.ioorcdp-eu.customer.io(CDP writes)api.customer.ioorapi-eu.customer.io(App API reads, via Named Credential)
Both endpoints are pre-registered as Remote Site Settings with SSL enforced.
Permission sets
CIO_Admin
- Full CRUD on
CIO_Event_Trigger__c,CIO_Field_Mapping__c,CIO_Activity_Log__c - Read/write access to all non-required custom fields
- Access to
CIO_Settings__ccustom setting (API credentials) View Encrypted Data(so admins can edit and round-trip API keys)CIO_Hometab visibility
CIO_ReadOnly
- Read-only access to event triggers, field mappings, and activity log
- No access to request/response body fields (PII protection)
- No access to
CIO_Settings__ccustom setting
Encryption & transport
- All credentials at rest: Salesforce platform encryption (AES-256) on
EncryptedTextfields - All API traffic in transit: TLS via Salesforce HTTP callouts + Named Credentials
- Platform encryption applies to the underlying database; backup/restore inherits encryption
Testing
- 36 test classes cover 40 production Apex classes
- All tests run in managed-package context (
System.runAs(CIO_TestDataFactory.getAdminUser())) - HTTP callouts are mocked via
CIO_HttpCalloutMock - No test uses
@isTest(SeeAllData=true) - Negative-path tests for the SOQL filter validator, webhook HMAC verification, and FLS enforcement
Reporting a vulnerability
Email security findings to james@creativeround.com. We will respond within 2 business days and coordinate disclosure with affected installations.
Need a SOC 2 questionnaire response or a custom DPA? Available on the Enterprise tier.
Talk to us