CodeGym /행동 /ChatGPT Apps /접근 제어와 권한 최소화: scopes, 세분화, per‑tool permissions

접근 제어와 권한 최소화: scopes, 세분화, per‑tool permissions

ChatGPT Apps
레벨 15 , 레슨 0
사용 가능

1. 왜 ChatGPT‑App에서 권한을 고민해야 하는가(그리고 왜 특히 위험한가)

“일반적인” 웹 애플리케이션에서는 사용자와 데이터베이스 사이에 프런트엔드, API, DB 정도의 몇 개 층만 있습니다. ChatGPT‑App에서는 사용자와 API 사이에 또 하나의 능동적 참여자 — LLM — 이 생깁니다. 이는 단순한 “텍스트 필터”가 아니라 다음과 같은 특성을 가진 주체입니다:

  • 스스로 결정하여 어떤 도구를 어떤 인자로 호출할지 선택합니다.
  • 데이터 내 프롬프트 인젝션속을 수 있습니다.
  • 도구를 혼동하거나, 당신이 예상하지 못한 인자를 만들어낼 수 있습니다.

LLM에 과도한 권한을 주면, 클래식한 Confused Deputy 문제가 발생합니다: 모델은 사용자나 문서의 텍스트가 요청한다고 “판단”하는 것을 성실히 수행하지만, get_last_order 대신 delete_all_orders를 호출할 수 있습니다.

따라서 우리의 목표:

  1. 최소화: auth_token의 권한(접근 가능한 데이터와 가능한 행동의 범위)을 최소화합니다.
  2. 제한: 특정 시나리오에서 모델이 사용할 수 있는 도구 자체를 제한합니다.
  3. 추가: 결과가 특히 치명적인 곳에는 사람의 확인 단계를 추가합니다.

그리고 이는 편집증 수준의 전면 금지 없이, App의 유용성을 해치지 않는 선에서 이루어져야 합니다. 편의성과 보안 사이의 균형 — 이 모듈의 핵심 과제입니다.

2. 에코시스템에서의 접근 모델: 누가 무엇에 접근하는가

헷갈리지 않도록 시스템 전체를 살펴봅시다. 각기 책임 범위와 권한이 다른 여러 레벨이 있습니다.

flowchart TD
  U[ChatGPT의 사용자] --> C[ChatGPT UI + LLM]
  C --> A["당신의 App(시각적 플랜 + 위젯)"]
  A --> G[MCP Gateway / API Edge]
  G --> S[MCP 서버와 마이크로서비스]
  S --> D[데이터베이스, 큐, 외부 API]

역할 요약:

  • ChatGPT UI와 LLM: OpenAI가 운영합니다. 당신은 이들에게 지시(system‑prompt, tool descriptions)를 제공할 수 있지만, 플랫폼의 내부 토큰과 권한은 제어할 수 없습니다.
  • 당신의 App(플랜, tools, 위젯): 어떤 도구를 노출할지, 어떻게 설명할지, 어떤 UX 확인이 필요한지, 위젯이 어떤 데이터를 보여줄지 등을 결정합니다.
  • MCP Gateway / API Edge: 여기서 토큰 검증, userId, tenantId, scopes 목록의 매핑과 적절한 서비스로의 라우팅이 이뤄집니다.
  • MCP 서버와 마이크로서비스: 도구를 실행하고, DB와 외부 API에 요청을 보냅니다. 여기서는 scopes, tenant 격리, 입력 데이터 검증 등 가장 엄격한 검사가 적용되어야 합니다.
  • 스토리지와 외부 API: 최후의 방어선(데이터베이스 권한, 외부 서비스 계정 권한 수준).

핵심: LLM은 접근 권한의 출처가 아닙니다. MCP 서버로 들어오는 모든 것은 “모델이 구성한 사용자 요청”으로 간주합니다. 실제로 작업을 수행할 수 있는지 결정하는 것은 프롬프트가 아니라 당신의 백엔드 코드의 의무입니다.

3. AuthN vs AuthZ: 이미 아는 것과 이제 추가할 것

인증 모듈에서 이미 했던 것:

  • AuthN(Authentication) — 사용자가 누구인지 확인. OAuth 2.1/PKCE를 통해 ChatGPT가 IdP에서 토큰을 받아 MCP 호출에 첨부했습니다. 그 안에는 sub, user_id 등의 클레임이 있었고, 때로는 tenant_id도 있었습니다.
  • 기본 AuthZ — 이미 user/admin 역할을 구분하고, 최소한 “일반 사용자냐” 또는 “관리자냐”를 검사했을 수 있습니다.

이제 난이도를 올립니다:

  • auth_tokenscopes 집합(문자열 권한) — 즉 resource:action 형태를 가져야 합니다. 예: catalog:read, orders:write, payments:create;
  • MCP 서버는 이 scopes각 행동에 적합한지 매번 검사해야 하며, “한 번만 입구에서” 검사해선 안 됩니다;
  • 서로 다른 도구, 심지어 하나의 도구 내에서도 서로 다른 작업은 서로 다른 scopes를 요구할 수 있습니다.

OAuth 2.1 용어로 보면 ChatGPT는 “public client”, MCP는 “resource server”이며, 당신의 OAuth 서버는 어떤 scopes가 지원되고 무엇을 의미하는지 알고 있습니다. MCP 리소스의 메타데이터는 보통 scopes_supported를 선언하여, ChatGPT가 사용자에게 정확히 필요한 권한만 요청하도록 합니다.

4. GiftGenius를 위한 scopes 설계

우리의 학습용 GiftGenius에서 데이터 도메인과 행동을 살펴봅시다. 기능은 대략 다음과 같습니다:

  • 카탈로그와 선물 카드 조회;
  • 히스토리를 기반으로 한 추천;
  • 주문 생성;
  • 체크아웃 시작 / 결제 청구;
  • 관리자용 카탈로그 편집.

만능 giftgenius:full_access 하나로 만들기보다, 이를 합리적인 scopes로 분해하는 편이 낫습니다.

이름 규칙: resource:action

resource:action 전략이 효과적입니다. 여기서:

  • resource는 도메인을 나타냅니다: catalog, recommendations, orders, payments, admin.
  • action은 행동의 종류를 나타냅니다: read, write, 때로는 더 구체적으로 create, delete, manage.

GiftGenius 예시:

Scope 허용하는 것
catalog:read
공개 선물 카탈로그 읽기
recommendations:read
사용자의 추천 히스토리 읽기
orders:write
새 주문 생성
orders:read
사용자의 주문 히스토리 읽기
payments:create
결제/체크아웃 시작
catalog:admin
카탈로그 편집(관리자 UI/지원 전용)

일반 GiftGenius 사용자는 다음(공백으로 구분) 정도가 필요합니다: catalog:read recommendations:read orders:write orders:read payments:create. 관리자에게는 catalog:admin을 추가합니다.

중요: 범용 *:* 또는 admin:all은 만들지 않습니다. 더 세분화되어 있을수록, 전체 앱을 망가뜨리지 않고도 특정 권한만 철회하기 쉽습니다.

Scope 유형: read vs write vs critical

scopes를 머릿속으로 범주화하면 도움이 됩니다:

  • 안전(read): 상태를 변경하지 않으며, 최대한 데이터를 노출합니다;
  • 변경(write): 엔티티를 생성/수정하지만, 돈을 건드리거나 무차별 삭제하지는 않습니다;
  • 치명(critical): 결제, 계정 삭제, 대량 데이터 삭제 등.

치명 권한에는 강화된 통제를 적용할 수 있습니다:

  • 최소한의 사용자에게만 부여;
  • 토큰 발급 시 ChatGPT UI에서 별도 동의를 요구;
  • MCP 측에서 추가 확인(예: 일회성 PIN — 고급 시나리오) 요구.

코드에서의 scopes: RequestContext와 requireScope

