Skip to content
Closed
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
33 changes: 33 additions & 0 deletions .githooks/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash

set -euo pipefail

repo_root="$(git rev-parse --show-toplevel)"
cd "$repo_root"

./scripts/check-public-safety.sh
python3 ./scripts/check-markdown-links.py

create_backup_ref() {
local backup_ref
backup_ref="backup/pre-push-main-$(date +%Y%m%d-%H%M%S)"
git branch "$backup_ref" HEAD >/dev/null 2>&1 || true
echo "Created local backup branch: $backup_ref"
}

while IFS=' ' read -r local_ref local_sha remote_ref remote_sha; do
if [ "$remote_ref" = "refs/heads/main" ]; then
create_backup_ref

if [ "${ALLOW_MAIN_PUSH:-0}" != "1" ]; then
echo
echo "Push to main blocked by local repo guard."
echo "Use a branch + PR flow by default."
echo "If you truly need an emergency direct push, rerun with:"
echo "ALLOW_MAIN_PUSH=1 git push origin HEAD:main"
exit 1
fi
fi
done

exit 0
18 changes: 18 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
## Summary

- What changed?
- Why now?

## Checks

- [ ] I ran `./scripts/check-public-safety.sh`
- [ ] I ran `python3 ./scripts/check-markdown-links.py`
- [ ] I reviewed [publication-safety-checklist.md](../docs/publication-safety-checklist.md)
- [ ] The change is public-safe and does not include private runtime wiring, credentials, or operational IDs
- [ ] Examples and screenshots are synthetic or safe for public distribution

## Reviewer Focus

- Privacy or publication risk:
- Claim quality or overstatement risk:
- Link or formatting risk:
35 changes: 35 additions & 0 deletions .github/workflows/repo-qa.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Repo QA

on:
pull_request:
push:
branches:
- main
- "codex/**"
workflow_dispatch:

permissions:
contents: read

jobs:
public-safe-qa:
name: Public-Safe QA
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Install ripgrep
run: sudo apt-get update && sudo apt-get install -y ripgrep

- name: Run public-safety scan
run: ./scripts/check-public-safety.sh

- name: Validate local Markdown links
run: python3 ./scripts/check-markdown-links.py
32 changes: 32 additions & 0 deletions docs/repo-publish-workflow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Repo Publish Workflow

This repo is public, so publication safety has to be enforced by default rather than remembered ad hoc.

## Intended Path

1. Make changes on a branch, not directly on `main`.
2. Open a pull request.
3. Let `Repo QA` run the public-safety scan and Markdown QA checks.
4. Review the PR against [`publication-safety-checklist.md`](./publication-safety-checklist.md).
5. Merge only after the checks pass.

## Local Guard On This Machine

This clone also uses a local `pre-push` hook in [`.githooks/pre-push`](../.githooks/pre-push) to make the safe path the default even before GitHub settings catch up.

- every push runs the public-safety scan and Markdown link checks
- any push to `main` creates a local backup branch first
- direct pushes to `main` are blocked unless you explicitly override with `ALLOW_MAIN_PUSH=1`

## What The Automated Gate Catches

- credential patterns and private key material
- Telegram bot tokens and operational IDs
- Slack workspace links and likely private dashboard links
- missing local Markdown targets

## What Still Needs GitHub Settings

The workflow is now in the repo, but GitHub branch protection should require the `Public-Safe QA` check before `main` can move.

That last step lives in GitHub settings, not in this public repository.
166 changes: 166 additions & 0 deletions scripts/check-markdown-links.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#!/usr/bin/env python3

from __future__ import annotations

import re
import subprocess
import sys
from pathlib import Path
from urllib.parse import unquote


ROOT = Path(__file__).resolve().parents[1]
SKIP_PREFIXES = ("http://", "https://", "mailto:", "tel:")
TITLE_SUFFIX_RE = re.compile(
r"""^(?P<target>.*?)(?:\s+(?:"[^"]*"|'[^']*'|\([^()]*\)))?\s*$"""
)


def tracked_markdown_files() -> list[Path]:
result = subprocess.run(
["git", "ls-files", "*.md"],
cwd=ROOT,
capture_output=True,
text=True,
check=True,
)
return [ROOT / line for line in result.stdout.splitlines() if line.strip()]


def normalize_target(raw_target: str) -> str:
target = raw_target.strip()
if not target:
return ""

if target.startswith("<"):
closing = target.find(">")
target = target[1:closing] if closing != -1 else target[1:]
else:
title_match = TITLE_SUFFIX_RE.match(target)
if title_match:
target = title_match.group("target").strip()

return unquote(target)


def should_skip(target: str) -> bool:
if not target or target.startswith("#"):
return True
return target.startswith(SKIP_PREFIXES)


