Commit 115b374
0
plików
+0
dodanych
-0
usuniętych
@@ -326,6 +326,23 @@ T = {
326
326
"autoremove_ok": "Usunięto {} osieroconych pakietów.",
327
327
"autoremove_none": "Brak osieroconych pakietów.",
328
328
"downloaded": "Pobrano {} do cache ({:.2f} MB).",
329
+ "sec_downgrade": "Downgrade blocked: {pkg} {new} < {old}",
330
+ "sec_suid": "SUID stripped from {path}",
331
+ "sec_https": "HTTPS required for repos",
332
+ "sec_badname": "Invalid package name: {name}",
333
+ "sec_toobig": "Package too large: {size_mb}MB > {max_mb}MB",
334
+ "sec_conflict": "File conflict: {path} owned by {owner}",
335
+ "sec_audit": "{pkg} installed by {user}",
336
+ "sec_locked": "Another pag process is running",
337
+ "sec_downgrade_pl": "Blokada downgrade: {pkg} {new} < {old}",
338
+ "sec_suid_pl": "SUID usuniety z {path}",
339
+ "sec_https_pl": "Repozytorium wymaga HTTPS",
340
+ "sec_badname_pl": "Nieprawidlowa nazwa pakietu: {name}",
341
+ "sec_toobig_pl": "Paczka za duza: {size_mb}MB > {max_mb}MB",
342
+ "sec_conflict_pl": "Konflikt plikow: {path} nalezy do {owner}",
343
+ "sec_audit_pl": "{pkg} zainstalowany przez {user}",
344
+ "sec_locked_pl": "Inny proces pag juz dziala",
345
+
329
346
"provides_mapped": "{} → {} (provides)",
330
347
"stats_title": "Statystyki PAG",
331
348
"stats_packages": "Zainstalowane pakiety",
@@ -671,6 +688,44 @@ def _check_path_safety(name: str) -> bool:
671
688
return True
672
689
return False
673
690
691
+
692
+def _validate_pkg_name(name):
693
+ return bool(ALLOWED_PKG_RE.match(name))
694
+
695
+def _acquire_lock():
696
+ import fcntl
697
+ os.makedirs(os.path.dirname(LOCK_FILE), exist_ok=True)
698
+ lf = open(LOCK_FILE, "w")
699
+ try:
700
+ fcntl.flock(lf, fcntl.LOCK_EX | fcntl.LOCK_NB)
701
+ return lf
702
+ except (IOError, OSError):
703
+ print(f" {_("sec_locked")}")
704
+ sys.exit(1)
705
+
706
+def _audit(msg):
707
+ from datetime import datetime, timezone
708
+ os.makedirs(os.path.dirname(AUDIT_LOG), exist_ok=True)
709
+ with open(AUDIT_LOG, "a") as f:
710
+ f.write(datetime.now(timezone.utc).isoformat() + " " + msg + "\n")
711
+
712
+def _strip_suid(path):
713
+ try:
714
+ st = os.stat(path)
715
+ if st.st_mode & 0o4000:
716
+ os.chmod(path, st.st_mode & ~0o4000)
717
+ print(f" {_("sec_suid", path=path)}")
718
+ except OSError:
719
+ pass
720
+
721
+def _check_downgrade(pkg_name, new_ver, installed_db):
722
+ if pkg_name in installed_db:
723
+ old = installed_db[pkg_name].get("version", "0")
724
+ if new_ver < old:
725
+ print(f" {_("sec_downgrade", pkg=pkg_name, new=new_ver, old=old)}")
726
+ return False
727
+ return True
728
+
674
729
def _safe_extractall(tar: tarfile.TarFile, dest: str, *, preserve_perms: bool = True):
675
730
"""
676
731
Bezpieczne rozpakowanie archiwum tar z ochroną przed Directory Traversal.
@@ -709,7 +764,9 @@ def _safe_extractall(tar: tarfile.TarFile, dest: str, *, preserve_perms: bool =
709
764
continue
710
765
711
766
# Rozpakuj z zachowaniem metadanych
767
+ target = os.path.join(dest, name)
712
768
tar.extract(member, dest, set_attrs=preserve_perms, numeric_owner=False)
769
+ _strip_suid(target)
713
770
714
771
715
772
def _sha256_file(path: str) -> str:
@@ -1305,6 +1362,7 @@ def cmd_rollback():
1305
1362
# =============================================================================
1306
1363
1307
1364
def cmd_install(package_names, as_dep=False):
1365
+ _lock = _acquire_lock()
1308
1366
ensure_dirs()
1309
1367
installed_db = load_json(INSTALLED_DB)
1310
1368
world = load_world()
@@ -1335,6 +1393,16 @@ def cmd_install(package_names, as_dep=False):
1335
1393
if not to_install:
1336
1394
print(f"✅ {_('all_installed')}"); return 0
1337
1395
1396
+ MAX_MB = MAX_PKG_SIZE // 1048576
1397
+ for n in to_install:
1398
+ if not _validate_pkg_name(n):
1399
+ print(f" {_("sec_badname", name=n)}")
1400
+ return 1
1401
+ sz = repo_pkgs[n].size_bytes if n in repo_pkgs else 0
1402
+ if sz > MAX_PKG_SIZE:
1403
+ mb = sz // 1048576
1404
+ print(f" {_("sec_toobig", size_mb=mb, max_mb=MAX_MB)}")
1405
+ return 1
1338
1406
total_size = sum(repo_pkgs[n].size_bytes for n in to_install if n in repo_pkgs)
1339
1407
print(f"\n📦 {_('to_install', len(to_install), total_size/1048576)}")
1340
1408
for name in to_install:
@@ -2011,6 +2079,8 @@ def cmd_stats():
2011
2079
2012
2080
2013
2081
def cmd_repo_add(url):
2082
+ if not url.startswith("https://") and not os.environ.get("PAG_INSECURE"):
2083
+ print(f" {_("sec_https")}"); return 1
2014
2084
repos = get_repos()
2015
2085
url = url.rstrip("/")
2016
2086
if url in repos: print(f"⚠ {_('repo_exists', url)}"); return