arachni 1.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +47 -0
- data/README.md +2 -2
- data/arachni.gemspec +1 -1
- data/components/checks/active/code_injection_php_input_wrapper.rb +8 -3
- data/components/checks/active/file_inclusion.rb +7 -3
- data/components/checks/active/path_traversal.rb +7 -3
- data/components/checks/passive/grep/cookie_set_for_parent_domain.rb +5 -3
- data/components/plugins/cookie_collector.rb +1 -1
- data/components/plugins/proxy.rb +4 -3
- data/components/plugins/vector_feed.rb +1 -1
- data/components/reporters/html/default/issue.erb +5 -0
- data/components/reporters/stdout.rb +4 -0
- data/lib/arachni/browser.rb +25 -6
- data/lib/arachni/browser/element_locator.rb +1 -1
- data/lib/arachni/browser/javascript/taint_tracer.rb +3 -3
- data/lib/arachni/browser/javascript/taint_tracer/frame.rb +1 -1
- data/lib/arachni/browser/javascript/taint_tracer/frame/called_function.rb +1 -1
- data/lib/arachni/browser/javascript/taint_tracer/sink/base.rb +1 -1
- data/lib/arachni/check/auditor.rb +2 -0
- data/lib/arachni/component/manager.rb +2 -2
- data/lib/arachni/component/options/base.rb +2 -2
- data/lib/arachni/element/base.rb +2 -2
- data/lib/arachni/element/cookie.rb +4 -4
- data/lib/arachni/element/form.rb +1 -1
- data/lib/arachni/element/generic_dom.rb +1 -1
- data/lib/arachni/framework.rb +9 -1
- data/lib/arachni/http/client.rb +2 -0
- data/lib/arachni/http/request.rb +2 -2
- data/lib/arachni/http/response.rb +1 -1
- data/lib/arachni/issue.rb +2 -2
- data/lib/arachni/option_group.rb +1 -1
- data/lib/arachni/option_groups/input.rb +1 -1
- data/lib/arachni/option_groups/scope.rb +1 -1
- data/lib/arachni/page.rb +1 -1
- data/lib/arachni/page/dom/transition.rb +3 -3
- data/lib/arachni/parser.rb +3 -1
- data/lib/arachni/platform/list.rb +1 -1
- data/lib/arachni/report.rb +1 -1
- data/lib/arachni/rpc/client/instance/framework.rb +6 -6
- data/lib/arachni/rpc/client/instance/service.rb +7 -7
- data/lib/arachni/rpc/server/dispatcher.rb +18 -5
- data/lib/arachni/rpc/server/dispatcher/node.rb +13 -6
- data/lib/arachni/rpc/server/framework/distributor.rb +1 -1
- data/lib/arachni/rpc/server/framework/master.rb +1 -1
- data/lib/arachni/rpc/server/framework/multi_instance.rb +2 -2
- data/lib/arachni/rpc/server/instance.rb +11 -3
- data/lib/arachni/ruby/hash.rb +7 -6
- data/lib/arachni/state/framework.rb +1 -0
- data/lib/version +1 -1
- data/spec/arachni/browser_spec.rb +25 -0
- data/spec/arachni/component/manager_spec.rb +1 -1
- data/spec/arachni/element/cookie_spec.rb +3 -3
- data/spec/arachni/http/request_spec.rb +3 -3
- data/spec/arachni/option_groups/scope_spec.rb +2 -2
- data/spec/arachni/parser_spec.rb +7 -0
- data/spec/arachni/reporter/manager_spec.rb +1 -1
- data/spec/arachni/rpc/server/dispatcher/node_spec.rb +2 -0
- data/spec/arachni/rpc/server/framework_spec.rb +1 -1
- data/spec/arachni/ruby/hash_spec.rb +8 -8
- data/spec/support/servers/checks/passive/grep/cookie_set_for_parent_domain.rb +1 -1
- data/spec/support/shared/element/capabilities/inputtable.rb +2 -2
- data/ui/cli/utilities.rb +3 -0
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a4f1fba6f25f0dd437f9d31c82b3f036d4b87ec9
|
4
|
+
data.tar.gz: eaa9814da188578b10283e23101525828d42b17a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a98cde207400471e0ba80cffc9fce6ce46348d43e76c509cbd06f5c7e6b4867f62f9e5382bedaa7b500d731a82428cde04d23db0ea72a4cfceb9087c8fe8062a
|
7
|
+
data.tar.gz: a66344c76f782b7042f003c0e7b525b5ea173d2f72257d77f9a4673be35280624d4c73a5f874c157b4b66fd5b3eb46a0196480d9b91379639da6cf26e288c045
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,52 @@
|
|
1
1
|
# ChangeLog
|
2
2
|
|
3
|
+
## 1.0.1 _(September 7, 2014)_
|
4
|
+
|
5
|
+
- `RPC::Server::Dispatcher`
|
6
|
+
- Check for Instance status via the bind address, not the external one.
|
7
|
+
- Added more status and debugging messages
|
8
|
+
- Fixed RPC connection leak when in Grid configuration.
|
9
|
+
- `Node`
|
10
|
+
- Don't raise error if the initial neighbour is unreachable, just add
|
11
|
+
it to the dead list as usual.
|
12
|
+
- `Browser`
|
13
|
+
- Fixed issue causing the removal of cookie HttpOnly flags.
|
14
|
+
- `Parser`
|
15
|
+
- `#link_vars` -- Return empty `Hash` when dealing with unparsable URL.
|
16
|
+
- `HTTP::Client`
|
17
|
+
- Debugging messages now include the `HTTP::Request#performer`.
|
18
|
+
- `HTTP::Request`
|
19
|
+
- `#to_typhoeus` -- Converted proxy type to `Symbol` to prevent the option
|
20
|
+
from being ignored.
|
21
|
+
- `UI::CLI::Utilities`
|
22
|
+
- `#print_issues` -- Updated to include all inputs of the given vector in
|
23
|
+
the message, if the issue is passive.
|
24
|
+
- `Check::Auditor`
|
25
|
+
- `#log` -- Updated to include all inputs of the given vector in the success
|
26
|
+
message, if the issue is passive.
|
27
|
+
- `Element::Cookie`
|
28
|
+
- `.encode` -- Encode `'` and `"`.
|
29
|
+
- `Hash` -- Renamed added methods to avoid clashes with `ActiveSupport`.
|
30
|
+
- `stringify_keys` => `my_stringify_keys`
|
31
|
+
- `symbolize_keys` => `my_symbolize_keys`
|
32
|
+
- `stringify` => `my_stringify`
|
33
|
+
- Plugins
|
34
|
+
- `proxy` -- Show control panel URL in output.
|
35
|
+
- Reporters
|
36
|
+
- `stdout`
|
37
|
+
- Updated to print out information about all available vector inputs.
|
38
|
+
- `html`
|
39
|
+
- Updated to include information about all available vector inputs in
|
40
|
+
issue title for passive issues.
|
41
|
+
- Checks
|
42
|
+
- Active
|
43
|
+
- `code_injection_php_input_wrapper` -- Fixed `nil` error when
|
44
|
+
manipulating mutations.
|
45
|
+
- `file_inclusion` -- Fixed `nil` error when manipulating mutations.
|
46
|
+
- `path_traversal` -- Fixed `nil` error when manipulating mutations.
|
47
|
+
- Passive
|
48
|
+
- `cookie_set_for_parent_domain` -- Only check `HTTP::Response` cookies.
|
49
|
+
|
3
50
|
## 1.0 _(August 29, 2014)_
|
4
51
|
|
5
52
|
- Executables:
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
* Arachni's license has changed, please see the _LICENSE_ file before working
|
4
4
|
with the project.
|
5
|
-
* v1.0 is not backwards compatible.
|
5
|
+
* v1.0 is not backwards compatible with v0.4.
|
6
6
|
|
7
7
|
<hr/>
|
8
8
|
|
@@ -11,7 +11,7 @@
|
|
11
11
|
<table>
|
12
12
|
<tr>
|
13
13
|
<th>Version</th>
|
14
|
-
<td>1.0</td>
|
14
|
+
<td>1.0.1</td>
|
15
15
|
</tr>
|
16
16
|
<tr>
|
17
17
|
<th>Homepage</th>
|
data/arachni.gemspec
CHANGED
@@ -6,9 +6,9 @@
|
|
6
6
|
web site for more information on licensing and terms of use.
|
7
7
|
=end
|
8
8
|
|
9
|
-
# @see OWASP https://www.owasp.org/index.php/Top_10_2007-Malicious_File_Execution
|
10
9
|
# @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
|
11
|
-
# @version 0.1
|
10
|
+
# @version 0.1.1
|
11
|
+
# @see OWASP https://www.owasp.org/index.php/Top_10_2007-Malicious_File_Execution
|
12
12
|
class Arachni::Checks::CodeInjectionPhpInputWrapper < Arachni::Check::Base
|
13
13
|
|
14
14
|
def self.options
|
@@ -20,6 +20,11 @@ class Arachni::Checks::CodeInjectionPhpInputWrapper < Arachni::Check::Base
|
|
20
20
|
# Add one more mutation (on the fly) which will include the extension
|
21
21
|
# of the original value (if that value was a filename) after a null byte.
|
22
22
|
each_mutation: proc do |mutation|
|
23
|
+
next if !mutation.affected_input_value ||
|
24
|
+
(mutation.is_a?( Arachni::Form ) &&
|
25
|
+
(mutation.mutation_with_original_values? ||
|
26
|
+
mutation.mutation_with_sample_values?))
|
27
|
+
|
23
28
|
# Don't bother if the current element type can't carry nulls.
|
24
29
|
next if !mutation.valid_input_value_data?( "\0" )
|
25
30
|
|
@@ -51,7 +56,7 @@ to try and load it.
|
|
51
56
|
},
|
52
57
|
elements: [ Element::Form, Element::Link, Element::Cookie, Element::Header ],
|
53
58
|
author: 'Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com> ',
|
54
|
-
version: '0.1',
|
59
|
+
version: '0.1.1',
|
55
60
|
platforms: [:php],
|
56
61
|
|
57
62
|
issue: {
|
@@ -9,8 +9,7 @@
|
|
9
9
|
# File inclusion check.
|
10
10
|
#
|
11
11
|
# @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
|
12
|
-
#
|
13
|
-
# @version 0.1.1
|
12
|
+
# @version 0.1.2
|
14
13
|
#
|
15
14
|
# @see http://cwe.mitre.org/data/definitions/98.html
|
16
15
|
# @see https://www.owasp.org/index.php/PHP_File_Inclusion
|
@@ -51,6 +50,11 @@ class Arachni::Checks::FileInclusion < Arachni::Check::Base
|
|
51
50
|
# Add one more mutation (on the fly) which will include the extension
|
52
51
|
# of the original value (if that value was a filename) after a null byte.
|
53
52
|
each_mutation: proc do |mutation|
|
53
|
+
next if !mutation.affected_input_value ||
|
54
|
+
(mutation.is_a?( Arachni::Form ) &&
|
55
|
+
(mutation.mutation_with_original_values? ||
|
56
|
+
mutation.mutation_with_sample_values?))
|
57
|
+
|
54
58
|
# Don't bother if the current element type can't carry nulls.
|
55
59
|
next if !mutation.valid_input_value_data?( "\0" )
|
56
60
|
|
@@ -101,7 +105,7 @@ content or errors in the HTTP response body.
|
|
101
105
|
elements: [ Element::Form, Element::Link, Element::Cookie,
|
102
106
|
Element::Header, Element::LinkTemplate ],
|
103
107
|
author: 'Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com> ',
|
104
|
-
version: '0.1.
|
108
|
+
version: '0.1.2',
|
105
109
|
platforms: options[:regexp].keys,
|
106
110
|
|
107
111
|
issue: {
|
@@ -9,8 +9,7 @@
|
|
9
9
|
# Path Traversal check.
|
10
10
|
#
|
11
11
|
# @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
|
12
|
-
#
|
13
|
-
# @version 0.4.1
|
12
|
+
# @version 0.4.2
|
14
13
|
#
|
15
14
|
# @see http://cwe.mitre.org/data/definitions/22.html
|
16
15
|
# @see http://www.owasp.org/index.php/Path_Traversal
|
@@ -40,6 +39,11 @@ class Arachni::Checks::PathTraversal < Arachni::Check::Base
|
|
40
39
|
# Add one more mutation (on the fly) which will include the extension
|
41
40
|
# of the original value (if that value was a filename) after a null byte.
|
42
41
|
each_mutation: proc do |mutation|
|
42
|
+
next if !mutation.affected_input_value ||
|
43
|
+
(mutation.is_a?( Arachni::Form ) &&
|
44
|
+
(mutation.mutation_with_original_values? ||
|
45
|
+
mutation.mutation_with_sample_values?))
|
46
|
+
|
43
47
|
# Don't bother if the current element type can't carry nulls.
|
44
48
|
next if !mutation.valid_input_value_data?( "\0" )
|
45
49
|
|
@@ -111,7 +115,7 @@ of relevant content in the HTML responses.
|
|
111
115
|
elements: [ Element::Form, Element::Link, Element::Cookie,
|
112
116
|
Element::Header, Element::LinkTemplate ],
|
113
117
|
author: 'Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com> ',
|
114
|
-
version: '0.4.
|
118
|
+
version: '0.4.2',
|
115
119
|
platforms: payloads.keys,
|
116
120
|
|
117
121
|
issue: {
|
@@ -7,11 +7,13 @@
|
|
7
7
|
=end
|
8
8
|
|
9
9
|
# @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
|
10
|
-
# @version 0.1
|
10
|
+
# @version 0.1.1
|
11
11
|
class Arachni::Checks::CookieSetForParentDomain < Arachni::Check::Base
|
12
12
|
|
13
13
|
def run
|
14
|
-
page.
|
14
|
+
return if !page.parser
|
15
|
+
|
16
|
+
page.parser.cookies.each do |cookie|
|
15
17
|
next if !cookie.domain.start_with?( '.' ) || audited?( cookie.name )
|
16
18
|
|
17
19
|
log( vector: cookie )
|
@@ -25,7 +27,7 @@ class Arachni::Checks::CookieSetForParentDomain < Arachni::Check::Base
|
|
25
27
|
description: %q{Logs cookies that are accessible by all subdomains.},
|
26
28
|
elements: [ Element::Cookie ],
|
27
29
|
author: 'Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>',
|
28
|
-
version: '0.1',
|
30
|
+
version: '0.1.1',
|
29
31
|
|
30
32
|
issue: {
|
31
33
|
name: %q{Cookie set for parent domain},
|
data/components/plugins/proxy.rb
CHANGED
@@ -16,7 +16,7 @@ require 'ostruct'
|
|
16
16
|
#
|
17
17
|
# @author Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>
|
18
18
|
#
|
19
|
-
# @version 0.3
|
19
|
+
# @version 0.3.1
|
20
20
|
class Arachni::Plugins::Proxy < Arachni::Plugin::Base
|
21
21
|
|
22
22
|
BASEDIR = "#{File.dirname( __FILE__ )}/proxy/"
|
@@ -58,7 +58,8 @@ class Arachni::Plugins::Proxy < Arachni::Plugin::Base
|
|
58
58
|
def run
|
59
59
|
print_status "Listening on: http://#{@server[:BindAddress]}:#{@server[:Port]}"
|
60
60
|
|
61
|
-
|
61
|
+
print_info "Control panel URL: #{url_for( :panel )}"
|
62
|
+
print_info "Shutdown URL: #{url_for( :shutdown )}"
|
62
63
|
print_info 'The scan will resume once you visit the shutdown URL.'
|
63
64
|
|
64
65
|
print_info
|
@@ -463,7 +464,7 @@ a way to restrict usage enough to avoid users unwittingly interfering with each
|
|
463
464
|
others' sessions.
|
464
465
|
},
|
465
466
|
author: 'Tasos "Zapotek" Laskos <tasos.laskos@arachni-scanner.com>',
|
466
|
-
version: '0.3',
|
467
|
+
version: '0.3.1',
|
467
468
|
options: [
|
468
469
|
Options::Port.new( :port,
|
469
470
|
description: 'Port to bind to.',
|
@@ -58,7 +58,7 @@ class Arachni::Plugins::VectorFeed < Arachni::Plugin::Base
|
|
58
58
|
page_buffer = []
|
59
59
|
print_status "Imported #{feed.size} vectors."
|
60
60
|
feed.each do |obj|
|
61
|
-
vector = (obj.respond_to?( :value ) ? obj.value : obj).
|
61
|
+
vector = (obj.respond_to?( :value ) ? obj.value : obj).my_symbolize_keys( false )
|
62
62
|
|
63
63
|
exception_jail false do
|
64
64
|
if page?( vector )
|
@@ -10,6 +10,11 @@
|
|
10
10
|
<% if issue.active? %>
|
11
11
|
input
|
12
12
|
<code><%= issue.affected_input_name %></code>
|
13
|
+
<% elsif issue.vector.respond_to? :inputs %>
|
14
|
+
with inputs
|
15
|
+
<% issue.vector.inputs.keys.map do |iname| %>
|
16
|
+
<code><%= iname %></code>
|
17
|
+
<% end.join( ', ') %>
|
13
18
|
<% end %>
|
14
19
|
|
15
20
|
<% if issue.variations.first.request %>
|
@@ -109,6 +109,10 @@ class Arachni::Reporters::Stdout < Arachni::Reporter::Base
|
|
109
109
|
print_info "Input name: #{issue.affected_input_name}"
|
110
110
|
end
|
111
111
|
|
112
|
+
if issue.vector.respond_to? :inputs
|
113
|
+
print_info "All inputs: #{issue.vector.inputs.keys.join(', ')}"
|
114
|
+
end
|
115
|
+
|
112
116
|
print_line
|
113
117
|
print_info "Tags: #{issue.tags.join(', ')}" if issue.tags.is_a?( Array )
|
114
118
|
print_line
|
data/lib/arachni/browser.rb
CHANGED
@@ -308,6 +308,7 @@ class Browser
|
|
308
308
|
|
309
309
|
@javascript.wait_till_ready
|
310
310
|
wait_for_timers
|
311
|
+
|
311
312
|
wait_for_pending_requests
|
312
313
|
end
|
313
314
|
|
@@ -553,7 +554,7 @@ class Browser
|
|
553
554
|
event = event.to_s.downcase.sub( /^on/, '' ).to_sym
|
554
555
|
locator = nil
|
555
556
|
|
556
|
-
options[:inputs] = options[:inputs].
|
557
|
+
options[:inputs] = options[:inputs].my_stringify if options[:inputs]
|
557
558
|
|
558
559
|
if element.is_a? ElementLocator
|
559
560
|
locator = element
|
@@ -792,10 +793,22 @@ class Browser
|
|
792
793
|
# @return [Array<Cookie>]
|
793
794
|
# Browser cookies.
|
794
795
|
def cookies
|
796
|
+
js_cookies = begin
|
797
|
+
# Watir doesn't tell us if cookies are HttpOnly, so we need to figure
|
798
|
+
# this out ourselves, by checking for JS visibility.
|
799
|
+
javascript.run( 'return document.cookie' )
|
800
|
+
# We may not have a page.
|
801
|
+
rescue Selenium::WebDriver::Error::UnknownError
|
802
|
+
''
|
803
|
+
end
|
804
|
+
|
795
805
|
watir.cookies.to_a.map do |c|
|
796
|
-
|
797
|
-
|
798
|
-
c[:
|
806
|
+
original_name = c[:name].to_s
|
807
|
+
|
808
|
+
c[:path] = '/' if c[:path] == '//'
|
809
|
+
c[:name] = Cookie.decode( c[:name].to_s )
|
810
|
+
c[:value] = Cookie.decode( c[:value].to_s )
|
811
|
+
c[:httponly] = !js_cookies.include?( original_name )
|
799
812
|
|
800
813
|
Cookie.new c.merge( url: @last_url )
|
801
814
|
end
|
@@ -944,7 +957,8 @@ class Browser
|
|
944
957
|
self.class.executable,
|
945
958
|
"--webdriver=#{port}",
|
946
959
|
"--proxy=http://#{@proxy.address}/",
|
947
|
-
'--ignore-ssl-errors=true'
|
960
|
+
'--ignore-ssl-errors=true',
|
961
|
+
"--debug=#{!!debug?}"
|
948
962
|
)
|
949
963
|
@process.detach = true
|
950
964
|
|
@@ -1065,7 +1079,12 @@ class Browser
|
|
1065
1079
|
set_cookies[cookie.name] = cookie
|
1066
1080
|
end
|
1067
1081
|
cookies.each do |name, value|
|
1068
|
-
set_cookies[name]
|
1082
|
+
if set_cookies[name]
|
1083
|
+
set_cookies[name] = set_cookies[name].dup
|
1084
|
+
set_cookies[name].update( name => value )
|
1085
|
+
else
|
1086
|
+
set_cookies[name] = Cookie.new( url: url, inputs: { name => value } )
|
1087
|
+
end
|
1069
1088
|
end
|
1070
1089
|
|
1071
1090
|
url = "#{url}/set-cookies-#{request_token}"
|
@@ -74,9 +74,9 @@ class TaintTracer < Proxy
|
|
74
74
|
return [] if !data
|
75
75
|
|
76
76
|
data.map do |entry|
|
77
|
-
Sink::DataFlow.new( (entry['data'] || {}).
|
77
|
+
Sink::DataFlow.new( (entry['data'] || {}).my_symbolize_keys( false ).merge(
|
78
78
|
trace: [entry['trace']].flatten.compact.
|
79
|
-
map { |h| Frame.new h.
|
79
|
+
map { |h| Frame.new h.my_symbolize_keys( false ) }
|
80
80
|
)
|
81
81
|
)
|
82
82
|
end
|
@@ -88,7 +88,7 @@ class TaintTracer < Proxy
|
|
88
88
|
data.map do |entry|
|
89
89
|
Sink::ExecutionFlow.new( entry.merge(
|
90
90
|
trace: [entry['trace']].flatten.compact.
|
91
|
-
map { |h| Frame.new h.
|
91
|
+
map { |h| Frame.new h.my_symbolize_keys( false ) }
|
92
92
|
)
|
93
93
|
)
|
94
94
|
end
|
@@ -172,7 +172,7 @@ class Manager < Hash
|
|
172
172
|
return {} if !info.include?( :options ) || info[:options].empty?
|
173
173
|
|
174
174
|
user_opts ||= {}
|
175
|
-
user_opts = user_opts.
|
175
|
+
user_opts = user_opts.my_symbolize_keys(false)
|
176
176
|
|
177
177
|
options = {}
|
178
178
|
errors = {}
|
@@ -209,7 +209,7 @@ class Manager < Hash
|
|
209
209
|
format_error_string( component_name, errors )
|
210
210
|
end
|
211
211
|
|
212
|
-
options.
|
212
|
+
options.my_symbolize_keys( false )
|
213
213
|
end
|
214
214
|
|
215
215
|
# It parses the component array making sure that its structure is valid
|
@@ -117,7 +117,7 @@ class Arachni::Component::Options::Base
|
|
117
117
|
# @return [Hash]
|
118
118
|
# Data representing this instance that are suitable the RPC transmission.
|
119
119
|
def to_rpc_data
|
120
|
-
to_h.merge( class: self.class.to_s ).
|
120
|
+
to_h.merge( class: self.class.to_s ).my_stringify_keys
|
121
121
|
end
|
122
122
|
|
123
123
|
# @param [Hash] data {#to_rpc_data}
|
@@ -127,7 +127,7 @@ class Arachni::Component::Options::Base
|
|
127
127
|
data.delete('class')
|
128
128
|
name = data.delete('name')
|
129
129
|
|
130
|
-
new name, data.
|
130
|
+
new name, data.my_symbolize_keys(false)
|
131
131
|
end
|
132
132
|
|
133
133
|
def ==( option )
|