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 +4 -4
- data/CHANGELOG.md +20 -1
- data/README.md +44 -29
- data/SECURITY.md +11 -14
- data/docs/changelogs/20260904180000_audit-fixes.md +33 -0
- data/docs/changelogs/20260925094000_head-injection-bufferable-chunked.md +25 -0
- data/docs/changelogs/20260928193000_sync-appraisal-locks-after-version-bump.md +19 -0
- data/docs/dependencies/20260904174500_json-cve-2026-71847.md +37 -0
- data/docs/issues/20260904174500_engineering-and-dependency-audit.md +51 -0
- data/docs/issues/20260917123253_ci-drop-automatic-dependency-audit.md +28 -0
- data/docs/issues/20261103150931_engineering-prose-release-audit.md +40 -0
- data/docs/non_rails_support.md +2 -2
- data/docs/reviews/20261028_engineering-audit-release-preparation.md +25 -0
- data/docs/reviews/20261103150931_engineering-prose-release-audit.md +42 -0
- data/lib/style_capsule/component.rb +1 -1
- data/lib/style_capsule/component_builder.rb +1 -1
- data/lib/style_capsule/component_styles_support.rb +10 -0
- data/lib/style_capsule/css_file_writer.rb +5 -4
- data/lib/style_capsule/css_processor.rb +220 -83
- data/lib/style_capsule/head_injection_middleware.rb +22 -7
- data/lib/style_capsule/stylesheet_registry.rb +93 -62
- data/lib/style_capsule/version.rb +1 -1
- data/lib/style_capsule/view_component.rb +1 -1
- data/sig/style_capsule.rbs +6 -1
- metadata +22 -72
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7a6c3fda27b85ee3899cbe434dd5230de0d1c9c4ff6c961deefa9b058fd8f6b3
|
|
4
|
+
data.tar.gz: e70f42c7ee1fc234201107862e1c7dfe9f609a9a55333983101aed4d1ec74ff7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 163cd5d8f4f0a324121a866adc94c48f31438cd560ba2b30b2d8f3721b645fe4a339e89308cb38552a96294fd8b01a834936615617ff6889203003f593c4e716
|
|
7
|
+
data.tar.gz: 39ca2e8c56745ddcbeae553b401e3e1b0a4b92409f0d066f0a50a9309bc3efac5369f38e929e6d04f0df991b8abbbe30723602c7f5458ac152a4bdf67ad4dc3a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
-
##
|
|
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
|
-
[](https://badge.fury.io/rb/style_capsule) [](https://github.com/amkisko/style_capsule.rb/actions/workflows/test.yml) [](https://app.codecov.io/github/amkisko/style_capsule.rb)
|
|
4
4
|
|
|
5
|
-
CSS scoping
|
|
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
|
-
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
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
|
|
105
|
-
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
322
|
+
fallback_dir: StyleCapsule::CssFileWriter::FALLBACK_OUTPUT_DIR
|
|
321
323
|
)
|
|
322
324
|
```
|
|
323
325
|
|
|
324
|
-
**Fallback Directory:**
|
|
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
|
-
|
|
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.
|
|
456
|
-
- Rails >=
|
|
457
|
-
- ActiveSupport >=
|
|
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
|
|
471
|
-
3. **HTML Wrapping**: Component content is
|
|
472
|
-
4. **
|
|
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
|
-
-
|
|
518
|
+
- Capsule id and HTML attribute validation and escaping
|
|
517
519
|
- Size limits (1MB per component)
|
|
518
|
-
-
|
|
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
|
-
|
|
5
|
+
Email security details to: security@kiskolabs.com
|
|
6
6
|
|
|
7
|
-
|
|
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
|
|
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
|
-
|
|
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
|
data/docs/non_rails_support.md
CHANGED
|
@@ -81,5 +81,5 @@ stylesheets = StyleCapsule::StylesheetRegistry.request_inline_stylesheets
|
|
|
81
81
|
|
|
82
82
|
## Configuration entry points
|
|
83
83
|
|
|
84
|
-
-
|
|
85
|
-
-
|
|
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
|
-
|
|
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
|
|
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]
|