Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ commit and exposed without running build hooks; a lone `--require-hashes`
directive is not trust evidence. See
[`docs/doctoring/opencode-exact-vcs-dependency-evidence.md`](docs/doctoring/opencode-exact-vcs-dependency-evidence.md).
Conflict-scope roots fail closed when the immediate parent directory is a symbolic link.
Governance SBOM Markdown is a rendering-integrity surface: untrusted inventory strings are neutralized before interpolation. See [`docs/doctoring/sbom-markdown-data-integrity.md`](docs/doctoring/sbom-markdown-data-integrity.md).

All 18 product hourly review-repair callers (OriginWeave at minute 10, nonnest2 at minute 16, and 16 others) are one file, [`.github/workflows/hourly-review-repair.yml`](.github/workflows/hourly-review-repair.yml), a `github.event.schedule` lookup table rather than 18 near-copy-pasted files. See [`docs/doctoring/hourly-review-repair-single-file-consolidation.md`](docs/doctoring/hourly-review-repair-single-file-consolidation.md); the per-repository doctoring records (e.g. [`docs/doctoring/originweave-hourly-review-caller.md`](docs/doctoring/originweave-hourly-review-caller.md), [`docs/doctoring/nonnest2-hourly-review-caller.md`](docs/doctoring/nonnest2-hourly-review-caller.md)) remain as historical background per repository.
Organization edge runtimes use Cloudflare Pingora. Do not add or preserve active Nginx containers, packages, commands, service/config files, or Kubernetes Nginx ingress annotations/classes. Read [`docs/policies/PINGORA_EDGE_POLICY.md`](docs/policies/PINGORA_EDGE_POLICY.md) and ADR-0019 before changing HTTP edge, static-serving, ingress, TLS, or proxy deployment behavior.

