Module: Abqari::PathSafe
- Defined in:
- lib/abqari/path_safe.rb
Overview
Path-safety helpers for build-time file I/O.
The engine joins user-controlled input (frontmatter permalink:,
ABQARI_OUTPUT_DIR, slugs, bundle filenames) into filesystem paths.
Plain File.join will happily build _site/../../etc/passwd from
an attacker-controlled segment — a hostile or careless content
contributor can use that to write outside the build's output dir.
join_under expands the candidate, then re-checks that it sits
under the base. Anything that escapes (via .., an absolute
segment, or a symlink) raises rather than silently writing the
wrong file.
assert_under is the assertion-only form for paths that were
already computed elsewhere (e.g. an output_dir that came in from
an env var, where we want to guard the deletion site, not rebuild
the path).
Defined Under Namespace
Classes: Error
Class Method Summary collapse
-
.assert_under(base, path) ⇒ Object
Raise unless
pathisbaseor a descendant of it. -
.join_under(base, *segments) ⇒ Object
Join
segmentsunderbaseand refuse anything that escapes the base directory.
Class Method Details
.assert_under(base, path) ⇒ Object
Raise unless path is base or a descendant of it. Use at sites
that already have a fully-computed path and need a containment
check before a destructive operation (e.g. rm_rf).
53 54 55 56 57 58 59 60 |
# File 'lib/abqari/path_safe.rb', line 53 def assert_under(base, path) base_real = File.(base.to_s) path_real = File.(path.to_s) return if path_real == base_real return if path_real.start_with?(base_real + File::SEPARATOR) raise Error, "containment check failed: #{path_real} is not under #{base_real}" end |
.join_under(base, *segments) ⇒ Object
Join segments under base and refuse anything that escapes the
base directory. Returns the expanded absolute path.
PathSafe.join_under('/site/_site', 'posts', 'hello', 'index.html')
# => "/site/_site/posts/hello/index.html"
PathSafe.join_under('/site/_site', '../etc/passwd')
# => raises Abqari::PathSafe::Error
Empty / nil segments are tolerated (treated as no-op), so callers can pass through optional path parts without pre-checking.
37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/abqari/path_safe.rb', line 37 def join_under(base, *segments) base_real = File.(base.to_s) clean = segments.flatten.compact.map(&:to_s).reject(&:empty?) candidate = File.(File.join(base_real, *clean)) return candidate if candidate == base_real return candidate if candidate.start_with?(base_real + File::SEPARATOR) raise Error, "path traversal blocked: #{clean.inspect} resolves to #{candidate}, " \ "which is outside #{base_real}" end |