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.
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.
While forceCalendar ships zero runtime dependencies, development tooling (testing, bundling, linting) does use dev dependencies. Dependabot monitors these for known vulnerabilities.
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.
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 Directive | Status | Notes |
|---|---|---|
| script-src 'self' | Compatible | No eval(), no new Function(), no inline scripts |
| style-src 'self' | Compatible | All styles via CSS custom properties and stylesheets |
| img-src 'self' | Compatible | No dynamic image loading from external sources |
| connect-src 'self' | Compatible | ICS fetch respects connect-src (configurable) |
| object-src 'none' | Compatible | No plugins, embeds, or applets |
| base-uri 'self' | Compatible | No base tag manipulation |
The following JavaScript patterns are explicitly avoided throughout the codebase:
* 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.
A calendar library has a specific and bounded attack surface. The following analysis covers the primary vectors relevant to forceCalendar's architecture.
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.
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.
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.
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.
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.
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.
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.
| Issue | Bug | Impact | Status |
|---|---|---|---|
| #107 | TimezoneManager.parseTimezone() property name mismatch | Timezone abbreviations (e.g. PST, EST) could fail to resolve | Resolved |
| #108 | ICSParser VALARM export uses wrong property name | Alarm/reminder data lost during ICS round-trip export | Resolved |
| #109 | DateUtils.isDST() returns inverted boolean | DST detection logic inverted, causing incorrect time offsets | Resolved |
| #110 | DateUtils.addHoursWithDST() double-adjusts offset | Events shifted by double the DST offset during transitions | Resolved |
All fixes shipped in PRs #113--#118. See individual GitHub issues for technical details and regression tests.
Fetched from GitHub Issues at build time. 35 resolved, 7 open.
| Issue | Finding | Component | Severity | Status | Opened | Closed |
|---|---|---|---|---|---|---|
| #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/core | Critical | Resolved | Feb 21, 2026 | Feb 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/core | Critical | Resolved | Feb 21, 2026 | Mar 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/core | Critical | Resolved | Feb 21, 2026 | Mar 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/core | Critical | Resolved | Feb 21, 2026 | Feb 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/core | Critical | Resolved | Feb 21, 2026 | Feb 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/interface | Critical | Resolved | Feb 21, 2026 | Feb 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/core | Critical | Resolved | Feb 26, 2026 | Feb 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/core | Critical | Resolved | Feb 26, 2026 | Feb 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/core | Critical | Resolved | Feb 26, 2026 | Feb 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/core | Critical | Resolved | Feb 26, 2026 | Feb 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/core | Critical | Resolved | Feb 26, 2026 | Feb 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/interface | Critical | Resolved | Mar 5, 2026 | Mar 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/interface | Critical | Resolved | Mar 5, 2026 | Mar 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/core | Critical | Resolved | Mar 5, 2026 | Mar 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/core | Critical | Resolved | Mar 5, 2026 | Mar 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/core | Critical | Resolved | Mar 20, 2026 | Mar 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/core | Critical | Resolved | Mar 20, 2026 | Mar 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/core | Critical | Resolved | Mar 20, 2026 | Mar 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/core | Critical | Resolved | Mar 20, 2026 | Mar 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/core | High | Resolved | Feb 21, 2026 | Mar 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/core | High | Resolved | Feb 21, 2026 | Mar 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/core | High | Resolved | Feb 21, 2026 | Mar 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/interface | High | Resolved | Feb 21, 2026 | Mar 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/interface | High | Open | Feb 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/core | High | Resolved | Feb 26, 2026 | Feb 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/interface | High | Open | Mar 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/interface | High | Resolved | Mar 5, 2026 | Mar 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/core | High | Resolved | Mar 5, 2026 | Mar 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/interface | High | Resolved | Mar 5, 2026 | Mar 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/core | High | Resolved | Mar 5, 2026 | Mar 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/interface | High | Open | Mar 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/core | High | Resolved | Mar 5, 2026 | Mar 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/interface | High | Open | Mar 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/core | High | Resolved | Mar 5, 2026 | Mar 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/interface | High | Open | Mar 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/core | High | Open | Mar 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/core | High | Resolved | Mar 5, 2026 | Mar 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/core | High | Resolved | Mar 5, 2026 | Mar 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/core | High | Resolved | Mar 5, 2026 | Mar 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/core | High | Resolved | Mar 20, 2026 | Mar 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/core | Medium | Resolved | Feb 26, 2026 | Feb 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/interface | Unknown | Open | Mar 5, 2026 | -- |
Data sourced from forceCalendar/core and forceCalendar/interface GitHub issues with type:security label. Last built: March 2026. Refreshed on each deploy.
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.