AI가 지금 당신에 대해 볼 수 있는 것은 무엇인가요? 스캐너를 직접 만들어 확인했습니다
Source: Dev.to
불편한 진실
지금 이 글을 읽고 있는 머신에는 아마도 수십 개의 비밀이 눈에 보이는 곳에 놓여 있을 것입니다.
OPENAI_API_KEY 가 환경 변수로 설정돼 있어 실행하는 모든 프로세스가 볼 수 있습니다. ~/.aws/credentials 파일에는 AWS 액세스 키가 평문으로 들어 있습니다. ~/.ssh/id_rsa 에는 패스프레이즈가 없는 SSH 개인 키가 있습니다. 여러 프로젝트 디렉터리마다 .env 파일이 세 개씩 존재하고, 각각에 데이터베이스 자격 증명과 토큰이 들어 있습니다. 클립보드에 한 시간 전 복사한 API 키가 아직 남아 있을 수도 있습니다.
이제 스스로에게 물어보세요: 로컬에서 실행되는 AI 어시스턴트가 이 모든 정보를 읽고 싶다면, 읽을 수 있을까요?
답은 거의 예 입니다.
저는 이를 구체화하기 위해 shadowscan 을 만들었습니다. 한 번의 명령으로 지금 바로 AI—또는 여러분과 동일한 권한으로 실행되는 어떤 프로세스가 읽을 수 있는 내용을 확인하세요.
shadowscan 이 하는 일
shadowscan 은 로컬 보안 스캐너입니다. 8가지 노출 카테고리를 검사하고 위험 등급이 매겨진 보고서를 생성합니다. 네트워크 호출은 전혀 없습니다. 모든 결과는 여러분의 머신에만 남습니다. 비밀 값은 항상 마스킹됩니다.
SHADOW SCAN REPORT
==================
[CRITICAL] ~/.aws/credentials — AWS access key found
[HIGH] ENV: OPENAI_API_KEY — API key exposed to all child processes (sk-a****)
[HIGH] ~/.ssh/id_rsa — Unencrypted SSH private key
[MEDIUM] Dotenv file found: /home/user/project/.env — review manually
[LOW] Clipboard — empty
Overall risk: CRITICAL | Findings: 5
Run 'shadowscan explain ' for details and fix instructions.
(sk-a****) 와 같은 마스킹 형식은 앞 네 글자를 보여주고 뒤에 **** 로 가립니다. 키를 식별할 수 있을 정도는 보여주지만, 실제로 사용할 수 있을 정도는 절대 노출되지 않습니다.
8가지 스캔 카테고리
| 카테고리 | 무엇을 검사하는가 |
|---|---|
env | 비밀 패턴(*KEY*, *TOKEN*, *SECRET*, *PASSWORD*, …)과 일치하는 환경 변수 |
creds | ~/.aws/credentials, ~/.netrc, ~/.pypirc, ~/.npmrc |
ssh | ~/.ssh/id_* — 암호화되지 않은 개인 키 |
dotenv | 현재 디렉터리와 홈 디렉터리(최대 2단계 깊이) 내 .env 파일 |
clipboard | 클립보드 내용 — 휴리스틱을 통해 비밀을 감지 |
mcp | Claude와 Cursor MCP 설정 파일에 포함된 API 키 |
git | 실수로 커밋된 비밀을 찾기 위해 최근 50개의 커밋 검사 |
tmp | /tmp/ 에 있는 민감해 보이는 이름의 파일 |
MCP 설정 스캐너는 종종 사람들을 놀라게 합니다. Claude Desktop이나 Cursor를 사용한다면, MCP 설정 파일에 API 키가 JSON 안에 직접 삽입돼 있을 수 있습니다. 대부분의 개발자는 한 번 설정하고는 다시 생각하지 않으니, 놓치기 쉽습니다.
내부 동작 방식
각 스캐너는 하나의 추상 기본 클래스를 상속받습니다:
from abc import ABC, abstractmethod
from shadowscan.models import Finding
class BaseScanner(ABC):
"""All scanners must inherit from this and implement scan()."""
@abstractmethod
def scan(self) -> list[Finding]:
"""Run the scanner and return a list of findings."""
...
def redact(self, value: str) -> str:
"""Return first 4 characters of value followed by '****'."""
if len(value)
이 기본 클래스는 모든 스캐너가 반드시 구현해야 하는 scan() 메서드를 정의하고, 비밀 값을 마스킹하는 redact() 헬퍼를 제공합니다.