-
Notifications
You must be signed in to change notification settings - Fork 0
Security Hardening: XSS Protection (v1.1.0) #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…nd CI uses build:strict
…/test-runner -> 0.20.2\n- @babel/eslint-parser -> 7.28.5\n- eslint-plugin-import -> 2.32.0\n- esbuild -> 0.25.11\n- overrides: koa@2.16.3, @babel/helpers@^7.28.4\n- refresh lockfile\n\nAll tests pass; build stays under size limit.,
… and node env for scripts; size: add core+total gzip caps
…ort, docs & tests
- Simplified size check execution in `build.mjs` to directly exit with the appropriate code. - Updated `check-size.mjs` to use a consistent return code structure and improved logging for size limits. - Removed redundant function calls and streamlined the size check process for better clarity and maintainability.
- Keep all directives.js changes with getSecurity() integration - Revert all other files to main branch baseline - Create minimal security stub (allows everything by default) - Add comprehensive tests for security integration in directives - Test custom security hooks - Test unsafe mode (security: false and 'unsafe') - Add placeholder skipped tests for default security validation - Delete complex security test files for incremental rebuild - All tests pass: 72 passed, 2 skipped - 100% code coverage maintained - Bundle size: 2680 bytes (under 4096 limit)
- Add comprehensive test suite for security module (15 tests) - Implement attribute blocking by pattern (event handlers) and name (srcdoc) - Implement URL scheme validation with URL constructor - Refactor helper functions to top level with JSDoc - Relative URLs now explicitly use window.location.protocol - All tests pass: 88 passed, 2 skipped - 100% code coverage maintained - Bundle: 3361 bytes gzipped (under 6144 limit)
- Clean up DEFAULT_CONFIG object (comments weren't adding value) - Property names are self-explanatory - Reduces bundle size by 61 bytes (3361 -> 3300 bytes) - All tests still pass
- Test now verifies default security module loads and blocks dangerous attributes - Validates that onclick is blocked while safe attributes like class are allowed - 89 tests passing, 1 skipped (down from 2 skipped) - 100% coverage maintained
- Replace includeBasePath with allowedTemplatePaths array - Remove variable substitution complexity - Default to context.codeBasePath (or / as fallback) - Users can provide custom array for more control - Unskip and implement last processInclude test - Add test for blocking paths outside codeBasePath - All tests pass: 98 passed, 0 skipped - 100% coverage maintained - Bundle: 3456 bytes gzipped (under 6144 limit)
- Remove allowedTemplatePaths config entirely - Default security enforces same-origin for includes (critical boundary) - Users can provide custom allowIncludePath for additional restrictions - Simpler implementation and API - All tests pass: 95 passed, 0 skipped - 100% coverage maintained - Bundle: 3326 bytes gzipped (130 bytes smaller!)
- Document default security features (attribute sanitization, URL validation, same-origin) - Show custom security hooks and configuration options - Move unsafe mode section to bottom with stronger warnings - Add explicit WARNING about user-supplied data in context - Clarify trust boundaries (what is protected vs trusted by design) - Update Getting Started to mention faintly.security.js file - Add security to rendering context list
- Add security module to project purpose - Document dual bundle architecture (core + security) - Update build size limits (4KB core, 6KB total) - Add security module section with development guidelines - Update repo layout to include test/security/ - Clarify that security is enabled by default - Emphasize testing and conservative defaults for security changes
- Move security initialization to renderElement() - happens once per render - Add security check to resolveTemplate() - protects initial template fetch - Export initializeSecurity() for tests that call directives directly - Remove getSecurity() helper - no longer needed - Directives now use context.security directly (always initialized) - Clean separation: renderElement handles init, directives use it - Update all tests to initialize security when calling directives directly - Add test for template security blocking - All tests pass: 94 passed, 0 skipped - 100% coverage maintained - Bundle: 3430 bytes gzipped
src/directives.js
Outdated
| const { updated, updatedText } = await resolveExpressions(el.getAttribute(attrName), context); | ||
| if (updated) el.setAttribute(attrName, updatedText); | ||
| if (updated) { | ||
| if (!context.security.shouldAllowAttribute(attrName, updatedText, context)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor, but can this be a positive check same as in processAttributesDirective
src/directives.js
Outdated
| const allowed = context.security.allowIncludePath(templatePath, context); | ||
| if (!allowed) { | ||
| // eslint-disable-next-line no-console | ||
| console.warn(`Blocked include outside allowed scope: ${new URL(templatePath, window.location.origin).href}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I notice a console warn here, lets be consistent and either always warn on security violations or never
- Change negative check to positive check in processAttributes for consistency - Remove console.warn calls for silent security violations - Security failures now consistently silent (attributes removed, includes blocked) - Still throws error for template fetch blocking (prevents app from continuing)
Overview
Adds built-in XSS protection with attribute sanitization, URL scheme validation, and same-origin enforcement for template includes.
What's New
on*,srcdoc), validates URL schemes, enforces same-originfaintly.js(2736b) +faintly.security.js(646b) = 3382 bytes gzipped (under 6KB limit)security: false, customize with config, or provide custom hooksSecurity Features
Attribute Sanitization
/^on/ipattern (onclick, onerror, etc.)srcdochref,src,action,formaction,xlink:hrefhttp:,https:,mailto:,tel:(relative URLs always allowed)Template Include Protection
resolveTemplate()Documentation
Testing
Architecture
renderElement()(single point, happens once)initializeSecurity()exported for tests calling directives directlycontext.securitydirectly (clean separation)Backward Compatibility
✅ Fully backward compatible - existing code works unchanged
security: falseorsecurity: 'unsafe'to disableBreaking Changes
None. This is a minor version bump (
1.0.1→1.1.0).Bundle Size
Commits