ANSSI-PA-102 mapping for colandix
This document explains how colandix maps to the ANSSI guide for secure generative AI deployment.
Two outputs per scan. Each scan_input or scan_output call yields:
ScanResult.sanitized_text: rewritten text ready for the model (PII, secrets, and blocked topic keywords masked viaDetectionEvent.match_textfor regex; NER / blocked-topic evidence otherwise; “out of scope”alloweddrives the decision without a dedicated mask span inapply_redactions). For NER, masking uses the first person span (PERorPERSONper SpaCy andentities). Each replacement uses a typed tag fromDetectionEvent.trigger_type(e.g.[EMAIL_REDACTED]including EMAIL_OBFUSQUE,[PHONE_REDACTED]for TEL_FR / TEL_US / TEL_INTL,[NIR_REDACTED]including SSN_US and NINO_UK,[CRYPTO_REDACTED],[TOPIC_REDACTED]) viaredaction.py; unknown or unmapped types fall back to[REDACTED]. Optionalplaceholder=onapply_redactionsforces one string for all spans.ScanResult.action: aggregate decision (pass,warn,human_review,block).ScanResult.blockedmeansaction == block.
YAML action qualifies each detector; aggregate_score applies thresholds per detector action family (see README and architecture.md, scoring). Tests: testing.md, “Scoring, redaction, and masking”. Triggers: triggers-par-profil.md.
R25: Input and output filtering and rewriting
ANSSI expectation (paraphrased):
Filter model inputs and outputs to detect and block malicious or unauthorized content.
colandix:
The main R25 artifact is ScanResult.sanitized_text: PII, structured secrets (regex / NER), and (when masking applies) blocked topic fragments replaced with typed tags (see redaction.py and get_redaction_tag). Out of scope (TopicDetector with allowed) mostly drives ScanResult.action (block on shipped profiles) without a dedicated apply_redactions fragment. Detection also feeds ScanResult.action for block or human review.
Active detectors:
RegexDetector: sensitive data (NIR, SSN_US, NINO_UK, SIRET, IBAN FR / generic IBAN, French passport,CARD_PAN/CARD_AMEX/CARD_DISCOVER_UNIONPAY, DB URLs, markings, private and IPv6 IPs, FR phone (+33, E.164+336…asTEL_FR), E.164 international (TEL_INTL), US NANP on exhaustive profiles,EMAIL/EMAIL_OBFUSQUE, known secrets (API_KEY_SK, tokens,AWS_*, Slack, HF, Stripe, Google, npm,TWILIO_SID, JWT,CRYPTO_*,CONNECTION_STRING,CREDENTIAL, PEM, …). Each regex event carriesmatch_textandtrigger_type.ALNUM_MIXED_12:strict/devusejeton_alnum_mixedinhuman_review.devuses explicitcredentials_codein YAML; otherstrictcoverage followspii_completblock(builtins minusALNUM_MIXED_12,exclude_patterns).juridique:clauses_sensiblesblock.EntropyDetector: context (password=, JSON"password":, Authorization, env SCREAMING_SNAKE) → score 1.0; onstrictYAMLblock. No context: Shannon + complexity;gray_structuralcapped at 0.75.analyze_token()is Shannon-only, see architecture.md.NERDetector: SpaCy, defaultfr_core_news_md; other pipeline viaextra.model.PERon shipped profiles;PERSONon Englishen_core_web_*.action: blockon NER is coerced tohuman_reviewunlessextra.ner_allow_block: true.GuardPipeline.ner_fr_core_status()lists each NERmodelname.InjectionDetector: normalized text; 1.0 per category by default (one pattern can veto BLOCK ≥ 0.9). Includes EN/DE/ES/PT/IT resets andNEW_PERSONA, jailbreak, gzipH4sI…, URL-encoding, etc. See triggers-par-profil.md.apply_redactions(scoring.py): prefersmatch_text; merges overlaps; injection and entropy do not mask. Independent ofScanResult.action.original_textis full input;sanitized_textis the analyzed slice after masking.
Example:
guard = GuardPipeline(profile="generique")
res_in = guard.scan_input(user_prompt)
response = call_llm(res_in.sanitized_text)
guard.scan_output(response)
# stricter profile (all builtins, NER, entropy):
# guard = GuardPipeline(profile="strict")
Validation tests:
tests/test_detectors/test_regex.py, test_entropy.py, test_injection.py, test_ner.py, tests/test_pipeline.py, tests/test_redaction.py. Full suite: testing.md (~362 collected; NER tests skip if the expected SpaCy model, usually fr_core_news_md, is missing).
compliance_report() / generate_report() include a sanitization section (typed tags, REDACTION_KEYS, injection / entropy not sanitizable).
Shapes intentionally not in builtins: UK sort code, US EIN bare, Spanish DNI/NIE, Italian codice fiscale, Dutch BSN, bare 9-digit US SSN, generic MRZ line, see triggers-par-profil.md; use custom_pattern with context when needed.
R26: Control of application interactions
ANSSI expectation (paraphrased):
Limit privileges and control data exchanged between the AI system and other applications.
colandix:
TopicDetector:allowed(whitelist) orblocked(blacklist); accent-insensitive comparison; auditable evidence (topic bloqué: …/ out-of-scope message).- Out of scope (
allowednon-empty, no allowed keyword): score 0.5 if detector YAML is notblock; 0.9 ifaction: block(can participate in aggregate veto). sante:perimetre_medicalblockwithallowedlist.dev:modules_critiquesblockwithblockedlist.
Note: strict has no TopicDetector. For explicit R26 conversational scope, use a domain profile (sante, dev, custom YAML). See triggers-par-profil.md.
R27: Human control of critical actions
ANSSI expectation (paraphrased):
Avoid critical automated actions without prior human validation.
colandix:
Action.HUMAN_REVIEW: per-detector YAML; after aggregation,human_reviewwhen max score ofhuman_reviewevents exceeds the threshold (0.60). NERblockbecomeshuman_reviewunlessextra.ner_allow_block: true. Legacy YAMLredactloads ashuman_reviewwithDeprecationWarning.
R29: Logging interactions
ANSSI expectation (paraphrased):
Log interactions with the generative AI system for audit and incident detection.
colandix:
ColandixLogger: logs detections with SHA-256 user pseudonymization; no raw user content (GDPR-oriented).- Scope: reflects
scan_input/scan_outputmetadata (direction, scores, truncated evidence); not plugin calls, RAG retrieval, or every filtering step. Extend in the application for full guide-style logging. See Scope R25–R35.
R31: Securing access to critical modules
ANSSI expectation (paraphrased):
Control access to critical AI system components.
colandix:
- Segmented profiles:
dev,juridique,strict,sante,rh(see README, triggers-par-profil.md,colandix/profiles/*.yaml).
Clarification: This mapping is a product reading (conversation scope, secrets, topics). It does not replace guide §5.5 code generation process controls (no auto-run of generated code, commit policy, IDE hygiene). See Scope R25–R35.
R34: Sovereign hosting and data transit
ANSSI expectation (paraphrased):
Ensure sensitive data does not transit to unmanaged third parties.
colandix:
- No network calls in orchestration; regex, scoring, and optional local SpaCy
fr_core_news_md. Alertevidencetruncated (50 chars);match_textis not logged. Sending sensitive data to a third-party LLM remains an organizational R34 decision; the library does not choose the provider.
Scope: R25–R35 (guide excerpt vs library)
The official ANSSI guide (PDF) mixes R25–R35 items: controls around the model and broader IT / organizational measures. The library colandix targets filtering and rewriting at model input and output. TLS, isolation, DDoS, generated code lifecycle, training, consumer-facing services, and collaboration-tool access reviews stay with the integrator / organization.
Summary table
| Ref | Theme (official guide) | Role |
|---|---|---|
| R25 | I/O filtering; response size limits | Core (detectors, sanitized_text, scan_output). Nuance: below. |
| R26 | Inter-app / network interaction | Partial: TopicDetector for dialog scope only; not TLS or SI-wide flow logging (R26). |
| R27 | Limit automation on untrusted input | Partial: human_review, aggregation; not email/plugins/SI cut-through. |
| R28 | Isolation; user request vs text sent to model | Isolation: out of scope. Dual trace before/after app preprocessing: not in ColandixLogger; app-side. |
| R29 | Fine-grained logging (plugins, filters, responses) | Partial: scan metadata, no raw text. No dedicated plugin/context events (R29). |
| R30 | Generated code: execution, repos, hygiene tools | Out of scope |
| R31 | Do not use AI to generate critical-module code (§5.5) | Out of scope for code-gen procedure; R31 here means profile segmentation, not IDE/CI policy. |
| R32 | Developer awareness | Out of scope |
| R33 | Consumer-facing exposure | Out of scope (auth, DDoS, front door, response validation process). |
| R34 | Sensitive data to public/third-party AI | Orchestration has no outbound calls; “do not send to ChatGPT” remains organizational (R34). |
| R35 | Review AI tool rights on business apps | Out of scope (governance). |
Integrator notes
- R25 size:
PipelineConfig.max_text_lengthtruncates the analyzed slice (default 10_000 inresult.py), not a full product-level prompt/response cap. Enforce stricter limits at API/gateway. - R25 model-internal leakage:
InjectionDetectorcovers common request-side phrasing. Model replies that leak internals without those patterns have no dedicated family; extend withcustom_patternsor human review. - R28: log raw request vs text actually sent to the LLM outside the current logger surface (see
logger.py). - R29: no structured log of plugin calls or context fetch in the library; add downstream if audit requires it.
- R26, R27, R30–R33, R35: infra, process, governance; out of library scope but not hidden from an audit.
- R31: separate colandix profile segmentation from guide §5.5 generated code measures (R31).