Skip to content

security

Ary Rabelo edited this page Jul 22, 2026 · 3 revisions

Security & Trust Boundaries

Relevant source files

  • SECURITY.md
  • src/repodocs/publish.py
  • src/repodocs/_util.py

Overview

RepoDocs treats the target repository as untrusted input throughout generation and publishing. The security posture documented in SECURITY.md centers on three boundaries: what the tool reads and writes locally, what it scans before anything reaches a public remote, and how it resolves repository-relative paths so agent- or repo-supplied strings can't escape the intended directory tree.

Sources: SECURITY.md:L13-L25

Threat model summary

RepoDocs reads the target repository and writes generated output under the selected output directory, invokes a locally installed OMP, Claude Code, or Codex CLI when generation is requested, and may invoke Graphify during repodocs all. Network access is scoped to those external tools and to publishing against GitHub Pages / GitHub Wikis. The tool does not copy or bundle agent credentials during setup, and it explicitly warns that generated documentation can still expose sensitive repository content — reviewing output and repository visibility before publishing remains the operator's responsibility.

Sources: SECURITY.md:L13-L25

Path containment: safe_repo_file

safe_repo_file(repo, rel) in src/repodocs/_util.py is the single chokepoint used to resolve a repo-relative path string (as might come from a citation, a plan entry, or agent output) against the actual repository root. It rejects the input outright for absolute paths and NUL bytes, then resolves both the repo root and the candidate target with Path.resolve() (which follows symlinks) and calls target.relative_to(root) to confirm the resolved target still lives under the resolved root. Any ValueError, OSError, or RuntimeError — including .. traversal, symlink escapes outside the repo, and symlink loops — causes it to return None rather than raise. It also refuses to return a path that isn't a regular file.

Security & Trust Boundaries — diagram 1

Because resolution happens after symlink-following, a symlink inside the repo that points outside it is caught by the relative_to check rather than trusted at face value; every caller that needs to touch a repo-relative path derived from generated or agent content is expected to go through this function rather than concatenating paths directly.

Sources: src/repodocs/_util.py:L77-L91

Symlink rejection during staging

The publish pipeline in src/repodocs/publish.py applies the same "don't trust the filesystem tree" discipline when copying generated output into a staging directory. stage_publish refuses to stage any symlinked file or directory it encounters (put_file, put_dir, and the top-level subdirectory loop each raise ValueError on src.is_symlink()), and stage_wiki's read() helper does the same before reading a source Markdown page or plan.json. On the wiki-clone side, _reject_symlink_dest walks each path component of a destination inside the cloned wiki repo and refuses to write through any component that is itself a symlink, so a manually-tampered wiki clone can't redirect a write to an arbitrary location on disk.

Sources: src/repodocs/publish.py:L36-L78, src/repodocs/publish.py:L213-L266, src/repodocs/publish.py:L299-L308

Secret-pattern scanning before publish

Before any staged content can reach a public remote, staged_secret_findings walks every file in the staging directory and matches each line against PUBLISH_SECRET_PATTERNS: PEM-style private key headers, GitHub personal-access/OAuth tokens (ghp_/gho_/ghu_/ghs_/ghr_/github_pat_), and generic cloud/API key shapes (AWS AKIA... access key IDs, sk-... secret-style keys). Findings record only the file path, line number, and pattern label — the function's docstring notes it deliberately never echoes the matching value, so a caught secret isn't itself leaked into logs or error output. Both cmd_publish (GitHub Pages) and cmd_publish_wiki (GitHub Wiki) call this scan on the staged tree and abort with die(...) if any findings are present, before any push is attempted.

Pattern label Matches
private key -----BEGIN (RSA/EC/OPENSSH/DSA/ENCRYPTED) PRIVATE KEY-----
GitHub token ghp_/gho_/ghu_/ghs_/ghr_/github_pat_ prefixed tokens
cloud/API key AWS AKIA... access key IDs, sk-... style secret keys

Sources: src/repodocs/publish.py:L119-L139, src/repodocs/publish.py:L179-L185, src/repodocs/publish.py:L384-L389

Publish-time guardrails

Publishing has several independent gates that all must pass before a push happens:

  • Protected-branch refusal. publish_branch_safe checks the target branch (after stripping a refs/heads/ prefix, case-insensitively) against PROTECTED_PUBLISH_BRANCHES = {"main", "master", "trunk"}; cmd_publish calls die(...) immediately if the branch is protected, before touching the network or the filesystem beyond that check.
  • Citation integrity. cmd_publish calls citation_problems on the publishable Markdown pages and dies, listing offending citations, if any citation is missing or resolves outside the repository. cmd_publish_wiki similarly calls enforce_citations after staging.
  • Explicit opt-in to go public. Both cmd_publish and cmd_publish_wiki require --allow-public; without it, they print the staged file list and exit 0 without pushing (dry_run path), or die with a reminder to rerun with --dry-run first.
  • Non-destructive wiki pushes. publish_wiki_push clones the target GitHub Wiki, overwrites only the filenames actually staged (leaving any other manually-added wiki pages untouched), commits only if the tree changed, and pushes normally — never with --force. By contrast, publish_push (GitHub Pages) pushes a single orphan commit with git push -f, but does so from a throwaway detached worktree, never touching the user's working tree or current branch.
  • Bounded, credential-safe error output. _wiki_git raises WikiPublishError with output passed through failure_detail(result) rather than the raw subprocess argv, specifically so a credential-bearing remote URL embedded in a git command's arguments doesn't leak into an exception message.

Sources: src/repodocs/publish.py:L116-L143, src/repodocs/publish.py:L160-L211, src/repodocs/publish.py:L280-L349, src/repodocs/publish.py:L351-L408

Publish flow

Security & Trust Boundaries — diagram 2

Sources: src/repodocs/publish.py:L160-L211, src/repodocs/publish.py:L351-L408

Reporting vulnerabilities

SECURITY.md directs vulnerability reports to GitHub's private vulnerability reporting flow or to aryrabelo@gmail.com, explicitly asking that public issues not be opened for suspected vulnerabilities, with an acknowledgement expected within 72 hours. Security fixes are applied to the latest release and current main.

Sources: SECURITY.md:L1-L11

Clone this wiki locally