CLAWPOD ENGINEERING

에이전트 시크릿을 찾고, 위임하고, 지우는 법

Clawpod 메모리 시크릿의 검색·사용·회전·위임·삭제 흐름과 소유권, 세션, 감사 기록의 현재 보안 경계를 설명합니다.

메타데이터 카드와 실행 도구 사이에서 암호화 저장소와 수명이 정해진 위임 경로를 관리하는 작업대

API key, token, password를 일반 메모리(MEMORY.md), 작업 문서, 이슈에 적어 두면 검색과 협업 과정에서 값이 퍼질 수 있습니다. Memory Secrets는 보호 저장소의 값과 검색 가능한 설명을 분리하고, 값 대신 포인터를 선택한 뒤, 승인된 실행 순간에만 runtime이 해석하거나 주입하도록 돕습니다.

이 글은 현재 OpenClaw runtime의 도구 계약을 기준으로 한 실전 안내입니다. 도구 제공 범위와 비밀 입력 방식은 배포마다 다를 수 있으므로 운영자가 승인한 경로를 먼저 확인하세요.

1. Memory Secret과 catalog 이해하기

보호 저장소에는 credential 값이 들어가고, catalog와 검색 결과에는 값 없는 metadata가 나옵니다. 일반 메모리에는 포인터도 꼭 필요한 경우에만 기록하고 값은 기록하지 않습니다.

필드 용도
label 사람이 구분하는 이름
service, host 제품과 대상 위치
account credential이 속한 계정
kind, status token·password 같은 종류와 현재 상태
pointerId 값을 복사하지 않고 선택하는 식별자
description, purpose 운영 참고와 의도 설명

Metadata는 catalog, 검색, prompt 문맥에 보일 수 있습니다. 따라서 metadata에도 password, token, 복구 코드 같은 값을 넣지 마세요.

flowchart TD
  I[승인된 비밀 입력 경로] --> S[보호된<br/>memory-secret store]
  S --> M[값 없는 metadata<br/>catalog와 search]
  M --> P[정확한 pointer 선택]
  P --> R{누가 사용하나요?}
  R -->|소유 세션| U[즉시 resolve 또는<br/>runtime injection]
  R -->|하위 세션| G[세션에 묶인<br/>제한 위임]
  G --> U
  U --> T[대상 tool 또는 service]
  T --> V[결과 확인 후<br/>위임 revoke]
  linkStyle default fill:none,stroke:#64748b,stroke-width:1.5px

2. 일곱 도구의 역할

도구 언제 쓰나
memory_secret 새 값을 보호 저장소에 저장하고 구조화 metadata를 등록할 때
memory_secret_search label뿐 아니라 service, host, account, kind로 기존 항목을 찾을 때
memory_secret_get 소유 세션이 즉시 실행할 tool call에 값을 해석할 때
memory_secret_update 기존 포인터의 값을 회전하거나 metadata를 고칠 때
memory_secret_dedupe 저장소의 정확히 같은 값 중 정리 가능한 중복을 접을 때
memory_secret_delete 소유 Agent의 포인터와 위임을 현재 store에서 영구 삭제할 때
memory_secret_delegate child session에 포인터 접근 grant를 만들거나 revoke할 때

Catalog와 search는 발견, get과 runtime injection은 사용입니다. 발견 결과에는 값을 싣지 않고, 해석한 값을 출력·일반 메모리·prompt·보고서로 다시 복사하지 않는 것이 운영 원칙입니다.

3. 안전하게 저장하고 먼저 검색하기

  1. <available_memory_secrets> catalog에서 service, host, account를 확인합니다.
  2. 바로 보이지 않으면 memory_secret_search로 label, service, host, account, kind를 검색합니다.
  3. 그래도 없을 때만 새 credential이 필요한지 확인합니다.
  4. 자연어 대화에는 값이 아닌 metadata만 전달합니다.
  5. 실제 값은 배포 운영자가 제공한 승인된 보호 입력 경로에만 입력합니다. 일반 채팅, 문서, 이슈, 예제, 로그, shell history에는 넣지 않습니다.
  6. 저장 결과의 안전한 pointer metadata만 확인합니다.

