secryst 1.0.0 → 1.0.1

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: 185de54f6ce981b7739770b422262452752460c5559e029e4b5039f67f6483ef
4
- data.tar.gz: df7df11ff4fc5a476074e8155448a3b1ab0577ab693c37e796a98c0cf7934af8
3
+ metadata.gz: 41fa8b9cc35c52b6ed2a5ffb898d40f83df55e89533d48878e899bcf20ee142f
4
+ data.tar.gz: 697eae0009ab525080e429e213b09b4b06a828a4157db9649a6cea04abd16dd7
5
5
  SHA512:
6
- metadata.gz: 1c51c600385d5a05e9f9298925f2ba7fb1c32a49b26f0345d877c8a11da04459a50d4adb5a23aedac480212e5b83af621a917db472382194ddeb716bd2ae5c4f
7
- data.tar.gz: 558249e6dcf7ec5cb927512f6487ee8edd911cb06f9f1685bd4ab1b00ec651e230940880cc2c0f3cd61a095a37c3dc0cad046ea4299b25acdd5ee2669ba35df4
6
+ metadata.gz: 007d2fb6944c93ee1e38ace06e06a025336b00318c7a5446e9daf33cc8ab0448caa4646dd943176452d1d4fe61ea3d706e3713b0deabc9f526b24e600707f432
7
+ data.tar.gz: 8dfdc84550803d2d87566fc1c70c268505b05fe963213e98e12a89a16cada7e07eedfcb8cc138738b615b346db554b13fa71a1a352ab721581b359d3d588faca
data/lib/secryst/imf.rb CHANGED
@@ -15,7 +15,7 @@ module Secryst
15
15
  EOS_ID = 1
16
16
  UNK_ID = 2
17
17
 
18
- DEFAULT_INDEX_URL = "https://raw.githubusercontent.com/interscript/interscript-ml/main/models.yaml"
18
+ DEFAULT_INDEX_URL = "https://github.com/interscript/interscript-ml/releases/download/index-v1/models-index.yaml"
19
19
 
20
20
  class FormatError < StandardError; end
21
21
  class RegistryError < StandardError; end
@@ -142,7 +142,7 @@ module Secryst
142
142
 
143
143
  def load_index(source)
144
144
  text = if source.start_with?("http://", "https://")
145
- URI.open(source) { |remote| remote.read }
145
+ fetch_index_with_sidecar(source)
146
146
  else
147
147
  File.read(source)
148
148
  end
@@ -150,6 +150,28 @@ module Secryst
150
150
  raise RegistryError, "index must have version: 1" if raw["version"] != 1
151
151
  raw.fetch("models", {})
152
152
  end
153
+ public :load_index
154
+
155
+ # HTTP sources must ship a sibling .sha256 sidecar; verify the
156
+ # body against it before parsing (GitHub Releases index assets do).
157
+ def fetch_index_with_sidecar(source)
158
+ body = URI.open(source, "rb") { |remote| remote.read }
159
+ sidecar =
160
+ begin
161
+ URI.open("#{source}.sha256") { |remote| remote.read }
162
+ rescue OpenURI::HTTPError => e
163
+ raise RegistryError, "index sha256 sidecar missing: #{source}.sha256 (#{e.message})"
164
+ end
165
+ expected = sidecar.to_s.strip.split(/\s+/).first.to_s
166
+ unless expected.match?(/\A[0-9a-fA-F]{64}\z/)
167
+ raise RegistryError, "index sha256 sidecar malformed: #{source}.sha256"
168
+ end
169
+ actual = Digest::SHA256.hexdigest(body)
170
+ unless actual == expected.downcase
171
+ raise RegistryError, "index sha256 mismatch: got #{actual}, sidecar says #{expected.downcase}"
172
+ end
173
+ body.force_encoding(Encoding::UTF_8)
174
+ end
153
175
  end
154
176
  end
155
177
  end
@@ -1,3 +1,3 @@
1
1
  module Secryst
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.1"
3
3
  end
@@ -178,3 +178,67 @@ RSpec.describe Secryst::IMF do
178
178
  end
179
179
  end
180
180
  end
181
+
182
+ describe "index distribution contract" do
183
+ it "pins DEFAULT_INDEX_URL to a GitHub Release asset, never raw" do
184
+ expect(Secryst::IMF::DEFAULT_INDEX_URL).to match(
185
+ %r{\Ahttps://github\.com/interscript/interscript-ml/releases/download/index-v\d+/models-index\.yaml\z}
186
+ )
187
+ expect(Secryst::IMF::DEFAULT_INDEX_URL).not_to include("raw.githubusercontent")
188
+ end
189
+
190
+ it "verifies the .sha256 sidecar on HTTP index fetches" do
191
+ require "socket"
192
+ body = "version: 1\nmodels: {}\n"
193
+ good = Digest::SHA256.hexdigest(body)
194
+ bad = "0" * 64
195
+
196
+ serve = lambda do |sidecar, &blk|
197
+ server = TCPServer.new("127.0.0.1", 0)
198
+ port = server.addr[1]
199
+ thread = Thread.new do
200
+ loop do
201
+ client = server.accept rescue break
202
+ request = client.readline
203
+ client.each_line { |line| break if line == "\r\n" }
204
+ path = request.split(" ")[1]
205
+ case path
206
+ when "/models-index.yaml"
207
+ client.write("HTTP/1.1 200 OK\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}")
208
+ when "/models-index.yaml.sha256"
209
+ if sidecar.nil?
210
+ client.write("HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n")
211
+ else
212
+ payload = "#{sidecar} models-index.yaml\n"
213
+ client.write("HTTP/1.1 200 OK\r\nContent-Length: #{payload.bytesize}\r\n\r\n#{payload}")
214
+ end
215
+ else
216
+ client.write("HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n")
217
+ end
218
+ client.close
219
+ end
220
+ end
221
+ begin
222
+ blk.call("http://127.0.0.1:#{port}/models-index.yaml")
223
+ ensure
224
+ server.close
225
+ thread.join(5)
226
+ end
227
+ end
228
+
229
+ serve.call(good) do |url|
230
+ entries = Secryst::IMF.load_index(url)
231
+ expect(entries).to eq({})
232
+ end
233
+ serve.call(bad) do |url|
234
+ expect {
235
+ Secryst::IMF.load_index(url)
236
+ }.to raise_error(Secryst::IMF::RegistryError, /index sha256 mismatch/)
237
+ end
238
+ serve.call(nil) do |url|
239
+ expect {
240
+ Secryst::IMF.load_index(url)
241
+ }.to raise_error(Secryst::IMF::RegistryError, /index sha256/)
242
+ end
243
+ end
244
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secryst
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Interscript / Secryst contributors
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-20 00:00:00.000000000 Z
11
+ date: 2026-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor