Skip to content

Commit 6acce21

Browse files
committed
Fix: Sanitizing HTML inputs and Cors misconfiguration
1 parent 4a62feb commit 6acce21

File tree

7 files changed

+102
-76
lines changed

7 files changed

+102
-76
lines changed

devika.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626

2727

2828
app = Flask(__name__)
29-
CORS(app)
29+
CORS(app, resources={r"/*": {"origins": ["https://localhost:3000"]}}) # Change the origin to your frontend URL
3030
app.register_blueprint(project_bp)
3131
socketio.init_app(app)
3232

@@ -116,14 +116,6 @@ def get_agent_state():
116116
return jsonify({"state": agent_state})
117117

118118

119-
@app.route("/api/get-project-files/", methods=["GET"])
120-
@route_logger(logger)
121-
def project_files():
122-
project_name = request.args.get("project_name")
123-
files = AgentState.get_project_files(project_name)
124-
return jsonify({"files": files})
125-
126-
127119
@app.route("/api/get-browser-snapshot", methods=["GET"])
128120
@route_logger(logger)
129121
def browser_snapshot():

src/apis/project.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from flask import blueprints, request, jsonify, send_file, make_response
2+
from werkzeug.utils import secure_filename
23
from src.logger import Logger, route_logger
34
from src.config import Config
45
from src.project import ProjectManager
@@ -13,20 +14,28 @@
1314

1415

1516
# Project APIs
17+
18+
@project_bp.route("/api/get-project-files", methods=["GET"])
19+
@route_logger(logger)
20+
def project_files():
21+
project_name = secure_filename(request.args.get("project_name"))
22+
files = manager.get_project_files(project_name)
23+
return jsonify({"files": files})
24+
1625
@project_bp.route("/api/create-project", methods=["POST"])
1726
@route_logger(logger)
1827
def create_project():
1928
data = request.json
2029
project_name = data.get("project_name")
21-
manager.create_project(project_name)
30+
manager.create_project(secure_filename(project_name))
2231
return jsonify({"message": "Project created"})
2332

2433

2534
@project_bp.route("/api/delete-project", methods=["POST"])
2635
@route_logger(logger)
2736
def delete_project():
2837
data = request.json
29-
project_name = data.get("project_name")
38+
project_name = secure_filename(data.get("project_name"))
3039
manager.delete_project(project_name)
3140
AgentState().delete_state(project_name)
3241
return jsonify({"message": "Project deleted"})
@@ -35,7 +44,7 @@ def delete_project():
3544
@project_bp.route("/api/download-project", methods=["GET"])
3645
@route_logger(logger)
3746
def download_project():
38-
project_name = request.args.get("project_name")
47+
project_name = secure_filename(request.args.get("project_name"))
3948
manager.project_to_zip(project_name)
4049
project_path = manager.get_zip_path(project_name)
4150
return send_file(project_path, as_attachment=False)
@@ -44,7 +53,7 @@ def download_project():
4453
@project_bp.route("/api/download-project-pdf", methods=["GET"])
4554
@route_logger(logger)
4655
def download_project_pdf():
47-
project_name = request.args.get("project_name")
56+
project_name = secure_filename(request.args.get("project_name"))
4857
pdf_dir = Config().get_pdfs_dir()
4958
pdf_path = os.path.join(pdf_dir, f"{project_name}.pdf")
5059

src/project.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,32 @@ def project_to_zip(self, project: str):
144144

145145
def get_zip_path(self, project: str):
146146
return f"{self.get_project_path(project)}.zip"
147+
148+
def get_project_files(self, project_name: str):
149+
if not project_name:
150+
return []
151+
152+
project_directory = "-".join(project_name.split(" "))
153+
base_path = os.path.abspath(os.path.join(os.getcwd(), 'data', 'projects'))
154+
directory = os.path.join(base_path, project_directory)
155+
156+
# Ensure the directory is within the allowed base path
157+
if not os.path.exists(directory) or not os.path.commonprefix([directory, base_path]) == base_path:
158+
return []
159+
160+
files = []
161+
for root, _, filenames in os.walk(directory):
162+
for filename in filenames:
163+
file_relative_path = os.path.relpath(root, directory)
164+
if file_relative_path == '.':
165+
file_relative_path = ''
166+
file_path = os.path.join(file_relative_path, filename)
167+
try:
168+
with open(os.path.join(root, filename), 'r') as file:
169+
files.append({
170+
"file": file_path,
171+
"code": file.read()
172+
})
173+
except Exception as e:
174+
print(f"Error reading file {filename}: {e}")
175+
return files

src/state.py

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -174,28 +174,4 @@ def get_latest_token_usage(self, project: str):
174174
if agent_state:
175175
return json.loads(agent_state.state_stack_json)[-1]["token_usage"]
176176
return 0
177-
178-
def get_project_files(self, project_name: str):
179-
if not project_name:
180-
return []
181-
project_directory = "-".join(project_name.split(" "))
182-
directory = os.path.join(os.getcwd(), 'data', 'projects', project_directory)
183-
if(not os.path.exists(directory)):
184-
return []
185-
files = []
186-
for root, _, filenames in os.walk(directory):
187-
for filename in filenames:
188-
file_relative_path = os.path.relpath(root, directory)
189-
if file_relative_path == '.': file_relative_path = ''
190-
file_path = os.path.join(file_relative_path, filename)
191-
print("file_path",file_path)
192-
try:
193-
with open(os.path.join(root, filename), 'r') as file:
194-
print("File:", filename)
195-
files.append({
196-
"file": file_path,
197-
"code": file.read()
198-
})
199-
except Exception as e:
200-
print(f"Error reading file {filename}: {e}")
201-
return files
177+

