Zasady bezpieczeństwa wykonywania kodu
Ograniczenia uprawnień, izolacja sieci, limity systemu plików i limity czasu.
Zasady bezpieczeństwa wykonywania kodu to bezpłatna lekcja AI Agents na CoddyKit. To lekcja 4 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej AI Agents, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs AI Agents zawiera 4 lekcji w sumie.
Dlaczego same zasady nie wystarczą
Powiedzenie LLM „nie uzyskuj dostępu do sieci” nie jest mechanizmem bezpieczeństwa — to tylko wskazówka. LLM może ją zignorować, ulec jailbreakowi, a wygenerowany kod może pośrednio naruszyć tę zasadę.
Rzeczywiste zasady bezpieczeństwa muszą być wymuszane na warstwie wykonawczej, a nie wyłącznie w promptcie.
Przegląd ograniczeń uprawnień
Ograniczenia uprawnień określają na poziomie systemu operacyjnego, jakie działania może wykonywać proces uruchomiony w piaskownicy. Mechanizm capabilities systemu Linux dzieli uprawnienia roota na precyzyjnie określone możliwości, które można niezależnie odbierać.
Odebranie wszystkich uprawnień z wyjątkiem minimalnego niezbędnego zestawu nosi nazwę zasady najmniejszych uprawnień.
import docker
client = docker.from_env()
# Drop all Linux capabilities
output = client.containers.run(
'python:3.12-slim',
'python -c "print(\"safe run\")"',
cap_drop=['ALL'],
network_disabled=True,
mem_limit='256m',
auto_remove=True
)
print(output.decode())Blokowanie zapisów na dysku poza /tmp
Należy użyć głównego systemu plików tylko do odczytu i zamontować jako zapisywalny wyłącznie katalog /tmp. Zapobiega to modyfikowaniu przez kod plików systemowych lub utrwalaniu danych poza wyznaczonym obszarem roboczym.
import tempfile, docker
client = docker.from_env()
with tempfile.TemporaryDirectory() as tmpdir:
output = client.containers.run(
'python:3.12-slim',
'python -c "open(\"/tmp/ok.txt\",\"w\").write(\"ok\"); print(open(\"/tmp/ok.txt\").read())"',
read_only=True,
tmpfs={'/tmp': 'size=64m,mode=1777'},
network_disabled=True,
mem_limit='128m',
auto_remove=True
)
print(output.decode()) # 'ok' — /tmp write allowed
# Writing anywhere else raises ReadOnlyFileSystemProfile seccomp: filtrowanie wywołań systemowych
seccomp (tryb bezpiecznego przetwarzania) pozwala określić listę dozwolonych wywołań systemowych. Każde wywołanie systemowe, którego nie ma na liście, powoduje zakończenie procesu z sygnałem SIGSYS.
Docker dostarcza domyślny profil seccomp. Można podać niestandardowy profil JSON, aby wprowadzić bardziej restrykcyjne ograniczenia.
import json, docker
client = docker.from_env()
# Minimal seccomp profile: only allow read, write, open, close, exit
seccomp_profile = json.dumps({
'defaultAction': 'SCMP_ACT_ERRNO',
'architectures': ['SCMP_ARCH_X86_64'],
'syscalls': [
{'names': ['read', 'write', 'open', 'openat', 'close',
'fstat', 'mmap', 'mprotect', 'munmap', 'brk',
'exit', 'exit_group', 'rt_sigaction',
'rt_sigprocmask', 'futex'],
'action': 'SCMP_ACT_ALLOW'}
]
})
output = client.containers.run(
'python:3.12-slim',
'python -c "print(1+1)"',
security_opt=[f'seccomp={seccomp_profile}'],
auto_remove=True
)
print(output.decode())Limity zasobów: CPU, pamięć i czas
Trzy limity zasobów, które powinna wymuszać każda piaskownica:
- Pamięć:
mem_limit— zapobieganie atakom OOM - CPU:
nano_cpuslubcpu_quota— zapobieganie wyczerpaniu zasobów CPU - Czas: limit czasu wykonania — zapobieganie nieskończonym pętlom
output = client.containers.run(
'python:3.12-slim',
'python -c "import time; time.sleep(100)"',
mem_limit='128m',
nano_cpus=500_000_000, # 0.5 CPU
network_disabled=True,
auto_remove=True,
timeout=10 # kill after 10 seconds
)
# Raises ReadTimeout after 10 secondsTworzenie listy dozwolonych importów Pythona
Przed wykonaniem kodu należy przeanalizować AST pod kątem instrukcji importu. Należy odrzucić każdy import, którego nie ma na liście dozwolonych. Jest to warstwa obrony wielowarstwowej — nie zastępuje kontroli na poziomie systemu operacyjnego, ale pomaga wcześnie wykrywać oczywiste naruszenia zasad.
import ast
ALLOWED_IMPORTS = {'math', 'statistics', 'json', 'csv', 'datetime', 'collections', 're'}
def check_imports(code: str) -> list[str]:
tree = ast.parse(code)
blocked = []
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
if alias.name.split('.')[0] not in ALLOWED_IMPORTS:
blocked.append(alias.name)
elif isinstance(node, ast.ImportFrom):
if node.module and node.module.split('.')[0] not in ALLOWED_IMPORTS:
blocked.append(node.module)
return blocked
print(check_imports('import socket; print(1)')) # ['socket']
print(check_imports('import math; print(math.pi)')) # []Wykrywanie niebezpiecznych wzorców kodu
Oprócz importów należy sprawdzać wzorce omijające ograniczenia importowania: __import__, exec(), eval(), compile() oraz open() ze ścieżkami znajdującymi się poza /tmp.
import ast
DANGEROUS_CALLS = {'exec', 'eval', 'compile', '__import__', 'breakpoint'}
def check_dangerous_calls(code: str) -> list[str]:
tree = ast.parse(code)
found = []
for node in ast.walk(tree):
if isinstance(node, ast.Call):
if isinstance(node.func, ast.Name):
if node.func.id in DANGEROUS_CALLS:
found.append(node.func.id)
elif isinstance(node.func, ast.Attribute):
if node.func.attr in DANGEROUS_CALLS:
found.append(node.func.attr)
return found
print(check_dangerous_calls('exec("import os")')) # ['exec']Limity rozmiaru danych wyjściowych
Niekończąca się pętla print może wygenerować gigabajty danych wyjściowych. Należy ograniczyć stdout/stderr do rozsądnego rozmiaru (np. 1 MB), a po przekroczeniu limitu skrócić dane lub zakończyć działanie kontenera.
MAX_OUTPUT_BYTES = 1 * 1024 * 1024 # 1 MB
def run_with_output_limit(client, code: str) -> dict:
container = client.containers.run(
'python:3.12-slim',
f'python -c "{code}"',
mem_limit='256m',
network_disabled=True,
detach=True
)
try:
container.wait(timeout=30)
logs = container.logs(stdout=True, stderr=True)
if len(logs) > MAX_OUTPUT_BYTES:
return {'success': False, 'error': 'Output too large'}
return {'success': True, 'output': logs.decode(errors='replace')}
finally:
container.remove(force=True)
if __name__ == '__main__':
class FakeContainer:
def wait(self, timeout=30): pass
def logs(self, stdout=True, stderr=True): return b'Analysis complete: 42 rows processed.'
def remove(self, force=True): pass
class FakeContainers:
def run(self, *args, **kwargs): return FakeContainer()
class FakeClient:
containers = FakeContainers()
result = run_with_output_limit(FakeClient(), 'print(42)')
print('Success:', result['success'])
print('Output :', result['output'])
Flaga no-new-privileges
Opcja bezpieczeństwa no-new-privileges uniemożliwia procesowi uzyskanie dodatkowych uprawnień za pośrednictwem plików binarnych setuid lub sudo. Nawet jeśli kod otworzy powłokę, nie może eskalować uprawnień.
output = client.containers.run(
'python:3.12-slim',
'python -c "import os; print(os.getuid())"',
user='nobody', # run as non-root
security_opt=['no-new-privileges:true'], # no privilege escalation
cap_drop=['ALL'], # no capabilities
network_disabled=True,
mem_limit='128m',
auto_remove=True
)
print(output.decode()) # numeric UID of 'nobody'Łączenie wszystkich zasad
Sandbox klasy produkcyjnej łączy wszystkie warstwy: usunięte uprawnienia systemu operacyjnego, profil seccomp, system plików tylko do odczytu, no-new-privileges, wyłączoną sieć, limity zasobów oraz limit czasu. Strategia obrony wielowarstwowej sprawia, że obejście jednej warstwy zostaje zatrzymane przez inną.
import tempfile, json, docker
client = docker.from_env()
def maximum_security_run(code: str) -> dict:
with tempfile.TemporaryDirectory() as tmpdir:
try:
out = client.containers.run(
'python:3.12-slim',
f'python -c "{code}"',
user='nobody',
security_opt=['no-new-privileges:true'],
cap_drop=['ALL'],
network_disabled=True,
read_only=True,
tmpfs={'/tmp': 'size=32m,mode=1777'},
mem_limit='128m',
nano_cpus=500_000_000,
auto_remove=True,
timeout=15
)
return {'success': True, 'output': out.decode()}
except Exception as e:
return {'success': False, 'error': str(e)}Rejestrowanie naruszeń zasad
Po wykryciu naruszenia zasad (zablokowanego importu, niebezpiecznego wywołania lub przekroczenia limitu czasu) należy zarejestrować zdarzenie wraz z identyfikatorem sesji agenta, skrótem fragmentu kodu oraz typem naruszenia. Dane te zasilają pulpit bezpieczeństwa i pomagają z czasem dostrajać zasady.
import hashlib, logging, time
logging.basicConfig(level=logging.INFO)
security_logger = logging.getLogger('sandbox.security')
def policy_check_and_run(code: str, session_id: str) -> dict:
violations = check_imports(code) + check_dangerous_calls(code)
code_hash = hashlib.sha256(code.encode()).hexdigest()[:16]
if violations:
security_logger.warning(
'POLICY_VIOLATION session=%s hash=%s violations=%s',
session_id, code_hash, violations
)
return {'success': False, 'error': f'Blocked: {violations}'}
result = maximum_security_run(code)
security_logger.info(
'EXEC session=%s hash=%s success=%s',
session_id, code_hash, result['success']
)
return resultCo robi ustawienie „defaultAction: SCMP_ACT_ERRNO” w profilu seccomp?
Profile seccomp określają, które wywołania systemowe proces może wykonywać. Pole defaultAction określa, co dzieje się z wywołaniami systemowymi, których nie wymieniono jawnie w profilu.
Podsumowanie zasad bezpieczeństwa
Skuteczne zabezpieczenie wykonywania kodu wymaga wielu warstw: usunięcia uprawnień systemu Linux, filtrowania wywołań systemowych za pomocą seccomp, systemu plików tylko do odczytu z przestrzenią roboczą tmpfs, no-new-privileges, wyłączonej sieci, limitów pamięci/CPU/czasu oraz sprawdzania importów na poziomie AST.
Pojedyncza warstwa nie wystarcza — obrona wielowarstwowa oznacza, że każda warstwa kompensuje obejścia pozostałych.
Często zadawane pytania
Czy lekcja „Zasady bezpieczeństwa wykonywania kodu” jest bezpłatna?
Tak — pełny tekst „Zasady bezpieczeństwa wykonywania kodu” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu AI Agents, przejdź na CoddyKit PRO. Kurs AI Agents zawiera 4 lekcji w sumie.
Co nauczysz się w „Zasady bezpieczeństwa wykonywania kodu”?
Ograniczenia uprawnień, izolacja sieci, limity systemu plików i limity czasu. Ćwiczysz AI Agents z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.
Czy potrzebuję doświadczenia, aby zacząć AI Agents?
Nie wymagamy żadnego doświadczenia. AI Agents w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 4 z 4.
Ile czasu zajmuje lekcja „Zasady bezpieczeństwa wykonywania kodu”?
Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.
Czy mogę pisać i uruchamiać kod w tej lekcji AI Agents?
Tak. Każda lekcja AI Agents zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.
Wszystkie lekcje w tym kursie
- Sandboxy agentów oparte na Dockerze
- Izolacja maszyn wirtualnych dla agentów wykonujących kod o wysokim poziomie bezpieczeństwa
- E2B i chmurowe usługi sandboxów
- Zasady bezpieczeństwa wykonywania kodu