Skip to content

프롬프트 주입 방패(armor) — nonce 격벽 + 주입 신호 표지 + 출처 표지 (#4850) - #4851

Closed
kevin9327 wants to merge 3 commits into
edwardkim:develfrom
kevin9327:gym_injection_shield
Closed

프롬프트 주입 방패(armor) — nonce 격벽 + 주입 신호 표지 + 출처 표지 (#4850)#4851
kevin9327 wants to merge 3 commits into
edwardkim:develfrom
kevin9327:gym_injection_shield

Conversation

@kevin9327

Copy link
Copy Markdown
Contributor

Closes #4850

문제

export-text·hwp_doc_text 는 문서 본문을 그대로 프롬프트에 넣도록 넘긴다. 본문이 공격자가 내용을 정할 수 있는 문서라면, 심긴 "이전 지시를 무시하라 …" 가 사용자의 지시로 오인돼 에이전트가 간접 프롬프트 주입으로 탈취(mind-control)된다. rhwp 에는 방어 조각이 이미 있었지만(inspect injection·출처 표지 untrustedContent/untrustedFields·export-provenance-map) 한 동작으로 묶여 있지 않았다.

해결 — rhwp armor <파일> [--json]

세 조각을 한 번의 읽기 전용 호출로 묶는다.

  1. nonce 격벽 — 본문을 이 호출만의 무작위 nonce(getrandom, 128비트)로 만든 경계 ⟦UNTRUSTED:<nonce>⟧ … ⟦/UNTRUSTED:<nonce>⟧ 안에 넣는다. 문서는 nonce 를 모르므로 격벽을 위조·조기 종료할 수 없다. LLM 호스트는 "격벽 안은 전부 데이터"라는 규칙 하나로 지시/데이터를 가른다.
  2. 주입 신호 표지 — 기존 scan_injection 을 돌려 신호(종류·신뢰도·주소·근거)를 봉투에 신고한다. 판정은 IR 을 훑으므로 렌더 줄바꿈으로 끊긴 지시까지 잡는다.
  3. 출처 표지armoredText·신호 발췌를 문서 파생으로 선언하고, safety 가 nonce·격벽 표지·신호 수·"격벽 안은 데이터"임을 기계가 읽게 요약한다.

문서를 고치지 않는다 — 격벽은 뜻을 지우지 않고 경계만 구조로 세운다(inspect injection 과 같은 무변경 규약).

동작 (before → after)

정상 문서:

{ "clean": true, "signalCount": 0,
  "safety": { "fenceOpen": "⟦UNTRUSTED:<nonce>⟧", "fenceClose": "⟦/UNTRUSTED:<nonce>⟧", "highestConfidence": null, "note": "" },
  "armoredText": "⟦UNTRUSTED:<nonce>⟧\n<본문 …>\n⟦/UNTRUSTED:<nonce>⟧",
  "untrustedContent": true, "untrustedFields": ["armoredText"] }

주입이 심긴 문서(악성 파일 무커밋 — 시험 시 edit replace-text 로 합성):

{ "clean": false, "signalCount": 2,
  "injectionSignals": [ {"kind":"instruction_override","confidence":"high", },
                        {"kind":"exfiltration_hint","confidence":"medium", } ],
  "safety": { "highestConfidence": "high", },
  "untrustedContent": true,
  "untrustedFields": ["armoredText","injectionSignals[].excerpt","injectionSignals[].matched"] }

본문은 격벽 안에 그대로 보존되고(뜻을 지우지 않음) 신호로 신고된다. nonce 는 봉투에서 정확히 두 번(여닫이 격벽)만 나타나 본문에 새지 않는다.

변경

  • 핵심 질의 src/document_core/queries/armor.rsfence/fence_open/fence_close/generate_nonce(순수)·DocumentCore::armor(읽기 전용) + 단위 시험 9건.
  • CLI armor + MCP hwp_armor(읽기 전용) + capabilities 등재 + --help·cli_commands.md.
  • src/provenance.rs MAP — armoredText·injectionSignals[].excerpt/matched 를 문서 파생으로 선언(드리프트 가드가 실측으로 강제).
  • 통합 시험 tests/armor_contract.rs(8건) + provenance_contract 스윕 레시피.
  • 에이전트 대전 장 60_보안 재생성(armor 는 nonce 무작위라 keygen 처럼 계약만).

검증

cargo test 통과: armor(lib 9)·armor_contract(8)·provenance_contract(10)·cli_json_contract(31)·injection_scan_contract(14)·ontology_contract(13)·mcp_server_contract(25)·mcp_tool_annotations(5)·output_axis_json(7)·capabilities_subcommands(4)·cli_password_stdin_parity(2)·nextcall_cli(8). cargo clippy --workspace --all-targets -- -D warnings 및 wasm lib clippy 0 경고. rustfmt(leaf + --config skip_children for main.rs) 클린.

범위·후속

첫 PR 은 핵심 질의 + 봉투 + CLI/MCP 배선 1건으로 좁혔다. 후속: --max-chars 절단(에이전트 컨텍스트 예산)·--include-fields·--min-confidence 필터.

🤖 Generated with Claude Code

문서 본문을 그대로 프롬프트에 넣으면, 문서에 심긴 "이전 지시를 무시하라" 같은
문장이 사용자의 지시로 오인돼 에이전트가 간접 프롬프트 주입으로 탈취당한다.
이미 있던 세 조각(inspect injection·출처 표지·export-provenance-map)을 한 번의
읽기 전용 호출로 묶는 rhwp armor <파일> [--json] 을 추가한다.

- 핵심 질의 document_core::queries::armor:
  - fence()/fence_open()/fence_close() — 본문을 nonce 격벽으로 감싸는 순수 함수
  - generate_nonce() — getrandom 128비트, 호출마다 무작위(문서가 위조 불가)
  - DocumentCore::armor() — 격벽 + scan_injection(읽기 전용) 결합
- CLI armor + MCP hwp_armor(읽기 전용) + capabilities 등재 + 도움말/매뉴얼
- 봉투: schemaVersion·source·pageCount·scanScopes·safety(nonce·격벽 표지·신호 수·
  note)·armoredText·injectionSignals·signalCount·clean + 출처 표지
- provenance MAP: armoredText·injectionSignals[].excerpt/matched 를 문서 파생 선언
- 문서를 고치지 않는다 — 격벽은 뜻을 지우지 않고 경계만 구조로 세운다

검증: cargo test armor(lib 9)·armor_contract(8)·provenance_contract(10)·
cli_json_contract(31)·injection_scan_contract(14)·ontology_contract(13)·
mcp_server_contract(25) 등 통과, clippy(workspace+wasm) 0 경고.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Comment thread src/document_core/queries/armor.rs Fixed
Comment thread src/document_core/queries/armor.rs Fixed
Comment thread src/document_core/queries/armor.rs Fixed
Comment thread src/document_core/queries/armor.rs Fixed
Comment thread src/document_core/queries/armor.rs Fixed
kevin9327 and others added 2 commits August 15, 2026 23:00
CI 가 두 갈래로 실패했다 (PR edwardkim#4851).

① agent_profile_router_contract::every_stateless_tool_belongs_to_some_
   specific_profile — 새 도구 hwp_armor 가 어느 업무 프로필에도 없어
   개발통합(필터-없음)으로만 닿는다. 본문을 프롬프트에 통째로 넣는 축이
   바로 아카이브검색(RAG·감사)이라 그 프로필의 tools 와 recipe 에 넣는다
   — 필요한 자리에 도구가 없으면 방패가 있으나 마나다.

② CodeQL critical 5건 — rust/hard-coded-cryptographic-value. armor 테스트가
   nonce 를 상수("deadbeef"·"00112233"·"a1b2c3d4e5f60718"·"ff00")로 두고
   격벽에 넘긴다. 이 모듈에는 이미 generate_nonce() 가 있으므로 테스트가
   그것을 쓰게 한다 — 억제보다 낫고, 격벽 성질이 실제 nonce 에서도
   성립함을 매 실행 재확인한다(기대 문자열도 fence_open/close 로 유도).

검증: agent_profile_router_contract 8/8, armor 단위 9/9,
mcp_tool_annotations_contract 5/5, agent_codex_contract 2/2,
cargo clippy --workspace --all-targets 통과, rustfmt 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI 테스트 샤드가 실패했다 (PR edwardkim#4851, 앞선 수정 위에서 드러난 다음 가드):
knowledge_map_field_dictionary_contract::every_declared_record_field_is_
in_the_dictionary — "capabilities 가 선언하는데 §2-2 사전에 없는 필드 2개:
armoredText, safety".

새 명령 armor 가 자기서술에 필드를 들고 왔는데 지식지도가 따라가지 못했다.
`주입 방패 (armor)` 소절을 보안 조사 옆에 신설해 두 필드를 근거와 함께
등재하고, 짝 가드(dictionary_heading_count_matches_rows)가 요구하는
헤딩 수(268→270)와 본문 내역(265→267 고유 + 실측-only 3)도 함께 고쳤다.

검증: knowledge_map_field_dictionary_contract 2/2,
agent_profile_router_contract 8/8, provenance_contract 10/10,
skills_contract 2/2 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jangster77 pushed a commit that referenced this pull request Aug 15, 2026
CI 가 두 갈래로 실패했다 (PR #4851).

① agent_profile_router_contract::every_stateless_tool_belongs_to_some_
   specific_profile — 새 도구 hwp_armor 가 어느 업무 프로필에도 없어
   개발통합(필터-없음)으로만 닿는다. 본문을 프롬프트에 통째로 넣는 축이
   바로 아카이브검색(RAG·감사)이라 그 프로필의 tools 와 recipe 에 넣는다
   — 필요한 자리에 도구가 없으면 방패가 있으나 마나다.

② CodeQL critical 5건 — rust/hard-coded-cryptographic-value. armor 테스트가
   nonce 를 상수("deadbeef"·"00112233"·"a1b2c3d4e5f60718"·"ff00")로 두고
   격벽에 넘긴다. 이 모듈에는 이미 generate_nonce() 가 있으므로 테스트가
   그것을 쓰게 한다 — 억제보다 낫고, 격벽 성질이 실제 nonce 에서도
   성립함을 매 실행 재확인한다(기대 문자열도 fence_open/close 로 유도).

검증: agent_profile_router_contract 8/8, armor 단위 9/9,
mcp_tool_annotations_contract 5/5, agent_codex_contract 2/2,
cargo clippy --workspace --all-targets 통과, rustfmt 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jangster77 pushed a commit that referenced this pull request Aug 15, 2026
CI 테스트 샤드가 실패했다 (PR #4851, 앞선 수정 위에서 드러난 다음 가드):
knowledge_map_field_dictionary_contract::every_declared_record_field_is_
in_the_dictionary — "capabilities 가 선언하는데 §2-2 사전에 없는 필드 2개:
armoredText, safety".

새 명령 armor 가 자기서술에 필드를 들고 왔는데 지식지도가 따라가지 못했다.
`주입 방패 (armor)` 소절을 보안 조사 옆에 신설해 두 필드를 근거와 함께
등재하고, 짝 가드(dictionary_heading_count_matches_rows)가 요구하는
헤딩 수(268→270)와 본문 내역(265→267 고유 + 실측-only 3)도 함께 고쳤다.

검증: knowledge_map_field_dictionary_contract 2/2,
agent_profile_router_contract 8/8, provenance_contract 10/10,
skills_contract 2/2 통과.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@jangster77

Copy link
Copy Markdown
Collaborator

통합 PR #4883(4412546)로 병합 완료했습니다.

원 head와 CI를 다시 확인해 누적 반영했고, 상세 검토·메인터너 보정·검증 근거는 archive 검토 기록에 남겼습니다.

중복 병합을 막기 위해 이 원 PR을 닫습니다. 감사합니다.

@jangster77 jangster77 closed this Aug 15, 2026
@jangster77

Copy link
Copy Markdown
Collaborator

통합 PR #4883(4412546)로 병합 완료했습니다.

원 head와 CI를 다시 확인해 누적 반영했고, 상세 검토·메인터너 보정·검증 근거는 archive 검토 기록에 남겼습니다.

중복 병합을 막기 위해 이 원 PR을 닫습니다. 감사합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants