You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于xterm.js与Django的终端文本编辑器检测及别名管控问询

解决xterm.js+Django环境下禁止终端别名并检测文本编辑器运行的问题

一、完全禁止用户使用别名的可行方案

要解决别名导致关键词过滤失效的问题,最直接的方案是彻底禁用bash的别名功能,具体可通过以下几个层面实现:

  • 启动bash时跳过用户配置文件:使用--noprofile --norc参数,避免加载~/.bashrc或~/.bash_profile中定义的别名
  • 清空所有已存在的别名:执行unalias -a命令,确保当前会话内无任何别名残留
  • 关闭别名扩展功能:通过set +o expand_aliases彻底禁用bash的别名解析能力

这样用户无法创建或使用任何别名,原有的关键词过滤方案就能准确检测nano/vim等编辑器的直接调用。

二、代码实现示例

1. Django 后端 views.py 代码

from django.http import HttpResponse
from django.views.decorators.http import require_http_methods
import subprocess
import os
import threading
import time
from django.middleware.csrf import get_token

# 存储终端进程,用session_id做标识
terminal_processes = {}
# 编辑器命令黑名单
EDITOR_BLACKLIST = {'nano', 'vim', 'vi', 'emacs'}

@require_http_methods(["GET"])
def terminal_connect(request):
    session_id = request.GET.get('session_id', '')
    if not session_id:
        return HttpResponse("Missing session_id", status=400)
    
    # 启动禁用别名的bash会话:跳过配置文件+清空别名+关闭别名扩展
    startup_cmd = 'unalias -a; set +o expand_aliases; exec /bin/bash'
    process = subprocess.Popen(
        ['/bin/bash', '--noprofile', '--norc', '-c', startup_cmd],
        stdin=subprocess.PIPE,
        stdout=subprocess.PIPE,
        stderr=subprocess.STDOUT,
        start_new_session=True,
        preexec_fn=os.setsid,
        text=True
    )
    
    terminal_processes[session_id] = process
    
    # 后台线程:记录终端输出+检测编辑器调用
    def monitor_and_log():
        while process.poll() is None:
            output = process.stdout.readline()
            if not output:
                continue
            # 写入终端会话日志
            with open(f'terminal_session_{session_id}.log', 'a', encoding='utf-8') as f:
                f.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {output}")
            # 检测编辑器命令并记录
            for cmd in EDITOR_BLACKLIST:
                if cmd in output.lower().strip():
                    with open('editor_usage.log', 'a', encoding='utf-8') as f:
                        f.write(f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] Session {session_id} executed editor: {output.strip()}\n")
                        break
    
    threading.Thread(target=monitor_and_log, daemon=True).start()
    return HttpResponse("Terminal session started", status=200)

@require_http_methods(["POST"])
def terminal_input(request):
    session_id = request.POST.get('session_id', '')
    input_data = request.POST.get('input', '')
    process = terminal_processes.get(session_id)
    
    if not process or process.poll() is not None:
        return HttpResponse("Terminal session not found or exited", status=404)
    
    process.stdin.write(input_data)
    process.stdin.flush()
    return HttpResponse("Input sent", status=200)

# 前端获取CSRF Token的接口(可选)
def get_csrf_token(request):
    return HttpResponse(get_token(request))

2. 前端 index.html 代码(xterm.js 5.2.1)

<!DOCTYPE html>
<html>
<head>
    <title>Web Terminal</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/xterm@5.2.1/css/xterm.css" />
    <script src="https://cdn.jsdelivr.net/npm/xterm@5.2.1/lib/xterm.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/xterm-addon-fit@0.7.0/lib/xterm-addon-fit.js"></script>
</head>
<body>
    <div id="terminal-container" style="width: 90vw; height: 80vh; margin: 20px auto;"></div>
    <input type="hidden" name="csrfmiddlewaretoken" id="csrf-token">

    <script>
        // 初始化终端
        const term = new Terminal({
            cursorBlink: true,
            fontSize: 14,
            scrollback: 1000,
            theme: {
                background: '#1a1a1a',
                foreground: '#e0e0e0',
                cursor: '#ffffff'
            }
        });
        const fitAddon = new FitAddon.FitAddon();
        term.loadAddon(fitAddon);
        term.open(document.getElementById('terminal-container'));
        fitAddon.fit();

        // 生成唯一会话ID
        const sessionId = `term_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
        // 获取CSRF Token
        fetch('/get-csrf-token')
            .then(res => res.text())
            .then(token => document.getElementById('csrf-token').value = token);

        // 连接后端终端会话
        fetch(`/terminal/connect?session_id=${sessionId}`)
            .catch(err => console.error('Failed to connect terminal:', err));

        // 监听终端输入,发送到后端
        term.onData(data => {
            fetch('/terminal/input', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                    'X-CSRFToken': document.getElementById('csrf-token').value
                },
                body: `session_id=${sessionId}&input=${encodeURIComponent(data)}`
            });
        });

        // 注:生产环境建议用WebSocket替代轮询实现实时输出推送,以下为简化示例
        setInterval(() => {
            // 此处需补充后端获取终端输出的接口逻辑
            // 示例省略,实际可通过WebSocket实时接收后端推送的终端输出并写入term
        }, 100);
    </script>
</body>
</html>

三、额外注意事项

  • 上述方案中,bash启动命令不会在前端终端中显示,完全后台执行
  • 日志记录逻辑在后台线程中运行,不影响终端的正常交互
  • 生产环境务必用WebSocket替代轮询,实现终端输出的实时推送,提升用户体验
  • 可根据需求扩展EDITOR_BLACKLIST,添加更多需要检测的编辑器或命令

内容的提问来源于stack exchange,提问作者ivenoidea

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 20:40:28