테스트 도구의 실행 로그에 개인키뿐 아니라 개인키를 복원하는 데 쓰일 수 있는 ECDSA nonce도 나타나지 않게 했고, 입력 해석이 실패해도 원문을 그대로 출력하지 않도록 바꿨다.
Foundry의 cheatcode는 테스트 중 서명 생성, 시간 변경, storage 조작 같은 기능을 제공한다. forge를 상세 추적 모드로 실행하면 함수 이름과 인자가 trace에 표시되는데, 디버깅에는 편리하지만 서명 함수의 개인키가 그대로 보이면 위험하다.
ECDSA 서명에는 서명마다 사용하는 비밀 난수 k가 있다. 같은 k를 재사용하거나 k가 알려지면, 공개된 서명값과 메시지 digest를 이용해 개인키를 계산할 수 있다. Foundry의 signWithNonceUnsafe는 테스트를 위해 사용자가 이 nonce를 직접 지정할 수 있는 함수다. 따라서 여기서 nonce는 단순한 디버깅 숫자가 아니다.
Foundry는 2026-07-31 커밋에서 signWithNonceUnsafe의 첫 번째 인자인 private key뿐 아니라 세 번째 인자인 nonce도 trace에서 <nonce>로 대체했다. 알려진 서명 cheatcode selector인데 calldata가 잘렸거나 형태가 잘못되어 ABI decoding에 실패하면, 기존처럼 raw calldata 출력으로 돌아가지 않고 전체 인자를 <redacted>로 표시한다.
출처: https://github.com/foundry-rs/foundry/commit/77474138ca032f4dc297fd8888f6b4ecf7dd6df2
실제 Rust diff의 핵심을 단순화한 의사코드다.
이전:
args = abiDecode(calldata)
if decode fails:
return renderRawCalldata(calldata)
args.privateKey = "<pk>"
변경:
if selector belongsToSensitiveCheatcode:
args = abiDecode(calldata)
if decode fails:
return ["<redacted>"]
args.privateKey = "<pk>"
if function == signWithNonceUnsafe:
args.nonce = "<nonce>"
CI 로그, 테스트 실패 기록, 협업 도구에 trace가 복사되면 민감 정보가 저장소 밖으로 퍼질 수 있다. 특히 ‘디코딩 실패 시 원문 출력’은 정상 경로의 마스킹을 우회하는 예외 경로가 된다. 이번 변경은 알려진 민감 함수라면 해석에 실패할수록 더 적게 보여 주는 fail-closed 원칙을 적용한다.
다만 이 기능이 모든 로그와 외부 프로그램의 비밀 유출을 막아 준다는 뜻은 아니다. 개발자는 실제 자산이 있는 개인키를 테스트에 사용하지 않아야 한다.
쉬운 비유
문서의 주민등록번호 칸을 가리는 시스템이 양식 오류를 만나면 원본 문서 전체를 보여 주던 문제를, 오류가 날수록 해당 칸 전체를 가리도록 고친 것과 같다.
개발 도구가 강력해질수록 키·서명·세션 권한 같은 비밀을 취급하는 경로도 늘어난다. 스마트컨트랙트 보안은 온체인 코드 감사만이 아니라 테스트 로그와 CI artifact를 포함하는 개발 공급망 보안으로 넓어지고 있다.
1차 출처: https://github.com/foundry-rs/foundry/commit/77474138ca032f4dc297fd8888f6b4ecf7dd6df2