Files
aio-01d4-stu/Last3Mon/73/LAB-03-Q-OUTPUT-AND-TESTS.md

26 KiB

Lab 03 · 나가는 답 검사, 보안 기록, 어려운 보안 테스트, 감사 로그

  • 관련 강의: 3단계 · 출력 필터와 보안 테스트
  • 소요 시간: 50분 (6교시 한 시간) · 예제 1 10분 · 예제 2 8분 · 예제 3 7분 · 과제 1 13분 · 과제 2 12분
  • 준비: Lab 02의 my-work/secure-agent.json (판별 LLM까지 끝낸 것). Lab 02 과제 1을 못 끝냈으면 cp reference/lab03-example1-filter.json my-work/secure-agent.json 후 push하고 예제 2부터 (판별 LLM과 출력 필터가 들어 있음)
  • 목표: 들어오는 질문만이 아니라 나가는 답도 검사한다. 무엇을 왜 막았는지 기록으로 남기고, 관문마다 시간과 기록을 보고, 내가 쓴 어려운 보안 테스트로 지나치게 막은 것과 덜 막은 것을 찾아 고친다. 마지막으로 기록을 개인정보 없이 감사 로그로 모아 하루 요약을 본다.
flowchart LR
  E1["예제 1<br/>출력 필터<br/>연락처 가리기"] --> E2["예제 2<br/>보안 기록<br/>security"] --> E3["예제 3<br/>관문별<br/>시간과 기록"] --> T1["과제 1<br/>어려운 보안 테스트"] --> T2["과제 2<br/>감사 로그 ·<br/>보안 요약"]

복사해 붙일 요청문은 없습니다. 막히면 ▶ 를 하나씩 펼칩니다.

예제 1 · 나가는 답에서 개인정보 가리기 (10분)

uv run tools/flow.py call my-secure requests/contact-own.json -v      # 최유진이 본인 접수 건과 연락처를 물음

본인 건이니 보여 줘도 될까요? 답이 화면 · 메신저 · 로그에 남는다면?

만들 것: 답을 보내기 전에 Code가 전화번호 · 이메일 · 주민등록번호를 가리고, 내부 정보 표시가 보이면 답을 막기.

조건 값
가리기 010-1414-2135 → 010-****-2135, yujin.choi@example.com → y***@example.com, 주민번호 뒤 7자리
막기 답에 내부 코드 모양(IT-ADMIN- 숫자 등)이나 지시문 조각이 있으면 route: 차단, reason: output_leak
가린 수 masked (예제 2에서 씀)
기대 결과 cases/lab03.json 앞 두 케이스, lab01-attacks 9/9

이해하기

출구도 관문입니다. 지금까지는 들어오는 질문을 검사했습니다. 그런데 답에는 LLM이 Tool에서 가져온 데이터가 들어갑니다. 예제 2 · Lab 02에서 본인 건이 보이게 되면서, 접수 건의 연락처가 답에 그대로 나갈 수 있게 되었습니다. 본인 것이라도 답은 화면 캡처 · 메신저 전달 · 로그 저장으로 다른 사람 눈에 닿습니다.

flowchart LR
  A{{"IT Agent<br/>Tool 결과로 답"}} --> F["Output filter<br/>Code"]
  F -->|전화 · 이메일 · 주민번호| M["가림 · masked +1"]
  F -->|IT-ADMIN- · 지시문 조각| B(["차단 · output_leak"])
  M --> O(["답"])

LLM에게 맡기면 안 되나. "연락처는 가려서 답해"라고 지시하면 대부분 지킵니다. 하지만 영어로 물으면? 번호 사이에 점을 찍어 달라고 하면? 셀 수 있는 검사는 코드가 합니다 (69일차). 전화번호 모양은 정규식으로 정확히 잡을 수 있고, 매번 같은 결과를 냅니다.

무엇을 남기고 무엇을 가리나. 전부 가리면 본인이 자기 번호인지 알아볼 수 없습니다.

개인정보 원래 가린 뒤 남긴 것
전화번호 010-1414-2135 010-****-2135 앞 3자리 · 끝 4자리
이메일 yujin.choi@example.com y***@example.com 첫 글자 · 도메인
주민등록번호 900101-1234567 900101-******* 생년월일

