Skip to content

Preventing file-system access via include/layouts #131

@pmalouin

Description

@pmalouin

Context

We are building a Node.js back-end application that sends emails to end-users and we would like to use liquidjs to render email templates. The application provides default email templates out of the box, but we also allow tenants to customize the templates.

In order to support this use case, we'd like to restrict the use of the {% include ... %} and {% layout ... %} tags: we should not allow reading arbitrary files from the file system.

For example, a tenant would be able to use includes in a custom template like this:

<div>
{% include ../package.json %}
</div>

That would render the package.json file inside the email. Note that the root option is set to a sub-directory of the project like myproject/templates.

Potential solutions

We found various approaches to address this concern but we would like to validate them with you.

Restrict root

One way to address this concern would be to keep allowing file-system access, but set the root option to an empty directory. But at the same time, liquidjs should not allow relative paths OUTSIDE of this root (../ should not be allowed).

This would require a change to this library.

Override the include and layout tags

In the related issue #20, it is suggested to override the include and layout tags by registering alternate implementations. We could provide implementations that return an empty string or throw an exception.

One drawback of this approach is that tags are currently registered statically. See:

static impls: { [key: string]: ITagImplOptions } = {}
)

That means that ALL instances of the liquidjs engine will apply the overridden tags. Unfortunately, this is problematic for us: we want to have instances of the engine that allow fs access and others that don't allow them (basically, default email templates allow them and custom email templates don't allow them).

Patch the Liquid.getTemplate() method

Both the include and layout tags' current implementation are calling the Liquid.getTemplate() method to read from disk. As a matter of fact, all file-system access is confined to this method. It might be more secure to patch this method by a custom implementation (returning an empty string or throwing an exception). This might be more future-proof if this library starts adding more tags that access the file-system. On the other hand, patching this method breaks the Liquid.renderFile() method, but we don't use this method.

If this approach sounds like a good idea, could we consider providing abstractions to this library that allow extending this behavior in a more structured approach? For example, there's the liquid-node library that supports registering custom "File System" objects: https://github.com/liquid-lang/liquid-node/blob/7f5076526adcef13ad03b11dfe69b33e9011731b/lib/liquid/engine.js#L76-L82

Adding a new option to liquidjs

If our use case is shared by other users of this lib, we might want to add a new option in liquidjs to disable file system access (something like disableFs?).


Any guidance with our use case is much appreciated. We'd be glad to contribute PRs if valuable.

Thanks for maintaining this library! ❤️

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions