senren-ui 0.4.1 → 0.4.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: 0af2c6143260dc7e068bb79237a99b7e58d6ea0523c7863ab328ff7773d4148e
4
- data.tar.gz: 31b1bd8c86eb92ba5caf578ed314ebd857aac247b6fb214007a145acfe7dc76f
3
+ metadata.gz: 6a7c6c06cf2fbc28902f954562926b0b5a75a5b22591074c2bd7bc94a398ebf0
4
+ data.tar.gz: 1c272c73f0008bca421d43a85ca55d217c10d563aa8ec0880083fad0c412dab0
5
5
  SHA512:
6
- metadata.gz: 292220303c382a0b84f7e59cd4b1c0806f901f3fe4cb27a85d70eaddad304f501c1412e6df19d49bcf5546cf050cdf60937b9f2233d191f660bbb6f0e0f1f3c5
7
- data.tar.gz: 88efe90a4fac627f9fc103d1c811738f63b03f7413f83040c5d4c7d893188f47587284d89fe249b2c902025f82fb9ba46a53f0dce7fa595eef8b4ba2644cff2d
6
+ metadata.gz: b200e044d76d070cfa3657f967603adff7236a1b224a20ee08ed3e5a9e9ff35492d57a28782257b470df1c2901112ec6a60874556e292f5fc075a7ee5f29abb9
7
+ data.tar.gz: edee24c78567f1199468eebe25c676f23044fb622122a0bdb1658cbb4398fd3e8ac9d664812d7ccd3b13a52c8cfbe9039e61aa5e49ecbeeb124f01da13df24f6
data/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
  v0.x is a pre-stable line: minor bumps may break things; patch bumps are
8
8
  bug fixes only.
9
9
 
10
+ ## [0.4.2] — 2026-10-26
11
+
12
+ ### Security
13
+
14
+ - Refuse `senren:install` destinations that resolve outside the host app,
15
+ including symlinked files, parent directories, and dangling links, before
16
+ creating or changing files.
17
+ - Detect component source exposed through symlinked asset paths before a
18
+ deployed app boots or precompiles assets. Dedicated sidecar asset directories
19
+ remain supported.
20
+ - Require Crass 1.0.8 and ViewComponent 4.14.0 or newer so consumer bundles
21
+ enforce the CSS sanitizer and render-state security fixes.
22
+ - Update development tooling's `source-map-js` to 1.2.2 for CVE-2026-93749
23
+ and add JavaScript dependency audits to local and GitHub CI.
24
+
10
25
  ## [0.4.1] — 2026-10-08
11
26
 
12
27
  ### Fixed
@@ -29,6 +29,25 @@ module Senren
29
29
  class_option :force_locales, type: :boolean, default: false,
30
30
  desc: 'Replace locale files that already exist with the shipped ones.'
31
31
 
32
+ # Thor's template/copy/gsub actions follow destination symlinks. Check
33
+ # every destination before the first directory is created, including
34
+ # files read before rewriting and links whose targets do not exist yet.
35
+ def validate_install_destinations
36
+ destinations = [
37
+ host_paths.senren_dir, host_paths.components_dir, host_paths.stimulus_dir,
38
+ host_paths.stylesheet_path, host_paths.stylesheet_path.dirname.join('senren_themes.css'),
39
+ host_paths.base_component_path, host_paths.conventions_file, host_paths.installed_components,
40
+ host_paths.registry_mirror, host_paths.skill_file, host_paths.agent_rules_file,
41
+ host_paths.codex_agents_md, host_paths.claude_md, host_paths.copilot_instructions,
42
+ host_paths.cursor_rule_file,
43
+ host_paths.root.join('app/javascript/controllers/index.js'), host_paths.root.join('config/importmap.rb')
44
+ ]
45
+ destinations.concat(options[:locales].map { |locale| host_paths.locale_file(locale) })
46
+ destinations.each do |path|
47
+ Senren::Rails::SafeWrite.assert_inside!(path, host_paths.root, 'senren:install')
48
+ end
49
+ end
50
+
32
51
  def create_senren_dir
33
52
  empty_directory '.senren'
34
53
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'pathname'
4
+
3
5
  module Senren
4
6
  module Rails
5
7
  # Refuses to let component source be served as a static asset.
@@ -58,23 +60,20 @@ module Senren
58
60
  components = components_dir(app)
59
61
  return [] unless components&.directory?
60
62
 
61
- asset_paths(app).select { |path| publishes_source?(path, components) }
63
+ sources = component_sources(components)
64
+ return [] if sources.empty?
65
+
66
+ asset_paths(app).select { |path| publishes_source?(path, sources) }
62
67
  end