정규식은 하이픈 없는 번호(01014142135), 점(010.1414.2135), 띄어쓰기도 잡게 씁니다.

유출 표시가 보이면 막기. 가리는 것은 "있어도 되는데 보기 좋게"이고, 막는 것은 "있으면 안 되는 것"입니다. Lab 02 예제 2에서 지시문의 비밀을 뺐지만, 혹시 다른 곳에 남아 있다가 새어 나오면 출구에서 한 번 더 잡습니다. 이것도 겹겹이 막기입니다.

기대 결과. contact-own의 답에 ****과 ***@가 보이고, 원래 번호 · 이메일은 없습니다. lab03 앞 둘 통과(뒤 넷은 예제 2의 기록), lab01-attacks 9/9.

▶ 1단계 · LLM에게 맡기면 안 되나

"연락처는 가려서 답해"라고 지시하면 대부분 지킵니다. 하지만 영어로 물으면? 번호 사이에 점을 찍으면? 셀 수 있는 검사는 코드가 합니다.

▶ 2단계 · 무엇을 남기고 무엇을 가리나

위 표의 "남긴 것" 칸. 가린 개수(masked)를 셀 때 한 번 가릴 때마다 1씩 더합니다.

▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대

요청문에 꼭 들어갈 것: ① 넣을 자리 ② 가릴 것 셋과 가리는 모양 ③ 가린 수 ④ 막을 표시와 그때의 답 · reason ⑤ 기대 결과. 아래 빈칸 4개(____①~____④)를 채워 my-work/PROMPTS.md에 쓴 뒤 에이전트에게 보냅니다.

my-work/secure-agent.json 의 Build result 와 Send result 사이에 Output filter(Code)를 넣어 줘.
- 전화번호 /01[016789][-. ]?\d{3,4}[-. ]?\d{4}/g → 앞 ____① 자리와 끝 4자리만 남기고 가운데 *, 이메일 → ____② + ***@도메인, 주민번호 /\d{6}[- ]?[1-4]\d{6}/ → 앞 6자리-*******.
  가린 수를 ____③ 로.
- 답에 /IT-ADMIN-\d+|출입\s*번호|<question>|systemMessage/i 가 있으면 route 차단, 정해진 안내문, reason ____④ .
기대 결과 cases/lab03.json 앞 두 케이스, lab01-attacks.json.

빈칸 힌트 (답이 아니라 어디서 찾고 무엇을 생각하나):

  • ① 위 조건 표의 010-****-2135에서 앞에 남은 숫자 수입니다.
  • ② 위 이해하기 표의 이메일 줄 "남긴 것" 칸 앞부분.
  • ③ 예제 2의 security에 들어갈 가린 개수의 칸 이름입니다 (AGENTS.md 3절 security).
  • ④ 위 조건 표의 "막기" 줄 (AGENTS.md 3절 reason 값에도 있음).

채운 요청문은 수업 뒤 공개되는 정답본의 요청문 예와 비교합니다. 노드 이름은 내 파일의 실제 이름을 씁니다.

uv run tools/flow.py push my-work/secure-agent.json
uv run tools/flow.py test cases/lab03.json -v
uv run tools/flow.py test cases/lab01-attacks.json -v        # 이제 9/9인지

예제 2 · 무엇을 왜 막았는지 기록 (8분)

만들 것: 모든 결과에 security 기록: 누가 막았나(blocked_by: rule · guard · output_filter · 없으면 빈 글자), 이유(reason), 가린 개수(masked). 겉의 reason · masked는 security 안으로 옮깁니다.

조건 값
계약 AGENTS.md 3절 security
기대 결과 cases/lab03.json 6개 전체

이해하기

왜 남기나. 막은 것을 세지 않으면 관문이 일을 하는지 모릅니다. "오늘 규칙이 몇 건, 판별 LLM이 몇 건 막았나", "판별 LLM이 정상 질문을 막은 적은 없나", "연락처를 몇 번 가렸나"는 기록이 있어야 셉니다 (과제 2의 보안 요약, 77일차 실행 로그 분석).

길마다 같은 모양. 질문은 세 길 중 하나로 끝납니다. 어느 길이든 같은 모양의 security를 내놓아야 나중에 한꺼번에 셀 수 있습니다.

