diff --git a/app/models/break_escape/game.rb b/app/models/break_escape/game.rb index 3c47a82..5c09bf7 100644 --- a/app/models/break_escape/game.rb +++ b/app/models/break_escape/game.rb @@ -274,42 +274,50 @@ module BreakEscape return true end - # Find object in all rooms - check both id and name - scenario_data['rooms'].each do |_room_id, room_data| - object = room_data['objects']&.find { |obj| - obj['id'] == target_id || obj['name'] == target_id - } - - if object - Rails.logger.info "[BreakEscape] Found object: id=#{object['id']}, name=#{object['name']}, locked=#{object['locked']}, requires=#{object['requires']}" - - # Handle method='unlocked' - verify against scenario data - if method == 'unlocked' - if !object['locked'] - Rails.logger.info "[BreakEscape] Object is unlocked in scenario data, granting access" - return true - else - Rails.logger.warn "[BreakEscape] SECURITY VIOLATION: Client sent method='unlocked' for LOCKED object: #{target_id}" - return false - end + # Find object in all rooms - check id, name, or generated client ID + object = nil + scenario_data['rooms'].each do |room_id, room_data| + next unless room_data['objects'] + room_data['objects'].each_with_index do |obj, index| + # Client generates IDs as: roomId_type_index + client_generated_id = "#{room_id}_#{obj['type']}_#{index}" + if obj['id'] == target_id || obj['name'] == target_id || client_generated_id == target_id + object = obj + break end + end + break if object + end - # NPC unlock: Validate NPC has been encountered and has permission to unlock this object - if method == 'npc' - npc_id = attempt # NPC id is passed as 'attempt' - return validate_npc_unlock(npc_id, target_id) - end + if object + Rails.logger.info "[BreakEscape] Found object: id=#{object['id']}, name=#{object['name']}, locked=#{object['locked']}, requires=#{object['requires']}" - case method - when 'key', 'lockpick', 'biometric', 'bluetooth', 'rfid' - # Client validated the unlock - trust it + # Handle method='unlocked' - verify against scenario data + if method == 'unlocked' + if !object['locked'] + Rails.logger.info "[BreakEscape] Object is unlocked in scenario data, granting access" return true - when 'pin', 'password' - result = object['requires'].to_s == attempt.to_s - Rails.logger.info "[BreakEscape] Password validation: required='#{object['requires']}', attempt='#{attempt}', result=#{result}" - return result + else + Rails.logger.warn "[BreakEscape] SECURITY VIOLATION: Client sent method='unlocked' for LOCKED object: #{target_id}" + return false end end + + # NPC unlock: Validate NPC has been encountered and has permission to unlock this object + if method == 'npc' + npc_id = attempt # NPC id is passed as 'attempt' + return validate_npc_unlock(npc_id, target_id) + end + + case method + when 'key', 'lockpick', 'biometric', 'bluetooth', 'rfid' + # Client validated the unlock - trust it + return true + when 'pin', 'password' + result = object['requires'].to_s == attempt.to_s + Rails.logger.info "[BreakEscape] Password validation: required='#{object['requires']}', attempt='#{attempt}', result=#{result}" + return result + end end Rails.logger.warn "[BreakEscape] Object not found: #{target_id}" false diff --git a/scenarios/cybok_heist/scenario.json.erb b/scenarios/cybok_heist/scenario.json.erb index ca28c66..c30c2c6 100644 --- a/scenarios/cybok_heist/scenario.json.erb +++ b/scenarios/cybok_heist/scenario.json.erb @@ -32,6 +32,7 @@ { "type": "bag", "name": "Heist Gear Backpack", + "locked": false, "contents": [ { "type": "lockpick", diff --git a/scripts/scenario-schema.json b/scripts/scenario-schema.json index 2b0a203..3483df2 100644 --- a/scripts/scenario-schema.json +++ b/scripts/scenario-schema.json @@ -289,6 +289,8 @@ "tablet", "safe", "suitcase", + "bag", + "briefcase", "bluetooth_scanner", "fingerprint_kit", "pin-cracker", @@ -296,7 +298,8 @@ "flag-station", "text_file", "id_badge", - "rfid_cloner" + "rfid_cloner", + "office-misc-hdd4" ], "description": "Item type. Custom types (like 'id_badge', 'rfid_cloner') are valid for #give_item tags in Ink scripts." }, diff --git a/scripts/validate_scenario.rb b/scripts/validate_scenario.rb index deff62a..6e82836 100755 --- a/scripts/validate_scenario.rb +++ b/scripts/validate_scenario.rb @@ -207,6 +207,14 @@ def check_common_issues(json_data) has_container_with_contents = true end + # REQUIRED: Containers with contents must specify locked field explicitly + container_types = ['briefcase', 'bag', 'bag1', 'suitcase', 'safe', 'pc', 'bin1'] + if container_types.include?(obj['type']) && obj['contents'] && !obj['contents'].empty? + unless obj.key?('locked') + issues << "❌ INVALID: '#{path}' is a container with contents but missing required 'locked' field - must be explicitly true or false for server-side validation" + end + end + # Track readable items (notes, documents) if obj['readable'] || (obj['type'] == 'notes' && obj['text']) has_readable_items = true diff --git a/start_server.sh b/start_server.sh new file mode 100755 index 0000000..ba7315c --- /dev/null +++ b/start_server.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# kill any puma processes +pkill -9 -f puma; sleep 1 +# start the server +bundle exec rails server -b 0.0.0.0 -p 3000 2>&1 &