안전한 요청 예시는 다음과 같습니다.

example-serviceoperator@example.com 계정용 API token을 저장해 주세요. Host는 api.example.test, kind는 api-token, status는 current입니다. 값은 <ENTER_IN_APPROVED_PROTECTED_INPUT> 경로로 제공하겠습니다. 대화에 값을 출력하지 마세요.”

문서와 테스트에는 TEST_ONLY_NOT_A_REAL_SECRET처럼 실제 credential로 오해하기 어려운 문자열만 사용하세요. 승인된 보호 입력 경로가 무엇인지 불분명하면 값을 받거나 저장하지 말고 운영자에게 확인해야 합니다.

4. 회전은 새 저장이 아니라 update입니다

값이 바뀌면 catalog에서 기존 포인터를 찾고 memory_secret_update로 회전합니다. Named field인 service, account, host, kind를 고치면 기존 metadata에 병합됩니다. 반면 raw metadata map을 전달하면 전체 map을 대체할 수 있어 빠진 항목이 사라질 수 있으므로 named field를 우선합니다.

example-service / operator@example.com 포인터를 찾아 새 값으로 회전해 주세요. 새 값은 <ENTER_IN_APPROVED_PROTECTED_INPUT>로 받으며 기존 pointerId를 유지하고, 안전한 metadata만 보고하세요.”

회전 전후 값은 서로 다른 값입니다. Dedupe가 회전된 두 값을 같은 credential로 임의 병합하지 않습니다.

5. 중복 제거는 핵심 수명주기 작업입니다

같은 session에서 같은 service/account로 동일한 값을 다시 저장하면 기존 포인터가 재사용될 수 있습니다. 과거에 쌓인 중복은 memory_secret_dedupe가 실제 값의 정확한 일치를 기준으로 정리합니다. 이름이 비슷하다는 이유만으로 합치지는 않습니다.

현재 보존 규칙은 다음과 같습니다.

  • 같은 값 그룹에서는 최신 포인터를 유지합니다.
  • 활성 delegation이 걸린 포인터는 진행 중인 child 작업을 끊지 않도록 유지합니다.
  • 같은 값이어도 service 또는 account가 다르면 별도 credential로 유지합니다.
  • 서로 다른 값과 회전된 값은 변경하지 않습니다.

Dedupe는 소유 Agent의 저장소에만 작용합니다. 실행 전 catalog/search 결과를 검토하고, 실행 뒤에는 유지·제거된 pointer ID만 확인하며 값을 보고서에 싣지 않습니다.

미리 보기: “중복 후보를 값 없이 service/account와 pointerId로만 정리해 보여 주세요. 아직 변경하지 마세요.”

실행: “보존 규칙을 적용해 내 memory-secret store의 중복을 정리하고, 유지·제거된 pointerId만 보고하세요.”

6. 삭제와 legacy tombstone 정리

memory_secret_delete는 owner Agent가 소유한 포인터와 그 위임을 현재 memory-secret store에서 물리적으로 제거합니다. 만든 세션이 끝났더라도 같은 owner Agent는 삭제할 수 있습니다. 이 작업은 현재 store에서 복구할 수 없으며, backup, snapshot, 입력 채널, 외부 서비스에 생긴 사본까지 지운다는 뜻은 아닙니다.

먼저 목록과 의존 작업을 확인하세요.

미리 보기: “example-service의 폐기 후보를 pointerId와 metadata로만 보여 주세요. 삭제하지 마세요.”

확인 후 실행: “확인한 pointer <POINTER_ID_FROM_CATALOG>를 현재 store에서 hard delete하세요. 관련 delegation도 제거됨을 알리고 값은 출력하지 마세요.”

예전 soft-delete 방식으로 남은 tombstone은 store에 다음 write가 일어날 때 정리될 수 있습니다. 별도 파일 수정이나 임의 purge를 시도하지 마세요. 정상 포인터 삭제와 legacy tombstone cleanup은 구분해야 합니다.