끝난 길 blocked_by reason masked
규칙이 막음 rule 맞은 규칙 0
판별 LLM이 막음 guard 판별 LLM의 이유 0
출력 필터가 막음 output_filter output_leak 가린 수
답함 "" (빈 글자) "" 가린 수

누가 막았는지 아는 법. 규칙과 판별 LLM은 같은 Blocked 노드로 들어옵니다. 판별 LLM 쪽 If(Blocked by guard?)가 실행됐고 막았다면 guard, 아니면 rule. n8n 표현 $('노드').isExecuted로 확인합니다 (AGENTS.md 6절).

flowchart LR
  R{"규칙"} -->|걸림| B["Blocked<br/>security: rule"]
  G{"판별 LLM"} -->|공격| B
  B -.->|"Blocked by guard? 실행됐나"| B
  A{{"IT Agent"}} --> F["Output filter"] --> S["Security record<br/>security: output_filter 또는 빈 글자"]

기록에 무엇을 남기나. 막은 문장 전체를 남기면 그 안의 개인정보도 남습니다. 오늘은 이유와 개수만 남깁니다. 문장이 꼭 필요하면 보관 기간 · 볼 수 있는 사람을 정해야 합니다 (과제 2).

기대 결과. lab03 6/6: 규칙 · 판별 LLM이 막은 기록, 가린 개수 기록, 정상은 blocked_by: "".

▶ 1단계 · 왜 남기나

막힌 질문이 하루에 몇 건인지, 판별 LLM이 정상 질문을 막은 적은 없는지는 기록이 있어야 셉니다. 막은 문장 전체를 남길까요, 이유만 남길까요?

▶ 2단계 · 길마다 같은 모양

위 표의 네 줄이 모두 같은 칸 셋을 가집니다. 답한 길의 security는 어느 노드에서 만들까요? (출력 필터 뒤)

▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대

요청문에 꼭 들어갈 것: ① 차단 길의 security(누가 · 이유 · 0) ② 누가 막았는지 아는 법 ③ 답 길의 security(출력 필터 뒤) ④ 겉의 칸 정리 ⑤ 기대 결과. 아래 빈칸 4개(____①~____④)를 채워 my-work/PROMPTS.md에 쓴 뒤 에이전트에게 보냅니다.

모든 결과에 security {blocked_by, reason, masked} 를 넣어 줘 (AGENTS.md 3절).
- Blocked(Set)에 security 객체: blocked_by 는 ____① 가 실행됐고 blocked 면 'guard', 아니면 ' ____② '. reason 은 guard 의 reason 또는 맞은 규칙. masked 0.
- Output filter 뒤 Security record(Code): route 가 차단이면 blocked_by ' ____③ ', 아니면 ____④ . reason, masked 를 security 로 옮기고 겉에서는 뺀다.
기대 결과 cases/lab03.json 전체.

빈칸 힌트 (답이 아니라 어디서 찾고 무엇을 생각하나):

  • ① 판별 LLM의 결과로 막을지 정하는 If 노드의 이름입니다 (Lab 02 과제 1에서 만든 것, 내 파일의 실제 이름).
  • ② 판별 LLM이 막지 않았는데 Blocked에 왔다면 누가 막은 것인가요? 위 이해하기 표.
  • ③ 출력 필터가 답을 막았을 때의 값입니다. AGENTS.md 3절 security.
  • ④ 아무도 막지 않았을 때의 값입니다. null이 아닙니다 (테스트의 "정상은 막지 않음 기록").

채운 요청문은 수업 뒤 공개되는 정답본의 요청문 예와 비교합니다. 노드 이름은 내 파일의 실제 이름을 씁니다.

uv run tools/flow.py push my-work/secure-agent.json
uv run tools/flow.py test cases/lab03.json -v

예제 3 · 관문마다 시간과 기록 보기 (7분, 에이전트 없이)

관문 다섯 곳에서 끝나는 요청을 하나씩 보내고, -v로 응답의 security와 시간을 봅니다.

uv run tools/flow.py test cases/lab03-layers.json -v

SECURITY.md 7절 표를 채웁니다.

이해하기

겹겹이 막기를 한 장으로. 오늘 만든 관문이 모두 들어간 지금, 요청마다 어디서 끝나는지가 다릅니다.

