Files
Dwi Siswanto 6f2ade6a9b fix(js): respect allow-local-file-access in require (#7332)
* fix(js): respect `allow-local-file-access` in `require`

The goja `require() `function used the default
host filesystem loader which let JavaScript
templates import any local files even when
`allow-local-file-access` was disabled.

Pooled runtimes kept `require()` state around so
a module loaded during a privileged execution
could remain cached for a later restricted one.

Rebuild the require registry per execution after
setting the execution context, and route file-
backed module loads to preserve native modules
while enforcing the same sandbox rules (as
`nuclei/fs`).

Signed-off-by: Dwi Siswanto <git@dw1.io>

* fix: cross-platform sandbox path checks

Replace lexical prefix checks in the template file
sandbox with a shared path containment helper that
canonicalizes both paths before comparing them to
prevent false rejections when the configured
templates directory and the resolved file path
differ only due to symlink expansion on macOS or
path normalization on Windows.

Apply the helper in `protocolstate.NormalizePath()`
and `Options.GetValidAbsPath()` so JS `require()`-
based module loads and helper file resolution use
the same rules.

Signed-off-by: Dwi Siswanto <git@dw1.io>

---------

Signed-off-by: Dwi Siswanto <git@dw1.io>
2026-04-10 08:06:39 +07:00

78 lines
2.3 KiB
Go

package protocolstate
import (
"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"
"github.com/projectdiscovery/nuclei/v3/pkg/types"
filepathutil "github.com/projectdiscovery/nuclei/v3/pkg/utils/filepath"
"github.com/projectdiscovery/utils/errkit"
fileutil "github.com/projectdiscovery/utils/file"
mapsutil "github.com/projectdiscovery/utils/maps"
)
var (
// LfaAllowed means local file access is allowed
LfaAllowed *mapsutil.SyncLockMap[string, bool]
)
func init() {
LfaAllowed = mapsutil.NewSyncLockMap[string, bool]()
}
// IsLfaAllowed returns whether local file access is allowed
func IsLfaAllowed(options *types.Options) bool {
if GetLfaAllowed(options) {
return true
}
// Otherwise look into dialers
dialers, ok := dialers.Get(options.ExecutionId)
if ok && dialers != nil {
dialers.Lock()
defer dialers.Unlock()
return dialers.LocalFileAccessAllowed
}
// otherwise just return option value
return options.AllowLocalFileAccess
}
func SetLfaAllowed(options *types.Options) {
_ = LfaAllowed.Set(options.ExecutionId, options.AllowLocalFileAccess)
}
func GetLfaAllowed(options *types.Options) bool {
allowed, ok := LfaAllowed.Get(options.ExecutionId)
return ok && allowed
}
func NormalizePathWithExecutionId(executionId string, filePath string) (string, error) {
options := &types.Options{
ExecutionId: executionId,
}
return NormalizePath(options, filePath)
}
// Normalizepath normalizes path and returns absolute path
// it returns error if path is not allowed
// this respects the sandbox rules and only loads files from
// allowed directories
func NormalizePath(options *types.Options, filePath string) (string, error) {
// TODO: this should be tied to executionID using *types.Options
if IsLfaAllowed(options) {
// if local file access is allowed, we can return the absolute path
return filePath, nil
}
cleaned, err := fileutil.ResolveNClean(filePath, config.DefaultConfig.GetTemplateDir())
if err != nil {
return "", errkit.Wrapf(err, "could not resolve and clean path %v", filePath)
}
// only allow files inside nuclei-templates directory
// even current working directory is not allowed
if filepathutil.IsPathWithinDirectory(cleaned, config.DefaultConfig.GetTemplateDir()) {
return cleaned, nil
}
return "", errkit.Newf("path %v is outside nuclei-template directory and -lfa is not enabled", filePath)
}