Skip to content

feat: implement Subresource Integrity (SRI) for CDN resources#2963

Merged
crivetimihai merged 6 commits intomainfrom
issue-2558-sri-implementation-v2
Feb 22, 2026
Merged

feat: implement Subresource Integrity (SRI) for CDN resources#2963
crivetimihai merged 6 commits intomainfrom
issue-2558-sri-implementation-v2

Conversation

@suciu-daniel
Copy link
Copy Markdown
Collaborator

Summary

Implements comprehensive Subresource Integrity (SRI) protection for all external CDN resources to prevent tampering and ensure content integrity.

Changes

Infrastructure

  • Pin 15 CDN resources to specific versions (HTMX 1.9.10, Alpine.js 3.14.1, Chart.js 4.4.1, etc.)
  • Generate SHA-384 integrity hashes for all CDN resources
  • Create shared scripts/cdn_resources.py module as single source of truth
  • Implement hash generation script (scripts/generate-sri-hashes.py)
  • Implement hash verification script (scripts/verify-sri-hashes.py)
  • Store hashes in mcpgateway/sri_hashes.json

Templates

  • admin.html: Add SRI to HTMX (using canonical /dist/htmx.min.js URL), Alpine.js, Chart.js, Marked, DOMPurify, CodeMirror
  • login.html: Add SRI to Font Awesome (fixed invalid HTML closing tag)
  • change-password-required.html: Add SRI to Font Awesome
  • Add Tailwind JIT exclusion comments (dynamic content incompatible with SRI)

Code Quality

  • Use @lru_cache(maxsize=1) for efficient hash loading (no global variables)
  • All integrity attributes include crossorigin="anonymous" for CORS compliance

CI/CD

  • Add make sri-generate and make sri-verify targets
  • Integrate hash verification into GitHub Actions pipeline
  • Automated detection of CDN content changes

Documentation

  • Update docs/docs/manage/securing.md with SRI section
  • Update ADR-0014 with SRI integration details
  • Document upgrade workflow for library updates

Testing

All 15 CDN resources verified:

make sri-verify
# ✅ All 15 SRI hashes verified successfully!

Security Impact

  • Prevents CDN tampering attacks
  • Ensures content integrity with cryptographic verification
  • Follows W3C SRI specification (SHA-384)
  • Maintains backward compatibility with airgapped mode

Closes #2558

@Nayana-R-Gowda
Copy link
Copy Markdown
Collaborator

The implementation correctly adds Subresource Integrity (SRI) across all external CDN assets and integrates verification into CI. The approach is clean and production-ready:

Single source of truth via scripts/cdn_resources.py
Automated generation + verification
CI enforcement
Backward compatibility with ui_airgapped
Proper use of crossorigin="anonymous"
Efficient loading via @lru_cache(maxsize=1)

Verification summary:
SRI hashes file at sri_hashes.json contains all 15 CDN resource hashes
Templates correctly render integrity="sha384-..." and crossorigin="anonymous" attributes
Scripts in scripts (cdn_resources.py, generate-sri-hashes.py, verify-sri-hashes.py) work correctly
Makefile targets sri-generate and sri-verify function as expected

The PR implementation is working correctly.

@suciu-daniel suciu-daniel added the MUST P1: Non-negotiable, critical requirements without which the product is non-functional or unsafe label Feb 19, 2026
crivetimihai
crivetimihai previously approved these changes Feb 19, 2026
Copy link
Copy Markdown
Member

@crivetimihai crivetimihai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid security enhancement. SRI for all 15 CDN resources is exactly the right approach.

Strengths:

  • Clean architecture: cdn_resources.py as single source of truth, generate-sri-hashes.py and verify-sri-hashes.py as separate scripts
  • load_sri_hashes() with lru_cache(maxsize=1) — file read once per process
  • CI pipeline includes sri-verify step — catches CDN drift on every build
  • Tailwind JIT compiler excluded with clear comment — correct, SRI doesn't work with dynamic content
  • Version pinning: Alpine.js from @3.x.x@3.14.1, Chart.js from generic URL to @4.4.1 — necessary for SRI
  • SHA-384 per W3C recommendation
  • Airgapped mode unaffected — SRI only added to CDN paths
  • Good test coverage for load_sri_hashes (caching, file not found, invalid JSON, permission error)

Minor observations:

  1. load_sri_hashes catches all exceptions (admin.py:274) — while this prevents startup failures, it also silently masks file system issues. Consider logging at error level instead of warning for PermissionError cases specifically.

  2. HTMX URL changed from https://unpkg.com/htmx.org@1.9.10 to https://unpkg.com/htmx.org@1.9.10/dist/htmx.min.js — this is the canonical path (the old URL redirects). Good fix.

  3. MANIFEST.in update (recursive-include mcpgateway *.json) — needed for sri_hashes.json to be included in package distributions.

  4. Documentation updates in ADR and securing.md are comprehensive and include the update workflow.

LGTM.

@crivetimihai crivetimihai added the security Improves security label Feb 21, 2026
@crivetimihai
Copy link
Copy Markdown
Member

Thanks @suciu-daniel — SRI for CDN resources is an important security hardening measure. Prevents supply chain attacks via tampered CDN assets. LGTM.

suciu-daniel and others added 4 commits February 21, 2026 20:34
Implements comprehensive SRI protection for all external CDN resources
to prevent tampering and ensure content integrity.

Changes:
- Pin 15 CDN resources to specific versions (HTMX 1.9.10, Alpine.js 3.14.1, etc.)
- Generate SHA-384 integrity hashes for all CDN resources
- Add integrity and crossorigin attributes to all CDN script/link tags
- Create shared scripts/cdn_resources.py module for DRY configuration
- Implement hash generation (scripts/generate-sri-hashes.py)
- Implement hash verification (scripts/verify-sri-hashes.py)
- Add CI pipeline verification (make sri-verify)
- Document SRI in security guide and ADR-0014
- Add Tailwind JIT exclusion comments (dynamic content incompatible with SRI)
- Use @lru_cache for efficient hash loading (no global variables)

Templates updated:
- admin.html: HTMX with canonical /dist/htmx.min.js URL
- login.html: Font Awesome with corrected closing tag
- change-password-required.html: Font Awesome

All 15 CDN resources now protected with cryptographic integrity verification.

Closes #2558

Signed-off-by: SuciuDaniel <Daniel.Vasile.Suciu@ibm.com>
Signed-off-by: SuciuDaniel <Daniel.Vasile.Suciu@ibm.com>
- Add 8 test cases covering all code paths in load_sri_hashes()
- Test success case, file not found, invalid JSON, permission errors
- Test lru_cache behavior and integration with admin endpoints
- Test edge cases: empty file, unicode content
- Remove shebang from scripts/cdn_resources.py (library module)

Closes #2558

Signed-off-by: SuciuDaniel <Daniel.Vasile.Suciu@ibm.com>
- Fix docs referencing wrong file (generate-sri-hashes.py -> cdn_resources.py)
- Add *.json to pyproject.toml package-data for wheel builds
- Add sri_hashes context to forgot-password and reset-password endpoints
- Fix HTMX script tag indentation in admin.html
- Use %s logger format instead of f-string in load_sri_hashes
- Remove unused monkeypatch parameter from SRI tests
- Add Tailwind JIT exclusion comments to forgot-password and reset-password

Closes #2558

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
@crivetimihai
Copy link
Copy Markdown
Member

Review Fixes Applied

Rebased onto main (clean, no conflicts) and applied the following fixes:

Bugs Fixed

  1. Docs referenced wrong file — Both ADR-014 and securing.md said "Update URL in scripts/generate-sri-hashes.py" but URLs live in scripts/cdn_resources.py. Fixed.
  2. pyproject.toml missing *.json in package-data — Wheel builds would exclude sri_hashes.json. Added "*.json" to [tool.setuptools.package-data].
  3. forgot-password and reset-password endpoints missing sri_hashes context — Would break if SRI-protected CDN resources are added to those templates later. Added load_sri_hashes() to both.

Style/Consistency Fixes

  1. HTMX <script> attribute indentation — Attributes weren't indented consistently with the rest of the template.
  2. LOGGER.warning(f"...")%s format — Per Python logging best practices (avoids string interpolation when log level is disabled).
  3. Removed unused monkeypatch parameter from all 8 SRI test methods (they use unittest.mock.patch instead).
  4. Added <!-- No SRI: Tailwind JIT --> comment to forgot-password.html and reset-password.html for consistency with other templates.

Verification

  • All 8 SRI tests pass after fixes
  • sri_hashes now passed to all 5 full-page template endpoints consistently

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
The SRI PR added crossorigin="anonymous" to CDN resources (required by
the W3C SRI spec for cross-origin integrity checks). This changed CDN
fetch mode from no-cors to cors. Playwright's set_extra_http_headers
sends the Authorization header on ALL requests including cross-origin
CDN fetches, triggering CORS preflight failures on CDNs that don't
whitelist Authorization — blocking Alpine.js, CodeMirror, and other
scripts from loading.

Fix: use cookie-only auth in _inject_jwt_cookie and
_set_admin_jwt_cookie. The JWT cookie alone is sufficient for
same-origin page navigation and HTMX requests.

Closes #2558

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
@crivetimihai
Copy link
Copy Markdown
Member

Review Round 2: RBAC test fix + Makefile sri-verify

Root cause found for RBAC test failures

The SRI PR added crossorigin="anonymous" to all 15 CDN <script>/<link> tags (required by the W3C SRI spec for cross-origin integrity checks). This changed CDN fetch mode from no-cors to cors.

