diff --git a/web/operator-gui/app.js b/web/operator-gui/app.js
index c835418..c4bef45 100644
--- a/web/operator-gui/app.js
+++ b/web/operator-gui/app.js
@@ -123,11 +123,29 @@ async function apiJson(path, options = {}) {
});
- const payload = await response.json();
+ const text = await response.text();
+
+ let payload = null;
+
+ if (text) {
+
+ try {
+
+ payload = JSON.parse(text);
+
+ } catch (error) {
+
+ payload = null;
+
+ }
+
+ }
if (!response.ok) {
- throw new Error(payload.error || `API 요청 실패: ${response.status}`);
+ const message = (payload && payload.error) || `API 요청 실패: ${response.status}`;
+
+ throw new Error(message);
}
@@ -523,6 +541,28 @@ function escapeHtml(value) {
+function safeUrl(value) {
+
+ // Defense-in-depth for URLs derived from external search results: only allow
+
+ // absolute http(s) or same-origin paths into href/src (blocks javascript:,
+
+ // data:, etc.). The server also normalizes these server-side.
+
+ const url = String(value || "").trim();
+
+ if (/^https?:\/\//i.test(url) || url.startsWith("/")) {
+
+ return url;
+
+ }
+
+ return "#";
+
+}
+
+
+
function formatReason(reason) {
const text = String(reason || "");
if (!text) return "";
@@ -1719,9 +1759,9 @@ function renderEvidencePreview(evidence) {
return `
-
+
-
+
@@ -1741,7 +1781,7 @@ function renderEvidenceLink(evidence) {
return `
-
+
${escapeHtml(label)}
@@ -1937,7 +1977,7 @@ function renderCollectionCandidates() {
${
candidate.sourceUrl
- ? `출처 열기`
+ ? `출처 열기`
: ""
}