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.
Files changed (47) hide show
  1. subvectors-0.2.0/.gitattributes +10 -0
  2. subvectors-0.2.0/.github/dependabot.yml +39 -0
  3. subvectors-0.2.0/.github/workflows/ci.yml +49 -0
  4. subvectors-0.2.0/.github/workflows/release.yml +58 -0
  5. subvectors-0.2.0/.gitignore +30 -0
  6. subvectors-0.2.0/ARCHITECTURE.md +173 -0
  7. subvectors-0.2.0/BACKLOG.md +300 -0
  8. subvectors-0.2.0/CONTRIBUTING.md +103 -0
  9. subvectors-0.2.0/LICENSE +201 -0
  10. subvectors-0.2.0/PKG-INFO +170 -0
  11. subvectors-0.2.0/README.md +158 -0
  12. subvectors-0.2.0/ROADMAP.md +150 -0
  13. subvectors-0.2.0/docs/JUDGMENT-CATALOG.md +193 -0
  14. subvectors-0.2.0/pyproject.toml +31 -0
  15. subvectors-0.2.0/scripts/coverage.py +139 -0
  16. subvectors-0.2.0/src/subvectors/__init__.py +8 -0
  17. subvectors-0.2.0/src/subvectors/cel.py +322 -0
  18. subvectors-0.2.0/src/subvectors/corpus.py +55 -0
  19. subvectors-0.2.0/src/subvectors/ffl.py +172 -0
  20. subvectors-0.2.0/src/subvectors/github.py +80 -0
  21. subvectors-0.2.0/src/subvectors/gitlab.py +97 -0
  22. subvectors-0.2.0/src/subvectors/matcher.py +208 -0
  23. subvectors-0.2.0/tests/test_cel.py +155 -0
  24. subvectors-0.2.0/tests/test_corpus.py +37 -0
  25. subvectors-0.2.0/tests/test_ffl.py +112 -0
  26. subvectors-0.2.0/tests/test_github_grammar.py +82 -0
  27. subvectors-0.2.0/tests/test_gitlab_grammar.py +71 -0
  28. subvectors-0.2.0/tests/test_judgment_catalog.py +50 -0
  29. subvectors-0.2.0/tests/test_matcher.py +253 -0
  30. subvectors-0.2.0/tests/test_readme_coverage.py +34 -0
  31. subvectors-0.2.0/tests/test_vectors.py +115 -0
  32. subvectors-0.2.0/vectors/LICENSE +121 -0
  33. subvectors-0.2.0/vectors/bitbucket-aws.json +126 -0
  34. subvectors-0.2.0/vectors/circleci-aws.json +144 -0
  35. subvectors-0.2.0/vectors/circleci-gcp.json +102 -0
  36. subvectors-0.2.0/vectors/github-aws.json +564 -0
  37. subvectors-0.2.0/vectors/github-azure-flexible.json +126 -0
  38. subvectors-0.2.0/vectors/github-azure.json +138 -0
  39. subvectors-0.2.0/vectors/github-gcp.json +186 -0
  40. subvectors-0.2.0/vectors/gitlab-aws.json +307 -0
  41. subvectors-0.2.0/vectors/gitlab-azure-flexible.json +88 -0
  42. subvectors-0.2.0/vectors/gitlab-azure.json +98 -0
  43. subvectors-0.2.0/vectors/gitlab-gcp.json +119 -0
  44. subvectors-0.2.0/vectors/schema/vector-suite.schema.json +178 -0
  45. subvectors-0.2.0/vectors/terraform-aws.json +126 -0
  46. subvectors-0.2.0/vectors/terraform-azure-flexible.json +98 -0
  47. subvectors-0.2.0/vectors/terraform-gcp.json +116 -0
@@ -0,0 +1,10 @@
1
+ # Normalize line endings to LF in the repo (kills the CRLF churn on Windows).
2
+ * text=auto eol=lf
3
+
4
+ # Explicitly text
5
+ *.py text eol=lf
6
+ *.json text eol=lf
7
+ *.toml text eol=lf
8
+ *.md text eol=lf
9
+ *.yml text eol=lf
10
+ *.yaml text eol=lf
@@ -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.