Security Audit

Transparent security analysis of forceCalendar's codebase. Built for environments where security is non-negotiable.

This is not a marketing page. This audit documents real findings, including open vulnerabilities and their remediation status. We believe transparency builds more trust than a clean report that hides issues.

Supply Chain Security

0
Dependencies
0
Transitive Deps
0
npm Advisories

Why this matters

Every dependency is a potential attack vector. Supply chain attacks (like the event-stream, ua-parser-js, and colors incidents) have demonstrated that even popular packages can be compromised. forceCalendar eliminates this entire class of vulnerability by shipping zero runtime dependencies.

forceCalendar
+Zero runtime dependencies
+No transitive dependency tree
+No supply chain attack surface
+npm audit always clean
Typical Calendar Library
--30-100+ transitive dependencies
--Each dependency is an attack vector
--Vulnerability churn from dep updates
--Requires continuous monitoring

Dev dependency hygiene

While forceCalendar ships zero runtime dependencies, development tooling (testing, bundling, linting) does use dev dependencies. Dependabot monitors these for known vulnerabilities.

28/30
Alerts Resolved
2
Dev-Only Remaining
14
Dependabot PRs Merged

The 2 remaining alerts are dev-only transitive dependencies (esbuild via Vite and @tootallnate/once via Jest) that do not affect published packages or runtime behavior. These will be resolved when upstream tooling releases updates.

Verified by running npm ls --all --json on @forcecalendar/core and @forcecalendar/interface. Both packages list zero dependencies in their package.json.

Content Security Policy Compliance

forceCalendar was built specifically for strict CSP environments, including Salesforce Locker Service -- one of the most restrictive JavaScript sandboxes in production use. The library uses no patterns that would violate common CSP directives.

CSP DirectiveStatusNotes
script-src 'self'CompatibleNo eval(), no new Function(), no inline scripts
style-src 'self'CompatibleAll styles via CSS custom properties and stylesheets
img-src 'self'CompatibleNo dynamic image loading from external sources
connect-src 'self'CompatibleICS fetch respects connect-src (configurable)
object-src 'none'CompatibleNo plugins, embeds, or applets
base-uri 'self'CompatibleNo base tag manipulation

Prohibited patterns

The following JavaScript patterns are explicitly avoided throughout the codebase:

eval()
new Function()
document.write()
innerHTML *

* innerHTML is used in @forcecalendar/interface renderers and is tracked as finding DOM-001. The core library is entirely DOM-free.

Salesforce Locker Service compatibility has been verified in production deployments. The library runs in Lightning Web Components without any CSP violations.

Attack Surface Analysis

A calendar library has a specific and bounded attack surface. The following analysis covers the primary vectors relevant to forceCalendar's architecture.

ICS Parser

@forcecalendar/core
Resolved

The ICS parser processes external .ics files, which are untrusted input by definition. Previously lacked input size limits, which could allow denial-of-service via crafted files.

+Fixed in v2.1.21 -- configurable size limits with safe defaults
+Maximum input size, line count, and event count enforcement
+Parser is read-only -- cannot execute code or modify system state
GitHub Issue #37-- Resolved in v2.1.21

URL Handling / SSRF

@forcecalendar/core
Resolved

The ICS file fetching mechanism previously accepted URLs pointing to internal network resources. In server-side contexts, this created a Server-Side Request Forgery (SSRF) vector.

+Fixed in v2.1.21 -- URL validation against private/internal IP ranges
+Scheme allowlisting restricts to http/https only
+Client-side usage additionally mitigated by browser same-origin policy
GitHub Issue #38-- Resolved in v2.1.21

DOM Rendering / XSS

@forcecalendar/interface
In Progress

The Web Components interface layer renders event data into the DOM. Certain renderers use innerHTML to insert content, which creates a cross-site scripting vector if event data contains untrusted input.

!innerHTML in renderers can execute injected scripts via event titles or descriptions
+Shadow DOM provides partial isolation from the host page
+Core library is entirely DOM-free and not affected
GitHub Issue #39-- Under remediation

Recurrence Engine

@forcecalendar/core
Resolved

The recurrence expansion engine processes RFC 5545 RRULE patterns. Previously lacked hard limits on occurrence count, which could cause excessive computation via algorithmic complexity attack.

+Fixed in v2.1.21 -- hard cap on maxOccurrences prevents unbounded expansion
+Expansion is CPU-bound only -- no I/O or network side effects
+Can be further mitigated with Web Worker isolation (already supported)
GitHub Issue #56-- Resolved in v2.1.21

