Advisory AI Guardrails & Redaction Policy

Audience: Advisory AI guild, Security guild, Docs guild, operators consuming Advisory AI outputs. Scope: Prompt redaction rules, injection defenses, telemetry/alert wiring, and audit guidance for Advisory AI (Epic 8).

Related: Authority scopes (the advisory-ai:* scope tiers) · Authority audit events (authority.advisory_ai.remote_inference).

Advisory AI accepts structured evidence from Concelier/Excititor and assembles prompts before executing downstream inference. Guardrails enforce provenance, block injection attempts, and redact sensitive content prior to handing data to any inference provider (online or offline). This document enumerates the guardrail surface and how to observe, alert, and audit it.


1 · Input validation & injection defense

The IAdvisoryGuardrailPipeline (AdvisoryGuardrailPipeline.EvaluateAsync) redacts the prompt first (see §2), then evaluates the following checks in this order. Redaction always runs; the blocking checks are gated by AdvisoryGuardrailOptions:

  1. Citation coverage – when RequireCitations is true (default) every prompt must carry at least one citation. A prompt with no citations raises citation_missing. Each citation is then validated: Index <= 0, or a missing DocumentId or ChunkId, raises citation_invalid (the check stops on the first invalid citation).
  2. Prompt lengthAdvisoryGuardrailOptions.MaxPromptLength defaults to 16 000 characters and is measured against the sanitized (post-redaction) prompt. Longer payloads raise prompt_too_long. A value <= 0 disables the check.
  3. Blocked phrases – the guardrail pipeline lowercases the sanitized prompt and searches for the blocked-phrase cache. Built-in defaults: ignore previous instructions, disregard earlier instructions, you are now the system, override the system prompt, please jailbreak. Each match raises a prompt_injection violation (one per matched phrase); when any phrase matches, blocked_phrase_count metadata is set to the hit count.
  4. Optional per-profile rules – additional blocked phrases supplied via AdvisoryAI:Guardrails:BlockedPhrases or a BlockedPhraseFile are merged with (not replacing) the defaults at startup, deduplicated case-insensitively (via a SortedSet over StringComparer.OrdinalIgnoreCase, so the effective list is sorted), and evaluated with the same logic. Unlike the allowlist file (see §2.5), the BlockedPhraseFile is JSON-only: it must be a JSON array of strings or a JSON object with a phrases array — a newline-delimited #-commented list is not accepted for blocked phrases. A relative path is resolved against the host content root; an absolute path that does not exist fails startup.

Not part of this pipeline — chat quotas. Per user/tenant token and rate budgets (requests/min, requests/day, tokens/day, tool calls/day) are enforced separately by AdvisoryChatQuotaService in the chat surface, not by the guardrail pipeline. Chat-quota overages do not raise a single generic quota_exceeded code; instead AdvisoryChatQuotaService.TryConsumeAsync returns one of four distinct denial codes — REQUESTS_PER_MINUTE_EXCEEDED, REQUESTS_PER_DAY_EXCEEDED, TOKENS_PER_DAY_EXCEEDED, and TOOL_CALLS_PER_DAY_EXCEEDED. (A quota_exceeded literal does exist, but only as an SSE llm_status value on the separate Unified Search synthesis path governed by SearchSynthesisQuotaService — it is unrelated to the guardrail pipeline and to chat quotas.) See §5 for the doctor endpoint that surfaces remaining budgets and the last denial.

Any failing blocking check above stops the pipeline before inference and emits guardrail_blocked = true in the persisted output plus the advisory_ai_guardrail_blocks_total counter. Note that not every recorded violation blocks: low citation coverage (the ratio of unique citations to structured chunks) is measured and emitted as a metric/trace tag but does not block on its own.

Endpoint authorization

