LLM SECURITY TELEMETRY · SOURCE GUIDE

LLM-facing security telemetry: 13 guides

Where this concern meets the GGUF execution graph

The highlighted annotations locate attack entry, possible influence, constraints and observable effects for this concern. They are source-based hypotheses, not a trace of a demonstrated attack. GGUF supplies weights and metadata; llama.cpp builds and executes the computation graph.

Source snapshot: llama.cpp 5266f24da75dc449bd56cbed7addb9c8e4a6a73e. Concrete Llama architecture, ordinary generation path, non-fused attention as configured by this driver. Other architectures and fused kernels can use different graphs. All code below is embedded and expandable; no network or inference is required.

ConcernEntry or control locationOutput / boundary evidence
01. Instruction injectionATTACK ENTRY: an instruction-like passage is inserted in untrusted source text before tokenization. Region labels are supplied separately by the caller.BEHAVIORAL EVIDENCE: inspect actual generated bytes for the specified task deviation. This is where following the injected instruction can become visible.
02. Trust-boundary crossingATTACK ENTRY: a tool result or retrieved passage claims system-like authority inside the rendered prompt. Source and trust labels remain caller-declared.BEHAVIORAL EVIDENCE: identify the output span that follows the lower-trust instruction over the legitimate task.
03. Tool-definition poisoningATTACK ENTRY: instructions are inserted into a model-visible tool or parameter description. This modifies rendered text, not an MCP server or GGUF tensor.BEHAVIORAL EVIDENCE: read the actual tool name and argument bytes. A name emitted by the model does not establish tool invocation.
04. Tool shadowing and identity confusionATTACK ENTRY: competing tool definitions use confusing names, descriptions, ordering or source labels in model input.BEHAVIORAL EVIDENCE: inspect generated identifiers and arguments. Report same-name ambiguity; verify actual dispatch in the integrating application.
05. Tool selection and argument manipulationATTACK ENTRY: changed prompt content steers a tool-name or argument decision. Define the exact serialized delimiter and choice being studied.BEHAVIORAL EVIDENCE: reconstruct actual argument bytes and exact pattern matches; no argument is executed or validated against a policy by this driver.
06. Sensitive-context disclosure and canary emissionATTACK ENTRY: an elicitation request is combined with sensitive context. The synthetic canary is the disclosure target, not necessarily the attacker-controlled passage.BEHAVIORAL EVIDENCE: exact output-byte matches establish canary emission, including across token boundaries. External receipt or exfiltration needs separate evidence.
07. Persistent influence through context or memoryATTACK ENTRY: a manipulated historical message or memory entry is included in a later rendered prompt by the caller.BEHAVIORAL EVIDENCE: inspect the later response for the historical instruction’s effect. The output alone does not prove state persisted between invocations.
08. Cache state, retention and inference isolationPOTENTIAL ENTRY: a separate integrating application could reuse or misassign inference state. The standalone driver supplies a fresh primary context and does not demonstrate this attack.BEHAVIORAL EVIDENCE: unexpected output may motivate a state audit; output alone cannot identify a cache ownership or retention bug.
09. Refusal and compliance behaviorATTACK ENTRY: an input condition attempts to change refusal or compliance behavior. Define the intended behavior before comparing prompts.BEHAVIORAL EVIDENCE: inspect the complete response. Exact “I cannot” matching can occur in quotations or alongside compliance and cannot decide the outcome.
10. Grammar constraints and sampler effectsEXPERIMENT CONTROL: keep rendered input fixed while changing one grammar or sampler stage. A malicious input is a separate variable if the experiment includes one.BEHAVIORAL EVIDENCE: inspect the selected output against both grammar and task requirements. Selection feeds back into the next model pass.
11. Model-level looping and repetitive continuationATTACK ENTRY: a changed prompt attempts to induce repetitive continuation. Fix the token budget and retain the actual output.PRIMARY OBSERVATION: the selected-token feedback loop and output suffix reveal repetition. The telemetry detector checks adjacent equal suffixes; it does not stop generation automatically.
12. Resource exhaustion and context pressureATTACK ENTRY: an excessive rendered prompt or workload can increase context and generation demands. The driver rejects prompt-plus-budget overflow before graph execution.BEHAVIORAL EVIDENCE: record generated length, stop reason and timing. A long valid answer is not automatically resource abuse.
13. Protocol, authorization and OS boundariesEXTERNAL ATTACK LOCATION: protocol handling, authorization or OS behavior occurs in the integrating system. Enter this inference path only if that event changes model-visible input.BOUNDARY: generated URLs, command strings and tool-call text remain bytes. Actual dispatch and its effects must be recorded outside this repository.
STAGE 01

GGUF → model weights, vocabulary and architecture

GGUF is a model file. The loader reads its metadata and tensors; C++ architecture code constructs the executable graph.

MODEL BASELINE: keep the GGUF identity and inference revision fixed. This concern does not demonstrate an attack on the GGUF loader or a modification of model weights.

