style_capsule 2.0.1 → 2.0.2

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: afc951f02fe29cd319b521b363049d3dba5e607099216e1782f48b4578f06f4b
4
+ data.tar.gz: 85916b390e95a3109da5a481f6b193a44309864499b5d3cb4ec4a634960c0ccd
5
5
  SHA512:
6
- metadata.gz: cfa2dd98b0465d2d83145b11801e35b7f7a1f1e8d4d26fb18ae8f2c9af35b4d6d4b58a8d4fed3c66d037c45dd8e1a988091b9d8aef28b50e9981c1a03a3f0807
7
- data.tar.gz: 10390d9e96e507b1966a4b31bc4eb490d232eda1034f029a1604d7765d75653c75b1649054aa0170b2b32acba4eca406b5c49922823696e95da3f0da24ce4c40
6
+ metadata.gz: bf300d946cf83d68e40a16862698bd8354f8c51dd927e2fb4e6cb8fd9e3f1d6c6298bb545233132399ea496985e1a1b45b9279bae7b9467663a525ef4893f530
7
+ data.tar.gz: 95450d3eeef09959b75dca145f06abfe9f5e59619044d3456e43a1c073adac4e51e1c94e59187ddee72ba823de54c4cc640754d80373fdf1e18316e4b7046bb9
data/CHANGELOG.md CHANGED
@@ -1,6 +1,14 @@
1
1
  # CHANGELOG
2
2
 
3
- ## Unreleased
3
+ ## 2.0.2 (2026-09-25)
4
+
5
+ - Fix `HeadInjectionMiddleware` dropping pending stylesheets on bufferable chunked HTML
6
+ - Inject when the Rack body responds to `to_ary`; skip only non-bufferable streaming bodies
7
+ - After rewrite, set `Content-Length` and remove `Transfer-Encoding`
8
+ - Fail closed when CSS would close a style tag before it is written into HTML
9
+ - Escape allowed attributes on the non-Rails stylesheet link fallback
10
+ - Bound the inline stylesheet cache
11
+ - Align documented Ruby and Rails floors with the gemspec
4
12
 
5
13
  ## 2.0.1 (2026-07-03)
6
14
 