Expand Down
10 changes: 10 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ flowchart TD
Labels --> Verify
```

## SBOM Markdown integrity

`scripts/ci/sbom_inventory_aggregator.py` keeps machine-readable JSON
lossless and renders the governance Markdown inventory only after
`_markdown_text` collapses line structure and encodes active CommonMark /
GFM punctuation. Missing repository evidence increments `error_count` and
marks the receipt incomplete.
Comment on lines +67 to +73

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Minor Markdown formatting quirks in ARCHITECTURE.md

The new ## SBOM Markdown integrity heading is placed with no blank line separating it from the preceding paragraph (ARCHITECTURE.md), and two consecutive blank lines were inserted between list items in the 'Related durable documents' list (ARCHITECTURE.md). CommonMark/GFM still renders the ATX heading (headings may interrupt paragraphs) and still renders the trailing bullet, so these are cosmetic only and do not break the rendered document.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


The metadata reconciler is convergent and mode-aware. Already-correct
descriptions/topics and legacy default-branch `/docs` Pages sites receive no
write; absent or drifted legacy Pages state is created/updated, and disabled
Expand Down Expand Up @@ -262,6 +270,8 @@ resolver conflict.
— import-only exact source dependencies for networkless coverage.
- [`docs/doctoring/fast-mlsirm-hourly-review-caller.md`](docs/doctoring/fast-mlsirm-hourly-review-caller.md)
— product-specific psychometric repair heartbeat and scientific gates.


- [`docs/doctoring/exact-artifact-sbom-attestation.md`](docs/doctoring/exact-artifact-sbom-attestation.md)
— current increment's attestation decision and APA 7th citations.
- [`docs/doctoring/sandboxed-web-readiness-loopback-boundary.md`](docs/doctoring/sandboxed-web-readiness-loopback-boundary.md)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,7 @@ Semantic Versioning where the repository publishes a release.

- Fail closed when GitHub dependency-review evidence is unavailable (non-200, transport failure, or truncated compare) instead of treating HTTP 403/404 as a clean skip; the probe checks out the exact head SHA and never prints the API body.
- Keep the Quarantine Sandbox Runtime caller read-only and model-secret-free, grant only job-scoped OIDC to the reusable scheduler, and preserve the product boundary in which the sandbox returns artifact-analysis evidence while hosts retain WAF/IDS, admission, final verdict, incident, and retention authority.
- Neutralize untrusted SBOM repository, component, license, and timestamp text in the governance Markdown inventory and publish `error_count` / completeness so missing evidence cannot look like a clean roll-up.
- Reject `.github/` and `scripts/ci/` from review-thread-derived autofix path authority so an untrusted inline reviewer cannot authorize the write-capable repair agent to modify workflows, CODEOWNERS, actions, scheduler code, or CI helpers that govern its own control plane.
- Require the model-write snapshot and exact-path allowlist to remain outside the pull-request worktree, checking both absolute and resolved locations so repository-local controls and outside-looking symlinks resolving into the repository fail closed before they can authorize or verify model changes.
- Snapshot the complete pre-model worktree for ordinary and conflict repair and reject every model-caused created, deleted, modified, mode-changed, retargeted, ignored, dangling, directory-backed, external-link, metadata-race, or out-of-scope path before staging or push.
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ repeatable compile command.
- **Product hourly callers** stay thin. Do not hard-code OriginWeave, aFIPC, naruon, or Keyverse
into `pr-review-fix-scheduler.yml`. The model credential remains `NVIDIA_NIM_API_KEY`
on the worker, never `COPILOT_GITHUB_TOKEN`.
- **SBOM Markdown** is not trusted input. `render_inventory_markdown` must neutralize
repository, component, license, and timestamp text; JSON stays lossless.
- **Central review routes through the vendored contextual-orchestrator gateway.**
`pr-review-autofix.yml` provisions `scripts/ci/contextual_orchestrator_review_sidecar.sh`
(the five provider secrets flow into its KV; the writer runs
Expand Down
66 changes: 66 additions & 0 deletions docs/doctoring/sbom-markdown-data-integrity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# SBOM Markdown data-integrity boundary

## Incident

The organization SBOM inventory treated repository names, component names,
versions, license expressions, API failure details, and the generation label as
trusted Markdown. Newlines, table delimiters, link brackets, code delimiters,
or raw-HTML delimiters in dependency metadata could therefore create forged
rows, headings, links, or presentation markup in the governance-facing report.
The machine-readable JSON remained structurally valid, but reviewers could be
misled by its human-readable companion.

## Decision

The renderer now passes every externally derived string through one bounded
text encoder before interpolation. It collapses CR/LF line structure and emits
numeric or named character references for ampersands, backslashes, table pipes,
angle brackets, brackets, backticks, URI punctuation, mention markers, and issue
reference markers. Encoding dots, colons, at-signs, and number signs, plus
asterisks, underscores, and tildes, additionally prevents emphasis and
strikethrough presentation. The existing punctuation encoding prevents GFM
bare URLs, email autolinks, GitHub mentions, and issue references
from becoming active while browsers still render the intended text. Counts and
fixed policy labels remain native values. The JSON inventory deliberately
retains the original data so machine consumers and incident investigators do
not lose evidence.

This is a rendering-integrity control, not license verification. A component
with an unknown or policy-relevant license remains flagged by the existing
policy logic after its display text is neutralized.

The report summary also publishes `error_count` and an explicit completeness
state. Missing repository SBOM evidence therefore cannot be interpreted as a
clean zero-finding inventory merely because the unavailable repository has no
components in the roll-up.

Repository unavailability uses `error is not None` in both the JSON summary and
Markdown per-repository rendering. An empty error string is still unavailable
evidence rather than an empty repository, so the human and machine channels
cannot disagree about completeness.

## Test-first evidence

`tests/test_sbom_markdown_integrity.py` first demonstrated that crafted SBOM
metadata produced a second-level heading and a forged table row. A follow-up
RED fixture proved that bare URLs, email addresses, mentions, and issue numbers
remained active without brackets. The accepted contract rejects active row,
heading, link, autolink, mention, issue-reference, and raw-HTML structure while
keeping the corresponding text visibly represented through character
references.

## Failure, recovery, and rollback

Unexpected display text should be compared with `inventory.json`, which is the
lossless evidence channel. Rollback requires an independently reviewed renderer
that proves all externally derived strings remain text in every Markdown
context. Removing the encoder or escaping only table pipes is not acceptable
because headings, links, code spans, and raw HTML are separate parse surfaces.

## APA 7th references

GitHub. (2019). *GitHub Flavored Markdown specification* (Version 0.29-gfm).
https://github.github.com/gfm/

MacFarlane, J. (2024, January 28). *CommonMark specification* (Version 0.31.2).
https://spec.commonmark.org/0.31.2/
120 changes: 105 additions & 15 deletions scripts/ci/sbom_inventory_aggregator.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import argparse
import concurrent.futures
import json
import re
import subprocess
import sys
from dataclasses import dataclass, field
Expand Down Expand Up @@ -51,6 +52,11 @@
# Sentinel emitted by SBOM tooling when it cannot determine a license.
NOASSERTION = "NOASSERTION"

_OWNER_LOGIN_PATTERN = re.compile(
r"[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}"
)
_REPOSITORY_NAME_PATTERN = re.compile(r"[A-Za-z0-9._-]{1,100}")


@dataclass(frozen=True)
class Component:
Expand Down Expand Up @@ -202,8 +208,10 @@ def build_inventory(repo_inventories: Sequence[RepoInventory]) -> dict[str, Any]
license_totals: dict[str, int] = {}
flagged: list[dict[str, str]] = []
total_components = 0
error_count = 0

for repo_inventory in sorted(repo_inventories, key=lambda r: r.repo.lower()):
error_count += int(repo_inventory.error is not None)
components_payload = [
{
"name": component.name,
Expand Down Expand Up @@ -242,6 +250,8 @@ def build_inventory(repo_inventories: Sequence[RepoInventory]) -> dict[str, Any]
"repo_count": len(repos_payload),
"component_count": total_components,
"flagged_count": len(flagged),
"error_count": error_count,
"complete": error_count == 0,
"policy": "commercial-license-only",
},
"license_totals": dict(sorted(license_totals.items())),
Expand All @@ -250,13 +260,37 @@ def build_inventory(repo_inventories: Sequence[RepoInventory]) -> dict[str, Any]
}


def _markdown_text(value: Any) -> str:
"""Return untrusted inventory text without active Markdown structure."""
text = str(value).replace("\r\n", " ").replace("\r", " ").replace("\n", " ")
replacements = {
"&": "&",
"\\": "\",
"|": "|",
"<": "&lt;",
">": "&gt;",
"[": "&#91;",
"]": "&#93;",
"`": "&#96;",
"*": "&#42;",
"_": "&#95;",
"~": "&#126;",
":": "&#58;",
"@": "&#64;",
"#": "&#35;",
".": "&#46;",
"$": "&#36;",
}
Comment on lines +263 to +283

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Version/license text is now entity-encoded in the Markdown report

