Skip to content
Open
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
22 changes: 22 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Dependabot security updates were already on for this repo, but without this
# config there were no scheduled version-update PRs — so a dependency could sit
# on a vulnerable-but-unadvised release indefinitely. This adds weekly checks
# for both the Python deps and the GitHub Actions used by CI.
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
labels: ["dependencies"]
groups:
# One PR for routine bumps; security fixes still arrive individually.
python-minor-and-patch:
update-types: ["minor", "patch"]

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
labels: ["dependencies", "ci"]
19 changes: 18 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
python: ["3.10", "3.13"]
python: ["3.10", "3.13"] # matches requires-python floor
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
Expand All @@ -20,9 +20,26 @@ jobs:
python-version: ${{ matrix.python }}
- run: pip install -e '.[dev]'
- run: pytest
- name: Lint
run: ruff check abapit tests
- name: Security lint
run: bandit -q -r abapit -ll
- name: Demo smoke test
run: |
abapit export devices --demo | head -1 | grep serialNumber
abapit changes --demo 2>&1 | grep -q "Need at least two snapshots"
env:
ABAPIT_DATA_DIR: ${{ runner.temp }}/abapit-data

audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-python@v6
with:
python-version: "3.12"
# Fails the build on a known-vulnerable dependency. Paired with the
# version floors in pyproject.toml, this is what stops the app shipping
# a CVE-carrying starlette/python-multipart again.
- run: pip install -e . pip-audit
- run: pip-audit --strict
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,18 @@ it to your Dock. For a standalone window with its own icon, open
After pulling new code, run `abapit restart-app` so the service loads it.
`abapit uninstall-app` removes both.

`install-app` reuses whatever Python you ran it with (it works against the
3.9 that ships with macOS Command Line Tools — abapit supports 3.9+).
`install-app` reuses whatever Python you ran it with, so it needs **Python
3.10+**. That floor is a security one, not a syntax one: every patched
`starlette` and `python-multipart` release requires 3.10, and the last
3.9-compatible versions carry unfixed CVEs. The 3.9 that ships with macOS
Command Line Tools is therefore not supported — use the self-contained
bundle below, which brings its own interpreter.

### Self-contained app (bundled Python)

To get an `abapit.app` that depends on **no** system Python — so it runs on a
clean Mac, and isn't tied to Apple's aging 3.9 — build a bundle with its own
CPython:
clean Mac, with a current interpreter and patched dependencies — build a
bundle with its own CPython:

```sh
scripts/build_app.sh # -> ~/Applications/abapit.app + login service
Expand Down Expand Up @@ -222,6 +226,11 @@ profiles; switch from the header dropdown.
- **Egress**: the only hosts ever contacted are `account.apple.com`,
`api-business.apple.com` / `api-school.apple.com`, and — for a Mosyle org —
`businessapi.mosyle.com` plus `businessapilogs.mosyle.com`.
- **Dependencies**: `pyproject.toml` carries security floors for the packages
that parse or transport untrusted input (`python-multipart`, `starlette`,
`cryptography`, `h2`). CI runs `pip-audit --strict`, `ruff`, and `bandit`, so
a newly-disclosed CVE in a dependency fails the build even if nothing in the
repo changed; Dependabot opens weekly update PRs on top of that.
- **Honest limits**: anything running as *your user* can read the config and
key files — the same trust model as `~/.ssh`; secrets are not in the
Keychain. The Apple API account can read inventory, reassign devices, and
Expand Down
26 changes: 18 additions & 8 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ version = "0.2.0"
description = "Apple Business API Tool — a local web GUI for the Apple Business and Apple School Manager APIs, built for Mac admins."
readme = "README.md"
license = { text = "MIT" }
# 3.9 is the Python that ships with macOS (Command Line Tools); abapit runs
# on it (no 3.10+ syntax/stdlib), so admins can install against the system
# Python without adding a newer one.
requires-python = ">=3.9"
# 3.10+ is a SECURITY floor, not a syntax one. Every patched starlette and
# python-multipart release requires >=3.10; the last 3.9-compatible versions
# (starlette 0.49.3, python-multipart 0.0.20) carry unfixed CVEs. macOS ships
# 3.9, but abapit no longer needs it — scripts/build_app.sh bundles its own
# CPython, so the supported install has a current interpreter either way.
requires-python = ">=3.10"
authors = [{ name = "abapit contributors" }]
keywords = ["apple", "abm", "asm", "mdm", "macadmin", "apple-business-manager"]
classifiers = [
Expand All @@ -20,19 +22,27 @@ classifiers = [
"Intended Audience :: System Administrators",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: System :: Systems Administration",
]
dependencies = [
"fastapi>=0.110",
"fastapi>=0.121",
"uvicorn>=0.29",
"jinja2>=3.1",
"httpx[http2]>=0.27",
"PyJWT[crypto]>=2.8",
"python-multipart>=0.0.9",
# Security floors (pip-audit). These parse or transport untrusted input, so
# they are pinned above the advisories rather than left to transitive luck:
# python-multipart <0.0.31 - multipart form parsing DoS chain
# starlette <1.3.1 - request handling advisories
# cryptography <50.0.0 - PYSEC-2026-3552
# h2 <4.4.1 - PYSEC-2026-3628 (HTTP/2, enabled in client.py)
"python-multipart>=0.0.31",
"starlette>=1.3.1",
"cryptography>=50.0.0",
"h2>=4.4.1",
]

[project.optional-dependencies]
Expand All @@ -53,7 +63,7 @@ packages = ["abapit"]
# for best-effort launchctl calls — are left out on purpose. A linter that
# cries wolf gets ignored, which defeats the point of having one.
[tool.ruff]
target-version = "py39"
target-version = "py310"
line-length = 100

[tool.ruff.lint]
Expand Down
Loading