Baseline one AI application and alert on departures from it

applied · 85 min · Objective 2.5

Task

Apply objective 2.5's monitoring half to your own app: extend the log record with the fields an investigation needs, build a per-application baseline from ordinary traffic, write detection rules for the query and response signals the lesson lists, prove each rule fires on a planted inert event and stays quiet on the baseline users, and make the log append-only.

Steps

  1. Add the missing fields to every gateway record. Lesson 2's record already has model (the name you configured) and template (the name); add session (from an optional session field in the request body, or the record's own id when there is none), model_id (the identifier or digest your runner reports for that model), template_version (the first 12 characters of the template file's SHA-256), params (temperature and maximum tokens) and flags (a list). Send one request and confirm all of them appear.
  2. Write lab/questions.txt with 40 ordinary questions your documents answer, and lab/traffic.py, which sends them through the gateway as alice, bob and carol with short random pauses, one session per simulated visit. Produce at least 60 records. Lesson 10 allows each user five requests in ten minutes, so first give the three users their own entries under rate_limits in gateway/rules.json (such as 40 each), and remove them when the traffic has run.
  3. Write lab/monitor-baseline.json (lesson 6's baseline.json is a different record; leave it alone) from those users' records only: a users list, the median and 95th percentile of prompt_tokens, response_tokens and latency_ms, the refusal rate (responses matching phrases you list in lab/refusal-phrases.txt), and the documents each user retrieves.
  4. Write lab/detect.py LOG OUT, which reads a log and lab/monitor-baseline.json and writes one JSON alert per line with ts, user, rule and evidence. Five rules: instruction_in_query (the question refers to the assistant's instructions, rules, role or system prompt, from a term list in lab/instruction-terms.txt); canary_in_response (the canary in either form, CANARY-7Q4X-SECAI or [CANARY], in a response for a user outside HR; not the bare word CANARY, which lesson 17's CANARY-REQUEST marker also contains); restricted_retrieval (a retrieved id under restricted/ for a user outside HR); length_outlier (prompt tokens above twice the 95th percentile, or prompt characters when a request was blocked before the model counted tokens); and near_duplicate_burst (five or more prompts from one user in ten minutes, each with a difflib.SequenceMatcher ratio of 0.8 or more to the first).
  5. Plant one inert event per rule as user tester, recording each in lab/planted.csv with header rule,user,description. Three go through the live gateway: a question asking the assistant to repeat its system prompt, a question padded with the HR policy pasted three times, and six rewordings of one ordinary question in five minutes.
  6. The other two cannot happen live if lesson 5's retrieval filter works, and that is correct. Test those rules the way detection rules are unit-tested: copy the log to lab/replay.jsonl and append two synthetic tester records, one with a restricted retrieved id and one with the canary in its response.
  7. Run detect.py over both logs: logs/gateway.jsonl into lab/alerts-live.jsonl, and lab/replay.jsonl into lab/alerts-replay.jsonl. Tune the rules until every planted event fires and no alert names alice, bob or carol, and record each change you made in lab/tuning.md.
  8. Protect the log: sudo chattr +a logs/gateway.jsonl, set mode 640 with a log-reader group, and confirm that truncating it as your own user fails. Note in lab/log-protection.md how rotation now works, since only root can lift the attribute.

Verify

python lab/detect.py logs/gateway.jsonl lab/alerts-live.jsonl
python lab/detect.py lab/replay.jsonl lab/alerts-replay.jsonl
python - <<'EOF'
import csv, json
def load(f):
    return [json.loads(l) for l in open(f) if l.strip()]
live, replay = load("lab/alerts-live.jsonl"), load("lab/alerts-replay.jsonl")
planted = list(csv.DictReader(open("lab/planted.csv")))
fired = {(a["rule"], a["user"]) for a in live + replay}
missed = [p["rule"] for p in planted if (p["rule"], p["user"]) not in fired]
users = set(json.load(open("lab/monitor-baseline.json"))["users"])
false_pos = [a["rule"] for a in live if a["user"] in users]
print(len(planted), "planted;", len({p["rule"] for p in planted}), "distinct rules; missed:", missed)
print(len(false_pos), "alert(s) on baseline users", false_pos)
print("PASS" if len({p["rule"] for p in planted}) >= 5 and not missed and not false_pos else "FAIL")
EOF
lsattr logs/gateway.jsonl | awk '{print $1}' | grep -c a
( : > logs/gateway.jsonl ) 2>&1 | grep -c 'Operation not permitted'

Five distinct planted rules, none missed, zero alerts on the baseline users, and PASS; then 1 for the append-only attribute and 1 for the refused truncation. A rule that also fires on carol's ordinary salary questions has ignored the per-application baseline -- HR asking about salary bands is normal for HR.

Notes

The two replayed events are not a shortcut. When an earlier control makes a real trigger impossible, the detection rule behind it still has to be shown to work, because the day the earlier control fails is the day you need the rule. Keep detect.py: its near-duplicate burst rule is the live-log form of the templated, high-volume evidence that lesson 19's theft lab reads from a whole day of traffic.

This is an independent study companion for CompTIA SecAI+ CY0-001 and is not produced by or endorsed by CompTIA.