Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: CI

on:
push:
branches: ["main", "master"]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.10", "3.11"]
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Lint
run: ruff check .
- name: Type check
run: mypy .
- name: Run tests
run: pytest
61 changes: 61 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: build-and-release
on:
push:
tags: ["v*.*.*"]

jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install deps
run: |
python -m pip install -U pip
pip install -r requirements.txt
pip install -r requirements-optional.txt || true
pip install pyinstaller PySimpleGUI
pip install ruff mypy pytest
- name: Lint & Typecheck
run: |
ruff check .
mypy .
- name: Test
run: pytest -q
- name: Build PyInstaller
shell: bash
run: |
if [[ "$RUNNER_OS" == "Windows" ]]; then
python -m PyInstaller --noconfirm --clean --name TeacherSafeScanner --onefile --windowed --add-data "scanner/reporting/html_theme.css;scanner/reporting" scanner/gui.py
7z a TeacherSafeScanner-windows.zip dist/TeacherSafeScanner.exe
elif [[ "$RUNNER_OS" == "macOS" ]]; then
python -m PyInstaller --noconfirm --clean --name TeacherSafeScanner --onefile --windowed --add-data "scanner/reporting/html_theme.css:scanner/reporting" scanner/gui.py
ditto -c -k --sequesterRsrc --keepParent dist/TeacherSafeScanner dist/TeacherSafeScanner-macos.zip || \
(cd dist && zip -r ../TeacherSafeScanner-macos.zip TeacherSafeScanner)
else
python -m PyInstaller --noconfirm --clean --name TeacherSafeScanner --onefile --windowed --add-data "scanner/reporting/html_theme.css:scanner/reporting" scanner/gui.py
(cd dist && tar -czf ../TeacherSafeScanner-linux.tar.gz TeacherSafeScanner)
fi
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: TeacherSafeScanner-${{ matrix.os }}
path: |
TeacherSafeScanner-*.zip
TeacherSafeScanner-*.tar.gz
release:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
with:
path: ./artifacts
- name: Create Release
uses: softprops/action-gh-release@v2
with:
files: artifacts/**/*
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Generated demo assets
/docs/demo.gif
/docs/demo-placeholder.gif
14 changes: 14 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.9
hooks:
- id: ruff
args: ["--fix"]
- repo: https://github.com/psf/black
rev: 24.8.0
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.11.2
hooks:
- id: mypy
20 changes: 20 additions & 0 deletions BEGINNERS_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ This command creates three safe files inside `examples/benign_samples/`:
## 7. Run your first scan

```bash
python -m scanner scan examples/benign_samples
python -m scanner.main scan examples/benign_samples
```

Expand All @@ -86,6 +87,7 @@ python -m scanner.main scan examples/benign_samples
1. **Do not open the file.**
2. Move it away from your main folders using:
```bash
python -m scanner quarantine PATH_TO_FILE --dest quarantine
python -m scanner.main quarantine PATH_TO_FILE --dest quarantine
```
3. Share the JSON or HTML report with your school IT team.
Expand All @@ -105,4 +107,22 @@ python -m scanner.main scan examples/benign_samples
- Read [SAFETY.md](SAFETY.md) for more safety advice.
- If you are stuck, ask a colleague or your IT support team for help. Share any error messages exactly as they appear.

## Windows (PowerShell)

```powershell
py -3 -m venv .venv
. .venv\Scripts\Activate.ps1
pip install -r requirements.txt
python -m scanner.gui
```

## macOS

```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python -m scanner.gui
```

Stay safe and never execute files that you do not fully trust.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

> To add your own walkthrough, drop a GIF at `docs/demo.gif` and update this link.

Teacher-Safe Local File Scanner is a Python-based, offline-friendly toolkit that helps educators quickly triage student-submitted files before opening them. It performs static checks only—no execution of untrusted code—and produces human-readable and machine-readable reports.
Teacher-Safe Local File Scanner is a Python-based, offline-friendly toolkit that helps educators quickly triage student-submitted files before opening them. It performs static checks only, no execution of untrusted code and produces human readable and machine/readable reports.

## Table of contents
Expand Down Expand Up @@ -48,6 +49,7 @@ python examples/generate_benign_samples.py # Materialise demo files
### Scan files or folders

```bash
python -m scanner scan ./examples/benign_samples --max-file-size 5000000 --threads 4
python -m scanner.main scan ./examples/benign_samples --max-file-size 5000000 --threads 4
```

Expand All @@ -59,19 +61,23 @@ python -m scanner.main scan ./examples/benign_samples --max-file-size 5000000 --
### Watch a directory (polling, non-blocking)

```bash
python -m scanner scan --watch ./incoming
python -m scanner.main scan --watch ./incoming
```

### Produce reports

```bash
python -m scanner scan submissions --report-json scan_report.json --report-html scan_report.html
python -m scanner report scan_report.json --html --output scan_report.html
python -m scanner.main scan submissions --output scan_report.json
python -m scanner.main report scan_report.json --html --output scan_report.html
```

