2024-07-03 19:50:41 -07:00
|
|
|
class SwfAssetsController < ApplicationController
|
|
|
|
# We're very careful with what content is allowed to load. This is because
|
|
|
|
# asset movies run arbitrary JS, and, while we generally trust content from
|
|
|
|
# Neopets.com, let's not be *allowing* movie JS to do whatever it wants! This
|
|
|
|
# is a good default security stance, even if we don't foresee an attack.
|
|
|
|
content_security_policy do |policy|
|
|
|
|
policy.sandbox "allow-scripts"
|
|
|
|
policy.default_src "none"
|
|
|
|
|
|
|
|
policy.img_src -> {
|
|
|
|
src_list(
|
|
|
|
helpers.image_url("favicon.png"),
|
|
|
|
@swf_asset.image_url,
|
|
|
|
*@swf_asset.canvas_movie_sprite_urls,
|
Simplify CSP header for SWF asset embeds, to fix 502 for some assets
Fun little bug: viewing the "Engulfed in Flames Effect" item was
showing our "502 Bad Gateway" custom error page in the embed. This is
because the Rails app was providing a `Content-Security-Policy` header
value that was longer than nginx is configured by default to allow, so
it was refusing the response, and showing the same 502 error as if the
app hadn't responded at all. (We discovered this by opening
`/var/log/nginx/error.log`, which explained this very clearly, ty~!)
In this change, we no longer list every `images.neopets.com` asset,
instead marking the entire domain as a valid image source for the
SWF asset embed iframe. I don't _love_ this solution, I liked the
property of specifying literally exactly the assets we allow! But I
don't think there's any practical danger here, and it helps a *lot* for
making this more reliable.
(If we could have solved this reliably by increasing nginx's allowed
response header size, I probably would've done that? But I researched a
bit, and ultimately concluded that I don't trust other intermediary
software like firewalls not to have the same issue. Let's not be
pushing the limits of HTTP headers of all things!)
2024-09-12 15:59:18 -07:00
|
|
|
|
|
|
|
# For images, `images.neopets.com` is a generally safe host to load
|
|
|
|
# from (shouldn't be a vulnerable site or exfiltration vector), and
|
|
|
|
# doing this can help make this header a *lot* shorter, which helps
|
|
|
|
# our nginx reverse proxy (and probably some clients) handle it. (For
|
|
|
|
# example, see asset `667993` for "Engulfed in Flames Effect".)
|
|
|
|
hosts: ["https://images.neopets.com"],
|
2024-07-03 19:50:41 -07:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
Use {script,style}_src instead of _elem, for better compatibility
Oh, I didn't realize the `_elem` variant of these parts of the
`Content-Security-Policy` is newer, and so doesn't even work on my
current version of Safari on my Mac.
My rationale at the time was: `script_src_elem` is stricter against
things like imports, and I figured, ok let's do the strictest policy
that works. But since it's not fully compatible with browsers even
*I'm* using right now, and I'm not aware of an actual problem it would
prevent, let's back off that a bit! This should have the same effective
security properties for our case.
Note that the effect of this compatibility issue wasn't *weakening* the
policy; it was being *too* strict, by blocking the scripts and the
stylesheets. This is because `script_src_elem` was ignored, and
`script_src` was absent, so it fell back to `default_src none`.
2024-07-06 12:47:59 -07:00
|
|
|
policy.script_src -> {
|
2024-07-03 19:50:41 -07:00
|
|
|
src_list(
|
|
|
|
helpers.javascript_url("lib/easeljs.min"),
|
|
|
|
helpers.javascript_url("lib/tweenjs.min"),
|
|
|
|
helpers.javascript_url("swf_assets/show"),
|
|
|
|
@swf_asset.canvas_movie_library_url,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
Use {script,style}_src instead of _elem, for better compatibility
Oh, I didn't realize the `_elem` variant of these parts of the
`Content-Security-Policy` is newer, and so doesn't even work on my
current version of Safari on my Mac.
My rationale at the time was: `script_src_elem` is stricter against
things like imports, and I figured, ok let's do the strictest policy
that works. But since it's not fully compatible with browsers even
*I'm* using right now, and I'm not aware of an actual problem it would
prevent, let's back off that a bit! This should have the same effective
security properties for our case.
Note that the effect of this compatibility issue wasn't *weakening* the
policy; it was being *too* strict, by blocking the scripts and the
stylesheets. This is because `script_src_elem` was ignored, and
`script_src` was absent, so it fell back to `default_src none`.
2024-07-06 12:47:59 -07:00
|
|
|
policy.style_src -> {
|
2024-07-03 19:50:41 -07:00
|
|
|
src_list(
|
|
|
|
helpers.stylesheet_url("swf_assets/show"),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
def show
|
|
|
|
@swf_asset = SwfAsset.find params[:id]
|
|
|
|
render layout: nil
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
Simplify CSP header for SWF asset embeds, to fix 502 for some assets
Fun little bug: viewing the "Engulfed in Flames Effect" item was
showing our "502 Bad Gateway" custom error page in the embed. This is
because the Rails app was providing a `Content-Security-Policy` header
value that was longer than nginx is configured by default to allow, so
it was refusing the response, and showing the same 502 error as if the
app hadn't responded at all. (We discovered this by opening
`/var/log/nginx/error.log`, which explained this very clearly, ty~!)
In this change, we no longer list every `images.neopets.com` asset,
instead marking the entire domain as a valid image source for the
SWF asset embed iframe. I don't _love_ this solution, I liked the
property of specifying literally exactly the assets we allow! But I
don't think there's any practical danger here, and it helps a *lot* for
making this more reliable.
(If we could have solved this reliably by increasing nginx's allowed
response header size, I probably would've done that? But I researched a
bit, and ultimately concluded that I don't trust other intermediary
software like firewalls not to have the same issue. Let's not be
pushing the limits of HTTP headers of all things!)
2024-09-12 15:59:18 -07:00
|
|
|
def src_list(*urls, hosts: [])
|
|
|
|
urls.
|
|
|
|
# Ignore any `nil`s that might arise
|
|
|
|
filter(&:present?).
|
|
|
|
# Remove query strings from URLs (they're invalid in CSPs)
|
|
|
|
map { |url| url.sub(/\?.*\z/, "") }.
|
|
|
|
# For the given `hosts`, remove all their specific URLs, and just list
|
|
|
|
# the host itself.
|
|
|
|
reject { |url| hosts.any? { |h| url.start_with? h } } + hosts
|
2024-07-03 19:50:41 -07:00
|
|
|
end
|
|
|
|
end
|