Because _markdown_text encodes . and : (scripts/ci/sbom_inventory_aggregator.py:278,281), ordinary values such as 1.2.3 render in the Markdown source as 1&#46;2&#46;3 and timestamps as ...T00&#58;00&#58;00Z. These display correctly in any HTML/Markdown renderer and inventory.json stays lossless, so this is the intended rendering-integrity tradeoff. Flagged only so reviewers are not surprised that the raw inventory.md text is heavily entity-escaped and that grepping the Markdown for a literal version/URL will miss.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

return "".join(replacements.get(character, character) for character in text)
Comment thread
coderabbitai[bot] marked this conversation as resolved.


def render_inventory_markdown(inventory: dict[str, Any], *, generated_at: str) -> str:
"""Render the consolidated inventory as a governance-facing markdown page."""
summary = inventory["summary"]
lines: list[str] = [
"# Organization SBOM inventory",
"",
f"Generated: {generated_at}",
f"Generated: {_markdown_text(generated_at)}",
"",
"One central view of every managed repository's software components,",
"versions, and licenses. Feeds license and vulnerability governance",
Expand All @@ -268,6 +302,8 @@ def render_inventory_markdown(inventory: dict[str, Any], *, generated_at: str) -
f"- Components: {summary['component_count']}",
f"- Policy: {summary['policy']}",
f"- Flagged licenses: {summary['flagged_count']}",
f"- SBOMs unavailable: {summary['error_count']}",
f"- Evidence completeness: {'complete' if summary['complete'] else 'incomplete'}",
"",
"## License roll-up",
"",
Expand All @@ -276,7 +312,7 @@ def render_inventory_markdown(inventory: dict[str, Any], *, generated_at: str) -
]
for license_key, count in inventory["license_totals"].items():
flag = " ⚠️" if is_flagged_license(license_key) else ""
lines.append(f"| {license_key}{flag} | {count} |")
lines.append(f"| {_markdown_text(license_key)}{flag} | {count} |")

