style_capsule 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 66f1dc7d0d5addb480cfba5b2301305f390e23b8f6d533c69e57d8703fe9bf9d
4
- data.tar.gz: 559cb7b3d87328da61419bf7a8ce1f4014a4425e4476966bfa0b129acc2fca26
3
+ metadata.gz: 7a6c3fda27b85ee3899cbe434dd5230de0d1c9c4ff6c961deefa9b058fd8f6b3
4
+ data.tar.gz: e70f42c7ee1fc234201107862e1c7dfe9f609a9a55333983101aed4d1ec74ff7
5
5
  SHA512:
6
- metadata.gz: cfa2dd98b0465d2d83145b11801e35b7f7a1f1e8d4d26fb18ae8f2c9af35b4d6d4b58a8d4fed3c66d037c45dd8e1a988091b9d8aef28b50e9981c1a03a3f0807
7
- data.tar.gz: 10390d9e96e507b1966a4b31bc4eb490d232eda1034f029a1604d7765d75653c75b1649054aa0170b2b32acba4eca406b5c49922823696e95da3f0da24ce4c40
6
+ metadata.gz: 163cd5d8f4f0a324121a866adc94c48f31438cd560ba2b30b2d8f3721b645fe4a339e89308cb38552a96294fd8b01a834936615617ff6889203003f593c4e716
7
+ data.tar.gz: 39ca2e8c56745ddcbeae553b401e3e1b0a4b92409f0d066f0a50a9309bc3efac5369f38e929e6d04f0df991b8abbbe30723602c7f5458ac152a4bdf67ad4dc3a
data/CHANGELOG.md CHANGED
@@ -1,6 +1,25 @@
1
1
  # CHANGELOG
2
2
 
3
- ## Unreleased
3
+ ## 2.0.3 (2026-11-03)
4
+
5
+ - Leave `@keyframes` / `@font-face` / `@property` steps unscoped under selector patching
6
+ - Hoist non-nestable at-rules outside the CSS nesting wrapper
7
+ - Clear pending request stylesheets when head injection cannot find `</head>`
8
+ - Replace ActiveSupport `blank?` on core CSS and content-type checks
9
+ - Warn once when `:file` cache strategy falls back to `:none` without class-method `component_styles`
10
+ - Fail closed when release confirmation input reaches EOF; accept case-insensitive `y`
11
+ - Require a dated CHANGELOG heading for the release version before `gem build`
12
+ - Run the test workflow on pull requests targeting `main`
13
+
14
+ ## 2.0.2 (2026-09-25)
15
+
16
+ - Fix `HeadInjectionMiddleware` dropping pending stylesheets on bufferable chunked HTML
17
+ - Inject when the Rack body responds to `to_ary`; skip only non-bufferable streaming bodies
18
+ - After rewrite, set `Content-Length` and remove `Transfer-Encoding`
19
+ - Fail closed when CSS would close a style tag before it is written into HTML
20
+ - Escape allowed attributes on the non-Rails stylesheet link fallback
21
+ - Bound the inline stylesheet cache
22
+ - Align documented Ruby and Rails floors with the gemspec
4
23
 
5
24
  ## 2.0.1 (2026-07-03)
