rack-libinjection 0.1.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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +55 -0
  3. data/CHANGELOG.md +112 -0
  4. data/GET_STARTED.md +418 -0
  5. data/LICENSE-libinjection.txt +33 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +68 -0
  8. data/SECURITY.md +65 -0
  9. data/ext/libinjection/extconf.rb +113 -0
  10. data/ext/libinjection/libinjection_ext.c +1132 -0
  11. data/ext/libinjection/vendor/libinjection/.vendored +5 -0
  12. data/ext/libinjection/vendor/libinjection/COPYING +33 -0
  13. data/ext/libinjection/vendor/libinjection/MIGRATION.md +393 -0
  14. data/ext/libinjection/vendor/libinjection/README.md +251 -0
  15. data/ext/libinjection/vendor/libinjection/src/libinjection.h +70 -0
  16. data/ext/libinjection/vendor/libinjection/src/libinjection_error.h +26 -0
  17. data/ext/libinjection/vendor/libinjection/src/libinjection_html5.c +830 -0
  18. data/ext/libinjection/vendor/libinjection/src/libinjection_html5.h +56 -0
  19. data/ext/libinjection/vendor/libinjection/src/libinjection_sqli.c +2342 -0
  20. data/ext/libinjection/vendor/libinjection/src/libinjection_sqli.h +297 -0
  21. data/ext/libinjection/vendor/libinjection/src/libinjection_sqli_data.h +9651 -0
  22. data/ext/libinjection/vendor/libinjection/src/libinjection_xss.c +1203 -0
  23. data/ext/libinjection/vendor/libinjection/src/libinjection_xss.h +23 -0
  24. data/lib/libinjection/version.rb +6 -0
  25. data/lib/libinjection.rb +31 -0
  26. data/lib/rack/libinjection.rb +586 -0
  27. data/lib/rack-libinjection.rb +3 -0
  28. data/samples/README.md +67 -0
  29. data/samples/libinjection_detect_raw_hot_path.rb +161 -0
  30. data/samples/rack_all_surfaces_hot_path.rb +198 -0
  31. data/samples/rack_params_hot_path.rb +166 -0
  32. data/samples/rack_query_hot_path.rb +176 -0
  33. data/samples/results/.gitkeep +0 -0
  34. data/script/fuzz_smoke.rb +39 -0
  35. data/script/vendor_libs.rb +227 -0
  36. data/test/test_helper.rb +7 -0
  37. data/test/test_libinjection.rb +223 -0
  38. data/test/test_middleware.rb +404 -0
  39. metadata +148 -0
