Jeremy BryantCybersecurity portfolio

Offline security exercise / source reading

Offline file integrity and change triage

Read the supplied instructions here, then download the complete project to use the Python utilities and training materials. These are guided practice materials; reference checks do not assess independent understanding.

Download complete source ZIP

Archive folder: jeremy-bryant-pages/labs/file-integrity/

Lab instructions

labs/file-integrity/README.md

# Offline file integrity and change triage

A fictional community workshop has approved a notice update. Other files may
also have changed. Your task is to describe the evidence, separate explained
changes from unexplained ones, and judge whether the baseline deserves confidence.

This is original guided practice for a beginner. It uses generated dummy text
files, runs entirely offline, and needs only Python 3.10 or later. There are no
accounts, live systems, executable payloads, or dependencies to install. You use
commands and explain results; you do not need to write a program.

Independent completion and understanding have not been assessed. The utility,
fixtures, and supplied answer key are learning scaffolds, not a claim of
professional experience or independent learner authorship.

## The security idea

A SHA-256 hash is a compact value calculated from file bytes. Comparing it with
a previously recorded value helps detect a byte change. A changed hash does not
explain the change: an approved edit and an unauthorised edit both alter bytes.
Even a line-ending conversion can alter a hash.

This lab combines three pieces of evidence:

1. A **baseline** records file names, byte lengths, and hashes before the change
   window, if that history is supported.
2. A **current inventory** records what is present now.
3. The supplied **change list** records approved paths and exact before/after
   hashes. A path or ticket number alone does not approve arbitrary new contents.

Hash agreement does not prove that a file is safe, authentic, or unchanged since
some earlier time that was never measured. An attacker could replace a file and
an unprotected baseline together. This lab's scenario includes a fictional
reference anchor for most variants; it is editable local training evidence,
not a digital signature or an independent trust service.

## Command contract

Run these commands from this folder. `lab.py` is the single maintained entry
point. The default `--run` is `run/` beside the script; explicit paths are
relative to your current working directory. The parent of a new run must exist.

| Command | Input and result | Exit codes |
| --- | --- | --- |
| `python lab.py generate` | Creates a new sandbox with `reference/`, `current/`, scenario, approvals, and answer key. Defaults to `mixed`, seed `23`. Refuses an existing run folder, even an empty one. | `0` created; `2` invalid input or existing folder |
| `python lab.py baseline` | Scans `run/reference/` and creates `run/baseline.json`. Does not modify the reference or current files. Refuses an existing output. | `0` created; `2` invalid input or existing output |
| `python lab.py check` | Scans `run/current/`, compares it with the baseline, and applies the supplied exact change approvals. Prints a report without repairing anything. | `0` no unexplained changes and exercise anchor matches; `1` review needed; `2` invalid or unreadable input |
| `python lab.py manifest --verify` | Checks the shipped source and fixture hashes against `PROVENANCE.json`. | `0` match; `1` difference; `2` invalid or unreadable input |

All commands accept `--json` for structured output. Input errors go to standard
error. Use `python lab.py COMMAND --help` for options.

`generate` accepts `--variant approved`, `mixed`, `replacement`, or
`suspect-baseline`, plus `--seed 0` through `999999`. `baseline` accepts
`--source reference` or `--source current` and `--name NAME.json`. `check`
accepts `--baseline NAME.json`. Baseline names must be a single lowercase JSON
file name inside the chosen run; they cannot point outside it.

```console
python lab.py generate
python lab.py baseline
python lab.py check
```

The default check exits `1` deliberately: three changes need investigation.
Read [the worked example](WORKED-EXAMPLE.md), then use [the drills](DRILLS.md)
with unfamiliar inputs. A check result is an evidence classification, not a
learner grade. Explain your reasoning before opening `expected-findings.json`.

## How the report decides

- **Unchanged:** the name, hash, and byte length match the supplied baseline.
- **Approved:** a changed, added, or deleted path matches both hashes in its
  change-list entry. A missing side is recorded as `null` for an addition or
  deletion.
- **Unexpected:** the observed change lacks that exact approval. It needs
  investigation; the output does not call it malicious.
- **Unused approval:** a listed change was not observed exactly. This can mean
  work has not happened yet, the wrong baseline was used, or contents differ
  from the approved version. It is context, not automatically an incident.

The checker independently reports whether the baseline content matches the
scenario's reference anchor. It canonicalises JSON before hashing, so indentation
changes to the inventory do not create an anchor mismatch. Hashes of the actual
dummy files always cover their exact raw bytes. A matching anchor supports only
the supplied exercise history. An absent or mismatched anchor produces exit `1`,
even when no unexpected file differences appear.

