Skip to content

Commit ff55c83

Browse files
robotlearning123sandia777claude
authored
fix(security): XSS, CORS, command injection, bind address (#24)
* fix(security): restrict CORS origins to localhost only Wildcard CORS allowed any origin to make authenticated requests. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(security): replace innerHTML with textContent for user data XSS via innerHTML interpolation of user-controlled data (dataset titles, error messages, PubChem fields). Use textContent/DOM creation instead of innerHTML for all user-sourced strings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(security): escape quotes in AppleScript command to prevent injection Unescaped double quotes in command string allowed breaking out of the AppleScript keystroke string literal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(security): bind to 127.0.0.1 instead of 0.0.0.0 Binding to 0.0.0.0 exposes the service on all network interfaces. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(security): safe access to response.content in claude backend response.content[0].text raises IndexError if content is empty. Use safe iterator with fallback to empty string. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(security): safe access to response.content in brain.py Same fix as claude.py — response.content[0].text raises IndexError on empty content. Use safe iterator with fallback. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Cong <72737794+robolearning123@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b7b73aa commit ff55c83

4 files changed

Lines changed: 51 additions & 20 deletions

File tree

src/device_use/backends/claude.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ async def _call(
5454
kwargs["system"] = system
5555

5656
response = await self._client.messages.create(**kwargs)
57-
return response.content[0].text
57+
return next((b.text for b in response.content if hasattr(b, "text")), "")
5858

5959
@staticmethod
6060
def _encode_image(screenshot: bytes) -> str:

src/device_use/instruments/nmr/brain.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def _call(self, system: str, user_message: str, max_tokens: int = 2000) -> str:
111111
system=system,
112112
messages=[{"role": "user", "content": user_message}],
113113
)
114-
return response.content[0].text
114+
return next((b.text for b in response.content if hasattr(b, "text")), "")
115115

116116
def _stream(self, system: str, user_message: str, max_tokens: int = 2000) -> Generator[str]:
117117
"""Streaming API call — yields text chunks."""

src/device_use/instruments/nmr/gui_automation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,14 @@ def type_command(self, command: str) -> None:
252252

253253
if platform.system() == "Darwin":
254254
# Use AppleScript to type into TopSpin
255+
safe_command = command.replace('"', '\\"')
255256
script = f'''
256257
tell application "TopSpin 5"
257258
activate
258259
end tell
259260
delay 0.5
260261
tell application "System Events"
261-
keystroke "{command}"
262+
keystroke "{safe_command}"
262263
keystroke return
263264
end tell
264265
'''

src/device_use/web/app.py

Lines changed: 47 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545

4646
app.add_middleware(
4747
CORSMiddleware,
48-
allow_origins=["*"],
48+
allow_origins=["http://localhost:8420", "http://127.0.0.1:8420"],
4949
allow_methods=["*"],
5050
allow_headers=["*"],
5151
)
@@ -661,10 +661,14 @@ def index():
661661
datasets.forEach(ds => {
662662
const card = document.createElement('div');
663663
card.className = 'dataset-card';
664-
card.innerHTML = `
665-
<div class="name">${ds.sample}/${ds.expno}</div>
666-
<div class="title">${ds.title || 'No title'}</div>
667-
`;
664+
const nameDiv = document.createElement('div');
665+
nameDiv.className = 'name';
666+
nameDiv.textContent = `${ds.sample}/${ds.expno}`;
667+
const titleDiv = document.createElement('div');
668+
titleDiv.className = 'title';
669+
titleDiv.textContent = ds.title || 'No title';
670+
card.appendChild(nameDiv);
671+
card.appendChild(titleDiv);
668672
card.onclick = () => selectDataset(ds.sample, ds.expno, ds.title);
669673
container.appendChild(card);
670674
});
@@ -680,10 +684,14 @@ def index():
680684
datasets.forEach(ds => {
681685
const card = document.createElement('div');
682686
card.className = 'dataset-card';
683-
card.innerHTML = `
684-
<div class="name">${ds.name}</div>
685-
<div class="title">${ds.protocol} · ${ds.mode} · ${ds.wavelength_nm}nm</div>
686-
`;
687+
const pNameDiv = document.createElement('div');
688+
pNameDiv.className = 'name';
689+
pNameDiv.textContent = ds.name;
690+
const pTitleDiv = document.createElement('div');
691+
pTitleDiv.className = 'title';
692+
pTitleDiv.textContent = `${ds.protocol} · ${ds.mode} · ${ds.wavelength_nm}nm`;
693+
card.appendChild(pNameDiv);
694+
card.appendChild(pTitleDiv);
687695
card.onclick = () => selectPlate(ds.name);
688696
container.appendChild(card);
689697
});
@@ -776,7 +784,7 @@ def index():
776784
} else if (data.type === 'error') {
777785
es.close();
778786
document.getElementById('btnAnalyze').disabled = false;
779-
panel.innerHTML = `<span style="color:red;">Error: ${data.message}</span>`;
787+
panel.textContent = `Error: ${data.message}`;
780788
}
781789
};
782790
@@ -785,7 +793,7 @@ def index():
785793
document.getElementById('btnAnalyze').disabled = false;
786794
};
787795
} catch (e) {
788-
panel.innerHTML = `<span style="color:red;">Error: ${e.message}</span>`;
796+
panel.textContent = `Error: ${e.message}`;
789797
document.getElementById('btnAnalyze').disabled = false;
790798
}
791799
}
@@ -832,11 +840,33 @@ def index():
832840
| **Weight** | ${data.MolecularWeight || 'N/A'} |
833841
| **SMILES** | ${data.CanonicalSMILES || data.SMILES || 'N/A'} |
834842
| **InChIKey** | ${data.InChIKey || 'N/A'} |`;
835-
panel.innerHTML = typeof marked !== 'undefined' ? marked.parse(md) : md;
843+
panel.textContent = '';
844+
const table = document.createElement('table');
845+
table.innerHTML = '<tr><th>Property</th><th>Value</th></tr>';
846+
const rows = [
847+
['CID', data.CID],
848+
['IUPAC', data.IUPACName || 'N/A'],
849+
['Formula', data.MolecularFormula || 'N/A'],
850+
['Weight', data.MolecularWeight || 'N/A'],
851+
['SMILES', data.CanonicalSMILES || data.SMILES || 'N/A'],
852+
['InChIKey', data.InChIKey || 'N/A'],
853+
];
854+
rows.forEach(([label, value]) => {
855+
const tr = document.createElement('tr');
856+
const tdLabel = document.createElement('td');
857+
tdLabel.textContent = label;
858+
const tdValue = document.createElement('td');
859+
tdValue.textContent = value;
860+
tr.appendChild(tdLabel);
861+
tr.appendChild(tdValue);
862+
table.appendChild(tr);
863+
});
864+
panel.textContent = '';
865+
panel.appendChild(table);
836866
} catch (e) {
837867
const panel = document.getElementById('aiPanel');
838868
panel.style.display = 'block';
839-
panel.innerHTML = `<span style="color:var(--dim);">PubChem: ${e.message}</span>`;
869+
panel.textContent = `PubChem: ${e.message}`;
840870
}
841871
document.getElementById('btnPubchem').disabled = false;
842872
}
@@ -902,7 +932,7 @@ def index():
902932
903933
document.getElementById('plateHeatmap').innerHTML = html;
904934
} catch (e) {
905-
document.getElementById('plateMeta').innerHTML = `<span style="color:red;">Failed: ${e.message}</span>`;
935+
document.getElementById('plateMeta').textContent = `Failed: ${e.message}`;
906936
}
907937
}
908938
@@ -937,7 +967,7 @@ def index():
937967
} else if (data.type === 'error') {
938968
es.close();
939969
document.getElementById('btnPlateAnalyze').disabled = false;
940-
panel.innerHTML = `<span style="color:red;">Error: ${data.message}</span>`;
970+
panel.textContent = `Error: ${data.message}`;
941971
}
942972
};
943973
@@ -946,7 +976,7 @@ def index():
946976
document.getElementById('btnPlateAnalyze').disabled = false;
947977
};
948978
} catch (e) {
949-
panel.innerHTML = `<span style="color:red;">Error: ${e.message}</span>`;
979+
panel.textContent = `Error: ${e.message}`;
950980
document.getElementById('btnPlateAnalyze').disabled = false;
951981
}
952982
}
@@ -963,4 +993,4 @@ def index():
963993

964994
print("\n Device-Use Web GUI starting...")
965995
print(" Open http://localhost:8420 in your browser\n")
966-
uvicorn.run(app, host="0.0.0.0", port=8420, log_level="info")
996+
uvicorn.run(app, host="127.0.0.1", port=8420, log_level="info")

0 commit comments

Comments
 (0)