GNL
Dokümanlar menüsü
Core · Ücretsiz@gnldev/durable

Taint guard

untrusted: true ile işaretlenmiş bir araç başarılı olduğunda, çalıştırma tainted (kirlenmiş) olarak işaretlenir. Bu noktadan SONRAKİ her yan-etki araç çağrısı merdiven (off → warn → reflect → block → suspend) tarafından yönetilir — modeli yeniden düşünmeye yönlendirir ya da çağrıyı doğrudan durdurur.

Ne işe yarar / ne zaman kullanılır#

Bir web sayfası çeken, bir e-posta okuyan ya da bir belge açan bir ajan, bir saldırganın yazmış olabileceği içeriği okuyordur — bir prompt injection. Taint guard, çalışma-zamanı cevabıdır: o içeriği üreten aracı untrusted: true ile işaretleyin; başarılı olduktan sonraki herhangi bir YAN-ETKİ araç çağrısı (sendMail, transfer, …), enjekte edilmiş talimatlara göre hareket edemeden önce merdiven tarafından yakalanır. Taint monoton ve ilk-kazanırdır — bir çalıştırma bir kez kirlendiğinde kirli kalır, ve kaydedilen kaynak her zaman ilk güvenilmeyen çağrıdır.

Merdiven#

Cevabı limits.taintedSideEffects ile belirlersiniz (varsayılan warn). Seviyeler sertlik bakımından yükselir:

const'off'

Zorlama yok — taint hot path'te okunmaz bile.

const'warn'

Eskisi gibi yürür ama taint kaynağını adlandıran, journal'lanan bir incident yazar.

const'reflect'

