Skip to content

Commit 1240f62

Browse files
committed
csp + error
1 parent 80efa07 commit 1240f62

File tree

8 files changed

+109
-256
lines changed

8 files changed

+109
-256
lines changed

app.rb

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,22 @@ def development? = self.class.development?
9191

9292
plugin :content_security_policy do |csp|
9393
csp.default_src :none
94-
csp.style_src :self
95-
csp.script_src :self
94+
csp.style_src :self, "'unsafe-inline'" # Allow inline styles for Starlight
95+
csp.script_src :self, "'unsafe-inline'" # Allow inline scripts for progressive enhancement
9696
csp.connect_src :self
97-
csp.img_src :self
97+
csp.img_src :self, 'data:', 'blob:'
9898
csp.font_src :self, 'data:'
9999
csp.form_action :self
100100
csp.base_uri :none
101-
csp.frame_ancestors :self
102-
csp.frame_src :self
101+
csp.frame_ancestors :none # More restrictive than :self
102+
csp.frame_src :none # More restrictive than :self
103+
csp.object_src :none # Prevent object/embed/applet
104+
csp.media_src :none # Prevent media sources
105+
csp.manifest_src :none # Prevent manifest
106+
csp.worker_src :none # Prevent workers
107+
csp.child_src :none # Prevent child contexts
103108
csp.block_all_mixed_content
109+
csp.upgrade_insecure_requests # Upgrade HTTP to HTTPS
104110
end
105111

106112
plugin :default_headers,
@@ -110,7 +116,11 @@ def development? = self.class.development?
110116
'X-Frame-Options' => 'DENY',
111117
'X-Permitted-Cross-Domain-Policies' => 'none',
112118
'Referrer-Policy' => 'strict-origin-when-cross-origin',
113-
'Permissions-Policy' => 'geolocation=(), microphone=(), camera=()'
119+
'Permissions-Policy' => 'geolocation=(), microphone=(), camera=()',
120+
'Strict-Transport-Security' => 'max-age=31536000; includeSubDomains; preload',
121+
'Cross-Origin-Embedder-Policy' => 'require-corp',
122+
'Cross-Origin-Opener-Policy' => 'same-origin',
123+
'Cross-Origin-Resource-Policy' => 'same-origin'
114124

115125
plugin :exception_page
116126
plugin :error_handler do |error|

app/auto_source_routes.rb

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -85,48 +85,33 @@ def handle_auto_source_error(router, error)
8585
XmlBuilder.build_error_feed(message: error.message)
8686
end
8787

88-
# Helper methods that need to be implemented by the main app
88+
# Delegate to centralized ResponseHelpers methods
8989
def bad_request_response(router, message)
90-
router.response.status = 400
91-
router.response['Content-Type'] = 'application/xml'
92-
XmlBuilder.build_access_denied_feed(message)
90+
ResponseHelpers.bad_request_response_with_router(router, message)
9391
end
9492

9593
def unauthorized_response(router)
96-
router.response.status = 401
97-
router.response['Content-Type'] = 'application/xml'
98-
XmlBuilder.build_error_feed(message: 'Unauthorized')
94+
ResponseHelpers.unauthorized_response_with_router(router)
9995
end
10096

10197
def access_denied_response(router, url)
102-
router.response.status = 403
103-
router.response['Content-Type'] = 'application/xml'
104-
XmlBuilder.build_access_denied_feed(url)
98+
ResponseHelpers.access_denied_response_with_router(router, url)
10599
end
106100

107101
def method_not_allowed_response(router)
108-
router.response.status = 405
109-
router.response['Content-Type'] = 'application/xml'
110-
XmlBuilder.build_error_feed(message: 'Method Not Allowed')
102+
ResponseHelpers.method_not_allowed_response_with_router(router)
111103
end
112104

113105
def internal_error_response(router)
114-
router.response.status = 500
115-
router.response['Content-Type'] = 'application/xml'
116-
XmlBuilder.build_error_feed(message: 'Internal Server Error')
106+
ResponseHelpers.internal_error_response_with_router(router)
117107
end
118108

119109
def forbidden_origin_response(router)
120-
router.response.status = 403
121-
router.response['Content-Type'] = 'application/xml'
122-
XmlBuilder.build_error_feed(message: 'Forbidden Origin')
110+
ResponseHelpers.forbidden_origin_response_with_router(router)
123111
end
124112

