~/tutorials/057-a-baseline-scan-read-honestly.md
057: A baseline scan, read honestly
The trilogy taught you how to read one scan. Testing real targets is reading scans over time, and the instrument for that is the baseline: one scan, saved, that every later scan is measured against. This lesson builds one, changes the target in a way that matters, and reads what actually happens to the report. The receipts are more instructive than the plan, because the honest result is not the tidy one.
What this lesson is for
Change detection. When you scan the same application next month, or after their release, the question that matters is what is different, and answering it requires a saved then to compare against now. A baseline makes drift visible. Without one, every scan is a first scan, and you re-read the same sixteen findings from scratch, with no way to say the sentence a client actually needs: this is new.
What the reader meets
The lab box from the web lab page, scanned twice from the scanner seat, with the reports saved and diffed. That diff command, file against file, is the whole instrument, and this lesson's receipts are its output.
First, the discipline that makes baselines mean anything: scan an unchanged target twice. Same box, same hour, nothing touched between the runs. If a scanner were a deterministic instrument, the two reports would be identical. The receipt, from the real lab:
baseline-1 vs baseline-2, target UNCHANGED between runs
7 diff lines
same findings, different order, one row split into two
Seven lines of difference on a machine that did not change by one byte. Read the rows: the same header suggestions, arriving in a different order, one finding split across lines differently. Nothing new was found. Nothing old was lost. The scanner is a census taker whose route varies with its own timing, and identical inputs do not produce identical reports. This is not a flaw to fix. It is the first fact of reading scans over time, and the receipt that proves it is the most valuable thing in your baseline directory.
Now the experiment the lesson is named for. Between scan one and scan two, one real change was planted on the target: a config.bak.php, thirty bytes, containing a database password, dropped into the web root, and a debug.txt beside it. Both answer 200. This is the class of change baselines exist for, a credential-bearing backup file, the exact artifact a re-scan should catch screaming. The receipt:
baseline vs after, target CHANGED by the planted files
baseline: 16 rows after: 16 rows
new findings: none
config.bak.php answers 200, unflagged
debug.txt answers 200, unflagged
The row count did not move. The diff shows the same seven lines of order noise and nothing else. The planted files are live, readable by anyone who guesses their names, and the scanner walked past them, because config.bak.php is not in this scanner's checklist database under that name, and the 055 lesson's law holds at the diff layer too: the scanner asks what it knows how to ask. A file it never asks about cannot appear in any report, no matter how many baselines you keep.
Read that pair of receipts together, because together they are the lesson: the diff caught the noise and missed the signal. The unchanged target produced seven lines of apparent change. The changed target produced none. A naive read of scan diffs, more lines means something happened, is exactly backwards here, and would have been exactly backwards in the field: you would spend your morning investigating the seven noise lines, file no report on the credential file, and be confidently wrong in both directions at once.
The instrument
The honest baseline method, five steps, none optional.
Save the raw output, every scan, timestamped, forever. nikto -h TARGET -o base-2026-09.txt. The filename carries the date. The directory holding these is your measurement instrument, and deleting a baseline to tidy up is deleting last month's evidence.
Normalize before diffing. The receipt's seven noise lines came from row order, and the fix is to sort both files before comparing: diff <(sort base.txt) <(sort after.txt). Sorted, the unchanged-target diff collapses toward zero and the changed-target diff becomes readable. The noise does not fully vanish, the split-line row still splits, but the method is to spend the machine's patience, sorting, on the machine's noise before you spend yours on it.
Know your scanner's coverage, because the baseline can only detect what the scanner can see. The planted file receipt is the proof: the baseline was perfect, the diff was perfect, and both were perfect about a checklist that never asks for config.bak.php. The 052 lesson's fuzzer is the complement here, asking thousands of names the checklist never learned, and a serious change-detection posture pairs them: the scanner's diff for what it knows, a content-diff of key pages for what changed where, and a fuzzer pass for what is new that nobody has a name for yet.
Distinguish three diff outputs. New finding: a row in after that was not in base, the thing baselines exist to catch. Lost finding: a row that vanished, which means either fixed or the scanner's route varied, and the first possibility is worth a celebration and the second a sigh, and telling them apart is a re-run, not a guess. Changed finding: same identifier, different content, the version bump case, and the 056 banner card governs it before you act on it.
And the honest verdict on the whole instrument: a baseline is a commitment device, not a detector. It does not find things. It forces your future self to compare instead of re-reading, and it makes the comparison cheap. The finding still comes from the scanner's questions, the fuzzer's guesses, or your eyes, and the receipt above is the proof of the boundary: the best baseline in the world, over the most honest diff, reports only what was asked.
Try it
- Build the baseline pair on your own lab: two scans, unchanged target, sorted diff, and count your scanner's noise floor in lines. That number, once measured, changes how you read every future diff, because you now know how many lines of difference mean nothing.
- Plant your own change, a backup file with a fake credential, and re-scan. If your scanner's checklist catches it, note the row it arrives as. If it does not, and the receipt says it may well not, you have now personally receipted the coverage boundary, and the fuzzer lesson's purpose will already make sense to you.
- The third scan. After planting and re-scanning, delete the planted file and scan once more. The diff should return to the baseline. When it does not fully return, and the noise floor says it will not fully return, you have met the last honest fact of the instrument: baselines drift slightly even on targets that do not, and the professional read is calibrated by the noise receipt from step one, every time.
Reveal the answer
The baseline directory card, the whole instrument in one place, receipts wired in:
# the directory that is the instrument
mkdir -p scans/TARGET && cd scans/TARGET
# scan one, raw, dated, saved forever
nikto -h http://TARGET/ -nointeractive -o base-20260925.txt
# the noise receipt, before any change exists
nikto -h http://TARGET/ -nointeractive -o base-20260925b.txt
diff <(sort base-20260925.txt) <(sort base-20260925b.txt) | wc -l
# YOUR scanner's noise floor, in lines. Remember this number.
# after any change, the honest compare
nikto -h http://TARGET/ -nointeractive -o after-20261001.txt
diff <(sort base-20260925.txt) <(sort after-20261001.txt)
# reading the diff, three verdicts:
# NEW row -> what baselines exist for. Verify with 056's banner card.
# LOST row -> fixed, or route noise. Re-run to tell them apart.
# CHANGED row -> version bump. 056's card before any severity call.
# under the noise floor -> not a signal, whatever it feels like
The card's last line is the quiet one, and it is the whole lesson compressed: your noise floor, measured once on an unchanged target, is the threshold under which diff lines are not findings. The seven-line receipt was not a failure of the method. It was the method, working, telling you its own resolution limit before you needed to know it. An instrument that tells you its resolution honestly is an instrument you can trust inside that resolution, and a scanner plus a saved baseline plus a measured noise floor is exactly that, all the way down to the line where it stops meaning anything, printed by you, in advance.
What this lesson does not claim
The baseline sees the scanner's world, and the scanner's world has edges this trilogy has now receipted three times: unasked files, unmeant meanings, and now unreported changes to the unasked. What the method adds is not coverage. It is memory, and memory plus known coverage beats amnesia at any coverage. The changes that matter most to a client, the new credential file before the release, the debug page that shipped by accident, are often exactly the ones outside the checklist, and catching those is the fuzzer's question, the differ's question, and yours. The baseline is the spine they hang from, and the next lessons hand you the fuzzing hand and the template hand, one rung at a time.