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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ This project follows semantic-versioning guidance once recurring releases are ta

### Documentation-only updates

- Refreshed the README repository-layout tree to cover `docs/`, `examples/policies/`, `examples/schema-adapters/`, all four CI workflow examples, the combined report example, `scripts/`, and `package-skills.sh`, with `tests/test_readme_repository_layout.py` drift guards keeping the tree in sync with disk.
- Added `examples/ci/github-actions/agent-security-prompt-sarif.yml` for prompt-injection signal and exposure SARIF uploads.
- Added `docs/report-comparison.md` and `examples/ci/github-actions/agent-security-compare-reports.yml` for stored config-risk report comparison.
- Added `docs/schema-adapters.md` and Phase 12 regression coverage in `tests/test_phase12_schema_adapters.py` for adapter fixtures, explicit ignored fields, SARIF/Markdown adapter reporting, and cross-platform path serialization.
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -227,19 +227,30 @@ Boundary guide:
## Repository layout

```text
docs/
*.md
examples/
high-risk-agent-config.json
hardened-agent-config.json
baselines/
agent-security-baseline.json
config-shapes/
*.json
policies/
agent-security-policy.json
schema-adapters/
*.json
reports/
high-risk-agent-security-review.md
combined-browser-private-network-boundary.md
ci/
github-actions/
agent-security-strict.yml
agent-security-sarif.yml
agent-security-prompt-sarif.yml
agent-security-compare-reports.yml
scripts/
package_skills.py
skills/
agent-security/
SKILL.md
Expand All @@ -257,6 +268,7 @@ tests/
test_*.py
.github/workflows/
ci.yml
package-skills.sh
```

## Prompt-injection fixture corpus
Expand Down
95 changes: 95 additions & 0 deletions tests/test_readme_repository_layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"""Drift guards for the README repository-layout tree.

The README renders an abbreviated repository layout so new users can find the
scanners, examples, workflows, and docs quickly. These tests keep that tree
honest in both directions:

1. Every concrete path named in the tree must exist on disk, so renamed or
removed files cannot linger as stale documentation.
2. New `examples/ci/github-actions/` workflows must be added to the tree, since
that directory grows with every new downstream-integration example.
3. Core top-level entries users rely on (`skills/`, `docs/`, `scripts/`,
`package-skills.sh`, `.github/workflows/`) must stay listed.

Abbreviated glob entries such as `*.json`, `test_*.py`, and `*.txt / *.json`
are allowed and skipped by the existence checks so the tree stays compact.
"""

import re
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
README = ROOT / "README.md"
WORKFLOW_DIR = ROOT / "examples" / "ci" / "github-actions"

REQUIRED_TOP_LEVEL_ENTRIES = {
"examples/",
"skills/",
"tests/",
"scripts/",
"docs/",
"package-skills.sh",
".github/workflows/",
}


def _layout_lines() -> list[str]:
text = README.read_text(encoding="utf-8")
assert "## Repository layout" in text, "README must keep a Repository layout section"
section = text.split("## Repository layout", 1)[1]
parts = section.split("```", 2)
assert len(parts) >= 2, "Repository layout must be a fenced code block"
lines = parts[1].splitlines()
if lines and lines[0].strip() == "text":
lines = lines[1:]
return [line for line in lines if line.strip()]


def _layout_paths() -> list[tuple[int, str, str]]:
"""Reconstruct (depth, name, full-relative-path) rows from the tree."""
rows: list[tuple[int, str, str]] = []
stack: list[str] = []
for line in _layout_lines():
assert "\t" not in line, "layout tree must use spaces, not tabs"
indent = len(line) - len(line.lstrip(" "))
assert indent % 2 == 0, f"layout indentation must be a multiple of two: {line!r}"
depth = indent // 2
name = line.strip()
del stack[depth:]
rows.append((depth, name, "/".join(stack + [name])))
if name.endswith("/"):
stack.append(name[:-1])
return rows


def test_layout_entries_exist_on_disk() -> None:
for _depth, name, full in _layout_paths():
if "*" in name:
continue
path = ROOT / full
if name.endswith("/"):
assert path.is_dir(), f"layout lists missing directory: {full}"
else:
assert path.exists(), f"layout lists missing path: {full}"


def test_layout_lists_every_ci_workflow_example() -> None:
listed = {full for _depth, name, full in _layout_paths() if name.endswith(".yml")}
for workflow in sorted(WORKFLOW_DIR.glob("*.yml")):
assert workflow.relative_to(ROOT).as_posix() in listed, (
f"README layout must list new workflow example: {workflow.name}"
)


def test_layout_lists_core_top_level_entries() -> None:
top_level = {name.rstrip("/") for depth, name, _full in _layout_paths() if depth == 0}
missing = {entry.rstrip("/") for entry in REQUIRED_TOP_LEVEL_ENTRIES} - top_level
assert not missing, f"README layout must list core entries: {sorted(missing)}"


def test_layout_names_are_globe_or_plain() -> None:
"""Guard against tree rows the parser above cannot reconstruct."""
for _depth, name, _full in _layout_paths():
assert re.fullmatch(r"[A-Za-z0-9._/*-]+( / [A-Za-z0-9._/*-]+)*", name), (
f"unexpected layout entry shape: {name!r}"
)
Loading