lines.extend(["", "## Flagged components (policy violations)", ""])
if inventory["flagged_licenses"]:
Expand All @@ -288,17 +324,19 @@ def render_inventory_markdown(inventory: dict[str, Any], *, generated_at: str) -
)
for item in inventory["flagged_licenses"]:
lines.append(
f"| {item['repo']} | {item['name']} | {item['version'] or '—'} | {item['license']} |"
f"| {_markdown_text(item['repo'])} | {_markdown_text(item['name'])} | "
f"{_markdown_text(item['version'] or '—')} | "
f"{_markdown_text(item['license'])} |"
)
else:
lines.append("No copyleft or NOASSERTION components detected.")

lines.extend(["", "## Per-repository components", ""])
for repo in inventory["repos"]:
lines.append(f"### {repo['repo']}")
lines.append(f"### {_markdown_text(repo['repo'])}")
lines.append("")
if repo["error"]:
lines.append(f"SBOM unavailable: {repo['error']}")
if repo["error"] is not None:
lines.append(f"SBOM unavailable: {_markdown_text(repo['error'])}")
Comment on lines +338 to +339

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Empty-error repo now treated as unavailable in both channels

The render guard changed to repo["error"] is not None matches error_count's error is not None in build_inventory (scripts/ci/sbom_inventory_aggregator.py:214), so an empty-string error is reported as unavailable in both JSON and Markdown instead of rendering an empty component list.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

lines.append("")
continue
if not repo["components"]:
Expand All @@ -311,19 +349,39 @@ def render_inventory_markdown(inventory: dict[str, Any], *, generated_at: str) -
for component in repo["components"]:
flag = "yes" if component["flagged"] else "no"
lines.append(
f"| {component['name']} | {component['version'] or '—'} | {component['license']} | {flag} |"
f"| {_markdown_text(component['name'])} | "
f"{_markdown_text(component['version'] or '—')} | "
f"{_markdown_text(component['license'])} | {flag} |"
)
lines.append("")
return "\n".join(lines).rstrip() + "\n"


