Skip to content

Update dependencies and calculate correct integrity hashes for local files#347

Open
pivaldi wants to merge 13 commits intonext-theme:mainfrom
pivaldi:main
Open

Update dependencies and calculate correct integrity hashes for local files#347
pivaldi wants to merge 13 commits intonext-theme:mainfrom
pivaldi:main

Conversation

@pivaldi
Copy link

@pivaldi pivaldi commented Feb 12, 2026

See the commit message of the last commit.
The rest are just version updates of the dependencies.

renovate bot and others added 13 commits January 8, 2026 16:30
This patch fixes the integrity hash mismatch bug when using `plugins: local`
with hexo-theme-next.

## Problem

When using `plugins: local`, the NexT theme was using hardcoded CDN integrity
hashes for local files, causing browser SRI (Subresource Integrity) validation
failures. All vendor assets were blocked, resulting in blank pages.

## Root Cause

The theme's `_vendors.yml` contains integrity hashes for CDN versions of
libraries. When `plugins: local` was enabled, the plugin copied files from
node_modules which had different content than the CDN versions, but the theme
still used the CDN hashes.

## Solution

The plugin now:
- Calculates SHA-256 integrity hashes for all files it copies
- Stores hashes in `localIntegrityMap` object
- Exports `getLocalIntegrity(path)` function for theme integration
- Registers `next_vendor_integrity` Hexo helper for template access
- Adds debug logging for calculated hashes

## Changes

- Add `crypto` module import for hash calculation
- Add `calculateIntegrity()` function to compute SHA-256 hashes in SRI format
- Modify `readFile()` to calculate and store hashes for all copied files
- Convert module.exports to named function `pluginMain` for better extensibility
- Export `getLocalIntegrity()` function to expose hash map to consumers
- Register Hexo helper for template/theme access to hashes
- Add informational logging

## Theme Integration

For complete fix, the theme's `scripts/events/lib/vendors.js` should be
updated to use these local hashes when `plugins === 'local'`:

```javascript
const shouldUseIntegrity = plugins !== 'local';
vendors[key] = {
  url: links[plugins] || links.cdnjs,
  integrity: shouldUseIntegrity ? value.integrity : undefined
};
```

Or to use calculated hashes:

```javascript
let integrityHash = value.integrity;
if (plugins === 'local' && typeof internal === 'function') {
  const localHash = internal.getLocalIntegrity(`lib/${name}/${file}`);
  if (localHash) integrityHash = localHash;
}
```

## Testing

Tested with:
- hexo-theme-next latest version
- Verified integrity hashes match actual file content
- Confirmed no SRI errors in browser when using `plugins: local`
- All vendor assets load correctly

## Breaking Changes

None - maintains full backward compatibility.

## Related Issues

Fixes the "integrity hash bug" that prevented using `plugins: local`.
pivaldi added a commit to pivaldi/hexo-theme-next that referenced this pull request Feb 12, 2026
When `vendors.plugins` is set to `local`, integrity hashes are now
computed from the actual local files via `@next-theme/plugins`
`getLocalIntegrity()` rather than using the hardcoded CDN hashes from
`_vendors.yml`.

This fixes SRI (Subresource Integrity) validation failures that caused
browsers to block all vendor assets when self-hosting, resulting in
blank pages.

CDN mode is unaffected: hardcoded hashes from `_vendors.yml` are still
used when `plugins` is not `local`.