7. Child Agent에는 값을 보내지 말고 grant를 위임하기

Owner에게 암호화된 msp_... 포인터는 다른 독립 Agent에게 보내도 그 Agent가 쓸 수 없고, 전달 자체도 피해야 합니다. 부모가 만든 실제 child session에는 다음 순서로 위임합니다.

  1. child를 먼저 만들고 반환된 정확한 childSessionKey를 확인합니다.
  2. pointerId + childSessionKey로 grant를 만듭니다.
  3. 기본 grant는 child session 수명 동안 유효합니다. 더 짧게 제한하려면 ttlSeconds, 한 run에 묶어야 할 때만 고급 옵션 childRunId를 사용합니다.
  4. Child는 허용된 session에서 memory_secret_get 또는 runtime injection으로 직접 사용합니다. 부모가 값을 메시지에 넣지 않습니다.
  5. 결과를 확인하면 revokeDelegationId로 즉시 revoke합니다. Secret 자체를 삭제하면 연결된 위임도 제거됩니다.

purposeallowedTargets는 문서용 메모이며 현재 접근 제어로 강제되지 않습니다. “배포 전용”이라고 적는 것만으로 다른 대상 사용이 차단되지는 않습니다. 실제 경계는 child session/run binding, TTL, revoke와 대상 tool 권한입니다.

위임: “<POINTER_ID_FROM_CATALOG>를 방금 만든 child session <EXACT_CHILD_SESSION_KEY>에 15분만 위임하세요. 값을 메시지로 보내지 말고 delegation ID만 보고하세요.”

회수: “작업 결과를 확인했습니다. delegation <DELEGATION_ID>를 revoke하고 상태만 보고하세요.”

8. 이제 어떻게 하면 잘 쓰나

Memory Secrets를 잘 쓰려면 두 관점을 함께 지켜야 합니다. **운영자(사용자)**는 Agent가 같은 credential을 다시 찾고 안전하게 갱신할 수 있도록 값이 아닌 문맥을 충분히 제공해야 합니다. Agent는 catalog와 metadata를 먼저 확인하고, 실제 값은 승인된 보호 경로와 필요한 runtime 경계 안에서만 다뤄야 합니다.

운영자 관점: 값을 설명하지 말고 credential의 문맥을 설명하기

  • service, account, host, kind를 가능한 한 구체적으로 알려 주세요. 같은 서비스에 계정이 여러 개거나 같은 계정이 여러 host에 쓰일 때 특히 중요합니다.
  • “새로 저장해 줘”보다 “기존 항목을 catalog와 metadata에서 먼저 검색해 줘”라고 요청하세요.
  • 값이 바뀌면 “다시 저장”이 아니라 “찾은 pointer를 update/rotate해 줘”라고 요청하세요. 그래야 기존 참조와 수명주기가 이어집니다.
  • Dedupe 전에는 합쳐질 후보와 보존될 항목을, delete 전에는 정확한 pointer와 active delegation을 먼저 보여 달라고 요청하세요.
  • Hard delete는 현재 보호 store의 해당 owner-Agent record와 active delegation을 되돌릴 수 없게 제거합니다. 백업이나 외부 사본까지 지운다는 뜻은 아닙니다. 범위를 확인하고 명시적으로 승인하세요.
  • 실제 credential 값은 배포 운영자가 승인한 보호 입력 또는 secret-storage 경로로만 입력하세요. Ordinary chat, 문서, issue, 로그, source code에는 넣지 마세요.

Agent에게 보낼 일반 메시지에는 값 대신 다음처럼 label이 붙은 문맥 블록을 사용하면 좋습니다.

[Credential metadata — documentation-only]
Title: <HUMAN_READABLE_LABEL>
URL: https://service.example.com/path
Host: service.example.com
Email: operator@example.com
Account: <ACCOUNT_OR_USERNAME>
ID: <OPTIONAL_NON_SECRET_ID>
kind: <password|token|api-key|oauth|email|credential>
service: <SERVICE_NAME>
description: <NON_SECRET_PURPOSE_AND_SCOPE>
value: <ENTER_ONLY_THROUGH_APPROVED_PROTECTED_INPUT>

Request: Search the catalog and metadata first. If this credential exists, update/rotate that pointer instead of storing another copy.

value 행은 실제 값을 붙여 넣는 자리가 아니라 보호 입력이 별도로 필요함을 표시하는 label입니다. 실제 값은 이 블록과 분리된 승인 경로로 전달해야 합니다.

문맥 블록을 structured fields로 옮기는 방법

블록 항목 Structured field 작성 기준
Title label 사람이 목록에서 구분할 수 있는 짧은 이름. 값이나 token 일부를 넣지 않습니다.
URL 주로 host, 필요 시 description URL에서 hostname만 host로 정규화하고, non-secret path 문맥이 꼭 필요할 때만 description에 적습니다. Query나 fragment에는 credential을 넣지 않습니다.
Host host 서버 IP 또는 hostname. 문서 예시는 reserved IP나 example.com 하위 도메인을 사용합니다.
Email account 서비스 로그인 identity가 email일 때 사용합니다.
Account account Username, bot handle, tenant 계정처럼 서비스를 사용하는 identity입니다.
ID account 또는 description 로그인 identity면 account, 단순 non-secret resource ID면 description에 역할과 함께 기록합니다.
kind kind password, token, api-key, oauth, email, credential처럼 credential 유형을 일관되게 씁니다.
service service Atlassian, SSH, Claude Code, LiteLLM처럼 시스템 또는 vendor를 식별합니다.
value 보호 store의 secret value Ordinary chat이나 metadata에 복사하지 않고 승인된 보호 입력/secret-storage 경로로만 저장합니다.
description description 또는 purpose 비밀값 없이 용도와 범위를 설명합니다. Runtime delegation의 purpose와 혼동하지 마세요.

문서용 입력 예시

아래 값은 모두 문서 전용 placeholder입니다. 실제 credential처럼 사용하거나 일반 메시지의 placeholder를 실제 값으로 바꾸지 마세요.

Atlassian API token

[Credential metadata — documentation-only]
Title: Atlassian API token for documentation workspace
URL: https://example.atlassian.net
Host: example.atlassian.net
Email: docs-operator@example.com
Account: docs-operator@example.com
kind: api-key
service: atlassian
description: Documentation workspace automation
value: <ENTER_ONLY_THROUGH_APPROVED_PROTECTED_INPUT>

SSH server password

[Credential metadata — documentation-only]
Title: Example staging SSH credential
Host: 192.0.2.44
Account: deploy-example
kind: password
service: ssh
description: Documentation-only staging host in TEST-NET-1
value: <ENTER_ONLY_THROUGH_APPROVED_PROTECTED_INPUT>

Claude setup token

[Credential metadata — documentation-only]
Title: Claude Code setup token for example operator
Host: console.example.com
Account: claude-operator@example.com
kind: oauth
service: Claude Code
description: Documentation-only CLI onboarding
value: <ENTER_ONLY_THROUGH_APPROVED_PROTECTED_INPUT>

LiteLLM host SSH key or password

[Credential metadata — documentation-only]
Title: LiteLLM example host SSH credential
URL: https://litellm.example.com
Host: 198.51.100.27
Account: litellm-admin-example
kind: credential
service: litellm-ssh
description: Documentation-only administration of the example LiteLLM host
value: <ENTER_ONLY_THROUGH_APPROVED_PROTECTED_INPUT>

Portal login

[Credential metadata — documentation-only]
Title: Example customer portal login
URL: https://portal.example.com/login
Host: portal.example.com
Email: portal-user@example.com
Account: portal-user@example.com
ID: customer-example-001
kind: password
service: example-portal
description: Documentation-only portal login; ID is a non-secret resource identifier
value: <ENTER_ONLY_THROUGH_APPROVED_PROTECTED_INPUT>