ui/bun.lockb

412 Bytes
Binary file not shown.

ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"@xterm/xterm": "^5.5.0",
2929
"bits-ui": "^0.21.2",
3030
"clsx": "^2.1.0",
31+
"dompurify": "^3.1.5",
3132
"mode-watcher": "^0.3.0",
3233
"paneforge": "^0.0.3",
3334
"socket.io-client": "^4.7.5",

ui/src/lib/components/MessageInput.svelte

Lines changed: 57 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script>
2+
import DOMPurify from "dompurify";
23
import { emitMessage, socketListener } from "$lib/sockets";
34
import { agentState, messages, isSending } from "$lib/store";
45
import { calculateTokens } from "$lib/token";
@@ -11,12 +12,27 @@
1112
if (value !== null && value.agent_is_active == false) {
1213
isSending.set(false);
1314
}
14-
if (value == null){
15+
if (value == null) {
1516
inference_time = 0;
1617
}
1718
});
1819
1920
let messageInput = "";
21+
22+
// Function to escape HTML
23+
function escapeHTML(input) {
24+
const map = {
25+
"&": "&amp;",
26+
"<": "&lt;",
27+
">": "&gt;",
28+
'"': "&quot;",
29+
"'": "&#039;",
30+
};
31+
return input.replace(/[&<>"']/g, function (m) {
32+
return map[m];
33+
});
34+
}
35+
2036
async function handleSendMessage() {
2137
const projectName = localStorage.getItem("selectedProject");
2238
const selectedModel = localStorage.getItem("selectedModel");
@@ -31,26 +47,29 @@
3147
return;
3248
}
3349
34-
if (messageInput.trim() !== "" && isSending) {
50+
const sanitizedMessage = DOMPurify.sanitize(messageInput);
51+
const escapedMessage = escapeHTML(sanitizedMessage);
52+
53+
54+
if (messageInput.trim() !== "" && escapedMessage.trim() !== "" && isSending) {
3555
$isSending = true;
36-
emitMessage("user-message", {
37-
message: messageInput,
56+
emitMessage("user-message", {
57+
message: escapedMessage,
3858
base_model: selectedModel,
3959
project_name: projectName,
4060
search_engine: serachEngine,
4161
});
4262
messageInput = "";
43-
4463
}
4564
}
4665
onMount(() => {
4766
socketListener("inference", function (data) {
48-
if(data['type'] == 'time') {
67+
if (data["type"] == "time") {
4968
inference_time = data["elapsed_time"];
5069
}
5170
});
5271
});
53-
72+
5473
function setTokenSize(event) {
5574
const prompt = event.target.value;
5675
let tokens = calculateTokens(prompt);
@@ -73,41 +92,41 @@
7392
{/if}
7493
</div>
7594
<!-- {#if $agentState !== null} -->
76-
<div class="px-1 rounded-md text-xs">
77-
Model Inference: <span class="text-orange-600">{inference_time} sec</span>
78-
</div>
95+
<div class="px-1 rounded-md text-xs">
96+
Model Inference: <span class="text-orange-600">{inference_time} sec</span>
97+
</div>
7998
<!-- {/if} -->
8099
</div>
81100

82-
<div class="expandable-input relative">
83-
<textarea
84-
id="message-input"
85-
class="w-full p-4 font-medium focus:text-foreground rounded-xl outline-none h-28 pr-20 bg-secondary
86-
{$isSending ? 'cursor-not-allowed' : ''}"
87-
placeholder="Type your message..."
88-
disabled={$isSending}
89-
bind:value={messageInput}
90-
on:input={setTokenSize}
91-
on:keydown={(e) => {
92-
if (e.key === "Enter" && !e.shiftKey) {
93-
e.preventDefault();
94-
handleSendMessage();
95-
document.querySelector('.token-count').textContent = 0;
96-
}
97-
}}
98-
></textarea>
99-
<button
100-
on:click={handleSendMessage}
101-
disabled={$isSending}
102-
class="absolute text-secondary bg-primary p-2 right-4 bottom-6 rounded-full
101+
<div class="expandable-input relative">
102+
<textarea
103+
id="message-input"
104+
class="w-full p-4 font-medium focus:text-foreground rounded-xl outline-none h-28 pr-20 bg-secondary
103105
{$isSending ? 'cursor-not-allowed' : ''}"
104-
>
105-
{@html Icons.CornerDownLeft}
106-
</button>
107-
<p class="absolute text-tertiary p-2 right-4 top-2">
108-
<span class="token-count">0</span>
109-
</p>
110-
</div>
106+
placeholder="Type your message..."
107+
disabled={$isSending}
108+
bind:value={messageInput}
109+
on:input={setTokenSize}
110+
on:keydown={(e) => {
111+
if (e.key === "Enter" && !e.shiftKey) {
112+
e.preventDefault();
113+
handleSendMessage();
114+
document.querySelector(".token-count").textContent = 0;
115+
}
116+
}}
117+
></textarea>
118+
<button
119+
on:click={handleSendMessage}
120+
disabled={$isSending}
121+
class="absolute text-secondary bg-primary p-2 right-4 bottom-6 rounded-full
122+
{$isSending ? 'cursor-not-allowed' : ''}"
123+
>
124+
{@html Icons.CornerDownLeft}
125+
</button>
126+
<p class="absolute text-tertiary p-2 right-4 top-2">
127+
<span class="token-count">0</span>
128+
</p>
129+
</div>
111130
</div>
112131

113132
<style>

0 commit comments

Comments
 (0)