Baseline one AI application and alert on departures from it
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
- Add the missing fields to every gateway record. Lesson 2's record already has
model(the name you configured) andtemplate(the name); addsession(from an optionalsessionfield in the request body, or the record's ownidwhen 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) andflags(a list). Send one request and confirm all of them appear. - Write
lab/questions.txtwith 40 ordinary questions your documents answer, andlab/traffic.py, which sends them through the gateway as alice, bob and carol with short random pauses, onesessionper 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 underrate_limitsingateway/rules.json(such as 40 each), and remove them when the traffic has run. - Write
lab/monitor-baseline.json(lesson 6'sbaseline.jsonis a different record; leave it alone) from those users' records only: auserslist, the median and 95th percentile ofprompt_tokens,response_tokensandlatency_ms, the refusal rate (responses matching phrases you list inlab/refusal-phrases.txt), and the documents each user retrieves. - Write
lab/detect.py LOG OUT, which reads a log andlab/monitor-baseline.jsonand writes one JSON alert per line withts,user,ruleandevidence. Five rules:instruction_in_query(the question refers to the assistant's instructions, rules, role or system prompt, from a term list inlab/instruction-terms.txt);canary_in_response(the canary in either form,CANARY-7Q4X-SECAIor[CANARY], in a response for a user outside HR; not the bare word CANARY, which lesson 17'sCANARY-REQUESTmarker 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); andnear_duplicate_burst(five or more prompts from one user in ten minutes, each with adifflib.SequenceMatcherratio of 0.8 or more to the first). - Plant one inert event per rule as user
tester, recording each inlab/planted.csvwith headerrule,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. - 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.jsonland append two synthetictesterrecords, one with a restricted retrieved id and one with the canary in its response. - Run
detect.pyover both logs:logs/gateway.jsonlintolab/alerts-live.jsonl, andlab/replay.jsonlintolab/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 inlab/tuning.md. - 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 inlab/log-protection.mdhow 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.