125113
def configure_auto_source_headers(router)
126-
router.response['Content-Type'] = 'application/xml'
127-
router.response['Cache-Control'] = 'public, max-age=3600'
128-
router.response['X-Content-Type-Options'] = 'nosniff'
129-
router.response['X-XSS-Protection'] = '1; mode=block'
114+
ResponseHelpers.configure_auto_source_headers_with_router(router)
130115
end
131116

132117
def validate_and_decode_base64(encoded_url)

app/response_helpers.rb

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module Web
99
module ResponseHelpers
1010
module_function
1111

12+
# Methods that work with response object directly (for main app)
1213
def unauthorized_response
1314
response.status = 401
1415
response['Content-Type'] = 'application/xml'
@@ -58,6 +59,57 @@ def configure_auto_source_headers
5859
response['X-Content-Type-Options'] = 'nosniff'
5960
response['X-XSS-Protection'] = '1; mode=block'
6061
end
62+
63+
# Methods that work with router objects (for route modules)
64+
def unauthorized_response_with_router(router)
65+
router.response.status = 401
66+
router.response['Content-Type'] = 'application/xml'
67+
router.response['WWW-Authenticate'] = 'Basic realm="Auto Source"'
68+
XmlBuilder.build_error_feed(message: 'Unauthorized')
69+
end
70+
71+
def forbidden_origin_response_with_router(router)
72+
router.response.status = 403
73+
router.response['Content-Type'] = 'application/xml'
74+
XmlBuilder.build_error_feed(message: 'Origin is not allowed.')
75+
end
76+
77+
def access_denied_response_with_router(router, url)
78+
router.response.status = 403
79+
router.response['Content-Type'] = 'application/xml'
80+
XmlBuilder.build_access_denied_feed(url)
81+
end
82+
83+
def not_found_response_with_router(router)
84+
router.response.status = 404
85+
router.response['Content-Type'] = 'application/xml'
86+
XmlBuilder.build_error_feed(message: 'Feed not found', title: 'Not Found')
87+
end
88+
89+
def bad_request_response_with_router(router, message)
90+
router.response.status = 400
91+
router.response['Content-Type'] = 'application/xml'
92+
XmlBuilder.build_error_feed(message: message, title: 'Bad Request')
93+
end
94+
95+
def method_not_allowed_response_with_router(router)
96+
router.response.status = 405
97+
router.response['Content-Type'] = 'application/xml'
98+
XmlBuilder.build_error_feed(message: 'Method Not Allowed')
99+
end
100+
101+
def internal_error_response_with_router(router)
102+
router.response.status = 500
103+
router.response['Content-Type'] = 'application/xml'
104+
XmlBuilder.build_error_feed(message: 'Internal Server Error')
105+
end
106+
107+
def configure_auto_source_headers_with_router(router)
108+
router.response['Content-Type'] = 'application/xml'
109+
router.response['Cache-Control'] = 'public, max-age=3600'
110+
router.response['X-Content-Type-Options'] = 'nosniff'
111+
router.response['X-XSS-Protection'] = '1; mode=block'
112+
end
61113
end
62114
end
63115
end

app/static_file_helpers.rb

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -61,23 +61,6 @@ def serve_astro_file(file_path)
6161
response['Content-Type'] = 'text/html'
6262
File.read(file_path)
6363
end
64-
65-
##
66-
# Validate and decode Base64 string safely
67-
# @param encoded_string [String] Base64 encoded string
68-
# @return [String, nil] decoded string if valid, nil if invalid
69-
def validate_and_decode_base64(encoded_string)
70-
return nil unless encoded_string.is_a?(String)
71-
return nil if encoded_string.empty?
72-
73-
# Check if string contains only valid Base64 characters
74-
return nil unless encoded_string.match?(%r{\A[A-Za-z0-9+/]*={0,2}\z})
75-
76-
# Attempt to decode
77-
Base64.decode64(encoded_string)
78-
rescue ArgumentError
79-
nil
80-
end
8164
end
8265
end
8366
end

frontend/src/__tests__/url-restrictions.test.js

Lines changed: 0 additions & 194 deletions
This file was deleted.

0 commit comments

Comments
 (0)