IMPORTANT: Developer documentation for the current development branch. This content is unreleased, may change without notice, and must not be treated as Buildish release documentation.
Threat model
§1 Header
Project: Buildish Site Pipeline.
Version binding: this threat model is versioned alongside the project. A report against project version N is triaged against the threat model shipped with version N, not against the model at a later development commit. (documented)
Version: current unreleased buildish-site-pipeline development branch.
(documented)
Date: 2026-08-04. (documented)
Authors: Buildish maintainers. (documented)
Reporting cross-reference: findings that may violate §8 should be reported per
this repository’s SECURITY.md disclosure channel. Sections §3 and §9 inform
triage, but maintainers make the final scope determination. (documented)
Status: development draft and review aid. No Buildish release has been published; this model must be reviewed again before it is attached to a release. (documented)
Provenance legend: (documented) means supported by repository documentation or code; (maintainer) means confirmed by a maintainer; (inferred) means a draft claim derived from current structure and requiring confirmation.
Buildish Site Pipeline is a Python CLI and package for staging multi-repository documentation-site inputs into a normalized tree and metadata set for downstream renderers. It reads a consumer-owned catalog, optional provider snapshots, component metadata, site pages, docs, and static assets; validates the structural contract; stages normalized outputs; and emits reports and aggregate JSON files that renderers and deployment adapters consume. (documented)
§2 Scope and intended use
Primary intended use cases:
- Validate a trusted workspace’s site catalog, component metadata, publication model, paths, routes, redirects, and provider-derived inputs before staging. (documented)
- Build a staged output tree under
site/.stage/for a downstream renderer or deployment adapter. (documented) - Keep staged outputs fresh during local editing with
site-pipeline watch. (documented) - Expose effective component source roots for local wrappers or container mount
orchestration through
site-pipeline component-source-roots. (documented) - Provide JSON Schema and reference documentation for authored, provider, and emitted pipeline contracts. (documented)
Deployment contexts:
- Local developer workstation CLI. (documented)
- CI CLI, either from a Python environment or the optional container image. (documented)
- In-process Python imports are implementation detail unless a specific model or
helper is documented as a public contract. The stable invocation API is the
site-pipelineexecutable. (documented) - This project is not a network service, daemon, browser sandbox, or renderer. (documented)
Caller and actor expectations:
- The operator invoking the CLI is trusted for the workspace and output location. (documented)
- Repository-authored catalog and component files may be reviewed project input, but are not assumed to be safe HTML or safe filesystem paths until validated. (documented)
- Provider snapshots are external data inputs and are validated as data, not trusted code. (documented)
- Downstream renderers and deployment adapters are separate trusted components responsible for HTML escaping, browser-origin policy, CSP, cookies, storage, and publication. (documented)
Component-family table:
| Family | Representative entry point | External surface touched | In model? | Notes |
|---|---|---|---|---|
| CLI control plane | site-pipeline plan, check, build, watch, component-source-roots |
Filesystem, process stdout/stderr, signals for watch | Yes | Stable invocation API. (documented) |
| Document loading and models | load_site_catalog_document, load_provider_snapshot_document, Pydantic models |
YAML/JSON bytes, validation schemas | Yes | Safe loader, duplicate-key rejection, strict models. (documented) |
| Planning and evaluation | evaluate_planning, run_evaluation |
In-memory model data, workspace paths | Yes | Computes diagnostics, routes, publication decisions, limits. (documented) |
| Staging and publication | publish_stage, finalize_stage_publication |
Filesystem reads and writes under source roots and stage roots | Yes | Includes containment and symlink checks. (documented) |
| Watch mode | site-pipeline watch |
Filesystem watcher, stdout/stderr/events, signals | Yes | Local development and wrapper integration surface. (documented) |
| Staged output contracts | site/.stage/, manifest.json, data/*.json, staged pages/assets |
Filesystem output consumed by renderers | Yes | Stable output API. (documented) |
| Optional container image | tools/site-pipeline-image/*, published image entrypoint |
Container build/runtime environment | Partly | In scope for packaging expectations; base-image and registry security are out of scope. (inferred) |
| Release-legal helpers | make release-legal-preliminary, buildish_site_pipeline.legal.release_legal |
Local Python environment, package metadata, subprocess execution | Partly | In scope for repository maintenance safety; not part of normal site staging threat boundary. (inferred) |
| Tests, docs, generated schemas | tests/, docs/, site/pages/schemas/ |
Repository files | No for runtime guarantees | Useful evidence, but not runtime security boundary. (inferred) |
§3 Out of scope
Explicit non-goals:
- Making malicious authored HTML, JavaScript, CSS, or renderer templates safe to publish. Imported active content is trusted browser-executable code. (documented)
- Providing authentication, authorization, secrets management, or tenant isolation for published sites. (documented)
- Securing the downstream renderer, web server, CDN, browser origin, CSP, cookie scope, storage scope, or deployment adapter. (documented)
- Defending against an attacker who controls the local process, Python environment, installed dependencies, shell, container runtime, CI runner, or host filesystem permissions. (inferred)
- Fetching, authenticating, or verifying source repositories, SCM state, or provider APIs. The pipeline consumes local inputs and provider snapshots. (documented)
- Guaranteeing that generated preliminary release-legal drafts replace human release review. (documented)
- Treating test fixtures, generated outputs,
dist/,.venv/, caches, and local build artifacts as covered runtime components. (inferred) - Security of third-party dependencies beyond normal dependency-management and release processes. (inferred)
- Security of non-default or locally modified builds that bypass validation, monkeypatch internals, or call private Python functions directly. (inferred)
§4 Trust boundaries and data flow
The primary trust boundary is the CLI boundary plus the declared workspace and stage-root filesystem boundaries. CLI-local arguments are trusted operator choices; authored and provider documents are parsed as untrusted data and must pass schema, path, route, URL, publication, and size validation before their values influence staged outputs. (documented)
Data flow:
- The operator invokes
site-pipelinewith command arguments and optional report/event sinks. (documented) - The CLI resolves workspace, catalog, stage, work, report, and watch-event paths relative to the current process and rejects unsafe output combinations. (documented)
- Catalog, provider snapshot, and component metadata files are read as UTF-8, parsed with safe YAML or JSON loaders, rejected on duplicate keys, and loaded into strict Pydantic models. (documented)
- Planning resolves component source roots, publication paths, routes, redirects, provider references, limits, and diagnostics. (documented)
checkstops after validation and report emission;buildandwatchpublish a staged tree only when the stage gate allows it. (documented)- Staging copies authored pages/assets and emits normalized front matter, manifests, reports, and aggregate JSON files under the stage root. (documented)
- A downstream renderer consumes the staged tree and is responsible for HTML rendering and browser-facing security policy. (documented)
Reachability preconditions:
| Component family | In-model finding must be reachable from |
|---|---|
| CLI control plane | A supported site-pipeline command using documented flags and normal process streams. (documented) |
| Document loading and models | Authored catalog/component files, provider snapshot files, or emitted contract files parsed by public loaders. (documented) |
| Planning and evaluation | Valid or invalid loaded model data plus workspace paths accepted by CLI layout resolution. (documented) |
| Staging and publication | A build or watch path whose evaluation stage gate reaches publication. (documented) |
| Watch mode | A supported watch invocation, watched filesystem changes, or configured report/event sinks. (documented) |
| Staged output contracts | Data emitted by the pipeline and consumed according to documented staged-output contracts. (documented) |
| Optional container image | The published generic image running the documented site-pipeline entrypoint. (inferred) |
| Release-legal helpers | Maintainer-invoked release-legal commands, not ordinary site staging commands. (inferred) |
§5 Assumptions about the environment
Runtime assumptions:
- Python 3.13 or newer is available. (documented)
- The process can read declared local inputs and write configured reports, work roots, and stage roots. (inferred)
- Filesystem semantics are close enough to
pathlib.Path.resolve()andis_relative_to()expectations for containment checks to be meaningful. (inferred) - The operator controls CLI arguments, local overrides, and output destinations. (inferred)
- The workspace may contain symlinks, but output targets and staged trees must satisfy the project’s symlink-safety checks. (documented)
- Watch mode assumes local filesystem event delivery by
watchfilesand handlesSIGINT/SIGTERMfor shutdown. (documented) - The project does not assume concurrent writes by multiple independent pipeline processes to the same stage/work root are safe. (inferred)
No-surprise side effects inventory for the normal staging CLI:
- Reads local files under configured workspace/source roots and selected catalog or provider-snapshot paths. (documented)
- Writes reports to stdout or configured report paths, and watch events to stdout or configured event paths. (documented)
- Writes and replaces staged output under the configured stage root for
buildandwatch. (documented) - Writes transient work data under the configured work root during staging. (documented)
- Does not fetch source repositories, provider APIs, or remote content during
plan,check,build, orwatch. (documented) - Does not execute renderer commands, shell snippets, or repository-authored scripts as part of the stable staging commands. (inferred)
- Does not open listening network sockets in the stable CLI surface described by this model. (inferred)
- Does not install signal handlers except for watch-mode shutdown behavior. (documented)
- Does not treat metadata strings as trusted HTML. (documented)
§5a Build-time and configuration variants
| Variant or knob | Default | Security effect | Maintainer stance |
|---|---|---|---|
| Python version | >=3.13 |
The model assumes current Python 3.13 pathlib, typing, and dependency behavior. |
Required by package metadata. (documented) |
| CLI command | No command default | check is non-mutating; build and watch mutate stage/work outputs; component-source-roots prints local paths. |
Stable command API. (documented) |
--workspace-root / --catalog |
Current directory and site/catalog.yaml |
Selects the trusted workspace and catalog input boundary. | Operator-controlled. (documented) |
--report-output |
stdout | May write a report file; output is revalidated against forbidden stage/work roots. | Supported. (documented) |
--unstable-events-output |
stdout when events are enabled | Watch-only event sink; must differ from report output. | Supported but event format is explicitly unstable. (documented) |
| Local provider-size and route/count defaults | Documented safe operational defaults | Exceeding defaults should fail clearly unless a local operator policy raises them. | Override surface must remain local operator-controlled. (documented) |
| Optional container image | Not required for Python package use | Adds container runtime and base-image assumptions. | Intended for CI/container-first use. (documented) |
| Release-legal helper commands | Not run by staging commands | Use subprocesses and inspect local package metadata; not part of normal staging boundary. | Maintainer tooling only. (inferred) |
No build-time flag is currently documented as intentionally weakening a §8
security property for normal site-pipeline staging. (inferred)
§6 Assumptions about inputs
General input assumptions:
- Catalog, component metadata, provider snapshots, page front matter, route metadata, redirect metadata, and provider records are data inputs and may be malformed or adversarial within the bounds of local workspace access. (documented)
- CLI filesystem arguments are trusted operator input, but still validated where they select report, event, stage, work, or source locations. (documented)
- Page bodies and static assets can contain active browser content; staging them does not make them safe. (documented)
- Provider snapshots may be large, so documented byte and record defaults matter for availability. (documented)
Per-parameter trust table:
| Entry point | Parameter or input | Attacker-controllable? | Caller must enforce |
|---|---|---|---|
site-pipeline plan/check/build/watch |
Command name and flags | No, trusted operator | Do not expose CLI invocation directly to untrusted users. (inferred) |
--workspace-root |
Local filesystem root | No, trusted operator | Select a workspace whose content is appropriate for staging. (inferred) |
--catalog |
Local catalog path | No, trusted operator path; file contents may be untrusted data | Keep catalog under intended workspace policy. (documented) |
--report-output |
Report path or stdout | No, trusted operator | Do not point reports at sensitive or shared locations unintentionally. (inferred) |
--unstable-events-output |
Watch event path or stdout | No, trusted operator | Keep machine-readable events separate from report output. (documented) |
site/catalog.yaml |
Catalog document fields | Yes, if repository content is attacker-influenced | Review source roots, publication paths, and redirects before CI use. (documented) |
provider-snapshot.{yaml,yml,json} |
Provider records and metadata | Yes | Treat as external data; enforce documented size and schema limits. (documented) |
Component site/component.yaml |
Component identity/content roots | Yes, if component repo is attacker-influenced | Accept only component repos intended to participate in the site. (documented) |
| Page front matter | Titles, descriptions, translation keys, metadata | Yes | Renderer must escape by default; raw HTML requires explicit policy. (documented) |
| Page body and static assets | Markdown, AsciiDoc, HTML, JS, CSS, images, archives | Yes | Treat active content as trusted code or isolate it at deployment. (documented) |
| Staged outputs | JSON reports, manifests, aggregates, staged pages/assets | Pipeline-produced; may reflect input data | Downstream consumers must validate schema version and escape data in presentation. (documented) |
Size, shape, and rate assumptions:
- Mounted metadata payloads are limited to 16 KiB per
metadataobject after JSON serialization. (documented) - Diagnostic detail payloads are limited to 8 KiB per
detailsobject after JSON serialization, with a reduction rule instead of malformed JSON. (documented) - Provider snapshot, route, redirect, content-index, watched-directory, and staged-version-context defaults are documented operational limits. (documented)
- Watch mode is intended for local editing cadence, not hostile high-rate event floods. (inferred)
§7 Adversary model
In-scope adversaries:
- A contributor or compromised component repository that can modify authored catalog-referenced content, component metadata, page front matter, page bodies, static assets, redirects, or provider-like data that the operator chooses to stage. (inferred)
- A provider-data source that can supply malformed or oversized provider snapshot records. (inferred)
- A bug-finding tool or reporter that supplies malformed YAML/JSON, paths, redirects, URLs, metadata strings, or page content and claims violation of a stated §8 property. (inferred)
Attacker goals in scope:
- Cause writes outside the configured stage/work/report boundaries. (documented)
- Smuggle local filesystem paths or private workspace details into public staged outputs. (documented)
- Cause unsafe route, URL, redirect, or staged metadata output that violates the documented contract. (documented)
- Cause denial of service through oversized structured inputs beyond documented limits. (documented)
- Cause the stable CLI to execute attacker-controlled local commands or unsafe deserialization payloads. (inferred)
Out-of-scope adversaries:
- An attacker with control over the operator account, shell, Python interpreter, dependency installation, CI runner, container runtime, or host filesystem. (inferred)
- A downstream renderer or deployment adapter that intentionally renders
unescaped metadata, publishes active content on an unsafe origin, or ignores
staged
trustClassand route metadata. (documented) - A network attacker against a published website, CDN, renderer development server, SCM host, package index, or provider API. (inferred)
- A malicious maintainer modifying source code or release artifacts. (inferred)
§8 Security properties the project provides
| Property | Conditions | Violation symptom | Severity tier | Provenance |
|---|---|---|---|---|
| Safe YAML/JSON data loading | Public loaders parse YAML with a safe loader, reject duplicate keys, require mapping roots and schema versions, and validate with strict models. | Unsafe object construction, duplicate-key ambiguity, accepted unexpected fields, or accepted wrong schema version. | Security-critical if reachable from authored/provider input. | (documented) |
| Path containment for source and output decisions | Path-bearing inputs and output targets are normalized and must stay under declared roots or allowed operator-selected destinations. | Stage/report/work writes outside intended roots; source-root traversal. | Security-critical. | (documented) |
| Symlink safety for staged publication | Visible stage targets and staged trees must not resolve through symlinked parents or contain symlinked entries where publication validation forbids them. | Symlink escape into or out of staged output. | Security-critical. | (documented) |
| Non-mutating validation command | site-pipeline check stops before stage-root mutation, file copying, aggregate writes, or watch-loop startup. |
check mutates stage/work/output trees except explicit report output. |
Security-critical for CI validation usage; otherwise high-severity correctness. | (documented) |
| No repository-authored command execution in stable staging commands | Normal plan, check, build, and watch do not execute renderer commands or repo-authored shell snippets. |
Attacker-controlled catalog/content causes local command execution. | Security-critical. | (inferred) |
| Public-output local-path minimization | Public diagnostics and aggregate outputs prefer stable IDs, public paths, and public URLs; private roots are redacted in report details. | Absolute workstation paths or private stage/work roots leak into public reports or staged metadata contrary to contract. | Security-sensitive information disclosure. | (documented) |
| Metadata is data, not trusted HTML | Pipeline preserves human-facing strings as structured text and expects renderers to escape by default. | Pipeline itself marks arbitrary metadata as safe HTML or bypasses the staged trust contract. | Security-critical if it creates browser XSS in a compliant renderer. | (documented) |
| URL and redirect validation | URL-bearing fields reject unsupported schemes and internal redirects must resolve to known routes where required by the contract. | javascript:, data:, malformed public URL, or dangling internal redirect reaches staged contract as valid. |
Security-critical for browser-facing consumers. | (documented) |
| Active mounted content is classified | Imported active HTML/JS/CSS trees are represented as active content requiring deployment policy rather than inert content. | Active browser code is mislabeled as passive under the pipeline-owned contract. | Security-critical for downstream deployment policy. | (documented) |
| Resource ceilings for structured metadata | Hard and default limits are enforced or diagnosed for mounted metadata, diagnostic details, provider snapshots, routes, redirects, content index, watch roots, and version contexts. | Unbounded allocation, malformed JSON after truncation, or missing limit diagnostics. | Security-critical for hard ceilings; availability/correctness for operational defaults. | (documented) |
| Stable exit-code and report contract | CLI returns documented low application exit codes and emits selected text/JSON reports without mixing human logs into machine outputs. | Automation cannot distinguish validation failure from invocation/internal failure; machine output is polluted by lifecycle logs. | Correctness/security-adjacent for automation. | (documented) |
§9 Security properties the project does not provide
The project does not provide:
- HTML sanitization for authored page bodies, imported active content, renderer templates, or arbitrary metadata rendered by downstream systems. (documented)
- Browser same-origin isolation, CSP enforcement, cookie policy, local storage isolation, or web-server headers. (documented)
- Authentication, authorization, cryptographic integrity, signatures, or provenance verification for source repositories, provider snapshots, staged outputs, or published content. (documented)
- A sandbox boundary between untrusted repository content and the local operator account running the CLI. (inferred)
- Safe multi-tenant service behavior when exposing the CLI to untrusted network users. (inferred)
- Protection after an attacker controls the local process, Python environment, dependency set, container runtime, CI runner, or filesystem permissions. (inferred)
- Constant-time behavior, secret handling, or cryptographic APIs. (inferred)
- Complete denial-of-service resistance against unbounded local filesystem size, hostile watch-event storms, extremely large static assets, or intentionally expensive downstream rendering. (inferred)
- Security guarantees for generated preliminary legal drafts, release review conclusions, or third-party package metadata quality. (documented)
False-friend properties:
- JSON Schema validation is structural validation, not proof that published content is safe HTML or safe to execute in a browser. (documented)
trustClass: passiveortrustClass: activeis a deployment signal, not an isolation mechanism by itself. (documented)checkvalidates pipeline contracts; it does not prove that a downstream renderer, theme, web server, or CDN deployment is secure. (documented)- Provider snapshot validation does not authenticate provider identity or prove that provider data came from an official source. (inferred)
- The optional container image gives a reproducible entrypoint; it is not a sandbox for malicious workspaces unless the operator supplies appropriate container isolation and mounts. (inferred)
Well-known attack classes left to callers or downstream layers:
- XSS through authored HTML, Markdown extensions, imported active trees, or renderer templates must be handled by renderer escaping and deployment policy. (documented)
- Open redirect and unsafe URL publication must be controlled by catalog policy, URL validation, and deployment review. (documented)
- Supply-chain compromise of dependencies, base images, provider snapshots, or component repositories is outside the pipeline’s local staging boundary. (inferred)
- Local secret exfiltration by malicious renderer commands or wrapper scripts is outside this project unless the stable pipeline CLI executes those commands. (inferred)
§10 Downstream responsibilities
Operators and integrators must:
- Report suspected §8 violations through the private security channel in
SECURITY.md. (documented) - Run the CLI only on workspaces and component repositories they intend to trust for local file reads and browser-facing content publication. (inferred)
- Use
site-pipeline checkbeforebuildor publication in automation. (inferred) - Keep report and watch-event output paths out of staged/work roots and away from unintended public artifacts. (documented)
- Configure downstream renderers to HTML-escape metadata and treat raw HTML as an explicit opt-in outside the core pipeline contract. (documented)
- Apply origin isolation, CSP, cookie, storage, and server policies for active mounted content. (documented)
- Treat provider snapshots as external data and enforce documented size/count limits or local operator-only overrides. (documented)
- Do not expose the CLI directly as a multi-tenant network service or remote build API without an additional isolation layer. (inferred)
- Review optional container image, base image, dependency, and registry posture according to the deployment environment’s supply-chain policy. (inferred)
§11 Known misuse patterns
- Rendering metadata strings as raw HTML because they were accepted by the pipeline schema. This is unsafe; renderers should escape by default. (documented)
- Publishing imported active site trees on the same origin as sensitive pages without deliberate isolation policy. (documented)
- Treating provider snapshots as authenticated source-of-truth data rather than external data loaded from a local file. (inferred)
- Sharing staged reports or aggregate outputs without considering whether they contain repo-relative source paths or redacted local-path placeholders. (inferred)
- Calling private Python modules directly and expecting the same compatibility and threat-model guarantees as the stable CLI. (documented)
- Running multiple pipeline processes against the same stage/work root and treating the result as synchronized. (inferred)
§11a Known non-findings
- A report that a trusted operator can pass a sensitive
--workspace-root,--catalog,--report-output, or--unstable-events-outputpath is not by itself a vulnerability; those are trusted operator-selected CLI parameters per §6. (inferred) - A report that active HTML/JS/CSS can execute after publication is not by itself a pipeline vulnerability when the content is staged as active content and the downstream deployment shares an origin by policy; see §9. (documented)
- A report against generated files, caches,
dist/,.venv/, or test fixtures is out of model unless it is reachable through the supported CLI or staged output contract; see §3 and §4. (inferred) - A report that preliminary release-legal output requires human review is not a security bug; that limitation is documented and by design. (documented)
§12 Conditions that would change this model
Revise this threat model when the project:
- Adds a new stable CLI command, public Python API, staged output contract, or schema family. (inferred)
- Starts fetching remote repositories, provider APIs, package indexes, or other network resources during stable staging commands. (inferred)
- Adds a preview server, renderer integration, template execution, subprocess runner, plugin system, or user-supplied command hook to the stable CLI. (inferred)
- Changes defaults or operator policy for §5a resource limits, report/event sinks, or stage/work roots. (inferred)
- Changes path normalization, symlink handling, publication finalization, or public-output redaction behavior. (inferred)
- Promotes container-image, release-legal, generated-doc, or helper-script behavior into the core runtime security boundary. (inferred)
- Receives a vulnerability report that cannot be routed to one of the §13 dispositions. Such a report is a model gap, not an ad-hoc exception. (inferred)
§13 Triage dispositions
| Disposition | Meaning | Licensed by |
|---|---|---|
VALID |
Violates a §8 property through an in-scope adversary and an in-scope input or entry point. | §6, §7, §8 |
VALID-HARDENING |
No §8 property is violated, but the API or output contract makes a §11 misuse easy enough that the project elects to harden it. | §11 |
OUT-OF-MODEL: trusted-input |
Requires attacker control of a parameter or environment element marked trusted. | §6 |
OUT-OF-MODEL: adversary-not-in-scope |
Requires an attacker capability excluded from the model. | §7 |
OUT-OF-MODEL: unsupported-component |
Lands only in tests, generated outputs, caches, local artifacts, or helper areas excluded from the runtime model. | §3 |
OUT-OF-MODEL: non-default-build |
Only manifests under a locally modified, discouraged, or unsupported configuration outside §5a. | §5a |
BY-DESIGN: property-disclaimed |
Concerns a property explicitly not provided. | §9 |
KNOWN-NON-FINDING |
Matches a recurring false positive documented in this model. | §11a |
MODEL-GAP |
Cannot be cleanly routed to any disposition above. The model must be revised. | §12 |
§14 Optional machine-readable companion
No threat-model.yaml companion is currently maintained. The prose document is
canonical. If triage automation is added later, derive a sidecar from §2, §3,
§6, §8, §9, §11a, and §13 rather than treating the sidecar as independent
policy. (inferred)