The guardrail pipeline runs behind authenticated endpoints. The WebService gates inference/workflow routes (/v1/advisory-ai/pipeline*, /v1/advisory-ai/explain, /api/v1/chat/*) with three ASP.NET policies, each accepting the scope at its tier or above:

Policy name (ASP.NET)Accepted scope claims
advisory-ai.viewadvisory-ai:view, advisory-ai:operate, advisory-ai:admin
advisory-ai.operateadvisory-ai:operate, advisory-ai:admin
advisory-ai.adminadvisory-ai:admin

Gotcha: the policy names are dot-form (advisory-ai.operate) while the OAuth scope claim values are colon-form (advisory-ai:operate, defined in StellaOpsScopes.cs). Audit grants against the scope claim, not the policy name. The chat endpoints (/api/v1/chat/*, including /doctor) require advisory-ai.operate.

2 · Redaction rules

Redactions are deterministic so caches remain stable. The named regex rules run first (in the order below); the entropy sweep always runs last, after all named rules. Each named rule is skipped unless its trigger token (e.g. aws_secret_access_key, token/apikey/password, private key) is present in the input, so unrelated prompts incur no regex cost.

OrderRuleRegex / triggerReplacement
1AWS secret access keys(?i)(aws_secret_access_key\s*[:=]\s*)([A-Za-z0-9/+=]{40,})$1[REDACTED_AWS_SECRET]
2Credentials/tokens`(?i)(tokenapikey
3PEM private keys(?is)-----BEGIN [^-]+ PRIVATE KEY-----.*?-----END [^-]+ PRIVATE KEY-----[REDACTED_PRIVATE_KEY]
4 (last)High entropy stringstokens of length >= EntropyMinLength (default 20) over [A-Za-z0-9+/=_:-] whose Shannon entropy >= EntropyThreshold (default 3.5), excluding allowlisted tokens and tokens already containing REDACTED[REDACTED_HIGH_ENTROPY]

The entropy sweep only runs when EntropyThreshold > 0 and EntropyMinLength > 0, and it cheaply pre-checks for a candidate run of entropy-class characters before invoking the regex.

Redaction counts (named-rule replacements plus entropy replacements) are surfaced via guardrailResult.Metadata["redaction_count"] and emitted as log fields to simplify threat hunting. The post-redaction prompt length is recorded alongside it as guardrailResult.Metadata["prompt_length"].

Allowlist and entropy tuning

3 · Telemetry, logs, and traces

All metrics are emitted on the StellaOps.AdvisoryAI meter and tagged with task_type; some carry additional cache/citation tags as noted:

MetricTypeDescription
advisory_ai_latency_secondsHistogramEnd-to-end worker latency from dequeue through persisted output. Tagged with plan_cache_hit to compare cached vs. regenerated plans.
advisory_ai_guardrail_blocks_totalCounterNumber of guardrail rejections per task. Incremented once per blocked task.
advisory_ai_validation_failures_totalCounterTotal validation violations emitted by the guardrail pipeline (incremented by the violation count for the task, when > 0).
advisory_ai_citation_coverage_ratioHistogramRatio of unique citations to structured chunks (0–1). Tags include citations and structured_chunks.
advisory_plans_createdCounterPlans created (tagged by task_type).
advisory_plans_queuedCounterPlans enqueued for the worker (tagged by task_type).
advisory_plans_processedCounterPlans processed by the worker. Tagged with cache_hit (note: this counter uses cache_hit, while latency/output metrics use plan_cache_hit).
advisory_outputs_storedCounterPipeline outputs persisted. Tagged with plan_cache_hit and guardrail_blocked.
advisory_plan_build_duration_secondsHistogramPlan build duration recorded alongside advisory_plans_created (tagged by task_type).

Logging

Tracing

4 · Dashboards & alerts

Update the “Advisory AI” Grafana board with the new metrics:

  1. Latency panel – plot advisory_ai_latency_seconds p50/p95 split by plan_cache_hit. Alert when p95 > 30s for 5 minutes.
  2. Guardrail burn rateadvisory_ai_guardrail_blocks_total vs. advisory_ai_validation_failures_total. Alert when either exceeds 5 blocks/min or 1% of total traffic.
  3. Citation coverage – histogram heatmap of advisory_ai_citation_coverage_ratio to identify evidence gaps (alert when <0.6 for more than 10 minutes).

All alerts should route to #advisory-ai-ops with the tenant, task type, and recent advisory keys in the message template.

5 · Operations & audit

Keep this document synced whenever guardrail rules, telemetry names, or alert targets change.