09. 보안 고려 사항
원칙
- 키를 만지지 않는다. 서버 코드 어디에도 개인키·니모닉·서명 로직이 없다. 서명은 100% 지갑 앱에서 일어난다.
- 클라이언트 입력을 신뢰하지 않는다. 거래 기록 API는 tx hash만 받고 나머지는 체인에서 읽는다.
- 읽기 전용 백엔드. 백엔드는 체인에 트랜잭션을 보내지 않는다(
eth_call,eth_getLogs, 영수증 조회만).
위협과 대응
| 위협 | 대응 |
|---|---|
| 가짜 거래 내역 주입 | POST는 hash만 수용, 영수증·컨트랙트 주소·Transfer 토픽 검증, status==1 확인 |
| 잘못된 컨트랙트로 전송 유도(피싱) | 토큰 주소는 env로 고정, UI에 전체 주소와 BscScan 링크 표시 |
| 잘못된 네트워크에서 서명 | chainId !== 56이면 전송 비활성 + 전환 버튼 |
| 데스크톱 접근 | 서버 UA 게이트(403) + 클라이언트 터치/포인터 게이트 |
| CORS 남용 | 허용 오리진 화이트리스트, 메서드 GET/POST만 |
| RPC 남용/DoS | POST per-IP 레이트리밋(429), ACN 로그가 없는 tx는 블록 조회 생략, 인덱서 배치 제한, 프로덕션은 전용 RPC + 리버스 프록시 레이트리밋 |
| 정수 정밀도 손실 | wei는 문자열(JSON)/bigint(TS)/int(Py)로만 처리 |
| 주소 입력 오류 | isAddress 체크섬 검증, 자기 자신 전송 차단, 단축 주소 + 전체 주소 링크 표시 |
알려진 한계
- UA 기반 게이트는 위조 가능하다. "모바일 전용"은 UX 정책이지 보안 경계가 아니다. 자산 안전은 지갑 서명 모델이 보장한다.
- 인덱서는
head - 15블록까지만 색인해 일반적인 reorg를 회피하지만, 블록 해시를 검증하지는 않는다. 15블록을 넘는 비정상 reorg가 발생하면indexer_state를 되감아 재색인해야 한다. POST /api/transfers도INDEXER_CONFIRMATIONS이상 확정된 영수증만 저장한다. 그래도 내역은 표시용이며 자산 상태의 근거로 쓰지 않는다.- 데스크톱 UA를 보내는 iPadOS Safari는 서버 게이트에서 차단된다. 이는 "절대 모바일 전용" 정책에 따른 의도된 동작이다.
POST /api/transfers에는 프로세스 내 per-IP 레이트리밋(분당 10회)이 있다. 단일 프로세스 기준이므로 다중 워커/다중 인스턴스 운영 시에는 리버스 프록시(Nginxlimit_req) 제한을 추가한다. GET 엔드포인트는 프록시 제한에 의존한다.- 공용 RPC 장애 시 잔액 조회(지갑 provider 경유)는 되지만 내역 API는 실패한다. UI는 이를 분리해 표시한다.
운영 체크리스트
- HTTPS 강제, HSTS
- 백엔드에 레이트리밋(Nginx
limit_req또는 slowapi) - 의존성 취약점 점검:
npm audit,pip-audit -
.env,*.db가 저장소에 없는지 확인 (.env의DATABASE_URL에 DB 비밀번호가 들어 있다) -
pg_dump백업(/var/lib/acn/backup/)이 root 전용인지 확인 — 덤프에 비밀번호 해시와 TOTP 비밀키가 들어 있다 - 컨트랙트 소스 검증 여부와 owner 권한(mint/pause/blacklist) 문서화
- 소액 실전 전송 테스트 후 오픈
