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.
Files changed (52) hide show
  1. rootme_sdk-0.3.0/.gitignore +20 -0
  2. rootme_sdk-0.3.0/AGENTS.md +134 -0
  3. rootme_sdk-0.3.0/API.md +87 -0
  4. rootme_sdk-0.3.0/CAPABILITIES.md +50 -0
  5. rootme_sdk-0.3.0/CONTRIBUTING.md +59 -0
  6. rootme_sdk-0.3.0/LICENSE +21 -0
  7. rootme_sdk-0.3.0/PKG-INFO +127 -0
  8. rootme_sdk-0.3.0/PUBLISHING.md +69 -0
  9. rootme_sdk-0.3.0/README.md +101 -0
  10. rootme_sdk-0.3.0/REQUIREMENTS.md +79 -0
  11. rootme_sdk-0.3.0/SECURITY.md +26 -0
  12. rootme_sdk-0.3.0/Taskfile.yml +37 -0
  13. rootme_sdk-0.3.0/pyproject.toml +83 -0
  14. rootme_sdk-0.3.0/scripts/check_artifact_contents.py +56 -0
  15. rootme_sdk-0.3.0/scripts/check_distributions.py +57 -0
  16. rootme_sdk-0.3.0/scripts/check_installed_package.py +17 -0
  17. rootme_sdk-0.3.0/scripts/verify_pypi.py +83 -0
  18. rootme_sdk-0.3.0/src/rootme_sdk/__init__.py +56 -0
  19. rootme_sdk-0.3.0/src/rootme_sdk/authentication/__init__.py +1 -0
  20. rootme_sdk-0.3.0/src/rootme_sdk/authentication/browser.py +328 -0
  21. rootme_sdk-0.3.0/src/rootme_sdk/authentication/credentials.py +49 -0
  22. rootme_sdk-0.3.0/src/rootme_sdk/authentication/session.py +166 -0
  23. rootme_sdk-0.3.0/src/rootme_sdk/client.py +388 -0
  24. rootme_sdk-0.3.0/src/rootme_sdk/errors.py +56 -0
  25. rootme_sdk-0.3.0/src/rootme_sdk/models.py +120 -0
  26. rootme_sdk-0.3.0/src/rootme_sdk/parsers/__init__.py +1 -0
  27. rootme_sdk-0.3.0/src/rootme_sdk/parsers/api.py +101 -0
  28. rootme_sdk-0.3.0/src/rootme_sdk/parsers/web.py +199 -0
  29. rootme_sdk-0.3.0/src/rootme_sdk/py.typed +0 -0
  30. rootme_sdk-0.3.0/src/rootme_sdk/responses.py +47 -0
  31. rootme_sdk-0.3.0/src/rootme_sdk/transport.py +176 -0
  32. rootme_sdk-0.3.0/src/rootme_sdk/urls.py +27 -0
  33. rootme_sdk-0.3.0/tests/conftest.py +18 -0
  34. rootme_sdk-0.3.0/tests/fixtures/README.md +7 -0
  35. rootme_sdk-0.3.0/tests/fixtures/challenge.html +13 -0
  36. rootme_sdk-0.3.0/tests/fixtures/login.html +8 -0
  37. rootme_sdk-0.3.0/tests/fixtures/preferences.html +13 -0
  38. rootme_sdk-0.3.0/tests/fixtures/submission-already-solved.html +11 -0
  39. rootme_sdk-0.3.0/tests/packaging/test_publication.py +145 -0
  40. rootme_sdk-0.3.0/tests/scenarios/test_client_flows.py +141 -0
  41. rootme_sdk-0.3.0/tests/unit/authentication/test_browser.py +487 -0
  42. rootme_sdk-0.3.0/tests/unit/authentication/test_credentials.py +81 -0
  43. rootme_sdk-0.3.0/tests/unit/authentication/test_session.py +140 -0
  44. rootme_sdk-0.3.0/tests/unit/parsers/test_api.py +81 -0
  45. rootme_sdk-0.3.0/tests/unit/parsers/test_web.py +175 -0
  46. rootme_sdk-0.3.0/tests/unit/test_client.py +435 -0
  47. rootme_sdk-0.3.0/tests/unit/test_errors.py +25 -0
  48. rootme_sdk-0.3.0/tests/unit/test_models.py +29 -0
  49. rootme_sdk-0.3.0/tests/unit/test_responses.py +48 -0
  50. rootme_sdk-0.3.0/tests/unit/test_transport.py +209 -0
  51. rootme_sdk-0.3.0/tests/unit/test_urls.py +46 -0
  52. 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.
@@ -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.
@@ -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.