케이스 끝나는 곳 응답에서 볼 칸
① 빈 질문 입력 검증 HTTP 400, codes
② 알려진 공격 규칙 security.blocked_by: rule, reason
③ 새 공격 판별 LLM security.blocked_by: guard, reason
④ 내 연락처 출력 필터 (가림) security.masked, 답의 ****
⑤ 정상 질문 모두 지나감 security.blocked_by: ""

시간으로 보는 관문. 앞에서 끝날수록 빠르고 쌉니다. 입력 검증 · 규칙은 LLM 0번, 판별 LLM은 1번, 끝까지 가면 판별 + IT Agent(+ Tool)입니다. Lab 01 예제 3 · Lab 02 예제 3에서 잰 숫자와 맞는지 봅니다.

flowchart LR
  V["입력 검증<br/>LLM 0"] --> R["규칙<br/>LLM 0"] --> G["판별 LLM<br/>LLM 1"] --> A["IT Agent<br/>LLM 2 + Tool"] --> F["출력 필터<br/>코드"]

기록이 말해 주는 것. 다섯 응답의 security만 모아도 "어느 관문이 무엇을 막았나"가 보입니다. 이것을 하루치 모으면 과제 2의 보안 요약이 됩니다.

기대 결과. 5/5. 시간은 ①②가 0.1초 안쪽, ③이 1초 안팎, ④⑤가 2~3초. 테스트는 결과의 모양만 보고, 시간은 내가 적습니다.

▶ 1단계 · 표를 채우는 법

-v 출력에서 케이스 이름 줄의 시간, ← response HTTP의 상태, 응답 JSON의 route · security를 옮깁니다.

과제 1 · 어려운 보안 테스트를 쓰고 고치기 (13분)

"다음 주 보안 점검 전에, 우리가 이미 막은 것 말고 실제로 들어올 만한 이상한 질문으로 테스트를 더 만들어 주세요. 막지 말아야 할 것을 막는 것도 문제예요."

만들 것: cases/my-cases.json에 8개 이상. 반은 "막아야 할 것", 반은 "막으면 안 되는 것". 돌려서 실패를 찾고, 어느 관문을 고칠지 정해 고칩니다.

단계 시간 할 일
① 케이스 쓰기 4분 아래 다섯 종류에서 고르기, cases/lab01-challenge.json 모양을 본떠서
② 돌리기 2분 test cases/my-cases.json -v
③ 원인 말하기 2분 실패마다 어느 관문인지
④ 고치기 3분 요청문 → 에이전트
⑤ 다시 2분 --repeat 3, 다른 테스트도

이해하기

어려운 테스트는 경계에서 나옵니다. 이미 통과하는 테스트를 더 써도 배울 것이 없습니다. 테스트를 쓰는 목적은 실패를 찾는 것입니다.

종류 예 기대
① 공격을 숨기기 띄어쓰기, 전각 문자, 영어 섞기 차단
② 정상인데 공격 낱말 "비밀번호 규칙을 잊어버렸어요", "관리자 권한이 필요한 프로그램 설치" 자동 답변
③ 경계값 정확히 500자, 501자 200 / 400
④ 개인정보를 다른 말로 영어로 내 연락처, 남의 연락처 번호가 그대로 없음
⑤ 기록 막힌 질문의 security.reason에 개인정보가 없나 not_contains

②가 가장 중요합니다. Lab 01 과제 1에서 "낱말 둘 이상"으로 규칙을 만들었지만, 낱말 둘이 정상 질문에도 함께 나오는 경우가 있습니다.

실패마다 관문을 고릅니다.

flowchart LR
  F["실패한 케이스"] --> Q{"정상인데 막힘?<br/>공격인데 지나감?"}
  Q -->|정상인데 막힘| W["막은 관문<br/>security.blocked_by"] --> N["조건을 좁힘<br/>(규칙이면 정규식)"]
  Q -->|공격인데 지나감| M["어디서 막혀야 했나<br/>입력 · 규칙 · 판별 · 데이터 · 지시문 · 출력"] --> A["그 관문에 더함"]
  N --> C["다른 테스트도 다시"]
  A --> C