def resolve_target(source: Path, target: str) -> Path:
path_only = target.split("#", 1)[0]
return (source.parent / path_only).resolve()


def find_closing_bracket(markdown: str, start: int) -> int:
depth = 1
index = start + 1

while index < len(markdown):
char = markdown[index]
if char == "\\":
index += 2
continue
if char == "[":
depth += 1
elif char == "]":
depth -= 1
if depth == 0:
return index
index += 1

return -1


def extract_parenthesized(markdown: str, start: int) -> tuple[str, int] | None:
depth = 0
in_angle = False
chars: list[str] = []
index = start + 1

while index < len(markdown):
char = markdown[index]
if char == "\\" and index + 1 < len(markdown):
chars.append(char)
chars.append(markdown[index + 1])
index += 2
continue

if char == "<" and not in_angle:
in_angle = True
elif char == ">" and in_angle:
in_angle = False
elif not in_angle and char == "(":
depth += 1
elif not in_angle and char == ")":
if depth == 0:
return ("".join(chars), index)
depth -= 1

chars.append(char)
index += 1

return None


def collect_targets(markdown: str) -> list[str]:
targets: list[str] = []
index = 0

while index < len(markdown):
char = markdown[index]
if char == "!" and index + 1 < len(markdown) and markdown[index + 1] == "[":
label_start = index + 1
elif char == "[":
label_start = index
else:
index += 1
continue

label_end = find_closing_bracket(markdown, label_start)
if label_end == -1 or label_end + 1 >= len(markdown) or markdown[label_end + 1] != "(":
index = label_start + 1
continue

extracted = extract_parenthesized(markdown, label_end + 1)
if extracted is None:
index = label_end + 1
continue

target, target_end = extracted
targets.append(target)
index = target_end + 1

return targets


def main() -> int:
failures: list[str] = []

for markdown_file in tracked_markdown_files():
content = markdown_file.read_text(encoding="utf-8")
for raw_target in collect_targets(content):
target = normalize_target(raw_target)
if should_skip(target):
continue

resolved = resolve_target(markdown_file, target)
if not resolved.exists():
failures.append(
f"{markdown_file.relative_to(ROOT)} -> {target} (missing: {resolved.relative_to(ROOT) if resolved.is_relative_to(ROOT) else resolved})"
)

if failures:
print("Markdown link checks failed:", file=sys.stderr)
for failure in failures:
print(f"- {failure}", file=sys.stderr)
return 1

print("Markdown link checks passed.")
return 0


if __name__ == "__main__":
raise SystemExit(main())
56 changes: 56 additions & 0 deletions scripts/check-public-safety.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash

set -euo pipefail

if ! command -v rg >/dev/null 2>&1; then
echo "ripgrep (rg) is required for public-safety checks." >&2
exit 1
fi

declare -a files=()
while IFS= read -r -d '' file; do
files+=("$file")
done < <(git ls-files -z)

if [ ${#files[@]} -eq 0 ]; then
echo "No tracked files to scan."
exit 0
fi

checks=(
"GitHub PAT|ghp_[A-Za-z0-9]{36}"
"GitHub fine-grained PAT|github_pat_[A-Za-z0-9_]{20,}"
"OpenAI key|sk-[A-Za-z0-9]{20,}"
"Slack token|xox[baprs]-[A-Za-z0-9-]{10,}"
"AWS access key|AKIA[0-9A-Z]{16}"
"Private key block|-----BEGIN (OPENSSH|RSA|DSA|EC|PGP) PRIVATE KEY-----"
"Credential assignment|(?i)(api[_-]?key|secret|token|password)\\s*[:=]\\s*[\"'][^\"'\\n]{8,}[\"']"
"Telegram bot token|[0-9]{8,10}:[A-Za-z0-9_-]{35}"
"Operational ID wiring|(?i)(chat[_ -]?id|thread[_ -]?id|sender[_ -]?id)\\s*[:=]\\s*[\"']?-?[0-9]{6,}[\"']?"
"Slack workspace link|https://[^ )]*slack\\.com/"
"Private dashboard link|https://[^ )]*(looker|mode\\.com|metabase|superset)[^ )]*"
)

failures=0

for rule in "${checks[@]}"; do
label=${rule%%|*}
regex=${rule#*|}

if rg --pcre2 -n -I --color=never "$regex" "${files[@]}" >/tmp/ai_plus_data_public_safety_match.txt 2>/dev/null; then
failures=1
echo
echo "[FAIL] ${label}"
cat /tmp/ai_plus_data_public_safety_match.txt
fi
done

rm -f /tmp/ai_plus_data_public_safety_match.txt

if [ "$failures" -ne 0 ]; then
echo
echo "Public-safety checks failed. Remove private runtime details, credentials, or operational identifiers before publishing."
exit 1
fi

echo "Public-safety checks passed."
Loading