MCP 레벨에서 통합 컨텍스트 타입을 두면 편리합니다:

// mcp/context.ts
export interface RequestContext {
  userId: string;        // 누가
  tenantId: string;      // 어느 조직 내에서
  scopes: string[];      // 토큰에 부여된 권한
}

// 권한 검사용 간단한 헬퍼
export function requireScope(
  ctx: RequestContext,
  needed: string
) {
  if (!ctx.scopes.includes(needed)) {
    throw new Error(`Missing scope: ${needed}`);
  }
}

RequestContext는 토큰 검증 이후 MCP Gateway에서 구성합니다: JWT를 디코딩하고 서명/만료를 확인한 뒤 sub, tenant, scope를 추출하여, 모든 도구 호출에 이 컨텍스트를 첨부합니다.

툴 핸들러에서는 다음과 같이 사용합니다:

// mcp/tools/createOrder.ts
import { requireScope, RequestContext } from "../context";

export async function createOrder(
  input: CreateOrderInput,
  ctx: RequestContext
) {
  requireScope(ctx, "orders:write");
  // 이후 - 주문 생성 로직
}

이제 UX상 기대하지 않았던 순간에 모델이 갑자기 createOrder를 호출하더라도, orders:write가 없으면 도구는 실행되지 않습니다.

도구 레벨의 securitySchemes

MCP 사양은 각 도구가 어떤 인증 스킴과 scopes를 필요로 하는지 명시할 수 있게 합니다. 공식 예시에서는 securitySchemes가 도구 설명에 바로 붙습니다.

예시(가정):

// mcp/server.ts
server.registerTool(
  "createOrder",
  {
    title: "Create order",
    description: "Creates a new order for current user",
    inputSchema: {/*...*/},
    securitySchemes: [
      { type: "oauth2", scopes: ["orders:write"] }
    ]
  },
  async ({ input }, ctx: RequestContext) => {
    requireScope(ctx, "orders:write");
    // ...
  }
);

여기에는 두 겹의 보호가 있습니다:

  • 선언적: ChatGPT는 이 도구에 orders:write가 필요함을 알고, 권한이 없으면 인증 플로우를 시작(또는 사용자에게 알림)합니다;
  • 명령적: 실제 동작 전에 당신의 코드가 한 번 더 모든 것을 확인합니다.

토큰은 있지만 scopes가 부족할 경우, 서버는 WWW-Authenticate 헤더로 Bearer error="insufficient_scope", scope="orders:write" 를 반환해야 하며 — ChatGPT는 사용자에게 권한 확장을 요청(step‑up authorization)할 수 있습니다.

Insight

공식 예시에서는 securitySchemes를 사용합니다. 이는 ChatGPT Apps SDK의 예시에 적힌 형태로는 공식 사양에 아직 확정되지 않았습니다. 따라서 이를 공식 프로토콜의 확장으로 표시해야 하며 — _meta로 감싸야 합니다. 위 예시의 동작하는 형태:

// mcp/server.ts
server.registerTool(
  "createOrder",
  {
    title: "Create order",
    description: "Creates a new order for current user",
    inputSchema: {/*...*/},
    _meta: {										// 이렇게
      securitySchemes: [
        { type: "oauth2", scopes: ["orders:write"] }
      ]          
    }
  },
  async ({ input }, ctx: RequestContext) => {
    requireScope(ctx, "orders:write");
    // ...
  }
);

5. Per‑tool permissions와 ‘위험한’ 도구

Scopes는 “이 auth_token이 원칙적으로 무엇을 할 수 있는가”에 답합니다. 그러나 토큰 안에는 모델이 사용할 수 있는 도구 목록 또한 있습니다. 이 목록도 신중히 설계해야 합니다.

도구 분류

도구를 다음과 같이 나눌 수 있습니다:

  • 정보형(informational / read‑only): 데이터를 읽고, 리포트를 만들고, 부작용 없이 계산;
  • 행위형(consequential): 상태를 변경, 결제 청구, 삭제 등의 영향 발생.

ChatGPT Apps 문서는 read‑only 도구에 대해서는 안전함을 명시하고, 위험한 도구에 대해서는 결과를 서술하며 추가 UX 확인을 넣을 것을 권장합니다.

이것은 다음과 같이 할 수 있습니다:

  • 도구에 대한 애노테이션(예: readOnlyHint, destructiveHint 같은 필드);
  • 텍스트 설명을 통한 명시: “이 도구는 주문을 되돌릴 수 없이 삭제합니다”;
  • 별도 플래그 confirmation_required로, App 플랜이 대화에 확인 단계를 삽입하도록 함.

치명적 행동에 대한 UX 확인

예를 들어 GiftGenius에는 chargeCustomer(결제 청구 시작) 도구가 있습니다. 사용자의 동의 없이 모델이 이를 호출하는 상황은 원치 않을 것입니다.

App 플랜 레벨에서의 모양새(예시):

// app/plan/tools.ts (의사코드)
export const tools = [
  {
    name: "giftgenius.list_catalog",
    description: "선물 카탈로그를 보여줍니다",
    annotations: { readOnlyHint: true }
  },
  {
    name: "giftgenius.create_order",
    description: "결제 없이 주문을 생성합니다",
    annotations: { consequential: true }
  },
  {
    name: "giftgenius.charge_customer",
    description: "주문에 대해 결제를 청구합니다",
    annotations: {
      consequential: true,
      destructiveHint: true,
      confirmationRequired: {
        title: "카드에서 금액을 청구할까요?",
        message: "주문 N에 대해 결제가 진행됩니다."
      }
    }
  }
];

구체적인 필드 이름은 SDK 버전에 따라 다르지만, 아이디어는 권장사항과 같습니다: read‑only 도구는 안전하다고 표시하고, 위험한 도구는 명확한 설명과 함께 명시적 확인을 요구합니다.

그 다음 위젯은 이렇게 반응할 수 있습니다: 모델이 charge_customer 호출을 제안하면, 사용자에게 이해하기 쉬운 문구의 모달을 보여 주고 “확인” 클릭 이후에만 실제 tool‑call을 수행합니다.

위젯의 컴포넌트 예시(단순화):

// widget/components/ConfirmCharge.tsx
export function ConfirmCharge(props: {
  orderId: string;
  onConfirm: () => void;
}) {
  return (
    <div>
      <p>주문 {props.orderId}에 대해 결제하시겠습니까?</p>
      <button onClick={props.onConfirm}>
        예, 결제를 확인합니다
      </button>
    </div>
  );
}

모델은 “이제 결제할 때”라는 아이디어를 제시하지만, 최종 버튼 클릭은 사람이 합니다. 이것이 바로 보안 담당자들이 좋아하는 human‑in‑the‑loop입니다.

에이전트/백오피스 전용 도구

또 다른 흔한 사례: 특정 도구는 에이전트(Agents SDK의 의미)나 내부 관리자 도구만 사용 가능해야 하고, “일반” ChatGPT App 사용자에게는 노출되면 안 됩니다.

예: rebuildSearchIndexsyncCatalogFromERP. 다음과 같이 하는 것이 좋습니다:

  • 일반 App의 tools 목록에는 포함하지 않기;
  • 별도의 에이전트/오케스트레이터에 구성하기;
  • 별도 scopes 및(가능하다면) 별도의 인증 경계로 보호하기.

만약 이를 App의 사용 가능한 도구 목록에 그냥 추가해 두면, 모델이 “지금 인덱스를 재생성하면 선물을 더 잘 찾을지도?”라고 판단해 갑작스레 호출할 위험이 높아집니다.

6. 네트워크 세분화와 신뢰 경계

권한은 토큰의 scopes만이 아닙니다. 두 번째 큰 축은 네트워크/서비스의 세분화입니다.

