dsh 플러그인 작동 원리와 안전한 검증 방법
dsh 플러그인을 설치하면 에이전트 권한으로 외부 코드가 실행됩니다. 플러그인이 접근 가능한 파일 시스템과 셸 권한 범위를 확인하고, 설치 전 코드의 신뢰성을 검증하여 프롬프트 인젝션 및 보안 위협을 방지하는 구체적인 점검 절차를 안내합니다.
dsh 플러그인이란 무엇이며 어떤 기능을 수행합니까?
dsh 플러그인은 DeepSeek Harness가 자체 프로세스 내로 로드하는 Node 패키지입니다. 플러그인을 설치하면 에이전트의 권한으로, 에이전트가 접근할 수 있는 머신에서 타인의 코드가 실행됩니다. 로드된 플러그인과 Harness의 나머지 부분 사이에는 어떠한 제약도 없습니다. 따라서 플러그인을 설치하기 전에 해당 코드가 무엇에 접근할 수 있는지, 그리고 그 범위를 어떻게 최소화할 것인지 자문해야 합니다.
dsh(DeepSeek Harness)는 Cordis라는 플러그인 프레임워크를 기반으로 구축된 DeepSeek AI의 오픈 소스 에이전트 Harness입니다. 프로젝트의 README에 따르면 모든 것이 플러그인으로 구성됩니다. 모델 어댑터도 플러그인이며, 사용자가 입력하는 웹 인터페이스도 플러그인입니다. 프로젝트 외부에서 설치하는 모든 것은 프로젝트와 함께 제공된 구성 요소와 동일한 트리, 동일한 신뢰 수준에 위치합니다. 아직 구축하지 않았다면 VPS에 DeepSeek Harness 설치하기를 먼저 진행한 뒤, 추가 기능을 설치하기 전에 다시 돌아오십시오.
플러그인이 접근할 수 있는 확장 지점은 저장소의 AGENTS.md에 나열되어 있습니다. 2026년 8월 기준으로 해당 지점은 다음과 같습니다.
- LLM(거대 언어 모델): API 키로 비용을 지불하는 공급자
- Shell: 로컬 및 pwsh 공급자를 포함한 bash 기능
- Filesystem: 정책 기반 파일 접근
- Web: 검색 및 가져오기 공급자
- Subprocess: 프로세스 트리 공급자
- Workflow: 워커 스레드
- Subagent: 하위 에이전트로의 위임
- Settings and credentials: 저장된 구성 및 환경 변수
플러그인은 ctx.tools에 도구를 등록하며, 문서에는 등록된 도구의 스키마가 프롬프트 구성에 포함된다고 명시되어 있습니다. 많은 사용자가 이 뒷부분을 간과합니다. 플러그인은 자체 코드로 특별한 작업을 수행하지 않더라도 에이전트의 판단을 변경할 수 있습니다. 플러그인이 제공하는 설명이 모델이 읽는 텍스트가 되기 때문입니다. 이는 코딩 에이전트에 대한 프롬프트 인젝션과 동일한 형태의 문제이지만, 한 가지 차이점이 있습니다. 이 텍스트는 설치 시점에 전달되며 플러그인을 제거할 때까지 유지됩니다.
dsh는 어떻게 플러그인을 찾고 로드합니까?
전역 플러그인 디렉터리는 존재하지 않습니다. 실행 중인 dsh는 부팅 시점에 순서가 지정된 계층들로 구성된 플러그인 트리이며, 사용자의 선택 사항을 담은 단위는 프로필입니다. $DSH_HOME은 기본적으로 ~/.dsh을 사용하며, 각 프로필은 $DSH_HOME/profiles/<name>에 위치합니다. web 및 headless 프로필은 처음 사용할 때 제공된 템플릿으로부터 자동으로 생성됩니다.
프로필 디렉터리에는 모든 것을 결정하는 두 개의 파일이 있습니다.
package.json: 트리 외부 플러그인 의존성과 순서가 지정된bundles목록을 포함하는dsh.profile매니페스트를 담고 있습니다.cordis.patch.yml: 해당 번들 위에 적용되는 사용자의 패치 계층입니다.
ls ~/.dsh
ls ~/.dsh/profiles/web부팅 시 계층은 다음 순서로 적용되며, 나중에 적용된 계층이 우선합니다.
- 빈 루트
- 프로필의 번들 (매니페스트에 나열된 순서대로)
- 프로필의
cordis.patch.yml $DSH_HOME/cordis.patch.yml- 명령줄에서 전달된 모든
--patch <path>오버레이
두 개의 플래그를 사용하면 아무것도 시작하지 않고 구성 결과를 출력할 수 있습니다.
dsh --profile web --dump-default-config
dsh --profile web --dump-config--dump-default-config는 구성된 트리 자체를 출력합니다. --dump-config은 프로필 및 홈 패치 계층을 추가하므로, 다음 부팅 시 로드될 내용을 가장 정확하게 파악할 수 있는 방법입니다. 인계받은 장비를 신뢰하기 전에 이 명령을 확인하십시오.
패치 파일에 관한 한 가지 주의 사항이 있습니다. 여기서 설정은 단순한 데이터가 아닙니다. 형식상 플러그인의 config 블록 아래에 !!js 태그가 지정된 값을 허용하기 때문입니다. 포럼 게시물에서 복사한 cordis.patch.yml 스니펫은 코드이므로, 동일한 출처의 셸 스크립트를 다루는 것과 같은 방식으로 취급해야 합니다.
dsh plugin add은 실제로 무엇을 실행합니까?
dsh plugin --profile <name> <args>은 인수를 해당 프로필 디렉터리 내의 pnpm으로 전달하므로, pnpm이 PATH에 있어야 합니다. 사용되는 동사는 pnpm의 동사와 동일합니다.
dsh plugin --profile web add '<package-or-git-spec>'
dsh plugin --profile web remove '<package-name>'
dsh plugin --profile web why '<package-name>'
dsh plugin --profile web update따라서 dsh 플러그인을 설치하는 보안 모델은 npm 방식의 의존성을 설치하는 보안 모델과 동일하며, 결과물이 에이전트에 로드되는 추가 단계가 하나 더 포함됩니다. 패키지는 자체적인 의존성 트리를 가져오며, 해당 트리의 모든 패키지는 동일한 프로세스 내에서 실행됩니다. npm 공급망 공격이 서버에 도달하는 방식에 명시된 모든 내용은 수정 없이 그대로 적용됩니다.
pnpm 10 이상 버전은 기본적으로 의존성의 빌드 스크립트를 실행하지 않으며, onlyBuiltDependencies 또는 pnpm approve-builds을 통해 패키지별로 승인해야 합니다. 사용 중인 pnpm 버전을 확인하십시오.
pnpm --version이 기본 설정은 유지할 가치가 있으며, 생태계에서 가장 간과되기 쉬운 안전 기능이기도 합니다. 차단된 빌드 스크립트는 설치 과정에서 코드가 실행되는 것을 방지합니다. 하지만 플러그인 자체에 대해서는 아무런 조치를 취하지 않습니다. 플러그인의 목적 자체가 하네스가 이를 임포트하여 다음 부팅 시 호출하는 것이기 때문입니다. 플러그인은 postinstall 훅이 필요하지 않습니다. 이미 호출되도록 설계되었기 때문입니다.
dsh 플러그인을 설치하기 전에 읽어야 할 내용
배포된 tarball을 다운로드하여 내용을 확인하십시오. 아카이브를 압축 해제한다고 해서 코드가 실행되지는 않습니다.
npm pack '<package-name>@<version>'
tar -tzf '<package-name>-<version>.tgz'
tar -xzf '<package-name>-<version>.tgz'
less package/package.jsonpackage.json에 있는 4개의 필드는 필요한 정보의 대부분을 제공합니다. preinstall, install, postinstall 항목을 확인하려면 scripts을 읽으십시오. 익숙하지 않은 이름이나 기존 이름과 한 글자 차이인 이름은 dependencies에서 확인하십시오. 패키지가 PATH에 추가하려는 항목은 bin에서 확인하십시오. 진입점 파일은 main 또는 exports에서 확인한 뒤, 해당 파일을 열어 내용을 따라가 보십시오.
그다음 실제로 로드될 코드를 읽어보십시오. 알림 도구를 표방하는 플러그인이 ~/.ssh을 읽거나, 들어본 적 없는 호스트에 연결하거나, 셸을 생성할 이유는 없습니다. 패키지가 번들링되거나 최소화된 JavaScript만 제공하고 공개 저장소에 일치하는 소스 코드가 없다면, 그것이 곧 답입니다. 소스 코드를 읽을 수 있는 플러그인을 우선 선택하고, 가급적 규모가 작은 것을 선택하십시오.
아무것도 설치하지 않고도 레지스트리에 질의할 수 있습니다.
pnpm view '<package-name>' dependencies
pnpm view '<package-name>' versions지난주에 배포되었고, 버전은 하나뿐이며, 저장소 필드가 없고, 인기 있는 패키지의 이름을 도용하는 것은 모든 레지스트리에서 가장 흔히 쓰이는 수법입니다. 체크섬으로 다운로드 검증하기는 바로 옆에 있는 습관입니다. 코드를 실행하기 전에 무엇을 가져왔는지 정확히 파악하십시오.
버전을 고정하고 lockfile을 유지하십시오
유동적인 버전 범위는 에이전트 프로세스 내부의 코드가 사용자의 결정 없이도 설치나 업데이트 시마다 변경될 수 있음을 의미합니다. 버전을 고정하십시오.
dsh plugin --profile web add --save-exact '<package-name>@<version>'플래그 위치는 pnpm 버전에 따라 다르므로, 명령어를 맹신하지 말고 결과를 확인해야 합니다. 이후 프로필의 package.json를 열어 의존성 항목이 ^이나 ~ 기호 없이 순수한 버전 번호로만 표기되어 있는지 확인하십시오. 해당 파일이 실제 설치 내용을 결정합니다.
그런 다음 최상위 이름뿐만 아니라 전체 전이적 의존성 트리를 고정하는 lockfile을 유지하십시오.
find ~/.dsh -maxdepth 3 -name 'pnpm-lock.yaml'이 파일을 프로필의 package.json와 함께 백업 위치에 복사해 두십시오. 이 두 파일이 있으면 새로운 환경에서도 동일한 트리를 재구축할 수 있습니다. dsh plugin --profile web update은 버전을 변경하기로 결정했을 때만 실행하고, 일상적인 정리 작업으로 실행하지 마십시오. 실행 후에는 lockfile의 변경 사항(diff)을 확인하십시오.
레지스트리가 아닌 git에서 설치한 플러그인의 경우, 브랜치 대신 커밋을 고정하십시오. github:owner/repo#<full commit sha> 형식의 사양을 사용하면 고정된 트리를 얻을 수 있습니다. 브랜치 이름을 사용하면 pnpm이 다음번에 해석할 때 해당 브랜치가 가리키는 내용을 가져오게 되며, 이는 사용자가 타인에게 결정을 위임하는 꼴이 됩니다. 하네스 자체도 동일한 원칙을 요구합니다. 게시된 모든 dsh 빌드는 릴리스 후보이며, 고정되지 않은 설치는 매번 다른 버전으로 해석될 수 있기 때문입니다. 이것이 대부분의 dsh 설치 및 버전 오류가 발생하는 원인입니다.
플러그인 마켓과 "큐레이션"의 가치
dsh에는 마켓플레이스가 있으며, 이는 플러그인 형태로 설치됩니다. 이 구조를 보면 아키텍처의 특징을 알 수 있습니다.
dsh plugin --profile web add dshmarket재시작 후 Settings 메뉴 아래의 Plugin Market에서 확인할 수 있습니다. README에는 제한 사항이 명시되어 있습니다. 설치는 큐레이션된 레지스트리에 등록된 소스로만 제한되며, 그 외의 것은 거부됩니다. 빌드 스크립트는 기본적으로 차단되며, 이를 활성화하려면 패키지별로 승인이 필요합니다. 터미널 플러그인은 웹 프로필에 적용되기 전에 플래그가 지정됩니다. 가장 중요한 문구는 "목록 등재가 보증을 의미하지는 않는다"는 점입니다. 플러그인은 모두 제3자가 작성한 코드이기 때문입니다.
큐레이션된 목록은 최소한의 안전 기준을 높여줍니다. 하지만 코드를 대신 검토해주지는 않으며, 메인테이너 계정이 변경된 후 플러그인의 다음 버전이 어떤 동작을 할지 보장할 수 없습니다. 원클릭 설치는 동일한 작성자가 만든 curl | bash을 다룰 때와 같은 주의를 기울여야 합니다. README에서 한 번 더 강조할 만한 문구가 있습니다. 내보낸 백업 파일에는 프로필 설정에 포함된 자격 증명이 들어 있을 수 있으므로, 공개 이슈나 페이스트 사이트에 절대 첨부하지 마십시오. 방법론보다 추천 목록을 원하신다면 설치할 가치가 있는 dsh 플러그인을 이 글과 함께 참고하시기 바랍니다.
dsh를 root가 아닌 전용 사용자로 실행하기
검증은 악성 요소의 유입 빈도를 줄여줍니다. 최소 권한 원칙은 악성 요소가 유입되었을 때 도달할 수 있는 범위를 결정합니다. VPS 환경에서 후자는 적은 비용으로 설정할 수 있습니다.
harness를 위해 별도의 홈 디렉터리를 가진 유닉스 계정을 생성하고, 절대 root 권한으로 실행하지 마십시오:
sudo adduser --disabled-password --gecos "" dshrun
sudo -iu dshrun해당 세션 내에서 harness를 시작하여, 이 계정의 홈 디렉터리 아래에 데이터가 기록되도록 하십시오:
npx @deepseek-ai/dsh web웹 UI는 기본적으로 http://127.0.0.1:3080에서 서비스됩니다. 그대로 두십시오. 해당 포트에 도달할 수 있는 모든 대상은 셸 권한을 가진 에이전트를 제어할 수 있으므로, 3080 포트를 공개하는 것은 사용자 친화적인 인터페이스를 갖춘 root 권한 없는 원격 셸을 공개하는 것과 같습니다. 대신 SSH 터널을 통해 노트북에서 접속하십시오:
ssh -L 3080:127.0.0.1:3080 you@your-vps그런 다음 공용 주소에서 수신 대기 중인 서비스가 없는지 확인하십시오:
ss -lnt | grep 3080로컬 주소는 127.0.0.1:3080으로 표시되어야 합니다. 만약 0.0.0.0:3080로 표시된다면, 방화벽만이 외부 공격자와 에이전트 사이를 가로막고 있는 유일한 방어선인 상태입니다. VPS에서 Claude Code를 안전하게 실행하기에 담긴 논리는 dsh에도 그대로 적용됩니다. 에이전트가 망가뜨려도 되는 작업 디렉터리를 하나만 할당하고, 재구축할 수 없는 모든 데이터는 해당 머신에서 제외하십시오. 더 나은 방법은 해당 장비를 코딩 에이전트를 위한 일회용 VM으로 취급하는 것입니다. VPS를 재구축하는 데는 한 시간이 걸리지만, 보안을 감사하는 데는 일주일이 걸리기 때문입니다.
키가 저장되는 위치와 파일 권한의 한계
dsh는 API 키를 $DSH_HOME/.credentials.yaml에, 환경 변수 값을 $DSH_HOME/.env에 저장하며, 모델 설정은 $DSH_HOME/settings.yaml에, 세션 기록은 $DSH_HOME/storages에 보관합니다. 어떤 키를 어느 파일에 넣어야 하는지, 그리고 각 모드에서 실제로 외부로 전송되는 정보가 무엇인지에 대해서는 dsh의 API 키, 모델 및 엔드포인트 설정에서 다룹니다. 플러그인을 추가하기 전에 이 내용을 먼저 정리하는 것이 좋습니다. 연결하는 모든 키는 플러그인이 읽을 수 있는 대상이 하나 더 늘어나는 것과 같기 때문입니다. 다음 두 파일의 접근 권한을 엄격히 제한하십시오.
chmod 600 ~/.dsh/.credentials.yaml ~/.dsh/.env
ls -l ~/.dshMode 600은 소유자에게 읽기 및 쓰기 권한을 부여하고 다른 모든 사용자에게는 아무런 권한도 주지 않습니다. 이는 두 가지 표기법(숫자 및 기호 chmod 모드) 모두에서 알아둘 가치가 있습니다. 이 설정이 제공하는 보호 범위를 정확히 이해해야 합니다. 파일 모드는 해당 서버의 다른 계정으로부터 파일을 보호할 뿐입니다. 플러그인은 파일을 소유한 사용자의 권한으로, 파일을 읽는 프로세스 내부에서 실행되므로 파일 모드는 플러그인을 막지 못합니다. 이것이 바로 AI 에이전트가 접근할 수 없는 곳에 비밀 정보 보관하기가 중요한 이유이며, 애초에 기기에 비밀 정보를 두지 않아야 합니다. dsh 서버에는 필요한 모델 키 하나만 보관하십시오. 클라우드 자격 증명이나 서명 키는 다른 곳에 두어야 합니다.
웹을 읽는 플러그인이 위협 모델을 변화시키는 이유
웹 심(Web seam)은 플러그인에 검색 및 가져오기 공급자를 제공합니다. 세션으로 페이지를 가져오는 플러그인은 공격자가 작성할 수 있는 텍스트를 불러오는 것입니다. 모델 프롬프트는 지침과 데이터를 구분하지 않으므로, 가져온 페이지에 에이전트를 대상으로 하는 명령이 포함될 수 있습니다. 셸 기능을 보유한 하네스는 단 한 번의 순종적인 단계만으로 해당 명령을 실행할 수 있습니다.
제어 기능은 이미 하네스에 존재합니다. 모든 프로필의 첫 번째 번들인 dsh-base은 샌드박스와 승인 정책을 제공합니다. 이를 사용하십시오. 신뢰할 수 없는 페이지를 가져올 수 있는 세션은 쓰기나 실행이 발생하는 모든 작업에 대해 승인을 요구해야 하며, 이를 통해 가져온 지침이 독단적으로 동작으로 이어지지 않도록 해야 합니다. 에이전트 동작을 승인 뒤로 배치하기에서는 이러한 경계를 어디에 설정할지 고려하는 방법을 다룹니다. 이 관계는 양방향으로 작용합니다. 귀하의 서버 또한 누군가의 에이전트가 가져갈 페이지가 될 수 있기 때문이며, 이는 서버에서 AI 크롤러 차단하기에서 다루는 사례와 같습니다.
플러그인이 무엇을 변경했는지 확인하려면 어떻게 해야 합니까?
설치 전 스냅샷을 생성하고, 설치 후 스냅샷을 생성한 다음 차이점을 확인하십시오.
dsh --profile web --dump-config > /tmp/dsh-before.txt
dsh plugin --profile web add '<package-name>@<version>'
dsh --profile web --dump-config > /tmp/dsh-after.txt
diff -u /tmp/dsh-before.txt /tmp/dsh-after.txtdiff 결과는 설치 과정에서 구성된 트리에 어떤 플러그인 항목이 추가되었는지 보여줍니다. 작은 기능 하나를 위해 설치한 플러그인이 설명할 수 없는 여러 항목을 추가한다면, 부팅하기 전에 멈추고 소스 코드를 읽어봐야 합니다. dsh plugin --profile web why <package-name>은 어떤 직접 의존성이 특정 패키지를 불러왔는지에 대한 질문에 답을 줍니다.
설치된 패키지는 $DSH_HOME/profiles/node_modules 아래에 위치하므로, 디스크상의 트리 구조를 직접 확인할 수도 있습니다.
ls ~/.dsh/profiles/node_modules실험용으로 사용하지 않는 두 번째 프로필을 유지하십시오. 설치 후 하네스가 작동하지 않는다면, dsh --profile <clean-name>로 부팅하여 플러그인이 원인인지 즉시 확인할 수 있습니다.
dsh 플러그인은 어떻게 제거합니까?
dsh plugin --profile web remove '<package-name>'
dsh --profile web --dump-config > /tmp/dsh-after-removal.txt
diff -u /tmp/dsh-before.txt /tmp/dsh-after-removal.txt의존성을 제거한다고 해서 설정까지 항상 삭제되는 것은 아닙니다. 프로파일의 cordis.patch.yml에 기록된 항목은 그대로 남습니다. 해당 파일은 사용자의 소유이므로 하네스가 임의로 수정하지 않기 때문입니다. 파일을 열어 삭제한 패키지 이름이 포함된 블록을 직접 삭제하십시오.
less ~/.dsh/profiles/web/cordis.patch.yml그다음에는 제거 명령으로 해결할 수 없는 부분을 처리해야 합니다. 신뢰할 수 없다는 이유로 플러그인을 제거했다면, 해당 플러그인이 읽을 수 있었던 정보는 이미 유출되었을 가능성이 큽니다. 제공자 콘솔에서 DeepSeek API 키를 교체하고, $DSH_HOME에 저장되어 있던 다른 모든 정보도 함께 교체하십시오. 마지막으로 해당 플러그인이 실행되던 unix 계정이 네트워크의 다른 영역에 어디까지 접근할 수 있었는지 확인하십시오.
요약
- 설치 전 배포된 tarball을 읽으십시오.
scripts와 진입 파일부터 시작합니다. - 정확한 버전이나 git spec의 경우 정확한 커밋을 고정하고 lockfile을 유지하십시오.
- 하나의 프로필에 설치하고, 문제가 발생했을 때 부팅할 수 있는 깨끗한 프로필을 유지하십시오.
- 설치 전후로
--dump-config의 차이점을 확인하십시오. - 하네스를 별도의 unix 사용자로, loopback에서 실행하고, SSH를 통해 접속하십시오.
- 서버에 하나의 API key만 유지하고, 더 이상 신뢰하지 않는 플러그인을 제거하는 날에 해당 키를 교체하십시오.
이 중 어느 것도 플러그인 사용을 피해야 할 이유는 되지 않습니다. 플러그인 모델 덕분에 dsh가 유용하며, 확장할 수 없는 하네스는 결국 교체하게 될 하네스입니다. 이는 무엇을, 누구로부터, 어떤 버전으로 설치했는지 파악하고, 전체 환경을 재구축할 수 있는 곳에서 실행해야 할 이유입니다.
FAQ
Does dsh sandbox plugins from each other?
No. A plugin is loaded into the harness process through Cordis and can reach the documented capability seams, including shell, filesystem, web, subprocess, subagent and credentials. dsh-base, the first bundle in every profile, ships the sandbox and approval policy that governs what the agent's tools may do, and that policy is where your protection comes from. There is no per-plugin permission boundary, so the honest model is that installing a plugin extends your trust to its author and to every package in its dependency tree.
Can I install a dsh plugin without running its install scripts?
pnpm 10 and later block dependency build scripts by default, and dsh plugin ... add forwards to pnpm, so on a current pnpm the install does not run package scripts unless you approve that package. Confirm your version with pnpm --version. This does not make an unread plugin safe. The plugin's own code runs on the next boot because the harness loads it deliberately, which no install-time restriction affects.
Where do dsh plugins and their config actually live?
$DSH_HOME defaults to ~/.dsh. Profiles sit in $DSH_HOME/profiles/<name>, each holding a package.json with its plugin dependencies plus the dsh.profile manifest of ordered bundles, and a cordis.patch.yml patch layer. Installed packages land under $DSH_HOME/profiles/node_modules. Keys are in $DSH_HOME/.credentials.yaml, environment values in $DSH_HOME/.env, and a home-level $DSH_HOME/cordis.patch.yml applies over every profile. Run dsh --profile web --dump-config to see the composed result without booting.
Is it safe to install from the dsh plugin market?
The market restricts installs to sources on a curated registry and blocks build scripts unless you approve them per package, which is a real improvement over pasting a package name from a chat window. Its own README still states that listing is not endorsement, because the plugins are third-party code from other people. Read the source and pin the version. Keep the harness on a user account, and ideally a machine, you can afford to lose.