camada 0.2.1 → 0.2.2
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/README.md +1 -3
- data/lib/camada/constants.rb +2 -2
- data/lib/camada/snapshot/parse.rb +38 -20
- data/lib/camada/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b73c94df7715601ab82c6a470ccd976e6bb934f6dcf6c8c54c5bfc3fe00b97df
|
|
4
|
+
data.tar.gz: 94bd0d19b9259b6e03ae0e6df41ca717afbd9aa915d7b1f273c99b89b3dab425
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6b6a3f8d9b8185f0979dccfb92498bc6fac1b62fdb1221f0130f69ca606edf5783ef25df6414e11997a13bb1a1dc5b18b874b5e3b570bef8e980230626c55fa6
|
|
7
|
+
data.tar.gz: 0e879615dbc999eed321ca8bc7821498622ee91a0be08345c846508a9cbd74aea4eab42f55eb7468fbc27482d593c190fa8e94b66c19e7482731dc3cf5159100
|
data/README.md
CHANGED
|
@@ -170,9 +170,7 @@ valid `_cch`, then `nil`. A non-page (non-HTML) request also gets `nil` and is s
|
|
|
170
170
|
`sdk-ruby` is an in-app tap: status, latency, session, the beacon's browser signals and your
|
|
171
171
|
outcomes. Puma hands the env to Rack as binary strings; the middleware reads them as UTF-8 with
|
|
172
172
|
invalid bytes replaced (U+FFFD) before matching and shipping, so a stray high byte in a header
|
|
173
|
-
neither escapes a rule nor costs the batch it rides in. The Rack env carries no wire header order (`hord` is the env's order), so
|
|
174
|
-
reads no HEADER_ORDER signal from this tap, and it never scores the absence of header order, ASN,
|
|
175
|
-
country or a TLS fingerprint against a request; ASN and country it resolves itself. Enforcement
|
|
173
|
+
neither escapes a rule nor costs the batch it rides in. The Rack env carries no wire header order (`hord` is the env's order), so this tap ships no header-order signal; camada resolves ASN and country itself. Enforcement
|
|
176
174
|
at this position covers ip, path, user-agent and header conditions — ASN, country and TLS entries
|
|
177
175
|
fail open in-app. `matches` patterns are JS regexes read by Ruby's Onigmo: named groups are
|
|
178
176
|
native, `[^]` and `\cX` are translated, `^`/`$` become `\A`/`\z` (Ruby's are line anchors,
|
data/lib/camada/constants.rb
CHANGED
|
@@ -4,8 +4,8 @@ require "json"
|
|
|
4
4
|
|
|
5
5
|
module Camada
|
|
6
6
|
# Tap identifier this SDK claims on the wire. The server validates against its own enum and
|
|
7
|
-
#
|
|
8
|
-
# itself
|
|
7
|
+
# decides itself what each tap can see: an SDK can never grant
|
|
8
|
+
# itself more than that, only name its position — and an unknown name is silently read as a
|
|
9
9
|
# proxy, so this literal is load-bearing.
|
|
10
10
|
TAP = "sdk-ruby"
|
|
11
11
|
|
|
@@ -149,9 +149,10 @@ module Camada
|
|
|
149
149
|
|
|
150
150
|
# A pattern this runtime rejects never matches, and never throws (fail open). Patterns are
|
|
151
151
|
# authored as JS regexes (the analyst validates them with `new RegExp`), so the JS spellings
|
|
152
|
-
# Onigmo reads differently are translated first — see js_to_onigmo; \d \w
|
|
153
|
-
# Ruby by default, as JS reads them
|
|
154
|
-
#
|
|
152
|
+
# Onigmo reads differently are translated first — see js_to_onigmo; \d \w are ASCII in
|
|
153
|
+
# Ruby by default, as JS reads them, and \b \B are emitted as (?a:\b) for the same reason.
|
|
154
|
+
# An astral char is one code point here and two UTF-16 units in JS,
|
|
155
|
+
# so `.`, `[^a]` and `\S` count it once (an accepted gap, camada-all-vuvo).
|
|
155
156
|
# A quantifier right after a raw astral char repeats the whole char here but only its low unit in JS,
|
|
156
157
|
# so js_to_onigmo answers as V8 does: the char when the count allows 1, else `(?!)` (camada-all-5lra, see astral_count).
|
|
157
158
|
# Where a lookbehind ends up holding `(?!)`, Onigmo cannot compile it, so compile_regex returns nil and the rule never matches.
|
|
@@ -372,6 +373,7 @@ module Camada
|
|
|
372
373
|
# that split (camada-all-ccjo, camada-all-fe1r). A char whose fold is several chars (ß, ff, İ; raw, `\u00df` or `\ß`)
|
|
373
374
|
# becomes `(?-i:ß)` there, pulled out of a positive class like `\S` unless a `-` is beside it (camada-all-ozix):
|
|
374
375
|
# JS /i matches it only to itself, and Onigmo would read it as its fold alone (version-dependent).
|
|
376
|
+
# One of the 63 iota-subscript chars in a positive class is pulled out the same way in any context (camada-all-rmje).
|
|
375
377
|
# In a class `\c` is a control char only before a letter, digit or `_` (ClassControlLetter); before
|
|
376
378
|
# anything else (`[\c*]`) it is a literal `\` then `c` (camada-all-mqcb).
|
|
377
379
|
# A `{m,n}` bound above Onigmo's 100000 is clamped: m to 100000, and an n above it drops (`{m,}`,
|
|
@@ -398,13 +400,21 @@ module Camada
|
|
|
398
400
|
bars = [-1] # per open group, the root first: the index of the last `|` in it or an ancestor
|
|
399
401
|
lbs = [false] # per open group, the root first: inside a lookbehind (camada-all-ccjo)
|
|
400
402
|
lit_end = -1 # the pattern index right after the last literal emitted inside a lookbehind
|
|
401
|
-
class_start = nil # the out index of an open positive class's `[`
|
|
403
|
+
class_start = nil # the out index of an open positive class's `[`
|
|
402
404
|
class_alts = [] # the `\S`/`\W`/`\D` and multi-fold chars that class held
|
|
403
405
|
last_named = {} # name -> the index of its last opener
|
|
404
406
|
while i < n
|
|
405
407
|
ch = pattern[i]
|
|
408
|
+
w = multi_fold(pattern, i)
|
|
409
|
+
iota = w && IOTA_SUBSCRIPT.match?(w[2])
|
|
410
|
+
if !in_class && !lbs.last && iota
|
|
411
|
+
out << "(?-i:#{w[0]})" # outside a lookbehind (the branch below) and a class; JS /i matches it only to itself (camada-all-zn38)
|
|
412
|
+
i += w[1]
|
|
413
|
+
next
|
|
414
|
+
end
|
|
406
415
|
# in a lookbehind (positive class too, unless a range end) a multi-fold char becomes `(?-i:c)` (camada-all-ozix)
|
|
407
|
-
|
|
416
|
+
# an iota-subscript char in a positive class is pulled out the same way, in a lookbehind or not (camada-all-rmje)
|
|
417
|
+
if (lbs.last || (in_class && iota)) && (!in_class || class_start) && w &&
|
|
408
418
|
!(in_class && (pattern[i - 1] == "-" || pattern[i + w[1]] == "-"))
|
|
409
419
|
in_class ? class_alts << "(?-i:#{w[0]})" : out << "(?-i:#{w[0]})"
|
|
410
420
|
i += w[1]
|
|
@@ -423,7 +433,7 @@ module Camada
|
|
|
423
433
|
next
|
|
424
434
|
end
|
|
425
435
|
l = pattern[i + 1]
|
|
426
|
-
if in_class && class_start && (l == "S" || l == "W" || l == "D")
|
|
436
|
+
if in_class && class_start && lbs.last && (l == "S" || l == "W" || l == "D")
|
|
427
437
|
class_alts << (l == "S" ? JS_SPACE["S"] : "\\#{l}") # spliced in at the `]`: a positive class holding one gains multi-char folds
|
|
428
438
|
pull_end = i + 2
|
|
429
439
|
i += 2
|
|
@@ -435,6 +445,11 @@ module Camada
|
|
|
435
445
|
i += 2
|
|
436
446
|
next
|
|
437
447
|
end
|
|
448
|
+
if !in_class && (l == "b" || l == "B")
|
|
449
|
+
out << "(?a:\\#{l})" # Onigmo's \b reads é as a word char, JS (no u flag) as ASCII only
|
|
450
|
+
i += 2
|
|
451
|
+
next
|
|
452
|
+
end
|
|
438
453
|
if l == "c" && pattern[i + 2]&.match?(in_class ? /[A-Za-z0-9_]/ : /[A-Za-z]/) # JS Annex B: `[\c1]` too
|
|
439
454
|
out << Regexp.escape((pattern[i + 2].ord & 0x1F).chr)
|
|
440
455
|
i += 3
|
|
@@ -457,12 +472,13 @@ module Camada
|
|
|
457
472
|
i += 1
|
|
458
473
|
next
|
|
459
474
|
end
|
|
460
|
-
kept = !l.match?(/[A-Za-z]/) || pattern.match?(JS_ESCAPE, i + 1) ||
|
|
475
|
+
kept = !l.match?(/[A-Za-z]/) || pattern.match?(JS_ESCAPE, i + 1) ||
|
|
461
476
|
(l == "k" && named)
|
|
462
477
|
l = "\\\\c" if l == "c" # JS reads `\c` before a non-letter as text
|
|
463
478
|
if kept
|
|
464
479
|
e = (pattern.match(/\G\\\d+/n, i) || pattern.match(JS_ATOM, i))[0] # the whole escape: a lookbehind must not split `\x41`
|
|
465
|
-
|
|
480
|
+
# JS reads \xHH above 0x7F as that code point, Onigmo as a raw byte (camada-all-w2w4)
|
|
481
|
+
out << (e.match?(/\A\\x[89a-fA-F]\h\z/) ? "\\u00#{e[2..]}" : e)
|
|
466
482
|
i += e.length
|
|
467
483
|
next
|
|
468
484
|
end
|
|
@@ -487,12 +503,12 @@ module Camada
|
|
|
487
503
|
else
|
|
488
504
|
out << "]"
|
|
489
505
|
end
|
|
490
|
-
lit_end = i + 1 if class_start # Onigmo merges a one-char class into the literals beside it
|
|
506
|
+
lit_end = i + 1 if class_start && lbs.last # Onigmo merges a one-char class into the literals beside it
|
|
491
507
|
class_start = nil
|
|
492
508
|
i += 1
|
|
493
509
|
next
|
|
494
510
|
end
|
|
495
|
-
if ch == "-" && (pull_end == i || solo_end == i || pattern.match?(class_start ? /\G-\\[sSWD]/ : /\G-\\[sS]/, i))
|
|
511
|
+
if ch == "-" && (pull_end == i || solo_end == i || pattern.match?(class_start && lbs.last ? /\G-\\[sSWD]/ : /\G-\\[sS]/, i))
|
|
496
512
|
if pull_end == i # JS reads the atom after that dash as standalone: a dash right after it is text too
|
|
497
513
|
m = pattern.match(JS_ATOM, i + 1) # nil when the pattern ends on the dash
|
|
498
514
|
solo_end = m ? i + 1 + m[0].length : pattern.length
|
|
@@ -549,10 +565,8 @@ module Camada
|
|
|
549
565
|
i = j + 1
|
|
550
566
|
next
|
|
551
567
|
end
|
|
552
|
-
if !neg && lbs.last
|
|
553
|
-
|
|
554
|
-
class_start = out.length
|
|
555
|
-
end
|
|
568
|
+
out << "(?:)" if !neg && lbs.last && lit_end == i
|
|
569
|
+
class_start = out.length unless neg
|
|
556
570
|
out << pattern[i...j]
|
|
557
571
|
i = j
|
|
558
572
|
in_class = true
|
|
@@ -624,17 +638,21 @@ module Camada
|
|
|
624
638
|
str.encoding == Encoding::BINARY ? str : str.b
|
|
625
639
|
end
|
|
626
640
|
|
|
627
|
-
#
|
|
641
|
+
# The 63 Greek chars whose fold ends in an iota (ᾀ -> ἀι): Onigmo never matches one before a lookaround under /i.
|
|
642
|
+
IOTA_SUBSCRIPT = /\A[\u1F80-\u1FAF\u1FB2-\u1FB4\u1FB7\u1FBC\u1FC2-\u1FC4\u1FC7\u1FCC\u1FF2-\u1FF4\u1FF7\u1FFC]/
|
|
643
|
+
|
|
644
|
+
# [its text, its length, the char] when the char at i (raw UTF-8, `\uXXXX` or an identity escape `\ß`) has a case fold of
|
|
628
645
|
# several chars (ß, İ, ff: 104 of them), else nil. JS /i without `u` matches each of them only to itself.
|
|
629
646
|
def self.multi_fold(pattern, i)
|
|
630
647
|
if pattern[i] == "\\"
|
|
631
|
-
return (w = multi_fold(pattern, i + 1)) && [w[0], w[1] + 1] if pattern.getbyte(i + 1).to_i >= 0x80
|
|
648
|
+
return (w = multi_fold(pattern, i + 1)) && [w[0], w[1] + 1, w[2]] if pattern.getbyte(i + 1).to_i >= 0x80
|
|
632
649
|
|
|
633
|
-
m = pattern.match(/\G\\u(\h{4})/n, i)
|
|
634
|
-
return nil unless m && !(cp = m[1].hex).between?(0xD800, 0xDFFF)
|
|
650
|
+
m = pattern.match(/\G\\(?:u(\h{4})|x([89a-fA-F]\h))/n, i) # `\xHH` above 0x7F is the code point (camada-all-w2w4)
|
|
651
|
+
return nil unless m && !(cp = (m[1] || m[2]).hex).between?(0xD800, 0xDFFF)
|
|
635
652
|
|
|
636
653
|
c = cp.chr(Encoding::UTF_8)
|
|
637
|
-
len =
|
|
654
|
+
len = m[0].length
|
|
655
|
+
return [format("\\u%04x", cp), len] if c.downcase(:fold).length > 1 && m[2]
|
|
638
656
|
else
|
|
639
657
|
b = pattern.getbyte(i)
|
|
640
658
|
return nil unless b.between?(0xC2, 0xEF) # a 2- or 3-byte lead: every such char is in the BMP
|
|
@@ -642,7 +660,7 @@ module Camada
|
|
|
642
660
|
len = b < 0xE0 ? 2 : 3
|
|
643
661
|
c = pattern.byteslice(i, len).force_encoding(Encoding::UTF_8)
|
|
644
662
|
end
|
|
645
|
-
[pattern.byteslice(i, len), len] if c.valid_encoding? && c.downcase(:fold).length > 1
|
|
663
|
+
[pattern.byteslice(i, len), len, c] if c.valid_encoding? && c.downcase(:fold).length > 1
|
|
646
664
|
end
|
|
647
665
|
|
|
648
666
|
def self.brace_quantifier(match)
|
data/lib/camada/version.rb
CHANGED
|
@@ -6,6 +6,6 @@ module Camada
|
|
|
6
6
|
# camada-web, camada-mkt) parse this file with /^\s*VERSION = "([^"]+)"/m the way they parse
|
|
7
7
|
# a package.json. Plain X.Y.Z only, and @camada/ruby must stay in the analyst's SDK_NAMES:
|
|
8
8
|
# its sdkId drops anything else.
|
|
9
|
-
VERSION = "0.2.
|
|
9
|
+
VERSION = "0.2.2"
|
|
10
10
|
SDK_ID = "@camada/ruby/#{VERSION}".freeze
|
|
11
11
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: camada
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- camada
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: 'camada SDK for Ruby: inline enforcement of your snapshot (ordered custom
|
|
14
14
|
rules, then allow, block, challenge), first-party beacon and proof-of-work challenge,
|