More hardening
All checks were successful
All checks were successful
This commit is contained in:
parent
d2a46394fe
commit
1148b34bce
4 changed files with 251 additions and 2 deletions
|
|
@ -193,10 +193,52 @@ HIGH_CONFIDENCE_SECRET_PATTERNS = [
|
|||
# start-of-input or any non-identifier character, which is comment-marker
|
||||
# agnostic and still avoids matching an identifier such as
|
||||
# ``my_authorization:``.
|
||||
#
|
||||
# Both the header form (``Authorization: Bearer ...``) and the config
|
||||
# assignment form (``authorization = Bearer ...`` / ``http_authorization=...``)
|
||||
# are matched: a populated Authorization header is an obvious credential
|
||||
# whether it is written HTTP-style with ``:`` or config-style with ``=``. The
|
||||
# ``(?:proxy|http)`` prefix accepts ``proxy-``/``proxy_``/``http_`` spellings
|
||||
# (e.g. ``proxy_authorization``) that appear in real client/daemon configs.
|
||||
# A scheme keyword (bearer/basic/token/digest) plus a value is still required,
|
||||
# so ordinary keys such as ``AuthorizationEnabled = true`` do not match.
|
||||
re.compile(
|
||||
rb"(?im)(?:^|[^A-Za-z0-9_.-])(?:proxy-)?authorization\s*:\s*"
|
||||
rb"(?im)(?:^|[^A-Za-z0-9_.-])(?:(?:proxy|http)[-_])?authorization\s*[:=]\s*"
|
||||
rb"(?:bearer|basic|token|digest)\s+\S"
|
||||
),
|
||||
# Token-key credential assignments that the general assignment pattern above
|
||||
# misses because of a leading underscore or a camelCase/prefix spelling.
|
||||
#
|
||||
# _authToken=npm_xxxxxxxx
|
||||
# //registry.npmjs.org/:_authToken=npm_xxxxxxxx (npm .npmrc form)
|
||||
# authToken=xxxxxxxx
|
||||
# bearerToken=xxxxxxxx / bearer_token=xxxxxxxx
|
||||
#
|
||||
# The general assignment pattern requires a non-key boundary
|
||||
# (``[^A-Za-z0-9_.-]``) immediately before the credential keyword, which a
|
||||
# leading ``_`` (a word char) defeats -- so ``_authToken`` slips past it --
|
||||
# and its keyword list does not include a ``bearer[_-]?token`` spelling. This
|
||||
# dedicated pattern covers exactly those ``auth``/``bearer`` token keys and,
|
||||
# like the general one, requires a *populated, value-like* right-hand side
|
||||
# (>= 8 non-space chars, or a value containing a digit / token-ish
|
||||
# character), so a bare mention or a short boolean/number such as
|
||||
# ``authTokenEnabled = true`` does not trip it. It is intentionally scoped to
|
||||
# ``auth``/``bearer`` token keys only; broadening the general boundary to
|
||||
# accept a leading underscore everywhere would risk false positives on
|
||||
# value-less identifiers in ordinary config.
|
||||
re.compile(
|
||||
rb"""(?ix)
|
||||
(?:^|[^A-Za-z0-9.-])
|
||||
_?
|
||||
(?:auth|bearer)[_-]?token
|
||||
(?:[_.-][A-Za-z0-9]+)*
|
||||
[\"']?
|
||||
\s*[:=]\s*
|
||||
[\"']?
|
||||
(?=\S)
|
||||
(?:\S{8,}|\S*[0-9/+=._-]\S*)
|
||||
"""
|
||||
),
|
||||
]
|
||||
|
||||
SENSITIVE_CONTENT_PATTERNS = [
|
||||
|
|
|
|||
|
|
@ -189,14 +189,35 @@ def remote_harvest(
|
|||
)
|
||||
|
||||
|
||||
# Resource caps for untrusted tar extraction. These mirror the directory-bundle
|
||||
# freeze limits (see manifest_safety._FREEZE_MAX_FILES / _FREEZE_MAX_FILE_BYTES)
|
||||
# so a harvest delivered as a tarball is bounded the same way as one delivered as
|
||||
# a directory. The total-size cap additionally guards against a decompression
|
||||
# bomb whose members are each individually under the per-file cap.
|
||||
_TAR_MAX_MEMBERS = 200_000
|
||||
_TAR_MAX_FILE_BYTES = 64 * 1024 * 1024
|
||||
_TAR_MAX_TOTAL_BYTES = 10 * 1024 * 1024 * 1024
|
||||
_TAR_MAX_PATH_DEPTH = 64
|
||||
|
||||
|
||||
def _safe_extract_tar(tar: tarfile.TarFile, dest: Path) -> None:
|
||||
"""Safely extract a tar archive into dest.
|
||||
|
||||
Protects against path traversal (e.g. entries containing ../).
|
||||
Protects against path traversal (e.g. entries containing ../) and, as
|
||||
availability defence-in-depth, against resource-exhaustion by a structurally
|
||||
valid but abusive archive (a decompression bomb, a huge member, or millions
|
||||
of tiny members). The caps mirror the directory-bundle freeze limits so a
|
||||
tar bundle and a directory bundle are bounded the same way. A remote or
|
||||
user-supplied harvest tarball is untrusted input, so these limits keep a
|
||||
malicious archive from exhausting disk, inodes, memory, or time during local
|
||||
extraction/validation.
|
||||
"""
|
||||
# Note: tar member names use POSIX separators regardless of platform.
|
||||
dest = dest.resolve()
|
||||
|
||||
member_count = 0
|
||||
total_size = 0
|
||||
|
||||
for m in tar.getmembers():
|
||||
name = m.name
|
||||
|
||||
|
|
@ -205,16 +226,35 @@ def _safe_extract_tar(tar: tarfile.TarFile, dest: Path) -> None:
|
|||
if name in {".", "./"}:
|
||||
continue
|
||||
|
||||
member_count += 1
|
||||
if member_count > _TAR_MAX_MEMBERS:
|
||||
raise RuntimeError(
|
||||
f"tar archive has too many members (> {_TAR_MAX_MEMBERS})"
|
||||
)
|
||||
|
||||
# Reject absolute paths and any '..' components up front.
|
||||
p = PurePosixPath(name)
|
||||
if p.is_absolute() or ".." in p.parts:
|
||||
raise RuntimeError(f"Unsafe tar member path: {name}")
|
||||
|
||||
if len(p.parts) > _TAR_MAX_PATH_DEPTH:
|
||||
raise RuntimeError(f"tar member path is too deeply nested: {name}")
|
||||
|
||||
# Refuse to extract links or device nodes from an untrusted archive.
|
||||
# (A symlink can be used to redirect subsequent writes outside dest.)
|
||||
if m.issym() or m.islnk() or m.isdev():
|
||||
raise RuntimeError(f"Refusing to extract special tar member: {name}")
|
||||
|
||||
if m.isfile():
|
||||
if m.size > _TAR_MAX_FILE_BYTES:
|
||||
raise RuntimeError(f"tar member is too large: {name}")
|
||||
total_size += int(m.size)
|
||||
if total_size > _TAR_MAX_TOTAL_BYTES:
|
||||
raise RuntimeError(
|
||||
"tar archive uncompressed size exceeds limit "
|
||||
f"(> {_TAR_MAX_TOTAL_BYTES} bytes)"
|
||||
)
|
||||
|
||||
member_path = (dest / Path(*p.parts)).resolve()
|
||||
if member_path != dest and not str(member_path).startswith(str(dest) + os.sep):
|
||||
raise RuntimeError(f"Unsafe tar member path: {name}")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue