Student Privacy
Use student-privacy for a narrow, deterministic check around common
student-record terms and student-ID phrases. It can block a request or mark it
for redaction, and it has limited non-streaming response handling.
This policy is not a FERPA or COPPA compliance engine. It does not verify identity, parental consent, school context, jurisdiction, or the legal status of a record.
Outcome
Input evaluation
| Condition | Verdict | Reason code |
|---|---|---|
| No student marker | allow | student_privacy.clean |
Marker found, default action: redact | redact | student_privacy.redact |
Marker found with action: block | block | student_privacy.block |
Marker found with an under-13 age and age_gate: true | block | student_privacy.age_gate |
Input details contain action, age_gate, under_13, the first matching
keyword_hit if any, and student_id_like.
Output handling
Output behavior depends on action and response transport:
| Configuration | Current non-streaming behavior |
|---|---|
action: redact | The gateway's response-redaction pass replaces student-ID-like patterns in supported OpenAI JSON fields. |
action: block | The gateway blocks supported responses containing student id, transcript, ferpa, or a student ID/identifier/number phrase with student_privacy.block. |
An output block returns an HTTP 400 policy-violation response instead of the
provider body.
Prerequisites
- Add
student-privacyto the effective global or route chain. - Choose
blockwhen delivery must stop on any detected input marker. - Use
redactonly after testing the exact identifiers you expect the gateway to replace. - Combine it with
pii-detectorordlp-filterwhen broader personal or institutional data coverage is required.
Configuration
pack:
name: student-privacy-example-1
version: "1.0.0"
enabled: true
policies:
chain:
- student-privacy
policy:
student-privacy:
action: redact
age_gate: true
Supported fields
| Field | Type | Default | Notes |
|---|---|---|---|
action | string | redact | redact or block. |
age_gate | boolean | false | When enabled, a detected student marker is forced to block if the first matching inline age expression is from 1 through 12. |
Exact input detection
The evaluator joins message text and performs case-insensitive checks for:
| Detector | Values |
|---|---|
| Fixed terms | student id, student_id, transcript, iep, 504 plan, grade, gpa, disciplinary, ferpa |
| ID phrase | student id, student identifier, or student number, optional :, #, or -, then at least four letters, digits, or hyphens |
| Inline age | age N or N years old, where N has one or two digits |
The age expression does not trigger the policy by itself. It changes the action only when a fixed term or student-ID-like pattern also matched.
Examples:
| Text | Default result | With age_gate: true |
|---|---|---|
Help an age 12 learner with fractions. | allow | allow; age alone is not a marker |
Student ID: AB-1234 | redact | redact |
Student ID: AB-1234, age 12 | redact | block with student_privacy.age_gate |
Send the transcript. | redact | redact |
age_gate reads message text only. It does not consult account profiles,
headers, verified dates of birth, or consent records, and it has no separate
under-18 tier.
Redaction boundary
The input evaluator returns a redaction verdict for any fixed-term or ID match. The subsequent gateway redaction pass has one student-specific pattern: the student ID/identifier/number expression with an identifier of at least four characters.
Consequently:
Student ID: AB-1234can be replaced by the redaction pass.- A keyword-only phrase such as
send the transcriptproduces a redaction verdict but has no student-specific replacement target by itself. - The policy result does not expose redaction targets; applied replacements are reported by the gateway redaction path.
Do not interpret a redact verdict as proof that every student-related word was
removed. Verify the returned/forwarded payload.
For non-streaming output redaction, the same student-ID pattern is added to the
gateway response redactor. If it changes a supported response, the observable
gateway result uses nested reason pii.detected and final reason
redact.applied, even though the selected policy kind can be
student-privacy.
Output boundary
The output block detector is intentionally narrower than input detection. It
does not currently check iep, 504 plan, grade, gpa, or disciplinary.
Streaming behavior also differs:
action: redactenables buffered streaming redaction for matching student-ID patterns.action: blockis not evaluated against accumulated SSE output by the current streaming path.
If output blocking is required, use non-streaming requests and verify
stream: false in the client. Input blocking still occurs before either
streaming or non-streaming provider calls.
Test input behavior
Create tests/blocks-under-13-student-record.json:
{
"name": "blocks-under-13-student-record",
"input": {
"messages": [
{
"role": "user",
"content": "Student ID: AB-1234 and age 12."
}
]
},
"expected": {
"verdict": "block",
"reason_code": "student_privacy.age_gate"
}
}
Use this config while running that fixture:
policy:
student-privacy:
action: redact
age_gate: true
Then run:
kt policy lint --file policy-config.yaml
kt policy test --json
Add at least these companion cases:
- clean education content ->
allow/ok; - a student ID at age 13 ->
redact/redact.applied; - a keyword-only transcript request ->
redact/redact.applied; and - the same marker with
action: block->block/student_privacy.block.
kt policy test exercises the input evaluator. Verify response redaction,
response blocking, and streaming behavior through a running gateway.
Rollout checklist
- Inventory the actual student identifiers and record terms in your traffic.
- Decide whether a marker should block or merely enter the redaction path.
- Add positive, negative, age-gated, and substring false-positive fixtures.
- Test non-streaming responses for both redaction and block behavior.
- Test
stream: trueseparately if the application enables it. - Deploy to a limited route or gateway and correlate representative request
IDs with
kt events tail --since 10m --json. - Review false positives around broad substrings such as
gradeandgpa.
Troubleshooting
| Symptom | Likely cause | Resolution |
|---|---|---|
| An age-12 request was allowed | No student marker matched, age_gate was off, or a different earlier age expression was parsed. | Include a supported marker and test the exact message text. |
A transcript request says redact but text is unchanged | Keyword detection caused the verdict, but the student redactor only has an ID-like replacement pattern. | Use block if the whole request must stop, or add a separately supported DLP/PII control. |
Output with grade was not blocked | Output block detection uses a smaller marker set. | Test against the documented output terms or use another supported output control. |
| Streamed output was not blocked | action: block is not run against current SSE output. | Require stream: false for output blocking. |
| Policy never appears in results | It is not in the effective chain or is skipped by conditions/targeting. | Inspect the applied route chain and targeting context. |
| Team expects FERPA/COPPA certification | The policy is a deterministic text control only. | Complete the broader legal, consent, identity, retention, and operational controls outside this policy. |
Next steps
- PII Detector — cover broader personal identifiers
- DLP Filter — add organization-specific sensitive terms and patterns
- Data Policies and Data Routing — route sensitive data to appropriate providers
- Config Testing — build a regression suite around real examples
- Investigate a Blocked Request — correlate a live result with its config version