• 7.09.2026 21:28:00
  • Admin Admin

Yazılım eğitimi asistanlarında MCP araç çağrılarını güvenli kılmak için model girdisini yetkiden ayırın, OPA ile politika uygulayın, izlenebilir denetim kayıtları üretin ve gecikmeyi ölçerek darboğazları giderin.

Yazılım Eğitimi Asistanlarında MCP Araç Yetkisi ve Denetim İzleri

Yazılım Eğitimi Asistanlarında MCP Tehdit Modelini Araç Bazında Kurun

MCP tabanlı bir yazılım eğitimi asistanında en kritik hata, modelin ürettiği argümanları yetki bağlamı gibi yorumlamaktır. Örneğin modelin gönderdiği tenantId, userId, role veya repositoryOwner alanları güvenilir değildir; bunlar prompt injection ile değiştirilebilir. Araç ağ geçidinde kimliği yalnızca doğrulanmış oturumdan, örneğin API gateway'in JWT doğrulamasından gelen req.auth bağlamından alın. MCP istemcisi model adına repo.read_file çağırsa bile, dosyanın hangi depodan okunacağı sunucudaki üyelik ve ödev kaydından çözülmelidir.

Araç envanterini yalnızca isim listesi olarak tutmayın. Her araç için veri sınıfı, yan etki, güven sınırı ve geri alma biçimini içeren bir manifest oluşturun. Örneğin repo.read_file salt-okunur olsa da kaynak kodu, erişim anahtarı veya test fixture'ı sızdırabilir; submission.create_comment dışarıdan görünen yan etki üretir; grader.run_tests ise kuyruk, sandbox ve maliyet tüketir. Bu envanteri CI içinde denetlemek için araç tanımlarını JSON olarak dışa aktarın ve beklenmeyen yeni bir araç eklenince pull request'i durdurun:

jq -r '.tools[] | [.name, .annotations.sideEffect, .annotations.dataClass] | @tsv' tools-manifest.json

git diff --exit-code -- tools-manifest.json

Pratik tehdit modelinde en az şu dört kötüye kullanım senaryosunu test edin: başka öğrencinin depo kimliğiyle okuma, ../ veya çift URL kodlamasıyla yol geçişi, modelin araç sonucundaki talimatı sonraki çağrıya taşıması ve pahalı aracı paralel çağrılarla tüketmesi. Buradaki mekanizma önemlidir: model araç sonucunu güvenilir sistem verisi ile saldırgan denetimli metin arasında ayırt edemez. Bu nedenle araç sonucu içindeki metin, yeni bir yetkilendirme kararı veya yeni bir araç çağrısı için doğrudan kanıt kabul edilmemelidir.

Araç Argümanlarını Doğrulayın, Kimliği Sunucu Tarafında Bağlayın

JSON Schema veya Zod ile sözdizimsel doğrulama gerekli ama tek başına yeterli değildir. path alanının string olması, yolun öğrencinin atanan deposunda bulunduğunu kanıtlamaz. Aşağıdaki TypeScript örneği, modelden gelen argümanı şemaya göre doğrular; ancak etkin kullanıcıyı, depo üyeliğini ve ödev dalını sunucu tarafındaki kaynaklardan bağlar. Böylece modelin göndereceği sahte userId veya branch alanlarının yetkiye etkisi olmaz.

import path from 'node:path';
import { z } from 'zod';

const ReadFileArgs = z.object({
  repoId: z.string().uuid(),
  path: z.string().min(1).max(512),
  ref: z.string().regex(/^[A-Za-z0-9._/-]{1,120}$/)
}).strict();

function fullyDecode(value: string): string {
  let current = value;
  for (let i = 0; i < 3; i++) {
    const next = decodeURIComponent(current);
    if (next === current) return current;
    current = next;
  }
  throw new Error('too many encoding layers');
}

function canonicalVirtualPath(raw: string): string {
  const decoded = fullyDecode(raw);
  if (decoded.includes('\\') || decoded.includes('\0') || decoded.startsWith('/')) {
    throw new Error('invalid path form');
  }
  if (decoded.split('/').includes('..')) throw new Error('path traversal');
  return path.posix.normalize(decoded);
}

async function readFileTool(req: AuthenticatedRequest, rawArgs: unknown) {
  const args = ReadFileArgs.parse(rawArgs);
  const user = req.auth.user; // Gateway JWT'sinden gelir, modelden gelmez.
  const assignment = await assignments.findForStudent(user.id, args.repoId);
  if (!assignment) throw new ForbiddenError('repository is not assigned');

  const safePath = canonicalVirtualPath(args.path);
  if (!args.ref.startsWith(`assignment/${assignment.id}/`)) {
    throw new ForbiddenError('ref is outside assigned branch');
  }

  return git.readFile({ repoId: assignment.repoId, ref: args.ref, path: safePath });
}

Buradaki az bilinen kenar durum çift kodlamadır: yalnızca bir kez decodeURIComponent çağırırsanız %252e%252e%252fsecret ilk aşamada %2e%2e%2fsecret olarak kalabilir ve bunu daha sonra başka bir katman çözer. Örnekteki sınırlı tam çözme yaklaşımı bu nedenle kullanılır. Buna ek olarak, Git sanal yolunu yerel dosya sistemine eşliyorsanız yalnızca path.normalize yeterli değildir; sembolik bağlar için hedef dosyada realpath alıp izinli kökün önekiyle segment sınırında karşılaştırma yapmanız gerekir.

OPA ile MCP Araç Yetkisini Prompt'tan Bağımsız Uygulayın

Araç açıklamasına 'yalnızca kendi deponu oku' yazmak bir erişim kontrolü değildir; bu, modelin uyması beklenen metinsel bir talimattır. Yetki kararını Open Policy Agent'a taşıyın ve araç çağrısından önce tool, doğrulanmış kullanıcı, sunucuda çözümlenmiş depo ve kanonikleştirilmiş argümanlarla sorgu yapın. Politika motoru izin vermezse araç adaptörüne hiç girilmemelidir; aksi halde denetim kaydı oluşsa bile veri zaten okunmuş olabilir.

package toolauthz

default allow := false

allow if {
  input.tool == "repo.read_file"
  input.user.tenant_id == input.repo.tenant_id
  input.user.role in {"student", "instructor"}
  input.args.ref == input.assignment.head_ref
  input.args.path_segments[0] == "starter"
}

Uygulama, OPA'ya ham yol yerine kanonikleştirme sonrasında üretilen path_segments listesini göndermelidir. Bu, politika içinde kırılgan string önek kontrolü yazmayı engeller. Örneğin startsWith(path, "starter") kontrolü starter-private/answers.ts için de true dönebilir; ilk segmentin starter olması ise bu çakışmayı önler. Politikayı dağıtırken opa build policy/ -o policy-bundle.tar.gz komutuyla imzalanabilir bir bundle üretin, karar kayıtlarına bundle digest'ini ekleyin ve olay incelemesinde hangi kuralın çalıştığını geri getirin.

Yan etkili araçlarda ikinci bir sınır ekleyin: araç adını, kullanıcıyı, ödev kimliğini, argüman özetini ve kısa ömürlü onay kimliğini içeren sunucu üretimi bir capability kaydı oluşturun. submission.publish çağrısı yalnızca bu kaydın durumu confirmed ise yürüsün. Onay kaydını modelin bağlamına düz metin olarak vermek yerine, Redis veya PostgreSQL'de tek kullanımlık olarak saklayın; aynı onay kimliğini ikinci kez kullanan çağrıda atomik UPDATE ... WHERE consumed_at IS NULL ile sıfır satır dönmesini bekleyin.

Denetim İzlerini OpenTelemetry ile Araç Sınırında Toplayın

Bir olay sonrası 'hangi model yanıtı bunu yaptı?' sorusu tek başına yetersizdir. Her araç çağrısı için OpenTelemetry span'ını araç ağ geçidinde başlatın ve tool.name, politika sonucu, politika bundle digest'i, kullanıcı rolü, depo sınıfı, HTTP durum kodu ve idempotency sonucu gibi alanları kaydedin. Ham prompt, dosya içeriği ve dosya yolu gibi hassas değerleri span attribute olarak yazmayın; bunun yerine HMAC-SHA256 ile sabit uzunluklu bir argüman parmak izi kullanın. Salt SHA256 kullanmak, tahmin edilebilir kısa yollar için sözlük saldırısına açıktır.