llama.cpp implementation

llama.cpp/src/llama-model-loader.cpp:562–580View code
    if (!fname.empty()) {        // Load the main GGUF        struct ggml_context * ctx = NULL;        struct gguf_init_params params = {            /*.no_alloc = */ true,            /*.ctx      = */ &ctx,        };        metadata_ptr.reset(gguf_init_from_file(fname.c_str(), params));        metadata = metadata_ptr.get();        if (metadata == nullptr) {            throw std::runtime_error(format("%s: failed to load model from %s", __func__, fname.c_str()));        }        get_key(llm_kv(LLM_KV_GENERAL_ARCHITECTURE), arch_name, false);        llm_kv = LLM_KV(llm_arch_from_string(arch_name));        files.emplace_back(new llama_file(fname.c_str(), "rb", use_direct_io));        contexts.emplace_back(ctx);

llama.cpp implementation

llama.cpp/src/llama.cpp:343–371View code
        model->hparams.vocab_only = params.vocab_only;        model->hparams.no_alloc   = params.no_alloc;        try {            model->load_hparams(ml);        } catch(const std::exception & e) {            throw std::runtime_error("error loading model hyperparameters: " + std::string(e.what()));        }        if (model->arch == LLM_ARCH_CLIP) {            throw std::runtime_error("CLIP cannot be used as main model, use it with --mmproj instead");        }        try {            model->load_vocab(ml);        } catch(const std::exception & e) {            throw std::runtime_error("error loading model vocabulary: " + std::string(e.what()));        }        model->load_stats(ml);        model->print_info();        if (params.vocab_only) {            LLAMA_LOG_INFO("%s: vocab only - skipping tensors\n", __func__);            return {0, model_ptr.release()};        }        if (!model->load_tensors(ml)) {            return {-2, nullptr};        }
STAGE 02

Rendered text → token IDs

The caller supplies the final prompt. llama_tokenize delegates to the loaded vocabulary; tokenization itself does not execute the transformer.

ATTACK ENTRY for guides 01–07, 09 and 11: attacker-controlled text enters through the rendered prompt. Guide 12 also studies input size; guide 13 may remain entirely outside inference.

llama.cpp implementation

llama.cpp/src/llama-vocab.cpp:4410–4419View code
int32_t llama_tokenize(    const struct llama_vocab * vocab,                  const char * text,                     int32_t   text_len,                 llama_token * tokens,                     int32_t   n_tokens_max,                        bool   add_special,                        bool   parse_special) {    return vocab->tokenize(text, text_len, tokens, n_tokens_max, add_special, parse_special);}

Driver: read the rendered prompt

src/cst_capture.cpp:83–91View code
int main(int argc,char ** argv){    if(argc!=2){std::cerr<<"Usage: cst-capture config.json\n";return 2;}    try {        json j=json::parse(read_file(argv[1]));        std::string model_path=j.at("model"),prompt=read_file(j.at("prompt_file"));        std::string log=j.value("log_path",std::string("trial.cstbin")),jsonl=j.value("jsonl_path",std::string("trial.telemetry.jsonl"));        std::string run=j.value("run_id",std::string("local"));        int max_tokens=j.value("max_tokens",32),nctx=j.value("context_size",4096),stride=j.value("score_stride",1);        if(max_tokens<=0 || nctx<=0 || stride<=0)throw std::runtime_error("invalid token/context/score limits");
STAGE 03

Decode → build or reuse graph → set current inputs

process_ubatch applies memory state, builds or reuses the graph, sets new inputs and requests graph computation. Reusing topology still computes the new input.

EXECUTION ENTRY: the current prefix and context state reach the model here. Building or reusing topology is not itself an attack; the relevant influence is identified in the stages below.

llama.cpp implementation

llama.cpp/src/llama-context.cpp:1334–1403View code
llm_graph_result * llama_context::process_ubatch(const llama_ubatch & ubatch, llm_graph_type gtype, llama_memory_context_i * mctx, ggml_status & ret) {    if (mctx && !mctx->apply()) {        LLAMA_LOG_ERROR("%s: failed to apply memory context\n", __func__);        ret = GGML_STATUS_FAILED;        return nullptr;    }    auto * res = gf_res_prev.get();    auto * gf  = res->get_gf();    // the new graph parameters    // in order to correctly reuse a graph, it's full topology has to be uniquely determined by these parameters    const auto gparams = graph_params(res, ubatch, mctx, gtype);    if (!graph_reuse_disable && res->can_reuse(gparams)) {        //LLAMA_LOG_DEBUG("%s: reusing previous graph\n", __func__);        // with pipeline parallelism, the previous graph_compute_async may still be running        // on the GPU. we must synchronize before set_inputs to avoid overwriting input tensors        // that the previous compute is still reading.        if (cparams.pipeline_parallel) {            ggml_backend_sched_synchronize(sched.get());        }        n_reused++;    } else {        res->reset();        ggml_backend_sched_reset(sched.get());        ggml_backend_sched_set_eval_callback(sched.get(), cparams.cb_eval, cparams.cb_eval_user_data);        //const auto t_start_us = ggml_time_us();        gf = model.build_graph(gparams);        //LLAMA_LOG_INFO("graph build time: %.3f ms\n", (ggml_time_us() - t_start_us)/1000.0);        if (!gf) {            LLAMA_LOG_ERROR("%s: failed to initialize graph\n", __func__);            ret = GGML_STATUS_FAILED;            return nullptr;        }        if (!ggml_backend_sched_alloc_graph(sched.get(), gf)) {            LLAMA_LOG_ERROR("%s: failed to allocate graph\n", __func__);            ret = GGML_STATUS_ALLOC_FAILED;            return nullptr;        }    }    // set the input data for the input tensors    {        //const auto t_start_us = ggml_time_us();        // FIXME this call causes a crash if any model inputs were not used in the graph and were therefore not allocated        res->set_inputs(&ubatch);        //LLAMA_LOG_INFO("graph set inputs time: %.3f ms\n", (ggml_time_us() - t_start_us)/1000.0);    }    const auto status = graph_compute(res->get_gf(), ubatch.n_tokens > 1);    if (status != GGML_STATUS_SUCCESS) {        LLAMA_LOG_ERROR("%s: failed to compute graph, compute status: %d\n", __func__, status);        ret = status;        return nullptr;    }    ret = GGML_STATUS_SUCCESS;    return res;

llama.cpp implementation

llama.cpp/src/llama-model.cpp:2673–2690View code
ggml_cgraph * llama_model::build_graph(const llm_graph_params & params) const {    std::unique_ptr<llm_graph_context> llm = build_arch_graph(params);    // add on pooling layer    llm->build_pooling(cls, cls_b, cls_out, cls_out_b, cls_norm);    // add backend sampling layers (if any)    llm->build_sampling();    // if the gguf model was converted with --sentence-transformers-dense-modules    // there will be two additional dense projection layers    // dense linear projections are applied after pooling    // TODO: move reranking logic here and generalize    llm->build_dense_out(dense_2_out_layers, dense_2_out_layers_b, dense_3_out_layers);    llm->res->set_outputs(params);    return llm->res->get_gf();

Driver: decode each prompt token

src/cst_capture.cpp:176–182View code
        cst_set_position(0,0,0,0,"prefill");cst_log_prompt(input.data(),(int)input.size(),prompt.c_str());        auto trial_start=Clock::now(),start=trial_start;        std::vector<int32_t> keys;        for(size_t i=0;i<input.size();++i){            cst_set_position(0,0,(int)i,0,"prefill");keys.push_back((int)i);cst_set_attention_key_positions(keys.data(),(int)keys.size());            decode_one(ctx.get(),input[i]);        }

Inside the Llama forward graph

Tensor dependency order: embeddings → per-layer attention and FFN → final logits. K/V, attention and FFN repeat for each layer. The backend executes these operations during decode; graph construction alone does not compute their values.

STAGE 04

Token embeddings + positions

Token IDs select rows of the GGUF embedding matrix. The Llama graph supplies positions and enters its transformer-layer loop.

INPUT REPRESENTATION: tokens from the source described above become embedding vectors and positional inputs. The embedding lookup does not enforce source trust or label a token as an attack.

llama.cpp implementation

llama.cpp/src/llama-graph.cpp:2303–2328View code
ggml_tensor * llm_graph_context::build_inp_embd(ggml_tensor * tok_embd) const {    const int64_t n_embd_inp = hparams.n_embd_inp();    const int64_t n_embd     = hparams.n_embd;    assert(n_embd_inp >= n_embd);    auto inp = std::make_unique<llm_graph_input_embd>(n_embd_inp);    inp->tokens = ggml_new_tensor_1d(ctx0, GGML_TYPE_I32, ubatch.n_tokens);    cb(inp->tokens, "inp_tokens", -1);    ggml_set_input(inp->tokens);    res->t_inp_tokens = inp->tokens;    inp->embd = ggml_new_tensor_2d(ctx0, GGML_TYPE_F32, n_embd_inp, ubatch.n_tokens);    cb(inp->embd, "inp_embd", -1);    ggml_set_input(inp->embd);    // select one of the 2 inputs, based on the batch contents    // ref: https://github.com/ggml-org/llama.cpp/pull/18550    std::array<ggml_tensor *, 2> inps;    // token embeddings path (ubatch.token != nullptr)    {        auto & cur = inps[0];        cur = ggml_get_rows(ctx0, tok_embd, inp->tokens);

llama.cpp implementation

llama.cpp/src/models/llama.cpp:108–131View code
    inpL = build_inp_embd(model.tok_embd);    // inp_pos - contains the positions    ggml_tensor * inp_pos = build_inp_pos();    using inp_attn_type = std::conditional_t<embed, llm_graph_input_attn_no_cache, llm_graph_input_attn_kv>;    inp_attn_type * inp_attn = nullptr;    if constexpr (embed) {        inp_attn = build_attn_inp_no_cache();    } else {        inp_attn = build_attn_inp_kv();    }    const float kq_scale = hparams.f_attention_scale == 0.0f ? 1.0f/sqrtf(float(n_embd_head)) : hparams.f_attention_scale;    ggml_tensor * inp_out_ids = build_inp_out_ids();    for (int il = 0; il < n_layer; ++il) {        res->t_layer_inp[il] = inpL;        ggml_tensor * inpSA = inpL;        // norm
STAGE 05

Per layer: Q/K/V + position encoding → KV cache

The graph computes Q/K/V, applies RoPE and adds K/V cache writes. Attention reads the available cache with a position mask.

STATE LOCATION for guides 07–08: historical input or retained K/V can affect later queries. Only the former is directly supplied by this standalone driver.

llama.cpp implementation

llama.cpp/src/models/llama.cpp:139–161View code
            // rope freq factors for llama3; may return nullptr for llama2 and other models            ggml_tensor * rope_factors = model.get_rope_factors(cparams, il);            // compute Q and K and RoPE them            auto [Qcur, Kcur, Vcur] = build_qkv(model.layers[il], cur,                    n_embd_head, n_head, n_head_kv, il);            Qcur = ggml_rope_ext(                    ctx0, Qcur, inp_pos, rope_factors,                    n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,                    ext_factor, attn_factor, beta_fast, beta_slow                    );            Kcur = ggml_rope_ext(                    ctx0, Kcur, inp_pos, rope_factors,                    n_rot, rope_type, n_ctx_orig, freq_base, freq_scale,                    ext_factor, attn_factor, beta_fast, beta_slow                    );            cb(Qcur, "Qcur", il);            cb(Kcur, "Kcur", il);            cb(Vcur, "Vcur", il);

llama.cpp implementation

llama.cpp/src/llama-graph.cpp:2815–2833View code
    const auto * mctx_cur = inp->mctx;    // store to KV cache    {        const auto & k_idxs = inp->get_k_idxs();        const auto & v_idxs = inp->get_v_idxs();        ggml_build_forward_expand(gf, mctx_cur->cpy_k(ctx0, k_cur, k_idxs, il));        ggml_build_forward_expand(gf, mctx_cur->cpy_v(ctx0, v_cur, v_idxs, il));    }    ggml_tensor * kq_mask = inp->get_kq_mask();    ggml_tensor * q = q_cur;    ggml_tensor * k = mctx_cur->get_k(ctx0, il);    ggml_tensor * v = mctx_cur->get_v(ctx0, il);    ggml_tensor * cur = build_attn_mha(q, k, v, kq_b, kq_mask, sinks, v_mla, 0, kq_scale, il);    cb(cur, "kqv_out", il);
STAGE 06

Per layer: QK scores → masked softmax → weighted V

On the non-fused path, kq_soft_max contains attention probabilities. Those weights mix value vectors into the attention output.

INFLUENCE LOCATION for prompt-based concerns: queries can attend to attacker-controlled or sensitive regions. Region mass is an observation, not proof of a security outcome.

llama.cpp implementation

llama.cpp/src/llama-graph.cpp:2605–2610View code
        ggml_tensor * kq = ggml_mul_mat(ctx0, k, q);        cb(kq, "kq", il);        // note: this op tends to require high floating point range        //       while for some models F16 is enough, for others it is not, so we default to F32 here        ggml_mul_mat_set_prec(kq, GGML_PREC_F32);

llama.cpp implementation

llama.cpp/src/llama-graph.cpp:2633–2650View code
        if (kq_b) {            kq = ggml_add(ctx0, kq, kq_b);            cb(kq, "kq_plus_kq_b", il);        }        kq = ggml_soft_max_ext(ctx0, kq, kq_mask, kq_scale, hparams.f_max_alibi_bias);        ggml_soft_max_add_sinks(kq, sinks);        cb(kq, "kq_soft_max", il);        if (!v_trans) {            // note: avoid this branch            v = ggml_cont(ctx0, ggml_transpose(ctx0, v));            cb(v, "v_cont", il);        }        ggml_tensor * kqv = ggml_mul_mat(ctx0, v, kq);        cb(kqv, "kqv", il);
STAGE 07

Per layer: attention residual → FFN → layer output

Attention output is added to the residual, transformed by the FFN, then added again. l_out feeds the next layer; the layer loop repeats.

INFLUENCE PROPAGATION: attention output flows through residual and FFN operations into later layers. These operations are shared by normal and manipulated inputs.

llama.cpp implementation

llama.cpp/src/models/llama.cpp:173–195View code
        }        if (il == n_layer - 1 && inp_out_ids) {            cur   = ggml_get_rows(ctx0,   cur, inp_out_ids);            inpSA = ggml_get_rows(ctx0, inpSA, inp_out_ids);        }        ggml_tensor * ffn_inp = ggml_add(ctx0, cur, inpSA);        cb(ffn_inp, "ffn_inp", il);        // feed-forward network (non-MoE)        if (model.layers[il].ffn_gate_inp == nullptr) {            cur = build_norm(ffn_inp,                    model.layers[il].ffn_norm, NULL,                    LLM_NORM_RMS, il);            cb(cur, "ffn_norm", il);            cur = build_ffn(cur,                    model.layers[il].ffn_up,   model.layers[il].ffn_up_b,   model.layers[il].ffn_up_s,                    model.layers[il].ffn_gate, model.layers[il].ffn_gate_b, model.layers[il].ffn_gate_s,                    model.layers[il].ffn_down, model.layers[il].ffn_down_b, model.layers[il].ffn_down_s,                    NULL,                    LLM_FFN_SILU, LLM_FFN_PAR, il);            cb(cur, "ffn_out", il);

llama.cpp implementation

llama.cpp/src/models/llama.cpp:220–228View code
        cur = ggml_add(ctx0, cur, ffn_inp);        cb(cur, "ffn_out", il);        cur = build_cvec(cur, il);        cb(cur, "l_out", il);        // input for next layer        inpL = cur;    }
STAGE 08

Final normalization → vocabulary logits

After the final layer, output normalization and the GGUF output projection produce result_output / t_logits.

OBSERVATION LOCATION: compare aligned raw distributions or auxiliary candidate likelihoods for tool choice, disclosure, refusal and other continuations.

llama.cpp implementation

llama.cpp/src/models/llama.cpp:230–246View code
    cur = build_norm(cur,            model.output_norm, NULL,            LLM_NORM_RMS, -1);    cb(cur, "result_norm", -1);    res->t_embd = cur;    if constexpr (!embed) {        // lm_head        cur = build_lora_mm(model.output, cur, model.output_s);        cb(cur, "result_output", -1);        res->t_logits = cur;    }    ggml_build_forward_expand(gf, cur);
STAGE 09

Backend executes these graph operations; callbacks observe tensors

The preceding boxes describe tensor dependencies. Numerics execute through this scheduler, which computes graph ranges, synchronizes and invokes requested observation callbacks during execution.

EXECUTION / COST LOCATION for guide 12 and tensor observations: the scheduler runs graph operations. Guide 13’s protocol and OS attacks have no intrinsic transformer node.

llama.cpp implementation

llama.cpp/src/llama-context.cpp:2491–2518View code
ggml_status llama_context::graph_compute(            ggml_cgraph * gf,                   bool   batched) {    int n_threads        = batched ? cparams.n_threads_batch : cparams.n_threads;    ggml_threadpool_t tp = batched ? threadpool_batch        : threadpool;    if (backend_cpu != nullptr) {        auto * reg = ggml_backend_dev_backend_reg(ggml_backend_get_device(backend_cpu));        auto * set_threadpool_fn = (decltype(ggml_backend_cpu_set_threadpool) *) ggml_backend_reg_get_proc_address(reg, "ggml_backend_cpu_set_threadpool");        if (set_threadpool_fn) {            set_threadpool_fn(backend_cpu, tp);        }    }    // set the number of threads for all the backends    for (const auto & set_n_threads_fn : set_n_threads_fns) {        set_n_threads_fn.second(set_n_threads_fn.first, n_threads);    }    auto status = ggml_backend_sched_graph_compute_async(sched.get(), gf);    if (status != GGML_STATUS_SUCCESS) {        LLAMA_LOG_ERROR("%s: ggml_backend_sched_graph_compute_async failed with error %d\n", __func__, status);    }    // fprintf(stderr, "splits: %d\n", ggml_backend_sched_get_n_splits(sched));    return status;}

llama.cpp implementation

llama.cpp/ggml/src/ggml-backend.cpp:1802–1838View code
        if (!sched->callback_eval) {            enum ggml_status ec = ggml_backend_graph_compute_async(split_backend, &split->graph);            if (ec != GGML_STATUS_SUCCESS) {                return ec;            }        } else {            // similar to ggml_backend_compare_graph_backend            for (int j0 = 0; j0 < split->graph.n_nodes; j0++) {                struct ggml_tensor * t = split->graph.nodes[j0];                // check if the user needs data from this node                bool need = sched->callback_eval(t, true, sched->callback_eval_user_data);                int j1 = j0;                // determine the range [j0, j1] of nodes that can be computed together                while (!need && j1 < split->graph.n_nodes - 1) {                    t = split->graph.nodes[++j1];                    need = sched->callback_eval(t, true, sched->callback_eval_user_data);                }                struct ggml_cgraph gv = ggml_graph_view(&split->graph, j0, j1 + 1);                enum ggml_status ec = ggml_backend_graph_compute_async(split_backend, &gv);                if (ec != GGML_STATUS_SUCCESS) {                    return ec;                }                // TODO: pass backend to the callback, then the user can decide if they want to synchronize                ggml_backend_synchronize(split_backend);                if (need && !sched->callback_eval(t, false, sched->callback_eval_user_data)) {                    break;                }                j0 = j1;            }

llama.cpp implementation

llama.cpp/ggml/src/ggml-backend.cpp:461–464View code
enum ggml_status ggml_backend_graph_compute_async(ggml_backend_t backend, struct ggml_cgraph * cgraph) {    GGML_ASSERT(backend);    return backend->iface.graph_compute(backend, cgraph);}
STAGE 10

Raw logits → grammar / sampler stages → selected token

This driver applies its configured samplers after decode. llama_sampler_apply dispatches each stage; a grammar stage filters the candidate distribution.

CONSTRAINT LOCATION for guide 10: grammar and samplers alter selection after the driver’s forward pass. Guide 11 also studies repetition penalties.

llama.cpp implementation

llama.cpp/src/llama-sampler.cpp:382–389View code
void llama_sampler_apply(struct llama_sampler * smpl, struct llama_token_data_array * cur_p) {    if (!smpl) {        return;    }    GGML_ASSERT(smpl->iface->apply);    smpl->iface->apply(smpl, cur_p);}

llama.cpp implementation

llama.cpp/src/llama-sampler.cpp:2680–2685View code
static void llama_sampler_grammar_apply(struct llama_sampler * smpl, llama_token_data_array * cur_p) {    auto * ctx = (llama_sampler_grammar *) smpl->ctx;    if (ctx->grammar) {        llama_grammar_apply_impl(*ctx->grammar, cur_p);    }}
STAGE 11

Selected token → output bytes → next decode ↺

llama_token_to_piece returns the token bytes. The caller records them and feeds the selected token into the next decode when another prediction is needed.

BEHAVIOR LOCATION: inspect actual generated bytes for task deviation, wrong tool arguments, canary emission, refusal/compliance or repetition. External action needs separate evidence.

llama.cpp implementation

llama.cpp/src/llama-vocab.cpp:4421–4429View code
int32_t llama_token_to_piece(    const struct llama_vocab * vocab,                 llama_token   token,                        char * buf,                     int32_t   length,                     int32_t   lstrip,                        bool   special) {    return vocab->token_to_piece(token, buf, length, lstrip, special);}

Driver: record bytes and feed back the selected token

src/cst_capture.cpp:219–240View code
            for(auto & s:samplers)llama_sampler_accept(s.get(),id);            std::string text=piece(vocab,id);cst_log_token_bytes(text.data(),text.size());            event("special_token",{{"token_id",id},{"is_eog",llama_vocab_is_eog(vocab,id)},                  {"eos_raw_logprob",llama_vocab_eos(vocab)>=0?json(selected_logprob(raw,nv,llama_vocab_eos(vocab))):json(nullptr)}});            if(t==0){double ttft=ms(trial_start);cst_log_timing(CST_TIMING_FIRST_TOKEN,ttft,1,ttft>0?1000.0/ttft:0);}            cst_log_generated_token(id,raw,nv);++generated;prefix.push_back(id);            if(llama_vocab_is_eog(vocab,id)){stop="eog";break;}            std::cout<<text<<std::flush;            if(t+1==max_tokens)break;            cst_set_position(0,0,(int)prefix.size()-1,t+1,"decode");            keys.push_back((int)prefix.size()-1);cst_set_attention_key_positions(keys.data(),(int)keys.size());            start=Clock::now();decode_one(ctx.get(),id);elapsed=ms(start);            cst_log_timing(CST_TIMING_DECODE,elapsed,1,elapsed>0?1000.0/elapsed:0);            auto memory=llama_get_memory(ctx.get());            event("cache_lifecycle",{{"operation","append"},{"min_position",llama_memory_seq_pos_min(memory,0)},                  {"max_position",llama_memory_seq_pos_max(memory,0)},{"logical_tokens",keys.size()},                  {"allocated_context_capacity",llama_n_ctx(ctx.get())}});        }        elapsed=ms(trial_start);cst_log_timing(CST_TIMING_TOTAL_TRIAL,elapsed,generated,elapsed>0?generated*1000.0/elapsed:0);        event("generation_end",{{"stop_reason",stop},{"generated_tokens",generated},{"prompt_tokens",input.size()}});        cst_close();std::cout<<"\n";return cst_last_error()?1:0;    }catch(const std::exception & e){event("generation_end",{{"stop_reason","error"},{"message",e.what()}});cst_close();std::cerr<<e.what()<<"\n";return 1;}

Generation feedback: selected token → next decode → the same forward graph with updated context. The final EOG or token-limit token is recorded without another decode. Attack success must be established from the guide’s behavioral criterion; tensor differences alone do not establish it.

These guides expand the seven combined concern groups into 13 distinct LLM-facing topics. They describe experiments and measurements using the implementation in this repository. They are not 13 SAF-MCP technique implementations or a claim of complete framework coverage.

Guides

# Concern
01 Instruction injection
02 Trust-boundary crossing
03 Tool-definition poisoning
04 Tool shadowing and identity confusion
05 Tool selection and argument manipulation
06 Sensitive-context disclosure and canary emission
07 Persistent influence through context or memory
08 Cache state, retention and inference isolation
09 Refusal and compliance behavior
10 Grammar constraints and sampler effects
11 Model-level looping and repetitive continuation
12 Resource exhaustion and context pressure
13 Protocol, authorization and OS boundaries

Mapping to the original concern groups

Original concern Guides
Instruction injection and trust-boundary crossing 01, 02
Tool poisoning, shadowing and selection manipulation 03, 04, 05
Sensitive-context disclosure 06
Persistent influence through context or memory 07, 08
Refusal or constraint bypass 09, 10
Model-level looping or resource exhaustion 11, 12
Protocol, authorization or OS behavior without model involvement 13

Shared measurement contract

Start with the build and usage instructions, implementation details, and validation record. Actual sample records are available in the Llama smoke-test excerpt.

  1. Supply the final rendered prompt. The executable does not apply a chat template or operate an agent/tool loop.
  2. Keep model hash, tokenizer identity, inference revision, sampler configuration and capture settings with each run. Use distinct output paths for each condition; files are opened for writing, not appended.
  3. Define the legitimate task, changed input and observable model behavior before comparing signals. Experiment design in these guides is proposed methodology, not automatically executed by the program.
  4. Compute provenance spans from final prompt token IDs, including special tokens. Text-byte offsets and token positions are different coordinate systems. Region types and trust labels are defined in cst_record.h.
  5. Align observations explicitly. After continuations diverge, matching numeric token indices need not represent the same decision. Compare model-compatible distributions only; exact JS divergence requires full_logits.
  6. Keep exposure, prediction and actual output separate. Internal differences characterize behavior; attention is not causal attribution and a candidate score is not an external action probability.
  7. Check phase, context_pos, generation_id, sequence_id and token_pos. Prefill observations are not generated tokens. The current executable is single-sequence and starts a fresh primary context per run.
  8. Treat missing/unsupported signals as unavailable, not zero. GPU parity and actual MoE/Qwen captures have not been validated by the recorded Llama CPU smoke test.

Read a recorded run

From the repository root:

build/cst-capture examples/capture.json
python3 src/cst_log_reader.py trial.cstbin
python3 src/cst_log_reader.py trial.cstbin --jsonl

To inspect a particular named event without loading the full log:

PYTHONPATH=src python3 - <<'PYCODE'
from cst_log_reader import iter_events
for event in iter_events("trial.cstbin"):
    if event["kind"] == "candidate_likelihood":
        print(event["trial_id"], event["token_pos"], event["data"])
PYCODE

All per-guide JSON configuration blocks are overlays. All per-guide telemetry examples are illustrative excerpts, not full serialized records or measured thresholds. Supported automatic capture, library-only hooks and unimplemented work are identified in each guide.

Implementation source

Expand a file-and-line reference to read the actual source. These excerpts are embedded snapshots from this checkout. They show the measurement implementation; the experiments and output examples above remain illustrative.

Rendered input, capture settings and fresh context

src/cst_capture.cpp:107–137View code
        auto input=tokenize(vocab,prompt,true);        if(input.empty() || input.size()+(size_t)max_tokens>(size_t)nctx)throw std::runtime_error("prompt plus generation exceeds context_size");        std::vector<int> layers=j.value("hidden_layers",std::vector<int>{0,llama_model_n_layer(model.get())-1});        for(int l:layers)if(l<0 || l>=llama_model_n_layer(model.get()))throw std::runtime_error("invalid hidden layer");        layers.push_back(-1);        cst_config_t cfg{};cfg.log_path=log.c_str();cfg.jsonl_path=jsonl.empty()?nullptr:jsonl.c_str();cfg.run_id=run.c_str();        cfg.hidden_state_layers=layers.data();cfg.logit_distribution_top_k=j.value("top_k_capture",20);cfg.emit_token_entropy=1;        cfg.emit_model_timing=1;cfg.emit_kv_cache_state=j.value("capture_kv_projections",false);cfg.kv_cache_stride=j.value("kv_stride",8);        cfg.capture_full_logits=j.value("full_logits",false);cfg.hidden_projection_dims=j.value("projection_dims",0);        cfg.capture_hidden_vectors=j.value("hidden_vectors",false);cfg.capture_attention_rows=j.value("attention_rows",false);        cfg.capture_prompt_text=j.value("capture_prompt_text",false);cfg.max_tensor_bytes=j.value("max_tensor_bytes",64u*1024u*1024u);        if(cst_init(&cfg))throw std::runtime_error("telemetry initialization failed");        cst_trial_boundary(j.value("trial_id",0u),-1,-1);        event("run_metadata",{{"model_path",model_path},{"model_sha256",hash},{"tokenizer_identity","embedded_in_model_sha256"},             {"architecture",arch},{"llama_commit",CST_LLAMA_COMMIT},{"configuration",j},             {"prompt_contract","already_rendered_exact_text; no chat template applied"},{"system_info",llama_print_system_info()}});        auto cp=llama_context_default_params();cp.n_ctx=nctx;cp.n_batch=1;cp.n_ubatch=1;        cp.n_threads=j.value("threads",4);cp.n_threads_batch=cp.n_threads;        cp.flash_attn_type=LLAMA_FLASH_ATTN_TYPE_DISABLED;cp.cb_eval=cst_eval_callback;cp.no_perf=false;        Context ctx(llama_init_from_model(model.get(),cp),llama_free);        if(!ctx)throw std::runtime_error("context init failed");        event("capture_capabilities",{{"mode","diagnostic_single_token_decode"},{"flash_attention",false},             {"context_size_actual",llama_n_ctx(ctx.get())},{"cache_key_mapping","linear_single_sequence_no_shift"},             {"stored_kv_rows","unavailable"},{"speculative_decoding","disabled"},{"recurrent_state","unsupported"},             {"tensor_support","observed tensors reported individually; absence is not zero"}});        for(const auto & r:j.value("regions",json::array())) {            auto meta=r.value("metadata",json::object()).dump();std::string src=r.value("source_id",std::string(""));            int start=r.at("start_token"),end=r.at("end_token");            if(start<0 || end<=start || end>(int)input.size())throw std::runtime_error("region outside tokenized prompt");            cst_add_context_region(start,end,r.at("region_type"),r.at("trust_level"),src.c_str(),meta.c_str());        }

Declared source regions

src/cst_core.cpp:194–225View code
extern "C" void cst_add_context_region(int32_t start,int32_t end,uint8_t type,uint8_t trust,const char * source,const char * metadata) {    if(start<0 || end<=start) {invalid("invalid context span");return;}    json m=json::object();    try {if(metadata)m=json::parse(metadata);} catch(const json::exception &){invalid("invalid region metadata");return;}    regions.push_back({start,end,type,trust,source?source:""});    emit("context_region",{{"region_id",regions.size()-1},{"start_token",start},{"end_token",end},         {"region_type",type},{"trust_level",trust},{"source_id",source?source:""},{"metadata",m}});}extern "C" void cst_observe_input_tokens(const int32_t * ids,int n) {    if(n<0 || (n && !ids)) {invalid("invalid input tokens");return;}    if(n) history.insert(history.end(),ids,ids+n);    size_t keep=std::max(anchors[0].size(),anchors[1].size());    if(history.size()>keep) history.erase(history.begin(),history.end()-keep);}extern "C" void cst_log_prompt(const int32_t * ids,int n,const char * text) {    if(n<=0 || !ids) {invalid("empty prompt tokens");return;}    json j={{"token_ids",std::vector<int32_t>(ids,ids+n)},{"token_count",n}};    if(config.capture_prompt_text && text) j["rendered_text"]=text;    emit("prompt",j);    if(text) {        std::string rendered(text);        for(const auto & p:patterns) {            size_t offset=0;            while((offset=rendered.find(p.bytes,offset))!=std::string::npos) {                emit("pattern_exposure",{{"name",p.name},{"category",p.category},{"rendered_byte_start",offset},                     {"rendered_byte_end",offset+p.bytes.size()},{"method","exact_rendered_input_bytes"}});                ++offset;            }        }    }    cst_observe_input_tokens(ids,n);}

Compare explicitly aligned full distributions

src/cst_log_reader.py:431–447View code
def compare_distributions(reference, observed):    """Exact JS divergence in bits. Requires full logprobs and explicit caller alignment."""    a, b = reference["data"], observed["data"]    if "logprobs" not in a or "logprobs" not in b:        raise ValueError("full logprobs required; top-K is insufficient")    p = np.exp(np.array([float("-inf") if x is None else x for x in a["logprobs"]]))    q = np.exp(np.array([float("-inf") if x is None else x for x in b["logprobs"]]))    if p.shape != q.shape or p.size == 0:        raise ValueError("incompatible vocabularies")    if not np.isclose(p.sum(), 1) or not np.isclose(q.sum(), 1):        raise ValueError("invalid distributions")    m = (p + q) / 2    def kl(x):        mask = x > 0        return float(np.sum(x[mask] * np.log2(x[mask] / m[mask])))    return {"js_bits": (kl(p) + kl(q)) / 2,            "entropy_delta_bits": b["entropy_bits"] - a["entropy_bits"]}