deno_std icon indicating copy to clipboard operation
deno_std copied to clipboard

std/path: utility to check/prevent exploit in paths by setting boundaries

Open lowlighter opened this issue 1 year ago • 0 comments

Is your feature request related to a problem? Please describe.

A simple function that asserts a path is descending (i.e. not trying to go up ../), or that "virtually" change the root directory (similar to fsRoot from @std/http).

This is useful when dev are importing or resolving path dynamically from user-provided input, to ensure that they're not trying to leave unexpected boundaries.

Examples:

const { default: plugin } = await import(import.meta.resolve(`./plugins/${user_provided_plugin_name}.ts`))
const file = await Deno.open(`/data/images/${user_provided_input}`)

Side notes:

  • as shown above, it isn't necessarly for http serving (probably dev would use serveDir from @std/http anyways)
  • having a standard and tested solution would increase the overall security of apps relying on @std as devs wouldn't have to manually implement it
  • it should be both posix/windows compatible, and cover any edge-case
    • maybe it could be url compatible
  • several implementations are offered below, I think it'd make sense to make it configurable
    • sometimes you'd just want to throw to reject the user that tried to exploit a path provided input
    • or maybe you'd like to "sandbox" the user inside a specific subtree, where it's not possible to go further up

Describe the solution you'd like

NB: examples below are just snippets, they don't cover any edge cases

1. Throws if using ascending fragments

function assertDescending(path: string) {
  if (path.includes("../")
    throw new RangeError("Not allowed to go above in tree directory")
  return path
}

2. "Virtually" change the root directory

function chroot(root:string, path:string) {
  const resolved = resolve(root, path)
  return resolved.includes("..") ? root : path
}

3. A combination of both with configurable behavior

function assertsOrChroot(path:string, {root, throws}:{root:string, throws?:boolean}) {
  if (throws)
    return assertDescending(path)
  return chroot(root, path)
}

Describe alternatives you've considered

relative, resolve, normalize helps, but you still need to implement manually the logic anyway

Could also copy the logic from @std/http but it's limited to posix and there is special handling for routing in the batch.

I don't think it's hard to implement, or to use combined use of functions to achieve it, but a single function would be way more convenient and less prone to errors leading to exploit.

lowlighter avatar Sep 29 '24 18:09 lowlighter