rosett-ai 1.5.12 → 1.8.0

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: cb0358498de4f1123fd29a3ae3e728712340a70772a5469a31d25048a4525aca
4
- data.tar.gz: bbc11a0a29c5d4b8b14fe3c34e9d28ae93c1046b1b6fc6f1758f09e4c8d21684
3
+ metadata.gz: 4199d3afb89e1e5378dad08e0f989c085375643d7f48436a4786c7b7c615f646
4
+ data.tar.gz: 3af391ea23387c019d6247cb8e77ca78586f5e0e9626cca37b1946d3f4dff31f
5
5
  SHA512:
6
- metadata.gz: bbfc6704a022cbc1077b0c394282abb6ed055aea1de6810d7bfc56b915c2749831ba9484b2325aae150601bd8cf8c01db2b84e17856e2a71c928184b06121436
7
- data.tar.gz: 8a143b9cb92ee1e6dad0622cd0da6275e806589e6ec10fb171111af98a0e21882a24e4f952652e0b7b40aa079c69cda8b57f07cd7bfc2b1d5949030b1bb5021b
6
+ metadata.gz: 0db1b2103c8b5334786442adea3cb254ff81389354a6dba39a0a38c38d41ba0f337dab6be90758b75c8ae51e6ada6e391befe8f671502ecd08634133067e1d23
7
+ data.tar.gz: edb943d87180a8656fdd5a33a5d20e5cb3bf05201824fbfc581c8c13b31102ec9449d3e346bb3fe05612d63333abe89331eadb0fb116c626821d5f64b6d1eadb
data/CHANGELOG.md CHANGED
@@ -6,6 +6,79 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.8.0] - 2026-08-13
10
+
11
+ ### Added
12
+
13
+ - **Enforcement rule scoping** — `HookGenerator` now supports per-rule `match_target`
14
+ (`path` | `content` | `both`) and optional `path_scope` (regex guard on the target
15
+ file path) fields in generated PreToolUse hook scripts. Rules without these fields
16
+ default to `both`, preserving backward-compatible behaviour. This eliminates false
17
+ positives that occurred when security-documentation text mentioning a forbidden
18
+ pattern triggered the rule against the file that documents it (e.g. sec_018
19
+ documentation was itself blocked).
20
+
21
+ - **Behaviour schema v1.5.0** — two new optional fields on the `enforcement` block:
22
+ - `match_target: "path" | "content" | "both"` — selects which part of the tool
23
+ input the rule pattern is tested against. Default: `"both"`.
24
+ - `path_scope: string` — regex; when set, the rule only applies when the target
25
+ file path matches. Ignored when `match_target` is `"path"`.
26
+
27
+ - **`Validator#build_valid_entry`** — passes `match_target` and `path_scope` through
28
+ to the rule hash so `HookGenerator` can embed them in RULES entries.
29
+
30
+ ## [1.7.0] - 2026-08-02
31
+
32
+ ### Added
33
+
34
+ - **sec_018 bypass vector enforcement** — `HookGenerator` now uses per-tool field
35
+ extraction when generating PreToolUse hook scripts, preventing false positives
36
+ from raw hash stringification. Bash hooks match against the `command` field only;
37
+ Write/Edit hooks match against `file_path` and content fields. Covers all
38
+ sec_018 bypass vectors: `--no-verify`, `OVERCOMMIT_DISABLE`, `core.hooksPath=`,
39
+ `ruby-audit --ignore`, `SKIP=`, `allow_failure: true` on CI gates, and writes
40
+ to the bundler-audit ignore file. Red-team spec suite added covering all
41
+ known bypass vectors.
42
+
43
+ ## [1.6.2] - 2026-07-25
44
+
45
+ ### Fixed
46
+
47
+ - **`raictl config compile` / `config show` crash with older engine plugins** — `resolve_config_compiler`
48
+ unconditionally passed `strict:` to the engine's `ConfigCompiler#initialize`, causing
49
+ `ArgumentError: unknown keyword: :strict` with engine gems whose constructor predates the
50
+ `strict` parameter. The fix introspects the constructor's parameter list at runtime and
51
+ only includes `strict:` when the engine supports it.
52
+
53
+ ## [1.6.1] - 2026-07-23
54
+
55
+ ### Security
56
+
57
+ - **CVE-2026-41316**: pin `erb` to `>= 6.0.6`; update Gemfile.lock to erb 6.0.6 —
58
+ resolves the gem-level vector of the ERB deserialization bypass advisory.
59
+
60
+ ## [1.6.0] - 2026-07-19
61
+
62
+ ### Added
63
+
64
+ - **Behaviour-rule schema v1.4.0** — three new optional, backward-compatible fields on rule items,
65
+ laying the foundation for mechanical context-load reduction:
66
+ - `enforced: boolean` — marks rules whose behaviour is deterministically enforced by a live
67
+ PreToolUse/other hook; the C2 lean-context emitter will omit these from loaded bundles.
68
+ - `applies_to: [enum]` — one or more of `worker-impl`, `worker-research`, `orchestrator`,
69
+ `interactive`, `all`; the C3 role-scoped emitter uses this to produce per-role bundles.
70
+ Absence is treated as `["all"]`.
71
+ - `evidence: string` — the "Why / On \<date\> / incident" provenance narrative, separated from
72
+ the imperative rule body; the C2 lean emitter strips this field to reduce context size.
73
+ All three fields are optional; existing behaviour YAML files validate unchanged.
74
+
75
+ ### Security
76
+
77
+ - **`mcp` gem upgraded 0.9.2 → 0.25.0** — fixes five CVEs in the MCP ruby-sdk:
78
+ GHSA-52jp-gj8w-j6xh, GHSA-5p9g-j988-pcwv, GHSA-7683-3w9x-ch42, GHSA-h669-8m4g-r2hc,
79
+ GHSA-rjr6-rcgv-9m7m (memory exhaustion, session poisoning, DNS-rebinding; all fixed in 0.23.0).
80
+ Dependency constraint widened from `~> 0.9.2` to `>= 0.23.0`.
81
+
9
82
  ## [1.5.12] - 2026-07-15