| Variant | What changes in the conclusion |
| --- | --- |
| `approved` | The notice edit matches approval; no unexplained changes. |
| `mixed` | The notice is approved; a config edit, an addition, and a deletion are unexplained. |
| `replacement` | The notice and the exact report replacement are approved; additions and deletions are not inherently suspicious. |
| `suspect-baseline` | A prior config change is already in both trees. The comparison sees no config difference, but the baseline has no independent anchor and conflicts with a written requirement. |

A seed changes synthetic labels, counts, ticket IDs, and their hashes. It does
not change the scenario's security reasoning; select another variant for that.

## Files, safety, and limits

`reference/` is a supplied exercise snapshot, and `current/` is the observed dummy
tree. Preserve both while writing your first findings. Only fresh run creation
and new baseline files are written by normal commands. The checker does not
execute files, contact services, delete, quarantine, or restore anything.

The utility is deliberately limited to generated sandboxes with a valid scenario
file and the named `reference/` and `current/` children. It scans actual directory
entries rather than opening paths from a baseline. It rejects path traversal,
absolute baseline entries, Windows device names, uppercase or unsupported file
names, symbolic links, reparse points (including junctions), hard-linked files,
and nonregular files. Bounds are 100 files, 100 directories, eight directory
levels, 1 MB per file, and 10 MB of file content. An empty current tree can be
reported as deletions; an empty baseline is rejected.

This is a small static-tree exercise, not a race-resistant forensic acquisition
tool. Do not change files while a command is running. Permission errors or
unsupported inputs stop the check instead of producing a reassuring partial
report. Metadata such as owner, permissions, timestamps, alternate data streams,
and extended attributes is outside its model. Renames appear as addition and
deletion. The lab cannot determine actor, intent, execution, compromise, or
whether every required file was included in an originally trusted baseline.

Generated files use deterministic UTF-8/LF bytes, and the local `.gitattributes`
preserves LF for shipped files. `PROVENANCE.json` records original synthetic
provenance and hashes of the utility, documentation, tests, and worked fixture.
It does not authenticate its own contents or establish educational acceptance.
`python lab.py manifest --write` creates a missing provenance inventory for a
reviewed release; it refuses to overwrite an existing one. Updating a release
requires deliberate review and replacement of that inventory by a maintainer.

```console
python -m unittest discover -s . -p test_lab.py -v
python lab.py manifest --verify
python lab.py check --run fixtures/worked-example
```

The shipped fixture check exits `1` with the expected mixed-case findings.
The tests cover changed conclusions, exact approvals, poor baselines, removal
of every current file, deterministic bytes, malformed inputs, output protection,
and link/path restrictions. Link creation tests report a skip if the operating
system denies creation; a separate reparse-attribute test always checks rejection.

Worked example

labs/file-integrity/WORKED-EXAMPLE.md

# Worked example: a notice update and three unexplained changes

This supplied walkthrough uses the `mixed` variant and seed `23`. It is not a
record of independent learner work. All names and file contents are fictional.

## 1. Establish the scope

```console
python lab.py generate --variant mixed --seed 23 --run run-walkthrough
```

Read `run-walkthrough/scenario.json` and `run-walkthrough/approvals.json` in a
text editor. The supplied exercise history says that `reference/` was checked
before the change window. A fictional owner approved the notice's opening time
changing from `09:00` to `10:00`. The approval records the exact before/after
file hashes. No approval is supplied for the config, temporary note, or archive.

The scenario and approvals are the exercise's authority. In real work, their
source, custody, and protection would also need verification.

## 2. Create the measurement

```console
python lab.py baseline --run run-walkthrough
```

The baseline contains four file records. `docs/notice.txt` is 39 bytes before
and after its edit: a size-only check would miss that change. SHA-256 compares
the bytes, so equal length does not prevent detection.

Creating a baseline is only measurement. Confidence comes from the history and
protection of its source. Do not create a new baseline from current files merely
to remove findings.

## 3. Interpret the comparison

```console
python lab.py check --run run-walkthrough
python lab.py check --run run-walkthrough --json
```

The result is `REVIEW NEEDED`, exit code `1`. Its baseline status is
`matches-exercise-anchor`.

| Path | Observation | Assessment and next action |
| --- | --- | --- |
| `docs/notice.txt` | Modified; same byte length, different hash | Exact hashes match `CHANGE-000023`; record as the approved update. |
| `docs/contact.txt` | Unchanged | No difference from this baseline; make no broader security claim. |
| `config/service.txt` | Modified: `remote_access=disabled` became `enabled` | Unexpected. Preserve evidence and ask the fictional owner whether a change record is missing. This text does not enable a real service. |
| `docs/temporary-note.txt` | Added | Unexpected. Establish its source and purpose before deciding what action is appropriate. |
| `reports/archive.txt` | Deleted | Unexpected. Confirm the retention requirement and whether removal was authorised; do not restore automatically. |

There is **one unchanged file, one approved change, and three unexpected
changes** across five observed paths. The count includes the missing baseline
file and the new current file. The current folder itself contains four files.

A suitable short finding is:

> The supplied baseline matches the exercise anchor. The notice update matches
> its exact approval. The config edit, new temporary note, and missing archive
> have no matching change record. I would preserve these observations and check
> the owner's records before deciding on recovery or escalation. Hashes establish
> differences from this baseline; they do not identify the actor or prove an attack.

## 4. Test a misleading alternative

```console
python lab.py baseline --run run-walkthrough --source current --name baseline-current.json
python lab.py check --run run-walkthrough --baseline baseline-current.json
```

This comparison shows four unchanged current files and no unexpected differences.
However, its baseline status becomes `does-not-match-exercise-anchor`, and it
still exits `1`. The new measurement has absorbed the changes, so it cannot
establish the earlier state. Its unused approval also shows that the expected
notice transition was not observed in this comparison.

The separate `suspect-baseline` variant is harder: no independent anchor exists.
The written requirement and the source history must be considered even when
the file comparison itself looks uneventful. Continue with [the drills](DRILLS.md).

Practice drills

labs/file-integrity/DRILLS.md

# Practice: make and explain your own finding

Use a fresh folder for each attempt. These are optional practice stages, not
recorded passes or evidence of professional work. Before opening the supplied
`expected-findings.json`, write a short note with: observation, approval match,
baseline confidence, uncertainty, and next action.

## Guided: a routine approved change

```console
python lab.py generate --variant approved --seed 41 --run run-approved
python lab.py baseline --run run-approved
python lab.py check --run run-approved
```

Explain why a hash difference alone is not an incident. Identify the exact
evidence that permits the notice update. What would happen if the notice
contained `11:00` instead of the approved `10:00`? The same path and ticket
would no longer explain those different bytes.

Expected self-check: one approved edit, no unexpected differences, and a
matching exercise anchor; exit `0`. That result says nothing about your
understanding until you can explain the evidence and limits.

## Hints: an addition and a deletion

```console
python lab.py generate --variant replacement --seed 71 --run run-replacement
python lab.py baseline --run run-replacement
python lab.py check --run run-replacement
```

Read the change list before predicting the result. Do additions always need an
alert? Does a deletion always mean lost data? Explain how `null` represents the
missing side of each approved transition. Why would an exact byte check be more
useful than trusting the replacement report's name?

Expected self-check: the notice, archive deletion, and exact replacement report
are approved; no unexplained changes; exit `0`. This is a different conclusion
from the mixed case, even though both contain an addition and deletion.

## Independent: apparently unchanged, but uncertain

```console
python lab.py generate --variant suspect-baseline --seed 83 --run run-suspect
python lab.py baseline --run run-suspect
python lab.py check --run run-suspect
```

Keep the worked answer closed until you have a finding. Read the scenario's
baseline history and inspect `reference/config/service.txt` and
`current/config/service.txt` with a text editor.

- Does the baseline describe a verified earlier state?
- What does the written requirement say about remote access?
- Can matching hashes resolve the conflict with that requirement?
- What independent evidence would you request before calling the state clean?
- Why could simply accepting a new baseline conceal a problem?

Expected self-check: the notice is approved and the config is reported unchanged,
but the baseline has no anchor; exit `1`. Both copies already contain `enabled`,
which conflicts with the written `disabled` requirement. The comparison alone
cannot reveal when that change happened or who made it.

## Teach it back

Use a fresh mixed case with another seed. Explain one approved change, one
unexpected change, and the baseline's limits without reading the answer key.
Describe a plausible benign explanation for an unexpected change, the evidence
needed to test it, and why you would preserve the observation before repair.
Do not infer an attack from a hash mismatch alone.

An honest portfolio note could say: "I practised comparing synthetic file
inventories with an approved change list using a supplied offline utility."
Add your own specific reasoning and difficulties only after doing the exercise.
Do not claim the supplied utility or example findings as independent work.

Browse all 30 lab source pages