A comprehensive validator for Sparkle appcast.xml feeds.
Available as a CLI tool, JavaScript library, web application, and GitHub Action.
Appcast validation updated for Sparkle 2.10.0.
Note: This is an independent community project. It is not affiliated with, endorsed by, or sponsored by the official Sparkle project or its maintainers.
Validation covers appcast structure, metadata, and compatibility rules through Sparkle 2.10.0, including the macOS 12 minimum for targeted updates and signed release-note metadata. Signature checks validate format and length; they do not verify cryptographic authenticity or test installation of an update.
This validator was developed by analyzing 500+ real-world appcasts from production macOS applications including iTerm2, VLC, Cyberduck, Brave Browser, Dash, Transmission, and many others. Our validation rules are grounded in:
- Sparkle Source Code — Direct analysis of version comparison logic, signature verification, and parsing behavior in the Sparkle 2.x codebase
- Maintainer Feedback — Rule refinements based on feedback from Sparkle maintainer @zorgiepoo
- Real-World Patterns — Identifying common issues that cause silent failures (missing versions, malformed signatures, date/version ordering mismatches)
The test corpus includes apps that are:
- Perfect (zero warnings): LowProfile, Scroll Reverser, SourceTree, Skim
- Real-world valid (minor warnings): iTerm2, Dash, Cyberduck, Tunnelblick
- Edge cases: Large feeds (200+ items), delta-heavy feeds, multi-channel feeds
This empirical approach ensures the validator catches issues that actually matter in production while minimizing false positives.
- Validates Sparkle appcast.xml structure, metadata, and compatibility rules
- Reports errors, warnings, and informational messages with line numbers
- Provides fix suggestions for common issues
- Works as CLI, library, web app, or GitHub Action
- Checks:
- XML structure (RSS 2.0 + Sparkle namespace)
- Version declarations
- Enclosure attributes (url, length, type)
- URL validity
- Date formats (RFC 2822)
- Signature metadata format and length (EdDSA/DSA)
- System requirements
- Delta updates
- Phased rollouts
- Channel names
- And more...
Try it online at SparkleValidator.com
Requires Node.js 22 or later.
npm install -g sparkle-validatorOr run directly without installing:
npx sparkle-validator https://example.com/appcast.xmlOr with Homebrew:
brew tap dweekly/sparkle-validator
brew trust --formula dweekly/sparkle-validator/sparkle-validator
brew install dweekly/sparkle-validator/sparkle-validator# Validate a local file
sparkle-validator appcast.xml
# Validate from URL
sparkle-validator https://example.com/appcast.xml
# Validate from stdin
cat appcast.xml | sparkle-validator -
# JSON output
sparkle-validator --format json appcast.xml
# Strict mode (warnings as errors)
sparkle-validator --strict appcast.xml
# Only show errors
sparkle-validator --quiet appcast.xml
# Check that URLs exist and sizes match
sparkle-validator --check-urls appcast.xml
# Check URLs with custom timeout (ms)
sparkle-validator --check-urls --timeout 30000 appcast.xml| Option | Description |
|---|---|
-f, --format <type> |
Output format: text (default) or json |
-s, --strict |
Treat warnings as errors |
-c, --check-urls |
Check that URLs exist and sizes match |
--target-sparkle-version <version> |
Bundled Sparkle version; use 300=2.10.0 to target build 300 in a multi-item feed |
--require-signed-feed |
Require Ed25519 signature metadata on enclosures and signature/length metadata on in-app release notes |
--base-url <url> |
Resolve relative resource URLs against this URL |
--timeout <ms> |
Timeout for URL checks (default: 10000ms) |
--no-info |
Suppress informational messages |
--no-color |
Disable colored output |
-q, --quiet |
Only show errors |
-v, --version |
Show version number |
-h, --help |
Show help |
For Sparkle 2.10 updates in a mixed-history feed:
sparkle-validator --target-sparkle-version 300=2.10.0 appcast.xmlReplace 300 with the update’s build version. A bare 2.10.0 is suitable only
for a single-item feed. Signature checks validate metadata format and length;
they do not verify cryptographic authenticity.
| Code | Meaning |
|---|---|
| 0 | Valid (no errors) |
| 1 | Invalid (has errors, or warnings with --strict) |
| 2 | Input error (file not found, network error, etc.) |
Use the official GitHub Action for the simplest integration:
@v1 follows releases within major version 1 and already includes the Sparkle
2.10.0 checks shipped in v1.3.0. Existing @v1 users do not need to change
their Action reference. To pin this release, use @v1.3.0; users pinned to older
releases must update their reference to receive these checks.
name: Validate Appcast
on:
push:
paths: ['appcast.xml']
pull_request:
paths: ['appcast.xml']
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate appcast.xml
uses: dweekly/Sparkle-Validator@v1
with:
file: appcast.xml
# With options
- name: Validate with URL checking
uses: dweekly/Sparkle-Validator@v1
with:
file: appcast.xml
strict: true
check-urls: trueTo check an update that bundles Sparkle 2.10.0 in a feed with older releases:
- uses: dweekly/Sparkle-Validator@v1
with:
file: appcast.xml
target-sparkle-version: '300=2.10.0'Replace 300 with the update's build version. For a single-item feed, use
target-sparkle-version: '2.10.0'.
| Input | Description | Default |
|---|---|---|
file |
Path or URL to appcast.xml | (required) |
strict |
Treat warnings as errors | false |
check-urls |
Verify enclosure URLs exist | false |
timeout |
URL check timeout (ms) | 10000 |
quiet |
Only show errors | false |
no-info |
Suppress informational diagnostics | false |
base-url |
Base URL for relative resource links | (none) |
require-signed-feed |
Require signature metadata; does not verify authenticity | false |
target-sparkle-version |
Bundled Sparkle version, e.g. 300=2.10.0 |
(none) |
format |
Output format: text or json |
text |
| Output | Description |
|---|---|
valid |
true if no errors |
error-count |
Number of errors |
warning-count |
Number of warnings |
info-count |
Number of info messages |
json |
Full result as JSON |
- name: Validate appcast.xml
run: npx sparkle-validator appcast.xml
# Strict mode (warnings fail the build)
- name: Validate appcast.xml (strict)
run: npx sparkle-validator --strict appcast.xml
# JSON output for further processing
- name: Validate and capture results
run: |
npx sparkle-validator --format json appcast.xml > validation.json
cat validation.json - name: Validate published appcast
uses: dweekly/Sparkle-Validator@v1
with:
file: https://example.com/appcast.xml# .git/hooks/pre-commit
#!/bin/sh
npx sparkle-validator appcast.xml || exit 1import { validate } from 'sparkle-validator';
const xml = `<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
<channel>
<title>My App</title>
<link>https://example.com</link>
<item>
<title>Version 2.0</title>
<pubDate>Thu, 13 Jul 2023 14:30:00 -0700</pubDate>
<sparkle:version>200</sparkle:version>
<description><![CDATA[<p>New features!</p>]]></description>
<enclosure url="https://example.com/app.zip"
length="12345678"
type="application/octet-stream"
sparkle:edSignature="eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eA==" />
</item>
</channel>
</rss>`;
const result = validate(xml);
console.log(result.valid); // true
console.log(result.errorCount); // 0
console.log(result.diagnostics); // Array of diagnosticsinterface ValidationResult {
valid: boolean; // true if no errors
diagnostics: Diagnostic[];
errorCount: number;
warningCount: number;
infoCount: number;
}
interface Diagnostic {
id: string; // e.g. "E008", "W003"
severity: "error" | "warning" | "info";
message: string;
line?: number; // 1-based
column?: number; // 1-based
path?: string; // e.g. "rss > channel > item[2] > enclosure"
fix?: string; // Suggestion for fixing the issue
}| ID | Description |
|---|---|
| E001 | Not well-formed XML |
| E002 | Root element is not <rss> |
| E003 | Missing version="2.0" on <rss> |
| E004 | Missing Sparkle namespace declaration |
| E005 | Missing <channel> inside <rss> |
| E006 | More than one <channel> element |
| E007 | No <item> elements in <channel> |
| E008 | Item missing sparkle:version |
| E009 | Item has neither <enclosure> with url nor <link> |
| E010-E013 | Enclosure missing/invalid attributes |
| E014-E018 | Invalid URLs |
| E019 | Invalid channel name characters |
| E020-E021 | Phased rollout errors |
| E022 | Invalid installationType |
| E023-E025 | Delta update structure errors |
| E027 | URL returns non-2xx status (--check-urls) |
| E028 | Content-Length doesn't match declared length (--check-urls) |
| E029 | Version string is empty or whitespace-only |
| E030 | Invalid sparkle:os value (must be "macos" or "windows") |
| E031 | Invalid Ed25519/DSA enclosure signature (malformed base64 or wrong length) |
| E032 | Enclosure missing required sparkle:edSignature in signed-feed mode |
| E033 | Release note link signature is malformed |
| E034 | Release note link sparkle:length is not a valid non-negative integer |
| E035 | Release note link missing required signature or length in signed-feed mode |
| E036 | Item bundling Sparkle 2.10+ requires minimumSystemVersion >= 12.0 |
| E037 | Unmatched or ambiguous target Sparkle version configuration |
Note on Signature Verification Scope: Signature format validation (
E031,E033,E035) checks base64 encoding integrity, padding, and exact decoded byte sizes (e.g. exactly 64 bytes for Ed25519). Offline validation does not cryptographically authenticate payloads against private/public keys, which requires out-of-band key distribution.
| ID | Description |
|---|---|
| W001-W002 | Missing title on channel/item |
| W003-W004 | Missing or invalid pubDate |
| W006 | DSA-only signature (deprecated, use EdDSA) |
| W007-W008 | Redundant version declarations |
| W009 | No release notes |
| W010 | Non-standard MIME type |
| W011 | Missing enclosure length attribute |
| W012 | Missing enclosure type attribute |
| W013 | minimumSystemVersion is greater than maximumSystemVersion |
| W014 | (Moved to I011) |
| W016 | Unencoded URL characters |
| W017 | informationalUpdate with enclosure |
| W018 | Items not sorted by version (newest first) |
| W019 | Enclosure length is 0 |
| W020 | Duplicate version |
| W021 | URL redirects to different location (--check-urls) |
| W022 | Content-Length header missing (--check-urls) |
| W023 | Local/private URL skipped (--check-urls) |
| W024 | URL uses insecure HTTP instead of HTTPS (--check-urls) |
| W025 | pubDate is in the future |
| W026 | pubDate is implausibly old (before 2001/Mac OS X era) |
| W027 | Version string is non-numeric (may cause comparison failures) |
| W028 | Version decreases while pubDate increases |
| W030 | URL file extension doesn't match expected type |
| W031 | (Moved to I012) |
| W032 | Multiple delta enclosures for same deltaFrom |
| W033 | shortVersionString format unusual (not x.y.z) |
| W034 | criticalUpdate version attribute not valid format |
| W035 | Feed mixes HTTP and HTTPS URLs |
| W036 | hardwareRequirements contains unknown architecture |
| W037 | releaseNotesLink missing xml:lang for localization |
| W038 | CDATA section used in version/signature elements |
| W039 | XML declaration missing encoding attribute |
| W040 | Channel has language but items have different lang |
| W041 | Version missing but deducible from filename (Sparkle fallback) |
| W042 | Sparkle namespace URI differs from canonical |
| W043 | sparkle:os deprecated (prefer separate feeds per platform) |
| W044 | Conflicting version between <sparkle:version> element and enclosure attribute |
| W045 | Invalid minimumSystemVersion format |
| W046 | Invalid maximumSystemVersion format |
| W047 | Version only as enclosure attribute (prefer <sparkle:version> element) |
| W048 | minimumUpdateVersion format invalid or empty |
| W049 | minimumUpdateVersion is greater than item version (unreachable update) |
| W050 | sparkle:length on release notes link is 0 |
| W051 | Release notes link uses unqualified length instead of sparkle:length |
| ID | Description |
|---|---|
| I001 | Summary: N items across M channels |
| I002 | Item contains N delta updates |
| I003 | Item uses phased rollout |
| I004 | Item marked as critical update |
| I005 | Item targets non-macOS platform |
| I006 | Item requires specific hardware (Sparkle 2.9+) |
| I007 | Item requires minimum app version to update (Sparkle 2.9+) |
| I008 | Feed contains >50 items (performance consideration) |
| I009 | Summary of OS support range across all items |
| I010 | Enclosure has no signature (signatures are optional) |
| I011 | Missing channel link (informational) |
| I012 | Delta references version not in feed (old versions may be pruned) |
# Install dependencies
npm install
# Run tests
npm test
# Build
npm run build
# Type check
npm run lintThis package is published with:
- npm provenance — cryptographically attests that the package was built from this repository via GitHub Actions
- SBOM — Software Bill of Materials (CycloneDX format) attached to each GitHub release
You can verify provenance on npm: npm audit signatures
MIT License - see LICENSE for details.
See CONTRIBUTING.md for guidelines.
