lupaxa-github-token-validator 0.1.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.
- lupaxa_github_token_validator-0.1.0/LICENCE +21 -0
- lupaxa_github_token_validator-0.1.0/PKG-INFO +206 -0
- lupaxa_github_token_validator-0.1.0/README.md +159 -0
- lupaxa_github_token_validator-0.1.0/pyproject.toml +292 -0
- lupaxa_github_token_validator-0.1.0/setup.cfg +4 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa/github_token_validator/__init__.py +7 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa/github_token_validator/__main__.py +8 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa/github_token_validator/classify.py +52 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa/github_token_validator/cli.py +150 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa/github_token_validator/client.py +131 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa/github_token_validator/constants.py +37 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa/github_token_validator/probes.py +478 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa/github_token_validator/report.py +221 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa/github_token_validator/scopes.py +98 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa_github_token_validator.egg-info/PKG-INFO +206 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa_github_token_validator.egg-info/SOURCES.txt +26 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa_github_token_validator.egg-info/dependency_links.txt +1 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa_github_token_validator.egg-info/entry_points.txt +3 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa_github_token_validator.egg-info/requires.txt +17 -0
- lupaxa_github_token_validator-0.1.0/src/lupaxa_github_token_validator.egg-info/top_level.txt +1 -0
- lupaxa_github_token_validator-0.1.0/tests/test_catalog.py +93 -0
- lupaxa_github_token_validator-0.1.0/tests/test_classify.py +50 -0
- lupaxa_github_token_validator-0.1.0/tests/test_cli.py +280 -0
- lupaxa_github_token_validator-0.1.0/tests/test_client.py +153 -0
- lupaxa_github_token_validator-0.1.0/tests/test_report.py +172 -0
- lupaxa_github_token_validator-0.1.0/tests/test_runner.py +227 -0
- lupaxa_github_token_validator-0.1.0/tests/test_scopes.py +104 -0
- lupaxa_github_token_validator-0.1.0/tests/test_version.py +24 -0
|
@@ -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.
|
|
@@ -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,159 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://github.com/lupaxa-gh-toolbox">
|
|
3
|
+
<img src="https://raw.githubusercontent.com/the-lupaxa-project/brand-assets/master/logos/organisations/gh-toolbox/readme-logo.png" alt="Organisation Logo" />
|
|
4
|
+
</a>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<h1 align="center">GitHub Token Validator</h1>
|
|
8
|
+
|
|
9
|
+
**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.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Requires Python 3.11 or newer.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install lupaxa-github-token-validator
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Two commands are installed. They do the same work.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gtv --version
|
|
23
|
+
github-token-validator --version
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The shorter `gtv` command is used in the examples below.
|
|
27
|
+
|
|
28
|
+
## Token
|
|
29
|
+
|
|
30
|
+
`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.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
export GITHUB_TOKEN="your-token"
|
|
34
|
+
gtv
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
gtv --token "$OTHER_TOKEN"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Which flags a token needs
|
|
42
|
+
|
|
43
|
+
Classic, OAuth, and user-to-server tokens return their scopes in the `X-OAuth-Scopes` header, so `--org` and `--repo` are optional.
|
|
44
|
+
|
|
45
|
+
Fine-grained, installation, and unknown tokens do not, so they need at least one of those flags before any request is sent.
|
|
46
|
+
|
|
47
|
+
A refresh token makes no request.
|
|
48
|
+
|
|
49
|
+
| Prefix | Kind | Needs a target |
|
|
50
|
+
| -------------- | ----------------------------------------- | -------------- |
|
|
51
|
+
| `ghp_` | Classic personal access token | No |
|
|
52
|
+
| `github_pat_` | Fine-grained personal access token | Yes |
|
|
53
|
+
| `gho_` | OAuth access token | No |
|
|
54
|
+
| `ghu_` | GitHub App user-to-server token | No |
|
|
55
|
+
| `ghs_` | GitHub App installation or Actions token | Yes |
|
|
56
|
+
| `ghr_` | Refresh token | No |
|
|
57
|
+
| anything else | Unknown | Yes |
|
|
58
|
+
|
|
59
|
+
Passing `--org` checks only that organisation. Passing `--repo` checks only that repository. Passing both checks both. The tool never looks up further targets.
|
|
60
|
+
|
|
61
|
+
`--org` is one name. Surrounding spaces are removed. A blank name, a space inside the name, or a `/` is rejected.
|
|
62
|
+
|
|
63
|
+
`--repo` is exactly `OWNER/NAME`: one slash, both sides non-empty, and no whitespace.
|
|
64
|
+
|
|
65
|
+
## Examples
|
|
66
|
+
|
|
67
|
+
A classic, OAuth, or user-to-server token. The scope table is the permission report. Account checks still run.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
gtv
|
|
71
|
+
gtv --timeout 30
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
The same token, plus one named target. Only the catalog you name is probed.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
gtv --org lupaxa-gh-toolbox
|
|
78
|
+
gtv --repo lupaxa-gh-toolbox/github-token-validator
|
|
79
|
+
gtv --org lupaxa-gh-toolbox --repo lupaxa-gh-toolbox/github-token-validator
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
A fine-grained token, an installation or Actions token, or an unknown token. At least one target is required.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
gtv --repo lupaxa-gh-toolbox/github-token-validator
|
|
86
|
+
gtv --org lupaxa-gh-toolbox
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
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.
|
|
90
|
+
|
|
91
|
+
A refresh token (`ghr_`) prints a short summary and exits 2. It does not call the API.
|
|
92
|
+
|
|
93
|
+
## Flags
|
|
94
|
+
|
|
95
|
+
| Flag | Meaning |
|
|
96
|
+
| ------------------- | ---------------------------------------------------------------------------------------------------- |
|
|
97
|
+
| `-h`, `--help` | Help, then exit 0 |
|
|
98
|
+
| `-V`, `--version` | Version, then exit 0 |
|
|
99
|
+
| `-t`, `--token` | Token. Overrides `GITHUB_TOKEN`. A blank value does not fall back to the environment |
|
|
100
|
+
| `-T`, `--timeout` | Seconds per request. Default 10. Must be an integer greater than 0 |
|
|
101
|
+
| `--org NAME` | One organisation. Required for fine-grained, installation, and unknown tokens unless `--repo` is set |
|
|
102
|
+
| `--repo OWNER/NAME` | One repository, as `OWNER/NAME`. Required for those same tokens unless `--org` is set |
|
|
103
|
+
|
|
104
|
+
## Report
|
|
105
|
+
|
|
106
|
+
A completed run prints Rich tables.
|
|
107
|
+
|
|
108
|
+
**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`).
|
|
109
|
+
|
|
110
|
+
**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.`
|
|
111
|
+
|
|
112
|
+
A missing or blank header is `none`, with the text `GitHub sent no scopes.`
|
|
113
|
+
|
|
114
|
+
Fine-grained tokens do not return OAuth scopes. That row says `Fine-grained tokens do not return OAuth scopes.`
|
|
115
|
+
|
|
116
|
+
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.`
|
|
117
|
+
|
|
118
|
+
**Account permissions.** Run for every API token except installation and Actions tokens, which are not user tokens:
|
|
119
|
+
|
|
120
|
+
- Profile, Emails, Following, Organisations, Org membership roles
|
|
121
|
+
- Gists, Notifications
|
|
122
|
+
- Public keys, GPG keys, SSH signing keys
|
|
123
|
+
- Packages, Repository list, Codespaces, Installations
|
|
124
|
+
|
|
125
|
+
**Target permissions.** Printed only when `--org` or `--repo` was passed.
|
|
126
|
+
|
|
127
|
+
Organisation checks: Organisation, Members, Hooks, Audit log, Actions permissions, Packages.
|
|
128
|
+
|
|
129
|
+
Repository checks: Repository, Contents, Hooks, Workflows, Deployments, Invitations, Code scanning, Commits.
|
|
130
|
+
|
|
131
|
+
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.
|
|
132
|
+
|
|
133
|
+
| Result | Meaning |
|
|
134
|
+
| ----------------- | -------------------------------- |
|
|
135
|
+
| `granted` | HTTP 200 or 204 |
|
|
136
|
+
| `denied` | HTTP 403 |
|
|
137
|
+
| `not applicable` | HTTP 404 |
|
|
138
|
+
| `error N` | Any other HTTP status |
|
|
139
|
+
|
|
140
|
+
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.
|
|
141
|
+
|
|
142
|
+
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.
|
|
143
|
+
|
|
144
|
+
On the first request, only that error is printed. On a later request, the tables gathered so far are printed, then the error.
|
|
145
|
+
|
|
146
|
+
## Exit codes
|
|
147
|
+
|
|
148
|
+
| Code | Meaning |
|
|
149
|
+
| ---- | -------------------------------------------------------------------------------------------------------------------- |
|
|
150
|
+
| 0 | The report completed, including rows that are denied, not applicable, or an HTTP error |
|
|
151
|
+
| 1 | The token was rejected, the rate limit stopped the run, or a request timed out or could not connect |
|
|
152
|
+
| 2 | Missing token, refresh token, missing target, invalid `--org`, `--repo`, or `--timeout`, or an unrecognized argument |
|
|
153
|
+
| 130 | Keyboard interrupt |
|
|
154
|
+
|
|
155
|
+
An unrecognized argument is rejected without printing the argument values.
|
|
156
|
+
|
|
157
|
+
<a href="https://github.com/the-lupaxa-project">
|
|
158
|
+
<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%" />
|
|
159
|
+
</a>
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"setuptools>=77.0.0",
|
|
4
|
+
]
|
|
5
|
+
build-backend = "setuptools.build_meta"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "lupaxa-github-token-validator"
|
|
9
|
+
version = "0.1.0"
|
|
10
|
+
description = "Report what a GitHub token is allowed to do."
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = [
|
|
15
|
+
"LICENCE",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
authors = [
|
|
19
|
+
{ name = "The Lupaxa Project" },
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
maintainers = [
|
|
23
|
+
{ name = "The Lupaxa Project" },
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
keywords = [
|
|
27
|
+
"github",
|
|
28
|
+
"permissions",
|
|
29
|
+
"token",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
classifiers = [
|
|
33
|
+
"Development Status :: 3 - Alpha",
|
|
34
|
+
"Environment :: Console",
|
|
35
|
+
"Intended Audience :: Developers",
|
|
36
|
+
"Natural Language :: English",
|
|
37
|
+
"Operating System :: MacOS",
|
|
38
|
+
"Operating System :: Microsoft :: Windows",
|
|
39
|
+
"Operating System :: POSIX",
|
|
40
|
+
"Operating System :: POSIX :: Linux",
|
|
41
|
+
"Programming Language :: Python",
|
|
42
|
+
"Programming Language :: Python :: 3",
|
|
43
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
44
|
+
"Programming Language :: Python :: 3.11",
|
|
45
|
+
"Programming Language :: Python :: 3.12",
|
|
46
|
+
"Programming Language :: Python :: 3.13",
|
|
47
|
+
"Programming Language :: Python :: 3.14",
|
|
48
|
+
"Topic :: Software Development",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
dependencies = [
|
|
52
|
+
"colored>=2.3.2,<3.0.0",
|
|
53
|
+
"requests>=2.32.0,<3.0.0",
|
|
54
|
+
"rich>=14.0.0,<15.0.0",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[project.optional-dependencies]
|
|
58
|
+
dev = [
|
|
59
|
+
"build>=1.2.2,<2.0.0",
|
|
60
|
+
"mypy>=1.17.0,<2.0.0",
|
|
61
|
+
"pre-commit>=4.2.0,<5.0.0",
|
|
62
|
+
"pytest>=8.4.0,<9.0.0",
|
|
63
|
+
"pytest-cov>=6.2.0,<7.0.0",
|
|
64
|
+
"ruff>=0.12.0,<1.0.0",
|
|
65
|
+
"twine>=6.1.0,<7.0.0",
|
|
66
|
+
"types-setuptools>=80.1.6,<81.0.0",
|
|
67
|
+
]
|
|
68
|
+
test = [
|
|
69
|
+
"pytest>=8.0",
|
|
70
|
+
"pytest-cov>=5.0",
|
|
71
|
+
]
|
|
72
|
+
|
|
73
|
+
[project.urls]
|
|
74
|
+
Homepage = "https://github.com/lupaxa-gh-toolbox/github-token-validator"
|
|
75
|
+
Repository = "https://github.com/lupaxa-gh-toolbox/github-token-validator"
|
|
76
|
+
Issues = "https://github.com/lupaxa-gh-toolbox/github-token-validator/issues"
|
|
77
|
+
|
|
78
|
+
[project.scripts]
|
|
79
|
+
github-token-validator = "lupaxa.github_token_validator.cli:main"
|
|
80
|
+
gtv = "lupaxa.github_token_validator.cli:main"
|
|
81
|
+
|
|
82
|
+
[tool.setuptools]
|
|
83
|
+
package-dir = { "" = "src" }
|
|
84
|
+
|
|
85
|
+
[tool.setuptools.packages.find]
|
|
86
|
+
where = [
|
|
87
|
+
"src",
|
|
88
|
+
]
|
|
89
|
+
include = [
|
|
90
|
+
"lupaxa.github_token_validator*",
|
|
91
|
+
]
|
|
92
|
+
namespaces = true
|
|
93
|
+
|
|
94
|
+
[tool.ruff]
|
|
95
|
+
target-version = "py311"
|
|
96
|
+
line-length = 88
|
|
97
|
+
src = [
|
|
98
|
+
"src",
|
|
99
|
+
"tests",
|
|
100
|
+
]
|
|
101
|
+
extend-exclude = [
|
|
102
|
+
".mypy_cache",
|
|
103
|
+
".pytest_cache",
|
|
104
|
+
".tox",
|
|
105
|
+
".venv",
|
|
106
|
+
"build",
|
|
107
|
+
"dist",
|
|
108
|
+
]
|
|
109
|
+
|
|
110
|
+
[tool.ruff.lint]
|
|
111
|
+
# Core Lupaxa rule set
|
|
112
|
+
select = [
|
|
113
|
+
# --- Essential correctness + hygiene ---
|
|
114
|
+
"E", # pycodestyle: basic style errors
|
|
115
|
+
"F", # pyflakes: unused vars, undefined names
|
|
116
|
+
"I", # import sorting
|
|
117
|
+
"B", # bugbear: common footguns
|
|
118
|
+
|
|
119
|
+
# --- Security layer ---
|
|
120
|
+
"S", # bandit-like security checks
|
|
121
|
+
|
|
122
|
+
# --- Modernisation + cleanup ---
|
|
123
|
+
"UP", # pyupgrade: modern Python idioms
|
|
124
|
+
"SIM", # simplify complex expressions/conditions
|
|
125
|
+
"C4", # flake8-comprehensions
|
|
126
|
+
"ISC", # implicit string-concat safety
|
|
127
|
+
|
|
128
|
+
# --- New additions (strict mode) ---
|
|
129
|
+
"D", # pydocstyle: docstring formatting/consistency
|
|
130
|
+
"ANN", # annotations: missing/incorrect typing hints
|
|
131
|
+
"PT", # pytest: best practices for tests
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
# Disable rules that conflict with the Lupaxa documentation style
|
|
135
|
+
# or with Ruff's formatter.
|
|
136
|
+
ignore = [
|
|
137
|
+
"D200", # One-line docstring should fit on one line
|
|
138
|
+
"D201", # No blank lines allowed before function docstring
|
|
139
|
+
"D202", # No blank lines allowed after function docstring
|
|
140
|
+
"D203", # One blank line required before class docstring
|
|
141
|
+
"D204", # One blank line required after class docstring
|
|
142
|
+
"D205", # Blank line required between summary and description
|
|
143
|
+
"D206", # Docstring indentation
|
|
144
|
+
"D207", # Docstring under-indentation
|
|
145
|
+
"D208", # Docstring over-indentation
|
|
146
|
+
"D209", # Multi-line docstring closing quotes placement
|
|
147
|
+
"D210", # No surrounding whitespace allowed
|
|
148
|
+
"D211", # No blank line allowed before class docstring
|
|
149
|
+
"D212", # Summary must start on the first physical line
|
|
150
|
+
"ISC001", # Conflicts with Ruff formatting
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
[tool.ruff.lint.per-file-ignores]
|
|
154
|
+
"tests/**/*.py" = [
|
|
155
|
+
"ANN",
|
|
156
|
+
"D",
|
|
157
|
+
"S101",
|
|
158
|
+
"S105",
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
[tool.ruff.lint.flake8-annotations]
|
|
162
|
+
allow-star-arg-any = false
|
|
163
|
+
ignore-fully-untyped = false
|
|
164
|
+
mypy-init-return = true
|
|
165
|
+
suppress-dummy-args = false
|
|
166
|
+
suppress-none-returning = false
|
|
167
|
+
|
|
168
|
+
[tool.ruff.lint.flake8-builtins]
|
|
169
|
+
builtins-ignorelist = [
|
|
170
|
+
"id",
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
[tool.ruff.lint.flake8-pytest-style]
|
|
174
|
+
fixture-parentheses = false
|
|
175
|
+
mark-parentheses = false
|
|
176
|
+
parametrize-names-type = "tuple"
|
|
177
|
+
parametrize-values-row-type = "tuple"
|
|
178
|
+
parametrize-values-type = "list"
|
|
179
|
+
|
|
180
|
+
[tool.ruff.lint.flake8-quotes]
|
|
181
|
+
docstring-quotes = "double"
|
|
182
|
+
inline-quotes = "double"
|
|
183
|
+
multiline-quotes = "double"
|
|
184
|
+
|
|
185
|
+
[tool.ruff.lint.flake8-tidy-imports]
|
|
186
|
+
ban-relative-imports = "parents"
|
|
187
|
+
|
|
188
|
+
[tool.ruff.lint.isort]
|
|
189
|
+
combine-as-imports = true
|
|
190
|
+
force-sort-within-sections = true
|
|
191
|
+
known-first-party = [
|
|
192
|
+
"lupaxa",
|
|
193
|
+
]
|
|
194
|
+
split-on-trailing-comma = true
|
|
195
|
+
|
|
196
|
+
[tool.ruff.lint.mccabe]
|
|
197
|
+
max-complexity = 10
|
|
198
|
+
|
|
199
|
+
[tool.ruff.lint.pep8-naming]
|
|
200
|
+
classmethod-decorators = [
|
|
201
|
+
"classmethod",
|
|
202
|
+
]
|
|
203
|
+
staticmethod-decorators = [
|
|
204
|
+
"staticmethod",
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
[tool.ruff.lint.pylint]
|
|
208
|
+
allow-dunder-method-names = []
|
|
209
|
+
max-args = 10
|
|
210
|
+
max-branches = 12
|
|
211
|
+
max-returns = 6
|
|
212
|
+
max-statements = 50
|
|
213
|
+
|
|
214
|
+
[tool.ruff.lint.pyupgrade]
|
|
215
|
+
keep-runtime-typing = false
|
|
216
|
+
|
|
217
|
+
[tool.ruff.format]
|
|
218
|
+
docstring-code-format = true
|
|
219
|
+
docstring-code-line-length = 88
|
|
220
|
+
indent-style = "space"
|
|
221
|
+
line-ending = "lf"
|
|
222
|
+
quote-style = "double"
|
|
223
|
+
skip-magic-trailing-comma = false
|
|
224
|
+
|
|
225
|
+
[tool.mypy]
|
|
226
|
+
python_version = "3.11"
|
|
227
|
+
files = [ "src", "tests" ]
|
|
228
|
+
mypy_path = "src"
|
|
229
|
+
explicit_package_bases = true
|
|
230
|
+
packages = [ "lupaxa.github_token_validator" ]
|
|
231
|
+
strict = true
|
|
232
|
+
pretty = true
|
|
233
|
+
show_column_numbers = true
|
|
234
|
+
show_error_codes = true
|
|
235
|
+
show_error_context = true
|
|
236
|
+
show_traceback = true
|
|
237
|
+
warn_unreachable = true
|
|
238
|
+
warn_unused_configs = true
|
|
239
|
+
exclude = [ "^build/", "^dist/" ]
|
|
240
|
+
|
|
241
|
+
[[tool.mypy.overrides]]
|
|
242
|
+
module = "colored"
|
|
243
|
+
ignore_missing_imports = true
|
|
244
|
+
|
|
245
|
+
[tool.pytest.ini_options]
|
|
246
|
+
minversion = "8.0"
|
|
247
|
+
addopts = [
|
|
248
|
+
"--strict-config",
|
|
249
|
+
"--strict-markers",
|
|
250
|
+
"--showlocals",
|
|
251
|
+
"--tb=short",
|
|
252
|
+
]
|
|
253
|
+
testpaths = [
|
|
254
|
+
"tests",
|
|
255
|
+
]
|
|
256
|
+
pythonpath = [
|
|
257
|
+
"src",
|
|
258
|
+
]
|
|
259
|
+
xfail_strict = true
|
|
260
|
+
filterwarnings = [
|
|
261
|
+
"error",
|
|
262
|
+
]
|
|
263
|
+
|
|
264
|
+
[tool.coverage.run]
|
|
265
|
+
branch = true
|
|
266
|
+
parallel = true
|
|
267
|
+
source = [ "lupaxa.github_token_validator" ]
|
|
268
|
+
|
|
269
|
+
[tool.coverage.paths]
|
|
270
|
+
source = [
|
|
271
|
+
"src/lupaxa/github_token_validator",
|
|
272
|
+
"*/site-packages/lupaxa/github_token_validator",
|
|
273
|
+
]
|
|
274
|
+
|
|
275
|
+
[tool.coverage.report]
|
|
276
|
+
exclude_also = [
|
|
277
|
+
"if TYPE_CHECKING:",
|
|
278
|
+
"if __name__ == \"__main__\":",
|
|
279
|
+
"raise AssertionError",
|
|
280
|
+
"raise NotImplementedError",
|
|
281
|
+
]
|
|
282
|
+
fail_under = 90
|
|
283
|
+
precision = 2
|
|
284
|
+
show_missing = true
|
|
285
|
+
skip_covered = false
|
|
286
|
+
skip_empty = true
|
|
287
|
+
|
|
288
|
+
[tool.coverage.html]
|
|
289
|
+
directory = "coverage/html"
|
|
290
|
+
|
|
291
|
+
[tool.coverage.xml]
|
|
292
|
+
output = "coverage/coverage.xml"
|