versionsec 0.7.1__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.
- versionsec-0.7.1/LICENSE +184 -0
- versionsec-0.7.1/PKG-INFO +142 -0
- versionsec-0.7.1/README.md +108 -0
- versionsec-0.7.1/forgeguard/__init__.py +78 -0
- versionsec-0.7.1/pyproject.toml +59 -0
- versionsec-0.7.1/setup.cfg +4 -0
- versionsec-0.7.1/tests/test_audit_reconcile.py +41 -0
- versionsec-0.7.1/tests/test_closure.py +164 -0
- versionsec-0.7.1/tests/test_config_contract.py +210 -0
- versionsec-0.7.1/tests/test_lab_cleanup.py +68 -0
- versionsec-0.7.1/tests/test_lab_contract.py +285 -0
- versionsec-0.7.1/tests/test_migration_0_7.py +286 -0
- versionsec-0.7.1/tests/test_multiforge.py +629 -0
- versionsec-0.7.1/tests/test_release_contract.py +139 -0
- versionsec-0.7.1/tests/test_release_workflow.py +310 -0
- versionsec-0.7.1/tests/test_runner_review.py +324 -0
- versionsec-0.7.1/tests/test_sources.py +88 -0
- versionsec-0.7.1/tests/test_versionsec.py +1229 -0
- versionsec-0.7.1/versionsec/__init__.py +9 -0
- versionsec-0.7.1/versionsec/__main__.py +4 -0
- versionsec-0.7.1/versionsec/advisories/__init__.py +1 -0
- versionsec-0.7.1/versionsec/advisories/catalog/forgejo.json +38 -0
- versionsec-0.7.1/versionsec/advisories/catalog/gitea.json +68 -0
- versionsec-0.7.1/versionsec/advisories/evaluator.py +83 -0
- versionsec-0.7.1/versionsec/advisories/models.py +53 -0
- versionsec-0.7.1/versionsec/assessment.py +66 -0
- versionsec-0.7.1/versionsec/checks.py +469 -0
- versionsec-0.7.1/versionsec/cli.py +375 -0
- versionsec-0.7.1/versionsec/client.py +160 -0
- versionsec-0.7.1/versionsec/config_review.py +157 -0
- versionsec-0.7.1/versionsec/engine.py +333 -0
- versionsec-0.7.1/versionsec/exporters/__init__.py +1 -0
- versionsec-0.7.1/versionsec/exporters/json.py +12 -0
- versionsec-0.7.1/versionsec/exporters/markdown.py +40 -0
- versionsec-0.7.1/versionsec/exporters/sarif.py +68 -0
- versionsec-0.7.1/versionsec/models.py +79 -0
- versionsec-0.7.1/versionsec/output.py +72 -0
- versionsec-0.7.1/versionsec/policy.py +25 -0
- versionsec-0.7.1/versionsec/providers/__init__.py +1 -0
- versionsec-0.7.1/versionsec/providers/base.py +92 -0
- versionsec-0.7.1/versionsec/providers/forgejo.py +29 -0
- versionsec-0.7.1/versionsec/providers/gitea.py +21 -0
- versionsec-0.7.1/versionsec/providers/registry.py +14 -0
- versionsec-0.7.1/versionsec/py.typed +1 -0
- versionsec-0.7.1/versionsec/report.py +214 -0
- versionsec-0.7.1/versionsec/runner_review.py +440 -0
- versionsec-0.7.1/versionsec/safety.py +15 -0
- versionsec-0.7.1/versionsec/schemas/OASIS_NOTICE.md +79 -0
- versionsec-0.7.1/versionsec/schemas/assessment-v1.json +493 -0
- versionsec-0.7.1/versionsec/schemas/config-snapshot-v1.json +230 -0
- versionsec-0.7.1/versionsec/schemas/runner-snapshot-v1.json +155 -0
- versionsec-0.7.1/versionsec/schemas/sarif-2.1.0.json +3389 -0
- versionsec-0.7.1/versionsec/scoring.py +96 -0
- versionsec-0.7.1/versionsec/urls.py +54 -0
- versionsec-0.7.1/versionsec/version.py +14 -0
- versionsec-0.7.1/versionsec.egg-info/PKG-INFO +142 -0
- versionsec-0.7.1/versionsec.egg-info/SOURCES.txt +59 -0
- versionsec-0.7.1/versionsec.egg-info/dependency_links.txt +1 -0
- versionsec-0.7.1/versionsec.egg-info/entry_points.txt +3 -0
- versionsec-0.7.1/versionsec.egg-info/requires.txt +12 -0
- versionsec-0.7.1/versionsec.egg-info/top_level.txt +2 -0
versionsec-0.7.1/LICENSE
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
Copyright 2026 Gexiro Global Enterprises Ltd.
|
|
6
|
+
|
|
7
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
8
|
+
|
|
9
|
+
1. Definitions.
|
|
10
|
+
|
|
11
|
+
"License" shall mean the terms and conditions for use, reproduction, and
|
|
12
|
+
distribution as defined by Sections 1 through 9 of this document.
|
|
13
|
+
|
|
14
|
+
"Licensor" shall mean the copyright owner or entity authorized by the copyright
|
|
15
|
+
owner that is granting the License.
|
|
16
|
+
|
|
17
|
+
"Legal Entity" shall mean the union of the acting entity and all other entities
|
|
18
|
+
that control, are controlled by, or are under common control with that entity.
|
|
19
|
+
For the purposes of this definition, "control" means (i) the power, direct or
|
|
20
|
+
indirect, to cause the direction or management of such entity, whether by
|
|
21
|
+
contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity exercising
|
|
25
|
+
permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications, including
|
|
28
|
+
but not limited to software source code, documentation source, and configuration
|
|
29
|
+
files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical transformation or
|
|
32
|
+
translation of a Source form, including but not limited to compiled object code,
|
|
33
|
+
generated documentation, and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or Object form,
|
|
36
|
+
made available under the License, as indicated by a copyright notice that is
|
|
37
|
+
included in or attached to the work.
|
|
38
|
+
|
|
39
|
+
"Derivative Works" shall mean any work, whether in Source or Object form, that
|
|
40
|
+
is based on (or derived from) the Work and for which the editorial revisions,
|
|
41
|
+
annotations, elaborations, or other modifications represent, as a whole, an
|
|
42
|
+
original work of authorship. For the purposes of this License, Derivative Works
|
|
43
|
+
shall not include works that remain separable from, or merely link (or bind by
|
|
44
|
+
name) to the interfaces of, the Work and Derivative Works thereof.
|
|
45
|
+
|
|
46
|
+
"Contribution" shall mean any work of authorship, including the original version
|
|
47
|
+
of the Work and any modifications or additions to that Work or Derivative Works
|
|
48
|
+
thereof, that is intentionally submitted to Licensor for inclusion in the Work
|
|
49
|
+
by the copyright owner or by an individual or Legal Entity authorized to submit
|
|
50
|
+
on behalf of the copyright owner. For the purposes of this definition,
|
|
51
|
+
"submitted" means any form of electronic, verbal, or written communication sent
|
|
52
|
+
to the Licensor or its representatives, including but not limited to
|
|
53
|
+
communication on electronic mailing lists, source code control systems, and
|
|
54
|
+
issue tracking systems that are managed by, or on behalf of, the Licensor for
|
|
55
|
+
the purpose of discussing and improving the Work, but excluding communication
|
|
56
|
+
that is conspicuously marked or otherwise designated in writing by the copyright
|
|
57
|
+
owner as "Not a Contribution."
|
|
58
|
+
|
|
59
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
|
|
60
|
+
of whom a Contribution has been received by Licensor and subsequently
|
|
61
|
+
incorporated within the Work.
|
|
62
|
+
|
|
63
|
+
2. Grant of Copyright License. Subject to the terms and conditions of this
|
|
64
|
+
License, each Contributor hereby grants to You a perpetual, worldwide,
|
|
65
|
+
non-exclusive, no-charge, royalty-free, irrevocable copyright license to
|
|
66
|
+
reproduce, prepare Derivative Works of, publicly display, publicly perform,
|
|
67
|
+
sublicense, and distribute the Work and such Derivative Works in Source or
|
|
68
|
+
Object form.
|
|
69
|
+
|
|
70
|
+
3. Grant of Patent License. Subject to the terms and conditions of this License,
|
|
71
|
+
each Contributor hereby grants to You a perpetual, worldwide, non-exclusive,
|
|
72
|
+
no-charge, royalty-free, irrevocable (except as stated in this section) patent
|
|
73
|
+
license to make, have made, use, offer to sell, sell, import, and otherwise
|
|
74
|
+
transfer the Work, where such license applies only to those patent claims
|
|
75
|
+
licensable by such Contributor that are necessarily infringed by their
|
|
76
|
+
Contribution(s) alone or by combination of their Contribution(s) with the Work
|
|
77
|
+
to which such Contribution(s) was submitted. If You institute patent litigation
|
|
78
|
+
against any entity (including a cross-claim or counterclaim in a lawsuit)
|
|
79
|
+
alleging that the Work or a Contribution incorporated within the Work
|
|
80
|
+
constitutes direct or contributory patent infringement, then any patent licenses
|
|
81
|
+
granted to You under this License for that Work shall terminate as of the date
|
|
82
|
+
such litigation is filed.
|
|
83
|
+
|
|
84
|
+
4. Redistribution. You may reproduce and distribute copies of the Work or
|
|
85
|
+
Derivative Works thereof in any medium, with or without modifications, and in
|
|
86
|
+
Source or Object form, provided that You meet the following conditions:
|
|
87
|
+
|
|
88
|
+
(a) You must give any other recipients of the Work or Derivative Works a copy of
|
|
89
|
+
this License; and
|
|
90
|
+
|
|
91
|
+
(b) You must cause any modified files to carry prominent notices stating that
|
|
92
|
+
You changed the files; and
|
|
93
|
+
|
|
94
|
+
(c) You must retain, in the Source form of any Derivative Works that You
|
|
95
|
+
distribute, all copyright, patent, trademark, and attribution notices from the
|
|
96
|
+
Source form of the Work, excluding those notices that do not pertain to any part
|
|
97
|
+
of the Derivative Works; and
|
|
98
|
+
|
|
99
|
+
(d) If the Work includes a "NOTICE" text file as part of its distribution, then
|
|
100
|
+
any Derivative Works that You distribute must include a readable copy of the
|
|
101
|
+
attribution notices contained within such NOTICE file, excluding those notices
|
|
102
|
+
that do not pertain to any part of the Derivative Works, in at least one of the
|
|
103
|
+
following places: within a NOTICE text file distributed as part of the
|
|
104
|
+
Derivative Works; within the Source form or documentation, if provided along
|
|
105
|
+
with the Derivative Works; or, within a display generated by the Derivative
|
|
106
|
+
Works, if and wherever such third-party notices normally appear. The contents of
|
|
107
|
+
the NOTICE file are for informational purposes only and do not modify the
|
|
108
|
+
License. You may add Your own attribution notices within Derivative Works that
|
|
109
|
+
You distribute, alongside or as an addendum to the NOTICE text from the Work,
|
|
110
|
+
provided that such additional attribution notices cannot be construed as
|
|
111
|
+
modifying the License.
|
|
112
|
+
|
|
113
|
+
You may add Your own copyright statement to Your modifications and may provide
|
|
114
|
+
additional or different license terms and conditions for use, reproduction, or
|
|
115
|
+
distribution of Your modifications, or for any such Derivative Works as a whole,
|
|
116
|
+
provided Your use, reproduction, and distribution of the Work otherwise complies
|
|
117
|
+
with the conditions stated in this License.
|
|
118
|
+
|
|
119
|
+
5. Submission of Contributions. Unless You explicitly state otherwise, any
|
|
120
|
+
Contribution intentionally submitted for inclusion in the Work by You to the
|
|
121
|
+
Licensor shall be under the terms and conditions of this License, without any
|
|
122
|
+
additional terms or conditions. Notwithstanding the above, nothing herein shall
|
|
123
|
+
supersede or modify the terms of any separate license agreement you may have
|
|
124
|
+
executed with Licensor regarding such Contributions.
|
|
125
|
+
|
|
126
|
+
6. Trademarks. This License does not grant permission to use the trade names,
|
|
127
|
+
trademarks, service marks, or product names of the Licensor, except as required
|
|
128
|
+
for reasonable and customary use in describing the origin of the Work and
|
|
129
|
+
reproducing the content of the NOTICE file.
|
|
130
|
+
|
|
131
|
+
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in
|
|
132
|
+
writing, Licensor provides the Work (and each Contributor provides its
|
|
133
|
+
Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
134
|
+
KIND, either express or implied, including, without limitation, any warranties or
|
|
135
|
+
conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
136
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
137
|
+
appropriateness of using or redistributing the Work and assume any risks
|
|
138
|
+
associated with Your exercise of permissions under this License.
|
|
139
|
+
|
|
140
|
+
8. Limitation of Liability. In no event and under no legal theory, whether in
|
|
141
|
+
tort (including negligence), contract, or otherwise, unless required by
|
|
142
|
+
applicable law (such as deliberate and grossly negligent acts) or agreed to in
|
|
143
|
+
writing, shall any Contributor be liable to You for damages, including any
|
|
144
|
+
direct, indirect, special, incidental, or consequential damages of any character
|
|
145
|
+
arising as a result of this License or out of the use or inability to use the
|
|
146
|
+
Work (including but not limited to damages for loss of goodwill, work stoppage,
|
|
147
|
+
computer failure or malfunction, or any and all other commercial damages or
|
|
148
|
+
losses), even if such Contributor has been advised of the possibility of such
|
|
149
|
+
damages.
|
|
150
|
+
|
|
151
|
+
9. Accepting Warranty or Additional Liability. While redistributing the Work or
|
|
152
|
+
Derivative Works thereof, You may choose to offer, and charge a fee for,
|
|
153
|
+
acceptance of support, warranty, indemnity, or other liability obligations
|
|
154
|
+
and/or rights consistent with this License. However, in accepting such
|
|
155
|
+
obligations, You may act only on Your own behalf and on Your sole
|
|
156
|
+
responsibility, not on behalf of any other Contributor, and only if You agree to
|
|
157
|
+
indemnify, defend, and hold each Contributor harmless for any liability incurred
|
|
158
|
+
by, or claims asserted against, such Contributor by reason of your accepting any
|
|
159
|
+
such warranty or additional liability.
|
|
160
|
+
|
|
161
|
+
END OF TERMS AND CONDITIONS
|
|
162
|
+
|
|
163
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
164
|
+
|
|
165
|
+
To apply the Apache License to your work, attach the following boilerplate
|
|
166
|
+
notice, with the fields enclosed by brackets "[]" replaced with your own
|
|
167
|
+
identifying information. Do not include the brackets. The text should be
|
|
168
|
+
enclosed in the appropriate comment syntax for the file format. We also
|
|
169
|
+
recommend that a file or class name and description of purpose be included on
|
|
170
|
+
the same "printed page" as the copyright notice for easier identification within
|
|
171
|
+
third-party archives.
|
|
172
|
+
|
|
173
|
+
Copyright 2026 Gexiro Global Enterprises Ltd.
|
|
174
|
+
|
|
175
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
|
176
|
+
this file except in compliance with the License. You may obtain a copy of the
|
|
177
|
+
License at
|
|
178
|
+
|
|
179
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
180
|
+
|
|
181
|
+
Unless required by applicable law or agreed to in writing, software distributed
|
|
182
|
+
under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
183
|
+
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
|
184
|
+
specific language governing permissions and limitations under the License.
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: versionsec
|
|
3
|
+
Version: 0.7.1
|
|
4
|
+
Summary: Read-only security posture review for one authorized self-hosted Gitea or Forgejo instance, including offline configuration and runner review.
|
|
5
|
+
Author: Gexiro Global Enterprises Ltd
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://versionsec.com
|
|
8
|
+
Project-URL: Repository, https://github.com/gexiro-global/versionsec
|
|
9
|
+
Project-URL: Documentation, https://github.com/gexiro-global/versionsec/tree/main/docs
|
|
10
|
+
Project-URL: Issues, https://github.com/gexiro-global/versionsec/issues
|
|
11
|
+
Project-URL: Changelog, https://github.com/gexiro-global/versionsec/blob/main/CHANGELOG.md
|
|
12
|
+
Keywords: gitea,forgejo,security,posture,self-hosted,sarif,runner
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Classifier: Typing :: Typed
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: httpx<1,>=0.27
|
|
23
|
+
Requires-Dist: pydantic<3,>=2
|
|
24
|
+
Requires-Dist: typer<1,>=0.12
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: build==1.6.1; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest==9.1.1; extra == "dev"
|
|
28
|
+
Requires-Dist: ruff==0.16.6; extra == "dev"
|
|
29
|
+
Requires-Dist: twine==6.2.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-cov==7.1.0; extra == "dev"
|
|
31
|
+
Requires-Dist: jsonschema==4.26.0; extra == "dev"
|
|
32
|
+
Requires-Dist: PyYAML==6.0.3; extra == "dev"
|
|
33
|
+
Dynamic: license-file
|
|
34
|
+
|
|
35
|
+
# VersionSec
|
|
36
|
+
|
|
37
|
+
> **VersionSec was formerly ForgeGuard.** Releases up to and including 0.6.0 were published
|
|
38
|
+
> as `forgeguard` and remain available and unchanged. From 0.7.0 the canonical name is
|
|
39
|
+
> VersionSec (`pip install versionsec`). `forgeguard==0.7.1` is a compatibility bridge and
|
|
40
|
+
> the `forgeguard` CLI/import keep working. Stable `FG-*` finding IDs are unchanged.
|
|
41
|
+
> See [MIGRATION.md](MIGRATION.md).
|
|
42
|
+
|
|
43
|
+
[](https://github.com/gexiro-global/versionsec/actions/workflows/ci.yml)
|
|
44
|
+
[](https://pypi.org/project/versionsec/)
|
|
45
|
+
[](https://pypi.org/project/versionsec/)
|
|
46
|
+
[](https://github.com/gexiro-global/versionsec/blob/main/LICENSE)
|
|
47
|
+
|
|
48
|
+
[](https://scorecard.dev/viewer/?uri=github.com/gexiro-global/versionsec)
|
|
49
|
+
[Security and trust evidence](docs/SECURITY-TRUST.md) documents the project's policies and automated checks. No certification or badge level is claimed.
|
|
50
|
+
|
|
51
|
+
Read-only posture assessment for one explicitly authorized self-hosted Gitea or Forgejo instance, or an explicitly supplied anonymized configuration snapshot.
|
|
52
|
+
|
|
53
|
+
## VersionSec — 0.7.1
|
|
54
|
+
|
|
55
|
+
The ForgeGuard → VersionSec rebrand. No capability was removed: the separate Gitea or Forgejo providers, read-only live posture assessment, offline configuration review, offline runner review, Markdown/JSON/SARIF exports and deterministic completeness semantics all carry over unchanged. Canonical CLI is `versionsec`, canonical import is `versionsec`; the `forgeguard` CLI and `import forgeguard` continue to work through a compatibility shim that resolves to the same implementation. Machine-readable schema identifiers (`forgeguard.assessment.v1`, `forgeguard.config-snapshot.v1`, `forgeguard.runner-snapshot.v1`) and all `FG-*` finding IDs are deliberately unchanged so existing report consumers keep working. See [MIGRATION.md](MIGRATION.md).
|
|
56
|
+
|
|
57
|
+
## Control Plane Hardening — 0.6.0
|
|
58
|
+
|
|
59
|
+
Adds an offline runner posture review (`versionsec runner review`) for the trust boundaries that matter most to a Gitea or Forgejo administrator: execution engine isolation, privileged mode, host volume mounts, Docker socket exposure, job network mode, ephemeral registration, and the experimental Forgejo plugin engine. Runner review is zero-network, evaluates only an explicitly supplied `forgeguard.runner-snapshot.v1` snapshot, and never reads runner credentials, `.runner` files, or Docker/registry secrets. Runner qualification targets are Gitea Runner 3.4.2 and Forgejo Runner 13.0.0 / 13.1.0 — see [upstream evidence](docs/UPSTREAM.md) and [runner review](docs/RUNNER_REVIEW.md).
|
|
60
|
+
|
|
61
|
+
## Multi-Forge Support — 0.5.0
|
|
62
|
+
|
|
63
|
+
Adds Gitea or Forgejo as separate providers, with operator-declared product identity, finite provider-specific advisory catalogs, exposure intent, request profiles, offline configuration review of an explicitly supplied snapshot, and JSON/Markdown/SARIF exports. It does not scan application source code or private artifact contents.
|
|
64
|
+
|
|
65
|
+
Qualification targets are Gitea 1.26.4 / 1.27.3 and Forgejo 15.0.8 / 16.0.4 — these are the exact tested targets, not a universal support promise; see [release notes](docs/RELEASE_CANDIDATE_0_5.md) and the CI evidence linked from the release.
|
|
66
|
+
|
|
67
|
+
## Install
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
python -m pip install versionsec
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Or pin the exact release:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
python -m pip install versionsec==0.7.1
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
versionsec providers
|
|
83
|
+
versionsec checks
|
|
84
|
+
versionsec scan --url https://git.example.com/team --authorized --product forgejo --profile standard --policy public --dry-run
|
|
85
|
+
versionsec scan --url https://git.example.com/team --authorized --product forgejo --profile standard --policy public --format json
|
|
86
|
+
versionsec config review --snapshot anonymized-snapshot.json --policy private --format json
|
|
87
|
+
versionsec runner review --snapshot runner-snapshot.json --format json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Use minimal for one version request, standard for five bounded existing requests, or extended for those requests plus root. The profiles change scope, not aggressiveness. Exposure intent (public/private/unspecified) is independent; unspecified is the default.
|
|
91
|
+
|
|
92
|
+
The operator declares product identity. Compatible APIs and inventory versions alone do not identify a product. Opposing markers, conflicting versions, unknown syntax and unsupported evidence remain explicitly incomplete. No Gitea advisory is applied to Forgejo.
|
|
93
|
+
|
|
94
|
+
## Evidence boundaries
|
|
95
|
+
|
|
96
|
+
HTTP 200 on a checked path is only a status observation, not proof of repository readability, private data access or loaded configuration. Public-by-design status observations are informational. Private intent creates a bounded review warning. HTTP 401/403 may support denial on that path only. Errors, redirects, 404, 429, malformed version JSON, timeouts and truncation remain incomplete.
|
|
97
|
+
|
|
98
|
+
Incomplete assessments have null score, N/A grade and assessed=false, even when another check warns. Skipped checks remain listed. Version 2 scores are not comparable with 0.2.2. No score is security certification or a claim of no vulnerabilities.
|
|
99
|
+
|
|
100
|
+
Offline review performs zero requests and evaluates only the supplied snapshot. It supports a closed key list for registration, sign-in, new-repository privacy and product-specific MFA. It never reads app.ini, production files, tokens, databases or private keys. See [config review](docs/CONFIG_REVIEW.md).
|
|
101
|
+
|
|
102
|
+
## Transport and reports
|
|
103
|
+
|
|
104
|
+
One target, GET only, finite allowlist, no retries or redirects, serial requests, at most 12 requests, 10 seconds each and 60 seconds overall. Responses are streamed with a 256 KiB decompressed limit. HTTPS verifies certificates; --ca-bundle explicitly supplies a private CA. Ambient proxy/CA settings are ignored. HTTP never transmits a token. Anonymous requests do not inherit authentication or cookies.
|
|
105
|
+
|
|
106
|
+
Prefer VERSIONSEC_TOKEN for the optional version-read token. The legacy --token option warns because shell history/process listings may retain it. Reports do not contain the token.
|
|
107
|
+
|
|
108
|
+
Use --format md,json,sarif with --out reports/result.md for separate files. The directory must exist. Existing files, symlinks and collisions are refused, and writes are atomic. Multiple formats require files; single machine-readable stdout is clean.
|
|
109
|
+
|
|
110
|
+
JSON uses forgeguard.assessment.v1 with a packaged schema. SARIF 2.1.0 is schema-validated against frozen OASIS errata01. No source locations are invented. No DevGuard or GitHub Code Scanning importer integration is claimed.
|
|
111
|
+
|
|
112
|
+
## Documentation
|
|
113
|
+
|
|
114
|
+
- [Migration and exit codes](docs/MIGRATION_0_5.md)
|
|
115
|
+
- [Security model](docs/SECURITY_MODEL.md) and [authorized use](AUTHORIZED_USE.md)
|
|
116
|
+
- [Scoring](docs/SCORING.md), [checks](docs/CHECKS.md), [upstream evidence](docs/UPSTREAM.md)
|
|
117
|
+
- [Provider guide](docs/PROVIDERS.md), [runner review](docs/RUNNER_REVIEW.md) and [integration lab](docs/INTEGRATION_TESTING.md)
|
|
118
|
+
- [Candidate notes](docs/RELEASE_CANDIDATE_0_5.md) and [release checklist](docs/RELEASE_CHECKLIST.md)
|
|
119
|
+
|
|
120
|
+
Historical 0.2.2 before/after examples remain in examples/ for migration context. Current examples use the golden- prefix and are synthetic offline fixtures, not production scans.
|
|
121
|
+
|
|
122
|
+
## Commercial hardening
|
|
123
|
+
|
|
124
|
+
Gexiro offers a paid Hardening Report for authorized Gitea operators who want human review of bounded evidence, explicit limitations, prioritized findings, and a practical remediation plan.
|
|
125
|
+
|
|
126
|
+
A Remediation Sprint is a separate engagement: scope and acceptance criteria are frozen first, changes begin only after explicit approval, and the result includes before/after verification. Scope expansion, third-party targets, and remote changes require separate written authorization.
|
|
127
|
+
|
|
128
|
+
VersionSec remains usable as a free OSS CLI without a hosted account or control plane. Heavy SaaS and recurring monitoring are not offered at this stage.
|
|
129
|
+
|
|
130
|
+
[Request a scoped VersionSec Hardening Report](mailto:contact@gexiro.com?subject=VersionSec%20Hardening%20Report)
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
Apache-2.0. See [LICENSE](LICENSE).
|
|
135
|
+
|
|
136
|
+
Built and maintained by [Gexiro Global Enterprises Ltd](https://gexiro.com).
|
|
137
|
+
|
|
138
|
+
Part of the [Gexiro open-source toolkit](https://github.com/gexiro-global).
|
|
139
|
+
|
|
140
|
+
VersionSec by Gexiro
|
|
141
|
+
|
|
142
|
+
Not affiliated with Gitea, Forgejo, Codeberg, GitHub, or GitLab.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# VersionSec
|
|
2
|
+
|
|
3
|
+
> **VersionSec was formerly ForgeGuard.** Releases up to and including 0.6.0 were published
|
|
4
|
+
> as `forgeguard` and remain available and unchanged. From 0.7.0 the canonical name is
|
|
5
|
+
> VersionSec (`pip install versionsec`). `forgeguard==0.7.1` is a compatibility bridge and
|
|
6
|
+
> the `forgeguard` CLI/import keep working. Stable `FG-*` finding IDs are unchanged.
|
|
7
|
+
> See [MIGRATION.md](MIGRATION.md).
|
|
8
|
+
|
|
9
|
+
[](https://github.com/gexiro-global/versionsec/actions/workflows/ci.yml)
|
|
10
|
+
[](https://pypi.org/project/versionsec/)
|
|
11
|
+
[](https://pypi.org/project/versionsec/)
|
|
12
|
+
[](https://github.com/gexiro-global/versionsec/blob/main/LICENSE)
|
|
13
|
+
|
|
14
|
+
[](https://scorecard.dev/viewer/?uri=github.com/gexiro-global/versionsec)
|
|
15
|
+
[Security and trust evidence](docs/SECURITY-TRUST.md) documents the project's policies and automated checks. No certification or badge level is claimed.
|
|
16
|
+
|
|
17
|
+
Read-only posture assessment for one explicitly authorized self-hosted Gitea or Forgejo instance, or an explicitly supplied anonymized configuration snapshot.
|
|
18
|
+
|
|
19
|
+
## VersionSec — 0.7.1
|
|
20
|
+
|
|
21
|
+
The ForgeGuard → VersionSec rebrand. No capability was removed: the separate Gitea or Forgejo providers, read-only live posture assessment, offline configuration review, offline runner review, Markdown/JSON/SARIF exports and deterministic completeness semantics all carry over unchanged. Canonical CLI is `versionsec`, canonical import is `versionsec`; the `forgeguard` CLI and `import forgeguard` continue to work through a compatibility shim that resolves to the same implementation. Machine-readable schema identifiers (`forgeguard.assessment.v1`, `forgeguard.config-snapshot.v1`, `forgeguard.runner-snapshot.v1`) and all `FG-*` finding IDs are deliberately unchanged so existing report consumers keep working. See [MIGRATION.md](MIGRATION.md).
|
|
22
|
+
|
|
23
|
+
## Control Plane Hardening — 0.6.0
|
|
24
|
+
|
|
25
|
+
Adds an offline runner posture review (`versionsec runner review`) for the trust boundaries that matter most to a Gitea or Forgejo administrator: execution engine isolation, privileged mode, host volume mounts, Docker socket exposure, job network mode, ephemeral registration, and the experimental Forgejo plugin engine. Runner review is zero-network, evaluates only an explicitly supplied `forgeguard.runner-snapshot.v1` snapshot, and never reads runner credentials, `.runner` files, or Docker/registry secrets. Runner qualification targets are Gitea Runner 3.4.2 and Forgejo Runner 13.0.0 / 13.1.0 — see [upstream evidence](docs/UPSTREAM.md) and [runner review](docs/RUNNER_REVIEW.md).
|
|
26
|
+
|
|
27
|
+
## Multi-Forge Support — 0.5.0
|
|
28
|
+
|
|
29
|
+
Adds Gitea or Forgejo as separate providers, with operator-declared product identity, finite provider-specific advisory catalogs, exposure intent, request profiles, offline configuration review of an explicitly supplied snapshot, and JSON/Markdown/SARIF exports. It does not scan application source code or private artifact contents.
|
|
30
|
+
|
|
31
|
+
Qualification targets are Gitea 1.26.4 / 1.27.3 and Forgejo 15.0.8 / 16.0.4 — these are the exact tested targets, not a universal support promise; see [release notes](docs/RELEASE_CANDIDATE_0_5.md) and the CI evidence linked from the release.
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
python -m pip install versionsec
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Or pin the exact release:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python -m pip install versionsec==0.7.1
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
versionsec providers
|
|
49
|
+
versionsec checks
|
|
50
|
+
versionsec scan --url https://git.example.com/team --authorized --product forgejo --profile standard --policy public --dry-run
|
|
51
|
+
versionsec scan --url https://git.example.com/team --authorized --product forgejo --profile standard --policy public --format json
|
|
52
|
+
versionsec config review --snapshot anonymized-snapshot.json --policy private --format json
|
|
53
|
+
versionsec runner review --snapshot runner-snapshot.json --format json
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Use minimal for one version request, standard for five bounded existing requests, or extended for those requests plus root. The profiles change scope, not aggressiveness. Exposure intent (public/private/unspecified) is independent; unspecified is the default.
|
|
57
|
+
|
|
58
|
+
The operator declares product identity. Compatible APIs and inventory versions alone do not identify a product. Opposing markers, conflicting versions, unknown syntax and unsupported evidence remain explicitly incomplete. No Gitea advisory is applied to Forgejo.
|
|
59
|
+
|
|
60
|
+
## Evidence boundaries
|
|
61
|
+
|
|
62
|
+
HTTP 200 on a checked path is only a status observation, not proof of repository readability, private data access or loaded configuration. Public-by-design status observations are informational. Private intent creates a bounded review warning. HTTP 401/403 may support denial on that path only. Errors, redirects, 404, 429, malformed version JSON, timeouts and truncation remain incomplete.
|
|
63
|
+
|
|
64
|
+
Incomplete assessments have null score, N/A grade and assessed=false, even when another check warns. Skipped checks remain listed. Version 2 scores are not comparable with 0.2.2. No score is security certification or a claim of no vulnerabilities.
|
|
65
|
+
|
|
66
|
+
Offline review performs zero requests and evaluates only the supplied snapshot. It supports a closed key list for registration, sign-in, new-repository privacy and product-specific MFA. It never reads app.ini, production files, tokens, databases or private keys. See [config review](docs/CONFIG_REVIEW.md).
|
|
67
|
+
|
|
68
|
+
## Transport and reports
|
|
69
|
+
|
|
70
|
+
One target, GET only, finite allowlist, no retries or redirects, serial requests, at most 12 requests, 10 seconds each and 60 seconds overall. Responses are streamed with a 256 KiB decompressed limit. HTTPS verifies certificates; --ca-bundle explicitly supplies a private CA. Ambient proxy/CA settings are ignored. HTTP never transmits a token. Anonymous requests do not inherit authentication or cookies.
|
|
71
|
+
|
|
72
|
+
Prefer VERSIONSEC_TOKEN for the optional version-read token. The legacy --token option warns because shell history/process listings may retain it. Reports do not contain the token.
|
|
73
|
+
|
|
74
|
+
Use --format md,json,sarif with --out reports/result.md for separate files. The directory must exist. Existing files, symlinks and collisions are refused, and writes are atomic. Multiple formats require files; single machine-readable stdout is clean.
|
|
75
|
+
|
|
76
|
+
JSON uses forgeguard.assessment.v1 with a packaged schema. SARIF 2.1.0 is schema-validated against frozen OASIS errata01. No source locations are invented. No DevGuard or GitHub Code Scanning importer integration is claimed.
|
|
77
|
+
|
|
78
|
+
## Documentation
|
|
79
|
+
|
|
80
|
+
- [Migration and exit codes](docs/MIGRATION_0_5.md)
|
|
81
|
+
- [Security model](docs/SECURITY_MODEL.md) and [authorized use](AUTHORIZED_USE.md)
|
|
82
|
+
- [Scoring](docs/SCORING.md), [checks](docs/CHECKS.md), [upstream evidence](docs/UPSTREAM.md)
|
|
83
|
+
- [Provider guide](docs/PROVIDERS.md), [runner review](docs/RUNNER_REVIEW.md) and [integration lab](docs/INTEGRATION_TESTING.md)
|
|
84
|
+
- [Candidate notes](docs/RELEASE_CANDIDATE_0_5.md) and [release checklist](docs/RELEASE_CHECKLIST.md)
|
|
85
|
+
|
|
86
|
+
Historical 0.2.2 before/after examples remain in examples/ for migration context. Current examples use the golden- prefix and are synthetic offline fixtures, not production scans.
|
|
87
|
+
|
|
88
|
+
## Commercial hardening
|
|
89
|
+
|
|
90
|
+
Gexiro offers a paid Hardening Report for authorized Gitea operators who want human review of bounded evidence, explicit limitations, prioritized findings, and a practical remediation plan.
|
|
91
|
+
|
|
92
|
+
A Remediation Sprint is a separate engagement: scope and acceptance criteria are frozen first, changes begin only after explicit approval, and the result includes before/after verification. Scope expansion, third-party targets, and remote changes require separate written authorization.
|
|
93
|
+
|
|
94
|
+
VersionSec remains usable as a free OSS CLI without a hosted account or control plane. Heavy SaaS and recurring monitoring are not offered at this stage.
|
|
95
|
+
|
|
96
|
+
[Request a scoped VersionSec Hardening Report](mailto:contact@gexiro.com?subject=VersionSec%20Hardening%20Report)
|
|
97
|
+
|
|
98
|
+
## License
|
|
99
|
+
|
|
100
|
+
Apache-2.0. See [LICENSE](LICENSE).
|
|
101
|
+
|
|
102
|
+
Built and maintained by [Gexiro Global Enterprises Ltd](https://gexiro.com).
|
|
103
|
+
|
|
104
|
+
Part of the [Gexiro open-source toolkit](https://github.com/gexiro-global).
|
|
105
|
+
|
|
106
|
+
VersionSec by Gexiro
|
|
107
|
+
|
|
108
|
+
Not affiliated with Gitea, Forgejo, Codeberg, GitHub, or GitLab.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Compatibility shim: ForgeGuard was renamed to VersionSec in 0.7.0.
|
|
2
|
+
|
|
3
|
+
``import forgeguard`` and ``import forgeguard.cli`` keep working and resolve to the
|
|
4
|
+
canonical ``versionsec`` implementation. There is no second implementation here: every
|
|
5
|
+
name is an alias of the canonical module object, so the two import paths cannot drift.
|
|
6
|
+
|
|
7
|
+
Canonical from 0.7.0 onward::
|
|
8
|
+
|
|
9
|
+
python -m pip install versionsec
|
|
10
|
+
import versionsec
|
|
11
|
+
|
|
12
|
+
The legacy path stays quiet on purpose - it emits no warning to stdout or stderr, so
|
|
13
|
+
existing automation does not break. See MIGRATION.md.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import importlib
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
import versionsec as _versionsec
|
|
22
|
+
|
|
23
|
+
__version__ = _versionsec.__version__
|
|
24
|
+
__all__ = list(getattr(_versionsec, "__all__", []))
|
|
25
|
+
|
|
26
|
+
_SUBMODULES = (
|
|
27
|
+
"advisories",
|
|
28
|
+
"advisories.evaluator",
|
|
29
|
+
"advisories.models",
|
|
30
|
+
"assessment",
|
|
31
|
+
"checks",
|
|
32
|
+
"cli",
|
|
33
|
+
"client",
|
|
34
|
+
"config_review",
|
|
35
|
+
"engine",
|
|
36
|
+
"exporters",
|
|
37
|
+
"exporters.json",
|
|
38
|
+
"exporters.markdown",
|
|
39
|
+
"exporters.sarif",
|
|
40
|
+
"models",
|
|
41
|
+
"output",
|
|
42
|
+
"policy",
|
|
43
|
+
"providers",
|
|
44
|
+
"providers.base",
|
|
45
|
+
"providers.forgejo",
|
|
46
|
+
"providers.gitea",
|
|
47
|
+
"providers.registry",
|
|
48
|
+
"report",
|
|
49
|
+
"runner_review",
|
|
50
|
+
"safety",
|
|
51
|
+
"scoring",
|
|
52
|
+
"urls",
|
|
53
|
+
"version",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
# Register each canonical submodule under the legacy name as well, so that the
|
|
57
|
+
# statement form ``import forgeguard.cli`` resolves without a second implementation.
|
|
58
|
+
for _name in _SUBMODULES:
|
|
59
|
+
try:
|
|
60
|
+
_module = importlib.import_module(f"versionsec.{_name}")
|
|
61
|
+
except ModuleNotFoundError: # pragma: no cover - defensive
|
|
62
|
+
continue
|
|
63
|
+
sys.modules[f"{__name__}.{_name}"] = _module
|
|
64
|
+
if "." not in _name:
|
|
65
|
+
globals()[_name] = _module
|
|
66
|
+
del _name, _module
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def __getattr__(name: str):
|
|
70
|
+
"""Any remaining attribute resolves against the canonical package."""
|
|
71
|
+
try:
|
|
72
|
+
return getattr(_versionsec, name)
|
|
73
|
+
except AttributeError as exc:
|
|
74
|
+
raise AttributeError(name) from exc
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def __dir__():
|
|
78
|
+
return sorted(set(dir(_versionsec)) | {n for n in _SUBMODULES if "." not in n})
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools==84.0.0", "wheel==0.48.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "versionsec"
|
|
7
|
+
version = "0.7.1"
|
|
8
|
+
description = "Read-only security posture review for one authorized self-hosted Gitea or Forgejo instance, including offline configuration and runner review."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "Apache-2.0"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [
|
|
14
|
+
{name = "Gexiro Global Enterprises Ltd"}
|
|
15
|
+
]
|
|
16
|
+
keywords = ["gitea", "forgejo", "security", "posture", "self-hosted", "sarif", "runner"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Topic :: Security",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"httpx>=0.27,<1",
|
|
27
|
+
"pydantic>=2,<3",
|
|
28
|
+
"typer>=0.12,<1"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://versionsec.com"
|
|
33
|
+
Repository = "https://github.com/gexiro-global/versionsec"
|
|
34
|
+
Documentation = "https://github.com/gexiro-global/versionsec/tree/main/docs"
|
|
35
|
+
Issues = "https://github.com/gexiro-global/versionsec/issues"
|
|
36
|
+
Changelog = "https://github.com/gexiro-global/versionsec/blob/main/CHANGELOG.md"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
versionsec = "versionsec.cli:main"
|
|
40
|
+
# Compatibility alias retained for the ForgeGuard -> VersionSec migration; see MIGRATION.md.
|
|
41
|
+
forgeguard = "versionsec.cli:main"
|
|
42
|
+
|
|
43
|
+
[project.optional-dependencies]
|
|
44
|
+
dev = [
|
|
45
|
+
"build==1.6.1",
|
|
46
|
+
"pytest==9.1.1",
|
|
47
|
+
"ruff==0.16.6",
|
|
48
|
+
"twine==6.2.0",
|
|
49
|
+
"pytest-cov==7.1.0",
|
|
50
|
+
"jsonschema==4.26.0",
|
|
51
|
+
"PyYAML==6.0.3",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.packages.find]
|
|
55
|
+
# forgeguard* is the import-compatibility shim, not a second implementation.
|
|
56
|
+
include = ["versionsec*", "forgeguard*"]
|
|
57
|
+
|
|
58
|
+
[tool.setuptools.package-data]
|
|
59
|
+
versionsec = ["py.typed", "schemas/*.json", "schemas/*.md", "advisories/catalog/*.json"]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""E-FINAL: inventory/audit reconciliation is a set comparison, not a count."""
|
|
2
|
+
|
|
3
|
+
from tools.audit_reconcile import normalize, reconcile
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_full_coverage_ok():
|
|
7
|
+
inv = {"httpx": "0.28.1", "pydantic": "2.13.5", "typer": "0.27.2"}
|
|
8
|
+
r = reconcile(inv, dict(inv))
|
|
9
|
+
assert r["ok"] and not r["missing"] and not r["version_mismatches"]
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_detects_removed_record():
|
|
13
|
+
inv = {"httpx": "0.28.1", "setuptools": "84.0.0", "wheel": "0.48.0"}
|
|
14
|
+
audited = {"httpx": "0.28.1"} # tooling silently dropped
|
|
15
|
+
r = reconcile(inv, audited)
|
|
16
|
+
assert r["missing"] == ["setuptools", "wheel"]
|
|
17
|
+
assert not r["ok"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_detects_version_swap_at_same_count():
|
|
21
|
+
inv = {"packaging": "26.3", "httpx": "0.28.1"}
|
|
22
|
+
audited = {"packaging": "25.0", "httpx": "0.28.1"} # same count, wrong version
|
|
23
|
+
r = reconcile(inv, audited)
|
|
24
|
+
assert len(inv) == len(audited)
|
|
25
|
+
assert r["version_mismatches"] == [
|
|
26
|
+
{"package": "packaging", "inventory": "26.3", "audited": "25.0"}
|
|
27
|
+
]
|
|
28
|
+
assert not r["ok"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_detects_unexpected_extra():
|
|
32
|
+
r = reconcile({"httpx": "0.28.1"}, {"httpx": "0.28.1", "evil": "1.0"})
|
|
33
|
+
assert r["unexpected"] == ["evil"]
|
|
34
|
+
assert not r["ok"]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_name_normalisation():
|
|
38
|
+
assert normalize("PyYAML") == "pyyaml"
|
|
39
|
+
assert normalize("typing_extensions") == "typing-extensions"
|
|
40
|
+
r = reconcile({"PyYAML": "6.0.3"}, {"pyyaml": "6.0.3"})
|
|
41
|
+
assert r["ok"]
|