-
Notifications
You must be signed in to change notification settings - Fork 0
fix(sbom): enforce hourly non-fork commercial inventory #1603
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
606a27e
test(sbom): require hourly non-fork inventory contract
seonghobae c84d205
fix(sbom): collect non-forks hourly without history rewrite
seonghobae ed8c202
docs(sbom): define commercial license remediation policy
seonghobae 9caa175
chore(sbom): merge protected main into hourly inventory lane
seonghobae 931e346
test(sbom): require fail-closed org auth and publication wiring
seonghobae 2667d0a
fix(sbom): fail closed without org-wide credentials
seonghobae 4b078f1
docs(sbom): record fail-closed credential contract
seonghobae 674e0d5
merge current main into hourly non-fork SBOM inventory
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
45 changes: 45 additions & 0 deletions
45
docs/doctoring/hourly-commercial-license-sbom-remediation.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| # Hourly commercial-license SBOM remediation | ||
|
|
||
| Status: implementation evidence for the central ContextualWisdomLab supply-chain control plane. | ||
| Scope: live repositories whose GitHub metadata proves `fork=false`; forks are provenance evidence only and are never owner-side remediation targets. | ||
|
|
||
| ## Observed gap | ||
|
|
||
| At `ContextualWisdomLab/.github@5f81d8e665b7d3f51f379a090e077486dbf548c5`, the central SBOM inventory still reports `pending first scheduled run`, zero repositories, and zero components. The scheduler runs only once a week and delegates organization discovery to an aggregator that does not itself exclude forks on protected `main`. That combination can make a zero-finding report look materially cleaner than the evidence actually supports. | ||
|
|
||
| The existing license classifier is intentionally high-recall but is not a legal conclusion: it substring-flags GPL/AGPL/LGPL/MPL/EPL/CDDL and related expressions plus `NOASSERTION`. A flagged component therefore means **commercial-policy review is required**, not “commercial use is forbidden.” The GNU GPL explicitly permits selling copies; obligations depend on how covered code is combined, modified, conveyed, or offered as a network service. AGPLv3 adds a corresponding-source obligation for users interacting remotely with a modified covered program under section 13. | ||
|
|
||
| ## Decision | ||
|
|
||
| 1. Refresh the organization inventory every hour. | ||
| 2. Build the owned target set from live GitHub repository metadata and admit only entries with `isFork == false` before any SBOM collection. | ||
| 3. Require an organization-wide SBOM credential before discovery or collection. The repository-scoped `github.token` is not an acceptable fallback because it can silently hide private sibling repositories; absence of the dedicated token or successful OpenCode app exchange fails closed instead of publishing a partial inventory. | ||
| 4. Reconcile SPDX/CycloneDX evidence with manifests, lockfiles, vendored/native/binary assets, container inputs, generated packages, and dependency-graph evidence before calling an inventory complete. | ||
| 5. Interpret license expressions as evidence requiring an explicit `allow`, `review`, or `replace/block` outcome tied to the actual product distribution and hosted-service model. Do not equate copyleft with non-commercial use. | ||
| 6. For an actionable incompatibility, remediate in this order: remove an unused component; replace it with a maintained permissively licensed equivalent; implement only the bounded required capability cleanly in-house from independent product/API/standards behavior; isolate it behind an independently deployed service/process boundary only when that genuinely changes the technical and legal coupling; or redesign the feature to remove the dependency. | ||
| 7. A replacement implementation must not copy protected source, tests, comments, data, expressive structure, or other copyrightable material from the incompatible implementation. Product contracts, published standards, independent interoperability documentation, and lawful black-box behavior are the acceptable specification sources. | ||
| 8. Update manifests and lockfiles, SBOMs, NOTICE/THIRD_PARTY_NOTICES, tests, architecture/ADR evidence, CHANGELOG when release-relevant, and `docs/product-technical-gap-baseline.md`; then rerun exact-head Checks/reviews and merge only through ordinary branch protection. | ||
| 9. Preserve concurrent writers. The recurring inventory publication branch must advance without history rewriting; a race fails closed and is retried on a later run. Because checkout deliberately keeps `persist-credentials: false`, publication establishes Git authentication through the masked organization-wide `GH_TOKEN` with `gh auth setup-git` before the first remote Git operation. | ||
|
|
||
| ## Standards and interpretation baseline | ||
|
|
||
| - SPDX 3.0 is the current SPDX document specification; SPDX is standardized as ISO/IEC 5962:2021. SBOM license identifiers and expressions are machine contracts and must not be reduced to free-text substring heuristics for final policy decisions. | ||
| - CycloneDX 1.7 is the current stable BOM specification and ECMA-424 2nd Edition. CycloneDX 2.0 is announced for 2026 but is not yet the stable baseline as of 2026-09-01. | ||
| - GPL-family software can be used commercially. The engineering concern for ContextualWisdomLab is whether the concrete incorporation, modification, conveyance, hosted-service behavior, source-offer obligation, attribution, patent terms, or reciprocal scope conflicts with the intended proprietary/commercial product contract. | ||
| - Unknown (`NOASSERTION`/unlicensed) and explicitly non-commercial, evaluation-only, field-of-use, or source-available restrictions fail closed into review until provenance and rights are established. | ||
|
|
||
| This is an engineering governance policy and evidence record, not legal advice. Ambiguous rights or license compatibility that cannot be resolved from authoritative terms remains a legal-rights blocker rather than being guessed by automation. | ||
|
|
||
| ## Verification contract | ||
|
|
||
| The scheduler contract is executable in `tests/test_sbom_inventory_scheduler_contract.py`: it binds assertions to the named executable discovery, aggregation, credential, and publication steps; requires an hourly cron; requires live `isFork == false` filtering; passes only the verified repositories explicitly to the aggregator; rejects `github.token` fallback; configures authenticated Git before remote publication; and prohibits force-push behavior. The first inventory run after merge is not considered complete merely because it reports zero findings; unavailable SBOMs and incomplete dependency materialization remain explicit defects to repair. | ||
|
|
||
| ## References | ||
|
|
||
| Free Software Foundation. (n.d.). *Frequently asked questions about the GNU licenses*. https://www.gnu.org/licenses/gpl-faq.html | ||
|
|
||
| Free Software Foundation. (2007). *GNU Affero General Public License, version 3*. https://www.gnu.org/licenses/agpl-3.0.html | ||
|
|
||
| OWASP Foundation. (2025). *CycloneDX specification 1.7 (ECMA-424, 2nd ed.)*. https://cyclonedx.org/specification/overview/ | ||
|
|
||
| SPDX Workgroup. (n.d.). *SPDX specifications*. Linux Foundation. https://spdx.dev/use/specifications/ |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| """Executable contract for the central SBOM inventory scheduler.""" | ||
|
|
||
| from pathlib import Path | ||
|
|
||
|
|
||
| WORKFLOW = Path(".github/workflows/sbom-inventory-scheduler.yml") | ||
|
|
||
|
|
||
| def _workflow_text() -> str: | ||
| """Return the scheduler source as text for dependency-free contract checks.""" | ||
| return WORKFLOW.read_text(encoding="utf-8") | ||
|
|
||
|
|
||
| def _step_body(name: str) -> str: | ||
| """Return one named executable workflow step, excluding later steps.""" | ||
| workflow = _workflow_text() | ||
| marker = f" - name: {name}\n" | ||
| start = workflow.index(marker) | ||
| next_step = workflow.find("\n - name: ", start + len(marker)) | ||
| return workflow[start : next_step if next_step != -1 else len(workflow)] | ||
|
|
||
|
|
||
| def test_sbom_inventory_scheduler_runs_hourly() -> None: | ||
| """Organization license evidence must refresh once each hour.""" | ||
| workflow = _workflow_text() | ||
| assert 'cron: "0 * * * *"' in workflow | ||
| assert 'cron: "0 6 * * 1"' not in workflow | ||
|
|
||
|
|
||
| def test_sbom_inventory_scheduler_requires_cross_repo_credential() -> None: | ||
| """Repository-scoped github.token must never publish a partial org inventory.""" | ||
| workflow = _workflow_text() | ||
| credential_step = _step_body("Require organization-wide SBOM credential") | ||
| assert "|| github.token" not in workflow | ||
| assert ( | ||
| "GH_TOKEN: ${{ secrets.SBOM_INVENTORY_TOKEN || steps.aggregator_app_token.outputs.token }}" | ||
| in credential_step | ||
| ) | ||
| assert 'if [ -z "${GH_TOKEN:-}" ]; then' in credential_step | ||
| assert "refusing partial inventory" in credential_step | ||
| assert "exit 1" in credential_step | ||
|
|
||
|
|
||
| def test_sbom_inventory_scheduler_excludes_forks_before_collection() -> None: | ||
| """Only repositories proven non-forks may become owned inventory targets.""" | ||
| discovery_step = _step_body("Discover live non-fork repositories") | ||
| aggregation_step = _step_body("Aggregate org SBOM inventory") | ||
| assert "gh repo list" in discovery_step | ||
| assert '"nameWithOwner,isFork"' in discovery_step | ||
| assert ".[] | select(.isFork == false) | .nameWithOwner" in discovery_step | ||
| assert "cwl-nonfork-repositories.txt" in discovery_step | ||
| assert 'repo_args+=(--repo "$repo")' in aggregation_step | ||
| assert '"${repo_args[@]}"' in aggregation_step | ||
| assert '--org "$ORG_LOGIN"' not in aggregation_step | ||
|
|
||
|
|
||
| def test_sbom_inventory_scheduler_authenticates_git_before_publication() -> None: | ||
| """The non-persistent checkout must establish Git auth before remote mutation.""" | ||
| publication_step = _step_body("Open or update inventory PR") | ||
| auth_index = publication_step.index("gh auth setup-git") | ||
| first_remote_index = min( | ||
| publication_step.index("git ls-remote"), | ||
| publication_step.index("git push"), | ||
| ) | ||
| assert auth_index < first_remote_index | ||
|
|
||
|
|
||
| def test_sbom_inventory_scheduler_does_not_force_push() -> None: | ||
| """Recurring publication must preserve concurrent branch history.""" | ||
| publication_step = _step_body("Open or update inventory PR") | ||
| assert "--force" not in publication_step | ||
| assert "--force-with-lease" not in publication_step |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.