data/README.md CHANGED
@@ -1,9 +1,11 @@
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.2)](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
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.
6
6
 
7
+ Design changes go through `rfcs/` (RFC 0001).
8
+
7
9
  ## Installation
8
10
 
9
11
  Add to your Gemfile:
@@ -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:** In production environments where the app directory is read-only (e.g., Docker containers), StyleCapsule automatically falls back to writing files under a per-user directory in `Dir.tmpdir` 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.
325
327
 
326
328
  **Precompilation:**
327
329
 
@@ -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
 
@@ -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,29 @@
1
+ # Audit fixes for style_capsule 2.0.1
2
+
3
+ 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.
4
+
5
+ ## Decisions
6
+
7
+ 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.
8
+
9
+ 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.
10
+
11
+ 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.
12
+
13
+ CssFileWriter fallback directory is File.join(Dir.tmpdir, "style_capsule-#{Process.uid}"). Specs compare the FALLBACK_OUTPUT_DIR constant.
14
+
15
+ 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.
16
+
17
+ ## Effects
18
+
19
+ 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.
20
+
21
+ 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.
22
+
23
+ ## Next
24
+
25
+ stylesheet_registry.rb remains over the house line-count ceiling. Split only if a later change owns that file.
26
+
27
+ ## Source
28
+
29
+ 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
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,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,47 @@
1
+ # Engineering and dependency audit of style_capsule 2.0.1
2
+
3
+ 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.
4
+
5
+ ## Decisions
6
+
7
+ 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.
8
+
9
+ ## Effects
10
+
11
+ 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.
12
+
13
+ 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.
14
+
15
+ 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.
16
+
17
+ 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.
18
+
19
+ 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.
20
+
21
+ 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.
22
+
23
+ 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.
24
+
25
+ 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.
26
+
27
+ CssProcessor walks CSS with regex up to 1MB. A comment claims MAX_CSS_SIZE mitigates ReDoS. ReDoS was not proven. Spike was not run.
28
+
29
+ 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.
30
+
31
+ memory_profiler is a development gemspec dependency. No spec or lib caller was found this session.
32
+
33
+ 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.
34
+
35
+ 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.
36
+
37
+ 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.
38
+
39
+ 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.
40
+
41
+ ## Next
42
+
43
+ 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.
44
+
45
+ ## Source
46
+
47
+ 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
+ - 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
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "fileutils"
4
4
  require "digest/sha1"
5
+ require "tmpdir"
5
6
  require_relative "instrumentation"
6
7
 
7
8
  module StyleCapsule
@@ -12,7 +13,7 @@ module StyleCapsule
12
13
  # via Rails asset pipeline.
13
14
  #
14
15
  # In production environments where the app directory is read-only (e.g., Docker containers),
15
- # this class automatically falls back to writing files to /tmp/style_capsule when the
16
+ # this class automatically falls back to writing files under Dir.tmpdir when the
16
17
  # default location is not writable. When using the fallback directory, write_css returns
17
18
  # nil, causing StylesheetRegistry to fall back to inline CSS (keeping the UI functional).
18
19
  #
@@ -32,7 +33,7 @@ module StyleCapsule
32
33
  # StyleCapsule::CssFileWriter.configure(
33
34
  # output_dir: Rails.root.join(StyleCapsule::CssFileWriter::DEFAULT_OUTPUT_DIR),
34
35
  # filename_pattern: ->(component_class, capsule_id) { "capsule-#{capsule_id}.css" },
35
- # fallback_dir: "/tmp/style_capsule" # Optional, defaults to /tmp/style_capsule
36
+ # fallback_dir: StyleCapsule::CssFileWriter::FALLBACK_OUTPUT_DIR
36
37
  # )
37
38
  #
38
39
  # @example Usage
@@ -74,7 +75,7 @@ module StyleCapsule
74
75
  # Default output directory for CSS files (relative to Rails root)
75
76
  DEFAULT_OUTPUT_DIR = "app/assets/builds/capsules"
76
77
  # Fallback directory for when default location is read-only (absolute path)
77
- FALLBACK_OUTPUT_DIR = "/tmp/style_capsule"
78
+ FALLBACK_OUTPUT_DIR = File.join(Dir.tmpdir, "style_capsule-#{Process.uid}")
78
79
  # Positive cache for resolved asset-relative paths (avoids repeated File.exist? on hot path)
79
80
  FILE_PATH_CACHE_MAX = 512
80
81
 
@@ -90,7 +91,7 @@ module StyleCapsule
90
91
  # Default: `"capsule-#{capsule_id}.css"` (capsule_id is unique and deterministic)
91
92
  # @param enabled [Boolean] Whether file writing is enabled (default: true)
92
93
  # @param fallback_dir [String, Pathname, nil] Fallback directory when default location is read-only
93
- # Default: `StyleCapsule::CssFileWriter::FALLBACK_OUTPUT_DIR` (/tmp/style_capsule)
94
+ # Default: `StyleCapsule::CssFileWriter::FALLBACK_OUTPUT_DIR`
94
95
  # @example
95
96
  # StyleCapsule::CssFileWriter.configure(
96
97
  # output_dir: Rails.root.join(StyleCapsule::CssFileWriter::DEFAULT_OUTPUT_DIR),
@@ -49,7 +49,7 @@ module StyleCapsule
49
49
  raise ArgumentError, "CSS content exceeds maximum size of #{MAX_CSS_SIZE} bytes (got #{css_string.bytesize} bytes)"
50
50
  end
51
51
 
52
- # Validate capsule_id
52
+ reject_style_element_breakout!(css_string)
53
53
  validate_capsule_id!(capsule_id)
54
54
 
55
55
  # Instrument CSS processing with timing and size metrics
@@ -142,7 +142,7 @@ module StyleCapsule
142
142
  raise ArgumentError, "CSS content exceeds maximum size of #{MAX_CSS_SIZE} bytes (got #{css_string.bytesize} bytes)"
143
143
  end
144
144
 
145
- # Validate capsule_id
145
+ reject_style_element_breakout!(css_string)
146
146
  validate_capsule_id!(capsule_id)
147
147
 
148
148
  # Instrument CSS processing with timing and size metrics
@@ -169,6 +169,20 @@ module StyleCapsule
169
169
  css.gsub(/\/\*.*?\*\//m, "")
170
170
  end
171
171
 
172
+ # Reject CSS that would close an enclosing HTML style element
173
+ #
174
+ # HTML parses style-element content until the first case-insensitive closer,
175
+ # including sequences inside CSS comments or string literals.
176
+ #
177
+ # @param css_string [String, nil] CSS that will be placed inside a style tag
178
+ # @raise [ArgumentError] If the string contains a style element closer
179
+ def self.reject_style_element_breakout!(css_string)
180
+ return if css_string.blank?
181
+ return unless css_string.match?(%r{</style}i)
182
+
183
+ raise ArgumentError, "CSS content must not contain a style element closer"
184
+ end
185
+
172
186
  # Validate capsule ID to prevent injection attacks
173
187
  #
174
188
  # @param capsule_id [String] Capsule ID to validate
@@ -32,8 +32,11 @@ module StyleCapsule
32
32
  injected_body = StylesheetRegistry.inject_pending_head_stylesheets(body, view_context)
33
33
  return [status, headers, response] if injected_body.equal?(body)
34
34
 
35
+ close_response(response)
36
+
35
37
  headers = headers.dup
36
- headers["Content-Length"] = injected_body.bytesize.to_s if headers.key?("Content-Length")
38
+ headers.delete("Transfer-Encoding")
39
+ headers["Content-Length"] = injected_body.bytesize.to_s
37
40
 
38
41
  [status, headers, [injected_body]]
39
42
  end
@@ -41,15 +44,20 @@ module StyleCapsule
41
44
  def inject_response?(status, headers, response)
42
45
  return false unless SUCCESS_STATUS.cover?(status)
43
46
  return false unless html_content_type?(headers["Content-Type"])
44
- return false if chunked_response?(headers)
45
- return false if response.respond_to?(:to_ary) && response.to_ary == [""]
47
+ return false unless bufferable_body?(response)
48
+ return false if empty_body?(response)
46
49
 
47
50
  true
48
51
  end
49
52
 
50
- def chunked_response?(headers)
51
- transfer_encoding = headers["Transfer-Encoding"]
52
- transfer_encoding&.match?(/chunked/i)
53
+ # Rack 3: +to_ary+ means the body may be buffered without changing application behaviour.
54
+ # Streaming / Live bodies omit +to_ary+; leave those alone even when Transfer-Encoding is chunked.
55
+ def bufferable_body?(response)
56
+ response.respond_to?(:to_ary)
57
+ end
58
+
59
+ def empty_body?(response)
60
+ response.to_ary == [""]
53
61
  end
54
62
 
55
63
  def html_content_type?(content_type)
@@ -64,6 +72,10 @@ module StyleCapsule
64
72
  parts.join
65
73
  end
66
74
 
75
+ def close_response(response)
76
+ response.close if response.respond_to?(:close)
77
+ end
78
+
67
79
  def view_context_for(env)
68
80
  controller = env["action_controller.instance"]
69
81
  controller&.view_context
@@ -1,7 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "cgi"
3
4
  require_relative "instrumentation"
4
5
  require_relative "asset_path"
6
+ require_relative "css_processor"
5
7
 
6
8
  module StyleCapsule
7
9
  # Helper to determine the parent class for StylesheetRegistry
@@ -72,14 +74,18 @@ module StyleCapsule
72
74
  class StylesheetRegistry < StyleCapsule.stylesheet_registry_parent_class
73
75
  # Default namespace for backward compatibility
74
76
  DEFAULT_NAMESPACE = :default
77
+ MAX_INLINE_CACHE_ENTRIES = 256
78
+ FALLBACK_LINK_OPTION_NAME = /\A(?:media|title|crossorigin|integrity|nonce|type|id|class|data-[a-zA-Z0-9_-]+)\z/
75
79
 
76
80
  # Process-wide manifest for static file paths (like Propshaft)
77
81
  # Organized by namespace: { namespace => { logical_path => { file_path:, options: } } }
78
82
  @manifest = {} # rubocop:disable Style/ClassVars, ThreadSafety/MutableClassInstanceVariable
83
+ @manifest_mutex = Mutex.new
79
84
 
80
85
  # Process-wide cache for inline CSS (with expiration support)
81
86
  # Structure: { cache_key => { css_content: String, cached_at: Time, expires_at: Time } }
82
87
  @inline_cache = {} # rubocop:disable Style/ClassVars, ThreadSafety/MutableClassInstanceVariable
88
+ @inline_cache_mutex = Mutex.new
83
89
 
84
90
  # Track last cleanup time for lazy cleanup (prevents excessive cleanup calls)
85
91
  @last_cleanup_time = nil # rubocop:disable Style/ClassVars
@@ -222,8 +228,10 @@ module StyleCapsule
222
228
  inline_size: nil,
223
229
  cache_strategy: :none
224
230
  ) do
225
- @manifest[ns] ||= {}
226
- @manifest[ns][path] = {file_path: path, options: options}
231
+ with_manifest do
232
+ @manifest[ns] ||= {}
233
+ @manifest[ns][path] = {file_path: path, options: options}
234
+ end
227
235
  end
228
236
  end
229
237
 
@@ -350,11 +358,10 @@ module StyleCapsule
350
358
  # @param namespace [Symbol] Namespace (for proc strategy)
351
359
  # @return [String, nil] Cached CSS content or nil if not cached/expired
352
360
  def self.cached_inline(cache_key, cache_strategy:, cache_ttl: nil, cache_proc: nil, css_content: nil, capsule_id: nil, namespace: nil)
353
- # Lazy cleanup: remove expired entries if it's been a while (every 5 minutes)
354
- # This prevents memory leaks in long-running processes without impacting performance
355
- cleanup_expired_cache_if_needed
356
-
357
- cached_entry = @inline_cache[cache_key]
361
+ cached_entry = with_inline_cache do
362
+ cleanup_expired_cache_unlocked_if_needed
363
+ @inline_cache[cache_key]
364
+ end
358
365
  return nil unless cached_entry
359
366
 
360
367
  # Check expiration based on strategy
@@ -402,11 +409,16 @@ module StyleCapsule
402
409
  end
403
410
  end
404
411
 
405
- @inline_cache[cache_key] = {
406
- css_content: css_content,
407
- cached_at: current_time,
408
- expires_at: expires_at
409
- }
412
+ with_inline_cache do
413
+ unless @inline_cache.key?(cache_key)
414
+ @inline_cache.shift while @inline_cache.size >= MAX_INLINE_CACHE_ENTRIES
415
+ end
416
+ @inline_cache[cache_key] = {
417
+ css_content: css_content,
418
+ cached_at: current_time,
419
+ expires_at: expires_at
420
+ }
421
+ end
410
422
  end
411
423
 
412
424
  # Clear inline CSS cache
@@ -414,10 +426,12 @@ module StyleCapsule
414
426
  # @param cache_key [String, nil] Specific cache key to clear (nil clears all)
415
427
  # @return [void]
416
428
  def self.clear_inline_cache(cache_key = nil)
417
- if cache_key
418
- @inline_cache.delete(cache_key)
419
- else
420
- @inline_cache.clear
429
+ with_inline_cache do
430
+ if cache_key
431
+ @inline_cache.delete(cache_key)
432
+ else
433
+ @inline_cache.clear
434
+ end
421
435
  end
422
436
  end
423
437
 
@@ -437,13 +451,16 @@ module StyleCapsule
437
451
  # # In a scheduled job or initializer
438
452
  # StyleCapsule::StylesheetRegistry.cleanup_expired_cache
439
453
  def self.cleanup_expired_cache
454
+ with_inline_cache { cleanup_expired_cache_unlocked }
455
+ end
456
+
457
+ def self.cleanup_expired_cache_unlocked
440
458
  return 0 if @inline_cache.empty?
441
459
 
442
460
  now = current_time
443
461
  expired_keys = []
444
462
 
445
463
  @inline_cache.each do |cache_key, entry|
446
- # Remove entries that have an expires_at time and it's in the past
447
464
  if entry[:expires_at] && now > entry[:expires_at]
448
465
  expired_keys << cache_key
449
466
  end
@@ -455,23 +472,24 @@ module StyleCapsule
455
472
  expired_keys.size
456
473
  end
457
474
 
458
- # Check if cleanup is needed and perform it if so
459
- #
460
- # Only performs cleanup if it's been more than 5 minutes since the last cleanup.
461
- # This prevents excessive cleanup calls while still preventing memory leaks.
462
- #
463
- # @return [void]
464
- # @api private
465
- def self.cleanup_expired_cache_if_needed
466
- # Cleanup every 5 minutes (300 seconds) to balance memory usage and performance
475
+ def self.cleanup_expired_cache_unlocked_if_needed
467
476
  cleanup_interval = 300
468
477
 
469
478
  if @last_cleanup_time.nil? || (current_time - @last_cleanup_time) > cleanup_interval
470
- cleanup_expired_cache
479
+ cleanup_expired_cache_unlocked
471
480
  end
472
481
  end
473
482
 
474
- private_class_method :cleanup_expired_cache_if_needed
483
+ def self.with_inline_cache
484
+ @inline_cache_mutex.synchronize { yield }
485
+ end
486
+
487
+ def self.with_manifest
488
+ @manifest_mutex.synchronize { yield }
489
+ end
490
+
491
+ private_class_method :cleanup_expired_cache_unlocked, :cleanup_expired_cache_unlocked_if_needed,
492
+ :with_inline_cache, :with_manifest
475
493
 
476
494
  # Get all registered file paths from process-wide manifest (organized by namespace)
477
495
  #
@@ -540,11 +558,13 @@ module StyleCapsule
540
558
  # @param namespace [Symbol, String, nil] Optional namespace to clear (nil clears all)
541
559
  # @return [void]
542
560
  def self.clear_manifest(namespace: nil)
543
- if namespace.nil?
544
- @manifest = {}
545
- else
546
- ns = normalize_namespace(namespace)
547
- @manifest.delete(ns)
561
+ with_manifest do
562
+ if namespace.nil?
563
+ @manifest = {}
564
+ else
565
+ ns = normalize_namespace(namespace)
566
+ @manifest.delete(ns)
567
+ end
548
568
  end
549
569
  end
550
570
 
@@ -564,7 +584,17 @@ module StyleCapsule
564
584
  # If nil, falls back to basic HTML generation
565
585
  # @param namespace [Symbol, String, nil] Optional namespace to render (nil/blank renders all namespaces)
566
586
  # @return [String] HTML-safe string with stylesheet tags
567
- # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity -- renders mixed inline and file registrations
587
+ def self.render_stylesheet_entries(stylesheets, view_context)
588
+ stylesheets.map do |stylesheet|
589
+ if stylesheet[:type] == :inline
590
+ render_inline_stylesheet(stylesheet, view_context)
591
+ else
592
+ render_file_stylesheet(stylesheet, view_context)
593
+ end
594
+ end.join("\n").then { |s| safe_string(s) }
595
+ end
596
+
597
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity -- renders mixed inline and file registrations
568
598
  def self.render_head_stylesheets(view_context = nil, namespace: nil)
569
599
  if namespace.nil? || namespace.to_s.strip.empty?
570
600
  request_files_snapshot = request_file_stylesheets.transform_values { |files| files.keys.dup }
@@ -578,16 +608,9 @@ module StyleCapsule
578
608
 
579
609
  return safe_string("") if all_stylesheets.empty?
580
610
 
581
- rendered = all_stylesheets.map do |stylesheet|
582
- if stylesheet[:type] == :inline
583
- render_inline_stylesheet(stylesheet, view_context)
584
- else
585
- render_file_stylesheet(stylesheet, view_context)
586
- end
587
- end.join("\n").then { |s| safe_string(s) }
611
+ rendered = render_stylesheet_entries(all_stylesheets, view_context)
588
612
 
589
613
  clear_snapshotted_request_registrations(request_files_snapshot, request_inline_snapshot)
590
- rendered
591
614
 
592
615
  else
593
616
  # Render specific namespace
@@ -598,24 +621,19 @@ module StyleCapsule
598
621
 
599
622
  return safe_string("") if stylesheets.empty?
600
623
 
601
- rendered = stylesheets.map do |stylesheet|
602
- if stylesheet[:type] == :inline
603
- render_inline_stylesheet(stylesheet, view_context)
604
- else
605
- render_file_stylesheet(stylesheet, view_context)
606
- end
607
- end.join("\n").then { |s| safe_string(s) }
624
+ rendered = render_stylesheet_entries(stylesheets, view_context)
608
625
 
609
626
  clear_snapshotted_request_registrations_for_namespace(
610
627
  ns,
611
628
  file_paths: snapshotted_file_paths,
612
629
  snapshotted_inline_stylesheets: snapshotted_inline
613
630
  )
614
- rendered
615
631
 
616
632
  end
633
+
634
+ rendered
617
635
  end
618
- # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
636
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
619
637
 
620
638
  # Check if there are any registered stylesheets
621
639
  #
@@ -668,6 +686,7 @@ module StyleCapsule
668
686
  end
669
687
 
670
688
  # @api private
689
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity -- clears snapshotted request registries per namespace
671
690
  def self.clear_snapshotted_request_registrations_for_namespace(namespace, file_paths:, snapshotted_inline_stylesheets:)
672
691
  ns = normalize_namespace(namespace)
673
692
 
@@ -694,6 +713,7 @@ module StyleCapsule
694
713
 
695
714
  self.inline_stylesheets = inline_registry
696
715
  end
716
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
697
717
 
698
718
  # Inject pending request-scoped stylesheets into an HTML document before +</head>+.
699
719
  #
@@ -793,20 +813,24 @@ module StyleCapsule
793
813
  else
794
814
  # Fallback if no view context (logical path validated in .register)
795
815
  href = "/assets/#{file_path}.css"
796
- tag_options = options.map { |k, v| %(#{k}="#{v}") }.join(" ")
816
+ tag_options = fallback_link_option_attributes(options)
797
817
  safe_string(%(<link rel="stylesheet" href="#{href}"#{" #{tag_options}" unless tag_options.empty?}>))
798
818
  end
799
819
  end
800
820
 
821
+ def self.fallback_link_option_attributes(options)
822
+ options.filter_map do |key, value|
823
+ name = key.to_s
824
+ next unless name.match?(FALLBACK_LINK_OPTION_NAME)
825
+
826
+ %(#{name}="#{CGI.escapeHTML(value.to_s)}")
827
+ end.join(" ")
828
+ end
829
+
801
830
  # Render an inline stylesheet
802
- def self.render_inline_stylesheet(stylesheet, view_context)
803
- # CSS content is already scoped when registered from components
804
- # capsule_id is stored for reference but CSS is pre-processed
831
+ def self.render_inline_stylesheet(stylesheet, _view_context)
805
832
  css_content = stylesheet[:css_content]
806
-
807
- # Construct HTML manually to avoid any HTML escaping issues
808
- # CSS content should not be HTML-escaped as it's inside a <style> tag
809
- # Using string interpolation with html_safe ensures CSS is not escaped
833
+ CssProcessor.reject_style_element_breakout!(css_content)
810
834
  safe_string(%(<style type="text/css">#{css_content}</style>))
811
835
  end
812
836
 
@@ -822,6 +846,7 @@ module StyleCapsule
822
846
  end
823
847
  end
824
848
 
825
- private_class_method :render_file_stylesheet, :render_inline_stylesheet, :safe_string
849
+ private_class_method :render_file_stylesheet, :render_inline_stylesheet, :safe_string,
850
+ :fallback_link_option_attributes
826
851
  end
827
852
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StyleCapsule
4
- VERSION = "2.0.1"
4
+ VERSION = "2.0.2"
5
5
  end
@@ -9,6 +9,7 @@ module StyleCapsule
9
9
  def self.scope_selectors: (String css_string, String capsule_id, ?component_class: Class | String | nil) -> String
10
10
  def self.scope_with_nesting: (String css_string, String capsule_id, ?component_class: Class | String | nil) -> String
11
11
  def self.strip_comments: (String css) -> String
12
+ def self.reject_style_element_breakout!: (String | nil css_string) -> void
12
13
  end
13
14
 
14
15
  class CssFileWriter
@@ -24,6 +25,7 @@ module StyleCapsule
24
25
  # At runtime inherits Object when ActiveSupport::CurrentAttributes is unavailable.
25
26
  class StylesheetRegistry
26
27
  DEFAULT_NAMESPACE: Symbol
28
+ MAX_INLINE_CACHE_ENTRIES: Integer
27
29
 
28
30
  def self.normalize_namespace: (Symbol | String | nil namespace) -> Symbol
29
31
  def self.register: (String file_path, ?namespace: Symbol | String | nil, **Hash[untyped, untyped] options) -> void
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: style_capsule
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.1
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrei Makarov
@@ -15,7 +15,7 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: '6.0'
18
+ version: '7.0'
19
19
  - - "<"
20
20
  - !ruby/object:Gem::Version
21
21
  version: '9.0'
@@ -25,7 +25,7 @@ dependencies:
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: '6.0'
28
+ version: '7.0'
29
29
  - - "<"
30
30
  - !ruby/object:Gem::Version
31
31
  version: '9.0'
@@ -35,7 +35,7 @@ dependencies:
35
35
  requirements:
36
36
  - - ">="
37
37
  - !ruby/object:Gem::Version
38
- version: '6.0'
38
+ version: '7.0'
39
39
  - - "<"
40
40
  - !ruby/object:Gem::Version
41
41
  version: '9.0'
@@ -45,7 +45,7 @@ dependencies:
45
45
  requirements:
46
46
  - - ">="
47
47
  - !ruby/object:Gem::Version
48
- version: '6.0'
48
+ version: '7.0'
49
49
  - - "<"
50
50
  - !ruby/object:Gem::Version
51
51
  version: '9.0'
@@ -67,16 +67,16 @@ dependencies:
67
67
  name: polyrun
68
68
  requirement: !ruby/object:Gem::Requirement
69
69
  requirements:
70
- - - "~>"
70
+ - - ">="
71
71
  - !ruby/object:Gem::Version
72
- version: 1.5.0
72
+ version: 2.2.0
73
73
  type: :development
74
74
  prerelease: false
75
75
  version_requirements: !ruby/object:Gem::Requirement
76
76
  requirements:
77
- - - "~>"
77
+ - - ">="
78
78
  - !ruby/object:Gem::Version
79
- version: 1.5.0
79
+ version: 2.2.0
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: webmock
82
82
  requirement: !ruby/object:Gem::Requirement
@@ -105,48 +105,6 @@ dependencies:
105
105
  - - "~>"
106
106
  - !ruby/object:Gem::Version
107
107
  version: '13'
108
- - !ruby/object:Gem::Dependency
109
- name: simplecov
110
- requirement: !ruby/object:Gem::Requirement
111
- requirements:
112
- - - "~>"
113
- - !ruby/object:Gem::Version
114
- version: '0.22'
115
- type: :development
116
- prerelease: false
117
- version_requirements: !ruby/object:Gem::Requirement
118
- requirements:
119
- - - "~>"
120
- - !ruby/object:Gem::Version
121
- version: '0.22'
122
- - !ruby/object:Gem::Dependency
123
- name: rspec_junit_formatter
124
- requirement: !ruby/object:Gem::Requirement
125
- requirements:
126
- - - "~>"
127
- - !ruby/object:Gem::Version
128
- version: '0.6'
129
- type: :development
130
- prerelease: false
131
- version_requirements: !ruby/object:Gem::Requirement
132
- requirements:
133
- - - "~>"
134
- - !ruby/object:Gem::Version
135
- version: '0.6'
136
- - !ruby/object:Gem::Dependency
137
- name: simplecov-cobertura
138
- requirement: !ruby/object:Gem::Requirement
139
- requirements:
140
- - - "~>"
141
- - !ruby/object:Gem::Version
142
- version: '3'
143
- type: :development
144
- prerelease: false
145
- version_requirements: !ruby/object:Gem::Requirement
146
- requirements:
147
- - - "~>"
148
- - !ruby/object:Gem::Version
149
- version: '3'
150
108
  - !ruby/object:Gem::Dependency
151
109
  name: standard
152
110
  requirement: !ruby/object:Gem::Requirement
@@ -273,20 +231,6 @@ dependencies:
273
231
  - - "~>"
274
232
  - !ruby/object:Gem::Version
275
233
  version: '2'
276
- - !ruby/object:Gem::Dependency
277
- name: memory_profiler
278
- requirement: !ruby/object:Gem::Requirement
279
- requirements:
280
- - - "~>"
281
- - !ruby/object:Gem::Version
282
- version: '1'
283
- type: :development
284
- prerelease: false
285
- version_requirements: !ruby/object:Gem::Requirement
286
- requirements:
287
- - - "~>"
288
- - !ruby/object:Gem::Version
289
- version: '1'
290
234
  - !ruby/object:Gem::Dependency
291
235
  name: rbs
292
236
  requirement: !ruby/object:Gem::Requirement
@@ -344,6 +288,11 @@ files:
344
288
  - LICENSE.md
345
289
  - README.md
346
290
  - SECURITY.md
291
+ - docs/changelogs/20260904180000_audit-fixes.md
292
+ - docs/changelogs/20260925094000_head-injection-bufferable-chunked.md
293
+ - docs/dependencies/20260904174500_json-cve-2026-71847.md
294
+ - docs/issues/20260904174500_engineering-and-dependency-audit.md
295
+ - docs/issues/20260917123253_ci-drop-automatic-dependency-audit.md
347
296
  - docs/non_rails_support.md
348
297
  - lib/style_capsule.rb
349
298
  - lib/style_capsule/asset_path.rb
@@ -383,14 +332,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
383
332
  requirements:
384
333
  - - ">="
385
334
  - !ruby/object:Gem::Version
386
- version: '3.0'
335
+ version: '3.4'
387
336
  required_rubygems_version: !ruby/object:Gem::Requirement
388
337
  requirements:
389
338
  - - ">="
390
339
  - !ruby/object:Gem::Version
391
340
  version: '0'
392
341
  requirements: []
393
- rubygems_version: 4.0.3
342
+ rubygems_version: 4.0.6
394
343
  specification_version: 4
395
344
  summary: Attribute-based CSS scoping for Phlex, ViewComponent, and ERB templates.
396
345
  test_files: []