10
83
 
11
84
  ### Fixed
@@ -4,8 +4,8 @@
4
4
  "title": "NeatNerds Behaviour Configuration",
5
5
  "description": "Schema for validating behaviour configuration files used by the NeatNerds Code Companion (rosett-ai). Behaviour files define operational rules for Claude Code workflows.",
6
6
  "$comment": {
7
- "date": "2026-04-17",
8
- "version": "1.3.0",
7
+ "date": "2026-08-13",
8
+ "version": "1.5.0",
9
9
  "source": "rosett-ai",
10
10
  "compatible_claude_code_versions": [
11
11
  ">=0.1.0"
@@ -142,12 +142,46 @@
142
142
  "message": {
143
143
  "type": "string",
144
144
  "description": "Human-readable message shown when the rule triggers"
145
+ },
146
+ "match_target": {
147
+ "type": "string",
148
+ "enum": ["path", "content", "both"],
149
+ "description": "Which part of the tool input the pattern is tested against. Default: both (concatenated path + content, current behaviour)."
150
+ },
151
+ "path_scope": {
152
+ "type": "string",
153
+ "minLength": 1,
154
+ "description": "Regex string. When set, rule applies only when the target path matches. Ignored when match_target is path."
145
155
  }
146
156
  },
147
157
  "required": [
148
158
  "type"
149
159
  ],
150
160
  "additionalProperties": false
161
+ },
162
+ "enforced": {
163
+ "type": "boolean",
164
+ "description": "True when a live PreToolUse or other hook deterministically enforces this rule. Hook-enforced rules can be omitted from lean context bundles (C2 emitter). Absence is treated as false."
165
+ },
166
+ "applies_to": {
167
+ "type": "array",
168
+ "description": "Session roles this rule applies to. Used by the role-scoped bundle emitter (C3) to include only relevant rules per role. Absence is treated as [\"all\"].",
169
+ "items": {
170
+ "type": "string",
171
+ "enum": [
172
+ "worker-impl",
173
+ "worker-research",
174
+ "orchestrator",
175
+ "interactive",
176
+ "all"
177
+ ]
178
+ },
179
+ "minItems": 1,
180
+ "uniqueItems": true
181
+ },
182
+ "evidence": {
183
+ "type": "string",
184
+ "description": "Provenance narrative — the 'Why / On <date> / incident' context separated from the imperative rule body. Stripped from lean bundles (C2 emitter). Absence is treated as no separate evidence (provenance stays inline in description)."
151
185
  }
152
186
  }
153
187
  }
@@ -8,7 +8,7 @@ module RosettAi
8
8
  # Resolves the correct executor for an engine by reading its manifest
9
9
  # to determine the API type and constructor kwargs.
10
10
  #
11
- # SDK-based engines (api_gem) receive api_key: and base_url:.
11
+ # SDK-based engines (api_gem) receive api_key: and base_url:. # gitleaks:allow
12
12
  # HTTP-based engines (api_type: http|openai_compatible) receive
13
13
  # endpoint: and model: from the manifest defaults.
14
14
  # CLI-based engines (api_type: cli) receive binary: from the manifest.
@@ -5,7 +5,7 @@
5
5
 
6
6
  module RosettAi
7
7
  module Config
8
- # Resolves ${secret:backend:key} references in configuration values.
8
+ # Resolves ${secret:backend:key} references in configuration values. # gitleaks:allow
9
9
  #
10
10
  # Security constraints:
11
11
  # - NO regex anywhere — deterministic string parsing only (ReDoS prevention)
@@ -16,7 +16,7 @@ module RosettAi
16
16
  # is held server-side and NEVER distributed.
17
17
  class LicenseKey
18
18
  # @return [String] Hex-encoded public key for license verification.
19
- PUBLIC_KEY_HEX = '91f54336c0d42a7c88642b8d7f8bacab3adba4a4d633a33002fc15f676bfd8f0'
19
+ PUBLIC_KEY_HEX = '91f54336c0d42a7c88642b8d7f8bacab3adba4a4d633a33002fc15f676bfd8f0' # gitleaks:allow
20
20
  # @return [String] Prefix for rosett-ai license key strings.
21
21
  RAI_PREFIX = 'NNCC-'
22
22
  # @return [Integer] Days of grace period after license expiry.
@@ -163,6 +163,8 @@ module RosettAi
163
163
  " pattern: #{format_regex(rule[:pattern])},",
164
164
  " applies_to: [#{tools}],",
165
165
  " action: :#{rule[:action]},",
166
+ " match_target: :#{rule.fetch(:match_target, 'both')},",
167
+ " path_scope: #{rule[:path_scope] ? format_regex(rule[:path_scope]) : 'nil'},",
166
168
  " message: '#{msg}',",
167
169
  " description: '#{desc}' }"
168
170
  ].join("\n ")
@@ -189,6 +191,10 @@ module RosettAi
189
191
  # Generates the runtime enforcement logic body.
190
192
  # Uses non-interpolating heredoc — this code runs at hook time, not generation time.
191
193
  #
194
+ # Per-tool field extraction prevents false positives from raw hash stringification.
195
+ # Each tool has a known input schema; we extract only the fields that carry
196
+ # user-controlled content that sec_018 patterns must match against.
197
+ #
192
198
  # @return [String]
193
199
  def enforcement_body
194
200
  <<~'RUBY'
@@ -196,10 +202,39 @@ module RosettAi
196
202
 
197
203
  input = JSON.parse($stdin.read)
198
204
  tool_name = input['tool_name'].to_s
199
- tool_input = input['tool_input'].to_s
205
+
206
+ target_path, target_content = case tool_name
207
+ when 'Bash'
208
+ cmd = input.dig('tool_input', 'command').to_s
209
+ [cmd, cmd]
210
+ when 'Write'
211
+ [input.dig('tool_input', 'file_path').to_s,
212
+ input.dig('tool_input', 'content').to_s]
213
+ when 'Edit'
214
+ content = [input.dig('tool_input', 'old_string'),
215
+ input.dig('tool_input', 'new_string')].compact.join(' ')
216
+ [input.dig('tool_input', 'file_path').to_s, content]
217
+ when 'Agent'
218
+ ['', input.dig('tool_input', 'prompt').to_s]
219
+ else
220
+ raw = input.fetch('tool_input', '').to_s
221
+ [raw, raw]
222
+ end
200
223
 
201
224
  violations = RULES.select do |rule|
202
- rule[:applies_to].include?(tool_name) && rule[:pattern].match?(tool_input)
225
+ next false unless rule[:applies_to].include?(tool_name)
226
+
227
+ if rule[:path_scope] && rule[:match_target] != :path
228
+ next false unless rule[:path_scope].match?(target_path)
229
+ end
230
+
231
+ match_text = case rule[:match_target]
232
+ when :path then target_path
233
+ when :content then target_content
234
+ else "#{target_path} #{target_content}"
235
+ end
236
+
237
+ rule[:pattern].match?(match_text)
203
238
  end
204
239
 
205
240
  exit 0 if violations.empty?
@@ -209,7 +244,6 @@ module RosettAi
209
244
  "(P#{v[:priority]}, #{v[:action]}): #{v[:message].empty? ? v[:description] : v[:message]}"
210
245
  end
211
246
 
212
- # Block if any violation has action :block
213
247
  blocked = violations.any? { |v| v[:action] == :block }
214
248
  exit(blocked ? 2 : 0)
215
249
  RUBY
@@ -228,7 +228,9 @@ module RosettAi
228
228
  { rule_id: ctx.rule_id, behaviour: ctx.behaviour, type: 'enforceable',
229
229
  pattern: enforcement['pattern'], applies_to: enforcement['applies_to'],
230
230
  action: derive_action(enforcement, priority), message: enforcement['message'],
231
- priority: priority, description: rule['description'].to_s.strip }
231
+ priority: priority, description: rule['description'].to_s.strip,
232
+ match_target: (enforcement['match_target'] || 'both').to_s,
233
+ path_scope: enforcement['path_scope'] }
232
234
  end