6
25
 
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # style_capsule
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/style_capsule.svg?v=2.0.0)](https://badge.fury.io/rb/style_capsule) [![Test Status](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/amkisko/style_capsule.rb/graph/badge.svg?token=2U6NXJOVVM)](https://codecov.io/gh/amkisko/style_capsule.rb)
3
+ [![Gem Version](https://badge.fury.io/rb/style_capsule.svg?v=2.0.3)](https://badge.fury.io/rb/style_capsule) [![Test Status](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml/badge.svg)](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml) [![codecov](https://codecov.io/gh/amkisko/style_capsule.rb/graph/badge.svg?token=2U6NXJOVVM)](https://app.codecov.io/github/amkisko/style_capsule.rb)
4
4
 
5
- CSS scoping extension for Ruby components. Provides attribute-based style encapsulation for Phlex, ViewComponent, and ERB templates to prevent style leakage between components. Works with Rails and can be used standalone in other Ruby frameworks (Sinatra, Hanami, etc.) or plain Ruby scripts. Includes configurable caching strategies for optimal performance.
5
+ CSS scoping for Ruby components. Attribute selectors encapsulate styles for Phlex, ViewComponent, and ERB so rules stay inside the component wrapper. Works with Rails and standalone in other Ruby frameworks (Sinatra, Hanami, and similar) or plain Ruby. Cache strategies: none, time-based, custom proc, and file-based.
6
+
7
+ Design changes go through `rfcs/` (RFC 0001).
6
8
 
7
9
  ## Installation
8
10
 
@@ -16,15 +18,15 @@ Then run `bundle install`.
16
18
 
17
19
  ## Features
18
20
 
19
- - **Attribute-based CSS scoping** (no class name renaming)
20
- - **Phlex, ViewComponent, and ERB support** with automatic Rails integration
21
- - **Per-component-type scope IDs** (shared across instances)
22
- - **CSS Nesting support** (optional, more performant, requires modern browsers)
23
- - **Stylesheet registry** with thread-safe head rendering, namespace support, and compatibility with Propshaft and other asset bundlers
24
- - **Multiple cache strategies**: none, time-based, custom proc, and file-based (HTTP caching)
25
- - **Comprehensive instrumentation** via ActiveSupport::Notifications for monitoring and metrics
26
- - **Fallback directory support** for read-only filesystems (e.g., Docker containers)
27
- - **Security protections**: path traversal protection, input validation, size limits
21
+ - Attribute-based CSS scoping (class names stay as written)
22
+ - Phlex, ViewComponent, and ERB; Rails integration via the railtie
23
+ - Per-component-type scope IDs (shared across instances)
24
+ - Optional CSS nesting (`:nesting`) for browsers that support it
25
+ - Stylesheet registry for head rendering, namespaces, and asset bundlers such as Propshaft
26
+ - Cache strategies: none, time-based, custom proc, and file-based
27
+ - `ActiveSupport::Notifications` events for CSS processing and file writes
28
+ - Fallback output directory when the primary path is not writable
29
+ - Path traversal checks, input validation, and CSS size limits
28
30
 
29
31
  ## Usage
30
32
 
@@ -101,8 +103,8 @@ StyleCapsule supports two CSS scoping strategies:
101
103
  - Better browser support (all modern browsers)
102
104
  - Output: `[data-capsule="abc123"] .section { color: red; }`
103
105
 
104
- 2. **CSS Nesting (optional)**: Wraps entire CSS in `[data-capsule="..."] { ... }`
105
- - More performant (no CSS parsing needed)
106
+ 2. **CSS Nesting (optional)**: Wraps nestable rules in `[data-capsule="..."] { ... }`
107
+ - Skips per-selector rewrite; hoists `@keyframes` / `@font-face` and similar to top level
106
108
  - Requires CSS nesting support (Chrome 112+, Firefox 117+, Safari 16.5+)
107
109
  - Output: `[data-capsule="abc123"] { .section { color: red; } }`
108
110
 
@@ -143,7 +145,7 @@ MyComponent.clear_css_cache
143
145
 
144
146
  ## Stylesheet Registry
145
147
 
146
- For better performance, register styles for head rendering instead of rendering `<style>` tags in the body. Use the unified `style_capsule` method to configure all settings:
148
+ Register styles for head rendering when the layout should emit links or shared style tags instead of body `<style>` tags. Configure with `style_capsule`:
147
149
 
148
150
  ```ruby
149
151
  class MyComponent < ApplicationComponent
@@ -256,7 +258,7 @@ By default, `StyleCapsule::HeadInjectionMiddleware` appends pending request-scop
256
258
  config.style_capsule.head_injection_middleware = false
257
259
  ```
258
260
 
259
- The middleware skips chunked responses (`Transfer-Encoding: chunked`) and does not buffer the body when no pending request-scoped stylesheets remain. For ActionController::Live, SSE, or other streaming HTML, disable it and inject manually with `StyleCapsule::StylesheetRegistry.inject_pending_head_stylesheets` if needed.
261
+ The middleware only rewrites bufferable bodies (Rack bodies that respond to `to_ary`). Chunked HTML with a bufferable body is injected, then returned with `Content-Length` and without `Transfer-Encoding`. Non-bufferable streaming bodies (ActionController::Live, SSE, and similar) are left alone; disable the middleware and inject manually with `StyleCapsule::StylesheetRegistry.inject_pending_head_stylesheets` if needed. The middleware does not buffer when no pending request-scoped stylesheets remain. When it does rewrite, it holds the full 2xx HTML body in memory.
260
262
 
261
263
  ## Caching Strategies
262
264
 
@@ -317,11 +319,11 @@ StyleCapsule::CssFileWriter.configure(
317
319
  filename_pattern: ->(component_class, capsule_id) {
318
320
  "capsule-#{capsule_id}.css"
319
321
  },
320
- fallback_dir: "/tmp/style_capsule" # Optional, defaults to /tmp/style_capsule
322
+ fallback_dir: StyleCapsule::CssFileWriter::FALLBACK_OUTPUT_DIR
321
323
  )
322
324
  ```
323
325
 
324
- **Fallback Directory:** In production environments where the app directory is read-only (e.g., Docker containers), StyleCapsule automatically falls back to writing files to `/tmp/style_capsule` when the default location is not writable. When using the fallback directory, the gem gracefully falls back to inline CSS rendering, keeping the UI fully functional.
326
+ **Fallback Directory:** When the primary output directory is not writable (for example a read-only app tree in a container), writes go under a per-user directory in `Dir.tmpdir`. If that write also fails, rendering uses inline CSS.
325
327
 
326
328
  **Precompilation:**
327
329
 
@@ -336,7 +338,7 @@ Files are automatically built during `bin/rails assets:precompile`.
336
338
 
337
339
  ## Instrumentation
338
340
 
339
- StyleCapsule provides comprehensive instrumentation via `ActiveSupport::Notifications` for monitoring CSS processing and file writing operations. All instrumentation is zero-overhead when no subscribers are present.
341
+ Subscribe to `ActiveSupport::Notifications` for CSS processing and file write events. Metric payloads are built only when a subscriber is present.
340
342
 
341
343
  ### Available Events
342
344
 
@@ -452,9 +454,9 @@ end
452
454
 
453
455
  ## Requirements
454
456
 
455
- - Ruby >= 3.0
456
- - Rails >= 6.0, < 9.0 (optional, for Rails integration)
457
- - ActiveSupport >= 6.0, < 9.0 (optional, for Rails integration)
457
+ - Ruby >= 3.4
458
+ - Rails >= 7.0, < 9.0 (optional, for Rails integration)
459
+ - ActiveSupport >= 7.0, < 9.0 (optional, for Rails integration)
458
460
 
459
461
  **Note**: The gem can be used without Rails! See [Non-Rails Support](#non-rails-support) below.
460
462
 
@@ -467,9 +469,9 @@ For setup examples, API notes, and the relationship between `style_capsule` and
467
469
  ## How It Works
468
470
 
469
471
  1. **Scope ID Generation**: Each component class gets a unique scope ID based on its class name (shared across all instances)
470
- 2. **CSS Rewriting**: CSS selectors are rewritten to include `[data-capsule="..."]` attribute selectors
471
- 3. **HTML Wrapping**: Component content is automatically wrapped in a scoped element
472
- 4. **No Class Renaming**: Class names remain unchanged (unlike Shadow DOM)
472
+ 2. **CSS Rewriting**: CSS selectors gain `[data-capsule="..."]` attribute selectors (or a nesting wrapper)
473
+ 3. **HTML Wrapping**: Component content is wrapped in a scoped element
474
+ 4. **Class names**: Class names in CSS and markup stay as written
473
475
 
474
476
  ## Development
475
477
 
@@ -478,11 +480,11 @@ bundle install
478
480
  bundle exec appraisal install
479
481
 
480
482
  # Run tests
481
- bundle exec rspec
483
+ bundle exec polyrun parallel-rspec --workers 5 --merge-failures
482
484
 
483
485
  # Run tests for all Rails versions
484
- bundle exec appraisal rails72 rspec
485
- bundle exec appraisal rails8ruby34 rspec
486
+ bundle exec appraisal rails72 polyrun parallel-rspec --workers 5 --merge-failures
487
+ bundle exec appraisal rails8ruby34 polyrun parallel-rspec --workers 5 --merge-failures
486
488
 
487
489
  # Linting
488
490
  bundle exec standardrb --fix
@@ -513,12 +515,25 @@ gem push style_capsule-*.gem
513
515
 
514
516
  StyleCapsule includes security protections:
515
517
  - Path traversal protection
516
- - Input validation
518
+ - Capsule id and HTML attribute validation and escaping
517
519
  - Size limits (1MB per component)
518
- - XSS prevention via Rails' HTML escaping
520
+ - Reject CSS that would close a style tag
519
521
 
520
522
  For detailed security information, see [SECURITY.md](SECURITY.md).
521
523
 
524
+ ## Links
525
+
526
+ - [GitHub](https://github.com/amkisko/style_capsule.rb)
527
+ - [GitLab](https://gitlab.com/amkisko/style_capsule.rb)
528
+ - [RubyGems](https://rubygems.org/gems/style_capsule)
529
+ - [Versions Atom](https://rubygems.org/gems/style_capsule/versions.atom) (feed id `9946895197223`)
530
+ - [libraries.io](https://libraries.io/rubygems/style_capsule)
531
+ - [Deps.dev](https://deps.dev/rubygems/style_capsule)
532
+ - [SonarCloud](https://sonarcloud.io/project/overview?id=amkisko_style_capsule.rb)
533
+ - [Snyk](https://snyk.io/test/github/amkisko/style_capsule.rb)
534
+ - [Codecov](https://app.codecov.io/github/amkisko/style_capsule.rb)
535
+ - [OpenSSF Scorecard](https://scorecard.dev/viewer/?uri=github.com/amkisko/style_capsule.rb)
536
+
522
537
  ## License
523
538
 
524
539
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/SECURITY.md CHANGED
@@ -2,27 +2,24 @@
2
2
 
3
3
  ## Reporting a Vulnerability
4
4
 
5
- **Do NOT** open a public GitHub issue for security vulnerabilities.
5
+ Email security details to: security@kiskolabs.com
6
6
 
7
- Email security details to: **security@kiskolabs.com**
8
-
9
- Include: description, steps to reproduce, potential impact, and suggested fix (if available).
7
+ Send the report only to that address. Include description, steps to reproduce, potential impact, and a suggested fix when you have one.
10
8
 
11
9
  ### Response Timeline
12
10
 
13
- - We will acknowledge receipt of your report
14
- - We will provide an initial assessment
15
- - We will keep you informed of our progress and resolution timeline
11
+ - We will acknowledge receipt of your report.
12
+ - We will provide an initial assessment.
13
+ - We will keep you informed of our progress and resolution timeline.
16
14
 
17
15
  ### Disclosure Policy
18
16
 
19
- - We will work with you to understand and resolve the issue
20
- - We will credit you for the discovery (unless you prefer to remain anonymous)
21
- - We will publish a security advisory after the vulnerability is patched
22
- - We will coordinate public disclosure with you
17
+ - We will work with you to understand and resolve the issue.
18
+ - We will credit you for the discovery unless you prefer to remain anonymous.
19
+ - We will publish a security advisory after the vulnerability is patched.
20
+ - We will coordinate public disclosure with you.
23
21
 
24
22
  ## Automation Security
25
23
 
26
- * **Context Isolation:** It is strictly forbidden to include production credentials, API keys, or Personally Identifiable Information (PII) in prompts sent to third-party LLMs or automation services.
27
-
28
- * **Supply Chain:** All automated dependencies must be verified.
24
+ - Keep production credentials, API keys, and personal data out of prompts sent to third-party models or automation services.
25
+ - Verify automated dependencies.
@@ -0,0 +1,33 @@
1
+ # Audit fixes for style_capsule 2.0.1
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
7
+ ## Decisions
8
+
9
+ Recorded 2026-09-04. Implements the ranked findings from docs/issues/20260904174500_engineering-and-dependency-audit.md. Shipped in product CHANGELOG 2.0.2. json lockfile bump is recorded in docs/dependencies/20260904174500_json-cve-2026-71847.md.
10
+
11
+ Treat stored CSS as untrusted at the style-tag boundary. CssProcessor.reject_style_element_breakout! matches a case-insensitive style closer and raises ArgumentError. Call it after the size check in scope_selectors and scope_with_nesting, and again in render_inline_stylesheet. Empty CSS still returns early without that check.
12
+
13
+ Allowlist non-Rails fallback link attributes. Emit only media, title, crossorigin, integrity, nonce, type, id, class, and data-* names. Escape values with CGI.escapeHTML. Never emit option href, rel, or event handlers. Keep href as /assets/#{validated_path}.css.
14
+
15
+ Cap the inline CSS cache at 256 entries with Hash insertion-order shift. Mutex around cache mutations uses unlocked cleanup internals so cached_inline does not deadlock if a cache proc re-enters. Mutex also wraps register_eager writes and clear_manifest. The process-wide manifest is not capped.
16
+
17
+ CssFileWriter fallback directory is File.join(Dir.tmpdir, "style_capsule-#{Process.uid}"). Specs compare the FALLBACK_OUTPUT_DIR constant.
18
+
19
+ Add pull_request to .github/workflows/test.yml. Drop npm and docker from Dependabot. Do not add fake gemfiles Dependabot directories. Remove unused memory_profiler from the gemspec. Do not split stylesheet_registry.rb in this pass.
20
+
21
+ ## Effects
22
+
23
+ Specs added for style-tag breakout in CssProcessor and render_inline_stylesheet, quote and href and onload options on the non-Rails link fallback, and inline cache eviction of the oldest key. bundle exec rspec: 498 examples, 0 failures. bundle-audit check --no-update is clean on all four lockfiles with json 2.21.2.
24
+
25
+ README Requirements match the gemspec (Ruby >= 3.4, Rails >= 7.0, < 9.0). Badge uses v=2.0.1. Security section names style-tag closer rejection and capsule id and HTML attribute validation. Middleware docs state that rewrite buffers the full 2xx HTML body.
26
+
27
+ ## Next
28
+
29
+ stylesheet_registry.rb remains over the house line-count ceiling. Split only if a later change owns that file.
30
+
31
+ ## Source
32
+
33
+ Parent issue docs/issues/20260904174500_engineering-and-dependency-audit.md. json note docs/dependencies/20260904174500_json-cve-2026-71847.md. Branch patch/audit-fixes. Release 2.0.2.
@@ -0,0 +1,25 @@
1
+ # HeadInjectionMiddleware injects bufferable chunked HTML
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
7
+ ## Decisions
8
+
9
+ HeadInjectionMiddleware no longer skips every response with Transfer-Encoding chunked. Injection runs when the Rack body responds to to_ary. Non-bufferable streaming bodies are still passed through without calling each. After a successful rewrite, Transfer-Encoding is removed and Content-Length is set to the injected body size.
10
+
11
+ ## Effects
12
+
13
+ Pending request-scoped stylesheet tags reach head on bufferable chunked HTML. True streaming responses without to_ary stay untouched. README late-head section matches that contract. Specs cover bufferable chunked inject and non-bufferable pass-through.
14
+
15
+ ## Next
16
+
17
+ Shipped in 2.0.2. Confirm consumers that relied on the old skip still expect streaming bodies without to_ary.
18
+
19
+ ## Source
20
+
21
+ lib/style_capsule/head_injection_middleware.rb
22
+ spec/style_capsule/head_injection_middleware_spec.rb
23
+ CHANGELOG.md 2.0.2
24
+ README.md Late head injection
25
+ Release 2.0.2.
@@ -0,0 +1,19 @@
1
+ ## Participants
2
+
3
+ Andrei Makarov (@amkisko)
4
+
5
+ ## Decisions
6
+
7
+ Sync appraisal lockfiles to style_capsule 2.0.2 after the release bump left gemfiles/*.gemfile.lock on 2.0.1. Change usr/bin/release.rb to run appraisal install so path-gem version changes update those locks before the next push.
8
+
9
+ ## Effects
10
+
11
+ test #37 on main (c41eb95) failed: all three matrix jobs exited bundle with code 16 under frozen install because the gemspec version no longer matched the appraisal locks. quality used Gemfile.lock (already 2.0.2) and passed. After updating the three locks, BUNDLE_FROZEN=true bundle check succeeds for each appraisal gemfile. BUNDLE_GEMFILE=gemfiles/rails8ruby34.gemfile bundle exec polyrun parallel-rspec --workers 5 --merge-failures exited 0.
12
+
13
+ ## Next
14
+
15
+ Push patch/sync-appraisal-locks-2-0-2 and confirm the test workflow on main after merge.
16
+
17
+ ## Source
18
+
19
+ https://github.com/amkisko/style_capsule.rb/actions/runs/36104324632
@@ -0,0 +1,37 @@
1
+ # json 2.21.1 CVE-2026-71847 in development lockfiles
2
+
3
+ Recorded 2026-09-04 during an engineering and dependency audit of style_capsule 2.0.1. Not a drive-by hunt. bundler-audit reported the advisory on every lockfile this repository audits in CI.
4
+
5
+ ## Dependency
6
+
7
+ Name: json. Locked version: 2.21.1. Constraint in this repo: none in the gemspec. The published gem has no runtime gems. Gemfile.lock shows json as a dependency of activesupport 8.1.3.1 and of rubocop 1.88.2 via json (~> 2.3). The same 2.21.1 pin appears in gemfiles/rails72.gemfile.lock, gemfiles/rails8ruby34.gemfile.lock, and gemfiles/rails8ruby4.gemfile.lock.
8
+
9
+ ## Symptom
10
+
11
+ bundle-audit check fails. Title: JSON::ResumableParser#partial_value use-after-free crash on truncated duplicate-key streams. CVE-2026-71847. GHSA-9hj4-r449-hfvc. CVSS 8.7 availability. Affected 2.20.0 through 2.21.1. Solution: >= 2.21.2. StyleCapsule source does not parse JSON. A host that only adds this gem does not get json from this gemspec. Developers and CI that install this lockfile do.
12
+
13
+ ## Evidence
14
+
15
+ Commands:
16
+
17
+ bundle-audit update then bundle-audit check --no-update on Gemfile.lock.
18
+
19
+ bundle-audit check --no-update --gemfile-lock gemfiles/rails72.gemfile.lock
20
+
21
+ bundle-audit check --no-update --gemfile-lock gemfiles/rails8ruby34.gemfile.lock
22
+
23
+ bundle-audit check --no-update --gemfile-lock gemfiles/rails8ruby4.gemfile.lock
24
+
25
+ Observed: advisory database updated 2026-09-04, commit 478717d. All four locks named json 2.21.1 and the CVE above. bundle outdated --strict listed json 2.21.1 with newest 2.21.2. Rubygems versions API: 2.21.1 created 2026-07-13, 2.21.2 created 2026-07-31. GitHub advisory GHSA-9hj4-r449-hfvc. .github/workflows/dependency-audit.yml runs bundle-audit on every Gemfile.lock on push, pull_request, and weekly cron.
26
+
27
+ ## Suggested fix
28
+
29
+ Upgrade. bundle update json in the root Gemfile and in each appraisal gemfile until lockfiles resolve json >= 2.21.2. Do not add json as a direct dependency only to pin it unless Bundler cannot reach 2.21.2 through activesupport and rubocop. No patch or vendor is needed. An upstream report is already published.
30
+
31
+ ## Next
32
+
33
+ Lockfiles now pin json 2.21.2. bundle-audit check --no-update is clean on Gemfile.lock, gemfiles/rails72.gemfile.lock, gemfiles/rails8ruby34.gemfile.lock, and gemfiles/rails8ruby4.gemfile.lock. Keep the published gemspec free of json unless a later feature parses JSON on the hot path.
34
+
35
+ ## Source
36
+
37
+ ruby-advisory-db via bundler-audit, commit 478717d. GHSA-9hj4-r449-hfvc. CVE-2026-71847. Rubygems json 2.21.2. Gemfile.lock activesupport and rubocop entries. .github/workflows/dependency-audit.yml. Parent issue usr/docs/issues/20260904174500_engineering-and-dependency-audit.md.
@@ -0,0 +1,51 @@
1
+ # Engineering and dependency audit of style_capsule 2.0.1
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
7
+ ## Decisions
8
+
9
+ Recorded 2026-09-04. Library gem. No product UI, database, queue, or worker. Pipeline is CSS and HTML in, processor and helpers, class and thread caches, optional file writer, stylesheet registry, optional Rack head injection, HTML out.
10
+
11
+ This pass is an audit only. No code change, no lockfile bump, no workflow edit. Rankings follow engineering-audit order: danger, then certainty, then impact, then fix cost. Dependency-audit ran because the request named both. The published gemspec has no runtime gems. json 2.21.1 is a development lockfile pin from activesupport and rubocop, not a consumer runtime of this gem.
12
+
13
+ ## Effects
14
+
15
+ Test workflow .github/workflows/test.yml runs on workflow_dispatch and push to main only. Lint and appraisal rspec do not run on pull_request. dependency-audit.yml does run on pull_request and would fail today on json 2.21.1 in Gemfile.lock and gemfiles/*.gemfile.lock.
16
+
17
+ Inline CSS is interpolated into style tags and marked html_safe. Helpers use raw(scoped_css). README documents Theme#scoped_css from stored CSS and shows raw in ERB. README Security section claims XSS prevention via Rails HTML escaping. Capsule id is validated. CSS body is not sanitized for a style-tag break. Specs cover attribute escaping in StandaloneHelper, not a closing style tag in CSS.
18
+
19
+ Non-Rails file stylesheet fallback interpolates option keys and values into a link tag. AssetPath rejects path traversal, quotes, angle brackets, and overlong logical paths. It does not reject a javascript: href scheme or quote characters in option values.
20
+
21
+ StylesheetRegistry @inline_cache and @manifest are process-wide hashes without a mutex. Component CSS cache and helper scope cache cap at 256. File path cache caps at 512 with a mutex. Inline cache only drops entries that have expires_at. Registrations with type proc or no TTL grow for the process lifetime. README claims thread-safe head rendering.
22
+
23
+ HeadInjectionMiddleware buffers a 2xx HTML body to inject before the first closing head tag. Chunked transfer is skipped. Live and SSE caveats are documented. Impact of the buffer under large HTML is inference. No RSS or timing bench this session.
24
+
25
+ CssFileWriter falls back to /tmp/style_capsule, writes CSS, then returns nil so the registry can fall back to inline. Shared-host /tmp collision and symlink follow are inference. Fallback specs exist.
26
+
27
+ README Requirements still say Ruby >= 3.0 and Rails >= 6.0. Gemspec required_ruby_version is >= 3.4. Development Rails gems are >= 7.0, < 9.0. Appraisals cover Rails 7.2 and 8.1 on Ruby 3.4 and 4.0. README badge still uses v=2.0.0. Version constant is 2.0.1.
28
+
29
+ lib/style_capsule/stylesheet_registry.rb is 827 lines. House guidance prefers around 150 with a 300 ceiling. AssetPath and HeadInjectionMiddleware are already split out.
30
+
31
+ CssProcessor walks CSS with regex up to 1MB. A comment claims MAX_CSS_SIZE mitigates ReDoS. ReDoS was not proven. Spike was not run.
32
+
33
+ Dependabot lists npm and docker at directory / with no package.json and no Dockerfile. Bundler updates only directory /. Appraisal lockfiles under gemfiles/ are not a separate Dependabot directory.
34
+
35
+ memory_profiler is a development gemspec dependency. No spec or lib caller was found this session.
36
+
37
+ bundle-audit check --no-update on Gemfile.lock, gemfiles/rails72.gemfile.lock, gemfiles/rails8ruby34.gemfile.lock, and gemfiles/rails8ruby4.gemfile.lock all reported json 2.21.1, CVE-2026-71847, GHSA-9hj4-r449-hfvc, fix >= 2.21.2. Advisory database updated 2026-09-04, commit 478717d. Evidence is in usr/docs/dependencies/20260904174500_json-cve-2026-71847.md.
38
+
39
+ bundle outdated --strict listed erb 6.0.6 to 6.0.7, io-console 0.8.2 to 0.9.2, json 2.21.1 to 2.21.2, polyrun 2.2.2 to 2.2.4, psych 5.4.0 to 5.5.0, rack 3.2.6 to 3.2.7, reline 0.6.3 to 0.7.0, view_component 4.12.0 to 4.15.0, webmock 3.26.2 to 3.26.4, zeitwerk 2.8.2 to 2.8.3. Release-date lag for those three named packages versus latest: json 18 days, polyrun 41 days, view_component 82 days. Full lockfile libyears versus every gem latest date was not computed. rspec was not run.
40
+
41
+ Modes skipped with reason: product-surface, this gem has no own UI. Privacy, the gem does not collect personal data; host CSS may appear in instrumentation payloads when a subscriber exists. Observability, no product runtime or SLO of its own. Learned systems, none. Resource cheaper-or-greener without a bench stays inference. Identity claims without HAR or log fixtures stay inference.
42
+
43
+ A later pass on 2026-09-04 implemented the ranked fixes on branch patch/audit-fixes. See usr/docs/changelogs/20260904180000_audit-fixes.md. bundle exec rspec: 498 examples, 0 failures. bundle exec rubocop and bundle exec rbs validate ran after the cop fix in CssProcessor.reject_style_element_breakout!. This file remains the audit record.
44
+
45
+ ## Next
46
+
47
+ stylesheet_registry.rb remains over the house line-count ceiling. Split only if a later change owns that file. json lockfile bump is tracked in usr/docs/dependencies/20260904174500_json-cve-2026-71847.md.
48
+
49
+ ## Source
50
+
51
+ engineering-audit and dependency-audit skills in .agents/skills. README Requirements and Security. style_capsule.gemspec. lib/style_capsule/version.rb. lib/style_capsule/stylesheet_registry.rb. lib/style_capsule/helper.rb. lib/style_capsule/standalone_helper.rb. lib/style_capsule/css_file_writer.rb. lib/style_capsule/head_injection_middleware.rb. .github/workflows/test.yml. .github/workflows/dependency-audit.yml. .github/dependabot.yml. Gemfile.lock and gemfiles/*.gemfile.lock. bundle-audit 0.9.x against ruby-advisory-db commit 478717d. bundle outdated --strict. ruby -Ilib require of StyleCapsule::ClassRegistry. Rubygems version API dates for json, polyrun, and view_component. Downstream canvas is outside this repo.
@@ -0,0 +1,28 @@
1
+ CI quality failed on a trailing blank line in spec/support/junit_formatter.rb. Test CI also ran bundler-audit or osv-scanner, which fail whenever an advisory lands even when a separate dependency audit already exists.
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
7
+ ## Decisions
8
+
9
+ - Drop bundler-audit and security jobs from test.yml so tests no longer wait on advisory freshness.
10
+ - Keep dependency-audit.yml as workflow_dispatch only. Do not run it on push, pull request, or schedule.
11
+ - Disable osv-scanner in Trunk Check.
12
+ - Disable markdownlint MD013 in .markdownlint.yaml. Keep AGENTS.md in Trunk so other markdownlint rules still run.
13
+ - Keep lockfile and spec fixes that were already blocking quality or tests.
14
+
15
+ ## Effects
16
+
17
+ - Test CI runs lint and specs without an advisory gate.
18
+ - Trunk Check no longer treats dependency CVEs as a required check.
19
+ - Manual dependency-audit remains available on workflow_dispatch.
20
+
21
+ ## Next
22
+
23
+ - Run dependency audit on demand when a release or a known advisory needs it.
24
+ - Do not reintroduce advisory scanners into test.yml.
25
+
26
+ ## Source
27
+
28
+ - GitHub Actions test and Trunk Check failures on 2026-09-17
@@ -0,0 +1,40 @@
1
+ # Engineering and prose audit before 2.0.3 publish
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
7
+ ## Decisions
8
+
9
+ Re-audit main for the unpublished 2.0.3 cut after the 2026-10-28 release-tooling pass. Include CssProcessor keyframes and nesting hoist, pending head clear when `</head>` is missing, ActiveSupport-free blank checks, `:file` cache fallback warning, release confirmation and changelog gates, pull_request CI, and ASCII docs punctuation. Leave tag and RubyGems publish to `make release` after commit.
10
+
11
+ ## Effects
12
+
13
+ Pipeline: component/helper render -> CssProcessor -> StylesheetRegistry / CssFileWriter -> HeadInjectionMiddleware -> HTML. Boundary mode covered Rack body buffering and filesystem writes. Resource and identification modes: no new person identifiers in gem logs; cold-require numbers stay session observations. Queue, worker, learned-system, and physical plant modes skipped (absent).
14
+
15
+ Ranked fixes applied:
16
+
17
+ 1. Skip selector patching inside `@keyframes` / `@font-face` / `@property` (high confidence, observed; prior keyframes example was futile).
18
+ 2. Hoist non-nestable at-rules outside the nesting wrapper (high confidence, observed).
19
+ 3. Clear pending request stylesheets when inject cannot find `</head>` or tags are empty (high confidence, observed).
20
+ 4. Replace `blank?` with nil/empty checks in CssProcessor and HeadInjectionMiddleware (high confidence, observed).
21
+ 5. Warn once when `:file` falls back to `:none` without class-method `component_styles` (high confidence, observed).
22
+ 6. Changelog heading and confirmation specs; CHANGELOG dated 2026-11-03; prose pass on README, gemspec, docs/non_rails_support.md, and builder skip warn punctuation.
23
+
24
+ Deferred:
25
+
26
+ - Class-level CSS cache mutex under threaded hosts.
27
+ - Deduping Phlex / ViewComponent / helper render paths and stylesheet_registry split.
28
+ - `component_styles` double evaluation per render.
29
+ - CssFileWriter positive-path cache revalidation after external delete.
30
+ - Optional max-bytes skip for middleware buffering.
31
+
32
+ ## Next
33
+
34
+ Commit, then `make release`.
35
+
36
+ ## Source
37
+
38
+ - docs/reviews/20261103150931_engineering-prose-release-audit.md
39
+ - CHANGELOG.md section 2.0.3 (2026-11-03)
40
+ - Prior pass: docs/reviews/20261028_engineering-audit-release-preparation.md
@@ -81,5 +81,5 @@ stylesheets = StyleCapsule::StylesheetRegistry.request_inline_stylesheets
81
81
 
82
82
  ## Configuration entry points
83
83
 
84
- - **`style_capsule`** — Preferred class-level DSL for namespace, cache strategy, CSS scoping (`:selector_patching` or `:nesting`), head rendering, and wrapper `tag:`.
85
- - **`stylesheet_registry`** — Older alias that enables head rendering and cache options; new code should prefer `style_capsule` for a single configuration surface.
84
+ - `style_capsule` - Class-level DSL for namespace, cache strategy, CSS scoping (`:selector_patching` or `:nesting`), head rendering, and wrapper `tag:`.
85
+ - `stylesheet_registry` - Older alias for head rendering and cache options; use `style_capsule` in new code.
@@ -0,0 +1,25 @@
1
+ # Engineering audit and release preparation
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
7
+ ## Decisions
8
+
9
+ - Treat end-of-input at the release confirmation prompt as rejection.
10
+ - Keep the ViewComponent loading-error test isolated from unrelated class ancestry checks.
11
+ - Run tests for pull requests targeting `main`.
12
+
13
+ ## Effects
14
+
15
+ - `make test`, RBS validation, RuboCop, and the sharded suite pass.
16
+ - The 2.0.3 gem builds and passes `Gem::Package#verify`.
17
+ - A local cold require used 0.398 CPU seconds and 33,308,672 bytes peak RSS. These are single-machine observations, not release budgets.
18
+
19
+ ## Next
20
+
21
+ - Review the worktree, commit it, and let the release workflow create the tag and publish the gem.
22
+
23
+ ## Source
24
+
25
+ Iterative engineering audit on 2026-10-28.
@@ -0,0 +1,42 @@
1
+ # Engineering and prose audit before 2.0.3 publish
2
+
3
+ ## Participants
4
+
5
+ Andrei Makarov (@amkisko)
6
+
7
+ ## Decisions
8
+
9
+ Re-audit main for the unpublished 2.0.3 cut. Include member CssProcessor keyframes and nesting, pending head clear, blank checks, file-cache warn, release gates, pull_request CI, and ASCII docs punctuation. Leave tag and RubyGems publish to make release after commit.
10
+
11
+ ## Effects
12
+
13
+ Pipeline: render -> CssProcessor -> registry / file writer -> head inject -> HTML. Boundary mode covered Rack buffering and disk writes. Resource and identification modes: no new person identifiers in gem logs. Queue, worker, learned-system, and physical plant modes skipped (absent).
14
+
15
+ Ranked fixes applied:
16
+
17
+ 1. Unscoped `@keyframes` / `@font-face` / `@property` steps under selector patching (high confidence, observed).
18
+ 2. Hoist non-nestable at-rules outside nesting wrapper (high confidence, observed).
19
+ 3. Clear pending request stylesheets when `</head>` is missing (high confidence, observed).
20
+ 4. Nil/empty checks instead of ActiveSupport `blank?` on core paths (high confidence, observed).
21
+ 5. Warn once on illegal `:file` cache fallback (high confidence, observed).
22
+ 6. Changelog heading and confirmation specs; CHANGELOG dated 2026-11-03.
23
+ 7. ASCII and marketing-tone pass on README, CHANGELOG, gemspec, and docs/non_rails_support.md.
24
+
25
+ Deferred (documented in the issue): class CSS cache mutex, helper/component dedupe, double `component_styles` eval, file-path cache revalidation, middleware max-bytes.
26
+
27
+ Checks observed in this session:
28
+
29
+ - bundle exec rubocop on touched Ruby files: 11 files, no offenses.
30
+ - bundle exec rbs validate: ok.
31
+ - bundle exec rspec on css_processor, stylesheet_registry, head_injection_middleware, component, view_component, release_version_check: 249 examples, 0 failures.
32
+ - bundle exec polyrun parallel-rspec --workers 5 --merge-failures: exit 0; coverage/merged-summary.txt 97.00% lines (1101 / 1135) across 19 files.
33
+ - gem build style_capsule.gemspec and Gem::Package#verify for 2.0.3: ok.
34
+
35
+ ## Next
36
+
37
+ Commit, then make release.
38
+
39
+ ## Source
40
+
41
+ - docs/issues/20261103150931_engineering-prose-release-audit.md
42
+ - CHANGELOG.md section 2.0.3 (2026-11-03)
@@ -168,7 +168,7 @@ module StyleCapsule
168
168
 
169
169
  # File caching is only allowed for class method component_styles
170
170
  if cache_strategy == :file && !file_caching_allowed?
171
- # Fall back to :none strategy if file caching requested but not allowed
171
+ warn_file_cache_fallback(self.class)
172
172
  cache_strategy = :none
173
173
  cache_ttl = nil
174
174
  cache_proc = nil
@@ -113,7 +113,7 @@ module StyleCapsule
113
113
  file_path
114
114
  rescue ArgumentError, NoMethodError => e
115
115
  # Component requires arguments or has dependencies - skip it (common for ViewComponent with required kwargs)
116
- warn "[style_capsule] Skipped #{component_class.name} in style_capsule:build — #{e.class}: #{e.message}"
116
+ warn "[style_capsule] Skipped #{component_class.name} in style_capsule:build - #{e.class}: #{e.message}"
117
117
  output_proc&.call("Skipped #{component_class.name}: #{e.message}")
118
118
  nil
119
119
  end
@@ -48,6 +48,16 @@ module StyleCapsule
48
48
 
49
49
  private
50
50
 
51
+ def warn_file_cache_fallback(component_class)
52
+ return if component_class.instance_variable_get(:@_style_capsule_file_cache_warned)
53
+
54
+ component_class.instance_variable_set(:@_style_capsule_file_cache_warned, true)
55
+ label = component_class.name
56
+ label = component_class.inspect if label.nil? || label.empty?
57
+ warn "[StyleCapsule] :file cache strategy requires class-method component_styles; " \
58
+ "falling back to :none for #{label}"
59
+ end
60
+
51
61
  # Check if component defines instance method styles
52
62
  #
53
63
  # @return [Boolean]