perimeter_x 2.2.1 → 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/changelog.md +10 -0
- data/lib/perimeter_x.rb +10 -5
- data/lib/perimeterx/utils/px_constants.rb +2 -1
- data/lib/perimeterx/utils/px_template_factory.rb +2 -3
- data/lib/perimeterx/utils/templates/block_template.mustache +32 -163
- data/lib/perimeterx/version.rb +1 -1
- data/px_metadata.json +28 -0
- data/readme.md +7 -2
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7321ac61fcf5c1b2b8a573eb23744d807e0af5e430a3468cb4704e8d36c7f8a9
|
4
|
+
data.tar.gz: 5bd64b4340535a52d2f91db99cabb673693dec8dbf1526dd00d2aa200743a444
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3037218a40b007fd7a0aaf93c6fe2e367b622042f2653c2e08e3c9a5d53d12d0107374d81e841d46e9320562d95c2ac4957040d7cb3a932c971ab2a1ff16aed2
|
7
|
+
data.tar.gz: 5de6563d962cee1d9ba90ac370ecb4a0a8679dfab9a1ab69b2dbcfaafe54d3ce05b12f31b8c57f1a5b129299e215dd0e8f7e488bbadf03308099104ec7f2ab60
|
data/LICENSE.txt
CHANGED
data/changelog.md
CHANGED
@@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
7
7
|
|
8
|
+
## [2.3.0] - 2022-04-10
|
9
|
+
|
10
|
+
### Added
|
11
|
+
|
12
|
+
- Custom logo in block JSON response
|
13
|
+
|
14
|
+
### Changed
|
15
|
+
|
16
|
+
- Updated block page to use new template
|
17
|
+
|
8
18
|
## [2.2.1] - 2020-09-27
|
9
19
|
### Fixed
|
10
20
|
- bypass_monitor_header type validation
|
data/lib/perimeter_x.rb
CHANGED
@@ -56,19 +56,22 @@ module PxModule
|
|
56
56
|
end
|
57
57
|
|
58
58
|
is_mobile = px_ctx.context[:cookie_origin] == 'header' ? '1' : '0'
|
59
|
-
action = px_ctx.context[:block_action][0,1]
|
59
|
+
action = px_ctx.context[:block_action][0,1]
|
60
|
+
block_script_uri = "/captcha.js?a=#{action}&u=#{px_ctx.context[:uuid]}&v=#{px_ctx.context[:vid]}&m=#{is_mobile}"
|
60
61
|
|
61
62
|
if px_config[:first_party_enabled]
|
62
63
|
px_template_object = {
|
63
64
|
js_client_src: "/#{px_config[:app_id][2..-1]}/init.js",
|
64
|
-
block_script: "/#{px_config[:app_id][2..-1]}/captcha/#{px_config[:app_id]}
|
65
|
-
host_url: "/#{px_config[:app_id][2..-1]}/xhr"
|
65
|
+
block_script: "/#{px_config[:app_id][2..-1]}/captcha/#{px_config[:app_id]}#{block_script_uri}",
|
66
|
+
host_url: "/#{px_config[:app_id][2..-1]}/xhr",
|
67
|
+
alt_block_script: "//#{PxModule::ALT_CAPTCHA_HOST}/#{px_config[:app_id]}#{block_script_uri}"
|
66
68
|
}
|
67
69
|
else
|
68
70
|
px_template_object = {
|
69
71
|
js_client_src: "//#{PxModule::CLIENT_HOST}/#{px_config[:app_id]}/main.min.js",
|
70
|
-
block_script: "//#{PxModule::CAPTCHA_HOST}/#{px_config[:app_id]}
|
71
|
-
host_url: "https://collector-#{px_config[:app_id]}.perimeterx.net"
|
72
|
+
block_script: "//#{PxModule::CAPTCHA_HOST}/#{px_config[:app_id]}#{block_script_uri}",
|
73
|
+
host_url: "https://collector-#{px_config[:app_id]}.perimeterx.net",
|
74
|
+
alt_block_script: "//#{PxModule::ALT_CAPTCHA_HOST}/#{px_config[:app_id]}#{block_script_uri}"
|
72
75
|
}
|
73
76
|
end
|
74
77
|
|
@@ -92,6 +95,8 @@ module PxModule
|
|
92
95
|
:vid => px_ctx.context[:vid],
|
93
96
|
:hostUrl => "https://collector-#{px_config[:app_id]}.perimeterx.net",
|
94
97
|
:blockScript => px_template_object[:block_script],
|
98
|
+
:altBlockScript => px_template_object[:alt_block_script],
|
99
|
+
:customLogo => px_config[:custom_logo]
|
95
100
|
}
|
96
101
|
|
97
102
|
render :json => hash_json
|
@@ -36,11 +36,11 @@ module PxModule
|
|
36
36
|
PROP_APP_ID = :appId
|
37
37
|
PROP_VID = :vid
|
38
38
|
PROP_UUID = :uuid
|
39
|
-
PROP_LOGO_VISIBILITY = :logoVisibility
|
40
39
|
PROP_CUSTOM_LOGO = :customLogo
|
41
40
|
PROP_CSS_REF = :cssRef
|
42
41
|
PROP_JS_REF = :jsRef
|
43
42
|
PROP_BLOCK_SCRIPT = :blockScript
|
43
|
+
PROP_ALT_BLOCK_SCRIPT = :altBlockScript
|
44
44
|
PROP_JS_CLIENT_SRC = :jsClientSrc
|
45
45
|
PROP_HOST_URL = :hostUrl
|
46
46
|
PROP_FIRST_PARTY_ENABLED = :firstPartyEnabled
|
@@ -48,6 +48,7 @@ module PxModule
|
|
48
48
|
# Hosts
|
49
49
|
CLIENT_HOST = 'client.perimeterx.net'
|
50
50
|
CAPTCHA_HOST = 'captcha.px-cdn.net'
|
51
|
+
ALT_CAPTCHA_HOST = 'captcha.px-cloud.net'
|
51
52
|
|
52
53
|
VISIBLE = 'visible'
|
53
54
|
HIDDEN = 'hidden'
|
@@ -6,7 +6,7 @@ module PxModule
|
|
6
6
|
def self.get_template(px_ctx, px_config, px_template_object)
|
7
7
|
logger = px_config[:logger]
|
8
8
|
if (px_config[:challenge_enabled] && px_ctx.context[:block_action] == 'challenge')
|
9
|
-
logger.debug('PxTemplateFactory[get_template]: px
|
9
|
+
logger.debug('PxTemplateFactory[get_template]: px challenge triggered')
|
10
10
|
return px_ctx.context[:block_action_data].html_safe
|
11
11
|
end
|
12
12
|
|
@@ -23,15 +23,14 @@ module PxModule
|
|
23
23
|
Mustache.template_file = "#{File.dirname(__FILE__) }/templates/#{template_type}#{PxModule::TEMPLATE_EXT}"
|
24
24
|
|
25
25
|
view[PxModule::PROP_APP_ID] = px_config[:app_id]
|
26
|
-
view[PxModule::PROP_REF_ID] = px_ctx.context[:uuid]
|
27
26
|
view[PxModule::PROP_VID] = px_ctx.context[:vid]
|
28
27
|
view[PxModule::PROP_UUID] = px_ctx.context[:uuid]
|
29
28
|
view[PxModule::PROP_CUSTOM_LOGO] = px_config[:custom_logo]
|
30
29
|
view[PxModule::PROP_CSS_REF] = px_config[:css_ref]
|
31
30
|
view[PxModule::PROP_JS_REF] = px_config[:js_ref]
|
32
31
|
view[PxModule::PROP_HOST_URL] = px_template_object[:host_url]
|
33
|
-
view[PxModule::PROP_LOGO_VISIBILITY] = px_config[:custom_logo] ? PxModule::VISIBLE : PxModule::HIDDEN
|
34
32
|
view[PxModule::PROP_BLOCK_SCRIPT] = px_template_object[:block_script]
|
33
|
+
view[PxModule::PROP_ALT_BLOCK_SCRIPT] = px_template_object[:alt_block_script]
|
35
34
|
view[PxModule::PROP_JS_CLIENT_SRC] = px_template_object[:js_client_src]
|
36
35
|
view[PxModule::PROP_FIRST_PARTY_ENABLED] = px_ctx.context[:first_party_enabled]
|
37
36
|
|
@@ -3,173 +3,42 @@
|
|
3
3
|
<head>
|
4
4
|
<meta charset="utf-8">
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
|
-
<
|
7
|
-
<
|
8
|
-
<style>
|
9
|
-
html, body {
|
10
|
-
margin: 0;
|
11
|
-
padding: 0;
|
12
|
-
font-family: 'Open Sans', sans-serif;
|
13
|
-
color: #000;
|
14
|
-
}
|
15
|
-
|
16
|
-
a {
|
17
|
-
color: #c5c5c5;
|
18
|
-
text-decoration: none;
|
19
|
-
}
|
20
|
-
|
21
|
-
.container {
|
22
|
-
align-items: center;
|
23
|
-
display: flex;
|
24
|
-
flex: 1;
|
25
|
-
justify-content: space-between;
|
26
|
-
flex-direction: column;
|
27
|
-
height: 100%;
|
28
|
-
}
|
29
|
-
|
30
|
-
.container > div {
|
31
|
-
width: 100%;
|
32
|
-
display: flex;
|
33
|
-
justify-content: center;
|
34
|
-
}
|
35
|
-
|
36
|
-
.container > div > div {
|
37
|
-
display: flex;
|
38
|
-
width: 80%;
|
39
|
-
}
|
40
|
-
|
41
|
-
.customer-logo-wrapper {
|
42
|
-
padding-top: 2rem;
|
43
|
-
flex-grow: 0;
|
44
|
-
background-color: #fff;
|
45
|
-
visibility: {{logoVisibility}};
|
46
|
-
}
|
47
|
-
|
48
|
-
.customer-logo {
|
49
|
-
border-bottom: 1px solid #000;
|
50
|
-
}
|
51
|
-
|
52
|
-
.customer-logo > img {
|
53
|
-
padding-bottom: 1rem;
|
54
|
-
max-height: 50px;
|
55
|
-
max-width: 100%;
|
56
|
-
}
|
57
|
-
|
58
|
-
.page-title-wrapper {
|
59
|
-
flex-grow: 2;
|
60
|
-
}
|
61
|
-
|
62
|
-
.page-title {
|
63
|
-
flex-direction: column-reverse;
|
64
|
-
}
|
65
|
-
|
66
|
-
.content-wrapper {
|
67
|
-
flex-grow: 5;
|
68
|
-
}
|
69
|
-
|
70
|
-
.content {
|
71
|
-
flex-direction: column;
|
72
|
-
}
|
73
|
-
|
74
|
-
.page-footer-wrapper {
|
75
|
-
align-items: center;
|
76
|
-
flex-grow: 0.2;
|
77
|
-
background-color: #000;
|
78
|
-
color: #c5c5c5;
|
79
|
-
font-size: 70%;
|
80
|
-
}
|
81
|
-
|
82
|
-
@media (min-width: 768px) {
|
83
|
-
html, body {
|
84
|
-
height: 100%;
|
85
|
-
}
|
86
|
-
}
|
87
|
-
</style>
|
88
|
-
<!-- Custom CSS -->
|
6
|
+
<meta name="description" content="px-captcha">
|
7
|
+
<title>Access to this page has been denied</title>
|
89
8
|
{{#cssRef}}
|
90
9
|
<link rel="stylesheet" type="text/css" href="{{{cssRef}}}"/>
|
91
10
|
{{/cssRef}}
|
92
11
|
</head>
|
93
|
-
|
94
12
|
<body>
|
95
|
-
<
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
<p>
|
112
|
-
Access to this page has been denied because we believe you are using automation tools to browse the
|
113
|
-
website.
|
114
|
-
</p>
|
115
|
-
<p>
|
116
|
-
This may happen as a result of the following:
|
117
|
-
</p>
|
118
|
-
<ul>
|
119
|
-
<li>
|
120
|
-
Javascript is disabled or blocked by an extension (ad blockers for example)
|
121
|
-
</li>
|
122
|
-
<li>
|
123
|
-
Your browser does not support cookies
|
124
|
-
</li>
|
125
|
-
</ul>
|
126
|
-
<p>
|
127
|
-
Please make sure that Javascript and cookies are enabled on your browser and that you are not blocking
|
128
|
-
them from loading.
|
129
|
-
</p>
|
130
|
-
<p>
|
131
|
-
Reference ID: #{{refId}}
|
132
|
-
</p>
|
133
|
-
</div>
|
134
|
-
</div>
|
135
|
-
<div class="page-footer-wrapper">
|
136
|
-
<div class="page-footer">
|
137
|
-
<p>
|
138
|
-
Powered by
|
139
|
-
<a href="https://www.perimeterx.com/whywasiblocked">PerimeterX</a>
|
140
|
-
, Inc.
|
141
|
-
</p>
|
142
|
-
</div>
|
143
|
-
</div>
|
144
|
-
</section>
|
145
|
-
<!-- Px -->
|
146
|
-
<script>
|
147
|
-
window._pxAppId = '{{appId}}';
|
148
|
-
window._pxJsClientSrc = '{{{jsClientSrc}}}';
|
149
|
-
window._pxFirstPartyEnabled = {{firstPartyEnabled}};
|
150
|
-
window._pxVid = '{{vid}}';
|
151
|
-
window._pxUuid = '{{uuid}}';
|
152
|
-
window._pxHostUrl = '{{{hostUrl}}}';
|
153
|
-
</script>
|
154
|
-
<script>
|
155
|
-
var s = document.createElement('script');
|
156
|
-
s.src = '{{{blockScript}}}';
|
157
|
-
var p = document.getElementsByTagName('head')[0];
|
158
|
-
p.insertBefore(s, null);
|
159
|
-
if ({{firstPartyEnabled}}) {
|
160
|
-
s.onerror = function () {
|
161
|
-
s = document.createElement('script');
|
162
|
-
var suffixIndex = '{{{blockScript}}}'.indexOf('captcha.js');
|
163
|
-
var temperedBlockScript = '{{{blockScript}}}'.substring(suffixIndex);
|
164
|
-
s.src = '//captcha.px-cdn.net/{{appId}}/' + temperedBlockScript;
|
165
|
-
p.parentNode.insertBefore(s, p);
|
13
|
+
<script>
|
14
|
+
window._pxVid = '{{vid}}';
|
15
|
+
window._pxUuid = '{{uuid}}';
|
16
|
+
window._pxAppId = '{{appId}}';
|
17
|
+
window._pxCustomLogo = '{{customLogo}}';
|
18
|
+
window._pxHostUrl = '{{hostUrl}}';
|
19
|
+
window._pxJsClientSrc = '{{jsClientSrc}}';
|
20
|
+
window._pxFirstPartyEnabled = {{firstPartyEnabled}};
|
21
|
+
var script = document.createElement('script');
|
22
|
+
script.src = '{{blockScript}}';
|
23
|
+
document.head.appendChild(script);
|
24
|
+
script.onerror = function () {
|
25
|
+
script = document.createElement('script');
|
26
|
+
script.src = '{{altBlockScript}}';
|
27
|
+
script.onerror = window._pxDisplayErrorMessage;
|
28
|
+
document.head.appendChild(script);
|
166
29
|
};
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
30
|
+
window._pxDisplayErrorMessage = function () {
|
31
|
+
var style = document.createElement('style');
|
32
|
+
style.innerText = '@import url(https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap);body{background-color:#fafbfc}@media (max-width:480px){body{background-color:#fff}}.px-captcha-error-container{position:fixed;height:328px;background-color:#fff;font-family:Roboto,sans-serif}.px-captcha-error-header{color:#f0f1f2;font-size:29px;margin:67px 0 33px;font-weight:500;line-height:.83;text-align:center}.px-captcha-error-message{color:#f0f1f2;font-size:18px;margin:0 0 29px;line-height:1.33;text-align:center}div.px-captcha-error-button{text-align:center;line-height:50px;width:253px;margin:auto;border-radius:25px;border:solid 1px #f0f1f2;font-size:20px;color:#f0f1f2}div.px-captcha-error-wrapper{margin:23px 0 0}div.px-captcha-error{margin:auto;text-align:center;width:400px;height:30px;font-size:12px;background-color:#fcf0f2;color:#ce0e2d}img.px-captcha-error{margin:6px 10px -2px 0}@media (min-width:620px){.px-captcha-error-container{width:528px;top:50%;left:50%;margin-top:-164px;margin-left:-264px;border-radius:3px;box-shadow:0 2px 9px -1px rgba(0,0,0,.13)}}@media (min-width:481px) and (max-width:620px){.px-captcha-error-container{width:85%;top:50%;left:50%;margin-top:-164px;margin-left:-42.5%;border-radius:3px;box-shadow:0 2px 9px -1px rgba(0,0,0,.13)}}@media (max-width:480px){.px-captcha-error-container{width:528px;top:50%;left:50%;margin-top:-164px;margin-left:-264px}}';
|
33
|
+
document.head.appendChild(style);
|
34
|
+
var div = document.createElement('div');
|
35
|
+
div.className = 'px-captcha-error-container';
|
36
|
+
div.innerHTML = '<div class="px-captcha-error-header">Before we continue...</div><div class="px-captcha-error-message">Press & Hold to confirm you are<br>a human (and not a bot).</div><div class="px-captcha-error-button">Press & Hold</div><div class="px-captcha-error-wrapper"><div class="px-captcha-error"><img class="px-captcha-error" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABMAAAAQCAMAAADDGrRQAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAABFUExURUdwTNYELOEGONQILd0AONwALtwEL+AAL9MFLfkJSNQGLdMJLdQJLdQGLdQKLtYFLNcELdUGLdcBL9gFL88OLdUFLNEOLglBhT4AAAAXdFJOUwC8CqgNIRgRoAS1dWWuR4RTjzgryZpYblfkcAAAAI9JREFUGNNdj+sWhCAIhAdvqGVa1r7/oy6RZ7eaH3D4ZACBIed9wlOOMtUnSrEmZ6cHa9YAIfsbCkWrdpi/c50Bk2CO9mNLdMAu03wJA3HpEnfpxbyOg6ruyx8JJi6KNstnslp1dbPd9GnqmuYq7mmcv1zjnbQw8cV0xzkqo+fX1zkjUOO7wnrInUTxJiruC3vtBNRoQQn2AAAAAElFTkSuQmCC">Please check your network connection or disable your ad-blocker.</div></div>';
|
37
|
+
document.body.appendChild(div);
|
38
|
+
};
|
39
|
+
</script>
|
40
|
+
{{#jsRef}}
|
41
|
+
<script src="{{{jsRef}}}"></script>
|
42
|
+
{{/jsRef}}
|
174
43
|
</body>
|
175
|
-
</html>
|
44
|
+
</html>
|
data/lib/perimeterx/version.rb
CHANGED
data/px_metadata.json
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
{
|
2
|
+
"version": "2.3.0",
|
3
|
+
"supported_features": [
|
4
|
+
"additional_activity_handler",
|
5
|
+
"advanced_blocking_response",
|
6
|
+
"batched_activities",
|
7
|
+
"block_activity",
|
8
|
+
"block_page_captcha",
|
9
|
+
"block_page_rate_limit",
|
10
|
+
"bypass_monitor_header",
|
11
|
+
"client_ip_extraction",
|
12
|
+
"cookie_v3",
|
13
|
+
"css_ref",
|
14
|
+
"custom_logo",
|
15
|
+
"logger",
|
16
|
+
"filter_by_route",
|
17
|
+
"first_party",
|
18
|
+
"js_ref",
|
19
|
+
"mobile_support",
|
20
|
+
"module_enable",
|
21
|
+
"module_mode",
|
22
|
+
"page_requested_activity",
|
23
|
+
"vid_extraction",
|
24
|
+
"risk_api",
|
25
|
+
"sensitive_headers",
|
26
|
+
"sensitive_routes"
|
27
|
+
]
|
28
|
+
}
|
data/readme.md
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
[PerimeterX](http://www.perimeterx.com) Ruby SDK
|
6
6
|
=============================================================
|
7
7
|
|
8
|
-
> Latest stable version: [v2.
|
8
|
+
> Latest stable version: [v2.3.0](https://rubygems.org/gems/perimeter_x)
|
9
9
|
|
10
10
|
Table of Contents
|
11
11
|
-----------------
|
@@ -31,6 +31,7 @@ Table of Contents
|
|
31
31
|
* [Update Configuration on Runtime](#update-config)
|
32
32
|
* [First Party](#first-party)
|
33
33
|
|
34
|
+
**[Additional Information](#additional-information)**
|
34
35
|
**[Contributing](#contributing)**
|
35
36
|
|
36
37
|
<a name="Usage"></a>
|
@@ -350,8 +351,12 @@ Default: true
|
|
350
351
|
params[:first_party_enabled] = false
|
351
352
|
```
|
352
353
|
|
354
|
+
<a name="additional_information"></a> Additional Information
|
355
|
+
------------------------------
|
356
|
+
### URI Delimiters
|
357
|
+
PerimeterX processes URI paths with general- and sub-delimiters according to RFC 3986. General delimiters (e.g., `?`, `#`) are used to separate parts of the URI. Sub-delimiters (e.g., `$`, `&`) are not used to split the URI as they are considered valid characters in the URI path.
|
353
358
|
|
354
|
-
<a name="contributing"></a
|
359
|
+
<a name="contributing"></a> Contributing
|
355
360
|
------------------------------
|
356
361
|
The following steps are welcome when contributing to our project.
|
357
362
|
###Fork/Clone
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: perimeter_x
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nitzan Goldfeder
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -191,6 +191,7 @@ files:
|
|
191
191
|
- lib/perimeterx/utils/templates/ratelimit.mustache
|
192
192
|
- lib/perimeterx/version.rb
|
193
193
|
- perimeter_x.gemspec
|
194
|
+
- px_metadata.json
|
194
195
|
- readme.md
|
195
196
|
homepage: https://www.perimeterx.com
|
196
197
|
licenses:
|
@@ -215,7 +216,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
215
216
|
- !ruby/object:Gem::Version
|
216
217
|
version: '0'
|
217
218
|
requirements: []
|
218
|
-
rubygems_version: 3.0.3
|
219
|
+
rubygems_version: 3.0.3.1
|
219
220
|
signing_key:
|
220
221
|
specification_version: 4
|
221
222
|
summary: PerimeterX ruby implmentation
|