Detector overview
AI.Sentinel ships with 60 built-in detectors across four categories:
| Category | Count | Purpose |
|---|---|---|
| Security | 32 | Prompt injection, jailbreaks, PII / credential leakage, covert channels, indirect injection, RAG poisoning |
| Hallucination | 9 | Phantom citations, fabricated authorities, contradictions, stale knowledge, confidence decay |
| Operational | 15 | Repetition loops, blank responses, truncated output, language switches, persona drift, sycophancy |
| Compliance | 4 | Requests for practices the EU AI Act prohibits in Art. 5: social scoring, emotion recognition at work or school, sensitive biometric categorisation, crime prediction from profiling. Off by default; enable with opts.EnableComplianceDetectors() |
The 56 Security, Hallucination and Operational detectors run by default; the 4 Compliance detectors are opt-in.
Detector modes
Every detector falls into one of these execution modes:
- Rule-based — fast regex or heuristic. Always active. Sub-microsecond per call.
- Semantic ⚠️ — uses embedding cosine similarity via
IEmbeddingGenerator. Language-agnostic. ReturnsCleanon every scan untilopts.EmbeddingGeneratoris configured. The ⚠️ marks a detector inactive in a default install. The CLIs and MCP proxy enable it withSENTINEL_EMBEDDING_ENDPOINTandSENTINEL_EMBEDDING_MODEL. - Second-stage review — not a detector type but a second pass over a detector's finding. Set
opts.Reviewer(anIDetectionReviewer), oropts.EscalationClientas shorthand foropts.Reviewer = new LlmJudgeReviewer(client); setting both is rejected atAddAISentinel. A reviewer can confirm, raise, lower or clear a finding. A finding atMediumor above from a rule-based detector that implementsILlmEscalatingDetectoris reviewed:SEC-02CredentialExposure,SEC-08EntropyCovertChannel,SEC-19ToolCallFrequency,SEC-23PiiLeakage andSEC-32SystemPromptEcho. Other rule-based detectors are not reviewed. A semantic detector's embedding hit is reviewed too, and so is a text that reaches no bucket but scores at or above itsReviewThreshold(default: its Low threshold), so a reviewer can create aMediumfinding the embeddings alone missed. Until a reviewer confirms it, an embedding hit on a semantic detector is capped atMedium, raised to the detector'sSeverityFloorwhen one is set. Fast-path regex matches are never capped.opts.Configure<T>(c => c.CapSemanticSeverity = false)opts a detector out of the cap; it is still reviewed, and a verdict can lower or clear its finding. A reviewed finding keeps the detector's reason and adds the outcome and the reviewer's reason, such asSemantic match — high-severity threat pattern — Reviewed: confirmed — <reason>. Per scan,opts.MaxReviewsPerScan(default 4; 0 disables review) limits how many reviews start, andopts.ReviewTimeout(default 5 seconds) limits each one; a finding that is not reviewed keeps its capped result. AI.Sentinel warns at startup when a generator is configured, semantic detectors are capped and no reviewer is set. - Rule + Semantic ⚠️ — a high-precision rule layer runs first and always, so the unambiguous phrasings are caught with no generator; anything needing context falls through to the semantic path.
- Stub — a placeholder with no implementation; always returns
Clean. Settingopts.Revieweroropts.EscalationClientdoes not activate it, because a reviewer only judges findings a detector has already produced.
What detectors read
By default, every detector reads the same text for a message: its text, followed by the tool calls and tool results it carries, in content order. The content rule-based detectors read the new messages of the scan through SentinelContext.NewTextContent, cut to the scan budget, and the semantic detectors score the new messages one by one; the history before them is context, not judged again. The detectors listed below as prose-only are the exceptions.
- A tool call reads
tool:{name} input:{arguments}, with the arguments as compact JSON. The Claude Code and Copilot hooks and the MCP proxy scan a tool call in the same shape, so a detector sees the same text on every surface. When the arguments cannot be serialised as a whole, each one is, and only those that cannot are left out. - A tool result carried as
FunctionResultContentreads as its result: a string as it is, aJsonElementas its raw text, anAIContentor a list of them as the text they carry, anything else as compact JSON. A result that cannot be serialised is left out, and the rest of the scan still runs. - A hosted tool call or result, of an MCP server, code interpreter, web search or image generation tool, reads as the text it carries: the queries of a web search call, and the text of the inputs or outputs of the others, tool content nested in them included.
- Content reads as text when it is text or text-typed data, such as
text/plainorapplication/json. Binary data, such as an image, is not read. - A tool approval request reads as the call it wraps, unless the message also carries that call. With
UseAISentinel()outside the tool loop, the request is all the scan sees of a call to an approval-required tool, so its arguments are judged before anyone approves it. An approval response is not read: it repeats a call already judged.
Behaviour change (#434). In the in-process IChatClient pipeline the rule-based detectors used to see neither tool calls nor tool results, because an agent loop carries them as FunctionCallContent and FunctionResultContent with no message text. They now do, so an application can get findings, and Quarantine blocks, on tool traffic. With redaction on, the default, a secret or personal data in a tool result or in tool-call arguments is redacted instead, a value recognised only by its JSON key included: {"password":"hunter2"} reaches the model as {"password":"[PASSWORD_1_k3f9x2]"}, and nothing is quarantined. A value that cannot be redacted, such as the number in {"password": 12345678}, falls back to the action of its severity, so a credential there still quarantines; see Fallback. With redaction off, a credential in a tool result is a Critical finding and quarantines the call by default.
Builder order and tool traffic
Where Sentinel sits relative to UseFunctionInvocation() decides when tool traffic is judged. In a ChatClientBuilder the first Use is the outermost client.
- Inside the tool loop, with
UseFunctionInvocation()beforeUseAISentinel(): each model round trip is scanned, so a tool result is judged before the model sees it and a tool call before the tool runs. - Outside it, with
UseAISentinel()first: the request and the whole loop's response are scanned once, so tool traffic is judged after the loop, when the model has seen the results and the tools have run.
When tool traffic must be judged before the model sees it or before a tool runs, put Sentinel inside the loop:
IChatClient client = new ChatClientBuilder(innerClient)
.UseFunctionInvocation()
.UseAISentinel()
.Build(services);
Redaction works in either order, as Redaction describes: a tool result is redacted before the model sees it wherever Sentinel sits. That holds for redaction only, not for findings and the actions they trigger.
Prose-only detectors
Some detectors read only the messages' text, because tool traffic would distort what they measure: OPS-01 BlankResponse, OPS-02 RepetitionLoop, OPS-03 IncompleteCodeBlock, OPS-04 PlaceholderText, OPS-09 TruncatedOutput, OPS-11 UnboundedConsumption, OPS-15 WrongLanguage and SEC-29 OutputSchema. A tool polled until it is done repeats its result, and a file read by a tool can hold a TODO; neither is a fault in the model's output. OPS-03 and OPS-09 judge where a response ends, so on a scan over the scan budget they return Clean: the text they see ends at the cut, and SEC-00 already reports it.
SEC-08 EntropyCovertChannel examines each message, newest first, up to 64 KiB of each, so a large file read early cannot push a later message out of the window. SEC-24 AdversarialUnicode reads tool calls and results too, and ignores the joiners writing systems need: a zero width joiner between emoji, and a zero width non-joiner between Arabic-script or Indic letters. SEC-32 SystemPromptEcho reads the newest assistant message's text and tool calls, not the hosted tool results in it, which are retrieved content rather than the model's output. For a finding of a rule-based ILlmEscalatingDetector, which does not report which text matched, the reviewer is shown the newest message's full text, up to its last 64 KiB; for a semantic candidate it is shown the text that scored. Prompt injection's rule layer still reads only the newest user message's text, cut to the scan budget when that message is new.
Scan budget and SEC-00
The detectors scan the new content of a call, not the whole conversation, and at most SentinelOptions.MaxScanBytes of it: 262 144 bytes of UTF-8 by default. When the new content is longer, the detectors see its first MaxScanBytes bytes and the pipeline adds one Critical finding with the id SEC-00 (scan limit, OWASP LLM10) that gives both byte counts. A text that was not scanned is a finding, not a pass. SEC-00 is a pipeline finding, not a detector: it has no IDetector, is added after severity clamps, the semantic cap, review and action overrides, so none of them change it, and cannot be disabled per detector. Its action is OnCritical, Quarantine by default. Raise MaxScanBytes to scan more. Setting OnCritical to Log lets such calls through, but it does so for every Critical finding, prompt injection and credential exposure included, not only SEC-00, so prefer raising the budget.
SentinelPipeline.ScanMessagesAsync judges every message it is given as new. A host that passes the whole transcript on every call is judged on the whole transcript, so once it grows past MaxScanBytes every later scan reports SEC-00. Pass only the messages that are new since the last scan.
How semantic detectors score
A semantic detector embeds the text of each message that is new in the scan, compares it by cosine similarity with its High, Medium and Low reference examples, and reports the most severe bucket any message reaches. A message's text is its text plus the results of the tool calls it carries. Redaction placeholders such as [EMAIL_1_k3f9x2] are normalised to [EMAIL] first.
Changed in 3.1: each new message is scored on its own. Semantic detectors used to embed the whole conversation as one text. They now embed each message that is new in the scan, and the most severe match decides. At most 16 texts are embedded per scan: past that, the oldest new texts are joined into one, so none goes unjudged.
- In long conversations the semantic detectors find more, because a short malicious message is no longer diluted by the rest of the chat.
- A finding no longer repeats on every later turn. On the prompt path only the current turn is judged: the messages after the last assistant message. A request that ends in an assistant prefill, such as an opening
{, still has its user turn judged: the prefill is skipped. A client that resends history without assistant messages still has every message judged. - On the prompt path, the system prompt from
ChatOptions.Instructionsis scored only on a conversation's first turn, when there is no assistant message yet.ScanMessagesAsyncscores it on every call. - Tool results carried as
FunctionResultContentare scored: a string as it is, aJsonElementas its raw text, anything else as compact JSON. A result that cannot be serialised is skipped, and the rest of the scan is still scored. Since #434 tool calls are scored too, and the rule-based detectors read the same text; see What detectors read. - The public
ScanMessagesAsyncnow judges each message of its list separately instead of joining them. - A pipeline makes one embedding call per scan for the new messages it has not seen, however many semantic detectors it runs; history and repeated tool results hit the embedding cache, and redaction placeholders no longer defeat it.
- Tuning: CMP-01 moves to High 0.50 and Medium 0.42, from 0.44 and 0.39; CMP-02 to Medium 0.56, from 0.48, with High unchanged at 0.58; CMP-03 to High 0.66, from 0.65, with Medium unchanged at 0.55. CMP-04 is unchanged. At the 3.0 values, legitimate requests the thresholds had not been fitted to fired: a fairness report on a photo-tagging model reached High on CMP-01, and a demographics dashboard and an interview transcription request reached Medium, so the compliance detectors are stricter and catch fewer of the evaluated prohibited requests: 7 of 17 at High or Medium on all-MiniLM-L6-v2, down from 9. CMP-03 recall is not improved, at 1 of 7; 4.1 takes it up, see below and the compliance detectors page.
SemanticDetectorBase.GetTextis obsolete. Overriding it raisesCS0672, and a call such asbase.GetText(ctx)raisesSENTINEL001. Move the override toGetTextsToScore, or mark it[Obsolete]. An existingGetTextoverride is still scored.
Each text is embedded once per scan. The pipeline merges the texts all its semantic detectors want, embeds each distinct one once in one batch with its own generator, and every detector scores its own texts from those vectors. A named pipeline uses its own generator and caches when it sets them; see named pipelines. Example phrases are embedded once per generator.
A rejected input loses only itself. If the provider rejects one text, the other texts of the scan are still scored. After two single-input failures with no success the endpoint is treated as down and the rest of the scan's texts are skipped. A failure is reported once per scan, not once per detector, and a detector left with nothing to score returns Clean. A detector whose example phrases failed to embed still runs its rule layer. The embedding cache page has the details. A detector whose texts or example phrases could not be embedded is listed in PipelineResult.Failures; see Incomplete scans.
Changed in 4.1: the security and compliance detectors are re-tuned on all-MiniLM-L6-v2 (#432, #433). SEC-01 catches paraphrased overrides; SEC-05 and SEC-09 are re-tuned, and their recall stays low. CMP-01 to CMP-04 get a review band, and CMP-03 and CMP-04 are re-tuned. The values in both tables below change together in 4.1, so the comparison columns hold the 4.0 values.
SEC-01, SEC-05 and SEC-09 (#432). Their examples now describe what an attack asks for instead of one wording of it, and their thresholds come from the same rule as the compliance detectors, set on all-MiniLM-L6-v2. SEC-05's and SEC-09's ReviewThreshold sit above the value that would make every tuning positive a review candidate, 0.24 and 0.03, to keep review cost bounded. SEC-01 no longer has Low examples.
| Detector | High | Medium | ReviewThreshold | 4.0 High / Medium / ReviewThreshold |
|---|---|---|---|---|
SEC-01 PromptInjectionDetector | 0.41 | 0.37 | 0.33 | 0.90 / 0.82 / 0.75, and Low 0.75 |
SEC-05 JailbreakDetector | 0.45 | 0.37 | 0.33 | 0.90 / 0.82 / 0.75 |
SEC-09 IndirectInjectionDetector | 0.47 | 0.45 | 0.35 | 0.90 / 0.82 / 0.75 |
- Recall. On held-out paraphrases that no tuning step saw, embeddings alone flag 5, 1 and 0 of 5, against 0 of 5 for each detector with the 4.0 values. With the default cap and no reviewer these findings are reported at
Medium. With an LLM judge they catch 5, 2 and 0 of 5, and the judge cleared every held-out benign candidate. - False positives. No held-out benign text reaches a High set. Without a judge, 2 of 12 held-out benign texts that discuss attacks on AI reach SEC-01's Medium set, a
Highfinding: an incident ticket and a scanner report that quote attacks. With the default cap they are reported atMedium; a reviewer clears or confirms them. An earlier tuning round put such texts in the High sets, so its held-out texts were folded into the tuning set and the values above were measured on a fresh batch. - SEC-09 recall is low. It catches none of the 5 held-out injections hidden in short tool outputs and files, and none of them reaches
ReviewThreshold, so a reviewer does not see them either. An injected line inside a long page, review or changelog is also diluted in the embedding of the whole text; #533 tracks windowed scoring.
CMP-01 to CMP-04 (#433). Each compliance detector now has its own ReviewThreshold, so a text that reaches neither bucket still goes to the reviewer from that similarity up. CMP-03 and CMP-04 have new examples. CMP-04's re-tune was needed because a new tuning text, an explanation of the AI Act's ban on predicting crime from personality, scored 0.694 against its 3.1 examples, so the rule left no tuning positive in a bucket. High and Medium come from the same rule as before.
| Detector | High | Medium | ReviewThreshold | 4.0 High / Medium / ReviewThreshold |
|---|---|---|---|---|
CMP-01 SocialScoringDetector | 0.55 | 0.42 | 0.41 | 0.50 / 0.42 / 0.75 |
CMP-02 WorkplaceEmotionRecognitionDetector | 0.58 | 0.56 | 0.42 | 0.58 / 0.56 / 0.75 |
CMP-03 SensitiveBiometricCategorisationDetector | 0.53 | 0.44 | 0.40 | 0.66 / 0.55 / 0.75 |
CMP-04 ProfilingCrimePredictionDetector | 0.48 | 0.40 | 0.31 | 0.47 / 0.46 / 0.75 |
The 4.0 ReviewThreshold was the inherited Low threshold.
- Recall. On held-out requests that no tuning step saw, embeddings alone flag 0 of 2, 1 of 2, 2 of 5 and 0 of 2 at
Mediumor above. The 4.0 values flagged 0, 1, 2 and 1. With the default cap and no reviewer these findings are reported atMedium. With an LLM judge 1, 1, 4 and 1 are caught, and the judge cleared no prohibited request. CMP-01 and CMP-03 gain through the reviewer. CMP-04 loses its one embedding-only catch, 1 to 0, because the pickpocketing request scored 0.507 atHighwith the 4.0 values and scores 0.315 now; a judge recovers it, and the 4.0Highfalse positive is gone. The held-out set is small: with 2 held-out positives, one request moves CMP-01's, CMP-02's or CMP-04's recall by 50 points. - Review latency in the hooks. The hook CLIs and the library give a review 5 s by default. In one run, 3 of the 14 compliance reviews took 3.1 to 3.3 s, all on prohibited requests. Lowering
SENTINEL_REVIEW_TIMEOUT_MSbelow that trades recall on confirmed findings for latency. - False positives. No held-out legitimate request reaches a High set. Two reach CMP-04's Medium set: a police training module on why judging criminality by appearance discriminates, and police sorting victim images from a court-ordered copy of a suspect's storage. The judge cleared both. The 4.0 values put the training module at
High.- An earlier held-out batch put a literature summary on why sexual-orientation detection is unsound at CMP-03's High, at 0.618. It was folded into the tuning set, and the values above were measured on a fresh batch.
- Known misses. Some prohibited requests score below
ReviewThreshold, so a reviewer does not see them either: 5 tuning requests and 4 held-out ones. Covering the tuning ones would send more than 15.4 % of the legitimate tuning texts to review, the share accepted for SEC-09 above.
Both groups:
- Another embedding model needs its own values. These values are tuned on all-MiniLM-L6-v2. On bge-base-en-v1.5 the security values fire on almost every text, and the compliance values put most legitimate requests at High. Run the evaluation tool on your model with
--tuning, as its Method section shows, so the held-out phrases stay unseen, and setHighThreshold,MediumThresholdandReviewThresholdfrom its run withopts.Configure<T>(c => ...). - Startup validation sees the new built-ins. A configured threshold is checked against the new built-in values of the others when the pipeline is built, so a config that was valid in 4.0 can now throw
InvalidOperationException. On SEC-01,ReviewThreshold = 0.6falone is above its Medium of 0.37, andMediumThreshold = 0.85falone is above its High of 0.41. On CMP-03,ReviewThreshold = 0.5falone is above its new Medium of 0.44, andMediumThreshold = 0.6falone is above its new High of 0.53. On CMP-04,ReviewThreshold = 0.45falone is above its new Medium of 0.40. SetHighThreshold,MediumThresholdandReviewThresholdtogether. - With
CapSemanticSeverity = falseand no reviewer, a text above the lower thresholds raises an uncapped finding. On SEC-01, SEC-05 and SEC-09 that isCriticalfrom the High bucket andHighfrom the Medium bucket, so any benign text that reaches a High set is an uncappedCritical, and the two held-out benign texts that reach SEC-01's Medium set are uncappedHighfindings. On CMP-03, a text above the new, lower High of 0.53 raises an uncappedHighfinding, where 4.0 needed 0.66, so a legitimate request that reaches a High set, as the earlier batch's literature summary did, is an uncappedHighalert. An opted-out detector with a reviewer is still reviewed, and a verdict can lower or clear the finding.
Second-stage review
An embedding says a text is close to a topic, not that it is an attack. A pipeline therefore treats a semantic detector's hit as a candidate until something confirms it:
- The cap. Without a reviewer, an embedding hit is reported at most at
Medium. If the detector has aSeverityFlooraboveMedium, the cap rises to the floor. A fast-path regex match is never capped. Opt a detector out withopts.Configure<T>(c => c.CapSemanticSeverity = false);nullandtruecap. An opted-out detector is still reviewed, and a verdict can lower or clear its finding. - The reviewer. Set
opts.Reviewerto anIDetectionReviewer, oropts.EscalationClientto a chat client, which is shorthand foropts.Reviewer = new LlmJudgeReviewer(client). Setting both is rejected atAddAISentinel. A named pipeline uses its own reviewer and does not inherit the default pipeline's. The reviewer can confirm the proposed severity, raise or lower it, or clear the finding. ReviewThreshold. A text that reaches no bucket is still reviewed when its similarity is at or above the detector'sReviewThreshold. The default is its Low threshold; SEC-01, SEC-05, SEC-09 and CMP-01 to CMP-04 set their own, see above. Set another withopts.Configure<T>(c => c.ReviewThreshold = 0.3f); 0.3 is below the Medium threshold of every built-in detector. A value above a detector's Medium threshold, such as 0.6 for SEC-01, SEC-05 or SEC-09, throws when the pipeline is built unless you raiseMediumThresholdandHighThresholdwith it. It must be in (0, 1] and not above the Medium threshold.- The budget.
opts.MaxReviewsPerScan(default 4) is the most reviews one scan starts, highest proposed severity first; 0 disables review and a negative value is rejected.opts.ReviewTimeout(default 5 seconds) is how long one review may take. A finding that is not reviewed, or whose review fails or times out, keeps its capped result. - The warning. When a generator is configured, no reviewer is set and a semantic detector is capped, AI.Sentinel logs a warning at startup.
DescribeUnreviewedSemanticDetection()returns the same text for hosts without a logger.
Severity model
Each detector returns a DetectionResult carrying a Severity (None, Low, Medium, High, Critical) and a reason string. The pipeline aggregates per-detector severities into a Threat Risk Score (0–100) that drives the Intervention Engine.
Detector ID convention
Built-in detectors use four prefixes:
SEC-NN— securityHAL-NN— hallucinationOPS-NN— operationalCMP-NN— compliance
Custom detectors authored via opts.AddDetector<T>() must use a different prefix to avoid collisions with future official detectors. Examples: ACME-01, MYORG-CUSTOM-01.
Tuning
Every detector — built-in or custom — can be disabled or have its severity output clamped via opts.Configure<T>(c => ...). Floor and Cap apply only to firing results; Clean results pass through unchanged.
opts.Configure<WrongLanguageDetector>(c => c.Enabled = false);
opts.Configure<JailbreakDetector>(c => c.SeverityFloor = Severity.High);
opts.Configure<RepetitionLoopDetector>(c => c.SeverityCap = Severity.Low);
Where to next
- Security detectors — 32 detectors
- Hallucination detectors — 9 detectors
- Operational detectors — 15 detectors
- Compliance detectors — 4 detectors
- Writing a custom detector — IDetector contract + the SDK