import { trace, SpanStatusCode } from '@opentelemetry/api';
import { createHmac } from 'node:crypto';

const tracer = trace.getTracer('mcp-gateway');
const fingerprint = (value: unknown) =>
  createHmac('sha256', process.env.AUDIT_HMAC_KEY!)
    .update(JSON.stringify(value))
    .digest('hex');

async function tracedToolCall(ctx: ToolContext, args: unknown) {
  return tracer.startActiveSpan('mcp.tool.call', async (span) => {
    span.setAttribute('tool.name', ctx.toolName);
    span.setAttribute('authz.policy_digest', ctx.policyDigest);
    span.setAttribute('authz.decision', ctx.allowed);
    span.setAttribute('args.hmac_sha256', fingerprint(args));
    try {
      if (!ctx.allowed) throw new ForbiddenError('policy denied');
      const result = await ctx.invoke();
      span.setStatus({ code: SpanStatusCode.OK });
      return result;
    } catch (error) {
      span.recordException(error as Error);
      span.setStatus({ code: SpanStatusCode.ERROR });
      throw error;
    } finally {
      span.end();
    }
  });
}

Grafana veya Jaeger ekranında yalnızca toplam istek gecikmesini değil, ayrı span'lar olarak authn.verify, authz.opa, tool.adapter ve git.read sürelerini izleyin. Haftalık bir sorguda aynı args.hmac_sha256 için yüksek deny oranı, modelin hatalı araç şeması öğrendiğini; aynı kullanıcı ve araç için kısa sürede çok sayıda farklı parmak izi ise otomasyon veya prompt injection denemesi olabileceğini gösterir. Bu iki sinyal, ham kullanıcı içeriğini loglamadan soruşturma başlatmak için yeterlidir.

Araç Çağrısı Gecikmesini Profiling ile Ölçün ve Darboğazı İzole Edin

Araç katmanında gecikme çalışması yaparken önce-sonra karşılaştırmasını aynı trafik şekliyle yapın. Node.js ağ geçidini CPU örneklemesiyle incelemek için npx clinic flame -- node dist/server.js komutunu çalıştırın; yük üretmek için sabit bağlantı ve süreyle npx autocannon -c 40 -d 60 -m POST http://localhost:3000/tools/repo.read_file kullanın. Karşılaştırmada p50, p95, p99, OPA span süresi, hata oranı ve event-loop gecikmesini aynı dashboard'a koyun. Sadece ortalama süre, kısa süreli OPA bağlantı kuyruğu patlamalarını gizler.

Sık görülen bir maliyet, her çağrıda şema derlemek veya uzak politika servisine yeni TCP bağlantısı açmaktır. Şema derlemeyi işlem başlangıcına taşıyın, OPA istemcisinde keep-alive kullanın ve değişken yetki kararlarını körlemesine önbelleğe almayın. Özellikle öğrencinin dersten çıkarılması veya depo erişiminin geri alınması durumunda uzun TTL'li allow cache, doğrudan yetki açığı üretir. Aşağıdaki değişiklik, yalnızca değişmez araç meta verisini önbelleğe alır; izin kararı yine her çağrıda politika motorundan gelir.

import { Agent, request } from 'undici';
import Ajv from 'ajv';

const dispatcher = new Agent({
  connections: 32,
  keepAliveTimeout: 10_000
});

const ajv = new Ajv({ allErrors: true, strict: true });
const validateReadFile = ajv.compile(readFileJsonSchema); // Uygulama baslangicinda bir kez.

async function askOpa(input: unknown) {
  const response = await request('http://opa:8181/v1/data/toolauthz/allow', {
    method: 'POST',
    dispatcher,
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ input })
  });
  return (await response.body.json() as { result: boolean }).result;
}

Değişiklikten sonra aynı autocannon komutunu, aynı test deposu ve aynı OPA bundle digest'i ile tekrar çalıştırın. Eğer tool.adapter span'ı düşüyor ama p99 sabit kalıyorsa sorun ağ geçidinde değil, Git sağlayıcısı, bağlantı havuzu veya kuyrukta olabilir. Eğer Clinic flame grafiğinde JSON.stringify ve log transport CPU'nun anlamlı kısmını tüketiyorsa, büyük araç sonuçlarını loglamayı bırakın; sonuç boyutu ve HMAC parmak izi kaydetmek genellikle olay korelasyonu için yeterlidir.

Politika ve Yol Kanonikleştirmesini Saldırı Vektörleriyle Test Edin

Araç sözleşmesi testleri yalnızca geçerli örneklerle bitmemelidir. Vitest ve fast-check kullanarak yol kanonikleştiricisinin traversal biçimlerini reddettiğini, geçerli öğrenci dosyalarını ise bozmadığını sürekli test edin. Bu testleri araç şeması, politika veya Git adaptörü değiştiğinde çalışan CI hattına koyun; aksi halde bir URL kod çözme veya Windows yol desteği değişikliği sessiz bir yetki gerilemesine dönüşebilir.

import { describe, expect, it } from 'vitest';
import fc from 'fast-check';

describe('canonicalVirtualPath', () => {
  it('traversal ve mutlak yol bicimlerini reddeder', () => {
    fc.assert(fc.property(
      fc.constantFrom('../answer.ts', '%2e%2e%2fanswer.ts', '%252e%252e%252fanswer.ts', '/etc/passwd', '..\\secret'),
      (candidate) => expect(() => canonicalVirtualPath(candidate)).toThrow()
    ));
  });

  it('izinli gorev dosyasini korur', () => {
    expect(canonicalVirtualPath('starter/src/main.ts')).toBe('starter/src/main.ts');
  });
});

OPA politikası için de karar tablosu oluşturun: öğrenci kendi assignment.head_ref dalında starter altını okuyabilmeli, eğitmen farklı bir politika kuralıyla çözüm deposuna erişebilmeli, öğrenci ise çözüm dalını okuyamamalıdır. CI aşamasında opa test policy/ -v çalıştırın ve her deny vakasının beklenen hata kodunu doğrulayın. Özellikle araç adaptörünün policy deny sonrasında dış sisteme hiç istek göndermediğini bir sahte Git istemcisiyle assert edin; yalnızca 403 dönmesini test etmek, yan etkinin önceden gerçekleştiği hatayı yakalayamaz.

Sık Sorulan Sorular

Yazılım eğitimi asistanlarında MCP araç yetkisi prompt ile yönetilebilir mi?

Hayır. Prompt, model davranışı için talimattır ve güvenlik sınırı değildir. Yetkiyi API gateway'de doğrulanmış JWT kimliği, sunucuda çözümlenmiş depo üyeliği ve OPA gibi bir politika motoru ile uygulayın. Modelin gönderdiği userId, tenantId veya role alanlarını yetki kararına sokmayın.

Yazılım eğitimi için MCP dosya okuma aracında path traversal nasıl engellenir?

Yolu sınırlı sayıda tamamen URL decode edin, mutlak yolu, ters bölüyü, null byte'ı ve .. segmentini reddedin. Ardından posix normalize edin. Yerel dosya sistemine geçiyorsanız hedef için realpath alıp izinli kökle segment sınırında karşılaştırın; yalnızca startsWith kullanmak /workspace/student ile /workspace/student-private çakışmasını kaçırabilir.

Yazılım eğitimi asistanlarında araç çağrısı gecikmesi hangi araçla ölçülmeli?

Node.js ağ geçidi için CPU darboğazını npx clinic flame ile, uçtan uca yükü autocannon ile, bileşen sürelerini OpenTelemetry span'larıyla ölçün. Aynı bağlantı sayısı, süre, test verisi ve politika bundle digest'i altında önce-sonra p50, p95, p99, hata oranı ve event-loop gecikmesini karşılaştırın.

OPA allow kararlarını MCP araçlarında cachelemek güvenli mi?

Kullanıcı üyeliği, rol, depo ACL'i veya ödev durumu değişebildiği için allow kararını uzun TTL ile cachelemek risklidir. Öncelikle Ajv şema derlemesi ve HTTP keep-alive gibi yetki semantiğini değiştirmeyen maliyetleri azaltın. Cache zorunluysa ACL revision, policy digest, kullanıcı, depo, araç ve argüman bağlamını anahtara ekleyin; erişim iptalinde anlık invalidation uygulayın.

AI / LLM Discovery

Bu makale Opendart Akademi Yapay Zeka eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.

Opendart Akademi llms.txt