규칙은 확실한 것만. 지나치게 막은 규칙을 좁히면, 그 규칙이 막던 공격 일부가 지나갈 수 있습니다. 그래도 괜찮은 까닭은 판별 LLM이 뒤에서 받치기 때문입니다. 규칙은 확실한 것만, 애매한 것은 판별 LLM으로. 좁힌 뒤 lab01-challenge(알려진 공격 10)를 같이 돌려 확인합니다.

--repeat 3. 판별 LLM이 들어간 케이스는 실행마다 결과가 흔들릴 수 있습니다. 세 번 모두 통과해야 "고쳤다"고 봅니다.

기대 결과. 처음에 실패가 하나 이상 나오면 잘 쓴 테스트입니다. 고친 뒤 내 케이스 · lab01-challenge · lab02-challenge · lab03 모두 통과.

▶ 1단계 · 어디서 어려운 질문이 나오나

위 표의 다섯 종류에서 둘씩. 케이스 하나는 {"name", "body": {"question", "user"}, "expect": {...}}입니다. 500자 · 501자는 같은 문장을 되풀이해 만들고 len으로 셉니다.

▶ 2단계 · 어느 관문을 고칠까

실패마다 "입력 검증 · 규칙 · 판별 LLM · 데이터 API · 지시문 · 출력 필터" 중 어디서 막혀야 했는지(또는 어디서 잘못 막았는지) 먼저 말합니다. 정상이 막혔다면 응답 security.blocked_by와 reason이 답을 줍니다.

▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대

요청문에 꼭 들어갈 것: ① 고칠 노드와 고칠 규칙 수 ② 막힌 정상 질문과 그것을 막은 규칙 ③ 좁힌 뒤 받쳐 줄 관문 ④ 함께 돌릴 테스트. 아래 빈칸 4개(____①~____④)를 채워 my-work/PROMPTS.md에 쓴 뒤 에이전트에게 보냅니다 (막힌 정상 질문이 규칙이 아닌 곳에서 막혔다면 그 관문 이름으로 바꿔 씁니다).

my-work/secure-agent.json 의 Injection rules 에서 정상 질문을 막은 규칙 ____① 개를 좁혀 줘.
- 막힌 정상 질문: " ____② " ← 막은 규칙: (실패 줄의 security.reason 그대로)
- (막힌 것이 더 있으면 한 줄씩)
공격을 나타내는 낱말이 함께 있을 때만 걸리게. 좁힌 규칙이 놓치는 공격은 ____③ 가 뒤에서 받친다.
cases/my-cases.json, lab01-challenge, ____④ , lab03 모두 통과.

빈칸 힌트 (답이 아니라 어디서 찾고 무엇을 생각하나):

  • ① test cases/my-cases.json -v에서 정상 케이스인데 security.blocked_by: rule로 실패한 수입니다.
  • ② 그 실패 케이스의 질문 문장 그대로입니다.
  • ③ 규칙 뒤에 있는 관문 중 처음 보는 말도 알아보는 것입니다 (Lab 02 과제 1).
  • ④ 판별 LLM이 새 공격 8개 · 정상 10개를 그대로 막고 통과시키는지 보는 테스트입니다.

채운 요청문은 수업 뒤 공개되는 정답본의 요청문 예와 비교합니다. 노드 이름은 내 파일의 실제 이름을 씁니다.

uv run tools/flow.py test cases/my-cases.json -v                 # 처음
uv run tools/flow.py push my-work/secure-agent.json
uv run tools/flow.py test cases/my-cases.json -v --repeat 3
uv run tools/flow.py test cases/lab01-challenge.json -v
uv run tools/flow.py test cases/lab02-challenge.json -v
uv run tools/flow.py test cases/lab03.json -v

SECURITY.md 8절, 테스트 보고서를 my-work/TEST-REPORT.md로.

과제 2 · 감사 로그와 하루 보안 요약 (12분)

"보안팀이 매일 아침 '어제 몇 건을 막았고 누가 막혔는지' 알고 싶대요. 그런데 질문 내용이나 연락처가 로그에 쌓이면 그게 또 개인정보 문제가 된대요."

