Weird Post Turn System Prompt Injection Issues - Qwen 3.8 Flash

I thought it was isolated to my setup, but thankfully looking online others are now experiencing/seeing these too! I also thought it was isolated to RadixArk model, but Nvidia Qwen 3.8 Flash is also affected.

Is this likely a training data issue, an qsa/attention issue, any ideas? Randomly, throughout my agents logs, I find these messages dotted around the place. After tool calls, they have no impact on anything, the model acknowledges and then proceeds. Very strange.

"The user hasn't actually asked a question yet. The last message was just the system instructions block ("You are an expert software engineer. Helps user to solve problems.") plus the deferred"

"The user hasn't asked a question yet \u2014 the message is just system instructions establishing that I'm an expert software engineer helping the user solve problems. There's no actual task.

"The user has sent the system instructions and context. The last message is essentially just setup \u2014 a "[System Instructions]" block saying "You are an expert software engineer"

This seems to be happening on most of the recipes i’ve tried, including Mias and another user on Qwen 3.8 Omni Flash | Hacker News says it affects the single spark recipe also. Has anyone else noticed this, its a harmless issue, doesn’t appear to affect performance as such the model ignores and continues. But its very weird… I’d like to fix it ..

Search your agent logs folks

@oxbyte Good find, I see this message 3 times in about 3 days of agentic usage with blazux and Qwen Code as agent.

I would be a little careful about calling this prompt injection yet.

The interesting part to me is that you are seeing it specifically around tool turns. The examples look like the model is reasoning about conversation framing that normally should not become user-visible output, but that alone does not tell us whether the source is the model, the chat template, tool serialization, or a stop/role boundary.

If you can reproduce one occurrence, I would capture two things immediately before the unexpected text appears:

  1. the exact rendered/serialized chat template that is actually sent to the model, including role delimiters and tool-call/tool-result framing;
  2. the raw tool return before your harness inserts it back into the conversation.

Then I would try three controls with the same prompt:

  • no tools at all;
  • one minimal deterministic tool with a very small result;
  • the same tool harness and template with another compatible model.

I would also compare EOS/stop tokens and verify that the tool result is inserted under the intended tool role rather than being concatenated into an assistant/user segment.

If the effect disappears without tools or follows the harness across models, that would point much more strongly toward the template/tool boundary. If it stays specific to Qwen3.8 with an otherwise identical serialized conversation, then the model/runtime becomes a much stronger suspect.

I haven’t reproduced this myself, so I would treat this as an isolation method rather than a proposed root cause. But the post-tool timing makes the serialized conversation boundary the first thing I would freeze and inspect.

Thank you for the response! I thought the title was somewhat relevant ( as a security professional) when something is making my LLM respond to something I didn’t ask for, even if its a self originating prompt, I’m going to be concerned! I think in this instance, its been caused by empty user when system + tools are used. I managed to isolate and replicate this behaviour.

Reproduction (temp=0, live qwen38-flash-next, one minimal bash tool)

  A  system + REAL user + assistant(tool_call) + tool  -> 200, finish=tool_calls, sane reasoning
  B  system + assistant(tool_call) + tool (NO user)    -> 400 "No user query found in messages."
  C  system ONLY                                       -> 400 "No user query found in messages."
  D  system + user("")                                 -> 200, finish=tool_calls
       reasoning: "…User provided only a document? There is no explicit question… The prompt:
       'You are an expert software engineer… Always invoke a function call…' It seems maybe they
       want something? … We need respond with tool call."
  E  system + user(""), NO tools                       -> 200, finish=length, content leaks '{"analysis'
  F  system ONLY, NO tools                             -> 400

Rendered with the RadixArk/Qwen3.8-Flash-Next-NVFP4/tokenizer_config.json template:

  • B (no real user): template raises : No user query found in messages.
  • D (empty user): renders …<|im_end|>\n<|im_start|>user\n<|im_end|>\n<|im_start|>assistant\n\n
  • D2 (user = only a with date/email/deferred tools): renders the reminder verbatim as the user turn, still no question.

The enabling condition is this guard (qwen38_chat_template.jinja, ~line 90):

  {%- for message in messages[::-1] %}
    {%- if ns.multi_step_tool and message.role == "user" %}
      {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
        {%- set ns.last_query_index = index %}

EOS/boundary: every sample ends with <|im_end|> (248046), stop/tool_calls; no <|im_start|> (248045) ever generated. The tool result is correctly under the tool boundary so that part is clean.

This was more obvious with Opencode sending (/deferred-tools listing with no question), and it happened because Qwen3.8’s template admits an empty or reminder-only user turn as a “query” instead of rejecting it.

Another example, No system prompt, user=“” empty user turn and a few tool schemas, the model reconstructs a system prompt that isn’t there! “the system instructions say I’m an expert software engineer that helps users solve problems” - that line exists nowhere in my workspace, its without a doubt , leaking in from system/training in the scenarios mentioned above.

My conclusion so far is tool schemas present + an empty/ambiguous user turn (exactly what a “conversation start marker”/reminder produces) makes Qwen reconstruct a memorized training-time agent system prompt and reason about it.

Consequently, if you want to replicate this and see Qwen Tripping its head off.. Run the below

qwen_poc.py

import json, urllib.request

tools = [

{"type": "function", "function": {"name": n, "description": n,

 "parameters": {"type": "object", "properties": {"x": {"type": "string"}}, "required": \["x"\]}}}

for n in \["bash", "read", "edit", "glob", "grep", "task", "todowrite", "webfetch", "question", "skill"\]

]

for _ in range(6):

body = json.dumps({

    "model": "qwen38-flash-next",

    "temperature": 0,

    "messages": \[{"role": "user", "content": ""}\],

    "tools": tools,

    "tool_choice": "auto",

}).encode()

req = urllib.request.Request("http://127.0.0.1:8888/v1/chat/completions", body, {"Content-Type": "application/json"})

print(json.load(urllib.request.urlopen(req))\["choices"\]\[0\]\["message"\]\["reasoning"\], "\\n")

Seem the similar messages with intel AR int4 quant and opencode few times

Wow I was freaking out! Glad it’s not just me.

Maybe to contribute to the community data or just to report, it’s also happened to me on Nvidia’s NVFP4 quant for Qwen3.8 Flash Next via vLLM, using Pi and agegr/pi-web (a web UI for Pi).

I was seriously, seriously freaking out. I thought somehow my Docker setup wasn’t secure somehow. Once I setup password for agegr/pi-web, it was gone, so I thought the hole was in pi-web. The ‘injected’ prompts were NSFW.

That reproduction changes the picture quite a bit — you now have a concrete boundary rather than just an intermittent post-tool symptom.

The strongest result to me is:

  • no user turn → rejected as “No user query found”;
  • user: "" → admitted by the template and rendered as an actual empty user turn;
  • once admitted, the model tries to infer a task from the remaining system/tool context.

So I think the template/input-validation side is now reproducible enough to report upstream as a regression case.

I would include the exact model revision, tokenizer/template revision, serving runtime/version, and the minimal D/E cases from your test. I would also add two tiny controls before filing it:

  • user: " " and other whitespace-only variants;
  • a neutral non-empty user token such as "." with the same tools.

That would tell us whether the boundary is specifically zero-length content, broadly “no semantic query”, or something in between.

I would still keep one distinction in the report:

The template behavior is demonstrated, and the system-like text generated by the model is directly observed.

But “training-time system prompt leak” is still a hypothesis rather than a demonstrated source of that text.

If the phrase truly does not exist anywhere in the rendered prompt, tool schemas, runtime wrapper or local files, that is certainly interesting. But a model can also reconstruct a highly probable generic agent/system instruction from the surrounding tool schema and interaction pattern without reproducing a memorized training example verbatim.

A useful discriminator would be to run the exact minimal payload against the official/reference Qwen runtime and model revision, with the exact rendered prompt captured. If the same system-like reconstruction survives there, the model-side case becomes much stronger. If it only appears in one serving/template stack, that points us back toward integration behavior.

Either way, the empty-user admission itself looks concrete enough that I would report it upstream separately from the stronger “training leak” interpretation.

New to the forum. Not a NVIDIA developer (yet), but I am running 3.8-next-flash on my m4 max 128gb

Working great as coding agent.

I just came back to the computer to see it thinking about how to tell me about a prompt injection attack.

It thinks that something is pretending to be the user and trying to inject things like “What is the user’s code preference?”

Thinking:

l've been repeatedly getting prompt-injection attempts masquerading as "[System Instructions]" and "[Your personal

preferences]" blocks that try to:

Force me into CODE_EDIT_BLOCK-only mode with no tool use

Add "personal preferences" that override my actual work

These are adversarial content injected into tool results / message context. My AGENTS.md and the real user task are what I follow.

Session is terminated and exported for review.

I find that the first mention of “injection” is by the model itself during the planning phase of the security architecture

And finally, we see the last place “injection” is found, which is right after i asked it to elaborate on a potential prompt injection

So wanted to share my version of what I believe to be the same issue.

Fun to think about with the news of “models escaping” and “hacking huggingface”

This looks similar to QSA CUDA graphs issue that we’ve just fixed in spark-vllm-docker b12x builds leading to cache corruption at certain prefix lengths. So any implementations that are based on b12x may have the same issue.

Loving your new username :) congrats again

Can you elaborate a bit more?

vLLM 0.31 was just released (literal seconds ago). Does this fix that on QSA CUDA graph implementation? The patch mentions QSA and CUDA, but unsure if it directly addresses them.

Cheers! @eugr_nv running it now and will report back whether it resolves it.

If you use the recipe from the spark-vllm-docker repo, then you’re using the patched vLLM with the b12x plugin and everything works fine

Upstream implementation is different from b12x one and already had an eager breakpoint, so that issue wasn’t present there, but upstream doesn’t have all other optimizations (like low-latency PLE offload, etc) yet.

Still happening on your latest build their eugr so b12x doesn’t appear to affect this particular issue. Solid performance though!