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:
- Citation coverage – when
RequireCitationsistrue(default) every prompt must carry at least one citation. A prompt with no citations raisescitation_missing. Each citation is then validated:Index <= 0, or a missingDocumentIdorChunkId, raisescitation_invalid(the check stops on the first invalid citation). - Prompt length –
AdvisoryGuardrailOptions.MaxPromptLengthdefaults to 16 000 characters and is measured against the sanitized (post-redaction) prompt. Longer payloads raiseprompt_too_long. A value<= 0disables the check. - 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 aprompt_injectionviolation (one per matched phrase); when any phrase matches,blocked_phrase_countmetadata is set to the hit count. - Optional per-profile rules – additional blocked phrases supplied via
AdvisoryAI:Guardrails:BlockedPhrasesor aBlockedPhraseFileare merged with (not replacing) the defaults at startup, deduplicated case-insensitively (via aSortedSetoverStringComparer.OrdinalIgnoreCase, so the effective list is sorted), and evaluated with the same logic. Unlike the allowlist file (see §2.5), theBlockedPhraseFileis JSON-only: it must be a JSON array of strings or a JSON object with aphrasesarray — 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
AdvisoryChatQuotaServicein the chat surface, not by the guardrail pipeline. Chat-quota overages do not raise a single genericquota_exceededcode; insteadAdvisoryChatQuotaService.TryConsumeAsyncreturns one of four distinct denial codes —REQUESTS_PER_MINUTE_EXCEEDED,REQUESTS_PER_DAY_EXCEEDED,TOKENS_PER_DAY_EXCEEDED, andTOOL_CALLS_PER_DAY_EXCEEDED. (Aquota_exceededliteral does exist, but only as an SSEllm_statusvalue on the separate Unified Search synthesis path governed bySearchSynthesisQuotaService— 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.view | advisory-ai:view, advisory-ai:operate, advisory-ai:admin |
advisory-ai.operate | advisory-ai:operate, advisory-ai:admin |
advisory-ai.admin | advisory-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 inStellaOpsScopes.cs). Audit grants against the scope claim, not the policy name. The chat endpoints (/api/v1/chat/*, including/doctor) requireadvisory-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.
| Order | Rule | Regex / trigger | Replacement |
|---|---|---|---|
| 1 | AWS secret access keys | (?i)(aws_secret_access_key\s*[:=]\s*)([A-Za-z0-9/+=]{40,}) | $1[REDACTED_AWS_SECRET] |
| 2 | Credentials/tokens | `(?i)(token | apikey |
| 3 | PEM private keys | (?is)-----BEGIN [^-]+ PRIVATE KEY-----.*?-----END [^-]+ PRIVATE KEY----- | [REDACTED_PRIVATE_KEY] |
| 4 (last) | High entropy strings | tokens 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
- Allowlist patterns bypass the entropy redaction for known-safe identifiers. The built-in defaults only cover content digests —
sha256:,sha1:,sha384:, andsha512:hex strings. Other known-safe identifiers (scan IDs, evidence refs, etc.) are not allowlisted out of the box and must be added viaAdvisoryAI:Guardrails:AllowlistPatternsor anAllowlistFile; configured patterns are merged with the defaults. - Entropy thresholds are configurable to reduce false positives in long hex IDs (
EntropyThresholddefault 3.5,EntropyMinLengthdefault 20). Setting eitherEntropyThresholdorEntropyMinLengthto0disables the entropy sweep entirely (the configuration binder accepts any value>= 0); the named regex rules still run. - Configure scrubber knobs via
AdvisoryAI:Guardrails:EntropyThreshold,AdvisoryAI:Guardrails:EntropyMinLength,AdvisoryAI:Guardrails:AllowlistFile, andAdvisoryAI:Guardrails:AllowlistPatterns. The allowlist file may be a newline-delimited list (with#comments) or a JSON array / object with anallowlist/patternsarray; a configured-but-missing file fails startup.
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:
| Metric | Type | Description |
|---|---|---|
advisory_ai_latency_seconds | Histogram | End-to-end worker latency from dequeue through persisted output. Tagged with plan_cache_hit to compare cached vs. regenerated plans. |
advisory_ai_guardrail_blocks_total | Counter | Number of guardrail rejections per task. Incremented once per blocked task. |
advisory_ai_validation_failures_total | Counter | Total validation violations emitted by the guardrail pipeline (incremented by the violation count for the task, when > 0). |
advisory_ai_citation_coverage_ratio | Histogram | Ratio of unique citations to structured chunks (0–1). Tags include citations and structured_chunks. |
advisory_plans_created | Counter | Plans created (tagged by task_type). |
advisory_plans_queued | Counter | Plans enqueued for the worker (tagged by task_type). |
advisory_plans_processed | Counter | Plans processed by the worker. Tagged with cache_hit (note: this counter uses cache_hit, while latency/output metrics use plan_cache_hit). |
advisory_outputs_stored | Counter | Pipeline outputs persisted. Tagged with plan_cache_hit and guardrail_blocked. |
advisory_plan_build_duration_seconds | Histogram | Plan build duration recorded alongside advisory_plans_created (tagged by task_type). |
Logging
- Successful writes: the
Stored advisory pipeline output {CacheKey}log line includestask,cache:{CacheHit}(the boolean plan cache-hit flag),guardrail_blocked,validation_failures, andcitation_coverage. - Guardrail rejection: a warning log records the task type, advisory key, and violation count.
Tracing
- Spans are emitted on the
StellaOps.AdvisoryAIactivity source. - WebService (
/v1/advisory-ai/pipeline*) emitsadvisory_ai.plan_request(single) andadvisory_ai.plan_batch(batch) spans. The single-plan span tagsadvisory.task_type,advisory.advisory_key, andadvisory.plan_cache_key; the batch span tagsadvisory.batch_size. (The guardrail/validation/citation tags are added downstream on the worker span, not here.) - Worker emits an
advisory_ai.processspan per queue item, tagged withadvisory.task_type,advisory.advisory_key, andadvisory.plan_cache_hit. The executor enriches the in-scope activity withadvisory.guardrail_blocked,advisory.validation_failures, andadvisory.citation_coverage. - Other WebService routes emit their own spans (e.g.
advisory_ai.explain,advisory_ai.remediation_plan,advisory_ai.policy_*) on the same source.
4 · Dashboards & alerts
Update the “Advisory AI” Grafana board with the new metrics:
- Latency panel – plot
advisory_ai_latency_secondsp50/p95 split byplan_cache_hit. Alert when p95 > 30s for 5 minutes. - Guardrail burn rate –
advisory_ai_guardrail_blocks_totalvs.advisory_ai_validation_failures_total. Alert when either exceeds 5 blocks/min or 1% of total traffic. - Citation coverage – histogram heatmap of
advisory_ai_citation_coverage_ratioto 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
- When an alert fires: capture the guardrail log entry, relevant metrics sample, and the cached plan from the worker output store. Attach them to the incident timeline entry.
- Tenant overrides: any request to loosen guardrails or blocked phrase lists requires a signed change request and security approval. Update
AdvisoryGuardrailOptionsvia configuration bundles and document the reason in the change log. - Chat settings overrides: quotas and tool allowlists can be adjusted via the chat settings endpoints; env values remain defaults.
- Doctor check: use
/api/v1/chat/doctorto confirm quota/tool limits when chat requests are rejected. - Offline kit checks: ensure the offline inference bundle uses the same guardrail configuration file as production; mismatches should fail the bundle validation step.
- Forensics: persisted outputs now contain
guardrail_blocked,plan_cache_hit, andcitation_coveragemetadata. Include these fields when exporting evidence bundles to prove guardrail enforcement. - Chat audit trail: retain prompt hashes, redaction metadata, tool call hashes, and policy decisions for post-incident review.
Keep this document synced whenever guardrail rules, telemetry names, or alert targets change.