ReDoS in Email Validation

@forcecalendar/core
Resolved

The email validation regex used in organizer and attendee fields was vulnerable to Regular Expression Denial of Service (ReDoS). Crafted input strings could cause catastrophic backtracking, blocking the main thread.

+Fixed in v2.1.22 -- replaced vulnerable regex with linear-time validation
+New regex has O(n) worst-case complexity, preventing backtracking attacks
+Affects ICS parsing of ORGANIZER and ATTENDEE fields with mailto: URIs
GitHub Issue #111-- Resolved in v2.1.22

CI/CD Workflow Permissions

@forcecalendar/core
Resolved

GitHub Actions workflows were using default (write-all) permissions, granting more access than necessary. This is a supply chain hardening concern -- overly permissive workflows can be exploited if a dependency or action is compromised.

+Fixed -- explicit least-privilege permissions blocks added to all workflows
+Read-only default with scoped write access only where required
+Follows GitHub's recommended security hardening for Actions

Recent Critical Bug Fixes

The following correctness bugs were identified and resolved in v2.1.22. While not direct security vulnerabilities, correctness bugs in date/time handling can lead to data integrity issues in production calendar systems.

IssueBugImpactStatus
#107TimezoneManager.parseTimezone() property name mismatchTimezone abbreviations (e.g. PST, EST) could fail to resolveResolved
#108ICSParser VALARM export uses wrong property nameAlarm/reminder data lost during ICS round-trip exportResolved
#109DateUtils.isDST() returns inverted booleanDST detection logic inverted, causing incorrect time offsetsResolved
#110DateUtils.addHoursWithDST() double-adjusts offsetEvents shifted by double the DST offset during transitionsResolved

All fixes shipped in PRs #113--#118. See individual GitHub issues for technical details and regression tests.

Remediation Tracker

Fetched from GitHub Issues at build time. 35 resolved, 7 open.

42
Total Findings
35
Resolved
7
Open
IssueFindingComponentSeverityStatusOpenedClosed
#29
Fix fuzzy search: tokenize field values before Levenshtein comparison
`EventSearch.js:363-366` computes Levenshtein distance between a short search term and the entire field value string. For any field longer than ~6 chars, the distance will always exceed the threshold...
@forcecalendar/coreCriticalResolvedFeb 21, 2026Feb 21, 2026
#32
Fix fuzzy search — Levenshtein compares against entire field value
In `EventSearch.js`, the fuzzy matching calculates Levenshtein distance between the search term and the entire field value. For any field longer than a few characters, the distance will always exceed...
@forcecalendar/coreCriticalResolvedFeb 21, 2026Mar 20, 2026
#33
Fix RecurrenceEngineV2 byDay format mismatch with RRuleParser
`RecurrenceEngineV2` expects `byDay` values as `{weekday}` objects, but `RRuleParser` returns plain strings like `"MO"`, `"TU"`, `"2TU"`. These two core modules don't interop correctly. Either...
@forcecalendar/coreCriticalResolvedFeb 21, 2026Mar 20, 2026
#37
Add input size limits to ICS parser
The ICS parser accepts unbounded input. A malicious ICS file could be gigabytes in size, causing OOM. This is a denial-of-service vector. - Add configurable max input size (default: 10MB) - Add max...
@forcecalendar/coreCriticalResolvedFeb 21, 2026Feb 26, 2026
#38
Add SSRF protection to ICS importFromURL
`ICSHandler.importFromURL()` fetches arbitrary URLs without validation. An attacker could supply `http://169.254.169.254/latest/meta-data/` or internal network URLs to exfiltrate cloud metadata or...
@forcecalendar/coreCriticalResolvedFeb 21, 2026Feb 26, 2026
#39
Fix innerHTML XSS vectors in view renderers
Multiple renderers use innerHTML with unescaped date attributes, creating XSS vectors: - `MonthViewRenderer.js` line ~90 - `WeekViewRenderer.js` lines ~96, ~144 An attacker who controls event data...
@forcecalendar/interfaceCriticalResolvedFeb 21, 2026Feb 24, 2026
#51
CRITICAL: Remove Locker Service evasion in AdaptiveMemoryManager
The code uses intentional string concatenation ('proc' + 'ess') to evade Salesforce Locker Service static analysis. This violates Locker Service terms and will cause deployment rejection. - Blocks...
@forcecalendar/coreCriticalResolvedFeb 26, 2026Feb 26, 2026
#52
CRITICAL: Add SSRF protection to ICSHandler.importFromURL
No URL validation allows Server-Side Request Forgery (SSRF) attacks to: - Fetch from private IPs (127.0.0.1, 192.168.x.x, 10.x.x.x) - Access AWS metadata endpoints (169.254.169.254) - Accept file://...
@forcecalendar/coreCriticalResolvedFeb 26, 2026Feb 26, 2026
#53
CRITICAL: Add size/line/event limits to ICS parser
No input validation allows Denial of Service via: - Unbounded input size — can accept 1GB+ ICS strings - Unbounded line count — unfoldLines() creates unlimited array - Unbounded event count — no cap...
@forcecalendar/coreCriticalResolvedFeb 26, 2026Feb 26, 2026
#54
CRITICAL: Add field size limits to Event validation
Event fields have no size validation allowing storage exhaustion: - title: unbounded string (could store 1GB+) - description: unbounded string (could store 1GB+) - location: unbounded string (could...
@forcecalendar/coreCriticalResolvedFeb 26, 2026Feb 26, 2026
#56
CRITICAL: Hard cap maxOccurrences in RecurrenceEngine
maxOccurrences parameter has default of 365 but no hard upper limit. Caller can pass any value causing unbounded iteration and memory exhaustion. - Memory exhaustion - CPU spike during expansion -...
@forcecalendar/coreCriticalResolvedFeb 26, 2026Feb 26, 2026
#58
StateManager.setState has no prototype pollution protection
`StateManager.setState()` (line 96-99 in `src/core/StateManager.js`) uses object spread without sanitization: If `updates` contains `__proto__`, `constructor`, or `prototype` keys, these could...
@forcecalendar/interfaceCriticalResolvedMar 5, 2026Mar 5, 2026
#59
EventBus.matchesPattern is vulnerable to regex injection
`EventBus.matchesPattern()` (line 171-173 in `src/core/EventBus.js`) constructs a RegExp from unescaped user input: Only `*` is replaced. All other regex metacharacters (`.`, `+`, `(`, `)`, `[`, `]`,...
@forcecalendar/interfaceCriticalResolvedMar 5, 2026Mar 5, 2026
#76
EventStore.queryEvents() misses events at month boundaries in non-UTC timezones
The queryEvents() method with month filter uses the `indices.byMonth` index (keyed using local dates from `_indexEvent()` at line 686), but does not convert the month/year filter through the...
@forcecalendar/coreCriticalResolvedMar 5, 2026Mar 5, 2026
#78
ICSParser.parse() accepts unbounded input size (DoS vulnerability)
The ICSParser.parse() method at ICSParser.js:35-79 accepts an icsString parameter with no maximum size validation. An attacker can submit a multi-gigabyte ICS file causing memory exhaustion and...
@forcecalendar/coreCriticalResolvedMar 5, 2026Mar 5, 2026
#107
TimezoneManager.parseTimezone() references non-existent database.abbreviations
`TimezoneManager.parseTimezone()` at lines 368-373 references `this.database.abbreviations`, but the `TimezoneDatabase` class uses `this.aliases` (line 427 in TimezoneDatabase.js). The property...
@forcecalendar/coreCriticalResolvedMar 20, 2026Mar 20, 2026
#108
ICSParser VALARM export uses reminder.minutes instead of minutesBefore
`ICSParser.eventToICS()` at line 235 exports VALARM triggers using `reminder.minutes`, but the canonical property set by `Event._normalizeReminder()` is `reminder.minutesBefore` (Event.js lines 162,...
@forcecalendar/coreCriticalResolvedMar 20, 2026Mar 20, 2026
#109
DateUtils.isDST() logic is inverted — returns true when NOT in DST
`DateUtils.isDST()` at lines 493-501 returns `true` when the current offset equals the MAXIMUM of January/July offsets. In the Northern Hemisphere, the maximum offset is the standard (winter) offset,...
@forcecalendar/coreCriticalResolvedMar 20, 2026Mar 20, 2026
#110
DateUtils.addHoursWithDST() double-adjusts for DST transitions
`DateUtils.addHoursWithDST()` at lines 510-526 adds hours via UTC milliseconds (which already handles DST correctly since it operates on absolute time), then applies an ADDITIONAL DST offset...
@forcecalendar/coreCriticalResolvedMar 20, 2026Mar 20, 2026
#34
Fix SearchWorkerManager.processPendingSearches — no-op implementation
`SearchWorkerManager.processPendingSearches()` is a no-op. Pending searches are pushed to `this.pendingSearches` but never dispatched to the worker. Users waiting on search results hang forever....
@forcecalendar/coreHighResolvedFeb 21, 2026Mar 20, 2026
#35
Implement ICS recurring event expansion or remove the stub
`ICSHandler.expandRecurringEvents()` returns only the base event without actually expanding recurrence. This causes silent data loss — users import an ICS file with recurring events and only get the...
@forcecalendar/coreHighResolvedFeb 21, 2026Mar 20, 2026
#36
Fix EnhancedCalendar.getEventsInRange breaking parent API contract
`EnhancedCalendar.getEventsInRange()` overrides the parent `Calendar.getEventsInRange()` and changes the return type from synchronous Event[] to async plain objects. This is a Liskov Substitution...
@forcecalendar/coreHighResolvedFeb 21, 2026Mar 20, 2026
#41
Fix window keydown listener leak in EventForm
`EventForm.js` (line ~308-316) adds a window keydown listener but the reference is not tracked by the `cleanup()` method. When the form is destroyed and recreated, listeners accumulate. Store the...
@forcecalendar/interfaceHighResolvedFeb 21, 2026Mar 5, 2026
#43
Fix DOMUtils.trapFocus broken in Shadow DOM
`DOMUtils.trapFocus()` uses `document.activeElement` which returns the host element when focus is inside Shadow DOM. It should use `shadowRoot.activeElement` or traverse the composed path. Accept a...
@forcecalendar/interfaceHighOpenFeb 21, 2026--
#55
HIGH: Deep sanitize setState for prototype pollution
Prototype pollution protection is shallow-only. Only top-level __proto__/constructor/prototype are deleted, but nested objects merged via spread without sanitization. Attacker can pollute nested...
@forcecalendar/coreHighResolvedFeb 26, 2026Feb 26, 2026
#62
DOMUtils.parseHTML accepts unsanitized HTML — potential XSS vector
\`DOMUtils.parseHTML()\` (lines 153-156 in \`src/utils/DOMUtils.js\`) accepts raw HTML and creates DOM nodes without sanitization: \`\`\`js static parseHTML(htmlString) { const template =...
@forcecalendar/interfaceHighOpenMar 5, 2026--
#64
BaseViewRenderer.renderTimedEvent uses hardcoded 12-hour time format
\`BaseViewRenderer.formatTime()\` (lines 112-120 in \`src/renderers/BaseViewRenderer.js\`) and \`formatHour()\` (lines 101-105) use hardcoded 12-hour AM/PM formatting: \`\`\`js formatTime(date) {...
@forcecalendar/interfaceHighResolvedMar 5, 2026Mar 5, 2026
#65
DateUtils.differenceInDays uses DST-unsafe millisecond arithmetic
`DateUtils.differenceInDays()` (line 258-261 in `core/calendar/DateUtils.js`) uses millisecond arithmetic: This is inconsistent with the rest of the file, which deliberately uses `setDate()` for date...
@forcecalendar/coreHighResolvedMar 5, 2026Mar 5, 2026
#65
ForceCalendar component lacks proper disconnectedCallback cleanup
The \`ForceCalendar\` web component (\`src/components/ForceCalendar.js\`) needs to ensure complete cleanup when removed from the DOM via \`disconnectedCallback\`. Key concerns: 1. The StateManager...
@forcecalendar/interfaceHighResolvedMar 5, 2026Mar 5, 2026
#66
ICSParser.parse() silently drops EXDATE (exclusion dates)
`ICSParser` defines `EXDATE: 'excludeDates'` in its `propertyMap` (line 26 in `core/ics/ICSParser.js`), but the `parseProperty()` switch statement (lines 223-288) has no `case 'EXDATE'` handler. This...
@forcecalendar/coreHighResolvedMar 5, 2026Mar 5, 2026
#67
DOMUtils.waitForAnimation has no timeout — can hang forever
\`DOMUtils.waitForAnimation()\` (lines 133-141 in \`src/utils/DOMUtils.js\`) returns a Promise that resolves when an animation/transition ends, but has no timeout: \`\`\`js static...
@forcecalendar/interfaceHighOpenMar 5, 2026--
#68
EventStore.clear() doesn't clear byCategory and byStatus indices
`EventStore.clear()` (lines 614-627 in `core/events/EventStore.js`) clears the main events Map and several indices but misses `byCategory` and `byStatus` indices. After `clear()`, these indices still...
@forcecalendar/coreHighResolvedMar 5, 2026Mar 5, 2026
#68
MonthViewRenderer._renderEvent doesn't use getContrastingTextColor
\`MonthViewRenderer._renderEvent()\` (lines 103-111 in \`src/renderers/MonthViewRenderer.js\`) hardcodes \`color: white\` for event text: \`\`\`js _renderEvent(event) { const color =...
@forcecalendar/interfaceHighOpenMar 5, 2026--
#69
AdaptiveMemoryManager: unhandled promise rejections from async setInterval callback
`AdaptiveMemoryManager.startMonitoring()` (line 71 in `core/performance/AdaptiveMemoryManager.js`) passes an async function to `setInterval`: `checkMemoryPressure()` is an `async` method (line 92)....
@forcecalendar/coreHighResolvedMar 5, 2026Mar 20, 2026
#69
EventBus.matchesPattern() regex metacharacters not escaped
The `EventBus.matchesPattern()` method in `src/core/EventBus.js:171-173` only escapes the `*` wildcard character but does not escape other regex metacharacters. This causes unintended pattern...
@forcecalendar/interfaceHighOpenMar 5, 2026--
#70
ConflictDetector._isWithinBusinessHours has broken hour comparison logic
`ConflictDetector._isWithinBusinessHours()` (lines 507-516 in `core/conflicts/ConflictDetector.js`) has fundamentally broken logic: 1. **Ignores minutes**: A gap starting at 8:45 AM with business...
@forcecalendar/coreHighOpenMar 5, 2026--
#71
RecurrenceEngineV2: BYSETPOS is a TODO stub
`RecurrenceEngineV2.getNextMonthly()` (lines 305-308 in `core/events/RecurrenceEngineV2.js`) has a BYSETPOS branch that is effectively a no-op: It advances the month but doesn't apply any BYSETPOS...
@forcecalendar/coreHighResolvedMar 5, 2026Mar 20, 2026
#75
Event.addAttendee() bypasses email validation
The addAttendee() method at Event.js:586-607 only checks `if (\!attendee.email)` but never validates email format. Meanwhile, the Event constructor path calls `_validateAttendees()` (line 305) which...
@forcecalendar/coreHighResolvedMar 5, 2026Mar 20, 2026
#77
Event.overlaps() has fragile boundary handling with all-day events
The overlaps() method at Event.js:458-467 uses standard interval comparison logic without accounting for all-day event boundary normalization. All-day events are normalized with end time set to...
@forcecalendar/coreHighResolvedMar 5, 2026Mar 20, 2026
#111
ReDoS vulnerability in email validation regex (Event.js)
**Source**: GitHub Code Scanning alerts #6 and #7 The email validation regex `/^[^\s@]+@[^\s@]+\.[^\s@]+$/` used in two locations has overlapping character classes that cause polynomial (O(n^2))...
@forcecalendar/coreHighResolvedMar 20, 2026Mar 20, 2026
#57
MEDIUM: Validate BYWEEKNO in RRuleParser
BYWEEKNO parsing doesn't validate week numbers. ISO 8601 week numbers must be 1-53 (positive) or -1 to -53 (negative for last weeks), but parseIntList accepts any integer. - Invalid week numbers...
@forcecalendar/coreMediumResolvedFeb 26, 2026Feb 26, 2026
#71
EventForm: Escape color label attributes defensively
The EventForm component does not escape color label attributes, creating a potential XSS vector if color configuration becomes user-provided in the future. `src/components/EventForm.js:260-261` Color...
@forcecalendar/interfaceUnknownOpenMar 5, 2026--

Data sourced from forceCalendar/core and forceCalendar/interface GitHub issues with type:security label. Last built: March 2026. Refreshed on each deploy.

Methodology

Audit approach

  • 1.Manual code review -- line-by-line analysis of core and interface packages, focusing on input handling, DOM manipulation, and data flow
  • 2.Dependency analysis -- verification of zero-dependency claim via npm ls and package.json inspection
  • 3.CSP compatibility testing -- deployment and validation in Salesforce Locker Service and strict CSP headers
  • 4.Attack surface mapping -- identification of all input vectors, trust boundaries, and data flows

Recommended tooling

  • +npm audit -- checks for known vulnerabilities in dependencies (always clean for forceCalendar)
  • +eslint-plugin-security -- static analysis for common security anti-patterns in JavaScript
  • +Snyk -- continuous vulnerability monitoring and code analysis
  • +GitHub Dependabot -- automated dependency updates (minimal surface for forceCalendar due to zero deps)

Scope

This audit covers @forcecalendar/core and @forcecalendar/interface as published on npm. The Salesforce LWC wrapper, documentation site, and benchmark tooling are out of scope. This is a self-assessment, not a third-party audit. We encourage independent security researchers to verify these findings.