🌙 이 블로그는 다크모드에서 코드 블록의 가독성이 더욱 향상됩니다. 화면 우측 하단의 달 모양 아이콘을 클릭하여 다크모드로 전환하시면 보다 쾌적한 읽기 환경을 제공받으실 수 있습니다.
이번 포스팅에서는 LLM Llama3 서버에서 전달받은 비정형 코드 분석 데이터를 파싱하고 포맷팅 하는 기능을 개발하면서 직면했던 문제와 해결 방법을 다룹니다.
1️⃣ 과제 및 목표
LLM Llama3 서버에 코드 취약점 분석을 요청한 후, 서버에서 응답받은 결과를 검사 결과 페이지에 표시하는 기능을 담당했습니다.이를 위해 서버에서 응답받는 데이터를 파싱하고, 포맷팅하여 화면에 일관되게 바인딩하는 작업이 필요했습니다. 스팩스페이스에서 제공하는 API 명세서를 보면 다음과 같습니다.
스팩스페이스에서 제공한 API 명세서에 따르면, "user_message" 필드 안에 분석할 코드를 담고, question 항목에 코드 취약점 분석 요청을 포함한 내용을 전송하게 됩니다.
아래는 코드 보안 취약점 분석을 요청하는 요청 본문(body) 및 응답 예시 입니다.
{
"user_message" :"코드내용...\nquestion:Please let me know after analyzing the security vulnerabilities of the code"
"temperature" :0.6,
"top_p" : 0.9
}
The code you provided is a React component written in JavaScript, using the Next.js framework. It appears to be a clipping article item component that displays a list of articles with various features such as sorting, showing more articles, and a mini-modal for each article.
After analyzing the code, I did not find any major security vulnerabilities. However, I do have some suggestions for improvement:
1. **Input Validation**: The code does not perform any input validation on the `array` variable, which is used to generate the list of articles. If the `array` variable is not properly sanitized, it could lead to security issues such as cross-site scripting (XSS) attacks. It is recommended to validate and sanitize any user-input data before using it in the component.
2. **Use of `dangerouslySetInnerHTML`**: The code uses `dangerouslySetInnerHTML` to set the HTML content of the article title and subtitle. This can be a security risk if the content is not properly sanitized, as it can lead to XSS attacks. It is recommended to use a safer alternative, such as using a library like `DOMPurify` to sanitize the HTML content.
...
Llama3 서버에 위와 같은 방식으로 코드 보안 취약점을 요청한 후, 서버로부터 응답받은 결과를 검사 결과 페이지에 시각적으로 일관되게 바인딩하는 것이 이번 과제의 목표였습니다.
2️⃣ 문제 정의
Llama3 서버는 실시간으로 최적의 응답을 생성하기 때문에, 응답 데이터의 형식이 고정되지 않는 특징을 가지고 있습니다. 이는 LLM(Large Language Model)이 다양한 입력에 대해 유연하게 대응하고, 최적의 결과를 생성하기 위해 컨텍스트에 따라 동적으로 응답을 생성하기 때문입니다. 이러한 유연성은 LLM의 강점이지만, 데이터 처리 측면에서는 일관된 데이터 구조를 보장하기 어려운 문제를 야기했습니다.
결과적으로, 데이터 저장 및 화면 바인딩 과정에서 이러한 예측 불가능한 데이터 구조를 다루어야 했으며, 데이터 파싱 및 포맷팅 과정에서 지속적인 오류가 발생했습니다. 이러한 문제를 해결하기 위해 동적 데이터를 안정적으로 처리할 수 있는 로직을 설계하는 것이 핵심 과제가 되었습니다.
아래는 Llama3 서버에서 총 4번의 코드 분석 요청을 보낸 후 응답을 정리한 사진입니다.
3️⃣ 해결 과정
우선 데이터를 아래와 같이 배열에 저장해서 화면에 출력하려 했고 데이터를 아래와 같은 형식으로 추출해서 저장하는 것이 목표였습니다. 이를 위해서는 분석 요청 질문을 원하는 대로 받을 수 있게 잘하는 것이 관건이었습니다.
securityRes:[{
title: string;
description: string;
code: string;
line: number;
}]
suggestRes:[{
title: string;
description: string;
code: string;
line: number;
}]
가설 1 : 정규표현식을 활용하여 데이터를 쉽게 추출할 수 있도록 프롬프트를 수정한다.
가설 1-1: 필요한 데이터만 변수명으로 지정하여 요청한다.
Please translate the security vulnerability analysis results of the above code into Korean and respond in Korean only, following the requested format. Under each security vulnerability analysis result, write the modified code to which the analysis result is applied after "Modification Code:" and write the line number where the modified part is located after "Modification Line:". Once the security vulnerability analysis is complete, please provide a correction suggestion based on eslint. Under each correction suggestion result, write the modified code to which the correction suggestion is applied after "Correction code: ", and write the line number where the modified part is located after "Correction line:".
서버로부터 배열의 프로퍼티에 들어갈 값만 요청하여, 파싱 할 때 일관된 구조의 데이터를 받을 수 있도록 요청하였습니다.
하지만 해당 서버에서 배열의 프로퍼티를 한글로 번역하는 과정에서 값이 일관되지 않게 응답되는 문제가 발생하였습니다.
Modification Coed > "변경 코드" 또는 "수정 코드" , Correction Code > "수정 코드" 또는 "교정 코드" 이런 식으로 일관되지 않게 응답되는 문제가 발생하여, 이 방법은 개선이 필요했습니다.
가설 1-2: 변수명을 한글로 고정하여 데이터 요청한다.
Please translate the security vulnerability analysis results of the above code into Korean and respond in Korean only, following the requested format. Under each security vulnerability analysis result, write the modified code to which the analysis result is applied after \"수정 코드 :\" and write the line number where the modified part is located after \"수정 라인:\". Once the security vulnerability analysis is complete, please provide a correction suggestion based on eslint. Under each correction suggestion result, write the modified code to which the correction suggestion is applied after \"교정 코드: \", and write the line number where the modified part is located after \"교정 라인:\"
응답 데이터의 일관성을 유지하기 위해, 배열의 프로퍼티를 고정된 한글 값으로 요청하는 방식을 도입했습니다. 이를 통해 데이터 처리의 일관성을 확보하고자 했습니다.
아래는 해당 요청에 따른 응답 결과입니다.
" 보안 취약점 분석 결과:\n\n1. 보안 취약점: 날짜 형식 문자열이 하드코딩되어 있습니다.\n 설명: 날짜 형식 문자열("yyyy-MM-dd HH:mm:ss")이 하드코딩되어 있습니다. 이는 날짜 형식이 변경될 경우 코드를 수정해야 하는 문제가 있습니다.\n 수정 코드: \n
\nexport const formatDate = today.toLocaleString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });\n
\n 수정 라인: 3\n\n2. 보안 취약점: 날짜 및 시간 문자열이 분리되어 있습니다.\n 설명: 날짜 및 시간 문자열이 분리되어 있습니다. 이는 날짜 및 시간을 별도로 처리해야 하는 문제가 있습니다.\n 수정 코드: \n
\nexport const todayDate = today.toLocaleDateString('ko-KR');\nexport const todayTime = today.toLocaleTimeString('ko-KR');\n
\n 수정 라인: 4, 5\n\nESLint 교정 제안:\n\n1. 교정 제안: 날짜 형식 문자열을 상수로 정의합니다.\n 설명: 날짜 형식 문자열을 상수로 정의하여 코드의 가독성을 향상시킵니다.\n 교정 코드: \n
\nconst DATE_FORMAT = 'yyyy-MM-dd HH:mm:ss';\nexport const formatDate = today.toString(DATE_FORMAT);\n
\n 교정 라인: 3\n\n2. 교정 제안: 날짜 및 시간 문자열을 상수로 정의합니다.\n 설명: 날짜 및 시간 문자열을 상수로 정의하여 코드의 가독성을 향상시킵니다.\n 교정 코드: \n
\nconst DATE_FORMAT = 'yyyy-MM-dd';\nconst TIME_FORMAT = 'HH:mm:ss';\nexport const todayDate = today.toLocaleDateString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit' });\nexport const todayTime = today.toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false });\n
\n 교정 라인: 4, 5"
이러한 형태의 고정된 한글 프로퍼티를 사용한 요청은 데이터 처리 과정에서 포맷팅 작업을 상당히 복잡하게 만들었습니다. 또한, 코드 가독성 측면에서도 불리했으며, 실제 코드 블록은 다양한 패턴을 포함하고 있어 유연성 또한 크게 떨어졌습니다.
이러한 문제를 해결하기 위해 함수를 활용하여 응답 데이터를 포맷팅 했지만, 예상치 못한 설명이나 주석이 응답에 포함되면서 자주 오류가 발생했습니다. 이로 인해 모든 예외 상황을 처리해야 했고, 코드 복잡성이 증가하여 가독성 및 유지보수성에도 어려움이 있었습니다. 아래는 해당 응답을 포맷팅 하는 함수와 응답 결과입니다.
const formattingResult = (result) => {
const securityRes = [];
const suggestRes = [];
// Split the result by lines for easier parsing
const lines = result.split("\n").map((line) => line.trim()); // trim으로 앞뒤 공백 제거
let currentSecurity = null;
let currentSuggestion = null;
let inSecurity = false;
let inSuggestion = false;
let collectingCode = false; // Track if we're collecting code
lines.forEach((line) => {
// 보안 취약점과 교정 제안 앞에 숫자가 있는 경우를 인식
if (line.match(/^\d+\.\s+보안 취약점:/)) {
if (currentSecurity) {
securityRes.push(currentSecurity); // Push the previous security entry
}
currentSecurity = {
title: line.replace(/^\d+\.\s+보안 취약점:/, "").trim(),
description: "",
code: "",
line: 0,
};
inSecurity = true;
inSuggestion = false;
collectingCode = false;
} else if (line.match(/^\d+\.\s+교정 제안:/)) {
if (currentSuggestion) {
suggestRes.push(currentSuggestion); // Push the previous suggestion entry
}
currentSuggestion = {
title: line.replace(/^\d+\.\s+교정 제안:/, "").trim(),
description: "",
code: "",
line: 0,
};
inSecurity = false;
inSuggestion = true;
collectingCode = false;
} else if (line.startsWith("설명:") && inSecurity) {
currentSecurity.description = line.replace("설명:", "").trim();
} else if (line.startsWith("수정 코드:") && inSecurity) {
currentSecurity.code = ""; // Prepare to collect code
collectingCode = true;
} else if (line.startsWith("수정 라인:") && inSecurity) {
currentSecurity.line = parseInt(line.replace("수정 라인:", "").trim(), 10);
securityRes.push(currentSecurity); // Save the security object
currentSecurity = null; // Reset after adding to array
inSecurity = false;
collectingCode = false;
} else if (line.startsWith("설명:") && inSuggestion) {
currentSuggestion.description = line.replace("설명:", "").trim();
} else if (line.startsWith("교정 코드:") && inSuggestion) {
currentSuggestion.code = ""; // Prepare to collect suggestion code
collectingCode = true;
} else if (line.startsWith("교정 라인:") && inSuggestion) {
currentSuggestion.line = parseInt(line.replace("교정 라인:", "").trim(), 10);
suggestRes.push(currentSuggestion); // Save the suggestion object
currentSuggestion = null; // Reset after adding to array
inSuggestion = false;
collectingCode = false;
} else if (collectingCode && (line.includes("export") || line.includes("const"))) {
if (inSecurity && currentSecurity) {
currentSecurity.code += line + "\n"; // Add code lines to securityRes
} else if (inSuggestion && currentSuggestion) {
currentSuggestion.code += line + "\n"; // Add code lines to suggestRes
}
}
});
// Push any remaining security or suggestion objects
if (currentSecurity) {
securityRes.push(currentSecurity);
}
if (currentSuggestion) {
suggestRes.push(currentSuggestion);
}
return { securityRes, suggestRes };
};
가설 2 : 서버로부터 데이터를 배열 형태로 받으면 파싱이 더 쉬워지고 로직이 단순화될 것이다.
데이터를 반복적으로 배열에 저장하는 작업을 진행하던 중, 문제를 해결하는 새로운 접근 방식이 떠올랐습니다. 굳이 복잡한 비정형 데이터를 파싱 할 필요 없이, 서버에서 처음부터 배열 형태로 응답을 받으면 파싱이 훨씬 간단해지고 로직도 직관적으로 개선될 수 있겠다는 생각이었습니다. 사실 저희가 필요한 것은 분석 결과의 복잡한 텍스트가 아니라, 배열로 정리된 핵심 데이터였기 때문에, 포맷팅 작업까지 직접 처리하는 대신 이 부분을 LLM에게 맡기는 방식을 고민하게 되었습니다.
이 아이디어를 도연님께 제안드렸고, 로직의 단순화와 효율성 측면에서 설득력을 인정받았습니다. 이를통해 기존의 복잡한 접근 대신 더 효율적이고 직관적인 질문 전략으로 전환하게 되었습니다. 이러한 방식은 결과적으로 전체적인 코드 구조를 간결하게 만들고 유지보수성을 크게 향상시킬 수 있을거라 판단하였습니다.
가설 2-1 : 분석 결과를 배열 형태로 응답하도록 서버에 요청한다.
Please translate the security vulnerability analysis result of the above code into Korean and respond only in Korean according to the requested format. Save the object arrangement below for the security vulnerability analysis result of each code. securityRes:[{ title: string; // code vulnerability, description: string; // code vulnerability description ,code: string; // code vulnerability code, line: number; // code vulnerability code line }] Once the security vulnerability analysis is complete, create a sprint-based calibration proposal and store it in the object array below. suggestRes:[{ title: string; // calibration proposal, description: string; // calibration description, code: string; // calibration code, line: number; // calibration code line }] title, description should be in Korean and only securityRes and sugestRes should respond.
서버에서 응답받은 텍스트 데이터를 클라이언트에서 배열로 변환하는 대신, 처음부터 배열 형태의 응답을 서버에 요청했습니다.
서버가 텍스트 형식으로만 응답을 제공하는 한계가 있었기 때문에, 우선적으로 응답 데이터를 JSON 형식으로 변환해야 했습니다. 이를 위해 JSON.parse() 함수를 사용하여 문자열을 JSON 객체로 변환하는 방식으로 데이터를 파싱 했습니다. 이러한 접근 덕분에 복잡했던 파싱 로직을 대폭 간소화할 수 있었고, 코드의 복잡성도 크게 줄어들었습니다.
아래는 개선된 포맷팅 함수입니다.
// 보안 취약점 부분을 JSON 형식으로 변환
let cleanedResult = result
.replace(/,\s*}/g, "}")
.replace(/,\s*\]/g, "]")
.replace(/\\n/g, "")
.replace(/\\\"/g, '"')
.replace(/\s+/g, " ")
.trim();
cleanedResult = cleanedResult.replace(
/(\{|,)\s*(title|description|code|line):/g, // 중괄호나 쉼표 다음에 나오는 키에 대해
'$1 "$2":', // 키에 쌍따옴표 추가
);
cleanedResult = cleanedResult.replace(/^\s*"\s*/, "");
cleanedResult = cleanedResult.replace(/"\s*$/, "");
cleanedResult = cleanedResult.replace(/},\s*];/g, "}]");
cleanedResult = cleanedResult.replace(/];\s*(?=,|"suggestRes")/g, "]");
cleanedResult = cleanedResult.replace(/,\s*([\}\]])/g, "$1");
cleanedResult = cleanedResult.replace(
/"code":\s*"([^"]*)"/g,
(match, p1) => {
const updatedCode = p1
.replace(/"/g, "") // 내부 따옴표 제거
.replace(/:\s*/g, " = ") // 콜론을 =로 변환
.replace(/\\/g, "\\\\"); // 백슬래시 이스케이프 처리
return `"code": "${updatedCode}"`;
},
);
// JSON.parse가 가능한 형태로 만들기 위해 cleanedResult를 {}로 감싸서 변환
const finalResult = `{${cleanedResult}}`;
const parsedData = JSON.parse(finalResult);
개선된 사항 : 따옴표 형식 통일
Please translate the security vulnerability analysis result of the above code into Korean and respond only in Korean according to the requested format. Save the object arrangement below for the security vulnerability analysis result of each code. securityRes:[{ title: string; // code vulnerability, description: string; // code vulnerability description ,code: string; // code vulnerability code, line: number; // code vulnerability code line }]
Once the security vulnerability analysis is complete, create a sprint-based calibration proposal and store it in the object array below. suggestRes:[{ title: string; // calibration proposal, description: string; // calibration description, code: string; // calibration code, line: number; // calibration code line }] title, description should be in Korean and only securityRes and sugestRes should respond. Please answer all quotes using only large quotes instead of small ones
JSON 형식에서는 큰따옴표만을 허용하기 때문에, 간혹 작은따옴표와 큰따옴표가 혼합되어 전달되는 경우 파싱 오류가 발생했습니다. 이러한 문제는 데이터 처리 과정에서 예상치 못한 오류를 초래할 수 있기 때문에, 데이터의 일관성을 보장하기 위해 서버 측에 모든 따옴표를 JSON 표준에 맞춰 큰따옴표로 통일해 줄 것을 요청했습니다.
4️⃣ 성과
데이터 일관성 확보 (파싱 정확도 90% 달성)
서버로부터 응답받은 데이터의 90%에서 일관된 구조를 확보하였으며, 이를 기반으로 정확한 파싱과 화면 바인딩에 성공했습니다. 이로써 데이터 처리의 안정성과 신뢰성이 크게 향상되었습니다.
데이터 처리 효율성 극대화:
고정된 데이터 구조 덕분에 파싱 로직이 간소화되었으며, 그 결과 코드 복잡성을 현저히 낮추는 동시에 데이터 처리의 효율성과 성능이 비약적으로 개선되었습니다.
5️⃣ 배운 점
서버와의 긴밀한 협력:
클라이언트 측에서 데이터 파싱이 성공적으로 이루어지기 위해서는 서버와의 협력이 필수적임을 절실히 깨달았습니다. 특히, 데이터 형식에 대한 명확한 정의와 통일된 기준이 있어야만 효율적이고 정확한 데이터 처리가 가능하다는 점을 실감했습니다. 이를 통해 서버와의 소통이 데이터 처리 과정에서 중요한 역할을 한다는 것을 배웠습니다.
에러 예방을 위한 유연한 로직 설계:
예상치 못한 데이터 구조 변화에 대비하여 더 많은 테스트 케이스를 설정하고, 예외 처리를 강화한 유연한 파싱 로직의 필요성을 깨달았습니다. 다양한 시나리오에 대응할 수 있는 로직을 설계함으로써 오류를 미연에 방지하는 능력을 키웠으며, 이러한 과정에서 다양한 테스트 방법론을 적용하는 소중한 경험을 얻었습니다.
지속적인 개선을 위한 고민:
단순히 기능을 구현하는 데 그치지 않고, 어떻게 하면 더 나은 방법으로 기능을 개선할 수 있을지 끊임없이 고민했습니다. 개발 중에도 현재 로직의 복잡성을 줄이고 성능을 최적화할 수 있는 방법을 탐구했으며, 이미 구현된 기능에서도 더 나은 구조와 효율성을 위한 지속적인 개선을 고려했습니다. 이러한 사고방식은 앞으로의 코드 유지보수성과 확장성을 크게 향상시키는 데 중요한 역할을 할 것입니다.
6️⃣ 후기
Github PR : https://github.com/FlawDetector-team-yes/FlawDetector-team-yes/pull/103
Llam3를 상대로 끝까지 함께 싸워주셨던 도연님 정말 감사하고 고생 많으셨습니다.
그간 힘겹게 싸워왔던 모습들,,