Agent 관점: catalog에서 시작해 수명주기를 닫기

  1. Catalog first: 현재 제공된 safe catalog metadata에서 service, account, host, kind가 맞는 후보를 먼저 찾습니다.
  2. Search before declaring missing: catalog에 명확한 항목이 없으면 같은 metadata로 memory_secret_search를 실행합니다. 검색이 비었을 때만 새 credential이 필요한지 운영자에게 확인합니다.
  3. Save with structure: 새 값을 저장할 때 label, service, account, host, kind, status, purpose를 일관되게 채웁니다. Metadata에는 실제 값이나 값의 일부를 넣지 않습니다.
  4. Rotate by update: 값 또는 문맥이 바뀌면 memory_secret_update로 owner Agent의 기존 pointer를 회전합니다. Raw metadata map은 전체 교체이므로 named fields를 우선합니다.
  5. Dedupe without crossing identity boundaries: Exact same value의 중복만 다루고, 가장 새 pointer를 유지하되 active delegation이 있는 pointer는 보존합니다. 같은 값이어도 service/account가 다르면 별도 credential로 유지하며, distinct 또는 rotated value는 건드리지 않습니다.
  6. Delete only in the scoped store: 삭제 전 pointer를 재확인합니다. Hard delete는 현재 보호 store에 있는 현재 owner Agent의 record와 그 active delegations을 영구 제거합니다. 백업, 외부 시스템, 문서나 로그에 이미 생긴 사본까지 삭제한다고 주장하지 않습니다.
  7. Resolve only for immediate use: Runtime tool call 직전에 필요한 pointer를 resolve하거나 runtime injection을 사용합니다. Resolve된 값은 응답, ordinary prompt/chat, 로그, 일반 memory, 보고서, 파일, issue, source code, 검색 index에 출력하거나 옮기지 않습니다.
  8. Delegate narrowly and revoke: 실제 child session의 정확한 childSessionKey에 grant하고, 필요하면 짧은 ttlSeconds 또는 고급 childRunId binding을 사용합니다. purposeallowedTargets는 advisory 문서 필드일 뿐 강제 경계가 아닙니다. 결과를 확인한 뒤 revokeDelegationId로 revoke하며, owner-encrypted pointer나 값을 독립 Agent에게 보내지 않습니다.

9. 자주 묻는 질문

Q. Catalog나 검색에 실제 값이 나오나요?

아니요. 도구 계약상 안전한 metadata와 pointer ID를 발견하는 단계입니다. 다만 metadata는 보일 수 있으므로 그 필드에도 비밀값을 넣지 않아야 합니다.

Q. 값을 어디에 입력해야 하나요?

배포 운영자가 승인한 보호 입력 경로를 사용합니다. 그런 경로가 확인되지 않았다면 ordinary chat, 문서, issue, shell history에 값을 붙여 넣지 말고 먼저 확인하세요.

Q. 같은 password를 여러 service에서 쓰면 dedupe가 합치나요?

Service/account가 다르면 별도 credential로 보존합니다. 활성 delegation이 있는 포인터도 보존하며, 서로 다른 값이나 회전된 값은 건드리지 않습니다.

Q. Hard delete 뒤 복구할 수 있나요? Tombstone은 언제 없어지나요?

현재 store에서는 복구할 수 없습니다. Legacy tombstone은 이후 store write에서 정리될 수 있지만, store 밖의 사본은 별도 정책으로 처리해야 합니다.

Q. 위임은 자동으로 안전한 target에만 제한되나요?

purposeallowedTargets는 강제 정책이 아닙니다. 정확한 child session, 필요 시 run, 짧은 TTL, 명시적 revoke와 target tool 권한을 함께 사용해야 합니다.

Memory Secrets는 모든 환경에서 평문이 사라진다고 보장하는 기능이 아닙니다. 값 없는 metadata로 발견하고, 승인된 보호 입력과 제한된 runtime 경로로 사용하며, 회전·dedupe·revoke·delete로 수명주기를 닫는 운영 방식입니다.