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
13 changes: 13 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,19 @@ permissions:
contents: read

jobs:
public-boundary:
name: Public repository boundary
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Reject private-only material
run: python scripts/check_public_boundary.py

test:
name: Python ${{ matrix.python }} / ${{ matrix.os }}
runs-on: ${{ matrix.os }}
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Enforce public repository boundary
run: python scripts/check_public_boundary.py
- name: Build wheel and source distribution
run: |
python -m pip install build
Expand Down Expand Up @@ -78,10 +82,16 @@ jobs:
needs: [python, native-linux, native-macos]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
path: release-assets
merge-multiple: true
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- name: Validate release payload
shell: bash
run: |
Expand All @@ -92,6 +102,8 @@ jobs:
test -f release-assets/skill-runtime-hook-native-darwin-arm64
test -f release-assets/skill-runtime.pyz
test -f release-assets/install.sh
- name: Enforce public release boundary
run: python scripts/check_public_boundary.py --release-dir release-assets
- uses: softprops/action-gh-release@v2
with:
files: release-assets/*
Expand Down
16 changes: 16 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ Keep integrations behind versioned adapters and preserve raw source events
separately from normalized and inferred records. New behavior must strengthen
the Skill-specific run panorama or diagnosis workflow.

## Public repository boundary

This repository and every published release contain public material only. Do
not commit private infrastructure URLs, organization-only installation or
distribution instructions, credentials, employee identity data, or other
non-public content.

Run the same fail-closed check used by CI before opening a pull request:

```bash
python scripts/check_public_boundary.py
```

The release workflow repeats this check against both the source tree and the
contents of packaged archives before uploading any asset.

## Pull requests

1. Open an issue for substantial behavior or event-model changes.
Expand Down
160 changes: 160 additions & 0 deletions scripts/check_public_boundary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#!/usr/bin/env python3
"""Fail closed when public source or release assets contain internal material."""

from __future__ import annotations

import argparse
import subprocess
import sys
import tarfile
import zipfile
from pathlib import Path, PurePosixPath
from typing import Iterable


REPOSITORY_ROOT = Path(__file__).resolve().parents[1]

# Assemble markers so this policy file does not match itself. Keep the list
# intentionally narrow and high-confidence: these values identify private
# infrastructure, not ordinary uses of words such as "internal" in source code.
BLOCKED_CONTENT = (
("private Alibaba domain", b"alibaba" + b"-inc.com"),
("private Alipay domain", b"alipay" + b"-inc.com"),
("private Alipay domain", b"alipay" + b".com"),
("private ATA domain", b"ata" + b"tech.org"),
("private Yuque domain", b"ali" + b"yuque."),
("private Aliyun domain", b"aliyun" + b"-inc.com"),
("private proxy domain", b"intranet" + b"proxy."),
("private CLI distribution reference", b"cli" + b"-hub."),
("private distribution wording", b"Alibaba " + b"internal distribution"),
("private installation wording", b"internal installation " + b"guide"),
("private CLI distribution wording", b"Aone CLI " + b"Hub"),
)

BLOCKED_PATHS = {
"/".join(("docs", "internal" + "-installation.md")),
"/".join(("scripts", "install" + "-internal.sh")),
}


def _normalized_path(value: str) -> str:
return PurePosixPath(value.replace("\\", "/")).as_posix().lstrip("./").lower()


def scan_path_name(label: str) -> list[str]:
normalized = _normalized_path(label)
if normalized in BLOCKED_PATHS or any(
normalized.endswith(f"/{blocked}") for blocked in BLOCKED_PATHS
):
return [f"{label}: private-only path is not allowed in the public repository"]
return []


def scan_blob(label: str, payload: bytes) -> list[str]:
lowered = payload.lower()
findings = []
for description, marker in BLOCKED_CONTENT:
if marker.lower() in lowered:
findings.append(f"{label}: contains {description}")
return findings


def scan_archive(path: Path, label: str | None = None) -> list[str]:
display = label or str(path)
findings = scan_path_name(display)
try:
if zipfile.is_zipfile(path):
with zipfile.ZipFile(path) as archive:
for member in archive.infolist():
member_label = f"{display}!{member.filename}"
findings.extend(scan_path_name(member_label))
if not member.is_dir():
findings.extend(scan_blob(member_label, archive.read(member)))
return findings
if tarfile.is_tarfile(path):
with tarfile.open(path, mode="r:*") as archive:
for member in archive.getmembers():
member_label = f"{display}!{member.name}"
findings.extend(scan_path_name(member_label))
if member.isfile():
extracted = archive.extractfile(member)
if extracted is not None:
findings.extend(scan_blob(member_label, extracted.read()))
return findings
findings.extend(scan_blob(display, path.read_bytes()))
except (OSError, tarfile.TarError, zipfile.BadZipFile) as error:
findings.append(f"{display}: could not be inspected: {error}")
return findings


def tracked_files(root: Path) -> Iterable[Path]:
result = subprocess.run(
["git", "ls-files", "-z"],
cwd=root,
check=True,
stdout=subprocess.PIPE,
)
for relative in result.stdout.decode("utf-8", errors="strict").split("\0"):
if relative:
yield root / relative


def scan_source_tree(root: Path) -> list[str]:
findings = []
for path in tracked_files(root):
relative = path.relative_to(root).as_posix()
findings.extend(scan_path_name(relative))
if path.is_file():
findings.extend(scan_blob(relative, path.read_bytes()))
return findings


def scan_git_metadata(root: Path) -> list[str]:
result = subprocess.run(
["git", "log", "--format=%H%x00%B%x00", "HEAD"],
cwd=root,
check=True,
stdout=subprocess.PIPE,
)
return scan_blob("public commit history", result.stdout)


def scan_release_directory(directory: Path) -> list[str]:
findings = []
for path in sorted(directory.rglob("*")):
if path.is_file():
findings.extend(scan_archive(path, path.relative_to(directory).as_posix()))
return findings


def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--release-dir",
type=Path,
help="also inspect every release file and supported archive member",
)
parser.add_argument(
"--skip-history",
action="store_true",
help="skip public commit-message inspection",
)
args = parser.parse_args()

findings = scan_source_tree(REPOSITORY_ROOT)
if not args.skip_history:
findings.extend(scan_git_metadata(REPOSITORY_ROOT))
if args.release_dir is not None:
findings.extend(scan_release_directory(args.release_dir.resolve()))

if findings:
print("Public boundary check failed:", file=sys.stderr)
for finding in sorted(set(findings)):
print(f"- {finding}", file=sys.stderr)
return 1
print("Public boundary check passed.")
return 0


if __name__ == "__main__":
raise SystemExit(main())
39 changes: 39 additions & 0 deletions tests/test_public_boundary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import importlib.util
import tempfile
import unittest
import zipfile
from pathlib import Path


SCRIPT_PATH = Path(__file__).resolve().parents[1] / "scripts" / "check_public_boundary.py"
SPEC = importlib.util.spec_from_file_location("check_public_boundary", SCRIPT_PATH)
assert SPEC and SPEC.loader
BOUNDARY = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(BOUNDARY)


class PublicBoundaryTests(unittest.TestCase):
def test_clean_public_content_passes(self):
self.assertEqual(BOUNDARY.scan_blob("README.md", b"Public release docs"), [])

def test_private_domain_is_rejected(self):
marker = b"https://service." + b"alibaba" + b"-inc.com/path"
findings = BOUNDARY.scan_blob("README.md", marker)
self.assertTrue(any("private Alibaba domain" in item for item in findings))

def test_private_only_path_is_rejected(self):
path = "/".join(("docs", "internal" + "-installation.md"))
self.assertTrue(BOUNDARY.scan_path_name(path))

def test_embedded_archive_content_is_rejected(self):
marker = b"https://service." + b"aliyun" + b"-inc.com/path"
with tempfile.TemporaryDirectory() as directory:
archive_path = Path(directory) / "release.zip"
with zipfile.ZipFile(archive_path, "w") as archive:
archive.writestr("docs/help.txt", marker)
findings = BOUNDARY.scan_archive(archive_path)
self.assertTrue(any("private Aliyun domain" in item for item in findings))


if __name__ == "__main__":
unittest.main()
Loading