Skip to content
Merged
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: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ tools/capture_pack_report.sh
python3 -W error -m unittest discover -s tests -v
```

Current verified baseline: **89 tests**, **9/9 graph invariants**, **7/7 pack/index checks**, **57 isolated Git invocations** in the DAG evidence run, two independently replayed evidence packages, and two attested offline browser captures.
Current verified baseline: **90 tests**, **9/9 graph invariants**, **7/7 pack/index checks**, **57 isolated Git invocations** in the DAG evidence run, two independently replayed evidence packages, and two attested offline browser captures.

| Artifact | What it proves |
|---|---|
Expand Down
4 changes: 4 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ The command boundary is deliberately narrow:
- temporary roots with symlinked path components are rejected.
- stdout and stderr are captured in private temporary files and rejected before loading into memory when either exceeds 1 MiB.

## Evidence file permissions

Evidence refreshes reject symlinked targets and multi-link files, stage each generated artifact as owner-read/write only (`0600`), fsync it, and atomically replace the managed target. Committed evidence is intentionally public synthetic data and may be checked out with ordinary repository permissions; this pipeline must not be used for secrets or private inputs.

## Pack/index closed subset

The pack experiment passes only three fixed synthetic blob IDs to `git pack-objects`; it does not accept a repository path, revision, ref, or caller-provided object list. Generated `.pack` and `.idx` files must be regular, single-link files no larger than 1 MiB and must remain the same inode and size across the bounded read.
Expand Down
2 changes: 1 addition & 1 deletion docs/assets/evidence-pipeline.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions docs/demo/git-dag-v1/capture-attestation.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"schema_version": "git-dag-browser-capture-attestation/v1",
"script": {
"path": "tools/capture_report.sh",
"sha256": "74dcf776035069299067544d3bd7b8a1443724f1bd7b842876ea53831b0d8441",
"sha256": "124a296ccf38d36a5220c62ea4730fbba2b21414e26b668ded42d855a691d6d5",
"size": 10937
},
"viewport": {
Expand All @@ -55,6 +55,6 @@
"receipt": {
"algorithm": "sha256",
"canonicalization": "UTF-8 JSON; sorted keys; compact separators",
"sha256": "7c9b58040ada09b40dc57e21c36767d044ec08e8e6a1c928e7dd5da0941ff238"
"sha256": "6e1199b957e5e7cdaf0af582d1547ac5a63137b3a0a3b9eadcb85673e09576b5"
}
}
10 changes: 5 additions & 5 deletions docs/demo/git-dag-v1/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"path": "docs/assets/evidence-pipeline.svg",
"role": "evidence pipeline derived from execution summary",
"sha256": "55504a79c5025689c167020db503ddaf64adb1542776254d5dedb2fa11f8b157",
"sha256": "c20efa2ff42ce32a205d9c83343abae9b43c94826c5a19042b2d2a188b707fe3",
"size": 4600
},
{
Expand Down Expand Up @@ -33,7 +33,7 @@
{
"path": "docs/demo/git-dag-v1/capture-attestation.json",
"role": "capture provenance binding browser, isolation, input, and outputs",
"sha256": "ea18a02969796afd0900dc5883ab29490bb517bf0f8d74f7f5f6043cb36a7f08",
"sha256": "2df440c8071e0b4ab839527176660ce832acb97b0634915bcf2d795c074a737f",
"size": 1990
},
{
Expand Down Expand Up @@ -69,7 +69,7 @@
],
"capture": {
"attestation": "docs/demo/git-dag-v1/capture-attestation.json",
"attestation_receipt_sha256": "7c9b58040ada09b40dc57e21c36767d044ec08e8e6a1c928e7dd5da0941ff238",
"attestation_receipt_sha256": "6e1199b957e5e7cdaf0af582d1547ac5a63137b3a0a3b9eadcb85673e09576b5",
"browser_binary": "/ms-playwright/chromium_headless_shell-1193/chrome-linux/headless_shell",
"browser_sha256": "003728e0b77eb9d52e4d258594bd55ce22ecd245eb6d3b6858fbd844c901ad7d",
"browser_version": "Chromium 140.0.7339.186",
Expand Down Expand Up @@ -140,12 +140,12 @@
},
{
"path": "tools/generate_evidence.py",
"sha256": "442346818e239a4ab9b4fa9804929e61bf266dc7145a543fb40076298d47ce6b",
"sha256": "9276b529ec13eaa6ceda1cab1282b6679a689697e5c6930ca17b5e0bc8ba3a92",
"size": 49590
},
{
"path": "tools/capture_report.sh",
"sha256": "74dcf776035069299067544d3bd7b8a1443724f1bd7b842876ea53831b0d8441",
"sha256": "124a296ccf38d36a5220c62ea4730fbba2b21414e26b668ded42d855a691d6d5",
"size": 10937
}
]
Expand Down
4 changes: 2 additions & 2 deletions docs/demo/git-pack-index-v1/capture-attestation.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
"schema_version": "git-pack-browser-capture-attestation/v1",
"script": {
"path": "tools/capture_pack_report.sh",
"sha256": "3477eeec4a10026fe95b87803f68657069c77f2b491bd69d3cddd56777924f0b",
"sha256": "7cddc4ec1ba66e203195d9a9eb3c454fe39b3790472106bad410cfc9d313ce8f",
"size": 11083
},
"viewport": {
Expand All @@ -55,6 +55,6 @@
"receipt": {
"algorithm": "sha256",
"canonicalization": "UTF-8 JSON; sorted keys; compact separators",
"sha256": "9709eafc99c848f825d083b795a9ba2802109d76b21289e2a4f72c445519c35b"
"sha256": "6495ad5876921dcc8f1c6ad01573507c5107512ca3d1256d3e42f6357ac4f622"
}
}
15 changes: 10 additions & 5 deletions docs/demo/git-pack-index-v1/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
{
"path": "docs/demo/git-pack-index-v1/capture-attestation.json",
"role": "pack capture provenance and isolation attestation",
"sha256": "18e5fea29af65a0fd15b2959486b282f75425f1dabb1df20a556b57bf3c22757",
"sha256": "724b0c25f4298756385182cbb48a5c260c59051e4f06e21e3495b16c47655b3a",
"size": 2011
},
{
Expand Down Expand Up @@ -69,7 +69,7 @@
],
"capture": {
"attestation": "docs/demo/git-pack-index-v1/capture-attestation.json",
"attestation_receipt_sha256": "9709eafc99c848f825d083b795a9ba2802109d76b21289e2a4f72c445519c35b",
"attestation_receipt_sha256": "6495ad5876921dcc8f1c6ad01573507c5107512ca3d1256d3e42f6357ac4f622",
"browser_binary": "/ms-playwright/chromium_headless_shell-1193/chrome-linux/headless_shell",
"browser_sha256": "003728e0b77eb9d52e4d258594bd55ce22ecd245eb6d3b6858fbd844c901ad7d",
"browser_version": "Chromium 140.0.7339.186",
Expand Down Expand Up @@ -138,14 +138,19 @@
"sha256": "03648331670cdc350fd486b2939e4914e90ee7ca7c2ea2b5cabd4eced21fe766",
"size": 2335
},
{
"path": "tools/generate_evidence.py",
"sha256": "9276b529ec13eaa6ceda1cab1282b6679a689697e5c6930ca17b5e0bc8ba3a92",
"size": 49590
},
{
"path": "tools/generate_pack_evidence.py",
"sha256": "2a85d4e5d3a98cca6e562eae7fad28e5f64d61bd02f973dcffdade274b89514d",
"size": 41315
"sha256": "234af1ac72189cec9034d3b7f396f6950b01564495208a8891620bb7721ec3a9",
"size": 41376
},
{
"path": "tools/capture_pack_report.sh",
"sha256": "3477eeec4a10026fe95b87803f68657069c77f2b491bd69d3cddd56777924f0b",
"sha256": "7cddc4ec1ba66e203195d9a9eb3c454fe39b3790472106bad410cfc9d313ce8f",
"size": 11083
}
]
Expand Down
23 changes: 23 additions & 0 deletions tests/test_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
import json
from pathlib import Path
import re
import stat
import struct
import tempfile
import unittest
from unittest.mock import patch
import zlib

from tools import generate_evidence
Expand Down Expand Up @@ -160,6 +163,14 @@ def test_capture_toolchain_is_digest_pinned(self) -> None:
self.assertRegex(capture["browser_sha256"], r"^[0-9a-f]{64}$")
self.assertEqual(capture["network"], "none")
self.assertEqual(capture["viewport"], {"height": 1800, "width": 1440})
script = (ROOT / "tools/capture_report.sh").read_text()
self.assertIn(
'chmod 0600 "$temporary_root/output/git-dag-report.png"',
script,
)
self.assertIn('chmod 0600 "$dom_path"', script)
self.assertIn("os.chmod(temporary_path, 0o600)", script)
self.assertNotIn("chmod 0644", script)

def test_sources_are_hashed_and_path_relative(self) -> None:
for source in self.manifest["sources"]:
Expand Down Expand Up @@ -247,5 +258,17 @@ def test_readme_baseline_matches_current_evidence(self) -> None:
self.assertIn("11 local subcommands are allow-listed", readme)


def test_writer_keeps_generated_files_owner_only(self) -> None:
with tempfile.TemporaryDirectory() as temporary_root:
root = Path(temporary_root)
with patch.object(generate_evidence, "ROOT", root):
generate_evidence._write_artifacts(
{Path("nested/evidence.txt"): b"public synthetic evidence\n"}
)
target = root / "nested/evidence.txt"
self.assertEqual(target.read_bytes(), b"public synthetic evidence\n")
self.assertEqual(stat.S_IMODE(target.stat().st_mode), 0o600)


if __name__ == "__main__":
unittest.main()
8 changes: 8 additions & 0 deletions tests/test_pack_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ def test_manifest_binds_artifacts_capture_and_sources(self) -> None:
{
"git_dag_lab/pack.py",
"git_dag_lab/cli.py",
"tools/generate_evidence.py",
"tools/generate_pack_evidence.py",
"tools/capture_pack_report.sh",
},
Expand Down Expand Up @@ -229,6 +230,13 @@ def test_capture_toolchain_is_digest_pinned_and_network_disabled(self) -> None:
self.assertIn("--network none", script)
self.assertIn("--pull=never", script)
self.assertIn("--read-only", script)
self.assertIn(
'chmod 0600 "$temporary_root/output/git-pack-report.png"',
script,
)
self.assertIn('chmod 0600 "$dom_path"', script)
self.assertIn("os.chmod(temporary_path, 0o600)", script)
self.assertNotIn("chmod 0644", script)
self.assertNotRegex(script, re.compile(r"(?m)^\s*curl\b|\bwget\b"))


Expand Down
6 changes: 3 additions & 3 deletions tools/capture_pack_report.sh
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ if [[ $(sha256sum "$repo_root/tools/capture_pack_report.sh" | awk '{print $1}')
exit 1
fi

chmod 0644 "$temporary_root/output/git-pack-report.png"
chmod 0644 "$dom_path"
chmod 0600 "$temporary_root/output/git-pack-report.png"
chmod 0600 "$dom_path"
mv -f -- "$temporary_root/output/git-pack-report.png" "$output_path"
mv -f -- "$dom_path" "$rendered_dom_path"

Expand Down Expand Up @@ -312,7 +312,7 @@ try:
temporary.flush()
os.fsync(temporary.fileno())
temporary_path = Path(temporary.name)
os.chmod(temporary_path, 0o644)
os.chmod(temporary_path, 0o600)
os.replace(temporary_path, attestation_path)
temporary_path = None
finally:
Expand Down
6 changes: 3 additions & 3 deletions tools/capture_report.sh
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ if [[ $(sha256sum "$repo_root/tools/capture_report.sh" | awk '{print $1}') != "$
exit 1
fi

chmod 0644 "$temporary_root/output/git-dag-report.png"
chmod 0644 "$dom_path"
chmod 0600 "$temporary_root/output/git-dag-report.png"
chmod 0600 "$dom_path"
mv -f -- "$temporary_root/output/git-dag-report.png" "$output_path"
mv -f -- "$dom_path" "$rendered_dom_path"

Expand Down Expand Up @@ -312,7 +312,7 @@ try:
temporary.flush()
os.fsync(temporary.fileno())
temporary_path = Path(temporary.name)
os.chmod(temporary_path, 0o644)
os.chmod(temporary_path, 0o600)
os.replace(temporary_path, attestation_path)
temporary_path = None
finally:
Expand Down
2 changes: 1 addition & 1 deletion tools/generate_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -1011,7 +1011,7 @@ def _write_artifacts(generated: Mapping[Path, bytes]) -> None:
temporary.flush()
os.fsync(temporary.fileno())
temporary_path = Path(temporary.name)
os.chmod(temporary_path, 0o644)
os.chmod(temporary_path, 0o600)
os.replace(temporary_path, target)
temporary_path = None
finally:
Expand Down
1 change: 1 addition & 0 deletions tools/generate_pack_evidence.py
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,7 @@ def build_artifacts(*, allow_missing_screenshot: bool) -> dict[Path, bytes]:
"sources": [
_source_row(Path("git_dag_lab/pack.py")),
_source_row(Path("git_dag_lab/cli.py")),
_source_row(Path("tools/generate_evidence.py")),
_source_row(Path("tools/generate_pack_evidence.py")),
_source_row(Path("tools/capture_pack_report.sh")),
],
Expand Down
Loading