Initial commit (history cleared)
CI / test (3.4.1) (push) Has been cancelled

This commit is contained in:
2026-04-29 11:21:39 +01:00
commit 298610b5f6
277 changed files with 30877 additions and 0 deletions
+445
View File
@@ -0,0 +1,445 @@
<!DOCTYPE html>
<html lang="en" data-bs-theme="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>RailsGoat - OWASP Security Training</title>
<%#= csrf_meta_tags %> <!-- <~ What is this for? I hear it helps w/ JS and Sea-surfing.....whatevz -->
<!-- VULNERABILITY A03:2025 - Software Supply Chain Failures
Missing Subresource Integrity (SRI) checks on CDN assets
If the CDN is compromised, malicious code can be injected without detection
SECURE: Should include integrity="sha384-..." crossorigin="anonymous"
See: /tutorials/supply_chain for exploitation details
-->
<!-- Load jQuery FIRST - other scripts depend on it -->
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<!-- Bootstrap CSS and Icons -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css" rel="stylesheet">
<!-- Rails assets - loaded AFTER jQuery -->
<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => "reload" %>
<%= javascript_include_tag "application", "data-turbolinks-track" => "reload" %>
<!-- Bootstrap JS - loaded last -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- FullCalendar and dependencies for PTO page -->
<script src="https://cdn.jsdelivr.net/npm/moment@2.29.4/moment.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/fullcalendar@3.10.5/dist/fullcalendar.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@3.10.5/dist/fullcalendar.min.js"></script>
<!-- Modern Design System -->
<style>
:root {
--rg-primary: #e63946;
--rg-primary-dark: #d62828;
--rg-secondary: #457b9d;
--rg-secondary-dark: #1d3557;
--rg-success: #06d6a0;
--rg-warning: #ffb703;
--rg-danger: #e63946;
--rg-light: #f8f9fa;
--rg-dark: #1d3557;
--rg-sidebar-width: 250px;
--rg-header-height: 60px;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: var(--rg-light);
min-height: 100vh;
}
/* Modern Header */
.rg-header {
background: white;
border-bottom: none;
height: var(--rg-header-height);
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1030;
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
overflow: visible;
backdrop-filter: blur(10px);
background: rgba(255, 255, 255, 0.95);
}
.rg-header .container-fluid {
padding-left: 2rem;
padding-right: 2rem;
}
.rg-header .col-auto:first-child {
padding-left: 0;
margin-left: 0;
}
.rg-brand {
font-size: 1.5rem;
font-weight: 700;
color: var(--rg-primary);
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 0.5rem;
line-height: 1;
}
.rg-brand:hover {
color: var(--rg-primary-dark);
}
.rg-brand i {
font-size: 1.75rem;
line-height: 1;
display: inline-block;
min-width: 1.75rem;
text-align: center;
}
.rg-header .row {
margin-left: 0;
margin-right: 0;
}
/* Fix alignment of button_to forms in header */
.rg-header form {
margin: 0;
padding: 0;
display: inline-block;
}
/* Modern Sidebar */
.rg-sidebar {
position: fixed;
top: var(--rg-header-height);
left: 0;
bottom: 0;
width: var(--rg-sidebar-width);
background: var(--rg-dark);
color: white;
overflow-y: auto;
transition: transform 0.3s ease;
box-shadow: 2px 0 12px rgba(0,0,0,0.1);
}
.rg-sidebar-nav {
list-style: none;
padding: 1rem 0;
margin: 0;
}
.rg-sidebar-nav li a {
display: flex;
align-items: center;
padding: 0.75rem 1.5rem;
margin: 0.25rem 0.75rem;
color: rgba(255,255,255,0.8);
text-decoration: none;
transition: all 0.2s;
border-radius: 0.75rem;
}
.rg-sidebar-nav li a:hover,
.rg-sidebar-nav li a.active {
background: rgba(255,255,255,0.15);
color: white;
transform: translateX(4px);
}
.rg-sidebar-nav li a i {
font-size: 1.25rem;
margin-right: 0.75rem;
width: 24px;
text-align: center;
}
/* Main Content */
.rg-main {
margin-top: var(--rg-header-height);
margin-left: var(--rg-sidebar-width);
padding: 2rem;
min-height: calc(100vh - var(--rg-header-height));
}
.rg-main.no-sidebar {
margin-left: 0;
}
/* Modern Cards */
.rg-card, .card {
background: white;
border-radius: 1rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
margin-bottom: 1.5rem;
border: none;
overflow: hidden;
}
.rg-card:hover, .card:hover {
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
transition: box-shadow 0.3s ease;
}
/* Modern Buttons */
.btn {
border-radius: 0.75rem;
padding: 0.5rem 1.25rem;
font-weight: 500;
transition: all 0.2s ease;
border: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.btn-sm {
border-radius: 0.625rem;
padding: 0.375rem 1rem;
font-size: 0.875rem;
}
.btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.btn-primary {
background: linear-gradient(135deg, var(--rg-primary) 0%, var(--rg-primary-dark) 100%);
border-color: transparent;
color: white;
}
.btn-primary:hover {
background: linear-gradient(135deg, var(--rg-primary-dark) 0%, #c11f2b 100%);
border-color: transparent;
color: white;
}
.btn-outline-primary {
border: 2px solid var(--rg-primary);
color: var(--rg-primary);
background: white;
}
.btn-outline-primary:hover {
background: var(--rg-primary);
color: white;
border-color: var(--rg-primary);
}
.btn-warning {
background: linear-gradient(135deg, #ffb703 0%, #fb8500 100%);
color: white;
border: none;
}
.btn-warning:hover {
background: linear-gradient(135deg, #fb8500 0%, #e85d04 100%);
color: white;
}
.btn-outline-secondary {
border: 2px solid #dee2e6;
color: #6c757d;
background: white;
}
.btn-outline-secondary:hover {
background: #f8f9fa;
border-color: #adb5bd;
color: #495057;
}
/* Modern Alerts */
.alert {
border-radius: 0.875rem;
border: none;
padding: 1rem 1.25rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
}
/* Modern Tables */
.table {
border-radius: 0.75rem;
overflow: hidden;
}
.table thead th {
background: var(--rg-light);
font-weight: 600;
border-bottom: 2px solid #dee2e6;
padding: 1rem;
}
.table tbody tr {
transition: background-color 0.2s ease;
}
.table tbody tr:hover {
background-color: rgba(230, 57, 70, 0.03);
}
/* Modern Badges */
.badge {
border-radius: 0.5rem;
padding: 0.35rem 0.65rem;
font-weight: 500;
}
/* Modern Dropdowns */
.dropdown-menu {
border-radius: 0.75rem;
border: none;
box-shadow: 0 4px 16px rgba(0,0,0,0.12);
padding: 0.5rem;
}
.dropdown-item {
border-radius: 0.5rem;
padding: 0.5rem 1rem;
transition: all 0.2s ease;
}
.dropdown-item:hover {
background-color: rgba(230, 57, 70, 0.08);
transform: translateX(2px);
}
/* Modern Widget Styling */
.widget {
background: white;
border-radius: 1rem;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
margin-bottom: 1.5rem;
border: none;
overflow: hidden;
}
.widget-header {
background: var(--rg-light);
border-bottom: none;
padding: 1.25rem 1.5rem;
border-radius: 1rem 1rem 0 0;
}
.widget-header .title {
font-weight: 600;
color: var(--rg-dark);
font-size: 1.1rem;
}
.widget-body {
padding: 1.5rem;
}
/* Modern Login Page */
.rg-login-wrapper {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--rg-secondary-dark) 0%, var(--rg-secondary) 100%);
position: relative;
}
.rg-login-wrapper::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at 20% 50%, rgba(230, 57, 70, 0.1) 0%, transparent 50%),
radial-gradient(circle at 80% 80%, rgba(69, 123, 157, 0.1) 0%, transparent 50%);
}
.rg-login-card {
background: white;
border-radius: 1.5rem;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
padding: 3rem;
width: 100%;
max-width: 450px;
position: relative;
z-index: 1;
}
.form-control, .form-select {
border-radius: 0.75rem;
border: 2px solid #e9ecef;
padding: 0.75rem 1rem;
transition: all 0.2s ease;
}
.form-control:focus, .form-select:focus {
border-color: var(--rg-primary);
box-shadow: 0 0 0 3px rgba(230, 57, 70, 0.1);
}
.input-group-text {
border-radius: 0.75rem 0 0 0.75rem;
border: 2px solid #e9ecef;
border-right: none;
background: #f8f9fa;
}
.input-group .form-control {
border-radius: 0 0.75rem 0.75rem 0;
}
.rg-login-header {
text-align: center;
margin-bottom: 2rem;
}
.rg-login-logo {
font-size: 3rem;
color: var(--rg-primary);
margin-bottom: 1rem;
}
/* Responsive */
@media (max-width: 768px) {
.rg-sidebar {
transform: translateX(-100%);
}
.rg-sidebar.show {
transform: translateX(0);
}
.rg-main {
margin-left: 0;
}
}
/* VULNERABILITY: XSS via cookie font-size */
<%
if cookies[:font]
%>
body { font-size:<%= raw cookies[:font] %> !important;}
<%
end
%>
</style>
</head>
<body>
<%= render "layouts/shared/header" %>
<%= render "layouts/shared/sidebar" %>
<main class="rg-main <%= 'no-sidebar' unless current_user %>">
<%= render "layouts/shared/messages" %>
<%= yield %>
</main>
<%= render "layouts/shared/footer" %>
</body>
</html>
+56
View File
@@ -0,0 +1,56 @@
<% if current_user %>
<footer class="border-top mt-5 py-4 text-center text-muted bg-white">
<div class="container">
<div class="row">
<div class="col-md-12">
<p class="mb-1">
<i class="bi bi-shield-check"></i>
&copy; <%= Date.current.year %> The Open Worldwide Application Security Project - OWASP
</p>
<p class="small mb-0">
<a href="https://owasp.org" target="_blank" class="text-decoration-none me-3">
<i class="bi bi-globe"></i> OWASP.org
</a>
<a href="https://github.com/OWASP/railsgoat" target="_blank" class="text-decoration-none me-3">
<i class="bi bi-github"></i> GitHub
</a>
<a href="https://github.com/OWASP/railsgoat/wiki" target="_blank" class="text-decoration-none">
<i class="bi bi-book"></i> Documentation
</a>
</p>
</div>
</div>
</div>
</footer>
<% end %>
<!-- Scroll to Top Button -->
<button id="scrollTopBtn" class="btn btn-primary rounded-circle position-fixed bottom-0 end-0 m-4" style="width: 48px; height: 48px; display: none; z-index: 1000;" title="Scroll to top">
<i class="bi bi-arrow-up"></i>
</button>
<script>
// Modern scroll-to-top without jQuery
(function() {
const scrollBtn = document.getElementById('scrollTopBtn');
if (scrollBtn) {
// Show/hide button based on scroll position
window.addEventListener('scroll', function() {
if (window.pageYOffset > 300) {
scrollBtn.style.display = 'block';
} else {
scrollBtn.style.display = 'none';
}
});
// Scroll to top on click
scrollBtn.addEventListener('click', function() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
}
})();
</script>
+115
View File
@@ -0,0 +1,115 @@
<% if current_user %>
<!-- Authenticated Header -->
<header class="rg-header">
<div class="container-fluid h-100">
<div class="row h-100 align-items-center">
<div class="col-auto">
<a href="<%= home_dashboard_index_path %>" class="rg-brand">
<i class="bi bi-shield-fill-exclamation"></i> RailsGoat
</a>
</div>
<div class="col"></div>
<div class="col-auto">
<div class="d-flex align-items-center gap-3">
<!-- Font Size Controls -->
<div class="btn-group btn-group-sm" role="group" aria-label="Font size controls">
<a href="/dashboard/home?font=8pt" class="btn btn-outline-secondary" style="font-size: 10pt;" title="Small font" aria-label="Small font" data-turbolinks="false">
<i class="bi bi-type"></i>
</a>
<a href="/dashboard/home?font=200%25" class="btn btn-outline-secondary" style="font-size: 14pt;" title="Large font" aria-label="Large font" data-turbolinks="false">
<i class="bi bi-type"></i>
</a>
</div>
<!-- Tutorial Link -->
<%= button_to "https://github.com/OWASP/railsgoat/wiki", {
method: "get",
class: "btn btn-sm btn-outline-primary",
onclick: "window.open('https://github.com/OWASP/railsgoat/wiki', '_blank'); return false;"
} do %>
<i class="bi bi-book"></i> Tutorials
<% end %>
<!-- User Dropdown -->
<div class="dropdown">
<button class="btn btn-link text-decoration-none dropdown-toggle d-flex align-items-center gap-2" type="button" data-bs-toggle="dropdown" aria-expanded="false">
<div class="bg-primary rounded-circle d-flex align-items-center justify-content-center" style="width: 32px; height: 32px;">
<i class="bi bi-person-fill text-white"></i>
</div>
<!--
VULNERABILITY: XSS via html_safe
I'm going to use HTML safe because we had some weird stuff
going on with funny chars and jquery, plus it says safe so I'm guessing
nothing bad will happen
-->
<span class="text-dark"><%= current_user.first_name.html_safe %></span>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<%= link_to user_account_settings_path(user_id: current_user.id), class: "dropdown-item" do %>
<i class="bi bi-gear"></i> Account Settings
<% end %>
</li>
<li><hr class="dropdown-divider"></li>
<li>
<%= link_to logout_path, class: "dropdown-item text-danger" do %>
<i class="bi bi-box-arrow-right"></i> Logout
<% end %>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</header>
<% else %>
<!-- Unauthenticated Header -->
<header class="rg-header">
<div class="container-fluid h-100">
<div class="row h-100 align-items-center">
<div class="col-auto">
<a href="<%= login_path %>" class="rg-brand">
<i class="bi bi-shield-fill-exclamation"></i> RailsGoat
</a>
</div>
<div class="col"></div>
<div class="col-auto">
<div class="d-flex align-items-center gap-2">
<%= link_to credentials_tutorials_path, class: "btn btn-sm btn-warning" do %>
<i class="bi bi-key"></i> Demo Credentials
<% end %>
<%= button_to "https://github.com/OWASP/railsgoat/wiki", {
method: "get",
class: "btn btn-sm btn-outline-primary",
onclick: "window.open('https://github.com/OWASP/railsgoat/wiki', '_blank'); return false;"
} do %>
<i class="bi bi-book"></i> Tutorials
<% end %>
<%= button_to signup_path, {
class: "btn btn-sm btn-primary",
method: "get"
} do %>
<i class="bi bi-person-plus"></i> Sign Up
<% end %>
<%= button_to login_path, {
class: "btn btn-sm btn-outline-primary",
method: "get"
} do %>
<i class="bi bi-box-arrow-in-right"></i> Login
<% end %>
</div>
</div>
</div>
</div>
</header>
<% end %>
+38
View File
@@ -0,0 +1,38 @@
<% flash.each do |name, msg| %>
<% name = name.to_sym %>
<%
alert_class = case name
when :error, :alert
'alert-danger'
when :success, :notice
'alert-success'
when :info
'alert-info'
when :warning
'alert-warning'
else
'alert-secondary'
end
icon_class = case name
when :error, :alert
'bi-exclamation-circle-fill'
when :success, :notice
'bi-check-circle-fill'
when :info
'bi-info-circle-fill'
when :warning
'bi-exclamation-triangle-fill'
else
'bi-bell-fill'
end
%>
<div class="alert <%= alert_class %> alert-dismissible show d-flex align-items-center" role="alert">
<i class="bi <%= icon_class %> me-2"></i>
<div class="flex-grow-1">
<%= msg %>
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
</div>
<% end %>
+90
View File
@@ -0,0 +1,90 @@
<% if current_user %>
<nav class="rg-sidebar">
<ul class="rg-sidebar-nav">
<li>
<%= link_to home_dashboard_index_path, class: "#{controller_name == 'dashboard' ? 'active' : ''}" do %>
<i class="bi bi-speedometer2"></i>
<span>Dashboard</span>
<% end %>
</li>
<% if is_admin? %>
<li class="mt-3">
<div class="px-4 py-2 text-white-50 text-uppercase small fw-bold">Admin</div>
</li>
<li>
<%= link_to admin_dashboard_path(admin_id: "1"), class: "#{controller_name == 'admin' && action_name == 'dashboard' ? 'active' : ''}" do %>
<i class="bi bi-people"></i>
<span>Manage Users</span>
<% end %>
</li>
<li>
<%= link_to admin_analytics_path(admin_id: "1"), class: "#{controller_name == 'admin' && action_name == 'analytics' ? 'active' : ''}" do %>
<i class="bi bi-graph-up"></i>
<span>View Analytics</span>
<% end %>
</li>
<% end %>
<li class="mt-3">
<div class="px-4 py-2 text-white-50 text-uppercase small fw-bold">Employee</div>
</li>
<li>
<%= link_to user_benefit_forms_path(user_id: current_user.id), class: "#{controller_name == 'benefit_forms' ? 'active' : ''}" do %>
<i class="bi bi-file-earmark-text"></i>
<span>Benefit Forms</span>
<% end %>
</li>
<li>
<%= link_to user_retirement_index_path(user_id: current_user.id), class: "#{controller_name == 'retirement' ? 'active' : ''}" do %>
<i class="bi bi-piggy-bank"></i>
<span>401k Info</span>
<% end %>
</li>
<li>
<%= link_to user_paid_time_off_index_path(user_id: current_user.id), class: "#{controller_name == 'paid_time_off' ? 'active' : ''}" do %>
<i class="bi bi-calendar-check"></i>
<span>PTO</span>
<% end %>
</li>
<li>
<%= link_to user_work_info_index_path(user_id: current_user.id), class: "#{controller_name == 'work_info' ? 'active' : ''}" do %>
<i class="bi bi-briefcase"></i>
<span>Work Info</span>
<% end %>
</li>
<li>
<%= link_to user_performance_index_path(user_id: current_user.id), class: "#{controller_name == 'performance' ? 'active' : ''}" do %>
<i class="bi bi-bar-chart"></i>
<span>Performance</span>
<% end %>
</li>
<li>
<%= link_to user_messages_path(user_id: current_user.id), class: "#{controller_name == 'messages' ? 'active' : ''}" do %>
<i class="bi bi-envelope"></i>
<span>Messages</span>
<% end %>
</li>
<li>
<%= link_to user_pay_index_path(user_id: current_user.id), class: "#{controller_name == 'pay' ? 'active' : ''}" do %>
<i class="bi bi-credit-card"></i>
<span>Pay</span>
<% end %>
</li>
<li class="mt-4 pt-4 border-top border-secondary">
<div class="px-4 py-2 text-white-50 small">
<i class="bi bi-shield-exclamation"></i>
OWASP RailsGoat <%= Rails::VERSION::STRING %>
</div>
</li>
</ul>
</nav>
<% end %>