kiosk-redteam 0.5.20
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 +7 -0
- data/CHANGELOG.md +158 -0
- data/LICENSE.txt +202 -0
- data/README.md +247 -0
- data/lib/kiosk/redteam/battery.rb +148 -0
- data/lib/kiosk/redteam/leak_scan.rb +104 -0
- data/lib/kiosk/redteam/profile.rb +58 -0
- data/lib/kiosk/redteam/runner.rb +37 -0
- data/lib/kiosk/redteam/scenario.rb +34 -0
- data/lib/kiosk/redteam/scenarios/cross_tenant_read.rb +57 -0
- data/lib/kiosk/redteam/scenarios/device_grant_role_self_selection.rb +117 -0
- data/lib/kiosk/redteam/scenarios/expired_kyc.rb +27 -0
- data/lib/kiosk/redteam/scenarios/forged_kyc.rb +27 -0
- data/lib/kiosk/redteam/scenarios/forged_user_id.rb +108 -0
- data/lib/kiosk/redteam/scenarios/kyc_attack.rb +43 -0
- data/lib/kiosk/redteam/scenarios/mandate_principal_swap.rb +32 -0
- data/lib/kiosk/redteam/scenarios/mandate_replay.rb +42 -0
- data/lib/kiosk/redteam/scenarios/missing_kyc.rb +38 -0
- data/lib/kiosk/redteam/scenarios/pay_for_other_use_self.rb +59 -0
- data/lib/kiosk/redteam/scenarios/privilege_self_selection.rb +51 -0
- data/lib/kiosk/redteam/scenarios/registration_without_pow.rb +70 -0
- data/lib/kiosk/redteam/scenarios/spent_resource_reuse.rb +47 -0
- data/lib/kiosk/redteam/scenarios/token_tampering.rb +40 -0
- data/lib/kiosk/redteam/scenarios/unpaid_gated_action.rb +37 -0
- data/lib/kiosk/redteam/scenarios/wrong_currency_cart.rb +39 -0
- data/lib/kiosk/redteam/staging.rb +45 -0
- data/lib/kiosk/redteam/token_claims.rb +51 -0
- data/lib/kiosk/redteam/verdict.rb +13 -0
- data/lib/kiosk/redteam/verdicts.rb +130 -0
- data/lib/kiosk/redteam/version.rb +7 -0
- data/lib/kiosk/redteam.rb +94 -0
- metadata +113 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'base64'
|
|
5
|
+
|
|
6
|
+
module Kiosk
|
|
7
|
+
module Redteam
|
|
8
|
+
# Reads and edits the payload of a compact JWS. Nothing here verifies a signature.
|
|
9
|
+
module TokenClaims
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# The payload claims as a Hash, or nil when the token does not decode to one.
|
|
13
|
+
def claims(token)
|
|
14
|
+
payload_b64 = token.to_s.split('.')[1]
|
|
15
|
+
return nil if payload_b64.nil?
|
|
16
|
+
|
|
17
|
+
parsed = decode(payload_b64)
|
|
18
|
+
parsed.is_a?(Hash) ? parsed : nil
|
|
19
|
+
rescue StandardError
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Changes one payload claim and keeps the original signature.
|
|
24
|
+
def tamper(token)
|
|
25
|
+
header, payload_b64, sig = token.split('.', 3)
|
|
26
|
+
return token if payload_b64.nil?
|
|
27
|
+
|
|
28
|
+
new_payload = Base64.urlsafe_encode64(JSON.generate(flip(decode(payload_b64))), padding: false)
|
|
29
|
+
[header, new_payload, sig].join('.')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def decode(payload_b64)
|
|
33
|
+
padded = payload_b64 + ('=' * ((4 - (payload_b64.length % 4)) % 4))
|
|
34
|
+
JSON.parse(Base64.urlsafe_decode64(padded))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def flip(claims)
|
|
38
|
+
if claims.key?('role')
|
|
39
|
+
claims['role'] = claims['role'] == 'admin' ? 'superadmin' : 'admin'
|
|
40
|
+
elsif claims.key?('sub')
|
|
41
|
+
claims['sub'] = "#{claims['sub']}-tampered"
|
|
42
|
+
elsif claims.key?('exp')
|
|
43
|
+
claims['exp'] = (claims['exp'].to_i + 999_999)
|
|
44
|
+
else
|
|
45
|
+
claims['__tamper__'] = true
|
|
46
|
+
end
|
|
47
|
+
claims
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kiosk
|
|
4
|
+
module Redteam
|
|
5
|
+
# BLOCKED: blocked. UNTESTED: the attack never reached the gate under test,
|
|
6
|
+
# because a setup step or the environment failed. BREACH: none of these.
|
|
7
|
+
# SKIPPED: the profile lacks the surface, which is not a pass. status is 0
|
|
8
|
+
# for a skip.
|
|
9
|
+
Verdict = Data.define(:blocked, :skipped, :status, :detail, :untested) do
|
|
10
|
+
def initialize(untested: false, **) = super
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kiosk
|
|
4
|
+
module Redteam
|
|
5
|
+
# Builds the Verdict a Scenario returns. Mixed into Scenario as private methods.
|
|
6
|
+
module Verdicts
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def skip_verdict(reason)
|
|
10
|
+
Verdict.new(blocked: false, skipped: true, status: 0, detail: "SKIP — #{reason}")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# The first profile field that is unset, as a skip verdict; nil when every field is set.
|
|
14
|
+
def skip_unless(profile, reasons)
|
|
15
|
+
_field, reason = reasons.find { |field, _| !profile.public_send(field) }
|
|
16
|
+
reason && skip_verdict(reason)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def blocked_verdict(status, detail = '')
|
|
20
|
+
Verdict.new(blocked: true, skipped: false, status: status, detail: detail)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def breach_verdict(status, detail)
|
|
24
|
+
Verdict.new(blocked: false, skipped: false, status: status, detail: detail)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def untested_verdict(status, detail)
|
|
28
|
+
Verdict.new(blocked: false, skipped: false, status: status, detail: detail, untested: true)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# The verdict of a beat that sends several probes: a breach when a probe
|
|
32
|
+
# failed, untested when none failed but one was answered before the origin
|
|
33
|
+
# evaluated it, and blocked otherwise.
|
|
34
|
+
def probes_verdict(status, failures, unevaluated)
|
|
35
|
+
return breach_verdict(status, failures.join(' | ')) unless failures.empty?
|
|
36
|
+
return untested_verdict(status, "COULD NOT TEST: #{unevaluated.join(' | ')}") unless unevaluated.empty?
|
|
37
|
+
|
|
38
|
+
blocked_verdict(status)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# +expect+ / +expect_code+ name the gate that must refuse; with neither,
|
|
42
|
+
# any status or code Kiosk::Redteam.blocked? accepts counts. An answer given
|
|
43
|
+
# before the attack was evaluated is untested unless +expect_code+ names it.
|
|
44
|
+
def verdict_from(response, expect: nil, expect_code: nil, detail: nil)
|
|
45
|
+
return any_gate_verdict(response, detail) if expect.nil? && expect_code.nil?
|
|
46
|
+
return payment_required_stall(response) if unevaluated_unnamed?(response, expect_code)
|
|
47
|
+
|
|
48
|
+
misses = gate_misses(response, expect, expect_code)
|
|
49
|
+
return blocked_verdict(response.status) if misses.empty?
|
|
50
|
+
|
|
51
|
+
named_gate_breach(response, misses, detail)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def named_gate_breach(response, misses, detail)
|
|
55
|
+
breach_verdict(response.status,
|
|
56
|
+
"#{detail || 'attack was not refused by the named gate'} " \
|
|
57
|
+
"[#{misses.join('; ')}; got HTTP #{response.status} " \
|
|
58
|
+
"code=#{error_code(response).inspect}, body=#{response.body.inspect}]")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def unevaluated_unnamed?(response, expect_code)
|
|
62
|
+
Kiosk::Redteam.unevaluated?(response) && !Array(expect_code).include?(error_code(response))
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def any_gate_verdict(response, detail)
|
|
66
|
+
stall = payment_required_stall(response)
|
|
67
|
+
return stall if stall
|
|
68
|
+
return blocked_verdict(response.status) if Kiosk::Redteam.blocked?(response)
|
|
69
|
+
|
|
70
|
+
breach_verdict(response.status, detail || "HTTP #{response.status}: #{response.body.inspect}")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def gate_misses(response, expect, expect_code)
|
|
74
|
+
[
|
|
75
|
+
status_miss(response.status, expect),
|
|
76
|
+
code_miss(error_code(response), expect_code),
|
|
77
|
+
('5xx is never a block' if response.status >= 500),
|
|
78
|
+
(unpinned_payment_miss if response.status == 402 && expect_code.nil?)
|
|
79
|
+
].compact
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def status_miss(status, expect)
|
|
83
|
+
return nil if expect.nil? || Array(expect).include?(status)
|
|
84
|
+
|
|
85
|
+
"want status #{Array(expect).join('/')}"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def code_miss(code, expect_code)
|
|
89
|
+
return nil if expect_code.nil? || Array(expect_code).include?(code)
|
|
90
|
+
|
|
91
|
+
"want error.code #{Array(expect_code).map(&:inspect).join('/')}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def unpinned_payment_miss
|
|
95
|
+
'HTTP 402 is conclusive only with an explicit expect_code — ' \
|
|
96
|
+
"#{Kiosk::Redteam::PAYMENT_REQUIRED_CODES.keys.join('/')} all ride that status"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# A payment-required answer proves no refusal. It is untested when it came
|
|
100
|
+
# before the attack was evaluated, and a breach otherwise.
|
|
101
|
+
def payment_required_stall(response, step: nil)
|
|
102
|
+
reason = Kiosk::Redteam.payment_required_reason(response)
|
|
103
|
+
return nil unless reason
|
|
104
|
+
|
|
105
|
+
answered = "#{step || 'the attack under test'} was answered #{reason}."
|
|
106
|
+
return breach_verdict(response.status, "#{answered} No gate refused the attack.") unless Kiosk::Redteam.unevaluated?(response)
|
|
107
|
+
|
|
108
|
+
untested_verdict(
|
|
109
|
+
response.status,
|
|
110
|
+
"COULD NOT TEST: #{answered} That is neither a refusal of this attack nor a breach — nothing was proved " \
|
|
111
|
+
'either way. A provider that means a payment gate HERE must have the scenario ' \
|
|
112
|
+
'name the code it accepts (verdict_from expect_code:).'
|
|
113
|
+
)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# A failed setup step must not let a later refusal from another gate pass as this one's.
|
|
117
|
+
def setup_failure(response, step:, because:)
|
|
118
|
+
return nil if response.nil? || response.status == 200
|
|
119
|
+
|
|
120
|
+
untested_verdict(response.status,
|
|
121
|
+
"SETUP FAILED: #{step} returned HTTP #{response.status} " \
|
|
122
|
+
"#{response.body.inspect}. #{because}")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def error_code(response)
|
|
126
|
+
Kiosk::Redteam.error_code(response)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'kiosk/test_helpers/assistant'
|
|
4
|
+
require 'kiosk/redteam/version'
|
|
5
|
+
require 'kiosk/redteam/verdict'
|
|
6
|
+
require 'kiosk/redteam/leak_scan'
|
|
7
|
+
require 'kiosk/redteam/scenario'
|
|
8
|
+
require 'kiosk/redteam/runner'
|
|
9
|
+
require 'kiosk/redteam/profile'
|
|
10
|
+
require 'kiosk/redteam/battery'
|
|
11
|
+
|
|
12
|
+
require 'kiosk/redteam/scenarios/cross_tenant_read'
|
|
13
|
+
require 'kiosk/redteam/scenarios/forged_user_id'
|
|
14
|
+
require 'kiosk/redteam/scenarios/mandate_principal_swap'
|
|
15
|
+
require 'kiosk/redteam/scenarios/mandate_replay'
|
|
16
|
+
require 'kiosk/redteam/scenarios/token_tampering'
|
|
17
|
+
require 'kiosk/redteam/scenarios/registration_without_pow'
|
|
18
|
+
require 'kiosk/redteam/scenarios/privilege_self_selection'
|
|
19
|
+
require 'kiosk/redteam/scenarios/device_grant_role_self_selection'
|
|
20
|
+
require 'kiosk/redteam/scenarios/unpaid_gated_action'
|
|
21
|
+
require 'kiosk/redteam/scenarios/missing_kyc'
|
|
22
|
+
require 'kiosk/redteam/scenarios/expired_kyc'
|
|
23
|
+
require 'kiosk/redteam/scenarios/forged_kyc'
|
|
24
|
+
require 'kiosk/redteam/scenarios/spent_resource_reuse'
|
|
25
|
+
require 'kiosk/redteam/scenarios/pay_for_other_use_self'
|
|
26
|
+
require 'kiosk/redteam/scenarios/wrong_currency_cart'
|
|
27
|
+
|
|
28
|
+
module Kiosk
|
|
29
|
+
# Adversarial regression harness: drives hostile HTTP scenarios against a
|
|
30
|
+
# Kiosk origin and asserts each attack is refused.
|
|
31
|
+
module Redteam
|
|
32
|
+
# 402 is absent: see PAYMENT_REQUIRED_CODES.
|
|
33
|
+
BLOCKED_STATUSES = [401, 403].freeze
|
|
34
|
+
|
|
35
|
+
# The three codes kiosk-server maps onto HTTP 402, each with why it is not a refusal.
|
|
36
|
+
PAYMENT_REQUIRED_CODES = {
|
|
37
|
+
'pow_required' =>
|
|
38
|
+
'a toll was DEMANDED, not a refusal — the request was deferred until a proof is ' \
|
|
39
|
+
'supplied. This harness PAYS that toll on every verb, so seeing this code here ' \
|
|
40
|
+
'means the demand survived a paid retry (or arrived with no challenges to ' \
|
|
41
|
+
'solve); either way the attack itself was never evaluated',
|
|
42
|
+
'payment_setup_required' =>
|
|
43
|
+
"the principal has no payment instrument on file — a gap in the ATTACKER's setup, " \
|
|
44
|
+
'not a decision about the attack',
|
|
45
|
+
'payment_failed' =>
|
|
46
|
+
'the payment rail declined the charge AFTER the mandate trail verified — every ' \
|
|
47
|
+
'authorization gate the request passed through said yes; a declined card is not a gate'
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
# The 402 codes kiosk-server answers before it evaluates the request.
|
|
51
|
+
UNEVALUATED_CODES = %w[pow_required payment_setup_required].freeze
|
|
52
|
+
|
|
53
|
+
# A validation error (bad_request) is not evidence of an authorization gate.
|
|
54
|
+
BLOCKED_ERROR_CODES = %w[forbidden unauthenticated rls_denied].freeze
|
|
55
|
+
|
|
56
|
+
# A successful query answers a bare Array, so the body may not be a Hash.
|
|
57
|
+
def self.error_code(response)
|
|
58
|
+
body = response.body
|
|
59
|
+
return nil unless body.is_a?(Hash)
|
|
60
|
+
|
|
61
|
+
body['code']
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Why a payment-required answer cannot settle a verdict, or nil when it is not one.
|
|
65
|
+
def self.payment_required_reason(response)
|
|
66
|
+
code = error_code(response)
|
|
67
|
+
return nil unless response.status == 402 || PAYMENT_REQUIRED_CODES.key?(code)
|
|
68
|
+
|
|
69
|
+
why = PAYMENT_REQUIRED_CODES[code] ||
|
|
70
|
+
"kiosk-server maps three codes onto 402 — #{PAYMENT_REQUIRED_CODES.keys.join(', ')} — " \
|
|
71
|
+
'and this answer named none of them, so which gate fired (if any did) is unknowable'
|
|
72
|
+
"HTTP #{response.status} code=#{code.inspect}:#{paid_retry_note(response)} #{why}"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def self.paid_retry_note(response)
|
|
76
|
+
return '' unless response.pow_retried?
|
|
77
|
+
|
|
78
|
+
' [the harness already solved every issued challenge and re-sent the identical request once]'
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# True when the origin answered before it evaluated the request: a toll it
|
|
82
|
+
# demands, or a payment refused before the mandates are read.
|
|
83
|
+
def self.unevaluated?(response) = UNEVALUATED_CODES.include?(error_code(response))
|
|
84
|
+
|
|
85
|
+
def self.blocked?(response)
|
|
86
|
+
# Status first: a crash whose body carries `forbidden` is not a block. 0 is a connection error.
|
|
87
|
+
return false if response.status >= 500 || response.status.zero?
|
|
88
|
+
return false if payment_required_reason(response)
|
|
89
|
+
return true if BLOCKED_STATUSES.include?(response.status)
|
|
90
|
+
|
|
91
|
+
BLOCKED_ERROR_CODES.include?(error_code(response))
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: kiosk-redteam
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.5.20
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Phil Pirozhkov
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: base64
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: kiosk-test-support
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: 0.5.0
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: 0.5.0
|
|
40
|
+
description: |
|
|
41
|
+
kiosk-redteam drives hostile HTTP scenarios against any Kiosk provider
|
|
42
|
+
and asserts each attack is correctly blocked — a deliberate refusal, never
|
|
43
|
+
a crash and never a toll the harness could not settle. A scenario that
|
|
44
|
+
finds a real breach fails loudly.
|
|
45
|
+
|
|
46
|
+
Ships a Scenario/Verdict/Runner framework and a library of generic attack
|
|
47
|
+
scenarios parameterised by a per-provider Profile. It attacks through the
|
|
48
|
+
assistant client kiosk-test-support ships.
|
|
49
|
+
|
|
50
|
+
No dependency on Rails.
|
|
51
|
+
email:
|
|
52
|
+
- hello@fili.pp.ru
|
|
53
|
+
executables: []
|
|
54
|
+
extensions: []
|
|
55
|
+
extra_rdoc_files: []
|
|
56
|
+
files:
|
|
57
|
+
- CHANGELOG.md
|
|
58
|
+
- LICENSE.txt
|
|
59
|
+
- README.md
|
|
60
|
+
- lib/kiosk/redteam.rb
|
|
61
|
+
- lib/kiosk/redteam/battery.rb
|
|
62
|
+
- lib/kiosk/redteam/leak_scan.rb
|
|
63
|
+
- lib/kiosk/redteam/profile.rb
|
|
64
|
+
- lib/kiosk/redteam/runner.rb
|
|
65
|
+
- lib/kiosk/redteam/scenario.rb
|
|
66
|
+
- lib/kiosk/redteam/scenarios/cross_tenant_read.rb
|
|
67
|
+
- lib/kiosk/redteam/scenarios/device_grant_role_self_selection.rb
|
|
68
|
+
- lib/kiosk/redteam/scenarios/expired_kyc.rb
|
|
69
|
+
- lib/kiosk/redteam/scenarios/forged_kyc.rb
|
|
70
|
+
- lib/kiosk/redteam/scenarios/forged_user_id.rb
|
|
71
|
+
- lib/kiosk/redteam/scenarios/kyc_attack.rb
|
|
72
|
+
- lib/kiosk/redteam/scenarios/mandate_principal_swap.rb
|
|
73
|
+
- lib/kiosk/redteam/scenarios/mandate_replay.rb
|
|
74
|
+
- lib/kiosk/redteam/scenarios/missing_kyc.rb
|
|
75
|
+
- lib/kiosk/redteam/scenarios/pay_for_other_use_self.rb
|
|
76
|
+
- lib/kiosk/redteam/scenarios/privilege_self_selection.rb
|
|
77
|
+
- lib/kiosk/redteam/scenarios/registration_without_pow.rb
|
|
78
|
+
- lib/kiosk/redteam/scenarios/spent_resource_reuse.rb
|
|
79
|
+
- lib/kiosk/redteam/scenarios/token_tampering.rb
|
|
80
|
+
- lib/kiosk/redteam/scenarios/unpaid_gated_action.rb
|
|
81
|
+
- lib/kiosk/redteam/scenarios/wrong_currency_cart.rb
|
|
82
|
+
- lib/kiosk/redteam/staging.rb
|
|
83
|
+
- lib/kiosk/redteam/token_claims.rb
|
|
84
|
+
- lib/kiosk/redteam/verdict.rb
|
|
85
|
+
- lib/kiosk/redteam/verdicts.rb
|
|
86
|
+
- lib/kiosk/redteam/version.rb
|
|
87
|
+
homepage: https://kiosk.tech
|
|
88
|
+
licenses:
|
|
89
|
+
- Apache-2.0
|
|
90
|
+
metadata:
|
|
91
|
+
homepage_uri: https://kiosk.tech
|
|
92
|
+
source_code_uri: https://github.com/kiosk-hq/kiosk
|
|
93
|
+
changelog_uri: https://github.com/kiosk-hq/kiosk/blob/main/kiosk-redteam/CHANGELOG.md
|
|
94
|
+
bug_tracker_uri: https://github.com/kiosk-hq/kiosk/issues
|
|
95
|
+
rubygems_mfa_required: 'true'
|
|
96
|
+
rdoc_options: []
|
|
97
|
+
require_paths:
|
|
98
|
+
- lib
|
|
99
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - ">="
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '4.0'
|
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
105
|
+
requirements:
|
|
106
|
+
- - ">="
|
|
107
|
+
- !ruby/object:Gem::Version
|
|
108
|
+
version: '0'
|
|
109
|
+
requirements: []
|
|
110
|
+
rubygems_version: 4.0.3
|
|
111
|
+
specification_version: 4
|
|
112
|
+
summary: Adversarial regression harness for Kiosk providers
|
|
113
|
+
test_files: []
|