data/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # rack-libinjection
2
+
3
+ `rack-libinjection` is a small native Ruby binding plus Rack middleware for
4
+ [libinjection](https://github.com/libinjection/libinjection).
5
+
6
+ It adds a tokenizer/fingerprint-based SQLi/XSS **signal layer** to Rack and
7
+ Rails applications. It is report-only by default.
8
+
9
+ ## Important scope note
10
+
11
+ This is not a full WAF. The middleware scans only the configured Rack surfaces.
12
+ The default is:
13
+
14
+ - parsed Rack params (`scan: [:params]`)
15
+
16
+ Optional surfaces are available for raw query strings, path, headers, and cookies:
17
+
18
+ - `scan: [:query]` for a fast raw query-string signal that avoids Rack nested params parsing
19
+ - `scan: [:params, :path, :headers, :cookies]` for semantic params plus other Rack surfaces
20
+
21
+ The middleware can scan both SQLi and XSS signals by default, or only one class through `threats: [:sqli]` / `threats: [:xss]`. Path and raw-query values are decoded up to `path_decode_depth` times for detection inside the native extension, avoiding Ruby `gsub`/regex allocation on this hot path. Query/path/header-only configurations are scanned directly from the Rack env without constructing `Rack::Request`; params and cookies still use Rack parsers. Header
22
+ scanning skips common low-signal protocol/browser headers by default through
23
+ `ignore_headers`. Cookie values are scanned when `:cookies` is enabled; cookie
24
+ names are skipped unless `scan_cookie_names: true` is configured. In `mode: :block`, native/Rack parser errors fail closed by default through `parser_errors: :auto`, and values skipped by `max_value_bytes` / `max_depth` fail closed by default through `skipped_inputs: :auto`.
25
+
26
+ Raw JSON body scanning is not part of the current middleware. JSON bodies,
27
+ large request bodies, multipart file contents, and application-specific decoding
28
+ need separate design. See [GET_STARTED.md](GET_STARTED.md) before deploying.
29
+
30
+ ## Usage
31
+
32
+ All installation, middleware configuration, low-level API examples, vendoring,
33
+ system-library mode, GVL notes, threat model, and operational guidance live in
34
+ [GET_STARTED.md](GET_STARTED.md).
35
+
36
+ ## What this is
37
+
38
+ - A native binding to vendored `libinjection` `v4.0.0`.
39
+ - A Rack/Rails middleware that emits attack signals for configured request
40
+ surfaces.
41
+ - A diagnostic API for SQLi fingerprints, parser contexts, SQL tokens, XSS
42
+ contexts, and HTML5 tokens.
43
+ - A small signal layer that can feed logs, notifications, or `Rack::Attack`-style
44
+ scoring.
45
+
46
+ ## What this is not
47
+
48
+ - Not a full WAF.
49
+ - Not a replacement for Rails escaping, bind params, CSP, authorization, or
50
+ upstream WAF/rate-limit protections.
51
+ - Not a promise to catch every SQLi/XSS payload.
52
+ - Not enabled in blocking mode by default.
53
+ - Not a JSON-body scanner yet.
54
+
55
+ ## Native dependency
56
+
57
+ The default build uses pinned, vendored `libinjection` sources. Source checkouts
58
+ can regenerate or verify the vendored tree through `script/vendor_libs.rb`; see
59
+ [GET_STARTED.md](GET_STARTED.md) for commands and system-library mode.
60
+
61
+ ## Security policy
62
+
63
+ Report suspected vulnerabilities privately. See [SECURITY.md](SECURITY.md).
64
+
65
+ ## License
66
+
67
+ The Ruby gem is MIT-licensed. The vendored upstream `libinjection` sources are
68
+ BSD-3-Clause licensed and included as `LICENSE-libinjection.txt`.
data/SECURITY.md ADDED
@@ -0,0 +1,65 @@
1
+ # Security policy
2
+
3
+ `rack-libinjection` is a security signal layer, so vulnerability reports are
4
+ welcome even for edge cases that look minor.
5
+
6
+ ## Supported versions
7
+
8
+ The project is pre-1.0. Security fixes are applied to the latest released
9
+ version only until a stable compatibility policy exists.
10
+
11
+ ## Reporting a vulnerability
12
+
13
+ Please do not open a public issue for a suspected vulnerability.
14
+
15
+ Send a report to:
16
+
17
+ - `romanhajdarov@gmail.com`
18
+
19
+ Include:
20
+
21
+ - affected version / commit;
22
+ - Ruby, Rack, and OS versions;
23
+ - minimal reproduction;
24
+ - whether the issue is in the native binding, Rack middleware, vendoring, or
25
+ documentation;
26
+ - expected vs actual behavior.
27
+
28
+ ## Scope
29
+
30
+ Useful reports include:
31
+
32
+ - crashes or memory-safety issues in the native extension;
33
+ - incorrect use of vendored libinjection sources;
34
+ - middleware bypasses caused by this gem's Rack integration;
35
+ - false claims in documentation that can lead to unsafe deployment;
36
+ - unsafe default behavior around notifications, blocking mode, or skipped input.
37
+
38
+ Bypasses in upstream `libinjection` itself should also be reported upstream, but
39
+ it is still useful to notify this project when the Rack integration makes them
40
+ worse or hides the limitation.
41
+
42
+ ## Native hardening expectations
43
+
44
+ The native extension is expected to tolerate empty strings, binary strings,
45
+ invalid UTF-8 byte sequences, null bytes, and large inputs without crashing.
46
+ Public low-level scans copy large inputs before releasing the GVL, so the C
47
+ scanner does not read directly from a Ruby `String` buffer while Ruby code may
48
+ mutate the same object from another thread. Copied native buffers are released
49
+ through `rb_ensure` cleanup paths.
50
+
51
+ The project includes a `security:smoke` task with random/binary inputs and a CI
52
+ sanitizer job for AddressSanitizer/UBSan builds. These checks are not a
53
+ replacement for upstream libinjection fuzzing, but regressions in the Ruby
54
+ binding or middleware integration should be caught here.
55
+
56
+ The binding scans bytes. It does not perform Unicode normalization or transcode
57
+ UTF-16/UTF-32 payloads into UTF-8. Applications that accept non-UTF-8 text must
58
+ normalize before scanning if they expect semantic text detection rather than raw
59
+ byte scanning.
60
+
61
+ ## Fail-closed behavior in blocking mode
62
+
63
+ Vendored libinjection v4 can return parser errors, and Rack can raise parameter/cookie parser errors before values are available for semantic scanning. The middleware exposes `parser_errors:` so applications can choose report, block, or raise behavior. The default `:auto` policy reports parser errors in report mode and blocks them in block mode.
64
+
65
+ Inputs beyond `max_value_bytes` or deeper than `max_depth` are not scanned. The middleware exposes `skipped_inputs:` so applications can choose report, block, or allow behavior. The default `:auto` policy reports skipped input in report mode and blocks it in block mode.
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+ require "rbconfig"
5
+
6
+ USE_SYSTEM = arg_config("--use-system-libinjection") || ENV["LIBINJECTION_USE_SYSTEM"] == "1"
7
+
8
+ EXT_DIR = __dir__
9
+ PRIMARY_VENDOR_DIR = File.join(EXT_DIR, "vendor", "libinjection")
10
+
11
+ REQUIRED_VENDOR_FILES = %w[
12
+ src/libinjection.h
13
+ src/libinjection_error.h
14
+ src/libinjection_sqli.h
15
+ src/libinjection_sqli_data.h
16
+ src/libinjection_xss.h
17
+ src/libinjection_html5.h
18
+ src/libinjection_sqli.c
19
+ src/libinjection_xss.c
20
+ src/libinjection_html5.c
21
+ ].freeze
22
+
23
+
24
+ def compiler_version
25
+ cc = RbConfig::CONFIG.fetch("CC", "cc")
26
+ `#{cc} --version 2>&1`
27
+ rescue StandardError
28
+ ""
29
+ end
30
+
31
+ def gcc_without_clang?
32
+ version = compiler_version.downcase
33
+ version.include?("gcc") && !version.include?("clang")
34
+ end
35
+ def vendor_ready?(dir)
36
+ File.file?(File.join(dir, ".vendored")) && REQUIRED_VENDOR_FILES.all? { |path| File.file?(File.join(dir, path)) }
37
+ end
38
+
39
+ def abort_missing_vendor!
40
+ abort <<~MSG
41
+ libinjection vendored sources are missing.
42
+
43
+ Run:
44
+ ruby script/vendor_libs.rb
45
+
46
+ Security note: extconf.rb does not auto-download native sources during build.
47
+ MSG
48
+ end
49
+
50
+ def find_vendor_dir
51
+ candidates = [PRIMARY_VENDOR_DIR]
52
+
53
+ dir = __dir__
54
+ 6.times do
55
+ candidates << File.join(dir, "ext", "libinjection", "vendor", "libinjection")
56
+ dir = File.dirname(dir)
57
+ end
58
+
59
+ candidates.map! { |path| File.expand_path(path) }
60
+ candidates.uniq!
61
+
62
+ abort_missing_vendor! unless vendor_ready?(PRIMARY_VENDOR_DIR)
63
+ candidates.find { |path| vendor_ready?(path) }
64
+ end
65
+
66
+ def configure_system!
67
+ puts "Building with SYSTEM libinjection"
68
+
69
+ if find_executable("pkg-config")
70
+ cflags = `pkg-config --cflags libinjection 2>/dev/null`.strip
71
+ libs = `pkg-config --libs libinjection 2>/dev/null`.strip
72
+ $CPPFLAGS << " #{cflags}" unless cflags.empty?
73
+ $libs << " #{libs}" unless libs.empty?
74
+ end
75
+
76
+ abort "libinjection.h is required" unless have_header("libinjection.h")
77
+ abort "libinjection_sqli.h is required" unless have_header("libinjection_sqli.h")
78
+ abort "libinjection_xss.h is required" unless have_header("libinjection_xss.h")
79
+ abort "libinjection library is required" unless have_library("injection", "libinjection_sqli")
80
+ abort "libinjection_version() is required" unless have_func("libinjection_version")
81
+ end
82
+
83
+ def configure_vendored!(vendor_dir)
84
+ abort "libinjection vendored sources are missing" unless vendor_dir
85
+
86
+ versions = File.read(File.join(vendor_dir, ".vendored"))
87
+ puts "Building with VENDORED libinjection from #{vendor_dir}"
88
+ puts " #{versions.tr("\n", ", ")}"
89
+
90
+ src_dir = File.join(vendor_dir, "src")
91
+ $CPPFLAGS << " -I#{src_dir}"
92
+ $srcs = %w[libinjection_ext.c libinjection_sqli.c libinjection_xss.c libinjection_html5.c]
93
+ $VPATH = [EXT_DIR, src_dir]
94
+ end
95
+
96
+ $CFLAGS << " -std=c99 -Wall -Wextra -O3"
97
+ $CFLAGS << " -Wno-unused-function -Wno-unused-parameter"
98
+
99
+ if ENV["LIBINJECTION_SANITIZE"] == "1"
100
+ $CFLAGS << " -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer"
101
+ $LDFLAGS << " -fsanitize=address,undefined"
102
+ end
103
+
104
+ $CFLAGS << " -Wno-enum-int-mismatch" if gcc_without_clang?
105
+ $warnflags = ""
106
+
107
+ unless $CFLAGS.include?("LI_NOGVL_THRESHOLD")
108
+ $CFLAGS << " -DLI_NOGVL_THRESHOLD=1024"
109
+ end
110
+
111
+ USE_SYSTEM ? configure_system! : configure_vendored!(find_vendor_dir)
112
+
113
+ create_makefile("libinjection/libinjection_native")