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 +4 -4
- data/.github/workflows/ci.yml +5 -1
- data/CHANGELOG.md +6 -0
- data/ext/libinjection/libinjection_ext.c +21 -5
- data/lib/libinjection/version.rb +1 -1
- data/script/vendor_libs.rb +21 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 55b2c6d2117f60b522347c585c7b99ac85662fccf79f111de32efac96ceafba2
|
|
4
|
+
data.tar.gz: 8c521ba45e916f2226d7e874154f785a8bad0e3b47c4fe69305273dcc28a6ddf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 85034605c3e383e8344eaa389a95f8ab12fcce1eb114af0333955d208c98a651819e4ff667d72411794ff8b2f419d6b6bcf72fad35d184eef32336fdda3c0231
|
|
7
|
+
data.tar.gz: cd4d8fe1733d9deb7a959b278903fa7b58c3c9a1116c3f8835766831262a488e6a3b6070b184316e1250b63a936b26eb4d95668dfbe435adf7c8d21d3a86faff
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -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:
|
|
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,
|
|
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
|
-
|
|
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 ?
|
|
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
|
|
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)
|
|
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
|
}
|
data/lib/libinjection/version.rb
CHANGED
data/script/vendor_libs.rb
CHANGED
|
@@ -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
|