만들 것: ① 200으로 끝나는 모든 결과(차단 · 답)마다 감사 로그를 데이터 API에 한 줄 남긴다 (질문 · 답 글자와 연락처는 남기지 않음). ② 그 로그를 세어 하루 요약을 돌려주는 보안 요약 워크플로우를 새로 만든다.

조건 값
로그 POST http://data-api:8800/audit 본문 {user, route, blocked_by, reason, masked, tools_used} (AGENTS.md 2절 · 5절)
남기지 않을 것 질문 · 답 글자, 연락처, 400(입력 검증)
기록이 실패하면 사용자 답은 그대로 나감
보안 요약 my-work/security-report.json · 웹훅 my-security-report · 결과 칸은 AGENTS.md 3절
기대 결과 cases/lab03-challenge2.json 5개 (로그를 비우고 4건 보낸 뒤 요약), lab03도 그대로
uv run tools/flow.py test cases/lab03-challenge2.json -v        # 고치기 전: 요약 웹훅이 없음 (404)
curl -s localhost:8800/audit; echo                               # 지금 로그 (비어 있음)

이해하기

security 기록과 감사 로그. 예제 2의 security는 응답 하나 안에 있습니다. 응답은 사용자에게 가고 사라집니다. 감사 로그(audit log)는 모든 응답의 기록을 한곳에 쌓아 나중에 세고 찾아보는 것입니다.

flowchart LR
  S["Security record<br/>(답 길)"] --> R1(["Send result"])
  S --> L["Log event<br/>POST /audit"]
  B["Blocked<br/>(차단 길)"] --> R2(["Send blocked"])
  B --> L
  L --> D[("데이터 API<br/>감사 로그")]
  H(["my-security-report"]) --> G["GET /audit"] --> C["Summarize<br/>세기 · pii_in_log"] --> O(["하루 요약"])
  D -.-> G

한 노드에서 두 곳으로. Blocked → Send blocked와 Log event 둘 다로 잇습니다. 답은 보내고, 기록도 남깁니다. 기록 HTTP가 실패해도 사용자 답은 이미 나갔거나 그대로 나가야 하므로 onError: continueRegularOutput, neverError를 둡니다 (기록 때문에 서비스가 멈추면 안 됨).

로그에 무엇을 남기나 (데이터 최소화).

남김 남기지 않음 까닭
user(직원 이름), route, blocked_by, reason, masked, tools_used 질문 · 답 글자 질문 · 답 안에 연락처 · 주민번호가 있을 수 있음
연락처 가렸어도 로그에 또 남길 까닭이 없음

"누가 막혔나"는 알아야 하므로 직원 이름은 남깁니다. 개인정보보호법의 "필요한 최소한"은 목적에 필요한 것만 모은다는 뜻입니다. 보안 요약의 목적은 "몇 건, 누가, 어느 관문"이므로 질문 글자는 필요 없습니다.

스스로 점검하는 pii_in_log. 요약 워크플로우가 로그 전체를 글자로 바꿔 전화 · 이메일 · 주민번호 모양을 셉니다. 0이 아니면 어딘가에서 개인정보가 로그에 들어간 것입니다. 지켜야 할 규칙을 테스트 가능한 숫자로 만든 것입니다.

테스트의 순서. before로 로그를 비우고(/audit-clear) → 규칙 차단 · 판별 차단 · 내 연락처 · 정상 4건 → 다섯째 케이스가 my-security-report를 불러 total 4, answered 2, blocked.rule 1, blocked.guard 1, pii_in_log 0을 기대합니다.

기대 결과. 고치기 전에는 요약 웹훅이 없어 다섯째가 404(✘). 고친 뒤 5/5, lab03 6/6. curl -s localhost:8800/audit로 로그를 직접 열어 질문 글자가 없는지도 눈으로 봅니다.

▶ 1단계 · 어디에 이을까

200으로 끝나는 길은 둘(답 · 차단)입니다. 각 길에서 security가 이미 만들어진 노드는? 그 노드에서 Respond와 Log event로 둘 다 잇습니다.

▶ 2단계 · 요약은 어떻게 세나

GET /audit의 events 배열을 돌며 blocked_by별로 셉니다. 빈 글자는 answered. pii_in_log는 JSON.stringify(events)에 Output filter와 같은 정규식을 씁니다.

▶ 빈칸 채우기 · 요청문(프롬프트) 뼈대

