Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion src/components/terminal/terminal.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,8 @@ export default class TerminalComponent {
position: relative;
background: ${this.options.theme.background};
overflow: hidden;
padding: 0.25rem;
box-sizing: border-box;
`;

return this.container;
Expand Down Expand Up @@ -691,7 +693,18 @@ export default class TerminalComponent {
* @param {string} data - Data to write
*/
write(data) {
this.terminal.write(data);
if (
this.serverMode &&
this.isConnected &&
this.websocket &&
this.websocket.readyState === WebSocket.OPEN
) {
// Send data through WebSocket instead of direct write
this.websocket.send(data);
} else {
// For local mode or disconnected terminals, write directly
this.terminal.write(data);
}
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/components/terminal/terminalManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,7 @@ class TerminalManager {
background: #1e1e1e;
overflow: hidden;
position: relative;
padding: 0.25rem;
}
`;
}
Expand Down
97 changes: 96 additions & 1 deletion src/lib/acode.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export default class Acode {
createServer: (options) => TerminalManager.createServerTerminal(options),
get: (id) => TerminalManager.getTerminal(id),
getAll: () => TerminalManager.getAllTerminals(),
write: (id, data) => TerminalManager.writeToTerminal(id, data),
write: (id, data) => this.#secureTerminalWrite(id, data),
clear: (id) => TerminalManager.clearTerminal(id),
close: (id) => TerminalManager.closeTerminal(id),
themes: {
Expand Down Expand Up @@ -162,6 +162,101 @@ export default class Acode {
this.define("toInternalUrl", helpers.toInternalUri);
}

/**
* Secure terminal write with command validation
* Prevents execution of malicious or dangerous commands through plugin API
* @param {string} id - Terminal ID
* @param {string} data - Data to write
*/
#secureTerminalWrite(id, data) {
if (typeof data !== "string") {
console.warn("Terminal write data must be a string");
return;
}

// List of potentially dangerous commands/patterns to block
const dangerousPatterns = [
// System commands that can cause damage
/^\s*rm\s+-rf?\s+\/[^\r\n]*[\r\n]?$/m,
/^\s*rm\s+-rf?\s+\*[^\r\n]*[\r\n]?$/m,
/^\s*rm\s+-rf?\s+~[^\r\n]*[\r\n]?$/m,
/^\s*mkfs\.[^\r\n]*[\r\n]?$/m,
/^\s*dd\s+if=\/[^\r\n]*[\r\n]?$/m,
/^\s*:(){ :|:& };:[^\r\n]*[\r\n]?$/m, // Fork bomb
/^\s*sudo\s+dd\s+if=\/[^\r\n]*[\r\n]?$/m,
/^\s*sudo\s+rm\s+-rf?\s+\/[^\r\n]*[\r\n]?$/m,
/^\s*curl\s+[^\r\n]*\|\s*sh[^\r\n]*[\r\n]?$/m,
/^\s*wget\s+[^\r\n]*\|\s*sh[^\r\n]*[\r\n]?$/m,
/^\s*bash\s+<\s*\([^\r\n]*[\r\n]?$/m,
/^\s*sh\s+<\s*\([^\r\n]*[\r\n]?$/m,

// Network-based attacks
/^\s*nc\s+-l\s+-p\s+\d+[^\r\n]*[\r\n]?$/m,
/^\s*ncat\s+-l\s+-p\s+\d+[^\r\n]*[\r\n]?$/m,
/^\s*python\s+.*SimpleHTTPServer[^\r\n]*[\r\n]?$/m,
/^\s*python\s+.*http\.server[^\r\n]*[\r\n]?$/m,

// Process manipulation
/^\s*kill\s+-9\s+1\s*[\r\n]?$/m,
/^\s*killall\s+-9\s+\*[^\r\n]*[\r\n]?$/m,

// File system manipulation
/^\s*chmod\s+777\s+\/[^\r\n]*[\r\n]?$/m,
/^\s*chown\s+[^\s]+\s+\/[^\r\n]*[\r\n]?$/m,

// Sensitive file access attempts
/^\s*cat\s+\/etc\/passwd[^\r\n]*[\r\n]?$/m,
/^\s*cat\s+\/etc\/shadow[^\r\n]*[\r\n]?$/m,
/^\s*cat\s+\/root\/[^\r\n]*[\r\n]?$/m,

// Only block null bytes
/\x00/g,
];

// Check for dangerous patterns
for (const pattern of dangerousPatterns) {
if (pattern.test(data)) {
console.warn(
`Blocked potentially dangerous terminal command: ${data.substring(0, 50)}...`,
);
toast("Potentially dangerous command blocked for security", 3000);
return;
}
}

// Additional checks for suspicious character sequences
if (data.includes("$(") && data.includes(")")) {
const commandSubstitution = /\$\([^)]*\)/g;
const matches = data.match(commandSubstitution);
if (matches) {
for (const match of matches) {
// Check if command substitution contains dangerous commands
for (const pattern of dangerousPatterns) {
if (pattern.test(match)) {
console.warn(
`Blocked command substitution with dangerous content: ${match}`,
);
toast("Command substitution blocked for security", 3000);
return;
}
}
}
}
}

// Sanitize data length to prevent memory exhaustion
const maxLength = 64 * 1024; // 64KB max per write
if (data.length > maxLength) {
console.warn(
`Terminal write data truncated - exceeded ${maxLength} characters`,
);
data = data.substring(0, maxLength) + "\n[Data truncated for security]\n";
}

// If all security checks pass, proceed with writing
return TerminalManager.writeToTerminal(id, data);
}

/**
* Define a module
* @param {string} name
Expand Down