### Quarantine a file

```bash
python -m scanner quarantine ./submissions/suspicious.docx --dest ./quarantine
python -m scanner.main quarantine ./submissions/suspicious.docx --dest ./quarantine
```

Expand All @@ -87,6 +93,20 @@ python examples/generate_benign_samples.py

The script recreates a harmless text file, a minimal PNG image, and a macro-free `.docx` document without storing binary fixtures in the repository.

## One-click binaries

Grab the latest release assets for Windows, macOS, or Linux to run the scanner without Python. Each bundle ships offline-first and collects no telemetry.

### Windows context menu

1. Copy `TeacherSafeScanner.exe` to `C:\Program Files\TeacherSafe\`.
2. Double-click `scripts/windows_add_context_menu.reg` to register a **Scan with Teacher-Safe** right-click option.

### GUI launcher

- On Python: run `python -m scanner.gui` and use the picker to select files or folders, then press **Scan** and **Open Report**.
- On packaged builds: launch `TeacherSafeScanner` from the extracted bundle and follow the same steps to save and open the HTML report.

## How scanning works

The scanner combines lightweight type identification, static detectors, and heuristic scoring:
Expand All @@ -95,6 +115,7 @@ The scanner combines lightweight type identification, static detectors, and heur
| --- | --- | --- |
| Discovery | Files are walked recursively (respecting `--max-file-size`) and hashed using streaming reads. | [`scanner.utils`](scanner/utils.py) |
| Type sniffing | If `python-magic` is enabled, MIME detection is delegated; otherwise magic bytes are inspected. | [`scanner.scanner_core`](scanner/scanner_core.py) |
| Detection | Format-specific rules look for risky markers (e.g., macros, embedded executables, appended payloads). | [`scanner.detectors`](scanner/detectors/__init__.py) |
| Detection | Format-specific rules look for risky markers (e.g., macros, embedded executables, appended payloads). | [`scanner.detectors`](scanner/detectors.py) |
| Scoring | Each finding contributes a weighted score mapped to Safe/Caution/Suspicious/High labels. | [`scanner.heuristics`](scanner/heuristics.py) |
| Reporting | Results are aggregated into JSON, console, or HTML outputs. | [`scanner.reporters`](scanner/reporters.py) |
Expand All @@ -103,6 +124,7 @@ The entire pipeline avoids running untrusted content and is safe to execute on o

## Command reference

The CLI exposes three subcommands and several shared options:
The CLI exposes three subcommands and several shared options.

### `scan`
Expand All @@ -116,6 +138,8 @@ python -m scanner.main scan <path> [--output report.json] [--threads 8] [--max-f
Useful flags:

- `--watch <folder>`: poll for new files while continuing to monitor previously scanned ones.
- `--report-json` / `--report-html`: save structured and teacher-friendly reports in one run.
- `--pdf-rules`, `--office-rules`, `--zip-rules`, `--image-rules`: choose `off`, `normal`, or `strict` for per-format heuristics.
- `--use-magic` / `--use-yara`: opt into external libraries when installed.
- `--max-file-size`: skip overly large submissions to save time.
- `--threads`: increase if you have many CPU cores and fast storage.
Expand Down Expand Up @@ -157,6 +181,7 @@ The CLI flags are opt-in, and the scanner gracefully degrades when the libraries

## Workflow guidance for flagged files

1. **Do not open the file.** Treat warnings as serious until reviewed by IT.
1. Do not open the file. Treat warnings as serious until reviewed by IT.
2. Move the file to the quarantine folder for record keeping.
3. Escalate to your IT or security team with the JSON/HTML report.
Expand Down
2 changes: 2 additions & 0 deletions examples/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
benign_samples/sample_image.png
benign_samples/sample_docx.docx
Empty file added examples/__init__.py
Empty file.
5 changes: 5 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[mypy]
python_version = 3.11
ignore_missing_imports = True
warn_return_any = True
warn_unused_ignores = True
8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "teacher-safe-local-file-scanner"
version = "0.1.0"
description = "Offline-first local file scanner for teachers"
requires-python = ">=3.10"
requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"

Expand Down
3 changes: 3 additions & 0 deletions requirements-optional.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
python-magic==0.4.27
yara-python==4.5.1
watchdog==5.0.2
python-magic
yara-python
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
PySimpleGUI==4.60.5
pytest==8.3.3
ruff==0.6.9
mypy==1.11.2
pytest
ruff
2 changes: 2 additions & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
line-length = 100
target-version = "py311"
1 change: 1 addition & 0 deletions scanner/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
__all__ = ["__version__"]
"""Teacher-Safe Local File Scanner package."""
from __future__ import annotations

Expand Down
6 changes: 6 additions & 0 deletions scanner/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from .main import main

if __name__ == "__main__": # pragma: no cover - package entry point
import sys

sys.exit(main())
Loading
Loading