Depends on: next-theme/plugins#347
localIntegrityMap[dist_path] = calculateIntegrity(fileData);
});
} else if (stats.isFile()) {
const fileData = readFileSync(origin);

Check failure

Code scanning / CodeQL

Potential file system race condition High

The file may have changed since it
was checked
.
@stevenjoezhang
Copy link
Member

@pivaldi Thanks for your contribution! Under what circumstances did you encounter the integrity hash mismatch? Do you have a specific example and reproduction steps for testing?
Also, please do not modify other files such as package.json. Due to issues with the CDNJS service, the new versions of these dependencies have not been updated yet. If the dependency versions are changed, users who rely on CDNJS will encounter 404 errors.

@pivaldi
Copy link
Author

pivaldi commented Mar 7, 2026

Hello @stevenjoezhang

Sorry for breaking compatibility with CDNJS.
Here the answer wrote by an AI (quicker to produce for me) to your question.

Circumstances of the Issue

The integrity hash mismatch occurs when using vendors.plugins: local configuration instead of CDN providers. This is a Subresource Integrity (SRI) validation issue.

Root Cause Analysis

In scripts/events/lib/vendors.js (lines 56-68), the code handles integrity hashes differently based on the vendor source:

// For local plugins, use calculated integrity hash from the plugin
// For CDN, use the hardcoded hash from _vendors.yml
let integrityHash = value.integrity;
if (plugins === "local" && typeof internal === "function") {
  const localHash = internal.getLocalIntegrity(`lib/${name}/${file}`);
  if (localHash) {
    integrityHash = localHash;
  }
}

The Problem

  • _vendors.yml contains hardcoded integrity hashes calculated from CDN-served files
  • When using plugins: local, the @next-theme/plugins package dynamically calculates integrity hashes from locally-installed npm packages
  • These two sources can produce different files with different integrity hashes, even for the same version number

Why Files Differ

  1. Build variations: CDN providers may serve files with different compression/optimization than npm packages
  2. Source maps: Local packages may include or exclude source maps differently than CDN versions
  3. Whitespace differences: Build processes can introduce subtle differences in whitespace, comments, or formatting
  4. CDN optimization: Some CDNs automatically optimize/minify files further, changing their hash
  5. Version discrepancies: Even minor patch updates can cause hash mismatches if _vendors.yml isn't updated

Reproduction Steps

Step 1: Configure Local Vendors

Edit _config.next.yml:

vendors:
  internal: local
  plugins: local

Step 2: Install Dependencies

npm install @next-theme/plugins

Step 3: Generate the Site

hexo clean
hexo generate

Step 4: Inspect Generated HTML

Look at the generated HTML files (e.g., public/index.html) for script/link tags with integrity attributes:

<script src="/lib/mermaid/dist/mermaid.min.js"
        integrity="sha256-XXXXX..."
        crossorigin="anonymous"></script>

Step 5: Compare Integrity Hashes

Hash in _vendors.yml (line 138 for mermaid):

mermaid:
  name: mermaid
  version: 11.10.1
  file: dist/mermaid.min.js
  integrity: sha256-BmQmdWDS8X2OTbrwELWK366LV6escyWhHHe0XCTU/Hk=

Hash in generated HTML (calculated by @next-theme/plugins):

  • May differ from the above if the local file differs from the CDN file

Step 6: Browser Console Error

If hashes don't match, the browser will show:

Failed to find a valid digest in the 'integrity' attribute for resource
'https://yourdomain.com/lib/mermaid/dist/mermaid.min.js' with computed
SHA-256 integrity 'YYYYY...'. The resource has been blocked.

This causes the script to fail loading, breaking functionality.

Practical Example

Testing Hash Mismatch

You can verify this manually by comparing file hashes:

# Download CDN version
curl -sL https://cdnjs.cloudflare.com/ajax/libs/mermaid/11.10.1/dist/mermaid.min.js | \
  openssl dgst -sha256 -binary | openssl base64

# Hash local version
openssl dgst -sha256 -binary node_modules/mermaid/dist/mermaid.min.js | openssl base64

If these produce different hashes, you've confirmed the mismatch.

Why Removing Remote CDN Resolves This

By removing remote CDN fallback logic and relying solely on local files with dynamically-calculated integrity hashes:

  1. Consistency: The integrity hash always matches the actual file being served
  2. Accuracy: No mismatch between hardcoded CDN hashes and local file hashes
  3. Reliability: More robust SRI implementation that doesn't break on version updates
  4. Maintainability: No need to manually update _vendors.yml hashes when packages are updated

Impact on Users

This issue affects users who:

  • Use vendors.plugins: local for self-hosting
  • Deploy behind firewalls that block CDNs
  • Want reproducible builds with pinned dependencies
  • Use Subresource Integrity for security
  • Experience intermittent failures when CDN files change

Proposed Solution

The fix removes the dependency on hardcoded CDN integrity hashes by:

  1. Always calculating integrity hashes from actual local files
  2. Removing fallback to hardcoded _vendors.yml hashes when using local plugins
  3. Ensuring SRI attributes match the files actually being served

This makes the integrity checking reliable and automatic, without manual hash maintenance.

Additional Context

Current setup in my environment:

{
  "@next-theme/plugins": "8.27.0 (custom fork)"
}

The custom fork addresses this issue by ensuring integrity hashes are always calculated from the actual files being served, not from hardcoded CDN references.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants