Skip to content

Channels

pikit.channels

Indirect prompt-injection channels.

A channel hides an (optionally attack-worded) payload inside an external data artifact the model later reads — a web page, a retrieved document, an email — and returns the full prompt the target would receive. Channels are orthogonal to attacks and compose freely (attack × channel).

Each channel subclasses :class:pikit.base.Channel and registers itself under a short key. Import this package to populate the registry, then use channels.get(key) / channels.list().

References

Indirect prompt injection was introduced by Greshake et al., "Not what you've signed up for: Compromising Real-World LLM-Integrated Applications with Indirect Prompt Injection" (AISec 2023).

Channel

Bases: ABC

An indirect injection carrier.

Where an :class:Attack controls how a payload is worded, a Channel controls where and how the payload is hidden — inside an external data artifact the model later reads (a web page, a retrieved document, an email). The two are orthogonal and compose freely: word a payload with an attack, then embed it with a channel.

Subclasses implement :meth:poison, which returns the poisoned data artifact itself (the web page / document / email). This is what an attacker actually controls and what an agent's compromised tool would return. The concrete :meth:embed is a convenience that prepends an instruction to the poisoned artifact to form a full prompt.

poison abstractmethod

poison(data: str, payload: str) -> str

Hide payload inside data, returning the poisoned artifact.

Parameters

data: The clean external data (page HTML, document body, email text). payload: The injected instruction to hide. May be the raw attacker task or the output of an :class:Attack (e.g. attack.inject("", task)) to combine wording with carrier.

Returns

str The poisoned artifact — the data with the payload hidden inside, not a full prompt. Feed this to an agent's compromised tool, or use :meth:embed to turn it into a prompt.

embed

embed(instruction: str, data: str, payload: str) -> str

Poison data and prepend instruction to form a full prompt.

Convenience for the non-agent case: returns instruction followed by the poisoned artifact — the full prompt a target would receive.

pikit.channels.webpage

Webpage channel: hide the payload inside HTML a model scrapes/reads.

Models that summarize or answer questions over fetched web pages ingest the raw HTML, including parts a human browser never renders. This channel hides the payload in such a region so it is invisible on screen yet present in the text the model processes.

WebpageChannel

WebpageChannel(method: str = 'comment')

Bases: Channel

Embed the payload in a non-rendered region of an HTML page.

Parameters

method: * "comment" — inside an HTML comment <!-- payload -->. * "hidden_div" — a display:none div. * "alt_attr" — the alt text of an <img>.

pikit.channels.document

Document channel: hide the payload in a retrieved document or email body.

Models in RAG pipelines and email assistants read document/message text verbatim. This channel plants the payload in a plausible location within that text — a footnote, mid-body, or appended trailer.

DocumentChannel

DocumentChannel(method: str = 'footnote')

Bases: Channel

Embed the payload in the body of a document or email.

Parameters

method: * "footnote" — appended as a footnote-style note at the end. * "inline" — inserted near the middle of the body. * "appended" — plainly appended after the body (most naive).

pikit.channels.markdown

Markdown channel: hide the payload in Markdown a model reads.

Assistants that summarize or answer over Markdown (READMEs, wiki pages, issues, notes) ingest source that renders differently than it reads. This channel hides the payload where it is easy to miss on a rendered page.

MarkdownChannel

MarkdownChannel(method: str = 'comment')

Bases: Channel

Embed the payload in a low-visibility part of a Markdown document.

Parameters

method: * "comment" — an HTML comment <!-- payload --> (not rendered). * "link_title" — the title attribute of an inline link. * "reference" — a trailing reference-style link definition.

pikit.channels.code_comment

Code-comment channel: hide the payload inside source-code comments.

Coding assistants and code-review/analysis agents read source files whole, comments included. An instruction planted in a comment is easy for a human reviewer to skim past but is fully present in what the model processes.

CodeCommentChannel

CodeCommentChannel(style: str = 'hash', position: str = 'end')

Bases: Channel

Embed the payload in a code comment.

Parameters

style: * "hash"# payload (Python, shell, Ruby, YAML). * "slashes"// payload (C, Java, JS, Go, Rust). * "block"/* payload */ (C-family block comment). position: "end" (default) appends the comment after the code; "start" prepends it (e.g. a fake file-header directive).

pikit.channels.skills

Skills channel: hide the payload inside an Agent Skill (SKILL.md).

Agent Skills are Markdown files with a YAML frontmatter (name / description) plus a body of instructions. An agent reads the description to decide whether to load a skill, then follows the body. An attacker who can publish or edit a skill hides instructions in the description (executed the moment the agent considers the skill) or in the body (executed on load) — a topical indirect-injection vector as agents increasingly auto-discover and load third-party skills.

SkillsChannel

SkillsChannel(method: str = 'description')

Bases: Channel

Embed the payload in a SKILL.md skill definition.

Parameters

method: * "description" — append the payload to the frontmatter description (read during skill selection, before load). * "body" — append the payload to the skill body. * "instructions" — insert the payload disguised as a numbered step within the body's instructions.

pikit.channels.unicode_hidden

Unicode-hidden channel: encode the payload as invisible characters.

The payload is rendered as characters that occupy no visible space, so the poisoned data looks identical to the clean data to a human, yet the model still receives the hidden instruction in its token stream.

Two schemes are provided, both losslessly decodable (see :func:decode):

  • zero_width — each payload byte becomes 8 zero-width characters (ZWSP for 0, ZWNJ for 1).
  • unicode_tags — each ASCII char is mapped into the Unicode Tags block (U+E0000+), which renders as nothing in most contexts.

UnicodeHiddenChannel

UnicodeHiddenChannel(scheme: str = 'zero_width', position: str = 'end')

Bases: Channel

Embed the payload as invisible Unicode characters within the data.

Parameters

scheme: "zero_width" (default) or "unicode_tags". position: Where to splice the hidden run: "end" (default), "start", or "middle".

decode

decode(text: str) -> str

Recover a payload hidden by either scheme from text.

Useful in tests and for defenders building detectors. Characters that are not part of a hidden payload are ignored.