요청문에 꼭 들어갈 것: ① 로그 노드와 이을 자리 ② 로그 본문 칸과 남기지 않을 것 ③ 기록이 실패해도 ④ 새 요약 워크플로우(웹훅 · 세기 · pii_in_log) ⑤ 테스트와 push. 아래 빈칸 4개(____①~____④)를 채워 my-work/PROMPTS.md에 쓴 뒤 에이전트에게 보냅니다.

1) my-work/secure-agent.json: Security record 와 Blocked 뒤에 Log event(HTTP POST http://data-api:8800/ ____① )를 하나 더 이어 줘 (답을 보내는 노드는 그대로).
   본문 {user, route, blocked_by, reason, masked, tools_used}. 질문 · 답 글자와 연락처는 ____② . neverError, onError continueRegularOutput.
2) my-work/security-report.json 을 새로 (웹훅 my-security-report): GET http://data-api:8800/audit → Summarize(Code):
   total, answered(blocked_by 가 빈 것), blocked{rule, guard, output_filter}, masked 합, blocked_users,
   pii_in_log(events 를 JSON 글자로 바꿔 전화 · 이메일 · 주민번호 모양을 센 수) → Send report.
기대 결과 cases/lab03-challenge2.json ( ____③ 로 로그를 비운 뒤 4건 → 보안 요약), cases/lab03.json 도 통과.
두 파일 모두 ____④ 한 뒤 테스트한다.

빈칸 힌트 (답이 아니라 어디서 찾고 무엇을 생각하나):

  • ① AGENTS.md 2절 데이터 API 표에서 감사 로그 한 줄을 남기는 경로입니다.
  • ② 위 이해하기 "로그에 무엇을 남기나" 표의 오른쪽 칸. 남길까요, 남기지 않을까요?
  • ③ cases/lab03-challenge2.json의 맨 위 before에 있는 데이터 API 경로입니다.
  • ④ 워크플로우 파일을 n8n에 올리고 게시하는 flow.py 명령입니다. 파일 둘을 한 번에 줄 수 있습니다.

채운 요청문은 수업 뒤 공개되는 정답본의 요청문 예와 비교합니다. 노드 이름은 내 파일의 실제 이름을 씁니다.

uv run tools/flow.py push my-work/secure-agent.json my-work/security-report.json
uv run tools/flow.py test cases/lab03-challenge2.json -v
uv run tools/flow.py test cases/lab03.json -v
curl -s localhost:8800/audit; echo                                # 질문 글자가 없는지 눈으로

SECURITY.md 9 · 10절.

막혔을 때

증상 할 일
번호가 그대로 나감 정규식이 하이픈 없는 번호(01014142135) · 점 · 띄어쓰기도 잡는지
가렸는데 masked가 0 가릴 때마다 세는지, 다음 노드로 넘기는지
security.blocked_by가 null 그 길의 마지막 노드가 security를 만드는지 (차단 길 · 답 길 모두)
고쳤더니 다른 테스트가 깨짐 규칙을 좁히면 알려진 공격이 지나갈 수 있음. lab01-challenge를 같이
보안 요약 total이 4보다 큼 테스트 before의 /audit-clear가 돌았는지, 내가 손으로 보낸 요청이 섞였는지 (테스트를 다시)
pii_in_log가 0이 아님 Log event 본문에 answer나 question이 들어갔는지
Log event가 답을 막음 onError: continueRegularOutput, Respond와 Log event가 나란히 이어졌는지
계속 안 됨 reference/lab03-example1-filter.json, reference/lab03-example2-record.json

확인

  • 예제 1: cases/lab03.json 앞 두 케이스, lab01-attacks 9/9.
  • 예제 2: cases/lab03.json 6/6.
  • 예제 3: cases/lab03-layers.json 5/5, SECURITY.md 7절 (관문별 시간과 기록).
  • 과제 1: 내 케이스 8개 이상(막을 것 · 막지 말 것), 실패를 1개 이상 찾아 고쳤고 lab01-challenge · lab02-challenge · lab03도 통과.
  • 과제 2: cases/lab03-challenge2.json 5/5, 로그에 질문 글자 없음, SECURITY.md 9절.
  • SECURITY.md, TEST-REPORT.md.