-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add speedscope remote viewer #33
Conversation
54b47dd
to
19ad44a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code itself looks fine, but I feel like this gem overall is starting to do too much, as it is now trying to cover both production and development profiling use-cases.
It would be nice if there was a safety to prevent trying to use this remove viewer if the app is in production, right now it relies on users installing the middleware for development only.
I'd like to also ask @Shopify/appsec review this since rails application security isn't my forte.
<title>App Profiler</title> | ||
</head> | ||
<body> | ||
#{html} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe rendering like this leaves you vulnerable to XSS if html
ever had anything malicious in it since ActiveSupport provides the default sanitizations
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sanitize
belongs to Action View which IIRC uses https://github.com/rails/rails-html-sanitizer. I think I can use that here.
iframe.style.width = '100vw'; | ||
iframe.style.height = '100vh'; | ||
iframe.style.border = 'none'; | ||
iframe.setAttribute('src', '/app_profiler/viewer/index.html#profileURL=' + objUrl + '&title=' + 'Flamegraph for #{name}'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similarly here - not sure if name
can / will ever be user controlled, but this looks like it would lead to XSS
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The name would be the match of \A/app_profiler/(.*)\z
in the path as long as it maps to a file's basename without extension (eg. tmp/app_profiler/my_profile-4.json
=> my_profile-4
) . Since the filename is sanitized I don't think we need to worry, but I can remove the interpolation and make the title generalized instead if you'd like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does this sanitization/creation of this filename happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here:
app_profiler/lib/app_profiler/profile.rb
Line 66 in 576c29a
def path |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
lib/app_profiler/yarn/command.rb
Outdated
def yarn(command) | ||
setup_yarn unless yarn_setup | ||
exec("yarn #{command}") do | ||
raise YarnError, "Failed to run #{command}." | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems very dangerous. Would highly recommend we not just accept any command pass it to yarn. I don't know it well enough but if there's any flag that yarn takes that could result in arbitrary read/write, it would be bad. Would recommend instead we allow-list the commands or actual write the methods with the allowed yarn commands we'd accept
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was already in the codebase, I just moved it. Will address in a separate commit.
lib/app_profiler/yarn/command.rb
Outdated
def exec(command) | ||
system(command).tap do |return_code| | ||
yield unless return_code | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, this seems needlessly dangerous. Instead of accepting any command and passing it to system
, we should allow list what we want to run or just write individual methods with no variability
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a private method and the only consumer is yarn
. I can inline it if that would make sense to you?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pending appsec feedback
eb43259
to
a70273d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a comment to help add more proactive security around the call to system(...)
. Pete mentioned, the most secure way to use the system(...)
function is to have an allowlist of things that it can do. You do a great job of this with the ensure_command_valid?
command, and now the only remaining place of ambiguity is in the options
parameter.
From what I can see, yarn init
only gets called with the --yes
option, and yarn add
only gets called with the speedscope
option followed by either the file path, or --dev --ignore-workspace-root-check
. Narrowing the scope of potential inputs and enforcing devs working on this in the future to be explicit about what they intend to allow will minimize the risk of something slipping between the cracks into this powerful method
lib/app_profiler/yarn/command.rb
Outdated
def exec(*command) | ||
system(*command).tap do |return_code| | ||
yield unless return_code | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we would never want to run exec
without calling ensure_command_valid?
can we move the call to that function here? That way down the road if exec
gets reused somewhere, we can trust that it will always have its safeguards for checking the command bundled into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed it to permit all command strings. This should be the most secure option despite the cost of iteration and matching. I can instead hardcode these strings into the methods and forgo allow-listing, but I don't know how you feel about that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hardcode these strings into the methods and forgo allow-listing
Can you expand on what you mean by this? What you've done here is fantastic with the allow list, if we can maintain a similar strict list of commands another way that you feel is more efficient i'm ok with that as well 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I meant multiple allow-lists. I was originally permitting specific subcommands to yarn, and then adding another list for exec
, but permitting commands + options once in exec
simplified things and sidestepped allowing invalid things like yarn init speedscope --dev
.
ae4efd6
to
fd5cd2e
Compare
Co-authored-by: Pragya Daga <pragya.daga@shopify.com>
fd5cd2e
to
8e7a93d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a comment, but i'm happy with the allow list that was added! Re-request a review if theres any changes to that you need me to look over
Adds remote speedscope viewer for profiling on remote hosts. It uses rack middleware to hijack
/app_profiler
requests to list and view profiles of a booted rack / rails app. Very similar to what rack mini profiler does.The routes are:
/app_profiler
to list profiles/app_profiler/{id}
to show a specific profile in speedscope/app_profiler/viewer/index.html
to mount speedscope viaRack::File