제6장
앞선 장들에서는 생물안전 레코드에 무엇이 담기는지, 사고가 워크플로를 따라 어떻게 처리되는지, 어떤 서비스 인터페이스로 레코드를 노출하는지, 그리고 감사 추적을 남기며 레코드가 기관 사이를 어떻게 오가는지를 정의하였다. 이 장은 다른 질문을 던진다. 레코드의 어느 부분을 누가 볼 수 있는가, 그리고 레코드는 애초에 어떤 경로로 시스템에 들어오는가. 생물안전 레코드 서비스는 기관이 이미 운영하는 시스템(인사, 교육 관리, 출입 통제, 실험실 정보관리, 환경·보건·안전)에서 데이터를 공급받을 때에만 쓸모가 있고, 그렇게 모인 정보가 흩어져 있던 종이 서류보다 오용하기 쉬워지지 않을 때에만 안전하다.
일반적인 기업 데이터 보안보다 이 문제가 까다로운 이유는 두 가지이다. 첫째, 생물안전 메타데이터 중 일부는 그 안에 담긴 개인정보와 무관하게 생물보안 측면에서 그 자체로 민감하다. 둘째, 같은 레코드에 연구자의 개인정보, 경우에 따라 건강과 맞닿는 정보까지 함께 들어 있다. WIA-BIO-017 PHASE-4 문서의 보안 통제는 출발점이며, 이 장은 그것을 구체적인 접근 모델, 개인정보 보호 설계, 통합 패턴으로 풀어낸다.
위협 모델은 기술이 아니라 자산에서 출발한다. 생물안전 레코드 서비스에서 자산이란 제2장과 제3장에서 정의한 레코드들과 그 사이의 관계이다. 자산마다 노출·변조·가용성 상실 시 발생하는 위험의 성격이 다르므로, 통제 역시 데이터베이스 전체에 일률적으로 걸 것이 아니라 위험에 비례해 설계해야 한다.
일반 IT 조직에서 넘어온 팀이 가장 자주 놓치는 핵심은 결합이 민감성을 만든다는 점이다. 문패에 적힌 호실 번호 하나는 공개 정보이다. 그러나 기관 내 모든 등록 밀폐시설을 건물·호실·생물안전 등급·출입 통제 방식·보관 물질의 등록번호와 한데 묶은 목록은 전혀 다른 물건이다. "관심 대상 물질이 정확히 어디에 있고 그 방은 어떻게 보호되는가"라는 질문에 단 한 번의 조회로 답해 버리기 때문이다. 생물보안 지침은 이처럼 조각으로는 무해하지만 모이면 오용을 도울 수 있는 편집 정보를 정보 위해(information hazard)로 다룬다. 레코드 서비스는 설계상 바로 이 조각들이 모이는 곳이므로, 결합된 뷰를 개별 구성 요소보다 더 민감하게 취급해야 한다.
| 자산 | 주된 우려 | 민감도 등급(이 책의 제안) |
|---|---|---|
| 시설 요약(ID, 명칭, 생물안전 등급 라벨, 인증 상태) | 단독으로는 낮음. 일정·보고 업무에 폭넓게 필요 | 내부 |
시설 위치 상세(location.building, location.room, location.address) | 등급·보유 목록 참조와 결합될 때 생물보안 정보 위해 | 제한 |
출입 통제 프로파일(accessControls 블록) | 방이 어떻게 보호되는지 드러냄 | 제한 |
| 보유 목록·인수인계 참조(등록번호, 제5장의 이전 기록) | 생물보안 정보 위해 | 제한 |
| 교육 이수 기록 | 개인정보, 고용상 불이익과 연결 | 개인 |
| 사고 보고서·아차사고 보고서 | 개인정보, 평판 손상, 보고 위축 효과 | 개인 + 제한 |
| 직업보건 사례 정보 | 건강정보(민감정보) | 레코드 서비스 밖에서 보관 |
| 감사 이벤트 | 무결성이 핵심, 누가 무엇을 보았는지 드러냄 | 제한, 추가 전용 |
위 민감도 등급은 이 책의 제안이며 WIA-BIO-017의 규범 사항이 아니다. 명세는 분류 라벨을 정의하지 않지만, 필드 단위 규칙을 집행하려면 구현체에 어떤 형태로든 라벨 체계가 필요하다. 내부·제한·개인·"외부 보관"의 네 단계는 생물안전관리 부서가 일관되게 적용할 수 있을 만큼 단순하다.
가장 중요한 행위자는 세 부류이다. 외부 공격자는 여느 웹 애플리케이션을 노리듯 서비스를 공격한다. 크리덴셜 스터핑, 패치되지 않은 구성 요소의 취약점 악용, 노출된 API의 남용이 대표적이다. 더 어려운 것은 정당한 계정을 가진 내부자이다. 자기 실험실 레코드를 볼 권한이 있는 연구자라도 캠퍼스 내 모든 밀폐시설의 호실 단위 상세를 훑어볼 이유는 없고, 교육 이수 내역을 가져오는 인사 담당자가 사고 경위서를 읽을 이유도 없다. 레코드 시스템에서는 지나치게 넓은 정당한 접근이 흔한 노출 경로이며, 침입보다 알아차리기 어렵다. 세 번째는 협력 기관이다. 이들은 특정 교환(제5장)에 대해서는 신뢰받지만, 그 신뢰가 가명처리된 인원을 재식별하거나 요청하지 않은 필드를 받아 가는 데까지 미치지는 않는다.
"사고 INC-…를 읽을 수 있다"와 같은 레코드 단위 권한은 충분히 세밀하지 않다. 같은 사고 보고서 안에 IBC가 반드시 보아야 할 행정 핵심부, 조사팀이 보아야 할 경위 서술, 그리고 생물안전관리책임자만이 실명으로 풀 수 있어야 하는 인원 식별자 목록이 함께 들어 있기 때문이다. 따라서 설계 원칙은 필드 단위의 알 필요(need-to-know)이다. 모든 필드에 민감도 등급을 붙이고, 역할에는 레코드 전체가 아니라 등급을 부여하며, API는 직렬화하기 전에 호출자의 역할이 허용하는 필드만 남도록 레코드를 투영한다. 권한이 없는 필드는 null로 내려보내지 않는다. null은 실제로 비어 있는 값과 구별되지 않기 때문이다. 대신 해당 키 자체를 빼고, 응답에 보류된 필드 경로 목록을 함께 실어 클라이언트가 불완전한 양식을 조용히 그리는 대신 "제한됨"이라고 표시할 수 있게 한다.
PHASE-4 통합 문서는 간결한 보안 통제 목록을 제시한다. 저장 시 AES-256 암호화, 전송 시 TLS 1.3 암호화, 다중 인증(MFA)을 결합한 역할 기반 접근 제어(RBAC), 포괄적 감사 로깅, 그리고 OAuth 2.0과 API 키에 의한 API 인증이다. 기본선으로서 타당한 선택이다. 다만 문서는 이것들이 생물안전 데이터에 대해 어떻게 맞물려야 하는지는 말하지 않으며, 구현자에게 실제로 필요한 것은 바로 그 부분이다.
저장 데이터에는 GCM과 같은 인증 암호 모드로 256비트 키의 AES를 쓰는 것이 통상적인 선택이다. 실무의 쟁점은 알고리즘이 아니라 키이다. 키는 데이터베이스 옆 설정 파일이 아니라 키 관리 서비스나 하드웨어 보안 모듈(HSM)에 두어야 한다. 제한 등급 필드(시설 위치 상세, 출입 통제 프로파일, 보유 목록 참조)에는 별도 키로 애플리케이션 수준의 봉투 암호화를 한 겹 더 씌우는 것이 좋다. 그래야 테이블을 읽을 수 있는 데이터베이스 관리자가 그 필드까지 자동으로 읽게 되지 않는다. 백업은 운영 데이터와 같은 등급을 물려받으며, 백업과 함께 보관되지 않는 키로 암호화해야 한다.
전송 구간의 하한선으로는 TLS 1.3(IETF RFC 8446)이 적절하다. 레코드 서비스와 협력 기관 사이, 또는 레코드 서비스와 기관 내부에 설치된 어댑터 사이의 기계 간 연결에는 상호 TLS(mTLS)를 써서 양쪽이 인증서로 서로를 인증하게 한다. 제한 또는 개인 등급 필드가 흐르는 인터페이스에서는 마이크로서비스 사이의 내부 인터페이스를 포함해 구버전 프로토콜 협상을 거부해야 한다. "방화벽 안쪽이니 괜찮다"는 통제가 아니다.
사람은 기관의 ID 공급자(IdP)를 통해 다중 인증으로 로그인하고, 레코드 서비스는 비밀번호를 직접 다루지 않고 액세스 토큰을 받는다. 신원 확인에 OpenID Connect를 얹은 OAuth 2.0(IETF RFC 6749)이 일반적인 조합이다. PHASE-4 부록 J의 토폴로지 설명은 멀티테넌트 게이트웨이에 OAuth 2.1 ID 공급자를 언급하는데, OAuth 2.1은 현재의 OAuth 2.0 모범 사례를 통합한 IETF 초안 단계의 문서이다. 따라서 OAuth 2.0 보안 모범 사례(BCP) 지침을 따르는 구현이라면 실질적으로 그와 정렬되어 있다.
API 키의 역할은 더 좁다. 인사 연동 어댑터처럼 등록된 클라이언트 애플리케이션을 식별하는 용도로는 무방하지만, 제한 또는 개인 등급 필드를 읽는 호출자의 유일한 자격 증명이 되어서는 안 된다. 서비스 계정에는 고정 키보다 클라이언트 자격 증명으로 발급받는 수명이 짧은 토큰을 쓰고, 서비스 계정 하나를 연동 하나에만 묶고, 그 연동에 필요한 스코프만 부여한다. PHASE-4는 "시간당 1000건"이라는 호출 한도도 제시하지만, 이 수치는 공학적 요구사항이라기보다 템플릿에서 옮겨 온 값으로 보이므로 배포마다 조정하는 설정의 예시로 취급하는 것이 좋다.
RBAC의 품질은 역할 정의의 품질을 넘지 못한다. 아래 매트릭스는 대학이나 병원 배포를 위한 이 책의 제안이다. 생물안전 거버넌스의 실제 직위에 대응하는 여섯 역할을 두고, 레코드 계열별로 접근 범위를 배정하였다. "본인 소관"은 호출자 자신의 실험실이나 과제의 레코드로 한정됨을 뜻하고, "요약"은 제한 등급 필드를 뺀 시설 요약을, "가명"은 호출자가 풀 수 없는 토큰 형태로 인원 식별자가 전달됨을 뜻한다.
| 역할 | 시설 요약 | 위치·출입 상세 | 교육 이수 기록 | 사고 보고서 | 준수 결과 | 감사 이벤트 |
|---|---|---|---|---|---|---|
| 생물안전관리책임자 | 읽기/쓰기 | 읽기/쓰기 | 읽기/쓰기 | 읽기/쓰기, 인원 식별자 해제 가능 | 읽기/쓰기 | 읽기 |
| IBC 위원 | 읽기 | 읽기, 심의 중인 과제에 한함 | 읽기, 요약 상태만 | 읽기, 가명 | 읽기 | 없음 |
| 연구책임자(PI) | 읽기 | 읽기, 본인 소관 시설 | 읽기, 본인 연구팀 | 작성, 본인 실험실 읽기(본인 보고서 외 가명) | 읽기, 본인 과제 | 없음 |
| 연구원 | 요약 | 배정된 호실만 | 본인 기록 읽기 | 작성, 본인 보고서 읽기 | 없음 | 없음 |
| 점검자(외부) | 읽기, 기간 한정 | 읽기, 기간·범위 한정 | 읽기, 범위 내 | 읽기, 가명 | 읽기 | 없음 |
| 감사인(내부/외부) | 읽기 | 기본 없음 | 읽기, 표본 | 읽기, 가명 | 읽기 | 읽기 |
매트릭스의 몇 가지 선택은 설명이 필요하다. 감사 이벤트를 읽는 역할은 생물안전관리책임자 외에 감사인뿐이며, 누구도 감사 이벤트를 쓰거나 지울 수 없다. 감사 이벤트는 서비스 자신이 생성한다(제5장). 감사인은 기본적으로 위치 상세를 보지 않는다. 절차가 제대로 작동하는지 확인하는 데 방의 위치를 알아야 하는 경우는 드물기 때문이다. 점검자 역할은 기간이 한정된다. 점검 기간 동안 명시적 범위 목록과 함께 접근을 부여하고 자동으로 만료되게 하는 편이, 상시 계정을 만들어 두고 비활성화를 기억해 내길 바라는 것보다 훨씬 안전하다. 연구원은 언제든 사고·아차사고 보고서를 작성할 수 있다. 보고서 작성을 시스템에서 가장 낮은 권한으로도 가능한 행위로 두는 것은 제3장에서 다룬 비처벌적 설계의 일부이다.
역할은 속성과 결합해야 한다. 연구책임자의 "본인 소관 시설"은 역할이 아니라 데이터에 저장된 관계(시설의 authorizedPersonnel 목록, 과제 승인)이며, 인가 계층은 요청 시점에 이를 평가해야 한다. 이를 RBAC 위에 얹은 속성 기반 접근 제어라고 부르기도 한다. 실무적으로는 정책 엔진이 소수의 관계 데이터를 읽을 수 있어야 한다는 뜻이지, 모든 규칙을 개별 예외로 만든다는 뜻이 아니다.
제5장은 AuditEvent를 변경 내역을 해시 체인으로 묶은 레코드로 정의하였다. 제한·개인 등급 필드에 대해서는 읽기도 중요하다. 호출자가 위치 상세를 조회하거나, 가명 인원 식별자를 해제하거나, 레코드를 한 건 넘게 내보낼 때마다 서비스는 감사 이벤트를 남겨야 한다. 제4장에서 HTTP 403에 대응시킨 WIA-BIO-017 오류 코드 B003 "Unauthorized access"(명세 §9.4)는 항상 감사 이벤트를 생성해야 하며, 같은 계정에서 짧은 시간 안에 B003이 반복되면 생물안전관리책임자에게 알려야 한다. 이러한 읽기 감사 이벤트가 있어야 점검자의 기간 한정 접근도 사후에 검토할 수 있다.
생물안전 레코드에는 개인정보가 가득하다. 교육 이수 기록의 성명과 사번, 사고 관련자 목록, 보고자의 신원, 평가자와 검토자의 이름이 모두 그렇다. 한국에서 운영하는 기관은 이를 개인정보 보호법에 따라 처리해야 하고, 유럽연합 안에서 운영하거나 그곳의 정보주체를 다루는 기관은 일반개인정보보호규정(Regulation (EU) 2016/679, GDPR)도 고려해야 한다. 두 법 모두 최소 수집, 목적 제한, 처리의 안전성, 설계 단계의 개인정보 보호를 중시하며, 건강정보를 특별히 보호되는 범주(개인정보 보호법의 민감정보, GDPR 제9조의 특수 범주)로 다룬다.
PHASE-4는 구현체가 "HIPAA/GDPR에 따라" 개인정보를 보호해야 한다고 적고 있다. 이 문구는 임상 데이터를 염두에 두고 쓰인 것이다. HIPAA는 의료 제공자·건강보험 등 적용 대상 기관(covered entity)과 그 업무 협력자가 보유한 보호 건강정보를 규율하는 미국 법이다. 대학이나 기업의 인사·안전 기록은, 그 기관이 병원을 함께 운영하더라도, 일반적으로 이 법이 겨냥한 대상이 아니다. 생물안전 레코드 서비스에 더 유용한 해석은 이렇다. 교육 이수 기록과 사고 기록에는 기관의 직원 정보 규칙(개인정보 보호법 또는 GDPR, 그리고 해당 지역 노동법)을 적용하고, 진정으로 의학적인 정보는 자체 법적 근거와 비밀유지 의무를 가진 직업보건 부서 안에 둔다. 특정 기관이 HIPAA나 작업장 건강정보에 관한 한국의 특정 규정의 적용을 받는지는 소프트웨어가 아니라 기관의 법률 자문이 판단할 문제이다.
SDK의 IncidentReport 타입에는 MedicalEvaluation 타입의 선택 필드 medicalEvaluation이 있다. 제3장에서 정한 대로 이 책은 이 타입을 범위 밖으로 두고, 사고 레코드 안에 의료 내용을 절대 넣지 않는다. 개인정보 측면의 논거는 범위 측면의 논거와 별개이지만 결론은 같다. 의료 정보가 사고 레코드 안에 있으면, 사고를 읽을 수 있는 모든 역할(IBC, 연구책임자, 감사인, 점검자)이 실수 한 번으로 볼 권리가 없는 건강정보를 읽게 되고, 사고 통계를 기관 간에 교환할 때마다 의료 정보 수준의 취급이 필요해진다.
해법은 복사가 아니라 참조이다. 사고 레코드에는 직업보건 부서가 발급한 불투명한 사례 식별자와, 기껏해야 "의뢰됨"이나 "추적 관찰 종료" 같은 상태 플래그만 둔다. 나머지는 모두 직업보건 부서가 자기 시스템에 보관한다. 생물안전관리책임자는 의뢰가 있었는지, 그것이 종료되었는지는 알 수 있지만, 그 사유는 직업보건 부서만 안다. 배포 환경에서는 클라이언트 소프트웨어가 이 필드를 비워 두기를 기대할 것이 아니라, medicalEvaluation을 채운 쓰기 요청을 API 경계에서 명확한 검증 오류와 함께 거부해야 한다.
기관 내부에서 생물안전관리부서는 사고에 누가 관련되었는지 알아야 한다. 기관 밖에서는 그럴 필요가 있는 곳이 거의 없다. 국가 기관에 보내는 사고 집계 통계, 협력 기관과 나누는 아차사고 교훈, 기관 간 교육 이수 자격 확인(제5장)은 모두 가명 식별자로 충분하다. 가명처리는 직접 식별자를, 별도로 보관된 추가 정보 없이는 당사자와 연결할 수 없는 토큰으로 바꾸는 것이다. GDPR 제4조 제5호는 이를 정의된 용어로 두며, 가명처리된 데이터는 여전히 개인정보이다. 개인정보 보호법 역시 가명정보라는 개념과 그 처리 요건을 두고 있다. 가명처리는 위험을 낮추지만 데이터를 법의 적용 범위 밖으로 꺼내 주지는 않는다.
실무적으로 쓸 만한 방식은 내부 인원 ID에 대한 키 기반 해시(예: HMAC-SHA-256)를 쓰되, 교환 상대 기관별·목적별로 비밀키를 따로 두는 것이다. 상대 기관별 키를 쓰면 두 기관이 같은 토큰으로 서로의 데이터를 결합할 수 없다. 목적별 키를 쓰면 사고 통계에 쓰인 토큰을 교육 확인에 쓰인 토큰과 대조할 수 없다. 키는 송신 기관의 생물안전관리부서가 보관하며, 토큰을 사람으로 되돌리는 일은 생물안전관리책임자 역할로 제한되고 감사 대상이 된다.
// 예시: 상대 기관별·목적별 가명 인원 토큰
import { createHmac } from "node:crypto";
type Purpose = "incident-stats" | "training-verify";
function personnelToken(internalId: string, partnerId: string,
purpose: Purpose, keyFor: (p: string, u: Purpose) => Buffer): string {
const mac = createHmac("sha256", keyFor(partnerId, purpose));
mac.update(internalId);
return "PSN-" + mac.digest("base64url").slice(0, 22);
}
두 가지 단서가 붙는다. 첫째, 토큰의 강도는 레코드의 나머지 부분을 넘지 못한다. 작은 실험실 이름, 날짜, 그리고 "그 연구실의 유일한 박사후연구원" 같은 직위가 적힌 사고 보고서는 ID를 어떻게 해시하든 당사자를 특정한다. 준식별자는 교환 전에 일반화해야 한다(실험실은 학과로, 날짜는 월 단위로). 둘째, 가명처리는 익명처리가 아니다. 6.5절에서 다룰 WIA-SOCIAL 공유처럼 공개 게시가 목적이라면, 어떤 개인도 합리적으로 특정될 수 없을 때까지 레코드를 더 줄여야 한다.
교육 이수 기록과 사고 기록은 법령이나 기관 방침이 정한 기간 동안 보존해야 할 수 있으며, 제5장에서 보았듯 보존 기간은 관할에 따라 정해진다. 개인정보 법제는 그 반대편을 더한다. 데이터는 목적에 필요한 기간을 넘어 보관해서는 안 되며, 정보주체는 열람이나 정정을 요구할 수 있다. 레코드 서비스는 레코드 계열마다 보존 등급을 저장하고, 보존 기한이 지난 레코드를 자동 삭제하는 대신 검토 대상으로 표시하는 정기 작업을 돌리며, 본인의 교육 이수 기록을 읽을 수 있는 형식으로 내보내는 기능을 제공해야 한다. 감사 대상 레코드를 삭제할 때는 개인 필드를 묘비(tombstone) 값으로 바꾸고 감사 체인은 그대로 유지하여, 삭제가 무결성 검증을 깨뜨리지 않게 한다.
빈 시스템에서 시작하는 기관은 없다. 교육 이수 내역은 이미 인사 또는 교육 관리 시스템(LMS)에, 출입 이벤트는 물리 출입 통제 시스템에, 시료·물질 등록부는 LIMS에, 점검 지적 사항은 EHS 플랫폼에, 표준작업지침(SOP)은 문서관리 시스템에 있다. WIA-BIO-017 레코드 서비스가 성공하는 조건은 직원들에게 모든 것을 두 번 입력하게 하는 것이 아니라, 이 원천들을 생물안전 레코드로 조정·통합하는 자리가 되는 것이다.
권장 구조는 원천 시스템마다 레코드 서비스와의 사이에 어댑터 계층을 두는 것이다. 각 어댑터는 연동 하나를 책임진다. 원천에서 데이터를 가져오거나 받고, WIA-BIO-017 타입으로 매핑하고, 검증한 뒤, 해당 레코드 계열로 스코프가 제한된 서비스 계정으로 대화형 클라이언트와 동일한 API를 통해 제출한다. 레코드 서비스는 원천 데이터베이스에 직접 손대지 않고, 원천 시스템도 레코드 저장소에 직접 쓰지 않는다.
원천 시스템 어댑터 계층 레코드 서비스
----------- ----------- -------------
인사 / LMS ---- CSV/API ---> training-adapter --+
출입 통제 ---- 이벤트 ----> access-adapter --+
LIMS / 등록부 -- ID -------> inventory-adapter --+--> WIA-BIO-017 API --> 레코드 저장소
EHS 플랫폼 ---- 지적사항 --> ehs-adapter --+ (RBAC, 필드 투영, (암호화,
문서관리 ---- SOP 버전 --> sop-adapter --+ 감사 이벤트) 해시 체인
| 감사 추적)
+--> 게이트웨이(WIA-OMNI-API) --> 협력 기관
이 구조에는 세 가지 이점이 있다. 모든 데이터 원천에 대해 검증이 API 한곳에서 한 번 이루어진다. 어댑터별 서비스 계정을 다른 연동에 영향 없이 폐기하거나 교체할 수 있다. 그리고 감사 추적이 각 레코드를 어느 어댑터가 만들었는지 기록하므로, 의심스러운 교육 이수 항목을 그것을 만든 특정 가져오기 배치까지 추적할 수 있다.
| 원천 시스템 | 제공하는 것 | WIA-BIO-017 대상 | 원천에 남겨 두는 것 |
|---|---|---|---|
| 인사 / 교육 관리 | 과정 이수, 평가 결과, 수료증 번호 | TrainingRecord | 교육 내용, 인사 기록 전체 |
| 물리 출입 통제 | 출입 로그가 존재하고 보존된다는 근거, 출입 인가자 목록 | BiosaftyFacility.accessControls, authorizedPersonnel | 원시 출입 이벤트 |
| LIMS / 물질 등록부 | 기관 내부 등록번호와 분류 라벨 | 보유 목록 참조, 인수인계 기록(제5장) | 물질의 특성 정보 |
| EHS 플랫폼 | 점검 지적 사항, 시정 조치 진행 상태 | ComplianceResult, CAPA 필드(제3장) | 생물안전 외 EHS 데이터 |
| 문서관리 | SOP 문서 ID와 승인된 버전 | 시설·사고 레코드의 SOP 참조 필드 | SOP 본문 |
마지막 열은 다른 열만큼 중요하다. 레코드 서비스는 참조와 요약을 보유하고, 권위 있는 상세 정보는 그것을 소유한 시스템에 남는다. 이렇게 하면 레코드 서비스가 침해되었을 때 공격자가 얻는 것이 줄고, 레코드 서비스가 보유할 이유가 없는 데이터에 대한 책임 범위에서 벗어나며, 같은 SOP의 사본 두 개가 서로 어긋나는 일도 막을 수 있다.
accessLog 플래그
BiosaftyFacility의 accessControls 블록은 불리언 값들로 이루어진다. restrictedAccess, selfClosingDoors, lockableDoors, accessLog, 그리고 선택 항목 biometricAccess이다. accessLog를 출입 이벤트를 저장하는 곳으로 오해하기 쉽지만 그렇지 않다. 이 값은 출입 로그가 존재한다는 사실을 단언할 뿐이다. 그리고 그것이 올바른 설계이다. 출입 이벤트는 양이 많고, 특정인이 언제 어디에 있었는지를 보여 주는 개인정보이며, 이미 물리 보안 시스템에서 잘 보호되고 있다. 이를 생물안전 레코드 서비스로 복사하면 전문성이 떨어지는 위치 추적 데이터 저장소가 하나 더 생길 뿐이다.
따라서 출입 어댑터는 두 가지 좁은 일만 해야 한다. 첫째, 등록된 각 시설에 대해 출입 통제 시스템이 로그를 생성·보존하고 있는지 주기적으로 확인하여 accessLog를 설정하고, 값이 바뀔 때마다 감사 이벤트를 남긴다. 둘째, 시설의 authorizedPersonnel 목록을 출입 통제 시스템의 출입증 권한 목록과 대조하여, 출입 권한은 있으나 유효한 인가나 교육 이수 기록이 없는 사람이 있으면 준수 지적 사항을 발생시킨다. "특정 날짜에 X호실에 누가 들어갔는가"라는 질문은 여전히 그것을 물을 권한이 있는 사람이 출입 통제 시스템에 직접 조회한다.
인사·교육 시스템은 각자의 형식으로 이수 내역을 내보낸다. 아래 어댑터는 가상의 인사 시스템 내보내기 행을 TrainingRecord로 매핑한다. 생물안전관리부서가 관리하는 과정 카탈로그로 기관 고유의 과정 코드를 trainingType 열거형에 대응시키고, SDK의 Result 패턴을 반환하여 일괄 가져오기가 중단 없이 실패 건을 모을 수 있게 한다. 보수교육 주기를 정하는 것은 어댑터가 아니라 과정 카탈로그라는 점에 유의해야 한다. 제2장에서 설명했듯 보수교육 주기는 기관 방침이며, 어댑터는 인사 시스템이 그 방침에 따라 계산한 valid_until 날짜를 옮겨 적을 뿐이다.
import type { TrainingRecord, Result } from "@wia/bio-017";
// 인사/LMS 내보내기의 가상 행(필드명은 인사 시스템 고유의 것)
interface HrTrainingRow {
emp_no: string;
display_name: string;
course_code: string;
completed_on: string; // "2026-03-18"
valid_until?: string; // 기관 방침에 따라 인사 시스템이 계산
assessed: "PASS" | "FAIL" | "";
assessor_emp_no?: string;
cert_no?: string;
}
type CourseEntry = { type: TrainingRecord["trainingType"]; topics: string[] };
interface AdapterError { row: string; reason: string }
export function toTrainingRecord(
row: HrTrainingRow,
catalog: Map<string, CourseEntry>,
perIdOf: Map<string, string> // 사번 -> PER- ID (제2장의 매핑 테이블)
): Result<TrainingRecord, AdapterError> {
const course = catalog.get(row.course_code);
if (!course) {
return { success: false, error: { row: row.emp_no, reason: `unmapped course ${row.course_code}` } };
}
const trainingDate = new Date(row.completed_on + "T00:00:00Z");
if (Number.isNaN(trainingDate.getTime())) {
return { success: false, error: { row: row.emp_no, reason: "invalid completed_on" } };
}
const expirationDate = row.valid_until ? new Date(row.valid_until + "T00:00:00Z") : undefined;
const personnelId = perIdOf.get(row.emp_no);
const assessorId = row.assessor_emp_no ? perIdOf.get(row.assessor_emp_no) : undefined;
if (!personnelId || (row.assessed !== "" && !assessorId)) {
return { success: false, error: { row: row.emp_no, reason: "no PER- mapping for trainee or assessor" } };
}
const record: TrainingRecord = {
personnelId,
name: row.display_name, // 내부 저장소 전용, 교환 전에 제거
trainingType: course.type,
trainingDate,
expirationDate,
topicsCovered: course.topics,
competencyAssessment: row.assessed === ""
? undefined
: { passed: row.assessed === "PASS", assessor: assessorId! },
certificationIssued: Boolean(row.cert_no),
certificateNumber: row.cert_no || undefined,
nextTrainingDue: expirationDate,
};
return { success: true, data: record };
}
이 코드의 몇몇 세부 사항에는 정책이 담겨 있다. 인원 ID는 사번에서 만들어 내지 않는다. 제2장의 Personnel 매핑 테이블에서 조회하므로 PER- 식별자는 가명으로 유지되고 사번이 바뀌어도 변하지 않는다. 교육 대상자나 평가자의 매핑이 없는 행은 ID를 지어내지 않고 거부한다. 성명은 SDK 타입이 필수로 요구하므로 보관하지만, 제5장의 교환 계층이 이를 제거하고 6.3.3절의 가명 토큰으로 대체한다. 어댑터는 점수를 지어내지 않는다. 인사 시스템이 합격·불합격만 기록한다면 score는 비워 둔다. 그리고 매핑되지 않은 과정은 추측하지 않고 거부하여, 새 과정을 생물안전 교육으로 인정할지 생물안전관리부서가 명시적으로 결정하게 만든다.
사고 조사와 점검에서는 특정 시점에 어느 버전의 절차가 유효했는지를 자주 묻는다. 레코드 서비스는 SOP 본문을 저장하지 않는다. 대신 필요한 레코드에 문서관리 시스템의 식별자와 버전 번호를 저장한다. 시설 등록(이 시설에 어떤 SOP가 적용되는가), 사고 보고서(사고 당일 유효했던 SOP 버전은 무엇인가), CAPA 조치(시정 조치가 어떤 개정판을 낳았는가)가 그 대상이다. SOP 어댑터는 문서관리 시스템의 승인 이벤트를 구독하여 현행 버전 참조를 갱신하고, 이력을 재구성할 수 있도록 감사 이벤트를 남긴다.
WIA-BIO-017 README는 다른 WIA 표준과의 연계 네 가지를 열거한다. 감사 추적 앵커링에 쓰는 WIA-BLOCKCHAIN은 제5장에서 다루었다. 나머지 세 가지는 켜기 전에 정리해 두어야 할 개인정보 측면의 함의가 있다. 아래에서 각각을 범위 설명과 최소 수집 규칙과 함께 기술한다. 이 책의 다른 예시 엔드포인트와 마찬가지로, 이 서비스들의 가용 여부는 가정하지 말고 WIA Standards 작업반에 확인해야 한다.
| 연계 | README상의 역할 | 범위 설명 | 최소 수집 규칙 |
|---|---|---|---|
| WIA-OMNI-API | 범용 생물안전 데이터 게이트웨이 | 기관 간 교환을 위한 전송·라우팅. 데이터 저장소가 아님 | 교환 협약에 명시된 필드만 통과. 제한 등급 필드는 기본적으로 통과 불가 |
| WIA-INTENT | 의도 기반 실험실 안전 질의 | 호출자가 이미 볼 수 있는 레코드에 대한 자연어·의도 질의 | 호출자 본인의 토큰과 필드 투영으로 실행. 특권 서비스 계정 금지 |
| WIA-SOCIAL | 협업형 안전 사고 보고 | 비식별화된 아차사고 교훈의 기관 간 공유 | 검토를 거친 "교훈" 레코드만 외부로. 사고 레코드 자체는 절대 불가 |
게이트웨이는 협력 기관에게 연동 지점을 하나로 줄여 준다는 점에서 매력적이다. 동시에 여러 기관의 데이터가 지나가는 곳이므로 공격 대상이 된다. 게이트웨이는 협력 관계마다 교환 협약에서 합의한 레코드 유형과 필드 경로의 허용 목록을 집행하고, 송신 기관의 서비스가 그 이상을 내보내더라도 목록 밖의 것은 거부해야 한다. 기관 간 종단 간 암호화를 쓰는 경우, 게이트웨이는 페이로드를 복호화하지 않고도 제5장에서 설명한 방식으로 봉투와 서명을 검증할 수 있어야 한다. 게이트웨이 로그에는 페이로드 내용이 아니라 봉투 메타데이터(송신자, 수신자, 레코드 유형, 페이로드 해시)를 기록한다.
"우리 연구실에서 이번 분기에 교육 이수가 만료되는 사람은 누구인가"와 같은 의도 기반 질의는 연구책임자와 생물안전관리책임자에게 실제로 유용하다. 위험은 구조에 있다. 질의 계층이 강력한 서비스 계정으로 실행된 뒤 결과를 사후 필터링한다면, 필터의 결함 하나로 모든 것이 노출된다. 규칙은 질의 계층이 호출자로서 행동하는 것이다. 즉 호출자 본인의 토큰을 레코드 API로 전달하여 다른 모든 요청과 동일한 RBAC와 필드 투영이 적용되게 한다. 또한 질의 계층은 레코드에 관한 행정적 질문으로 한정해야 한다. 기술적 안전 지침을 생성하는 통로가 아니며, 이 경계는 제8장에서 더 다룬다.
아차사고 교훈을 기관 간에 나누면 다른 기관이 같은 절차상 허점을 피하는 데 도움이 될 수 있고, 익명성은 공유를 북돋운다. 안전한 공유 단위는 게시용으로 따로 작성하고 공개 전에 생물안전관리책임자가 검토한 별도의 교훈 레코드이다. 이름만 지운 사고 보고서는 결코 아니다. 교훈 레코드에는 사고 type, 일반화된 환경(호실이 아니라 "교육용 실험실" 수준), 기여한 행정적 요인, 효과가 있었던 시정 조치를 담는다. 월이나 분기보다 세밀한 날짜, 시설 식별자, 인원 토큰, 물질 식별자는 담지 않는다. 규모가 작은 기관은 일반화된 서술만으로도 동료 기관에게 자신이 드러날 수 있음을 인식해야 하며, 컨소시엄을 통해 게시하거나 시차를 두어 게시하는 방법을 택할 수 있다.
레코드 시스템에서 일어나는 개인정보·보안 실패는 대부분 특이하지 않다. 너무 많이 수집하고, 너무 많이 내보내고, 구조화된 필드가 일부러 배제한 내용을 자유 서술 필드가 실어 나르도록 방치한 데서 온다. 생물안전 배포에서 특히 흔한 함정은 세 가지이다.
원천 시스템이 제공할 수 있는 것을 "나중에 쓸모가 있을지 모르니" 모두 가져오고 싶은 유혹이 있다. 필드 하나가 늘 때마다 공격 표면, 개인정보 보호 의무, 교환 전 검토할 내용이 함께 는다. 6.4.2절의 매핑 표에 "원천에 남겨 두는 것" 열을 둔 이유가 바로 이것이다. 가장 분명한 예는 SDK의 BiologicalAgent 타입이다. 제2장에서 설명했듯 관리 레코드는 그 타입이 상정하는 특성 필드가 아니라 기관 내부 등록번호와 분류 라벨만 보유해야 한다. 좋은 판별법은 필드마다 "이 필드는 어느 역할의 어떤 레코드 수준 질문에 답하는가"를 묻는 것이다. 답이 "아직 없음"이라면 그 필드는 스키마에 들어올 자격이 없다.
평면도, 실 배치도, 출입문이나 장비 사진은 관계 기관에 낸 종이 신청서의 일부였다는 이유로 시설 등록 레코드에 첨부 파일로 딸려 들어오는 경우가 많다. 이런 자료는 레코드 서비스에 두어서는 안 되며, 생물안전관리부서 밖으로 나가는 교환이나 보고서에 절대 포함되어서는 안 된다. 점검에 평면도가 필요하면 관계 기관 자체의 채널이나, 자체 접근 통제를 갖춘 문서관리 시스템을 통해 공유한다. 시설 레코드에는 문서 참조만 저장한다.
description, rootCause, immediateActions, 그리고 시정·예방 조치 목록은 자유 서술 필드이다. 사람들은 구조화된 필드가 일부러 빼놓은 내용, 즉 동료의 이름, 의학적 세부 사항, 물질의 정체, 호실 번호를 자연스럽게 이곳에 적는다. 구조화된 가명처리는 문장 하나로 무력화된다. 배포 환경에서는 세 가지 조치를 함께 써야 한다. 입력 시점의 안내와 화면상 힌트, 저장 시 실명·사번·호실 식별자·등록번호로 보이는 표현을 자동으로 찾아 작성자에게 검토를 요청하는 검사, 그리고 레코드가 기관 밖으로 나가기 전 모든 자유 서술 필드에 대한 사람의 필수 검토이다.
레코드가 생물안전관리부서 밖으로 나가기 전 비식별 점검표
location 필드, accessControls 블록, 평면도·사진 첨부를 제거하였는가?medicalEvaluation이 비어 있고, 직업보건 참조는 불투명한 사례 ID나 상태 플래그만 담고 있는가?description, rootCause, 조치 목록)를 사람이 읽고 이름, 의학적 세부 사항, 호실 식별자, 물질의 정체가 없는지 확인하였는가?점검표는 생물안전관리책임자가 코드를 읽지 않고도 답할 수 있는 질문으로 일부러 구성하였다. 1~6번은 교환 계층과 게이트웨이가 자동으로 집행할 수 있고, 7~9번은 사람이나 로그, 또는 둘 다가 필요하다. 앞의 여섯 항목을 자동화한 구현이라도 반출 확인 화면에 점검표를 띄우는 것이 좋다. 버튼을 누르는 사람이 새어 나가는 문장을 잡을 수 있는 마지막 지점이기 때문이다.
생물안전 레코드 서비스는 조각으로는 무해하지만 결합하면 민감해지는 정보를 연구자의 개인정보와 함께 한곳에 모은다. PHASE-4의 통제(저장 시 AES-256, 전송 시 TLS 1.3, MFA를 결합한 RBAC, OAuth 2.0과 API 키, 감사 로깅)는 구체화될 때 건전한 기본선이 된다. 키는 키 관리 서비스에 두고, 기관 사이에는 상호 TLS를 쓰며, 필드 단위 투영을 갖춘 역할 매트릭스를 운영하고, 쓰기뿐 아니라 민감한 읽기에도 감사 이벤트를 남긴다. 개인정보 설계는 의료 정보를 직업보건 부서에 두고, 상대 기관별·목적별 키로 인원을 가명처리하며, 준식별자를 일반화한다. 통합은 원천 시스템마다 어댑터 하나를 두는 방식으로 이루어지며, 레코드에는 참조를 저장하고 상세 정보는 제자리에 남긴다. WIA-OMNI-API, WIA-INTENT, WIA-SOCIAL 연계는 각각 최소 수집 규칙의 제약을 받고, 모든 반출은 사람이 자유 서술을 읽는 것으로 끝나는 비식별 점검표를 통과한다.
기술적 안전 내용은 공식 생물안전 당국에 문의할 것. 이 장은 관리 레코드의 보안과 개인정보 보호만을 다룬다. 밀폐, 시설 설비, 개인보호구 및 그 밖의 기술적 생물안전 요건은 국가 생물안전 당국(한국의 경우 질병관리청과 LMO법상 소관 부처), WHO 실험실 생물안전 매뉴얼(LBM), 그리고 해당되는 경우 BMBL을 참조해야 한다.