이상적인 그림:

  • 백엔드로의 공개 진입점은 오직 하나 — MCP Gateway/Edge API;
  • PII와 돈을 다루는 것은 모두 프라이빗 네트워크/VPC에 있고 이 게이트웨이를 통해서만 접근 가능;
  • 백엔드의 외부로 나가는 트래픽은 허용된 도메인 목록(allowlist: 결제사, CRM, 자체 마이크로서비스)으로 제한.

도식화:

flowchart LR
  ChatGPT -- HTTPS --> Edge[API Gateway / MCP Endpoint]
  Edge -- private network --> MCP[MCP server]
  MCP -- private --> DB[(PII가 있는 DB)]
  MCP -- private --> SVC[Internal microservices]
  MCP -- HTTPS (allow) --> Stripe[Payments API]

여기서 중요한 규칙들:

  1. DB와 내부 서비스는 인터넷에 직접 노출되지 않습니다. 오직 프라이빗 네트워크에서, 그리고 정말 필요한 서비스에서만 직접 접근합니다.
  2. Edge/Gateway에서 인증과 rate‑limiting을 수행합니다. 여기서 토큰과 scopes를 검사하고, 과도한 요청을 제한하며, 핵심 감사 로그를 기록합니다.
  3. Egress 통제. MCP 서버는 인터넷의 임의 URL로 나갈 수 없어야 합니다(SSRF 공격, 데이터 유출). 외부 호스트 목록을 명시적으로 제한하는 편이 좋습니다.

실무에서는 MCP를 Vercel, Render 또는 Kubernetes 클러스터에 배포할 때 일부를 수동으로 설정하기 어렵기도 하지만, 그럼에도 다음을 분리할 수 있습니다:

  • dev/staging/prod를 위한 별도 프로젝트/클러스터;
  • 각 환경별 다른 환경 변수와 키;
  • 별도의 “edge” 서비스(HTTP 래퍼 MCP)와 별도의 프라이빗 서비스.

결론적으로, 지금까지 토큰 권한(scopes)과 네트워크 경계라는 두 축의 보호가 있습니다. 여기에 동일한 App이 여러 조직을 서비스하는 다중 테넌시를 추가합니다.

7. Multi‑tenant / 조직 컨텍스트

지금까지는 한 명의 사용자를 상정했습니다. 하지만 많은 ChatGPT 애플리케이션은 멀티 테넌트입니다: 동일한 App이 수십 개 회사를 서비스합니다. GiftGenius는 각 부서가 각자 카탈로그, 예산, 주문을 가지는 B2B 서비스로 쉽게 전환 가능합니다.

테넌트란 무엇이며 어디서 얻는가

테넌트(tenant)는 보통 다음 중 하나입니다:

  • 조직/회사(Acme Corp);
  • 워크스페이스(workspace);
  • 때로는 프로젝트나 환경.

핵심 속성: 한 테넌트의 데이터는 다른 테넌트에서 볼 수 없어야 합니다.

인증 플로우에서 테넌트는 보통 다음에 담깁니다:

  • 토큰의 클레임(tenant, org_id);
  • 인가 요청의 별도 파라미터(다만 IdP가 서명한 클레임보다 신뢰도 낮음).

중요: 검증된 토큰의 tenantId만 신뢰하고, 도구 인자에서 온 값은 신뢰하지 않습니다. 모델이 {"tenantId": "acme"}를 생성했지만, 사용자 토큰에는 tenantId: "globex"라면, 이는 공격 시도로 간주되어야 합니다.

요청 컨텍스트의 테넌트

우리의 RequestContexttenantId를 추가하고(위에서 이미 추가함), 입력 데이터가 이를 덮어쓰도록 허용하지 않습니다.

기본 검사:

// mcp/tenant.ts
import { RequestContext } from "./context";

export function enforceTenant<TInput>(
  input: TInput & { tenantId?: string },
  ctx: RequestContext
) {
  if (input.tenantId && input.tenantId !== ctx.tenantId) {
    throw new Error("Tenant mismatch");
  }
  return { ...input, tenantId: ctx.tenantId };
}

