v1.2.19: fixes from Codex audit — XSS in params, JS injection in onclick, scenario polling timeout, has_target validation, tracker atomic update
This commit is contained in:
+55
-23
@@ -113,36 +113,68 @@ def _locked_write(path, data):
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def add(client_id, stand, instance_uid, svc_id, display_name):
|
||||
"""Добавить инстанс в трекер (вызывается из executor сразу после create).
|
||||
def _atomic_update(path, mutator):
|
||||
"""Атомарно: открыть файл → заблокировать → прочитать → мутировать → записать.
|
||||
|
||||
Читает текущий файл → добавляет/обновляет запись → пишет обратно.
|
||||
Если инстанс уже есть — перезаписывает (идемпотентность).
|
||||
Вся операция под ОДНИМ lock, в отличие от старого подхода
|
||||
_locked_read() + _locked_write() где между ними lock снимался.
|
||||
Это предотвращает lost-update при конкурентных add/remove.
|
||||
|
||||
Args:
|
||||
client_id: str — ClientID из JWT
|
||||
stand: str — "dev"/"test"
|
||||
instance_uid: str — UUID созданного инстанса
|
||||
svc_id: int — ID сервиса
|
||||
display_name: str — displayName инстанса"""
|
||||
p = _path(client_id, stand)
|
||||
data = _locked_read(p)
|
||||
data[instance_uid] = {
|
||||
"svcId": svc_id,
|
||||
"displayName": display_name,
|
||||
"instanceUid": instance_uid,
|
||||
}
|
||||
_locked_write(p, data)
|
||||
path: str — путь к файлу
|
||||
mutator: callable(data) — функция, мутирующая dict (возвращать не нужно)
|
||||
|
||||
Returns:
|
||||
dict — результат после мутации (или {} если не смогли)"""
|
||||
try:
|
||||
fd = os.open(path, os.O_RDWR | os.O_CREAT, 0o644)
|
||||
except OSError:
|
||||
return {}
|
||||
try:
|
||||
if not _acquire_lock(fd):
|
||||
return {}
|
||||
# Читаем
|
||||
data = {}
|
||||
try:
|
||||
raw = os.read(fd, 65536)
|
||||
if raw:
|
||||
data = json.loads(raw.decode("utf-8"))
|
||||
except (json.JSONDecodeError, UnicodeDecodeError):
|
||||
pass
|
||||
# Мутируем
|
||||
mutator(data)
|
||||
# Пишем
|
||||
os.lseek(fd, 0, 0)
|
||||
os.ftruncate(fd, 0)
|
||||
os.write(fd, json.dumps(data, indent=2).encode("utf-8"))
|
||||
return data
|
||||
finally:
|
||||
fcntl.flock(fd, fcntl.LOCK_UN)
|
||||
os.close(fd)
|
||||
|
||||
|
||||
def add(client_id, stand, instance_uid, svc_id, display_name):
|
||||
"""Добавить инстанс в трекер (атомарно, под одним lock).
|
||||
|
||||
Читает → добавляет/обновляет запись → пишет — всё под эксклюзивной блокировкой.
|
||||
Если инстанс уже есть — перезаписывает (идемпотентность)."""
|
||||
def _add(data):
|
||||
data[instance_uid] = {
|
||||
"svcId": svc_id,
|
||||
"displayName": display_name,
|
||||
"instanceUid": instance_uid,
|
||||
}
|
||||
_atomic_update(_path(client_id, stand), _add)
|
||||
|
||||
|
||||
def remove(client_id, stand, instance_uid):
|
||||
"""Удалить инстанс из трекера (после успешного delete).
|
||||
"""Удалить инстанс из трекера (атомарно, под одним lock).
|
||||
|
||||
pop с default=None — не падает если инстанса уже нет."""
|
||||
def _rem(data):
|
||||
data.pop(instance_uid, None)
|
||||
_atomic_update(_path(client_id, stand), _rem)
|
||||
|
||||
pop с default=None — не падает если инстанса уже нет (уже удалён ранее)."""
|
||||
p = _path(client_id, stand)
|
||||
data = _locked_read(p)
|
||||
data.pop(instance_uid, None)
|
||||
_locked_write(p, data)
|
||||
|
||||
|
||||
def list_all(client_id, stand):
|
||||
|
||||
Reference in New Issue
Block a user