Trust, taint and provenance
An AI agent that reads a GitHub issue written by a stranger has just taken instructions from a stranger. Nothing in the agent's architecture distinguishes "the user asked me to" from "a comment in the issue asked me to".
Memnox does not try to detect that. Detection is a losing arms race against natural language. Instead it tracks where the context came from and raises the bar for what may be done afterwards.
Two words, kept apart
Trusted is about the author: does this person resolve to a known human in this workspace?
Tainted is about the session: has this agent read anything that was not ground truth?
An event is tainted at ingestion; a session becomes tainted by reading one.
Classification is actor-aware
It is not enough to ask what kind of source something is. The same GitHub issue is trustworthy from a maintainer and not from a passer-by, so classification looks at both.
Source
github_file, github_symbol, github_line_chunk
extracted_decision
GitHub issue or comment from OWNER / MEMBER / COLLABORATOR
The same issue from NONE
Slack message from a workspace member
Slack message from outside the workspace
Email, documents, third-party chat
Anything else
Derivatives ending in _enriched inherit their base classification. An LLM
rewrite cannot launder taint, summarizing a hostile document produces a
tainted summary.
Taint attaches to the session
Not to strings. Once an agent's session has read untrusted content, the session stays tainted for the store's TTL, and taint merges monotonically, it can be added, never removed.
There is no scrubbing step, because there is no reliable way to know which of the model's later tokens were shaped by what it read.
What a tainted session may not do
Privileged actions from a tainted session require a human:
file.write · shell.execute · deploy.* · database.* · mcp.* · data.export · *.deleteTwo are non-overridable: project.delete and database.drop are blocked
outright, and no approval, routine or break-glass, lifts the block.
Fail-closed, deliberately
Every other advisor fails silent: if it cannot run, it raises nothing, because a broken advisor must not block work.
Provenance is the exception. If the session taint store cannot be read, the session is treated as tainted. The reasoning: an unreadable taint store is exactly the situation in which you least want to assume innocence.
Reporting taint
The caller reports provenance on the check:
await memnox.check({
action: 'file.write',
target: 'src/config.ts',
sessionId: runId,
taint: [{ sourceType: 'github_issue_comment', actor: 'NONE' }],
});The MCP firewall and the editor hooks do this for you. A custom loop that reads external content and does not report it is the one gap in the model, and it is a gap you opened, not one Memnox has.
Running it across pods
Without Redis the taint store is per-process, which means a session tainted on
one pod is clean on the next. --redis-url moves it into Redis with a
lock-guarded read-merge-write and a 7-day TTL.
It is never reconstructed from the audit log. An audit event records that an action happened, not what the model read.