233
235
  end
234
236
  end
@@ -72,6 +72,24 @@ module RosettAi
72
72
  # @return [Array] Parameter types passed as keyword arguments.
73
73
  KEYWORD_PARAM_TYPES = [:keyreq, :key].freeze
74
74
 
75
+ # Lightweight adapter so plain resource-entry hashes satisfy the mcp >= 0.23.0
76
+ # server.resources= API, which requires objects responding to #uri and #to_h.
77
+ class ResourceListEntry
78
+ def initialize(attrs)
79
+ @attrs = attrs
80
+ end
81
+
82
+ def uri
83
+ @attrs[:uri]
84
+ end
85
+
86
+ def to_h
87
+ { uri: @attrs[:uri], name: @attrs[:name], description: @attrs[:description],
88
+ mimeType: @attrs[:mimeType] }.compact
89
+ end
90
+ end
91
+ private_constant :ResourceListEntry
92
+
75
93
  module_function
76
94
 
77
95
  # @param server [MCP::Server]
@@ -89,8 +107,9 @@ module RosettAi
89
107
  # @param server [MCP::Server]
90
108
  def register_resources(server)
91
109
  resources = RESOURCE_CLASSES.map(&:new)
92
- server.resources_list_handler { resources.flat_map(&:list) }
93
- server.resources_read_handler do |uri:|
110
+ server.resources = resources.flat_map(&:list).map { |e| ResourceListEntry.new(e) }
111
+ server.resources_read_handler do |params|
112
+ uri = params.is_a?(Hash) ? (params[:uri] || params['uri']) : params.to_s
94
113
  resource_name = uri.split('/').last
95
114
  resource_class = find_resource_for_uri(resources, uri)
96
115
  result = resource_class&.read(resource_name)
@@ -6,7 +6,7 @@
6
6
  module RosettAi
7
7
  module Retrofit
8
8
  # Detects sensitive values in parsed configuration data and replaces
9
- # them with ${secret:env:NAME} references.
9
+ # them with ${secret:env:NAME} references. # gitleaks:allow
10
10
  #
11
11
  # Recognises API keys, tokens, passwords, and other secrets by
12
12
  # key name patterns and value heuristics (length, entropy, prefixes).
@@ -69,7 +69,7 @@ module RosettAi
69
69
  env_name = full_key.upcase.gsub(/[^A-Z0-9]/, '_').squeeze('_')
70
70
  @warnings << "Sensitive value detected for '#{key}' — replaced with secret reference. " \
71
71
  "Set #{env_name} in your environment."
72
- "${secret:env:#{env_name}}"
72
+ "${secret:env:#{env_name}}" # gitleaks:allow
73
73
  end
74
74
 
75
75
  def sensitive?(key, value)
@@ -143,8 +143,12 @@ module RosettAi
143
143
  end
144
144
 
145
145
  secret_resolver = masking ? RosettAi::Config::MaskingSecretResolver.new : nil
146
- plugin.config_compiler_class.new(simulate: simulate, secret_resolver: secret_resolver,
147
- strict: strict)
146
+ klass = plugin.config_compiler_class
147
+ params = { simulate: simulate, secret_resolver: secret_resolver }
148
+ init_params = klass.instance_method(:initialize).parameters
149
+ supports_strict = init_params.any? { |type, name| name == :strict && [:key, :keyreq].include?(type) }
150
+ params[:strict] = strict if supports_strict
151
+ klass.new(**params)
148
152
  end
149
153
 
150
154
  def engine_hint(available, engine_name)
@@ -5,5 +5,5 @@
5
5
 
6
6
  module RosettAi
7
7
  # @return [String] Current version of the rosett-ai gem.
8
- VERSION = '1.5.12'
8
+ VERSION = '1.8.0'
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rosett-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.12
4
+ version: 1.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hugo Antonio Sepulveda Manriquez
@@ -237,16 +237,16 @@ dependencies:
237
237
  name: mcp
238
238
  requirement: !ruby/object:Gem::Requirement
239
239
  requirements:
240
- - - "~>"
240
+ - - ">="
241
241
  - !ruby/object:Gem::Version
242
- version: 0.9.2
242
+ version: 0.23.0
243
243
  type: :runtime
244
244
  prerelease: false
245
245
  version_requirements: !ruby/object:Gem::Requirement
246
246
  requirements:
247
- - - "~>"
247
+ - - ">="
248
248
  - !ruby/object:Gem::Version
249
- version: 0.9.2
249
+ version: 0.23.0
250
250
  description: Configuration management tool for AI-assisted development workflows.
251
251
  Author rules once, compile to Claude Code, Cursor, AGENTS.md, and more.
252
252
  email: