subvectors 0.2.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.
- subvectors-0.2.0/.gitattributes +10 -0
- subvectors-0.2.0/.github/dependabot.yml +39 -0
- subvectors-0.2.0/.github/workflows/ci.yml +49 -0
- subvectors-0.2.0/.github/workflows/release.yml +58 -0
- subvectors-0.2.0/.gitignore +30 -0
- subvectors-0.2.0/ARCHITECTURE.md +173 -0
- subvectors-0.2.0/BACKLOG.md +300 -0
- subvectors-0.2.0/CONTRIBUTING.md +103 -0
- subvectors-0.2.0/LICENSE +201 -0
- subvectors-0.2.0/PKG-INFO +170 -0
- subvectors-0.2.0/README.md +158 -0
- subvectors-0.2.0/ROADMAP.md +150 -0
- subvectors-0.2.0/docs/JUDGMENT-CATALOG.md +193 -0
- subvectors-0.2.0/pyproject.toml +31 -0
- subvectors-0.2.0/scripts/coverage.py +139 -0
- subvectors-0.2.0/src/subvectors/__init__.py +8 -0
- subvectors-0.2.0/src/subvectors/cel.py +322 -0
- subvectors-0.2.0/src/subvectors/corpus.py +55 -0
- subvectors-0.2.0/src/subvectors/ffl.py +172 -0
- subvectors-0.2.0/src/subvectors/github.py +80 -0
- subvectors-0.2.0/src/subvectors/gitlab.py +97 -0
- subvectors-0.2.0/src/subvectors/matcher.py +208 -0
- subvectors-0.2.0/tests/test_cel.py +155 -0
- subvectors-0.2.0/tests/test_corpus.py +37 -0
- subvectors-0.2.0/tests/test_ffl.py +112 -0
- subvectors-0.2.0/tests/test_github_grammar.py +82 -0
- subvectors-0.2.0/tests/test_gitlab_grammar.py +71 -0
- subvectors-0.2.0/tests/test_judgment_catalog.py +50 -0
- subvectors-0.2.0/tests/test_matcher.py +253 -0
- subvectors-0.2.0/tests/test_readme_coverage.py +34 -0
- subvectors-0.2.0/tests/test_vectors.py +115 -0
- subvectors-0.2.0/vectors/LICENSE +121 -0
- subvectors-0.2.0/vectors/bitbucket-aws.json +126 -0
- subvectors-0.2.0/vectors/circleci-aws.json +144 -0
- subvectors-0.2.0/vectors/circleci-gcp.json +102 -0
- subvectors-0.2.0/vectors/github-aws.json +564 -0
- subvectors-0.2.0/vectors/github-azure-flexible.json +126 -0
- subvectors-0.2.0/vectors/github-azure.json +138 -0
- subvectors-0.2.0/vectors/github-gcp.json +186 -0
- subvectors-0.2.0/vectors/gitlab-aws.json +307 -0
- subvectors-0.2.0/vectors/gitlab-azure-flexible.json +88 -0
- subvectors-0.2.0/vectors/gitlab-azure.json +98 -0
- subvectors-0.2.0/vectors/gitlab-gcp.json +119 -0
- subvectors-0.2.0/vectors/schema/vector-suite.schema.json +178 -0
- subvectors-0.2.0/vectors/terraform-aws.json +126 -0
- subvectors-0.2.0/vectors/terraform-azure-flexible.json +98 -0
- subvectors-0.2.0/vectors/terraform-gcp.json +116 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Dependabot configuration - deliberately low-noise.
|
|
2
|
+
#
|
|
3
|
+
# Non-major updates arrive as ONE grouped PR per ecosystem per month.
|
|
4
|
+
# Security updates stay ungrouped and uncooled so they land fast.
|
|
5
|
+
version: 2
|
|
6
|
+
updates:
|
|
7
|
+
- package-ecosystem: "pip"
|
|
8
|
+
directory: "/"
|
|
9
|
+
schedule:
|
|
10
|
+
interval: "monthly"
|
|
11
|
+
open-pull-requests-limit: 5
|
|
12
|
+
cooldown:
|
|
13
|
+
default-days: 7
|
|
14
|
+
semver-major-days: 30
|
|
15
|
+
groups:
|
|
16
|
+
python-non-major:
|
|
17
|
+
applies-to: version-updates
|
|
18
|
+
patterns:
|
|
19
|
+
- "*"
|
|
20
|
+
update-types:
|
|
21
|
+
- "minor"
|
|
22
|
+
- "patch"
|
|
23
|
+
labels:
|
|
24
|
+
- "dependencies"
|
|
25
|
+
|
|
26
|
+
- package-ecosystem: "github-actions"
|
|
27
|
+
directory: "/"
|
|
28
|
+
schedule:
|
|
29
|
+
interval: "monthly"
|
|
30
|
+
open-pull-requests-limit: 3
|
|
31
|
+
cooldown:
|
|
32
|
+
default-days: 7
|
|
33
|
+
groups:
|
|
34
|
+
actions-all:
|
|
35
|
+
applies-to: version-updates
|
|
36
|
+
patterns:
|
|
37
|
+
- "*"
|
|
38
|
+
labels:
|
|
39
|
+
- "dependencies"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
# Nothing in this workflow writes anywhere: tests are pure-offline and the
|
|
10
|
+
# GITHUB_TOKEN is never used. A repo about CI/CD OIDC trust conditions should
|
|
11
|
+
# hold itself to least privilege.
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
|
|
15
|
+
concurrency:
|
|
16
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
17
|
+
cancel-in-progress: true
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
test:
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
timeout-minutes: 10
|
|
23
|
+
strategy:
|
|
24
|
+
fail-fast: false
|
|
25
|
+
matrix:
|
|
26
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
27
|
+
steps:
|
|
28
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
29
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
30
|
+
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
31
|
+
with:
|
|
32
|
+
python-version: ${{ matrix.python-version }}
|
|
33
|
+
cache: pip
|
|
34
|
+
cache-dependency-path: pyproject.toml
|
|
35
|
+
# The editable install exercises the hatchling packaging path and
|
|
36
|
+
# enforces the dev floors from pyproject instead of hand-picking
|
|
37
|
+
# pytest + jsonschema versions here.
|
|
38
|
+
- name: Install package with dev dependencies
|
|
39
|
+
run: |
|
|
40
|
+
python -m pip install --upgrade pip
|
|
41
|
+
pip install -e ".[dev]"
|
|
42
|
+
- name: Run tests
|
|
43
|
+
run: python -m pytest -q
|
|
44
|
+
# The README coverage table is generated from vectors/ - a vector change
|
|
45
|
+
# without a regenerated table is silent doc drift.
|
|
46
|
+
- name: README coverage table is current
|
|
47
|
+
run: |
|
|
48
|
+
python scripts/coverage.py --write
|
|
49
|
+
git diff --exit-code README.md
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI via OIDC trusted publishing (no stored API token) - the
|
|
4
|
+
# same short-lived-token mechanism this corpus exists to judge. Fires when a
|
|
5
|
+
# GitHub Release is published; INERT until one exists. PyPI side is configured
|
|
6
|
+
# once as a pending publisher: pypi.org -> owner Dashtid, repository
|
|
7
|
+
# subvectors, workflow release.yml, environment pypi.
|
|
8
|
+
#
|
|
9
|
+
# [!] GATE before the first Release: decide vectors/ packaging. Today
|
|
10
|
+
# [tool.hatch.build.targets.wheel] ships only src/subvectors - a wheel
|
|
11
|
+
# WITHOUT the corpus, which for a corpus project is an empty shell. See
|
|
12
|
+
# ROADMAP "What is actually open".
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
release:
|
|
16
|
+
types: [published]
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
|
|
21
|
+
concurrency:
|
|
22
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
23
|
+
cancel-in-progress: false
|
|
24
|
+
|
|
25
|
+
jobs:
|
|
26
|
+
build:
|
|
27
|
+
runs-on: ubuntu-latest
|
|
28
|
+
timeout-minutes: 10
|
|
29
|
+
steps:
|
|
30
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
31
|
+
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
32
|
+
with:
|
|
33
|
+
python-version: "3.12"
|
|
34
|
+
- name: Build sdist and wheel
|
|
35
|
+
run: |
|
|
36
|
+
python -m pip install --upgrade pip build
|
|
37
|
+
python -m build
|
|
38
|
+
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
|
39
|
+
with:
|
|
40
|
+
name: dist
|
|
41
|
+
path: dist/
|
|
42
|
+
|
|
43
|
+
publish:
|
|
44
|
+
needs: build
|
|
45
|
+
runs-on: ubuntu-latest
|
|
46
|
+
timeout-minutes: 10
|
|
47
|
+
environment: pypi
|
|
48
|
+
permissions:
|
|
49
|
+
id-token: write # OIDC trusted publishing
|
|
50
|
+
steps:
|
|
51
|
+
- uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
|
52
|
+
with:
|
|
53
|
+
name: dist
|
|
54
|
+
path: dist/
|
|
55
|
+
# Deliberately a moving branch, per pypa's own guidance: it tracks
|
|
56
|
+
# security fixes in the publish action; SHA-pinning here would freeze
|
|
57
|
+
# them. The job has no other write surface (id-token only).
|
|
58
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
.venv/
|
|
5
|
+
venv/
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
.ruff_cache/
|
|
8
|
+
.mypy_cache/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
*.egg-info/
|
|
12
|
+
.coverage
|
|
13
|
+
htmlcov/
|
|
14
|
+
|
|
15
|
+
# Secrets / real cloud data — never commit live trust policies, tokens, or account data
|
|
16
|
+
.env
|
|
17
|
+
.env*.local
|
|
18
|
+
*.pem
|
|
19
|
+
credentials*
|
|
20
|
+
real-*/ # keep only sanitized fixtures under fixtures/; real org exports go here, ignored
|
|
21
|
+
|
|
22
|
+
# OS / editor
|
|
23
|
+
.DS_Store
|
|
24
|
+
Thumbs.db
|
|
25
|
+
.idea/
|
|
26
|
+
.vscode/
|
|
27
|
+
|
|
28
|
+
# Private Claude Code build-session context — kept in-repo, not published
|
|
29
|
+
CLAUDE.md
|
|
30
|
+
.claude/
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# How subvectors works
|
|
2
|
+
|
|
3
|
+
A plain-language tour, for picking the project back up after time away.
|
|
4
|
+
|
|
5
|
+
## The one thing to hold onto
|
|
6
|
+
|
|
7
|
+
**subvectors is not a tool. It is a pile of test data.**
|
|
8
|
+
|
|
9
|
+
That is the single most confusing thing about it, so it's worth saying plainly. There is Python in
|
|
10
|
+
this repo, but the Python is *not the product* — it exists only to prove the data is correct. The
|
|
11
|
+
product is the JSON in `vectors/`.
|
|
12
|
+
|
|
13
|
+
The closest analogy: a **standard test fire** used to certify smoke detectors. It isn't a smoke
|
|
14
|
+
detector and doesn't compete with any. It's the thing you test detectors *against*.
|
|
15
|
+
|
|
16
|
+
## The problem it exists for
|
|
17
|
+
|
|
18
|
+
When a CI pipeline authenticates to a cloud, the entire security boundary is one string comparison:
|
|
19
|
+
the token's `sub` claim versus an admin-written matching rule.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
subject (what the pipeline presents) condition (the admin's rule)
|
|
23
|
+
repo:acme/payments-api:pull_request vs repo:acme/payments-api:*
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The catch: **that comparison means different things to different clouds.** The same rule is a
|
|
27
|
+
wildcard glob to AWS `StringLike`, dead literal text to AWS `StringEquals`, dead literal text to
|
|
28
|
+
classic Azure FIC, and a typed expression to GCP.
|
|
29
|
+
|
|
30
|
+
Every security scanner (Checkov, Prowler, Wiz, …) has to re-implement this comparison, and they all
|
|
31
|
+
work it out alone from prose docs. They get it wrong. subvectors is a **shared answer key** so they
|
|
32
|
+
don't have to guess — and so their mistakes become visible.
|
|
33
|
+
|
|
34
|
+
Crucially it **grades scanners rather than competing with them**. A new scanner is a new *consumer*
|
|
35
|
+
of the corpus, not a rival. Same reason Wycheproof tests everyone's crypto.
|
|
36
|
+
|
|
37
|
+
## What a vector is
|
|
38
|
+
|
|
39
|
+
One vector is one falsifiable test case. From `vectors/github-aws.json`:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"id": "gh-aws-0007",
|
|
44
|
+
"issuer": "github",
|
|
45
|
+
"subject": "repo:acme/webapp:pull_request",
|
|
46
|
+
"condition": { "consumer": "aws-stringlike", "pattern": "repo:acme/webapp:*" },
|
|
47
|
+
"expect": "match",
|
|
48
|
+
"judgment": {
|
|
49
|
+
"grade": "dangerous",
|
|
50
|
+
"reason": "pattern admits pull_request runs"
|
|
51
|
+
},
|
|
52
|
+
"sources": ["https://docs.github.com/..."],
|
|
53
|
+
"status": "documented"
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Read it as: *"an issuer minted this subject; an admin wrote this rule; here is whether they match;
|
|
58
|
+
here is whether the rule is safe; here is the doc that proves it."*
|
|
59
|
+
|
|
60
|
+
Three separable layers, deliberately kept apart:
|
|
61
|
+
|
|
62
|
+
1. **Grammar** — is the subject well-formed for its issuer?
|
|
63
|
+
2. **Match** — does it satisfy the condition? *Mechanically checkable.* The matcher decides this.
|
|
64
|
+
3. **Judgment** — is the condition *safe*? *A human opinion with a citation.* The matcher never
|
|
65
|
+
reads it, so a consumer who disagrees with a grade can strip judgment and still use layers 1–2.
|
|
66
|
+
|
|
67
|
+
Two fields carry the honesty:
|
|
68
|
+
- **`sources`** — every vector cites a primary source. Required by the schema.
|
|
69
|
+
- **`status`** — `documented` (derived from docs) vs `observed` (confirmed against a real
|
|
70
|
+
issuer/cloud). **All vectors are currently `documented`.** Closing that gap is the highest-value
|
|
71
|
+
work available; see `BACKLOG.md`.
|
|
72
|
+
|
|
73
|
+
## The files
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
vectors/ <- THE PRODUCT. 13 suites, 127 vectors. CC0-licensed.
|
|
77
|
+
github-aws.json one file per (issuer x cloud) pair
|
|
78
|
+
github-azure.json github/gitlab/bitbucket/circleci/terraform x aws/azure/gcp
|
|
79
|
+
...
|
|
80
|
+
schema/ JSON Schema every vector must validate against
|
|
81
|
+
LICENSE CC0-1.0 - copy these freely, no attribution needed
|
|
82
|
+
|
|
83
|
+
src/subvectors/ <- THE ORACLE. ~865 lines. Proves the vectors are self-consistent.
|
|
84
|
+
matcher.py satisfies(subject, condition) -> bool. The entry point.
|
|
85
|
+
cel.py mini CEL evaluator (GCP conditions are CEL expressions)
|
|
86
|
+
ffl.py mini expression evaluator (Azure flexible FIC, preview)
|
|
87
|
+
github.py GitHub subject grammar (legacy + immutable @id formats)
|
|
88
|
+
gitlab.py GitLab subject grammar (project_path + project_id forms)
|
|
89
|
+
|
|
90
|
+
tests/ <- THE PROOF.
|
|
91
|
+
test_vectors.py runs EVERY vector through the matcher, asserts `expect`
|
|
92
|
+
test_matcher.py unit tests for the matching semantics
|
|
93
|
+
test_cel.py CEL evaluator tests
|
|
94
|
+
...
|
|
95
|
+
|
|
96
|
+
docs/
|
|
97
|
+
JUDGMENT-CATALOG.md the graded over-permission patterns
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## How the matcher works
|
|
101
|
+
|
|
102
|
+
`matcher.py` answers exactly one question: **does subject S satisfy condition C?**
|
|
103
|
+
|
|
104
|
+
Each `condition` names a **consumer** — which cloud's matching rules to apply:
|
|
105
|
+
|
|
106
|
+
| Consumer | Semantics |
|
|
107
|
+
|---|---|
|
|
108
|
+
| `aws-stringlike` | Glob. `*` = any characters, and it **spans `/` and `:`** — which is precisely why `repo:org/*` admits every repo in the org. |
|
|
109
|
+
| `aws-stringequals` | Exact equality. `*` is a literal character, so `repo:org/*` matches *nothing real*. |
|
|
110
|
+
| `azure-fic-exact` | Exact equality too — classic Azure FIC supports no wildcards at all. Opposite of AWS `StringLike`, from an identical-looking config. |
|
|
111
|
+
| `gcp-cel` | Not a string comparison. A CEL expression over the whole claim set, evaluated by `cel.py`. |
|
|
112
|
+
| `azure-fic-flexible` | Azure's preview expression language; wildcards are back. Evaluated by `ffl.py`. |
|
|
113
|
+
| `aws-all` | A composite: a full IAM Condition block, ANDing several AWS sub-conditions. |
|
|
114
|
+
|
|
115
|
+
Two behaviours worth remembering because they're where real bugs live:
|
|
116
|
+
|
|
117
|
+
- **Values are OR'd (AWS only).** A condition may carry a *list* of patterns, and IAM ORs them — so
|
|
118
|
+
**one loose value poisons an otherwise tight list.**
|
|
119
|
+
- **Unsupported consumers raise, never return False.** A vector can never pass by being silently
|
|
120
|
+
unmatched.
|
|
121
|
+
|
|
122
|
+
## How to run it
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pytest -q # runs every vector through the matcher
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
That's the whole loop. If a vector's `expect` disagrees with the matcher, the suite goes red — so
|
|
129
|
+
either the vector is wrong or the matcher is. Nothing else to start; there's no server, no CLI, no
|
|
130
|
+
cloud account.
|
|
131
|
+
|
|
132
|
+
## What it deliberately does NOT do
|
|
133
|
+
|
|
134
|
+
Guardrails, not gaps. See the Scope section of `CONTRIBUTING.md`.
|
|
135
|
+
|
|
136
|
+
- **Not a scanner, not a PR gate, no reachability graphs.** That was the original `oidc-reach` plan
|
|
137
|
+
and it was **killed on 2026-07-05** — the value decayed on a ~6-month incumbent fuse. If you find
|
|
138
|
+
a doc still describing this project as a "PR gate," that doc is stale.
|
|
139
|
+
- **No runtime dependencies.** A consumer should be able to read or vendor the oracle, not have to
|
|
140
|
+
trust a package.
|
|
141
|
+
- **It does not depend on subcheck** and never will. subcheck consumes *these* vectors as test
|
|
142
|
+
fixtures; the arrow points one way. An answer key can't depend on one of its students.
|
|
143
|
+
|
|
144
|
+
## How success is measured
|
|
145
|
+
|
|
146
|
+
**Bugs found and upstream PRs merged.** Not vector count.
|
|
147
|
+
|
|
148
|
+
Vectors added is an *input*. A tranche that surfaces no bug and no adoption is a signal to stop, not
|
|
149
|
+
progress. Cataloguing for its own sake is the named failure mode ("the librarian trap") — see
|
|
150
|
+
`ROADMAP.md`.
|
|
151
|
+
|
|
152
|
+
## Known issues
|
|
153
|
+
|
|
154
|
+
Recorded here so they aren't rediscovered later (from the 2026-07-30 verification pass).
|
|
155
|
+
Still open:
|
|
156
|
+
|
|
157
|
+
- **Azure "fails silently, no error" is only half right.** *Creation* of a FIC is unvalidated and
|
|
158
|
+
silent; *token exchange* returns `AADSTS700213`. Still to correct in `matcher.py` and several
|
|
159
|
+
Azure vector files. (`ROADMAP.md` was corrected in `42285eb` and now states it correctly.)
|
|
160
|
+
- **README says vectors carry a `documented` vs `observed` status**, implying a mix. All are
|
|
161
|
+
currently `documented`.
|
|
162
|
+
|
|
163
|
+
Resolved:
|
|
164
|
+
|
|
165
|
+
- **`RepoSegment.immutable` in `github.py` used `or`. FIXED 2026-08-18** — it is now `and`, since
|
|
166
|
+
GitHub emits `@id` on both segments or neither, so a one-sided subject is *malformed*, not
|
|
167
|
+
immutable. The branch was reachable (both id groups in `_REPO_RE` are independently optional, so
|
|
168
|
+
one-sided input parses); it is now covered by two unit tests. **subcheck had the identical bug
|
|
169
|
+
and fixed it first**: `decoder.py` shipped `"immutable" if (owner_id or repo_id) else "legacy"`
|
|
170
|
+
in `34a42ce` (2026-07-21) and corrected it to a three-state `immutable`/`malformed`/`legacy`
|
|
171
|
+
classification in `423964f` (2026-07-30). This repo lagged that correction by three weeks; the
|
|
172
|
+
two now agree on every input shape. `immutable` stays a two-state boolean here, so a one-sided
|
|
173
|
+
subject reads the same as a legacy one; `owner_id`/`repo_id` still distinguish them.
|