63
68
 
64
- # Whether serving this asset path would serve component source.
65
- #
66
- # `source_files?` used to be asked once about app/components as a whole,
67
- # which is the wrong question for a descendant path: with that shape,
68
- # app/components/assets holding nothing but CSS looked identical to
69
- # app/components/senren holding every component. Only the overlapping
70
- # subtree can publish anything, and that subtree is the deeper of the two
71
- # paths.
72
- def publishes_source?(asset_path, components)
69
+ # Propshaft follows directory and file symlinks when collecting assets.
70
+ # Compare their real targets, including links nested below an asset path,
71
+ # so an alias cannot publish source past the production boot guard.
72
+ def publishes_source?(asset_path, sources)
73
73
  asset = Pathname.new(asset_path.to_s).expand_path
74
- components = components.expand_path
75
- return false unless overlap?(asset, components)
74
+ return false unless asset.directory?
76
75
 
77
- source_files?(asset.to_s.length >= components.to_s.length ? asset : components)
76
+ each_file(asset).any? { |_, target| sources.key?(target) }
78
77
  end
79
78
 
80
79
  def components_dir(app)
@@ -83,8 +82,30 @@ module Senren
83
82
  nil
84
83
  end
85
84
 
86
- def source_files?(dir)
87
- SOURCE_EXTENSIONS.any? { |ext| Dir.glob(dir.join("**/*#{ext}")).any? }
85
+ def component_sources(components)
86
+ each_file(components).with_object({}) do |(path, target), sources|
87
+ sources[target] = true if [path.extname, target.extname].any? { |ext| SOURCE_EXTENSIONS.include?(ext) }
88
+ end
89
+ end
90
+
91
+ # Track real directory identities so aliases and cycles are visited once.
92
+ # Keep the original filename as well: widget.rb may point to a differently
93
+ # named file outside app/components, which is still component source.
94
+ def each_file(path, visited = {}, &block)
95
+ return enum_for(__method__, path, visited) unless block
96
+
97
+ target = path.realpath
98
+ if target.directory?
99
+ return if visited[target]
100
+
101
+ visited[target] = true
102
+ target.children.each { |child| each_file(child, visited, &block) }
103
+ else
104
+ block.call(path, target)
105
+ end
106
+ rescue Errno::ENOENT, Errno::ENOTDIR, Errno::ELOOP
107
+ # Broken links cannot publish content; visited directories stop cycles.
108
+ nil
88
109
  end
89
110
 
90
111
  def asset_paths(app)
@@ -93,24 +114,6 @@ module Senren
93
114
  []
94
115
  end
95
116
 
96
- # Two paths overlap when one contains the other, in EITHER direction, and
97
- # the test has to be separator-aware:
98
- #
99
- # app/components is an ancestor -> publishes everything
100
- # app/components/senren is a descendant -> publishes the components
101
- # app/comp shares a prefix -> publishes nothing
102
- #
103
- # The first version tested only the ancestor direction with a bare
104
- # start_with?, so it missed the descendant case — which is what a
105
- # developer writes after reading this module's own remediation text — and
106
- # it blocked production boots over an unrelated app/comp directory.
107
- def overlap?(one, other)
108
- a = "#{one}#{File::SEPARATOR}"
109
- b = "#{other}#{File::SEPARATOR}"
110
-
111
- a.start_with?(b) || b.start_with?(a)
112
- end
113
-
114
117
  def message_for(offenders)
115
118
  <<~MESSAGE.strip
116
119
  app/components is on the asset load path (#{offenders.join(', ')}).
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Senren
4
4
  module Rails
5
- VERSION = '0.4.1'
5
+ VERSION = '0.4.2'
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: senren-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - vutt
@@ -9,6 +9,20 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: crass
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: 1.0.8
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: 1.0.8
12
26
  - !ruby/object:Gem::Dependency
13
27
  name: rails
14
28
  requirement: !ruby/object:Gem::Requirement
@@ -29,14 +43,14 @@ dependencies:
29
43
  requirements:
30
44
  - - ">="
31
45
  - !ruby/object:Gem::Version
32
- version: 4.9.0
46
+ version: 4.14.0
33
47
  type: :runtime
34
48
  prerelease: false
35
49
  version_requirements: !ruby/object:Gem::Requirement
36
50
  requirements:
37
51
  - - ">="
38
52
  - !ruby/object:Gem::Version
39
- version: 4.9.0
53
+ version: 4.14.0
40
54
  description: |
41
55
  Senren UI is a Rails-native UI component library inspired by the developer
42
56
  experience of shadcn/ui. It ships generators, a registry, and a centralized