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
28 changes: 25 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:

# Create logs directory with absolute path
mkdir -p "$GITHUB_WORKSPACE/logs"
chmod 777 "$GITHUB_WORKSPACE/logs"
chmod 755 "$GITHUB_WORKSPACE/logs"

# Generate log file name
TIMESTAMP=$(date +"%Y%m%dT%H%M")
Expand All @@ -44,7 +44,7 @@ jobs:

# Create and verify log file
touch "${LOG_FILE}"
chmod 666 "${LOG_FILE}"
chmod 644 "${LOG_FILE}"

# Write initial content
echo "Starting CI run at $(date)" | tee -a "${LOG_FILE}"
Expand Down Expand Up @@ -89,7 +89,10 @@ jobs:
conda install -y -c conda-forge pytest pytest-cov pytest-xdist pluggy coverage execnet 2>&1 | tee -a "${LOG_FILE}"
echo "" | tee -a "${LOG_FILE}"
echo "Installing ruff and mypy..." | tee -a "${LOG_FILE}"
pip install ruff==0.8.4 mypy 2>&1 | tee -a "${LOG_FILE}"
# mypy pinned to match .pre-commit-config.yaml so CI agrees with local
# hooks. Must be >= the dev floor (mypy>=1.15.0); older versions report
# false positives the codebase is not written against.
pip install ruff==0.8.4 mypy==2.1.0 2>&1 | tee -a "${LOG_FILE}"
echo "" | tee -a "${LOG_FILE}"
echo "Installing mpasdiag..." | tee -a "${LOG_FILE}"
pip install -e . 2>&1 | tee -a "${LOG_FILE}"
Expand Down Expand Up @@ -244,3 +247,22 @@ jobs:
- name: Lint with ruff
run: |
ruff check mpasdiag/ tests/

security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0

- name: Set up Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: '3.11'

- name: Install security tooling
run: pip install bandit==1.8.0 pip-audit==2.7.3

- name: Static analysis with bandit
run: bandit -r mpasdiag/ -ll

- name: Audit dependencies for known vulnerabilities
run: pip-audit --progress-spinner=off || true
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,9 @@ examples/benchmark_results_*.csv

# Misc
*.pyc

# Internal security audit documents (DO NOT COMMIT / PUBLISH)
SECURITY_AUDIT.md
SECURITY_AUDIT_*.md
security_audit*.md
scripts/
10 changes: 9 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,16 @@ repos:
files: ^(mpasdiag|tests)/

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
rev: v2.1.0
hooks:
- id: mypy
files: ^mpasdiag/
additional_dependencies: ['types-PyYAML']

# Static security analysis (security audit finding MPAS-004).
- repo: https://github.com/PyCQA/bandit
rev: 1.8.0
hooks:
- id: bandit
args: ['-ll']
files: ^mpasdiag/
5 changes: 5 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,18 @@ also enforced in CI, so please run them before pushing:
ruff check mpasdiag/ tests/ # linting
black --check mpasdiag/ tests/ # formatting (use `black mpasdiag/ tests/` to apply)
mypy mpasdiag/ # static type checking
bandit -r mpasdiag/ -ll # static security analysis
```

Guidelines:

- Add type annotations to all new functions (mypy runs in strict-ish mode).
- Write clear docstrings (NumPy style) for public functions and classes.
- Keep changes consistent with the surrounding code.
- **Never use `assert` for input validation or any security/correctness check.**
Python strips `assert` statements when run under `python -O`, so a validation
written as an assertion silently disappears. Use an explicit `raise` (e.g.
`ValueError`) instead. Reserve `assert` for internal invariants only.

## Testing

Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ prune tests
prune examples
prune data
prune output
prune scripts
prune .github

# Exclude caches and compiled files
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1019,4 +1019,4 @@ If you use this package in your research, please cite:
---

**Version**: 1.0.0
**Last Updated**: June 06, 2026
**Last Updated**: June 28, 2026
44 changes: 44 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,50 @@ Security updates are provided for the most recent release line of MPASdiag.
| 1.0.x | :white_check_mark: |
| < 1.0 | :x: |

## Security considerations for users

MPASdiag is a **local command-line tool and Python library**. It runs with the
privileges of the user who invokes it and is not a network service. Please keep
the following trust boundaries in mind:

### Only process data you trust

MPASdiag reads NetCDF/HDF5 model output, grid files, and pre-computed remapping
weight caches. Parsing of these binary files is delegated to the underlying
`netCDF4`, `h5netcdf`/`HDF5`, and `xarray` libraries. As with any scientific data
tool, **opening a maliciously crafted or corrupt file can crash the process or,
through a vulnerability in those underlying libraries, be unsafe.** Only process
files obtained from sources you trust, and keep your scientific stack updated.

As a defense-in-depth measure, MPASdiag rejects inputs whose declared dimensions
exceed generous safety limits before allocating large arrays, to avoid
out-of-memory crashes on malformed files. If you legitimately work with very
large grids, you can raise these limits via environment variables:
`MPASDIAG_MAX_SOURCE_CELLS`, `MPASDIAG_MAX_TARGET_POINTS`,
`MPASDIAG_MAX_WEIGHTS_NNZ`, and `MPASDIAG_MAX_NUM_POINTS`.

The remapping **weights cache directory** (`weights_dir`) is treated as trusted
input: only point it at a location you control. A tampered cache file is
validated for internal consistency before use, but should not be shared across
trust boundaries.

### Output, log, and config paths

Output directories, log files (`--log-file`), configuration files (`--config`),
and weights paths are taken from the operator and are honored as given — this is
intended behavior for a local tool that writes to your own filesystem.
Configuration paths are confined to the working directory (or an explicit
`base_dir`) and must be `.yaml`/`.yml` files; this guards against accidental
path traversal, not against a user who deliberately targets their own files.

### Reproducible installation

Lower bounds in `pyproject.toml`/`requirements.txt` are set above known-vulnerable
releases, but `pip` will otherwise resolve to the latest compatible versions. For
a fully reproducible environment, install into the provided conda `environment.yml`,
or generate a pinned constraints file from a known-good environment
(`pip freeze > constraints.txt`) and install with `pip install mpasdiag -c constraints.txt`.

## Reporting a vulnerability

We take the security of MPASdiag seriously. If you discover a security
Expand Down
Loading
Loading