def write_inventory(inventory: dict[str, Any], markdown: str, output_dir: Path) -> None:
"""Write the JSON and markdown inventory artifacts to ``output_dir``."""
output_dir.mkdir(parents=True, exist_ok=True)
(output_dir / "inventory.json").write_text(
def _resolve_output_dir(output_dir: Path, *, base_dir: Path | None = None) -> Path:
"""Resolve an output directory while preventing writes outside the workspace."""
base = (base_dir or Path.cwd()).resolve()
resolved = (base / output_dir if not output_dir.is_absolute() else output_dir).resolve()
try:
resolved.relative_to(base)
except ValueError as exc:
raise ValueError("output directory must stay within the workspace") from exc
return resolved
Comment on lines +360 to +368

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Output dir resolution fails closed for absolute paths outside cwd

_resolve_output_dir (sbom_inventory_aggregator.py) rejects any absolute output_dir not under the base directory. The scheduler passes relative docs/sbom from the repo root, so there is no regression today, but any future caller using an absolute out-of-tree path now gets a hard failure.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.



def write_inventory(
inventory: dict[str, Any],
markdown: str,
output_dir: Path,
*,
base_dir: Path | None = None,
) -> None:
"""Write inventory artifacts only inside the configured workspace."""
safe_output_dir = _resolve_output_dir(output_dir, base_dir=base_dir)
safe_output_dir.mkdir(parents=True, exist_ok=True)
(safe_output_dir / "inventory.json").write_text(
json.dumps(inventory, indent=2, sort_keys=False) + "\n", encoding="utf-8"
)
(output_dir / "inventory.md").write_text(markdown, encoding="utf-8")
(safe_output_dir / "inventory.md").write_text(markdown, encoding="utf-8")


def _run(args: Sequence[str]) -> str: # pragma: no cover - thin subprocess wrapper
Expand All @@ -332,26 +390,54 @@ def _run(args: Sequence[str]) -> str: # pragma: no cover - thin subprocess wrap
return process.stdout


def _validate_owner_login(value: str) -> str:
"""Return a canonical GitHub owner login or reject an unsafe operand."""
if not isinstance(value, str) or _OWNER_LOGIN_PATTERN.fullmatch(value) is None:
raise ValueError("invalid GitHub organization login")
return value


def _validate_repo_full_name(value: str) -> str:
"""Return a canonical ``owner/repository`` name or reject it."""
if not isinstance(value, str) or value.count("/") != 1:
raise ValueError("invalid GitHub repository full name")
owner, repository = value.split("/", 1)
if (
_OWNER_LOGIN_PATTERN.fullmatch(owner) is None
or _REPOSITORY_NAME_PATTERN.fullmatch(repository) is None
or repository in {".", ".."}
or repository.startswith("-")
):
raise ValueError("invalid GitHub repository full name")
return value


def list_org_repos(org: str) -> list[str]: # pragma: no cover - network
"""List non-archived repositories for an organization via gh."""
validated_org = _validate_owner_login(org)
raw = _run(
[
"gh",
"repo",
"list",
org,
"--no-archived",
"--limit",
"500",
"--json",
"nameWithOwner",
"--",
validated_org,
]
)
return [entry["nameWithOwner"] for entry in json.loads(raw or "[]")]
return [
_validate_repo_full_name(entry["nameWithOwner"])
for entry in json.loads(raw or "[]")
Comment on lines 431 to +434

@devin-ai-integration devin-ai-integration Bot Aug 21, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: list_org_repos now fails closed on any unexpected repo name

list_org_repos (sbom_inventory_aggregator.py) now runs every discovered nameWithOwner through _validate_repo_full_name. This aborts the entire inventory run with ValueError if a single repository in the org fails the strict [A-Za-z0-9._-]{1,100} name / owner-login pattern, rather than skipping just that repo. In practice all real GitHub repo names satisfy the pattern (and .github is explicitly tested), so this is intended fail-closed behavior, not a bug. Worth noting only because a previously-tolerant discovery path is now all-or-nothing.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

]


def fetch_repo_sbom(repo: str) -> RepoInventory: # pragma: no cover - network
"""Fetch and parse one repository's dependency-graph SBOM via gh."""
repo = _validate_repo_full_name(repo)
try:
raw = _run(["gh", "api", f"/repos/{repo}/dependency-graph/sbom"])
except subprocess.CalledProcessError as exc:
Expand Down Expand Up @@ -448,7 +534,11 @@ def main(argv: list[str]) -> int: # pragma: no cover - CLI orchestration
if args.self_test:
self_test()
return 0
repos = args.repos if args.repos else list_org_repos(args.org)
repos = (
[_validate_repo_full_name(repo) for repo in args.repos]
if args.repos
else list_org_repos(args.org)
)
inventories = collect_inventories(repos)
inventory = build_inventory(inventories)
generated_at = args.generated_at or "unspecified"
Expand Down
Loading
Loading