FinHarness: An Inline Lifecycle Safety Harness for Finance LLM Agents
TL;DR Highlight
금융 AI 에이전트가 실행 중간에 위험한 툴 호출을 차단하면서도 정상 승인율을 유지하는 인라인 안전 프레임워크
Who Should Read
금융 도메인 AI 에이전트를 개발하거나 운영하는 백엔드/ML 엔지니어. 특히 프롬프트 인젝션(외부 악의적 명령 삽입)이나 자금 이체 같은 비가역적 툴 호출의 보안이 걱정되는 개발자.
Core Mechanics
- 기존 보안 방식은 두 가지 문제가 있음: 경계 필터(Boundary Filter)는 대화 입출력만 보고 중간 툴 호출을 못 보고, 사후 LLM 심판(Post-hoc Judge)은 에이전트가 이미 종료된 뒤에 감사해서 이미 늦음
- FINHARNESS는 3개 컴포넌트로 구성: Query Monitor(쿼리 위험도 계산), Tool Monitor(툴 호출 사전 평가), Cascade(위험 누적값 기반으로 저렴한 gpt-4o-mini vs 고급 gpt-4o 판단 라우팅)
- 핵심 아이디어는 '누적 위험'임 - 각 단계에서는 위험 점수가 낮아 보여도 슬라이딩 윈도우(최근 5스텝 합산)로 누적 신호를 감지해 조작 시도를 잡아냄
- 위험 신호가 감지되면 에이전트를 강제 차단하는 대신, fired된 규칙 헤드를 에이전트 입력에 '증거'로 다시 주입해서 에이전트가 스스로 거부/재계획/에스컬레이션 하도록 유도함(Self-rejection)
- Query Monitor는 단일 턴 의도(Q1~Q5)와 크로스 턴 드리프트(D1~D5) 두 가지 신호를 LLM 호출 없이 결정론적으로 계산해 비용 절감
- Selective Episodic Memory로 판단 컨텍스트를 최대 2개 과거 스텝으로 제한해서, 기존 풀 히스토리 방식처럼 O(t)로 비용이 늘어나는 걸 방지
Evidence
- FINVAULT 벤치마크에서 ASR(공격 성공률)을 38.3% → 15.0%로 낮추면서 정상 승인율은 41.1% → 39.3%로 거의 유지 (Net 점수 +2.8 → +24.3)
- 라우팅 FINHARNESS는 항상 고급 판사(gpt-4o)를 쓰는 ablation 대비 고급 판사 호출 수를 646회 → 138회로 4.7배 절감
- 856개 합성 공격 트레이스에서 B2 대비 에이전트 자가 거부(Self-rejection)가 +15.7pp 증가, 능동적 차단(하드스톱+자가거부+에스컬레이션)이 +6.7pp 증가
- 감정적 조작 공격 패밀리에서 ASR을 62.5% → 18.8%로 -43.8pp 감소, 문서 위조 공격은 -25.0pp 감소
How to Apply
- 금융 에이전트에 툴 호출 권한 티어(읽기/쓰기/오버라이드)를 정의하고, 각 티어에 수동 위험 점수(예: 읽기=0.10, 중요 쓰기=0.80)를 매긴 뒤 Tool Monitor로 래핑하면 LLM 호출 없이 1차 필터링이 가능함
- 슬라이딩 윈도우(W=5, θ=1.0)로 최근 5스텝 위험 점수 합산을 추적하다가 임계값 초과 시에만 gpt-4o 같은 고급 모델을 호출하면, 항상 고급 모델을 쓰는 것 대비 비용을 약 4.7배 절감하면서 비슷한 보안 수준 달성 가능
- 에이전트를 강제 차단하는 대신 위험 신호를 '증거' 형태로 시스템 프롬프트에 주입하는 패턴을 적용하면, 에이전트가 자율적으로 거부하거나 인간 에스컬레이션을 선택하게 되어 false positive로 인한 정상 요청 차단을 줄일 수 있음
Code Example
# FINHARNESS의 Fired-Signal Dynamic Injection 패턴 예시
# 위험 신호를 에이전트 프롬프트에 주입하는 방법
def build_agent_prompt_with_evidence(base_prompt: str, fired_heads: dict) -> str:
"""
fired_heads 예시:
{
'turn_k': 3,
'query_fired': ['Q4_coercion(0.85)', 'D1_false_reference(0.85)'],
'tool_fired': ['H1_perm(0.80)', 'H2_param(0.70)', 'H5_seq(0.55)'],
'step_signals': {'s_t': 0.85, 'window_sum': 1.12, 'C_query': 0.85}
}
"""
if not fired_heads:
return base_prompt
evidence_block = f"""
[SAFETY EVIDENCE - Turn {fired_heads['turn_k']}]
Fired query signals: {', '.join(fired_heads['query_fired'])}
Fired tool signals: {', '.join(fired_heads['tool_fired'])}
Risk metrics: s_t={fired_heads['step_signals']['s_t']},
window_sum={fired_heads['step_signals']['window_sum']},
C_query={fired_heads['step_signals']['C_query']}
Based on the above evidence, you may:
- REFUSE the request if you assess it as unauthorized
- RE-PLAN with additional verification steps
- ESCALATE to human review
- APPROVE if you assess the evidence as a false positive
"""
return base_prompt + evidence_block
# Risk Window 구현 예시
from collections import deque
class RiskWindow:
def __init__(self, W=5, theta=1.0):
self.W = W
self.theta = theta
self.scores = deque(maxlen=W)
def add_step_score(self, score: float) -> str:
"""Returns 'advanced' or 'cheap' judge tier"""
self.scores.append(score)
window_sum = sum(self.scores)
if window_sum > self.theta:
return 'advanced' # gpt-4o
return 'cheap' # gpt-4o-mini
# 사용 예시
window = RiskWindow(W=5, theta=1.0)
step_scores = [0.12, 0.12, 0.12, 0.12, 0.12] # 각 단계 낮은 점수
for score in step_scores:
tier = window.add_step_score(score)
print(f"Score: {score}, Window sum: {sum(window.scores):.2f}, Tier: {tier}")
# Score: 0.12, Window sum: 0.12, Tier: cheap
# Score: 0.12, Window sum: 0.24, Tier: cheap
# Score: 0.12, Window sum: 0.36, Tier: cheap
# Score: 0.12, Window sum: 0.48, Tier: cheap
# Score: 0.12, Window sum: 0.60, Tier: cheap
# (합계 0.60 < 1.0이므로 cheap 유지)
# 하지만 누적되면 결국 advanced로 escalate됨Terminology
Related Papers
Retrying vs Resampling in AI Control
Claude Code처럼 의심 행동을 막고 재시도하는 방식이 오히려 공격자에게 힌트를 줘서 더 위험할 수 있다는 연구.
Mitigating Provenance-Role Collapse in Long-Term Agents via Typed Memory Representation
LLM 에이전트의 장기 메모리가 출처를 뒤섞는 문제를 '타입이 있는 메모리 원자' 구조로 해결한 논문
Push Your Agent: Measuring and Enforcing Quantitative Goal Persistence in Long-Horizon LLM Agents
LLM 에이전트가 '100개 찾아줘'를 실제로 100개 찾을 때까지 멈추지 않게 만드는 방법과 벤치마크.
CoSPlay: Cooperative Self-Play at Test-Time with Self-Generated Code and Unit Test
Ground Truth 없이도 코드와 Unit Test가 서로 평가하며 함께 품질을 높이는 추론 시간 최적화 프레임워크
Multi-Stream LLMs: new paper on parallelizing/separating prompts, thinking, I/O
현재 LLM이 입력·사고·출력을 순차적으로만 처리하는 구조적 한계를 지적하고, 각 역할을 별도의 병렬 스트림으로 분리해 동시에 처리할 수 있는 Multi-Stream 방식을 제안한 논문이다. 에이전트의 효율성·보안·모니터링 가능성을 모두 개선할 수 있다는 점에서 주목받고 있다.
HarnessAPI: A Skill-First Framework for Unified Streaming APIs and MCP Tools
FastAPI HTTP 엔드포인트와 MCP 도구를 하나의 폴더에서 자동으로 동시에 만들어주는 Python 프레임워크
Related Resources
Original Abstract (Expand)
Finance LLM agents must simultaneously block prompt-induced unauthorized actions and approve legitimate multi-step business workflows. However, boundary filters often miss irreversible mid-trajectory tool calls, while post-hoc LLM judges perform auditing only after termination -- too late for intervention and at a computational cost that scales linearly with trace length. We present FinHarness, an inline safety harness that wraps a finance agent end-to-end with three components: a Query Monitor that fuses single-turn intent with cross-turn drift, a Tool Monitor that evaluates each prospective tool call, and a Cascade module that integrates per-step risk and adaptively routes verification between a lightweight and an advanced-tier LLM judge. Fired risk factors are re-injected into the agent input as ex-ante evidence, enabling the agent to refuse, re-plan, or approve on its own. On FinVault, routed FinHarness cuts ASR from 38.3% to 15.0% while largely preserving benign approval ($41.1\% \to 39.3\%$), and uses $4.7\times$ fewer advanced-judge calls than an always-advanced ablation.