도구 내에서는 다음과 같이 사용합니다:

// mcp/tools/listOrders.ts
export async function listOrders(
  input: { limit?: number; tenantId?: string },
  ctx: RequestContext
) {
  const safe = enforceTenant(input, ctx);
  return db.order.findMany({
    where: { tenantId: safe.tenantId },
    take: safe.limit ?? 20
  });
}

우리는 인자에 들어온 tenant를 무시하고, 컨텍스트의 값을 강제 주입합니다. 따라서 LLM이나 공격자가 다른 tenant를 “끼워 넣으려” 해도 아무 효과가 없습니다.

DB 레벨의 테넌트 격리

아키텍처적으로 다음과 같은 선택지가 있습니다:

  • 테넌트별 독립 DB;
  • 분리된 스키마;
  • 각 테이블에 tenant_id를 두고 엄격히 필터링하는 단일 DB.

무엇을 선택하든 황금률은 하나: 컨텍스트의 tenant_id 필터 없이 실행되는 DB 쿼리는 단 하나도 있어서는 안 됩니다. 특히 RAG/벡터 검색에서 중요합니다: tenant 필터를 잊으면, 모델이 다른 조직의 문서까지 검색하기 시작할 수 있습니다.

8. 우리의 Next.js/Apps SDK 애플리케이션과 어떻게 연결되는가

이제 모든 것을 종합하여, scopes, tenant, 네트워크 경계가 Apps SDK 기반의 Next.js 프로젝트에서 어떻게 구현되는지 보겠습니다. 구체적인 Next.js와 Apps SDK 코드를 함께 살펴봅니다.

프로젝트에서 scopes와 tenant는 어디에 있는가

학습용 프로젝트의 전형적인 구성:

  • Next.js 애플리케이션(Apps SDK)에는 App/커넥터 구성과 OAuth 콜백 페이지가 있습니다.
  • MCP 서버에는 ChatGPT에서 오는 HTTP/SSE 요청을 수신하고, 토큰을 검증하며, 필요한 도구를 호출하는 코드가 있습니다.

여기에 우리가 논의한 내용을 이식합니다:

  1. MCP 리소스의 OAuth 설정에서 GiftGenius를 위한 scopes_supported를 선언합니다(catalog:read, orders:write 등).
  2. Apps SDK 구성에서 도구 목록과 애노테이션(read‑only, consequential, confirmation‑flows)을 기술합니다.
  3. MCP 서버에서 다음을 구현합니다:
    • 토큰 파싱과 검증;
    • RequestContext 구성 — { userId, tenantId, scopes };
    • 헬퍼 requireScope, enforceTenant 등;
    • 항상 컨텍스트의 tenantId를 통해 DB를 호출.

주문 생성의 “격리된” 경로 예시

하나의 end‑to‑end 시나리오를 따라가 봅시다.

  1. 사용자: “이 세트를 $50 예산으로 주문해 줘.”
  2. 모델은 giftgenius.create_order{ productId, budget, ... } 인자로 호출해야 한다고 판단합니다.
  3. ChatGPT는 App에 create_order 도구가 있는지, 해당 도구에 어떤 scopes와 securitySchemes가 설정되어 있는지 확인합니다. orders:write가 필요함을 파악합니다.
  4. 이미 토큰이 있고 orders:write를 포함하면 요청이 진행됩니다. 없다면 — ChatGPT가 필요한 scope를 요청하는 OAuth 인가를 시작합니다.
  5. MCP Gateway는 요청을 수신, 토큰을 검증하고 RequestContext를 구성합니다: userId=123, tenantId="acme", scopes=["catalog:read","orders:write",...].
  6. MCP 내부의 createOrder는:
    • requireScope(ctx, "orders:write")를 수행하고;
    • enforceTenant로 tenant를 고정하며;
    • tenantId="acme" 범위에서만 주문을 생성합니다.
  7. 주문에 즉시 결제가 필요하다면, 모델 또는 백엔드가 이어서 charge_customer를 시작합니다. 여기서는:
    • 플랜의 도구가 confirmationRequired로 표시되어 있고;
    • 위젯이 ConfirmCharge를 렌더링하여 사용자의 명시적 확인을 요청합니다.