Playwright's context.set_extra_http_headers({"Authorization": ...}) sends the Authorization header on all requests, including cross-origin CDN fetches. In cors mode, this triggers CORS preflight on CDNs (jsdelivr, cdnjs, unpkg) which don't whitelist Authorization in Access-Control-Allow-Headers — causing all CDN scripts to be blocked. Alpine.js never loads → HTMX table init never fires → #gateways-table-body never appears → 9 RBAC tests fail consistently.

A/B test confirmed: cookie+header = 28 CORS errors, Alpine NOT LOADED; cookie-only = 0 CORS errors, Alpine LOADED.

Fixes in this push

  1. tests/playwright/test_rbac_permissions.py: Removed set_extra_http_headers from _inject_jwt_cookie. Cookie-only auth is sufficient for same-origin navigation and HTMX requests.

  2. tests/playwright/conftest.py: Same fix for _set_admin_jwt_cookie (JWT fallback path).

  3. Makefile: Added sri-verify to SECURITY_TOOLS so make security-all includes SRI hash verification.

Test results after fix

  • RBAC tests: 25 passed, 1 skipped, 0 failed (18s) — previously 9 consistent failures
  • All other Playwright tests: pass (flaky ones pass on retry, unrelated to SRI)
  • Browser console: zero SRI integrity errors, zero CDN load failures

@crivetimihai crivetimihai merged commit 7699ba6 into main Feb 22, 2026
54 checks passed
@crivetimihai crivetimihai deleted the issue-2558-sri-implementation-v2 branch February 22, 2026 00:18
vishu-bh pushed a commit that referenced this pull request Feb 24, 2026
* feat: implement Subresource Integrity (SRI) for CDN resources

Implements comprehensive SRI protection for all external CDN resources
to prevent tampering and ensure content integrity.

Changes:
- Pin 15 CDN resources to specific versions (HTMX 1.9.10, Alpine.js 3.14.1, etc.)
- Generate SHA-384 integrity hashes for all CDN resources
- Add integrity and crossorigin attributes to all CDN script/link tags
- Create shared scripts/cdn_resources.py module for DRY configuration
- Implement hash generation (scripts/generate-sri-hashes.py)
- Implement hash verification (scripts/verify-sri-hashes.py)
- Add CI pipeline verification (make sri-verify)
- Document SRI in security guide and ADR-0014
- Add Tailwind JIT exclusion comments (dynamic content incompatible with SRI)
- Use @lru_cache for efficient hash loading (no global variables)

Templates updated:
- admin.html: HTMX with canonical /dist/htmx.min.js URL
- login.html: Font Awesome with corrected closing tag
- change-password-required.html: Font Awesome

All 15 CDN resources now protected with cryptographic integrity verification.

Closes #2558

Signed-off-by: SuciuDaniel <Daniel.Vasile.Suciu@ibm.com>

* style: add blank line before lru_cache decorator for PEP 8 compliance

Signed-off-by: SuciuDaniel <Daniel.Vasile.Suciu@ibm.com>

* test: add comprehensive test coverage for load_sri_hashes()

- Add 8 test cases covering all code paths in load_sri_hashes()
- Test success case, file not found, invalid JSON, permission errors
- Test lru_cache behavior and integration with admin endpoints
- Test edge cases: empty file, unicode content
- Remove shebang from scripts/cdn_resources.py (library module)

Closes #2558

Signed-off-by: SuciuDaniel <Daniel.Vasile.Suciu@ibm.com>

* fix: review fixes for SRI implementation

- Fix docs referencing wrong file (generate-sri-hashes.py -> cdn_resources.py)
- Add *.json to pyproject.toml package-data for wheel builds
- Add sri_hashes context to forgot-password and reset-password endpoints
- Fix HTMX script tag indentation in admin.html
- Use %s logger format instead of f-string in load_sri_hashes
- Remove unused monkeypatch parameter from SRI tests
- Add Tailwind JIT exclusion comments to forgot-password and reset-password

Closes #2558

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* Makefile update

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

* fix(test): remove Authorization header from cookie injection to fix CORS

The SRI PR added crossorigin="anonymous" to CDN resources (required by
the W3C SRI spec for cross-origin integrity checks). This changed CDN
fetch mode from no-cors to cors. Playwright's set_extra_http_headers
sends the Authorization header on ALL requests including cross-origin
CDN fetches, triggering CORS preflight failures on CDNs that don't
whitelist Authorization — blocking Alpine.js, CodeMirror, and other
scripts from loading.

Fix: use cookie-only auth in _inject_jwt_cookie and
_set_admin_jwt_cookie. The JWT cookie alone is sufficient for
same-origin page navigation and HTMX requests.

Closes #2558

Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>

---------

Signed-off-by: SuciuDaniel <Daniel.Vasile.Suciu@ibm.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

MUST P1: Non-negotiable, critical requirements without which the product is non-functional or unsafe security Improves security

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[EPIC][SECURITY]: Subresource Integrity (SRI) for external CDN resources

3 participants