rack-libinjection 0.1.0 → 0.1.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: 0203a3f1b8a3447818e725857364461f8f62632cb8f466b6c42178948a02f93d
4
- data.tar.gz: 84d19b19a25a44987efdd3486d6896efad15e9c9d1ca715431bd612f4b0a580a
3
+ metadata.gz: 55b2c6d2117f60b522347c585c7b99ac85662fccf79f111de32efac96ceafba2
4
+ data.tar.gz: 8c521ba45e916f2226d7e874154f785a8bad0e3b47c4fe69305273dcc28a6ddf
5
5
  SHA512:
6
- metadata.gz: 24a29c7bb6ec739b8f35170a721978b55fe2e2b4cbbdfe03c1fde47b8513c3e8b3eb0d5da2274e6cf770533e96d4e0c545c45dbbb62c916f09bcd02601ed211b
7
- data.tar.gz: 57115bdc3838e5a5d47aaaf7a8e55f0b22a005dcee8042b0f71778a2f29a2a44c3c4bc8d7d8d7ca390694ddd83b831d15f178a9629fa86c1f32e18e6fc054531
6
+ metadata.gz: 85034605c3e383e8344eaa389a95f8ab12fcce1eb114af0333955d208c98a651819e4ff667d72411794ff8b2f419d6b6bcf72fad35d184eef32336fdda3c0231
7
+ data.tar.gz: cd4d8fe1733d9deb7a959b278903fa7b58c3c9a1116c3f8835766831262a488e6a3b6070b184316e1250b63a936b26eb4d95668dfbe435adf7c8d21d3a86faff
@@ -38,11 +38,15 @@ jobs:
38
38
  sanitizers:
39
39
  needs: vendor-verify
40
40
  runs-on: ubuntu-latest
41
+ strategy:
42
+ fail-fast: false
43
+ matrix:
44
+ ruby: ["3.3", "3.4", "4.0"]
41
45
  steps:
42
46
  - uses: actions/checkout@v4
43
47
  - uses: ruby/setup-ruby@v1
44
48
  with:
45
- ruby-version: "3.4"
49
+ ruby-version: ${{ matrix.ruby }}
46
50
  bundler-cache: true
47
51
  - run: bundle exec rake vendor
48
52
  - run: LIBINJECTION_SANITIZE=1 bundle exec rake compile
data/CHANGELOG.md CHANGED
@@ -2,8 +2,13 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.1 - 2026-05-22
6
+
5
7
  ### Security / hardening
6
8
 
9
+ - SQLi fingerprint Ruby strings are now constructed with a bounded length derived from the vendored libinjection fingerprint buffer instead of relying on C-string scanning.
10
+ - Vendored archive extraction now explicitly rejects absolute paths, malformed paths, and `..` traversal before the keep-list is applied.
11
+ - Sanitizer CI now runs the ASan/UBSan smoke job across Ruby 3.3, 3.4, and 4.0.
7
12
  - Added explicit Rack middleware parser-error policy. `parser_errors: :auto` now reports native libinjection and known Rack parameter/cookie parser errors in report mode and fails closed in block mode; `:report`, `:block`, and `:raise` are available for explicit behavior.
8
13
  - Added explicit skipped-input policy. `skipped_inputs: :auto` reports `max_value_bytes` / `max_depth` skips in report mode and fails closed in block mode; `:report`, `:block`, and `:allow` are available for explicit behavior.
9
14
  - Narrowed the default ignored params to `authenticity_token`; sensitive values such as `password` are scanned by default while raw values remain absent from notifications.
@@ -52,6 +57,7 @@
52
57
  (default 1024). The extension uses `rb_nogvl(..., RB_NOGVL_OFFLOAD_SAFE)`
53
58
  when the Ruby headers provide that flag, and falls back to
54
59
  `rb_thread_call_without_gvl` on older headers.
60
+ - Documented the native invariant that `LI_NOGVL_THRESHOLD` controls both no-GVL execution and the temporary C-buffer copy decision.
55
61
  - `Rack::LibInjection` middleware rewritten on top of a mutable accumulator:
56
62
  no more per-level `flat_map`, no more `path + [key]` allocations, no more
57
63
  intermediate hashes per match. Path keys are now built as plain `String`s.
@@ -288,6 +288,22 @@ static void raise_on_error(injection_result_t result) {
288
288
  }
289
289
  }
290
290
 
291
+ static size_t li_bounded_strlen(const char *str, size_t max_len) {
292
+ size_t len = 0;
293
+
294
+ while (len < max_len && str[len] != '\0') {
295
+ len++;
296
+ }
297
+
298
+ return len;
299
+ }
300
+
301
+ static VALUE li_sqli_fingerprint_value(const char *fingerprint) {
302
+ size_t len = li_bounded_strlen(fingerprint, LI_SQLI_FINGERPRINT_SIZE);
303
+
304
+ return len == 0 ? Qnil : rb_str_new(fingerprint, (long)len);
305
+ }
306
+
291
307
  typedef struct {
292
308
  const char *src;
293
309
  size_t len;
@@ -460,7 +476,7 @@ static int li_url_decode_into(char *dst, const char *src, size_t len, size_t *ou
460
476
 
461
477
  static VALUE li_scan_out_to_value(const li_scan_out_t *out) {
462
478
  if (out->found_type == LI_THREAT_SQLI) {
463
- return rb_ary_new3(2, sym_sqli, rb_str_new_cstr(out->sqli_fingerprint));
479
+ return rb_ary_new3(2, sym_sqli, li_sqli_fingerprint_value(out->sqli_fingerprint));
464
480
  }
465
481
  if (out->found_type == LI_THREAT_XSS) {
466
482
  return rb_ary_new3(2, sym_xss, Qnil);
@@ -578,7 +594,7 @@ static VALUE li_sqli_result_hash(const struct libinjection_sqli_state *state,
578
594
  rb_hash_aset(hash, li_id_sym("type"), sym_sqli);
579
595
  rb_hash_aset(hash, li_id_sym("detected"), result == LIBINJECTION_RESULT_TRUE ? Qtrue : Qfalse);
580
596
  rb_hash_aset(hash, li_id_sym("fingerprint"),
581
- fingerprint[0] == '\0' ? Qnil : rb_str_new_cstr(fingerprint));
597
+ li_sqli_fingerprint_value(fingerprint));
582
598
  rb_hash_aset(hash, li_id_sym("flags"), INT2NUM(flags));
583
599
  rb_hash_aset(hash, li_id_sym("context"), context_name);
584
600
  rb_hash_aset(hash, li_id_sym("stats"), li_sqli_stats_hash(state));
@@ -654,7 +670,7 @@ static VALUE rb_li_sqli_fingerprint(VALUE self, VALUE input) {
654
670
  memcpy(fingerprint, args.work.sqli_fingerprint, sizeof(fingerprint));
655
671
  raise_on_error(args.work.sqli_result);
656
672
 
657
- return args.work.sqli_detected ? rb_str_new_cstr(fingerprint) : Qnil;
673
+ return args.work.sqli_detected ? li_sqli_fingerprint_value(fingerprint) : Qnil;
658
674
  }
659
675
 
660
676
  typedef struct {
@@ -876,7 +892,7 @@ static VALUE rb_li_sqli_fingerprint_for(int argc, VALUE *argv, VALUE self) {
876
892
 
877
893
  result = li_run_sqli_context(str, flags, &state);
878
894
  raise_on_error(result);
879
- return state.fingerprint[0] == '\0' ? Qnil : rb_str_new_cstr(state.fingerprint);
895
+ return li_sqli_fingerprint_value(state.fingerprint);
880
896
  }
881
897
 
882
898
  static VALUE rb_li_sqli_contexts(VALUE self, VALUE input) {
@@ -924,7 +940,7 @@ static VALUE rb_li_sqli_tokens(int argc, VALUE *argv, VALUE self) {
924
940
  int tlen;
925
941
  int i;
926
942
  libinjection_sqli_fingerprint(&state, flags);
927
- tlen = (int)strlen(state.fingerprint);
943
+ tlen = (int)li_bounded_strlen(state.fingerprint, LI_SQLI_FINGERPRINT_SIZE);
928
944
  for (i = 0; i < tlen; i++) {
929
945
  rb_ary_push(out, li_sqli_token_hash(&state.tokenvec[i]));
930
946
  }
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LibInjection
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  LIBINJECTION_VERSION = "4.0.0"
6
6
  end
@@ -138,6 +138,25 @@ def http_download_to_file!(url, path, redirect_limit: 3)
138
138
  end
139
139
  end
140
140
 
141
+ def safe_vendor_target_for!(relative)
142
+ if relative.include?("\0") || relative.start_with?("/", "\\")
143
+ abort "Unsafe archive path: #{relative.inspect}"
144
+ end
145
+
146
+ parts = relative.split("/")
147
+ if parts.empty? || parts.any? { |part| part.empty? || part == "." || part == ".." }
148
+ abort "Unsafe archive path: #{relative.inspect}"
149
+ end
150
+
151
+ root = File.expand_path(LIB_DIR)
152
+ target = File.expand_path(relative, root)
153
+ unless target.start_with?(root + File::SEPARATOR)
154
+ abort "Unsafe archive path escapes vendor root: #{relative.inspect}"
155
+ end
156
+
157
+ target
158
+ end
159
+
141
160
  def download_archive!(path)
142
161
  url = format(PIN[:url], version: PIN[:version])
143
162
  puts "Downloading #{url}"
@@ -166,10 +185,11 @@ def extract_archive!(archive)
166
185
  tar.each do |entry|
167
186
  relative = entry.full_name.sub(prefix_re, "")
168
187
  next if relative.empty? || relative == entry.full_name
188
+
189
+ target = safe_vendor_target_for!(relative)
169
190
  next unless PIN[:keep].include?(relative)
170
191
  next unless entry.file?
171
192
 
172
- target = File.join(LIB_DIR, relative)
173
193
  FileUtils.mkdir_p(File.dirname(target))
174
194
  File.binwrite(target, entry.read)
175
195
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-libinjection
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Haydarov