Distinct (araç, argümanlar) çifti başına TEK bir nudge iletir — duplicate guard'ın 'reflect'i gibi tüm çalıştırma için tek seferlik DEĞİLDİR. Nudge'dan SONRAKİ özdeş tekrar YÜRÜR ('warn' kaydı olarak journal'lanır); block'a yükselmez. Bu, duplicate guard'ın 'reflect'inden kasıtlı olarak farklıdır.

const'block'

Çağrıyı TaintedSideEffectError ile durdurur.

const'suspend'

Askıya alınmış bir kayıt yazar → çağrı Approvals akışına düşer, ve insan sebepte taint kaynağını görür.

Kurulum / import#

Guard @gnldev/durable içinde yaşar ve çalıştırma başına RunLimits ile yapılandırılır:

import { createGnl, TaintedSideEffectError, readRunTaint, type RunLimits } from '@gnldev/durable';

Adım adım kullanım#

OUTPUT'u dışarıdan gelen, saldırgan tarafından yazılabilir içerik olan aracı untrusted: true ile işaretleyin. Başarılı olduğunda çalıştırma tainted işaretlenir; sonraki bir yan-etki aracı block altında TaintedSideEffectError fırlatır:

// 'fetchPage' is marked untrusted: true its OUTPUT is external, attacker-authorable
// content (whatever the fetched page contains). A successful call taints the run.
const fetchPage = { execute: async (a) => fetch(a.url).then((r) => r.text()), untrusted: true };

const limits: RunLimits = { taintedSideEffects: 'block' };

try {
  // A prompt-injected instruction inside the fetched page tells the model to email an
  // attacker. 'sendMail' is a side effect firing AFTER untrusted content entered the run.
  await gnl.run('research', { runId: 'task-9', prompt: 'summarize this page', limits });
} catch (e) {
  if (e instanceof TaintedSideEffectError) {
    // e.detail.taintSource which tool call introduced the taint
    console.log('tainted side effect blocked:', e.detail.toolName, e.detail.taintSource);
  }
}

Modelin kendini toparlamasını tercih ediyorsanız reflect kullanın — ancak davranışın çalıştırma-başına değil çağrı-başına olduğuna dikkat edin:

// Unlike the duplicate guard's 'reflect', the nudge here is per distinct (tool, args)
// pair. An identical retry AFTER the nudge EXECUTES (journaled as a 'warn' record) —
// it does NOT escalate to a block.
const limits: RunLimits = { taintedSideEffects: 'reflect' };

Bir block'a sebep olan taint'i okuyun, ya da bir aracın çıktısını enjekte edilmiş talimatlar için inceleyen bir processor'dan taint'i dinamik olarak belirleyin:

// Inspect the taint that caused a block, or set taint dynamically from a processor
// (e.g. a tool that scans its own output for injected instructions):
const taint = await readRunTaint(journal, runId);
if (taint) console.log('tainted by', taint.toolName, taint.source, taint.reason);

await markRunTainted(journal, runId, {
  toolCallId,
  toolName: 'customScan',
  source: 'processor',
  reason: 'injection pattern detected',
});

Turlar arası taint: taintScope#

Varsayılan olarak taint tek bir çalıştırmayla sınırlıdır — bir konuşmanın 2. turu, 1. tur tainted olsa bile temiz başlar. Aynı threadId üzerindeki turlar arasında taint'i yaymak için limits.taintScope'u thread olarak ayarlayın (varsayılan run): bir çalıştırma bir kez tainted olduğunda, aynı thread'i paylaşan SONRAKİ her çalıştırma bu taint'i devralır — böylece 1. turun kirli bir sayfa çektiği ve 2. turun, untrusted aracı hiç çağırmamış bir çalıştırmada bu içerik üzerinde hareket ettiği açık kapanır.

// Turn 1: fetchPage taints THIS run. Turn 2 is a SEPARATE run on the same threadId
// asking the model to act on what was fetched without taintScope: 'thread' the new
// run starts clean and the injected instruction is not gated.
const limits: RunLimits = { taintedSideEffects: 'block', taintScope: 'thread' };

await gnl.run('research', { runId: 'turn-1', threadId: 'conv-42', prompt: 'fetch this page', limits });

// Later, same conversation turn-2 inherits the taint from turn-1, so a side-effect
// tool call here also runs the ladder, even though turn-2 never called fetchPage:
await gnl.run('research', { runId: 'turn-2', threadId: 'conv-42', prompt: 'send it to my team', limits });

Taint'in süresi: taintLifetime#

Devralınan thread taint'i varsayılan olarak persistent'tir — bir thread bir kez kirlendiğinde sonsuza kadar kirli kalır. Devralınan taint'in, kirleten içerik o çalıştırmanın yüklü mesajlarında (recent + recalled) artık görünmediğinde VE working memory boş olduğunda SÜRESİNİN DOLMASINI istiyorsanız limits.taintLifetimecontent-window olarak ayarlayın. Sonraki bir recall adımı taint'i yeniden canlandırabilir, working memory ise onu canlı tutar. Yalnızca taintScope: thread ile birlikte anlamlıdır — bir çalıştırmanın kendi taint'i asla süresi dolmaz.

// Same thread, taint inherited from an earlier run but this run's loaded messages
// (recent + recalled) no longer include the tainting content, and working memory is
// empty: the taint EXPIRES for this run, and the ladder does not fire on it.
const limits: RunLimits = {
  taintedSideEffects: 'block',
  taintScope: 'thread',
  taintLifetime: 'content-window',
};

// If a later recall step pulls the tainting message BACK into this run's context, or
// a tool wrote it to working memory, the taint is revived / kept alive for this run.

taintGuardian: bir hassas aracı yalnızca tainted olduğunda kapıla#

taintGuardian bir Guard factory'sidir: ona sensitiveTools ve bir onTainted karar fonksiyonu, ayrıca opsiyonel bir otherwise fallback'i geçirirsiniz. Çalıştırma tainted OLDUĞUNDA VE çağrılan araç sensitiveTools'tan biri OLDUĞUNDA, onTainted çalışır ve kararı kullanılır; diğer her çağrı otherwise'a düşer (varsayılan allow). Pahalı kontrol YALNIZCA tainted bir çalıştırma ile hassas bir aracın kesiştiği noktada çalışır — her çağrıda değil.

import { taintGuardian } from '@gnldev/durable';

// The expensive judge runs ONLY when BOTH conditions hold: the run is tainted AND the
// tool being called is listed in sensitiveTools. Every other call is allowed without
// paying for the check.
const guard = taintGuardian({
  sensitiveTools: ['sendMail', 'transfer'],
  onTainted: async (call) => {
    // call.tainted holds the taint context set on EVERY GuardCall, not just here.
    return { action: 'require-approval', reason: call.tainted?.reason };
  },
  otherwise: { action: 'allow' },
});

await gnl.run('research', { runId: 'r1', prompt: 'summarize and email it', guard, limits });

Sadece taintGuardian değil, herhangi bir guard aynı bağlamı okuyabilir: GuardCall artık RunTaint tipinde opsiyonel bir tainted alanı taşır, çalıştırma tainted olduğunda ayarlanır — böylece elle yazılmış bir guard da doğrudan buna göre dallanabilir.

API referansı#

typeRunLimits.taintedSideEffects

RunLimits alanı: 'off' | 'warn' | 'reflect' | 'block' | 'suspend'. Varsayılan 'warn'. Güvenilmeyen içerik çalıştırmaya girdikten sonra yapılan bir yan-etki araç çağrısına uygulanır.

typeRunLimits.taintScope

RunLimits alanı: 'run' | 'thread'. Varsayılan 'run'. 'thread' ile, bir çalıştırmada kaydedilen taint, aynı threadId'yi paylaşan her sonraki çalıştırma tarafından devralınır.

typeRunLimits.taintLifetime

RunLimits alanı: 'persistent' | 'content-window'. Varsayılan 'persistent'. 'content-window' ile, devralınan thread taint'i, kirleten içerik o çalıştırmanın yüklü mesajlarından (recent + recalled) çıktığında ve working memory boş olduğunda süresi dolar; recall onu yeniden canlandırabilir, working memory canlı tutar. Yalnızca taintScope: 'thread' ile birlikte anlamlıdır.

classTaintedSideEffectError

'block' altında fırlatılır. detail: { toolName, toolCallId, taintSource: { toolCallId, toolName } }.

typeAnyTool.untrusted

AnyTool alanı: untrusted?: boolean. Çıktısı dışarıdan/saldırgan tarafından yazılabilir içerik olan bir aracı işaretler (bir web fetch, bir e-posta gövdesi, bir belge). Başarı halinde çalıştırma tainted işaretlenir (monoton, ilk-kazanır, journal'lanır).

fnmarkRunTainted

markRunTainted(journal, runId, taint): idempotent, ilk-kazanır, asla fırlatmaz. Untrusted araçlar için otomatik çağrılır; bir processor'ın taint'i dinamik olarak belirleyebilmesi için export edilir.

fnreadRunTaint

readRunTaint(journal, runId): bir çalıştırma için geçerli taint kaydını okur, çalıştırma tainted değilse undefined döner.

typeRunTaint

RunTaint = { at, toolCallId, toolName, source: 'tool' | 'processor', reason? }.

fntaintGuardian

taintGuardian({ sensitiveTools, onTainted, otherwise? }) → Guard. onTainted'ı YALNIZCA çalıştırma tainted VE çağrılan araç sensitiveTools içindeyken çalıştırır; aksi halde otherwise kararını uygular (varsayılan { action: 'allow' }).

typeGuardCall.tainted

GuardCall alanı: tainted?: RunTaint. Çalıştırma tainted olduğunda her GuardCall'da ayarlanır — böylece sadece taintGuardian değil, herhangi bir guard taint bağlamına göre dallanabilir.

Buradaki reflect, duplicate guard'ın reflect'i DEĞİLDİR
Taint guard'ın reflect'i distinct (araç, argümanlar) çifti başına BİR KEZ nudge verir, ve nudge'dan SONRAKİ özdeş tekrar YÜRÜR — 'warn' kaydı olarak journal'lanır, asla block'a yükselmez. Bu, duplicate-guard'ın reflect'inden kasıtlı olarak farklıdır; orada nudge'dan sonraki özdeş tekrar block'a yükselir. Merdiven seviyesini bu ayrımı akılda tutarak seçin.
Not
Varsayılan off değil warn'dır: davranış değişmez ama her tainted yan-etki, taint kaynağıyla birlikte journal'lanan bir incident olarak adlandırılır — böylece bir operatör, konsol satırına buharlaşmak yerine onu sorgulayabilir.
İlgili
Tekrarlanan bir çağrıda ateşlenen kardeş merdiven için duplicate-guard, suspend'in yönlendirdiği onay akışı için human-in-loop-approvals, ve bunun gibi merdivenlerin bir politikaya nasıl birleştiği için guard-policy sayfalarına bakın.