이렇게 깊이 있는 방어를 얻습니다: 과도한 프롬프트, 프롬프트 인젝션 또는 UX 버그가 있더라도, 최하단의 엄격한 scopes/tenant 검사와 치명적 행동에 대한 수동 확인이 통제되지 않은 동작으로 이어지지 않게 합니다.

9. 권한/세분화 설계 시 흔한 실수

실수 №1: app:full_access 같은 거대한 단일 scope.
데모에서는 편하지만, 프로덕션에서는 위험합니다. 토큰 하나가 유출되면 모든 것을 잃습니다. 개별 작업만 금지하거나 철회할 수 없습니다. 권한을 도메인과 작업 유형(read/write/critical)으로 분해하세요.

실수 №2: “입구에서만” 권한을 확인하고, 도구 내부에서는 확인하지 않음.
가끔 이렇게 합니다: “ChatGPT가 토큰을 받았으니 이미 다 할 수 있다.” 그 다음 createOrder 도구는 해당 토큰에 orders:write가 없어도 그냥 호출됩니다. 올바른 접근은 모든 도구(최소한 모든 변경 작업)에 대해 scopes를 확인하는 것입니다(또는 중앙 미들웨어로 일괄 확인).

실수 №3: 위험한 도구를 표시하지 않고 확인을 요구하지 않음.
결제를 청구하거나, 데이터를 삭제하거나, 접근 권한을 변경하는 도구는 listCatalog처럼 보이면 안 됩니다. 명시적 애노테이션과 UX 확인이 없으면, 모델이 “당연해 보여서” 호출할 가능성이 커집니다. 최소한 read‑only와 destructive 도구를 구분하고, 후자를 명확히 표시하세요.

실수 №4: 도구 인자의 tenantId 를 신뢰함.
매우 흔한 안티 패턴: getOrders({ tenantId }) 도구에서 tenantId가 모델로부터 옵니다. 이를 그대로 쓰면 tenantA 사용자가 다른 식별자를 넣는 것만으로 tenantB 데이터에 접근할 수 있습니다. tenant는 검증된 토큰에서 오며, 모든 DB/외부 서비스 요청에 강제 주입되어야 합니다. 사용자 입력은 무시하거나 일치 여부를 검증하세요.

실수 №5: MCP/DB가 인터넷에서 직접 접근 가능.
간단한 프로토타입에서 MCP 서버와 DB가 인터넷에 생노출(HTTP/5432)되는 경우가 있습니다. 프로덕션에서는 금물입니다: 모든 접근은 하나의 보호된 gateway/proxy를 통해서만 이뤄져야 하고, DB는 프라이빗 네트워크에 있어야 합니다. 취약한 엔드포인트나 허술한 웹훅 하나가 곧바로 데이터로 가는 길이 됩니다.

실수 №6: dev와 prod에서 같은 scopes/시크릿 사용.
로컬 데모 중 prod 데이터를 날리는 단골 사고 방식입니다. 각 환경마다 키, scopes, DB는 분리되어야 합니다. 누군가 dev 토큰에 접근하더라도 prod 데이터에는 피해를 줄 수 없도록 하세요.

실수 №7: “모델에게 거절하기”를 꺼림.
insufficient_scopeforbidden 오류를 자주 반환하면 모델이 덜 똑똑해지지 않을까?”라고 걱정하는 경우가 있습니다. 실제로는 정상적이고 기대되는 동작입니다: 모델은 어떤 행동이 허용되고, 어떤 행동이 추가 권한이나 확인이 필요한지 학습합니다. 더 나쁜 것은, 해서는 안 되는 일을 “성공적으로” 해버리는 것입니다 — 예를 들어, 이중 결제를 처리하는 것처럼요.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION