rootme-sdk 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rootme_sdk-0.3.0/.gitignore +20 -0
- rootme_sdk-0.3.0/AGENTS.md +134 -0
- rootme_sdk-0.3.0/API.md +87 -0
- rootme_sdk-0.3.0/CAPABILITIES.md +50 -0
- rootme_sdk-0.3.0/CONTRIBUTING.md +59 -0
- rootme_sdk-0.3.0/LICENSE +21 -0
- rootme_sdk-0.3.0/PKG-INFO +127 -0
- rootme_sdk-0.3.0/PUBLISHING.md +69 -0
- rootme_sdk-0.3.0/README.md +101 -0
- rootme_sdk-0.3.0/REQUIREMENTS.md +79 -0
- rootme_sdk-0.3.0/SECURITY.md +26 -0
- rootme_sdk-0.3.0/Taskfile.yml +37 -0
- rootme_sdk-0.3.0/pyproject.toml +83 -0
- rootme_sdk-0.3.0/scripts/check_artifact_contents.py +56 -0
- rootme_sdk-0.3.0/scripts/check_distributions.py +57 -0
- rootme_sdk-0.3.0/scripts/check_installed_package.py +17 -0
- rootme_sdk-0.3.0/scripts/verify_pypi.py +83 -0
- rootme_sdk-0.3.0/src/rootme_sdk/__init__.py +56 -0
- rootme_sdk-0.3.0/src/rootme_sdk/authentication/__init__.py +1 -0
- rootme_sdk-0.3.0/src/rootme_sdk/authentication/browser.py +328 -0
- rootme_sdk-0.3.0/src/rootme_sdk/authentication/credentials.py +49 -0
- rootme_sdk-0.3.0/src/rootme_sdk/authentication/session.py +166 -0
- rootme_sdk-0.3.0/src/rootme_sdk/client.py +388 -0
- rootme_sdk-0.3.0/src/rootme_sdk/errors.py +56 -0
- rootme_sdk-0.3.0/src/rootme_sdk/models.py +120 -0
- rootme_sdk-0.3.0/src/rootme_sdk/parsers/__init__.py +1 -0
- rootme_sdk-0.3.0/src/rootme_sdk/parsers/api.py +101 -0
- rootme_sdk-0.3.0/src/rootme_sdk/parsers/web.py +199 -0
- rootme_sdk-0.3.0/src/rootme_sdk/py.typed +0 -0
- rootme_sdk-0.3.0/src/rootme_sdk/responses.py +47 -0
- rootme_sdk-0.3.0/src/rootme_sdk/transport.py +176 -0
- rootme_sdk-0.3.0/src/rootme_sdk/urls.py +27 -0
- rootme_sdk-0.3.0/tests/conftest.py +18 -0
- rootme_sdk-0.3.0/tests/fixtures/README.md +7 -0
- rootme_sdk-0.3.0/tests/fixtures/challenge.html +13 -0
- rootme_sdk-0.3.0/tests/fixtures/login.html +8 -0
- rootme_sdk-0.3.0/tests/fixtures/preferences.html +13 -0
- rootme_sdk-0.3.0/tests/fixtures/submission-already-solved.html +11 -0
- rootme_sdk-0.3.0/tests/packaging/test_publication.py +145 -0
- rootme_sdk-0.3.0/tests/scenarios/test_client_flows.py +141 -0
- rootme_sdk-0.3.0/tests/unit/authentication/test_browser.py +487 -0
- rootme_sdk-0.3.0/tests/unit/authentication/test_credentials.py +81 -0
- rootme_sdk-0.3.0/tests/unit/authentication/test_session.py +140 -0
- rootme_sdk-0.3.0/tests/unit/parsers/test_api.py +81 -0
- rootme_sdk-0.3.0/tests/unit/parsers/test_web.py +175 -0
- rootme_sdk-0.3.0/tests/unit/test_client.py +435 -0
- rootme_sdk-0.3.0/tests/unit/test_errors.py +25 -0
- rootme_sdk-0.3.0/tests/unit/test_models.py +29 -0
- rootme_sdk-0.3.0/tests/unit/test_responses.py +48 -0
- rootme_sdk-0.3.0/tests/unit/test_transport.py +209 -0
- rootme_sdk-0.3.0/tests/unit/test_urls.py +46 -0
- rootme_sdk-0.3.0/uv.lock +1439 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
.venv/
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
.mypy_cache/
|
|
6
|
+
.ruff_cache/
|
|
7
|
+
.coverage*
|
|
8
|
+
htmlcov/
|
|
9
|
+
coverage.xml
|
|
10
|
+
dist/
|
|
11
|
+
.direnv/
|
|
12
|
+
.secrets/
|
|
13
|
+
.env
|
|
14
|
+
.env.*
|
|
15
|
+
!.env.example
|
|
16
|
+
.pypirc
|
|
17
|
+
.audit-requirements.txt
|
|
18
|
+
build/
|
|
19
|
+
*.log
|
|
20
|
+
*.egg-info/
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Agent guidelines — rootme-sdk
|
|
2
|
+
|
|
3
|
+
## Scope and language
|
|
4
|
+
|
|
5
|
+
Build the Python library described in [REQUIREMENTS.md](REQUIREMENTS.md). Keep it
|
|
6
|
+
small; do not add a server, solver or unrelated application. Choose implementation
|
|
7
|
+
details from verified platform behavior rather than guessed endpoints. Scope is
|
|
8
|
+
accounts and challenges, with password/session authentication; do not add API-key
|
|
9
|
+
authentication, messaging, community or virtual-environment management.
|
|
10
|
+
|
|
11
|
+
Write repository content in English: code, comments, documentation, commits and
|
|
12
|
+
PRs. Speak French with the maintainer. Never add `Co-Authored-By` or other
|
|
13
|
+
authorship trailers to commits.
|
|
14
|
+
|
|
15
|
+
## Python code and architecture
|
|
16
|
+
|
|
17
|
+
- Use `src/rootme_sdk/`. Start with focused modules and create subpackages only
|
|
18
|
+
when a concern needs multiple modules. Functions stay at most 30 lines; modules
|
|
19
|
+
stay below 1000 lines. Prefer shallow, coherent packages over speculative layers.
|
|
20
|
+
- DRY, one concern per module, one intent per code path. Keep code linear and
|
|
21
|
+
avoid abstractions or compatibility branches without a current requirement.
|
|
22
|
+
- State responsibility in names. Avoid `utils`, `helpers`, `misc`, `shared`,
|
|
23
|
+
`core`, `base` and `tools` as catch-all module/package names. Shared logic has
|
|
24
|
+
a named owner; do not create a generic `common` tree in advance.
|
|
25
|
+
- Define and preserve import direction. Lower-level transport, parsing and
|
|
26
|
+
authentication mechanisms must not depend on the public client orchestrating
|
|
27
|
+
them. No circular imports or unrelated cross-feature dependencies; promote
|
|
28
|
+
genuinely shared logic to its proper owner instead of duplicating it.
|
|
29
|
+
- `__init__.py` contains public re-exports and `__all__` only: no logic or
|
|
30
|
+
import-time side effects. Re-export intentional public APIs through the package
|
|
31
|
+
boundary so consumers do not depend on deep implementation imports.
|
|
32
|
+
- Type the SDK fully and run `mypy --strict`. Validate caller input and external
|
|
33
|
+
responses at their boundaries, then trust validated data internally. Do not
|
|
34
|
+
hide unexpected defects with broad `try/except`. Expected network, authentication
|
|
35
|
+
and platform failures must become explicit SDK outcomes or documented errors.
|
|
36
|
+
- In production code, use `assert` only for type narrowing, never for runtime
|
|
37
|
+
validation or control flow.
|
|
38
|
+
- Keep the public interface deliberate and simple. Unlike the source SaaS,
|
|
39
|
+
this library has external consumers: preserve published contracts or make
|
|
40
|
+
breaking changes explicit in the version and migration guidance. Update all
|
|
41
|
+
internal callers and tests in the same change; no speculative legacy paths.
|
|
42
|
+
- Keep runtime dependencies minimal. Password authentication manages JavaScript
|
|
43
|
+
assistance and headed browser setup automatically; callers supply only credentials.
|
|
44
|
+
Require a working graphical display. Do not reintroduce headless, HTTP-only or
|
|
45
|
+
manual login paths. Await native redirects/AJAX completion and verify account-only
|
|
46
|
+
access before reporting authentication success; a cookie alone is insufficient.
|
|
47
|
+
Browser support is installed by default, but its runtime starts only when
|
|
48
|
+
needed. Do not configure the consuming application's logging or emit secrets.
|
|
49
|
+
|
|
50
|
+
## Tooling and quality gates
|
|
51
|
+
|
|
52
|
+
- `pyproject.toml` owns package metadata, the release version, Python support,
|
|
53
|
+
dependencies and tool configuration. Use `uv` and commit `uv.lock`.
|
|
54
|
+
- `flake.nix` and `flake.lock` provide a reproducible devShell with Python, `uv`,
|
|
55
|
+
`task`, `gh` and required development tools. Add missing tools there. Do not
|
|
56
|
+
copy Nix package, NixOS/Home Manager module or binary-distribution machinery.
|
|
57
|
+
- Provide a small `Taskfile.yml`: formatting, lint, typecheck, docstrings, unit
|
|
58
|
+
tests, package build and an aggregate `task ci`. CI runs the same checks.
|
|
59
|
+
- Ruff handles lint and format; line length is 100. Enforce `ruff check` and
|
|
60
|
+
`ruff format --check`, strict mypy, and `interrogate --fail-under=100` for
|
|
61
|
+
applicable SDK docstrings. Use one documented docstring convention.
|
|
62
|
+
- Enforce **100% unit-test coverage of the complete SDK** with pytest/pytest-cov
|
|
63
|
+
and `--cov-fail-under=100`, locally and in CI. Broader tests cannot compensate
|
|
64
|
+
for missing unit coverage. Never lower, bypass or remove a quality gate.
|
|
65
|
+
- No `pragma: no cover`, `type: ignore`, `noqa`, blanket missing-import ignores,
|
|
66
|
+
coverage exclusions or equivalent escapes without an unavoidable, documented
|
|
67
|
+
constraint. An exception must be narrow and explained beside the suppression.
|
|
68
|
+
- Install a `.githooks/` pre-commit hook running `task ci` once tooling exists;
|
|
69
|
+
never bypass it with `--no-verify`. Documentation-only bootstrapping does not
|
|
70
|
+
require implementing the SDK just to validate these initial documents.
|
|
71
|
+
|
|
72
|
+
## Tests
|
|
73
|
+
|
|
74
|
+
- Mirror `src/rootme_sdk/` in `tests/unit/`, with one `test_<module>.py` for each
|
|
75
|
+
executable source module. Keep module ownership explicit; delete matching
|
|
76
|
+
tests when deleting a module. Re-export-only files need no artificial tests.
|
|
77
|
+
- Test observable behavior and failure cases, including denied access and
|
|
78
|
+
ambiguous submission outcomes. Mock external boundaries rather than the logic
|
|
79
|
+
under test. Fixtures contain no credentials, cookies or real submitted answers.
|
|
80
|
+
- All automated tests are offline and independent of a real Root-Me account.
|
|
81
|
+
Add multi-module scenarios or a local fake service when useful, without live
|
|
82
|
+
platform integration. Keep scenarios deterministic, isolated and free of fixed
|
|
83
|
+
sleeps. Do not skip or retry failing tests to obtain green CI.
|
|
84
|
+
- Verify built wheels/source distributions install and expose the typed public
|
|
85
|
+
interface without contacting Root-Me. Ship typing information (`py.typed`).
|
|
86
|
+
- Keep packaging verification tests offline too. Run dependency advisories and
|
|
87
|
+
redacted Git history scans separately with `task audit` and `task secrets`.
|
|
88
|
+
|
|
89
|
+
## Git workflow
|
|
90
|
+
|
|
91
|
+
- Once a remote `main` exists, fetch it and branch from its current state using
|
|
92
|
+
`feat/`, `fix/` or `chore/`. Rebase on `main`; do not merge it into the branch.
|
|
93
|
+
Preserve uncommitted user work. An empty repository needs its initial baseline
|
|
94
|
+
established before the ordinary PR workflow is possible.
|
|
95
|
+
- Changes land through PRs targeting `main`; no direct pushes to established
|
|
96
|
+
`main`, force-pushes to `main`, or protection bypasses. Use Conventional Commit
|
|
97
|
+
PR titles and squash merges: `git log main` is the release history; no separate
|
|
98
|
+
generated changelog is required.
|
|
99
|
+
- Run `task ci` before committing. Use `gh` to inspect PRs and CI failures; verify
|
|
100
|
+
an old branch has not already merged before adding commits to it. Watch checks
|
|
101
|
+
and fix failures. An ordinary PR may merge once required checks pass, subject
|
|
102
|
+
to the protected-path rule below; monitor the resulting `main` CI run too.
|
|
103
|
+
- Use `.github/CODEOWNERS` for `.github/`, `flake.nix`, `AGENTS.md` and
|
|
104
|
+
`SECURITY.md`. Merging changes to listed paths requires explicit human approval
|
|
105
|
+
in the conversation. Prepare and validate the complete change before requesting
|
|
106
|
+
that approval. Live GitHub protection/governance changes also need explicit
|
|
107
|
+
approval; missing permissions are reported rather than worked around.
|
|
108
|
+
|
|
109
|
+
## Releases
|
|
110
|
+
|
|
111
|
+
- Distribution is a wheel and source archive attached to a GitHub
|
|
112
|
+
Release. PyPI publication is deferred and disabled unless explicitly enabled
|
|
113
|
+
with publisher permissions. Merging to `main` does not publish a release.
|
|
114
|
+
Do not add PyInstaller binaries or Nix packaging.
|
|
115
|
+
- Bump `[project].version` in `pyproject.toml` through a normal PR **before**
|
|
116
|
+
tagging. Tag the corresponding validated commit on `main` as `vX.Y.Z`; the
|
|
117
|
+
tag, package metadata and artifact versions must agree.
|
|
118
|
+
- **Never push a release tag or publish a version without explicit human opt-in
|
|
119
|
+
in the current conversation.** Approval of a release tag authorizes both
|
|
120
|
+
all currently enabled publication destinations, without a second approval.
|
|
121
|
+
Never move or delete an existing release tag.
|
|
122
|
+
- The tag-triggered workflow builds and checks the wheel/source archive from
|
|
123
|
+
that commit, then publishes those artifacts to enabled destinations. Configure
|
|
124
|
+
authentication securely; never commit tokens. Confirm PyPI ownership/name
|
|
125
|
+
availability and permissions before enabling its first publication.
|
|
126
|
+
- Watch the workflow to completion. Verify both GitHub assets and clean artifact
|
|
127
|
+
installation/import, plus the PyPI version/install when publishing is enabled;
|
|
128
|
+
a successful tag push is not completion.
|
|
129
|
+
- Fix failed releases through the normal workflow. Roll forward with a new
|
|
130
|
+
version and tag; consumers can pin a previous known-good version. Never rewrite
|
|
131
|
+
release history. A corrective release still needs explicit tag approval.
|
|
132
|
+
|
|
133
|
+
These guidelines are self-contained; deployment, frontend, database and Nix
|
|
134
|
+
packaging rules from other applications do not apply to this SDK.
|
rootme_sdk-0.3.0/API.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Public API
|
|
2
|
+
|
|
3
|
+
Import supported names from `rootme_sdk`, not its internal submodules. This is a
|
|
4
|
+
synchronous SDK. Use one client in one thread and close it with a `with` block.
|
|
5
|
+
Its synchronous Playwright browser cannot run in a thread with an active asyncio
|
|
6
|
+
event loop; call the whole client lifecycle from a separate synchronous worker
|
|
7
|
+
when embedding it in an asynchronous application.
|
|
8
|
+
|
|
9
|
+
## Connection
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from rootme_sdk import RootMeClient
|
|
13
|
+
|
|
14
|
+
with RootMeClient("your-login", "your-password") as client:
|
|
15
|
+
page = client.list_challenges(language="en", score=5)
|
|
16
|
+
for challenge in page.items:
|
|
17
|
+
print(challenge.id, challenge.title)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Alternatively, `RootMeClient(credentials_file=".secrets/credentials.json")` reads
|
|
21
|
+
`{"login": "...", "password": "..."}` as UTF-8. Supply exactly one credential
|
|
22
|
+
source. An anonymous `RootMeClient()` can read website challenge URLs and categories;
|
|
23
|
+
an ID must first resolve through the authenticated API. Anonymous reads may still
|
|
24
|
+
open a graphical browser for JavaScript verification.
|
|
25
|
+
|
|
26
|
+
The constructor's optional keyword arguments are `timeout=30` (HTTP seconds),
|
|
27
|
+
`read_retries=1`, `max_retry_delay=5`, and `transport` (an HTTPX transport, useful
|
|
28
|
+
for offline tests). Managed browser login has a separate 180-second timeout;
|
|
29
|
+
`login(..., timeout=180)` can change it. Read retries are bounded and respect
|
|
30
|
+
eligible waiting intervals; authentication and writes are never replayed.
|
|
31
|
+
|
|
32
|
+
Advanced session reuse accepts `session=Session.load(path)` or
|
|
33
|
+
`spip_session="..."` instead of login credentials. These contain account secrets,
|
|
34
|
+
do not renew expired sessions and do not eliminate later JavaScript verification.
|
|
35
|
+
Ordinary users need only credentials. `client.session.save(path)` is opt-in.
|
|
36
|
+
|
|
37
|
+
## Account and challenges
|
|
38
|
+
|
|
39
|
+
| Method | Result and behavior |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| `login(username, password)` or `login(credentials_file=...)` | `Session`; authenticate/reconnect using the graphical browser |
|
|
42
|
+
| `logout()` | `None`; request server logout, clear local state and close the browser |
|
|
43
|
+
| `close()` | `None`; release browser and HTTP resources |
|
|
44
|
+
| `get_user(identifier)` | `UserProfile`; `id`, `name`, `score`, `position`, additional JSON in `data` |
|
|
45
|
+
| `preferences(language="en")` | `WebPage`; current editable controls in `forms` |
|
|
46
|
+
| `update_preferences(changes, files=None)` | `WebPage`; send selected editable fields once, refreshing hidden tokens |
|
|
47
|
+
| `get_challenge(id)` | `Challenge`; authenticated API metadata, often without a statement |
|
|
48
|
+
| `get_challenge(url)` / `read_challenge(id_or_url)` | `Challenge`; full website statement and resources |
|
|
49
|
+
| `list_challenges(title=None, subtitle=None, language=None, score=None, author_ids=())` | `Collection[Challenge]`; one API page, with `items` and `next_url`; all filters are keyword-only |
|
|
50
|
+
| `iter_challenges(**filters)` | `Iterator[Challenge]`; lazy pagination with native API filter names, such as `lang="en"`, `score=5`, `titre="..."` |
|
|
51
|
+
| `list_categories(language="en")` | `tuple[Category, ...]`; category `title` and `url` |
|
|
52
|
+
| `submit_answer(id_or_url, answer)` | `SubmissionResult`; status, sanitized feedback and optional retry interval |
|
|
53
|
+
| `download(resource_or_https_url, destination=None)` | `bytes`; optionally write to the supplied path; external hosts receive no account cookies |
|
|
54
|
+
|
|
55
|
+
Language arguments on website methods are keyword-only and accept `en` or `fr`.
|
|
56
|
+
API IDs must be positive integers. `Challenge` exposes `id`, `title`, `score`,
|
|
57
|
+
`category_id`, `url`, `statement`, `statement_html`, `resources` and `data`.
|
|
58
|
+
API fields absent from a response remain `None`; `.data` preserves extra JSON.
|
|
59
|
+
Resources contain a `url` and `label`, including links to services and documentation;
|
|
60
|
+
choose downloadable attachments rather than passing arbitrary service links.
|
|
61
|
+
|
|
62
|
+
`WebPage` contains `url`, `title`, `text`, `html`, `links` and `forms`.
|
|
63
|
+
Each `WebForm` exposes its name and typed `FormField` controls. Account updates
|
|
64
|
+
support one string per editable control and `Upload(filename, content, content_type)`
|
|
65
|
+
for observed upload controls. Hidden tokens cannot be overridden.
|
|
66
|
+
|
|
67
|
+
`SubmissionStatus` values are `ACCEPTED`, `REJECTED`, `ALREADY_SOLVED`, `BLOCKED`
|
|
68
|
+
and `INDETERMINATE`. After an indeterminate outcome, inspect account/challenge state
|
|
69
|
+
before deliberately submitting again. Never turn it into an automatic retry loop.
|
|
70
|
+
|
|
71
|
+
## Errors
|
|
72
|
+
|
|
73
|
+
All SDK operational exceptions inherit from `RootMeError`. Invalid caller input
|
|
74
|
+
raises `ValueError`; local destination/session file failures can raise `OSError`.
|
|
75
|
+
|
|
76
|
+
| Exception | Meaning |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| `AuthenticationRequiredError` | Login required or rejected; `reason` is `missing`, `expired` or `rejected` |
|
|
79
|
+
| `BrowserUnavailableError` | Missing graphical display or browser startup failure |
|
|
80
|
+
| `HumanInterventionRequiredError` | Platform verification unresolved; includes `url` |
|
|
81
|
+
| `RateLimitedError` | Platform rate limit; `retry_after` can be absent |
|
|
82
|
+
| `PermissionDeniedError` | Access denied |
|
|
83
|
+
| `NotFoundError` | Requested resource absent |
|
|
84
|
+
| `NetworkError` | Network operation failed |
|
|
85
|
+
| `UnexpectedResponseError` | Unrecognized or invalid platform response |
|
|
86
|
+
|
|
87
|
+
See [CAPABILITIES.md](CAPABILITIES.md) for live observations and unresolved limits.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Capabilities
|
|
2
|
+
|
|
3
|
+
Authentication uses username/password and reusable web sessions. Official API
|
|
4
|
+
reads reuse the login cookie. Website JavaScript gates can affect public reads.
|
|
5
|
+
|
|
6
|
+
| Client method | Purpose | Access | Evidence |
|
|
7
|
+
| --- | --- | --- | --- |
|
|
8
|
+
| `RootMeClient(login, password)` / `RootMeClient(credentials_file=...)` | Connect from credentials and manage browser/session internally | Existing account | Both modes observed without human input, including preferences and challenge reads |
|
|
9
|
+
| `login` | Connect/reconnect an existing client, with automatic JS assistance | Existing account | Same managed authentication flow |
|
|
10
|
+
| `logout` | Server logout and local credential erasure | Session for server logout | Route observed; behavior tested offline |
|
|
11
|
+
| `get_challenge(id)` | Metadata and additional API fields | Login session | Detail response observed |
|
|
12
|
+
| `get_challenge(url)`, `read_challenge` | Full statement, resources and access instructions | Website access | Challenge pages observed |
|
|
13
|
+
| `list_challenges`, `iter_challenges` | Filters and pagination | Login session | Documented API; response shapes observed |
|
|
14
|
+
| `list_categories` | Catalogue categories | Website access | 11 categories observed |
|
|
15
|
+
| `get_user` | Account profile, score and available progression data | Login session | Authenticated profile response observed; validations hold solved entries |
|
|
16
|
+
| `preferences` | Editable account field inventory | Web session | modifier_auteur form observed |
|
|
17
|
+
| `update_preferences` | Selected profile changes and file uploads | Web session | Controls observed; tested offline |
|
|
18
|
+
| `submit_answer` | One answer submission and structured result | Web session | Live already-solved response observed; other outcomes tested offline |
|
|
19
|
+
| `download` | Attachments with scoped credentials | Public or same-host session | Tested offline |
|
|
20
|
+
|
|
21
|
+
## Limits
|
|
22
|
+
|
|
23
|
+
Password authentication uses only graphical Chrome/Chromium. The SDK finds or
|
|
24
|
+
prepares Chromium automatically, submits credentials once, waits for native login
|
|
25
|
+
completion and verifies the account preferences page before returning a connected
|
|
26
|
+
client. A graphical display and Chromium's system dependencies are prerequisites;
|
|
27
|
+
missing Linux `DISPLAY` is rejected before browser startup. Headless and HTTP-only
|
|
28
|
+
login are not supported. Platform verification, network and rate-limit failures
|
|
29
|
+
are explicit errors; rejected credentials are never silently resubmitted.
|
|
30
|
+
|
|
31
|
+
Authentication is still at alpha maturity. Both credential sources produced
|
|
32
|
+
successful account/API/challenge reads, but some fresh sessions still returned a
|
|
33
|
+
login page during account verification after positive login feedback. The cause
|
|
34
|
+
is unresolved. Further live checks stopped after an HTTP 429 response. Automated
|
|
35
|
+
unit and packaging checks cannot establish reliable live platform authentication.
|
|
36
|
+
No guarantee of unattended login success is made. Account preference mutations
|
|
37
|
+
also lack live validation; use deliberate, minimal updates.
|
|
38
|
+
|
|
39
|
+
Submission feedback is read only inside the challenge validation form, using
|
|
40
|
+
observed success/error and SPIP feedback classes. Explicit English/French
|
|
41
|
+
already-solved messages are recognized. HTTP 429 is blocked; ambiguous failures
|
|
42
|
+
and unknown feedback remain indeterminate. Writes are never automatically replayed.
|
|
43
|
+
|
|
44
|
+
Account updates support one value per control and one file per upload control.
|
|
45
|
+
Preference mutations have not been manually exercised. The SDK exposes account
|
|
46
|
+
and challenge operations only; generic forms are private implementation details.
|
|
47
|
+
|
|
48
|
+
Source: [official API documentation](https://api.www.root-me.org/?lang=en) and
|
|
49
|
+
manual observations on 2026-10-04. No real answer, credential or account snapshot
|
|
50
|
+
is committed in fixtures.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Use `nix develop`, `uv sync --locked`, and `task ci`. Enable the
|
|
4
|
+
pre-commit hook with `git config core.hooksPath .githooks`. Automated tests are
|
|
5
|
+
offline and independent of Root-Me accounts. Distribution checks prepare locked
|
|
6
|
+
runtime dependencies from the local uv cache, install each artifact in a clean
|
|
7
|
+
environment and verify public exports, version and typing information.
|
|
8
|
+
|
|
9
|
+
Branch from current main, open a Conventional Commit PR and squash merge after
|
|
10
|
+
checks pass. Required checks are quality (3.13), quality (3.14) and lint-pr-title.
|
|
11
|
+
Also wait for Windows/macOS portability, dependency audit and secret scan checks.
|
|
12
|
+
Governance changes and merges touching CODEOWNERS paths require explicit approval
|
|
13
|
+
in the conversation, as defined in AGENTS.md.
|
|
14
|
+
|
|
15
|
+
## Without Nix
|
|
16
|
+
|
|
17
|
+
Install Python 3.13 or 3.14, [uv](https://docs.astral.sh/uv/getting-started/installation/)
|
|
18
|
+
and [Task](https://taskfile.dev/docs/installation), then run the same `uv sync --locked`
|
|
19
|
+
and `task ci` commands. Nix is only a development environment, never a runtime
|
|
20
|
+
requirement. Use Google-style docstrings and the self-contained [AGENTS.md](AGENTS.md)
|
|
21
|
+
coding rules. The public API reference is [API.md](API.md).
|
|
22
|
+
|
|
23
|
+
`task ci` includes offline packaging guard tests. Run `task audit` separately to
|
|
24
|
+
check the locked runtime dependencies against published vulnerability advisories
|
|
25
|
+
(network access required). Run `task secrets` with Gitleaks installed to scan the
|
|
26
|
+
complete local Git history with redacted findings. Both tools are in the devShell;
|
|
27
|
+
security CI repeats these checks weekly and on changes. Dependency updates arrive
|
|
28
|
+
as ordinary Dependabot PRs and must pass the same checks.
|
|
29
|
+
|
|
30
|
+
## Releases
|
|
31
|
+
|
|
32
|
+
The repository and GitHub releases are public. PyPI publication is deferred:
|
|
33
|
+
the release workflow skips it unless the repository variable PUBLISH_PYPI is true.
|
|
34
|
+
|
|
35
|
+
1. Set the version in pyproject.toml through a validated PR and merge.
|
|
36
|
+
2. Wait for successful CI on that exact main commit.
|
|
37
|
+
3. Obtain explicit release approval, then tag that commit as vX.Y.Z and push.
|
|
38
|
+
4. Watch release.yml: it verifies version/main CI, checks and builds artifacts,
|
|
39
|
+
then attaches the wheel and source archive to the GitHub release.
|
|
40
|
+
5. Verify assets and install/import each distribution from a clean environment.
|
|
41
|
+
|
|
42
|
+
Never rewrite release tags or published versions. Correct failures with a normal
|
|
43
|
+
PR and a newly approved version/tag.
|
|
44
|
+
|
|
45
|
+
## Future PyPI setup
|
|
46
|
+
|
|
47
|
+
Enable PyPI only after explicit authorization and project ownership checks.
|
|
48
|
+
Configure a [Pending Trusted Publisher](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/):
|
|
49
|
+
|
|
50
|
+
- Project: rootme-sdk; confirm name availability and ownership.
|
|
51
|
+
- GitHub owner/repository: Thomas97460/rootme-sdk.
|
|
52
|
+
- Workflow: release.yml; GitHub environment: pypi.
|
|
53
|
+
|
|
54
|
+
After the decision to open the repository, create that environment and set
|
|
55
|
+
PUBLISH_PYPI=true. The workflow uses OIDC without
|
|
56
|
+
stored long-lived tokens and publishes the same artifacts as GitHub. Once enabled,
|
|
57
|
+
release verification also checks the PyPI version and a clean installation from
|
|
58
|
+
PyPI. This repository's workflow also requires public repository visibility.
|
|
59
|
+
See [PUBLISHING.md](PUBLISHING.md) for the remaining owner actions and release procedure.
|
rootme_sdk-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thomas Collet
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: rootme-sdk
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Unofficial typed Python SDK for Root-Me accounts and challenges
|
|
5
|
+
Project-URL: Repository, https://github.com/Thomas97460/rootme-sdk
|
|
6
|
+
Project-URL: Documentation, https://github.com/Thomas97460/rootme-sdk/blob/main/API.md
|
|
7
|
+
Project-URL: Issues, https://github.com/Thomas97460/rootme-sdk/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/Thomas97460/rootme-sdk/releases
|
|
9
|
+
Author: Thomas Collet
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: challenges,ctf,root-me,sdk
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Python: >=3.13
|
|
21
|
+
Requires-Dist: beautifulsoup4<5,>=4.13
|
|
22
|
+
Requires-Dist: httpx<1,>=0.28
|
|
23
|
+
Requires-Dist: playwright<2,>=1.55
|
|
24
|
+
Provides-Extra: browser
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# rootme-sdk
|
|
28
|
+
|
|
29
|
+
An unofficial, typed Python SDK for [Root-Me](https://www.root-me.org/): automated login, session reuse, profile and challenge exploration, and answer submission.
|
|
30
|
+
|
|
31
|
+
> [!NOTE]
|
|
32
|
+
> This library is alpha software and is not affiliated with Root-Me. Respect Root-Me's terms of service and rate limits.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
Clone the repository and install the package with pip (Python >= 3.13 required):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone https://github.com/Thomas97460/rootme-sdk.git
|
|
40
|
+
cd rootme-sdk
|
|
41
|
+
pip install .
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Playwright is included. On first use, it automatically uses your local Chrome/Chromium or downloads a managed Chromium browser.
|
|
45
|
+
|
|
46
|
+
## Quickstart
|
|
47
|
+
|
|
48
|
+
### 1. Login and read challenges
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from rootme_sdk import RootMeClient
|
|
52
|
+
|
|
53
|
+
# Connect with credentials directly
|
|
54
|
+
with RootMeClient("your-username", "your-password") as client:
|
|
55
|
+
# Read a challenge statement
|
|
56
|
+
challenge = client.read_challenge(5)
|
|
57
|
+
print(f"Title: {challenge.title}")
|
|
58
|
+
print(f"Statement: {challenge.statement}")
|
|
59
|
+
|
|
60
|
+
# Browse challenges
|
|
61
|
+
for item in client.iter_challenges(lang="en", score=5):
|
|
62
|
+
print(f"[{item.id}] {item.title} ({item.score} pts)")
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Alternatively, pass credentials via a JSON file:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{"login": "your-username", "password": "your-password"}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
with RootMeClient(credentials_file=".secrets/credentials.json") as client:
|
|
73
|
+
user = client.get_user(12345)
|
|
74
|
+
print(f"User: {user.nom}, Score: {user.score}")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 2. Submit an answer
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from rootme_sdk import RootMeClient, SubmissionStatus
|
|
81
|
+
|
|
82
|
+
with RootMeClient("your-username", "your-password") as client:
|
|
83
|
+
result = client.submit_answer(5, "flag{your_flag_here}")
|
|
84
|
+
|
|
85
|
+
if result.status == SubmissionStatus.ACCEPTED:
|
|
86
|
+
print("Flag validated!")
|
|
87
|
+
elif result.status == SubmissionStatus.ALREADY_SOLVED:
|
|
88
|
+
print("Challenge already solved.")
|
|
89
|
+
elif result.status == SubmissionStatus.REJECTED:
|
|
90
|
+
print("Incorrect flag.")
|
|
91
|
+
elif result.status == SubmissionStatus.INDETERMINATE:
|
|
92
|
+
print(f"Ambiguous response: {result.message}")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 3. Session reuse
|
|
96
|
+
|
|
97
|
+
Save the session to avoid re-authenticating on every run:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from rootme_sdk import RootMeClient, Session
|
|
101
|
+
|
|
102
|
+
with RootMeClient("your-username", "your-password") as client:
|
|
103
|
+
client.session.save(".secrets/session.json")
|
|
104
|
+
|
|
105
|
+
# Later: reload the saved session
|
|
106
|
+
with RootMeClient(session=Session.load(".secrets/session.json")) as client:
|
|
107
|
+
challenge = client.read_challenge(5)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Important Notes
|
|
111
|
+
|
|
112
|
+
- **Graphical Display**: Password login uses an isolated, headed Chromium browser to handle Root-Me's native login flow. A working graphical display is required (`DISPLAY` on Linux).
|
|
113
|
+
- **Security**: Never commit your passwords or `.secrets/` directory. Saved sessions contain cookies and should be restricted to your user account.
|
|
114
|
+
- **Documentation**:
|
|
115
|
+
- [API Reference](API.md) — Exhaustive methods and types documentation.
|
|
116
|
+
- [Capabilities & Limits](CAPABILITIES.md) — Observed platform behaviors and known limitations.
|
|
117
|
+
- [Contributing](CONTRIBUTING.md) — Development workflow, quality gates, and testing guidelines.
|
|
118
|
+
- [Security Policy](SECURITY.md) — Vulnerability reporting and security practices.
|
|
119
|
+
|
|
120
|
+
## Development
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
nix develop # or install uv + task manually
|
|
124
|
+
uv sync --locked
|
|
125
|
+
git config core.hooksPath .githooks
|
|
126
|
+
task ci
|
|
127
|
+
```
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Public GitHub and PyPI preparation
|
|
2
|
+
|
|
3
|
+
The repository is public and `PUBLISH_PYPI=false`. No publication or
|
|
4
|
+
visibility change follows from merging the preparation work. The current package
|
|
5
|
+
version is 0.3.0; its tag has not been published.
|
|
6
|
+
|
|
7
|
+
## Prepared in the repository
|
|
8
|
+
|
|
9
|
+
- MIT license and SPDX metadata, author, Python support, alpha maturity, typing,
|
|
10
|
+
project links and a README suitable for PyPI rendering.
|
|
11
|
+
- Account/challenge API reference, graphical-runtime requirements, migration
|
|
12
|
+
guidance, contribution instructions and a private security contact.
|
|
13
|
+
- Offline tests with 100% SDK unit coverage, package guard tests, and wheel/source
|
|
14
|
+
installation checks on Python 3.13/3.14 across Linux, macOS and Windows.
|
|
15
|
+
- Runtime dependency advisory checks, redacted secret scanning and weekly updates.
|
|
16
|
+
GitHub Actions are pinned to commits, with minimal job permissions.
|
|
17
|
+
- Tag/version/exact-main-CI and security checks, a single validated build shared by GitHub/PyPI,
|
|
18
|
+
OIDC publishing and post-publication hash/clean-install verification.
|
|
19
|
+
|
|
20
|
+
Before opening, run `task ci`, `task audit` and `task secrets` on the final commit.
|
|
21
|
+
Gitleaks covers common secret formats; also compare historical blobs, package
|
|
22
|
+
contents and old release assets against locally stored account secrets without
|
|
23
|
+
printing them. Changing current files does not remove historical data. Commit
|
|
24
|
+
author identities, old docs and release notes become public with the repository;
|
|
25
|
+
review those intentionally. Fixtures contain synthetic examples, never copied
|
|
26
|
+
challenge statements or real answers.
|
|
27
|
+
|
|
28
|
+
Preparation audit on 2026-10-04: Gitleaks found no leaks in the existing 10 local
|
|
29
|
+
commits; 110 reachable historical blobs and four v0.1.0/v0.2.0 release artifacts
|
|
30
|
+
contained no locally known passwords, submitted answer or session cookie values.
|
|
31
|
+
The locked runtime dependency audit reported no known vulnerabilities. PyPI's
|
|
32
|
+
project API returned 404 for `rootme-sdk`; this is not a name reservation.
|
|
33
|
+
These checks are a dated result, not a guarantee about future changes.
|
|
34
|
+
|
|
35
|
+
## Product maturity decision
|
|
36
|
+
|
|
37
|
+
Publishing an **alpha** is possible with the current documented limits. Publishing
|
|
38
|
+
as a reliable unattended-login SDK is not yet justified: some fresh sessions fail
|
|
39
|
+
account verification after positive login feedback, for an unresolved reason.
|
|
40
|
+
Preference mutations also have not been tested on a real account. Offline coverage
|
|
41
|
+
does not resolve these platform-level limits. Further live tests are intentionally
|
|
42
|
+
deferred; do not run repeated logins merely to obtain one successful result.
|
|
43
|
+
|
|
44
|
+
## Owner actions after deciding to publish
|
|
45
|
+
|
|
46
|
+
1. Confirm the alpha positioning and choose whether to open the repository.
|
|
47
|
+
Enable GitHub private vulnerability reporting after it becomes public; the
|
|
48
|
+
email contact already works independently. Review main protection rules for
|
|
49
|
+
the quality, portability and security checks before accepting outside changes.
|
|
50
|
+
2. In the maintainer's PyPI account, confirm project name availability/ownership
|
|
51
|
+
and create a [Pending Trusted Publisher](https://docs.pypi.org/trusted-publishers/creating-a-project-through-oidc/):
|
|
52
|
+
project **rootme-sdk**, GitHub owner **Thomas97460**, repository **rootme-sdk**,
|
|
53
|
+
workflow **release.yml**, environment **pypi**. A pending publisher does not
|
|
54
|
+
reserve the project name; first successful publication creates the project.
|
|
55
|
+
3. Create the GitHub `pypi` environment and explicitly set `PUBLISH_PYPI=true`.
|
|
56
|
+
The workflow skips PyPI while the repository is private, even with that variable
|
|
57
|
+
enabled. No password/token is needed in GitHub for this OIDC publisher.
|
|
58
|
+
4. Adjust the README install status through a normal PR. Wait for green CI and
|
|
59
|
+
security checks on its exact main commit. Obtain explicit tag approval, then
|
|
60
|
+
create/push `v0.3.0` on that commit, provided the version remains untagged.
|
|
61
|
+
5. Watch the release workflow. It attaches wheel/source assets on GitHub and, when
|
|
62
|
+
enabled, publishes to PyPI. It checks PyPI's exact version file hashes against
|
|
63
|
+
the build and installs that version into a clean environment outside the repo.
|
|
64
|
+
Review both destinations; a successful tag push alone is insufficient.
|
|
65
|
+
|
|
66
|
+
Never move a tag or replace a published version. If the GitHub publication succeeds
|
|
67
|
+
but PyPI fails, repair the workflow through a PR and release a new approved version
|
|
68
|
+
instead of blindly replaying all publication jobs. Already uploaded files cannot
|
|
69
|
+
be replaced. Never upload credentials or session files as release assets.
|