Spaces:
Sleeping
Sleeping
Security: per-fold temp file, input length cap, pinned 3Dmol + SRI
Browse files
app.py
CHANGED
|
@@ -20,6 +20,7 @@ import io
|
|
| 20 |
import itertools
|
| 21 |
import json
|
| 22 |
import os
|
|
|
|
| 23 |
import time
|
| 24 |
import gradio as gr
|
| 25 |
from ImmuneBuilder import ABodyBuilder2
|
|
@@ -490,7 +491,11 @@ CONTROLLER_JS = """
|
|
| 490 |
|
| 491 |
if (!window.$3Dmol) {
|
| 492 |
const s = document.createElement("script");
|
| 493 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 494 |
s.onload = () => window.myabsInit();
|
| 495 |
document.head.appendChild(s);
|
| 496 |
} else { window.myabsInit(); }
|
|
@@ -499,23 +504,33 @@ CONTROLLER_JS = """
|
|
| 499 |
|
| 500 |
|
| 501 |
# ---------------------------------------------------------------------- fold
|
|
|
|
|
|
|
|
|
|
| 502 |
def fold(heavy: str, light: str):
|
| 503 |
"""Fold VH+VL, scan liabilities, and hand the structure to the client viewer."""
|
| 504 |
heavy, light = clean(heavy), clean(light)
|
| 505 |
reset_btn = gr.update(value="▶ Spin")
|
|
|
|
|
|
|
| 506 |
if not heavy or not light:
|
| 507 |
-
return ("Enter both a heavy and a light chain.",
|
| 508 |
-
|
|
|
|
|
|
|
| 509 |
try:
|
| 510 |
t0 = time.time()
|
| 511 |
antibody = PREDICTOR.predict({"H": heavy, "L": light})
|
| 512 |
dt = time.time() - t0
|
| 513 |
-
|
| 514 |
-
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
|
|
|
|
|
|
|
|
|
| 519 |
flags, highlights, h3_len, ok = developability(heavy, light, pdb)
|
| 520 |
status = (f"Folded in {dt:.1f} s · VH {len(heavy)} aa / VL {len(light)} aa · "
|
| 521 |
f"CDRs highlighted (H: yellow/orange/red, L: cyan/blue).")
|
|
@@ -525,7 +540,7 @@ def fold(heavy: str, light: str):
|
|
| 525 |
l_tokens, l_idx = build_track("L", number_chain(light) or [], highlights.get("L", []))
|
| 526 |
idxmap = {"H": h_idx, "L": l_idx}
|
| 527 |
payload = build_payload(pdb, highlights) # -> client-side viewer via .change bridge
|
| 528 |
-
return (status, flags_md,
|
| 529 |
h_tokens, l_tokens, idxmap, reset_btn)
|
| 530 |
|
| 531 |
|
|
|
|
| 20 |
import itertools
|
| 21 |
import json
|
| 22 |
import os
|
| 23 |
+
import tempfile
|
| 24 |
import time
|
| 25 |
import gradio as gr
|
| 26 |
from ImmuneBuilder import ABodyBuilder2
|
|
|
|
| 491 |
|
| 492 |
if (!window.$3Dmol) {
|
| 493 |
const s = document.createElement("script");
|
| 494 |
+
// Pinned, immutable version + Subresource Integrity so a compromised CDN
|
| 495 |
+
// cannot inject arbitrary JS into users' browsers.
|
| 496 |
+
s.src = "https://cdn.jsdelivr.net/npm/3dmol@2.5.5/build/3Dmol-min.js";
|
| 497 |
+
s.integrity = "sha384-OsczYbldvrHgslr9fFp/i4GiLSeuw9l+QIlv99ITw8soOwXcoGeflFMLg+CU/X1d";
|
| 498 |
+
s.crossOrigin = "anonymous";
|
| 499 |
s.onload = () => window.myabsInit();
|
| 500 |
document.head.appendChild(s);
|
| 501 |
} else { window.myabsInit(); }
|
|
|
|
| 504 |
|
| 505 |
|
| 506 |
# ---------------------------------------------------------------------- fold
|
| 507 |
+
MAX_CHAIN_LEN = 250 # antibody variable domains are ~110-130 aa; a generous DoS cap
|
| 508 |
+
|
| 509 |
+
|
| 510 |
def fold(heavy: str, light: str):
|
| 511 |
"""Fold VH+VL, scan liabilities, and hand the structure to the client viewer."""
|
| 512 |
heavy, light = clean(heavy), clean(light)
|
| 513 |
reset_btn = gr.update(value="▶ Spin")
|
| 514 |
+
# trailing 7 outputs (everything after `status`) for the early-return paths
|
| 515 |
+
tail = ("", None, "", [], [], {"H": [], "L": []}, reset_btn)
|
| 516 |
if not heavy or not light:
|
| 517 |
+
return ("Enter both a heavy and a light chain.",) + tail
|
| 518 |
+
if len(heavy) > MAX_CHAIN_LEN or len(light) > MAX_CHAIN_LEN:
|
| 519 |
+
return (f"Sequence too long (VH {len(heavy)}, VL {len(light)} aa; max "
|
| 520 |
+
f"{MAX_CHAIN_LEN} per chain). Paste one antibody variable domain per box.",) + tail
|
| 521 |
try:
|
| 522 |
t0 = time.time()
|
| 523 |
antibody = PREDICTOR.predict({"H": heavy, "L": light})
|
| 524 |
dt = time.time() - t0
|
| 525 |
+
# Per-fold unique dir so concurrent public users never share the output
|
| 526 |
+
# file (the download basename stays a clean "myabs_fold.pdb").
|
| 527 |
+
out_path = os.path.join(tempfile.mkdtemp(prefix="myabs_"), "myabs_fold.pdb")
|
| 528 |
+
antibody.save(out_path)
|
| 529 |
+
except Exception: # OpenMM refinement / numbering can occasionally fail
|
| 530 |
+
return ("Fold failed. Check that both inputs are valid antibody "
|
| 531 |
+
"variable-domain sequences.",) + tail
|
| 532 |
+
|
| 533 |
+
pdb = open(out_path).read()
|
| 534 |
flags, highlights, h3_len, ok = developability(heavy, light, pdb)
|
| 535 |
status = (f"Folded in {dt:.1f} s · VH {len(heavy)} aa / VL {len(light)} aa · "
|
| 536 |
f"CDRs highlighted (H: yellow/orange/red, L: cyan/blue).")
|
|
|
|
| 540 |
l_tokens, l_idx = build_track("L", number_chain(light) or [], highlights.get("L", []))
|
| 541 |
idxmap = {"H": h_idx, "L": l_idx}
|
| 542 |
payload = build_payload(pdb, highlights) # -> client-side viewer via .change bridge
|
| 543 |
+
return (status, flags_md, out_path, payload,
|
| 544 |
h_tokens, l_tokens, idxmap, reset_btn)
|
| 545 |
|
| 546 |
|