lupaxa-github-token-validator 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,98 @@
1
+ """Local descriptions for scopes GitHub returns."""
2
+
3
+ from __future__ import annotations
4
+
5
+ SCOPE_ALLOWS: dict[str, str] = {
6
+ "repo": (
7
+ "Full read and write access to public and private repository code, "
8
+ "statuses, invitations, collaborators, deployments, and webhooks, "
9
+ "plus related organisation resources that this scope includes"
10
+ ),
11
+ "repo:status": ("Read and write commit statuses without repository code access"),
12
+ "repo_deployment": (
13
+ "Read and write deployment statuses without repository code access"
14
+ ),
15
+ "public_repo": (
16
+ "Read and write access limited to public repositories, "
17
+ "including starring public repositories"
18
+ ),
19
+ "repo:invite": (
20
+ "Accept or decline repository collaboration invitations without code access"
21
+ ),
22
+ "security_events": (
23
+ "Read and write code-scanning security events without repository code access"
24
+ ),
25
+ "admin:repo_hook": "Read, write, ping, and delete repository hooks",
26
+ "write:repo_hook": "Read, write, and ping repository hooks",
27
+ "read:repo_hook": "Read and ping repository hooks",
28
+ "admin:org": (
29
+ "Full control of the organisation, its teams, projects, and memberships"
30
+ ),
31
+ "write:org": "Read and write organisation membership and organisation projects",
32
+ "read:org": (
33
+ "Read organisation membership, organisation projects, and team membership"
34
+ ),
35
+ "admin:public_key": "Full control of public keys",
36
+ "write:public_key": "Create, list, and view public keys",
37
+ "read:public_key": "List and view public keys",
38
+ "admin:org_hook": (
39
+ "Read, write, ping, and delete organisation hooks created by this token"
40
+ ),
41
+ "gist": "Write gists",
42
+ "notifications": (
43
+ "Read notifications, mark threads read, watch or unwatch repositories, "
44
+ "and manage thread subscriptions"
45
+ ),
46
+ "user": "Read and write profile info. Includes `user:email` and `user:follow`",
47
+ "read:user": "Read profile data",
48
+ "user:email": "Read email addresses",
49
+ "user:follow": "Follow and unfollow users",
50
+ "project": "Read and write user and organisation projects",
51
+ "read:project": "Read user and organisation projects",
52
+ "delete_repo": "Delete repositories the token can administer",
53
+ "write:packages": "Upload and publish GitHub Packages",
54
+ "read:packages": "Download and install GitHub Packages",
55
+ "delete:packages": "Delete GitHub Packages",
56
+ "admin:gpg_key": "Full control of GPG keys",
57
+ "write:gpg_key": "Create, list, and view GPG keys",
58
+ "read:gpg_key": "List and view GPG keys",
59
+ "admin:ssh_signing_key": "Full control of SSH signing keys",
60
+ "write:ssh_signing_key": "Create, list, and view SSH signing keys",
61
+ "read:ssh_signing_key": "List and view SSH signing keys",
62
+ "codespace": "Create and manage codespaces",
63
+ "workflow": "Add and update GitHub Actions workflow files",
64
+ "read:audit_log": "Read audit log data",
65
+ "offline_access": "Request an expiring access token and a refresh token",
66
+ }
67
+
68
+
69
+ def scope_names(header: str | None) -> tuple[str, ...]:
70
+ """Split ``X-OAuth-Scopes`` in header order."""
71
+ if header is None:
72
+ return ("none",)
73
+ names = tuple(part.strip() for part in header.split(",") if part.strip())
74
+ if not names:
75
+ return ("none",)
76
+ return names
77
+
78
+
79
+ def allows_text(scope: str) -> str:
80
+ """Return the local sentence for ``scope``."""
81
+ if scope == "none":
82
+ return "GitHub sent no scopes."
83
+ return SCOPE_ALLOWS.get(scope, "No local description.")
84
+
85
+
86
+ def accepted_permission_rows(header: str | None) -> tuple[tuple[str, str], ...]:
87
+ """Split ``X-Accepted-GitHub-Permissions`` into permission and access."""
88
+ if header is None:
89
+ return (("none", "GitHub sent no accepted permissions."),)
90
+ rows: list[tuple[str, str]] = []
91
+ for part in header.replace(",", ";").split(";"):
92
+ name, separator, access = part.strip().partition("=")
93
+ if separator != "=" or name == "" or access.strip() == "":
94
+ continue
95
+ rows.append((name, access.strip()))
96
+ if not rows:
97
+ return (("none", "GitHub sent no accepted permissions."),)
98
+ return tuple(rows)
@@ -0,0 +1,206 @@
1
+ Metadata-Version: 2.4
2
+ Name: lupaxa-github-token-validator
3
+ Version: 0.1.0
4
+ Summary: Report what a GitHub token is allowed to do.
5
+ Author: The Lupaxa Project
6
+ Maintainer: The Lupaxa Project
7
+ License-Expression: MIT
8
+ Project-URL: Homepage, https://github.com/lupaxa-gh-toolbox/github-token-validator
9
+ Project-URL: Repository, https://github.com/lupaxa-gh-toolbox/github-token-validator
10
+ Project-URL: Issues, https://github.com/lupaxa-gh-toolbox/github-token-validator/issues
11
+ Keywords: github,permissions,token
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Natural Language :: English
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Operating System :: Microsoft :: Windows
18
+ Classifier: Operating System :: POSIX
19
+ Classifier: Operating System :: POSIX :: Linux
20
+ Classifier: Programming Language :: Python
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3 :: Only
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Programming Language :: Python :: 3.12
25
+ Classifier: Programming Language :: Python :: 3.13
26
+ Classifier: Programming Language :: Python :: 3.14
27
+ Classifier: Topic :: Software Development
28
+ Requires-Python: >=3.11
29
+ Description-Content-Type: text/markdown
30
+ License-File: LICENCE
31
+ Requires-Dist: colored<3.0.0,>=2.3.2
32
+ Requires-Dist: requests<3.0.0,>=2.32.0
33
+ Requires-Dist: rich<15.0.0,>=14.0.0
34
+ Provides-Extra: dev
35
+ Requires-Dist: build<2.0.0,>=1.2.2; extra == "dev"
36
+ Requires-Dist: mypy<2.0.0,>=1.17.0; extra == "dev"
37
+ Requires-Dist: pre-commit<5.0.0,>=4.2.0; extra == "dev"
38
+ Requires-Dist: pytest<9.0.0,>=8.4.0; extra == "dev"
39
+ Requires-Dist: pytest-cov<7.0.0,>=6.2.0; extra == "dev"
40
+ Requires-Dist: ruff<1.0.0,>=0.12.0; extra == "dev"
41
+ Requires-Dist: twine<7.0.0,>=6.1.0; extra == "dev"
42
+ Requires-Dist: types-setuptools<81.0.0,>=80.1.6; extra == "dev"
43
+ Provides-Extra: test
44
+ Requires-Dist: pytest>=8.0; extra == "test"
45
+ Requires-Dist: pytest-cov>=5.0; extra == "test"
46
+ Dynamic: license-file
47
+
48
+ <p align="center">
49
+ <a href="https://github.com/lupaxa-gh-toolbox">
50
+ <img src="https://raw.githubusercontent.com/the-lupaxa-project/brand-assets/master/logos/organisations/gh-toolbox/readme-logo.png" alt="Organisation Logo" />
51
+ </a>
52
+ </p>
53
+
54
+ <h1 align="center">GitHub Token Validator</h1>
55
+
56
+ **GitHub Token Validator** takes a GitHub token and reports what that token is allowed to do. It sends read-only `GET` requests. It does not list repositories, organisations, or other resources, and it never prints the token.
57
+
58
+ ## Installation
59
+
60
+ Requires Python 3.11 or newer.
61
+
62
+ ```bash
63
+ pip install lupaxa-github-token-validator
64
+ ```
65
+
66
+ Two commands are installed. They do the same work.
67
+
68
+ ```bash
69
+ gtv --version
70
+ github-token-validator --version
71
+ ```
72
+
73
+ The shorter `gtv` command is used in the examples below.
74
+
75
+ ## Token
76
+
77
+ `gtv` reads `GITHUB_TOKEN`. `--token` overrides that variable. A blank `--token` does not fall back to the environment. Surrounding whitespace is removed before the token is classified or sent.
78
+
79
+ ```bash
80
+ export GITHUB_TOKEN="your-token"
81
+ gtv
82
+ ```
83
+
84
+ ```bash
85
+ gtv --token "$OTHER_TOKEN"
86
+ ```
87
+
88
+ ## Which flags a token needs
89
+
90
+ Classic, OAuth, and user-to-server tokens return their scopes in the `X-OAuth-Scopes` header, so `--org` and `--repo` are optional.
91
+
92
+ Fine-grained, installation, and unknown tokens do not, so they need at least one of those flags before any request is sent.
93
+
94
+ A refresh token makes no request.
95
+
96
+ | Prefix | Kind | Needs a target |
97
+ | -------------- | ----------------------------------------- | -------------- |
98
+ | `ghp_` | Classic personal access token | No |
99
+ | `github_pat_` | Fine-grained personal access token | Yes |
100
+ | `gho_` | OAuth access token | No |
101
+ | `ghu_` | GitHub App user-to-server token | No |
102
+ | `ghs_` | GitHub App installation or Actions token | Yes |
103
+ | `ghr_` | Refresh token | No |
104
+ | anything else | Unknown | Yes |
105
+
106
+ Passing `--org` checks only that organisation. Passing `--repo` checks only that repository. Passing both checks both. The tool never looks up further targets.
107
+
108
+ `--org` is one name. Surrounding spaces are removed. A blank name, a space inside the name, or a `/` is rejected.
109
+
110
+ `--repo` is exactly `OWNER/NAME`: one slash, both sides non-empty, and no whitespace.
111
+
112
+ ## Examples
113
+
114
+ A classic, OAuth, or user-to-server token. The scope table is the permission report. Account checks still run.
115
+
116
+ ```bash
117
+ gtv
118
+ gtv --timeout 30
119
+ ```
120
+
121
+ The same token, plus one named target. Only the catalog you name is probed.
122
+
123
+ ```bash
124
+ gtv --org lupaxa-gh-toolbox
125
+ gtv --repo lupaxa-gh-toolbox/github-token-validator
126
+ gtv --org lupaxa-gh-toolbox --repo lupaxa-gh-toolbox/github-token-validator
127
+ ```
128
+
129
+ A fine-grained token, an installation or Actions token, or an unknown token. At least one target is required.
130
+
131
+ ```bash
132
+ gtv --repo lupaxa-gh-toolbox/github-token-validator
133
+ gtv --org lupaxa-gh-toolbox
134
+ ```
135
+
136
+ Omitting both flags exits 2 and prints a message such as `Fine-grained personal access token requires --org or --repo.` The token is not included in that message.
137
+
138
+ A refresh token (`ghr_`) prints a short summary and exits 2. It does not call the API.
139
+
140
+ ## Flags
141
+
142
+ | Flag | Meaning |
143
+ | ------------------- | ---------------------------------------------------------------------------------------------------- |
144
+ | `-h`, `--help` | Help, then exit 0 |
145
+ | `-V`, `--version` | Version, then exit 0 |
146
+ | `-t`, `--token` | Token. Overrides `GITHUB_TOKEN`. A blank value does not fall back to the environment |
147
+ | `-T`, `--timeout` | Seconds per request. Default 10. Must be an integer greater than 0 |
148
+ | `--org NAME` | One organisation. Required for fine-grained, installation, and unknown tokens unless `--repo` is set |
149
+ | `--repo OWNER/NAME` | One repository, as `OWNER/NAME`. Required for those same tokens unless `--org` is set |
150
+
151
+ ## Report
152
+
153
+ A completed run prints Rich tables.
154
+
155
+ **Summary.** Token kind, login from `GET /user`, the raw scope header, and the rate-limit limit, used count, remaining count, and reset time in UTC (`YYYY-MM-DD HH:MM:SS`).
156
+
157
+ **Scopes.** Classic, OAuth, and user-to-server tokens list each `X-OAuth-Scopes` value, in header order, with a short description. An unknown scope is `No local description.`
158
+
159
+ A missing or blank header is `none`, with the text `GitHub sent no scopes.`
160
+
161
+ Fine-grained tokens do not return OAuth scopes. That row says `Fine-grained tokens do not return OAuth scopes.`
162
+
163
+ Installation and Actions tokens list `X-Accepted-GitHub-Permissions` as a permission and its access, such as `read` or `write`. A missing header is `none`, with the text `GitHub sent no accepted permissions.`
164
+
165
+ **Account permissions.** Run for every API token except installation and Actions tokens, which are not user tokens:
166
+
167
+ - Profile, Emails, Following, Organisations, Org membership roles
168
+ - Gists, Notifications
169
+ - Public keys, GPG keys, SSH signing keys
170
+ - Packages, Repository list, Codespaces, Installations
171
+
172
+ **Target permissions.** Printed only when `--org` or `--repo` was passed.
173
+
174
+ Organisation checks: Organisation, Members, Hooks, Audit log, Actions permissions, Packages.
175
+
176
+ Repository checks: Repository, Contents, Hooks, Workflows, Deployments, Invitations, Code scanning, Commits.
177
+
178
+ List requests use `per_page=1`. Response bodies are discarded except the login on `GET /user`, so names, paths, messages, and other resource details are not printed.
179
+
180
+ | Result | Meaning |
181
+ | ----------------- | -------------------------------- |
182
+ | `granted` | HTTP 200 or 204 |
183
+ | `denied` | HTTP 403 |
184
+ | `not applicable` | HTTP 404 |
185
+ | `error N` | Any other HTTP status |
186
+
187
+ If GitHub rejects the token (HTTP 401), a rate limit stops the run, or a request times out or cannot connect, the run stops and exits 1.
188
+
189
+ A rate limit is HTTP 429, or HTTP 403 when `X-RateLimit-Remaining` is `0` or `Retry-After` is set. A 403 that is only a permission denial stays a `denied` row.
190
+
191
+ On the first request, only that error is printed. On a later request, the tables gathered so far are printed, then the error.
192
+
193
+ ## Exit codes
194
+
195
+ | Code | Meaning |
196
+ | ---- | -------------------------------------------------------------------------------------------------------------------- |
197
+ | 0 | The report completed, including rows that are denied, not applicable, or an HTTP error |
198
+ | 1 | The token was rejected, the rate limit stopped the run, or a request timed out or could not connect |
199
+ | 2 | Missing token, refresh token, missing target, invalid `--org`, `--repo`, or `--timeout`, or an unrecognized argument |
200
+ | 130 | Keyboard interrupt |
201
+
202
+ An unrecognized argument is rejected without printing the argument values.
203
+
204
+ <a href="https://github.com/the-lupaxa-project">
205
+ <img src="https://raw.githubusercontent.com/the-lupaxa-project/brand-assets/master/logos/components/footer-for-child-orgs.svg" alt="The Lupaxa Project Footer" width="100%" />
206
+ </a>
@@ -0,0 +1,15 @@
1
+ lupaxa/github_token_validator/__init__.py,sha256=Cql0GuByJlUx76_TeqM-aw3qZTgPAxfCClCTvEa3k4I,159
2
+ lupaxa/github_token_validator/__main__.py,sha256=9wK_yqxiq_VaeXw0_ofhcgdkbUTzzNqkEf0Gj2KAn0w,174
3
+ lupaxa/github_token_validator/classify.py,sha256=mh6yHEXYTVsvUkXx2nbHSyxnVxGxb_U32ZBLgZ5QMb0,1439
4
+ lupaxa/github_token_validator/cli.py,sha256=NpX9kFCswoLeUybJVkGPWISY-OhO4xKNZckczrsUxqs,4856
5
+ lupaxa/github_token_validator/client.py,sha256=9opMMpvnzyUZucBorwxfjzoOiyJSf-2x62zdm9AC2s4,4177
6
+ lupaxa/github_token_validator/constants.py,sha256=K3snHneZc5cw_U3N73zy7Q-g0l8KdZISuueVvDiPIOA,974
7
+ lupaxa/github_token_validator/probes.py,sha256=SRv5LWEjMJkupHYF4vvTlmf6VXXTRfba1M3LdefKcyM,14333
8
+ lupaxa/github_token_validator/report.py,sha256=zuPYdWDcLObr20N90UgSwRKMClFmUK8taGcdfUmLYaU,6686
9
+ lupaxa/github_token_validator/scopes.py,sha256=cVcdNtC_Vcb1rfjpBH7jTMSGHbxbER9NGS7K5Ko1KQM,4200
10
+ lupaxa_github_token_validator-0.1.0.dist-info/licenses/LICENCE,sha256=RXShyrlT7e281QX54JLm3YrhOlC-TJxObadJl1gH5Es,1070
11
+ lupaxa_github_token_validator-0.1.0.dist-info/METADATA,sha256=rjUA3TcGXnPhRx7Kl0mB53-99O_Lc_HnlaY6XzKyGAE,9819
12
+ lupaxa_github_token_validator-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
13
+ lupaxa_github_token_validator-0.1.0.dist-info/entry_points.txt,sha256=Hcg0m2AUnFQRvFT-pfKlXBwHOxAQ5y4f7MillSSOMB8,127
14
+ lupaxa_github_token_validator-0.1.0.dist-info/top_level.txt,sha256=enJZ4xGyTomypf_yq2pEZRa_69hF0ulc9sWjyYQV9I8,7
15
+ lupaxa_github_token_validator-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ github-token-validator = lupaxa.github_token_validator.cli:main
3
+ gtv = lupaxa.github_token_validator.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) The Lupaxa Project
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.