mirror of
https://github.com/hansjone/netx.git
synced 2026-10-09 09:30:45 +08:00
Tighten WebCRT command audit: reject device errors and stdout replay.
Only audit interactive commands from frontend audit_line with a CLI prompt prefix; skip prompt-sync Enter and device error echoes; join wrapped xterm rows for long commands. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
52424dce37
commit
302ebfac23
5 changed files with 163 additions and 27 deletions
|
|
@ -539,14 +539,49 @@ def _is_prompt_command_line(line: str) -> bool:
|
|||
)
|
||||
|
||||
|
||||
def _has_cli_prompt_prefix(line: str) -> bool:
|
||||
s = normalize_audit_line(line)
|
||||
return bool(
|
||||
re.match(
|
||||
r"^(?:"
|
||||
r"[\w.-]+(?:\([^)]+\))*[#>]"
|
||||
r"|>[\w.-]+"
|
||||
r"|<[^>]+>"
|
||||
r"|\[[^\]]+\]"
|
||||
r")",
|
||||
s,
|
||||
flags=re.I,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _is_device_output_line(line: str) -> bool:
|
||||
"""Device error/warning echo — never an operator-typed command."""
|
||||
s = normalize_audit_line(line).strip()
|
||||
if not s:
|
||||
return False
|
||||
low = s.lower()
|
||||
if low.startswith("%error") or low.startswith("%warning"):
|
||||
return True
|
||||
if "invalid input detected" in low:
|
||||
return True
|
||||
if re.match(r"^\^+\s*$", s):
|
||||
return True
|
||||
if re.match(r"^enter configuration commands", low):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_auditable_command_line(line: str) -> bool:
|
||||
"""False for empty Enter (prompt only, no command text)."""
|
||||
"""False for empty Enter, device output, or lines without a CLI prompt prefix."""
|
||||
s = normalize_audit_line(line)
|
||||
if not s.strip():
|
||||
return False
|
||||
if _is_prompt_only_line(s):
|
||||
return False
|
||||
return True
|
||||
if _is_device_output_line(s):
|
||||
return False
|
||||
return _has_cli_prompt_prefix(s)
|
||||
|
||||
|
||||
def _is_prompt_only_line(line: str) -> bool:
|
||||
|
|
|
|||
|
|
@ -801,7 +801,10 @@ async def websocket_session(websocket: WebSocket, session_id: str) -> None:
|
|||
if sess.needs_live_prompt:
|
||||
sess.needs_live_prompt = False
|
||||
try:
|
||||
await asyncio.get_running_loop().run_in_executor(webcrt_io_executor(), sess.write_stdin, "\r")
|
||||
await asyncio.get_running_loop().run_in_executor(
|
||||
webcrt_io_executor(),
|
||||
lambda: sess.write_stdin("\r", audit_source="prompt_sync"),
|
||||
)
|
||||
except Exception:
|
||||
_log.debug("webcrt live prompt sync failed session=%s", session_id, exc_info=True)
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -385,28 +385,26 @@ class WebcrtSession:
|
|||
if redacted and (buf_lines or audit_line):
|
||||
self._password_mode = False
|
||||
if "\r" in text or "\n" in text:
|
||||
from .webcrt_channel import (
|
||||
extract_last_prompt_command,
|
||||
is_auditable_command_line,
|
||||
normalize_audit_line,
|
||||
)
|
||||
from .webcrt_channel import is_auditable_command_line, normalize_audit_line
|
||||
|
||||
# Ground truth: xterm visible row at Enter (frontend). PTY stdout/stdin
|
||||
# still carry intermediate backspaces / tab redraws — do not prefer those.
|
||||
src = str(source or "stdin")
|
||||
cmd: str | None = None
|
||||
if audit_line and str(audit_line).strip():
|
||||
if redacted and (audit_line or buf_lines):
|
||||
lines = ["***"]
|
||||
elif src == "prompt_sync":
|
||||
lines = []
|
||||
elif audit_line and str(audit_line).strip():
|
||||
candidate = normalize_audit_line(audit_line)
|
||||
if is_auditable_command_line(candidate):
|
||||
cmd = candidate
|
||||
if not cmd:
|
||||
raw = extract_last_prompt_command(self._stdout_tail)
|
||||
if raw and is_auditable_command_line(raw):
|
||||
cmd = raw
|
||||
elif buf_lines and str(buf_lines[-1]).strip():
|
||||
last = str(buf_lines[-1]).strip()
|
||||
if is_auditable_command_line(last):
|
||||
cmd = last
|
||||
lines = [cmd] if cmd else []
|
||||
lines = [cmd] if cmd else []
|
||||
elif src == "post_login":
|
||||
if buf_lines and str(buf_lines[-1]).strip():
|
||||
cmd = str(buf_lines[-1]).strip()
|
||||
lines = [cmd] if cmd else []
|
||||
else:
|
||||
# Interactive WebCRT: only trust frontend audit_line at Enter.
|
||||
lines = []
|
||||
self._last_prompt_line = ""
|
||||
else:
|
||||
lines = []
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue