- Python 90%
- Java 10%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks failed
CI / gate (push) Failing after 16s
- Name the trap fields a trap-enabling write would leave empty. - Decide it from the pre-write read, so it costs no extra request. - Report it as data rather than raising on a recoverable condition. - Keep it out of is_faithful, which judges device fidelity alone. - Stay silent on a write that never touches the trap block. - Soften the wire description that over-claimed the device check. |
||
| .github/workflows | ||
| docs | ||
| gradle/wrapper | ||
| java/peplink-fiwm-loader | ||
| python | ||
| .env.example | ||
| .gitignore | ||
| .mcp.json | ||
| .mmcp.toml | ||
| build.gradle | ||
| CLAUDE.md | ||
| gen-config.yaml | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| LICENSE | ||
| openapi.yaml | ||
| pyproject.toml | ||
| README.md | ||
| settings.gradle | ||
| uv.lock | ||
pepapy
pepapy is a version-aware, feature-gated Python SDK for the on-device Peplink Router API. Its entire surface is derived offline, by reverse-engineering firmware images for our own devices, never by probing a live router. A per-firmware OpenAPI spec is the single source of truth, and every other artifact is regenerated from it.
This targets the Router API served by the device itself, not the InControl 2 cloud API.
Exhaustive, version-aware coverage
pepapy models every command a firmware really exposes, not only the documented ones. Coverage spans the six Router API namespaces: auth, cmd, config, status, info, and system. Each operation records its path, method, parameters, response schema, and the firmware version window in which it exists.
Per-firmware OpenAPI specs are the source of truth
Each firmware version has its own OpenAPI 3.1 spec, and that spec is the only hand-edited artifact.
Every operation carries x-peplink-available-since, and x-peplink-removed-in when a command is withdrawn.
The union of the per-version specs reconstructs the full availability matrix and drives the version gating.
Every derived file is regenerated from the specs and never hand-edited: the transport client, the capability gating table, and the operation-path map.
Recovered offline from firmware
The API surface and its schemas come from firmware extraction and handler decompilation, never from a device. A packaged toolchain covers the whole chain:
- The FIWM firmware container and its embedded ext2 image.
- LUKS-serpent decryption of the encrypted rootfs on firmware 8.1 and later.
- A reader for the vendor's big-endian SquashFS 3.0 raw-LZMA1 variant.
- Enumeration of the cgibox dispatch table that routes every command.
- A Ghidra loader for the FIWM format, so the firmware opens directly for analysis.
Exact client, gated by version
pepapy-client is regenerated one-to-one from the specs and is never hand-edited. The same pass emits the capability gating table and the operation-path map, so the client, its gates, and its tests never drift from the spec.
Least-blocking, idempotent SDK
The SDK is the single home for every device interaction and every domain decision. One operation yields exactly one outcome: a success payload, a validation result, or a typed error. A batch never aborts on one item; it returns one result per item, recording which succeeded and which failed and why. Operations are idempotent wherever the domain allows, and version-aware about which commands exist on the target firmware.
Fleet credential rotation
pepapy rotates the administrative credential across the whole fleet, firmware 7.1.x through 8.5.x, dispatched by version.
Below firmware 8.0.1 it uses the staged, full-replace config.admin path.
At and after firmware 8.0.1 it uses the immediate, self-service cmd.password path.
Rotation runs in dry-run, stage, apply, and discard-pending modes, and can export the result to a password-manager CSV.
Mutation and secret safety
config.* writes are full-replace, so an omitted field is wiped.
Every such write is staged, the staged config is diffed against the running config, applied only on a clean diff, and discarded on every other exit.
No live secret is ever read, logged, or returned, and sessions and cookies stay local.
Separate packages, one source of truth
The repository is a uv workspace of five independently publishable Python packages plus a Ghidra loader. The generated client, the SDK, and the testing library stay separate, and every command is testable against mocks. Every caller, the CLI and the research scripts, routes through the SDK rather than duplicating its logic.
Repository layout
graph TD
root["pepapy/ (uv workspace root)"]
root --> spec["openapi.yaml: OpenAPI 3.1 source of truth"]
root --> gencfg["gen-config.yaml: client generator config"]
root --> py["python/"]
root --> java["java/peplink-fiwm-loader: Ghidra loader for the FIWM format"]
py --> pkgs["packages/"]
py --> scripts["scripts/"]
pkgs --> client["pepapy-client: generated transport client (never hand-edited)"]
pkgs --> sdk["pepapy: the SDK"]
pkgs --> testing["pepapy-testing: mocking and unit-testing library"]
pkgs --> cli["pepapy-cli: thin CLI over the SDK"]
pkgs --> conf["pepapy-config: the .conf configuration library"]
scripts --> fw["peplink_fw: FIWM, ext2, and SquashFS extraction"]
scripts --> luks["luks_serpent: LUKS-serpent rootfs decryption"]
scripts --> cgibox["peplink_cgibox: cgibox dispatch enumeration"]
scripts --> adminpw["admin_password: admin credential research"]
scripts --> cmdpw["cmd_password_probe: cmd.password probe"]
scripts --> pepacli["pepacli.py: dev CLI (config, codegen)"]
scripts --> devtools["devtools/codegen.py: regeneration from the specs"]
Source of truth and regeneration
graph TD
spec["openapi.yaml per-firmware specs (source of truth)"]
spec -->|codegen --client| client["pepapy-client (generated)"]
spec -->|codegen| caps["pepapy capability gating table"]
spec -->|codegen| ops["pepapy-testing operation-path map"]
sdk["pepapy (SDK)"] -->|depends on| client
testing["pepapy-testing"] -->|depends on| sdk
Regenerate every derived artifact from the specs with the dev CLI:
uv run python python/scripts/pepacli.py codegen --client
Omit --client to refresh only the capability gating table and the operation-path map.
The SDK surface, authentication modes, and version gating live in the SDK package README. See python/packages/pepapy/README.md.
Standards
pepapy holds to full mmcp-rule compliance, SSOT, and DRY. Names state exactly what the code does, one type lives per file, and comments are concise. Prose is one sentence per line, and no warning is tolerated.