diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..3edc385 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,21 @@ +# Code owners are requested for review automatically. +# Order matters: the last matching pattern wins. + +* @SarthakWade + +# Security-critical surfaces — the host-enforced contracts live here. +/apps/headless/Sources/HeadlessProtocol/Protocol.swift @SarthakWade +/apps/headless/Sources/HeadlessProtocol/Transport.swift @SarthakWade +/apps/headless/Sources/HeadlessProtocol/Artifacts.swift @SarthakWade +/apps/headless/Sources/HeadlessProtocol/AgentRuntime.swift @SarthakWade +/apps/headless/LinuxHost/ @SarthakWade +/apps/headless/Host/ @SarthakWade + +# Product direction. Changes here are decisions, not refactors. +/docs/ROADMAP.md @SarthakWade +/docs/roadmap/ @SarthakWade +/SECURITY.md @SarthakWade +/AGENTS.md @SarthakWade + +# Release and CI plumbing. +/.github/workflows/ @SarthakWade diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..bcb47e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,66 @@ +name: Bug report +description: Something in Headless behaves incorrectly +labels: ["type:bug"] +body: + - type: markdown + attributes: + value: | + If this is a **security** issue — a way past navigation rules, the + artifact store, the socket boundary, or redaction — do not file it + here. Use [private vulnerability reporting](https://github.com/LockInTime/headless/security/advisories/new). + + - type: textarea + id: what-happened + attributes: + label: What happened + description: What did you expect, and what did you get instead? + validations: + required: true + + - type: textarea + id: reproduce + attributes: + label: Commands to reproduce + description: The exact `headless` commands, in order. Include the page or fixture if it matters. + render: shell + validations: + required: true + + - type: dropdown + id: engine + attributes: + label: Engine + options: + - macOS (WKWebView) + - Linux (Chromium) + - Linux via Docker image + - Both / not sure + validations: + required: true + + - type: input + id: version + attributes: + label: Version + description: Output of `headless capabilities` (protocol version) plus the release tag or commit you built from. + validations: + required: true + + - type: textarea + id: output + attributes: + label: Output and diagnostics + description: | + The failing JSON response, and anything relevant from `headless qa report`, + `console list`, or `network list`. Redact page content you cannot share. + render: shell + + - type: checkboxes + id: checks + attributes: + label: Checks + options: + - label: I checked the [improvements backlog](https://github.com/LockInTime/headless/blob/main/docs/roadmap/improvements-backlog.md) and open `backlog` issues for an existing entry + required: true + - label: This is not a documented limitation in the [phase contracts](https://github.com/LockInTime/headless/tree/main/apps/headless/docs) (P0/P1/P2) + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..4fbfa5b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,11 @@ +blank_issues_enabled: true +contact_links: + - name: Security vulnerability + url: https://github.com/LockInTime/headless/security/advisories/new + about: Report privately. Never open a public issue for a boundary bypass. + - name: Roadmap and backlog + url: https://github.com/LockInTime/headless/blob/main/docs/ROADMAP.md + about: Planned work, phases, and the definition of done. Most known gaps are already filed as `backlog` issues. + - name: Using Headless as an agent tool + url: https://github.com/LockInTime/headless/blob/main/.agents/skills/headless-computer-use/SKILL.md + about: The skill that teaches an agent how to drive Headless safely. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..012efd0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,56 @@ +name: Feature or command proposal +description: Propose a new capability, command, or parameter +labels: ["type:feature", "status:needs-design"] +body: + - type: markdown + attributes: + value: | + Headless has deliberate non-goals. These will be closed: + an arbitrary-JavaScript verb, a TCP listener or remote control without + authentication, page-download support, and desktop/OS automation. + See [what is excellent](https://github.com/LockInTime/headless/blob/main/docs/roadmap/what-is-excellent.md). + + - type: textarea + id: problem + attributes: + label: What can an agent not do today + description: Describe the task that fails or requires glue code, not the solution you have in mind. + validations: + required: true + + - type: textarea + id: proposal + attributes: + label: Proposed surface + description: The command or parameter as an agent would type it, and the response shape you expect. + render: shell + validations: + required: true + + - type: dropdown + id: engines + attributes: + label: Which engines can support this + options: + - Both engines + - Chromium only (macOS returns UNSUPPORTED_CAPABILITY) + - WKWebView only (Linux returns UNSUPPORTED_CAPABILITY) + - Not sure + validations: + required: true + + - type: textarea + id: contracts + attributes: + label: Effect on the contracts + description: | + How does this interact with the host-enforced rules — bounded output, + untrusted content marking, fail-closed validation, no arbitrary + execution? If it needs an architecture-decision entry, say so. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..e3676f6 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,48 @@ + + +## What this changes + + + +Closes # + +## Why + + + +## How it was verified + + + +- [ ] `pnpm test` +- [ ] `pnpm test:runtime` +- [ ] `pnpm test:e2e:linux` — required if this touches a host, transport, agent runtime JS, or artifacts +- [ ] `pnpm test:e2e:mac` — or the `macos-e2e` label, required if this touches `main.swift`, `Host/`, or capture code +- [ ] New or updated tests cover the change + +## Contracts + + + +- [ ] No arbitrary-JS verb, no TCP listener, no debug port introduced +- [ ] Navigation stays HTTP/HTTPS only; downloads stay denied +- [ ] Artifacts stay bare-named, `O_EXCL`, `0600`, non-overwriting +- [ ] Validation still fails closed (unknown params rejected, capability errors explicit) +- [ ] Bounded output still reports truncation (`omitted`, `truncated`, `contextStats`) +- [ ] Page-derived text stays marked `untrustedContent`; no typed values recorded in flows +- [ ] Behaviour is identical on both engines, or the difference is an explicit `UNSUPPORTED_CAPABILITY` + +## Housekeeping + +- [ ] Backlog item checked off in `docs/roadmap/improvements-backlog.md` +- [ ] Architecture-decision entry added, if this changes a settled decision or the agent-facing contract +- [ ] A new protocol command was added to *both* hosts, the validator, the CLI, help text, and `capabilities` +- [ ] Docs updated — phase contract, README, and every duplicated copy on the site +- [ ] No committed media outside regenerated `docs/qa/evidence/` (checksums updated if so) diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..43b8daf --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,47 @@ +version: 2 + +updates: + # The Swift package has zero third-party dependencies by design, so there is + # nothing to update there — see docs/roadmap/what-is-excellent.md §11. + - package-ecosystem: npm + directory: /apps/web + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 5 + labels: + - "type:chore" + - "area:web" + groups: + react: + patterns: ["react", "react-dom", "@types/react", "@types/react-dom"] + next: + patterns: ["next", "eslint-config-next"] + tailwind: + patterns: ["tailwindcss", "@tailwindcss/*"] + types: + patterns: ["@types/*"] + + - package-ecosystem: npm + directory: / + schedule: + interval: monthly + open-pull-requests-limit: 3 + labels: + - "type:chore" + + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + labels: + - "type:ci" + + - package-ecosystem: docker + directory: /apps/headless + schedule: + interval: monthly + labels: + - "type:ci" + - "area:build-release" diff --git a/AGENTS.md b/AGENTS.md index ba92eda..6b34a93 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -21,6 +21,8 @@ Read before making non-trivial changes: stays Swift; don't propose rewrites). - `docs/roadmap/improvements-backlog.md` — known defects with file/line refs; check items off when you fix them and add the named test. +- `CONTRIBUTING.md` — the same rules for humans, plus setup detail. +- `SECURITY.md` — the boundaries a bug report is measured against. - To *use* Headless as a browser tool (rather than develop it), follow the skill: `.agents/skills/headless-computer-use/SKILL.md`. @@ -62,6 +64,21 @@ Docker E2E. The macOS WKWebView E2E is heavier — it runs nightly, on `workflow_dispatch`, or when a PR carries the `macos-e2e` label. Run it that way before merging changes to `main.swift`, `Host/`, or capture code. +## Picking up work + +Every open backlog item is a GitHub issue labelled `backlog`, cross-linked +from the backlog file in both directions, carrying type/area/priority labels +and sitting in the milestone for its roadmap phase. Work from either surface — +but when you fix something, close the issue **and** check the item off in +`docs/roadmap/improvements-backlog.md` in the same PR, so the two never drift. + +Phase 1 (`Trust the build`) is the current milestone; `priority:critical` and +`priority:high` items there come first. `good first issue` marks the +self-contained ones. + +Anything that changes the agent-facing contract needs an entry in +`docs/roadmap/architecture-decisions.md` before the code lands. + ## Hard rules (host-enforced contracts — never weaken) 1. No arbitrary-JS execution verb; no TCP listener; no Chromium debug port. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b602525 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,104 @@ +# Changelog + +All notable changes to Headless are documented here. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +Releases are tagged `vX.Y.Z` and published by +[`.github/workflows/release.yml`](.github/workflows/release.yml). + +Two versions travel independently, on purpose: + +- **Product version** — the git tag, flowing into the macOS `Info.plist` via + `HEADLESS_VERSION` and into release assets. +- **Protocol version** — `headlessProtocolVersion` in `Protocol.swift`, + currently `0.4`. It changes only when the wire contract changes, and always + with an entry in + [`docs/roadmap/architecture-decisions.md`](docs/roadmap/architecture-decisions.md). + +## [Unreleased] + +Everything below has landed on `main` but is **not yet in a tagged release**. +Cutting that release is tracked in +[#45](https://github.com/LockInTime/headless/issues/45). + +### Added + +- Progressive context pruning: `inspect --context summary|outline|text|actions|full` + with `--task` ranking, `--within @rN` scoping, and `--limit` / `--budget` / + `--depth` bounds. Every focused response reports `contextStats` and `omitted`. + Measured 94.5 % estimated-token reduction on the 120-section fixture. +- Capture formats: JPG/JPEG and PDF screenshots, `--every-viewport` and + `--by-section` screenshot series, and MOV/WebM/GIF recording containers. +- Product documentation set — [`docs/ROADMAP.md`](docs/ROADMAP.md), + [what is excellent](docs/roadmap/what-is-excellent.md), + [architecture decisions](docs/roadmap/architecture-decisions.md), and the + [improvements backlog](docs/roadmap/improvements-backlog.md). +- Contributor rule files for every coding harness: [`AGENTS.md`](AGENTS.md) + and `CLAUDE.md`. +- Continuous integration on every pull request + ([`ci.yml`](.github/workflows/ci.yml)): static checks, agent-runtime suite, + protocol suite on Linux and macOS, web lint and build, and the Linux Docker + E2E. The macOS WKWebView E2E runs nightly or on the `macos-e2e` label. +- Repository governance: MIT `LICENSE` preserving the upstream chromeless + notice, `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, issue forms, + a pull-request template, `CODEOWNERS`, and Dependabot. + +### Changed + +- QA evidence now includes a recorded progressive-pruning scenario, and the + committed bundle is checksum-verified in CI. +- Repository moved to the `LockInTime` organisation; site links updated. + +### Known gaps + +Tracked as [`backlog`](https://github.com/LockInTime/headless/labels/backlog) +issues, grouped by roadmap phase into milestones. Highlights: +a shutdown data race on the Linux host +([#12](https://github.com/LockInTime/headless/issues/12)), responses that can +exceed the 1 MiB frame ([#14](https://github.com/LockInTime/headless/issues/14)), +and unsigned macOS builds +([#39](https://github.com/LockInTime/headless/issues/39)). + +## [1.0.2] — 2026-07-19 + +### Fixed + +- macOS: recover when a restored page is unavailable at launch. + +## [1.0.1] — 2026-07-19 + +### Fixed + +- Release CI: harden the macOS E2E suite for GitHub runners. + +## [1.0.0] — 2026-07-19 + +First tagged release: the P0–P2 contract on macOS and Linux. + +### Added + +- Persistent host with a versioned JSON protocol over a private per-user Unix + socket, peer-UID checked, with no TCP listener and no arbitrary-JavaScript + verb. +- Two engines behind one CLI contract: macOS WKWebView windows, Linux + sandboxed Chromium driven over the inherited DevTools fd-3/4 pipe. +- Semantic interaction — `visit`, `inspect`, `click`, `fill`, `press`, + `scroll`, `wait`, `tour`, `back`, `reload` — targeted by role/name or by + element reference. +- Evidence capture: screenshots, built-in FFmpeg recording, visual comparison, + flows, and QA reports, written to a `0700` per-user artifact store with + `0600` non-overwriting files. +- Diagnostics with redaction: console, network, cookies, storage, computed + styles, performance, and animations. Sensitive values are double-gated. +- Linux-only network emulation and request mocking; macOS returns + `UNSUPPORTED_CAPABILITY` rather than partially emulating. +- `headless-mcp`, a stdio MCP server exposing the CLI as a single `argv` tool. +- Snap Chromium rejection before launch, after it proved unreliable over the + DevTools pipe. +- Tag-triggered release workflow publishing a macOS app zip and Linux + amd64/arm64 tarballs. + +[Unreleased]: https://github.com/LockInTime/headless/compare/v1.0.2...HEAD +[1.0.2]: https://github.com/LockInTime/headless/compare/v1.0.1...v1.0.2 +[1.0.1]: https://github.com/LockInTime/headless/compare/v1.0.0...v1.0.1 +[1.0.0]: https://github.com/LockInTime/headless/releases/tag/v1.0.0 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..5a1aa81 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,135 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic +status, nationality, personal appearance, race, caste, color, religion, or +sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances + of any kind +* Trolling, insulting or derogatory comments, and personal or political + attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards +of acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for +moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement by opening a +[private security advisory](https://github.com/LockInTime/headless/security/advisories/new) +— which is visible only to maintainers — or by contacting an owner of the +LockInTime organization directly. + +All complaints will be reviewed and investigated promptly and fairly. All +community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][mozilla]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][faq]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[mozilla]: https://github.com/mozilla/diversity +[faq]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..7cf0be5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,102 @@ +# Contributing to Headless + +Thanks for working on this. Headless is a security-boundary product: agents +drive real browsers through it, and the host — not the prompt — is what stops +them doing something harmful. That shapes how we review changes, so please +read this before your first PR. + +Contributors are humans *and* coding agents. If you are an agent, your rules +live in [`AGENTS.md`](AGENTS.md); everything below applies to you too. + +## Before you start + +1. **Find or open an issue.** Every known defect and gap is already filed — + see the [backlog](docs/roadmap/improvements-backlog.md) and the matching + GitHub issues, labelled `backlog` and grouped into milestones by roadmap + phase. Good entry points carry `good first issue`. +2. **Read the contracts.** [`docs/roadmap/what-is-excellent.md`](docs/roadmap/what-is-excellent.md) + lists twelve properties that define the product. A PR that weakens one is + rejected on principle, not on style — even if the code is good. +3. **Check the decisions.** [`docs/roadmap/architecture-decisions.md`](docs/roadmap/architecture-decisions.md) + records what is already settled (the core stays Swift; no hosted service; + Windows is a stretch goal). If your change contradicts one, propose a new + decision entry in the same PR rather than quietly diverging. + +Anything that changes the agent-facing contract — a new verb, a new +parameter, a changed response shape, a relaxed safety rule — needs an +architecture-decision entry **before** the implementation lands. + +## Development setup + +```sh +pnpm install # workspace tooling (Node 20+, pnpm 9) +pnpm test # protocol/security suite — needs a Swift toolchain +pnpm test:runtime # jsdom context-pruning suite — Node only +pnpm test:e2e:linux # Docker + sandboxed Chromium E2E +pnpm test:e2e:mac # real WKWebView E2E (macOS GUI session) +``` + +Platform notes: + +- **macOS** builds need Xcode Command Line Tools; `pnpm build` produces + `Headless.app`. FFmpeg is required for recording and visual diffs. +- **Linux** builds need only Docker: `pnpm build:linux` compiles inside + `swift:6.1-bookworm` and emits a tarball. Chromium must be non-Snap — + the runtime rejects Snap launchers before starting, by design. +- `pnpm test:e2e:mac` opens real windows and mutates `com.headless.app` user + defaults. Don't run it in a background session or on a machine where that + matters. + +## The bar for a pull request + +- `pnpm test` and `pnpm test:runtime` pass locally. +- Run `pnpm test:e2e:linux` if you touched a host, the transport, the agent + runtime JS, or artifact code. Add the `macos-e2e` label to the PR if you + touched `main.swift`, `Host/`, or capture code — that triggers the heavier + WKWebView suite in CI. +- New behaviour ships with a test. New *safety* behaviour ships with a test + that fails without the fix. +- A protocol command still has to be added in both hosts, the validator, the + CLI, the help text, and `capabilities` until the HostCore refactor + ([#21](https://github.com/LockInTime/headless/issues/21)) lands. Keep the + copies in sync. +- If you fixed a backlog item, check it off in + `docs/roadmap/improvements-backlog.md` in the same PR and close the issue. + +CI runs on every PR: static checks, the agent-runtime suite, the protocol +suite on Linux and macOS, the web lint/build, and the Linux Docker E2E. All of +it uses the same scripts you ran locally, so a green laptop should mean a +green PR. + +## Style + +- **Swift:** Foundation and system frameworks only. No new SwiftPM + dependencies without an architecture-decision entry — the zero-dependency + supply chain is a feature. Explicit validation, small structs, and no + force-unwraps in host paths (the existing ones are tracked defects, not + precedent). +- **Shell:** POSIX `sh` unless you need bash; scripts are syntax-checked in + CI. +- **Web:** the site currently duplicates content by hand in three places. + If you change CLI behaviour, grep `apps/web` and `README.md` and update + every copy until [#48](https://github.com/LockInTime/headless/issues/48) + makes that unnecessary. +- **Commits:** conventional-ish prefixes — `feat:`, `fix:`, `docs:`, `ci:`, + `refactor:`, `test:`, `chore:`, with an optional scope such as + `fix(macos):`. + +## Media and evidence + +Never commit images or video except regenerated QA evidence under +`docs/qa/evidence/`, which is checksum-verified in CI. Local artifacts belong +in `build/qa-evidence/` (gitignored). If your change alters what a recording +or screenshot looks like, regenerate the affected evidence and update +`SHA256SUMS`. + +## Reporting security issues + +Do not open a public issue. See [`SECURITY.md`](SECURITY.md). + +## Code of conduct + +Participation is governed by [`CODE_OF_CONDUCT.md`](CODE_OF_CONDUCT.md). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..68385f9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2026 LockInTime +Copyright (c) 2026 Antiwork, Inc. (original chromeless foundation) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index fc5be28..2947bb4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # headless +[![CI](https://github.com/LockInTime/headless/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/LockInTime/headless/actions/workflows/ci.yml) +[![Release](https://img.shields.io/github/v/release/LockInTime/headless?sort=semver)](https://github.com/LockInTime/headless/releases) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) +[![Protocol](https://img.shields.io/badge/protocol-0.4-informational)](apps/headless/Sources/HeadlessProtocol/Protocol.swift) +[![Platforms](https://img.shields.io/badge/platforms-macOS%20%7C%20Linux-lightgrey)](#build-and-install) + Persistent browser control for agents, without Playwright scripts or screen coordinates. @@ -12,8 +18,16 @@ distros used in CI/Docker). Windows is not supported natively (planned as a stretch goal; Docker/WSL2 is the interim path). Product direction, architectural decisions, and the full improvements backlog -live in [docs/ROADMAP.md](docs/ROADMAP.md). Contributors (human or agent): -start with [AGENTS.md](AGENTS.md). +live in [docs/ROADMAP.md](docs/ROADMAP.md). Every known gap is also filed as a +[`backlog` issue](https://github.com/LockInTime/headless/labels/backlog), +grouped into [milestones](https://github.com/LockInTime/headless/milestones) +by roadmap phase. + +Contributing: [AGENTS.md](AGENTS.md) for agents and the short version for +humans, [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide, +[SECURITY.md](SECURITY.md) to report a boundary bypass privately, and +[CHANGELOG.md](CHANGELOG.md) for what shipped when. New here? Start with a +[good first issue](https://github.com/LockInTime/headless/labels/good%20first%20issue). P2 uses the same CLI everywhere Headless runs: diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..1214ff6 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,78 @@ +# Security policy + +Headless exists to give an AI agent a browser it *cannot* misuse. The safety +rules are enforced by the host process, not by prompting, so a vulnerability +here is a vulnerability in the product's core promise. We take reports +seriously. + +## Reporting a vulnerability + +**Do not open a public issue.** + +Use GitHub's private vulnerability reporting: +[**Report a vulnerability**](https://github.com/LockInTime/headless/security/advisories/new). +That opens a private advisory visible only to maintainers. + +Please include: + +- affected version or commit, and platform (macOS engine or Linux Chromium engine) +- what boundary you crossed, and the commands or page that crossed it +- a minimal reproduction — a fixture page is ideal +- what an attacker gains + +We aim to acknowledge within 3 working days and to ship a fix or a documented +mitigation before any public disclosure. Tell us if you intend to publish, and +we will agree a timeline with you. + +## What counts as a vulnerability + +These are host-enforced contracts. Anything that defeats one is in scope: + +| Boundary | Expected behaviour | +| --- | --- | +| **No arbitrary code execution** | There is no JavaScript-evaluation verb and no shell verb. Reaching arbitrary in-page or host execution through the protocol is a vulnerability. | +| **Navigation** | HTTP/HTTPS only. `file:`, `javascript:`, `data:`, credential-bearing URLs, and external application schemes must be refused at every layer. | +| **Downloads** | Page-initiated downloads are denied. Executables, installers, scripts, libraries, and disk images are blocked by extension. | +| **Control plane** | A `0600` Unix socket inside a `0700` per-user directory, with a peer-UID check. There is no TCP listener and no Chromium debug port. Any remote reachability is a vulnerability. | +| **Artifacts** | Bare validated names, `O_EXCL` creation at `0600` inside a `0700` root, never overwritten. Path traversal or reading outside the store is a vulnerability. | +| **Secrets** | Cookie and storage *values* require both `--values` and `HEADLESS_ALLOW_SENSITIVE_DIAGNOSTICS=1`. Authorization, cookie, token, and secret headers, plus URL credentials, are always redacted. Flow recordings never contain typed values. | +| **Untrusted content** | Everything derived from a page is marked `untrustedContent` and is never executed as a command. A page that induces the host to act on its own text is a vulnerability. | +| **Sandbox** | The Linux host refuses to run as root and never passes `--no-sandbox`. Snap Chromium is rejected before launch. | + +Prompt injection that merely *persuades an agent* to do something within these +boundaries is not a host vulnerability — but if page content can escape the +`untrustedContent` marking or reach a privileged path, that is. + +## Known limitations (not vulnerabilities) + +These are documented design boundaries, not defects: + +- **Same-user access.** Any process running as your OS user can reach that + user's socket, browser profile, and artifacts. Run untrusted agents as + separate OS users. +- **Shared session state.** Sessions are windows (macOS) or tabs (Linux) over + one browser profile, so cookies and storage are shared between sessions. + Per-session isolation is tracked in the roadmap, not implied today. +- **macOS diagnostics are best-effort.** WebKit does not expose Chromium's + event stream; the macOS QA bridge runs in the page world and is therefore + observable by the page. Hardening it is tracked as + [#28](https://github.com/LockInTime/headless/issues/28). +- **Recording scope.** The recorder captures browser frames only — never OS + chrome, other applications, or audio. +- **Network mocking is Linux-only.** macOS returns `UNSUPPORTED_CAPABILITY` + rather than partially emulating traffic control. + +## Supported versions + +Headless is pre-1.x in practice: fixes land on `main` and ship in the next +tagged release. There is no long-term support branch yet. + +## Hardening guidance for operators + +- Run agents as a dedicated OS user, not your own account. +- Leave `HEADLESS_ALLOW_SENSITIVE_DIAGNOSTICS` unset unless you are actively + debugging, and never in a shared session. +- Keep the stdio MCP server local, or tunnel it over SSH. Do not bridge it to + a network listener. +- Treat every artifact, report, and console line as potentially sensitive + page content. diff --git a/docs/roadmap/improvements-backlog.md b/docs/roadmap/improvements-backlog.md index df6cd1e..9e3747a 100644 --- a/docs/roadmap/improvements-backlog.md +++ b/docs/roadmap/improvements-backlog.md @@ -10,11 +10,17 @@ Conventions: paths are relative to repo root; `HP/` = `apps/headless/Sources/HeadlessProtocol/`. Check items off here in the same PR that fixes them, and add the named missing test. +**Every open item below is also a GitHub issue**, linked inline and labelled +[`backlog`](https://github.com/LockInTime/headless/labels/backlog). Issues carry +type/area/priority labels and sit in the milestone for their roadmap phase, so +you can pick work up from either the tracker or this file. Keep both in sync: +when you fix an item, check it off here and close the issue in the same PR. + --- ## §A — Correctness & robustness (Phase 1: fix first) -**A1. Shutdown data race on the Linux host.** `LinuxBrowserHost` is +**A1. Shutdown data race on the Linux host.** ([#12](https://github.com/LockInTime/headless/issues/12)) `LinuxBrowserHost` is `@unchecked Sendable` with unlocked `sessions`/`trace`/`recordings`/ `activeFlows` (`apps/headless/LinuxHost/main.swift:9-12`). Normal commands are serialized by the transport's `requestQueue`, but `shutdown` deliberately @@ -27,14 +33,14 @@ bypass-the-queue property for shutdown. Test: none exists — add a concurrent-shutdown stress test beside the existing semaphore-based transport test (`ProtocolTests.swift:676-746`). -**A2. Force-unwraps in a long-lived host.** `visual compare` handlers do +**A2. Force-unwraps in a long-lived host.** ([#13](https://github.com/LockInTime/headless/issues/13)) `visual compare` handlers do `request.parameters["before"]!.stringValue!` on both hosts (`LinuxHost/main.swift:239-240`, `main.swift:1175-1176`). Safe only while `validate()` runs first; any future path that skips validation crashes the host and kills every session. Replace with guarded extraction returning `MISSING_PARAMETER`. -**A3. Oversized responses break the 1 MiB frame.** `qa report` can hold 500 +**A3. Oversized responses break the 1 MiB frame.** ([#14](https://github.com/LockInTime/headless/issues/14)) `qa report` can hold 500 events × ~4 KiB ≈ 2 MB; `artifact.list` is unbounded. `encodeLine` throws inside `handleClient` and the client receives a misleading `INVALID_REQUEST` (`HP/Transport.swift:219-227`). Fix: response-side bounding — @@ -42,11 +48,11 @@ pagination (`--limit/--cursor`) or truncation with `truncated: true` — per architecture decision §4. Test: generate >1 MiB of events, assert a bounded, well-formed response. -**A4. Accept-loop error spin.** All `accept()` errors are swallowed with +**A4. Accept-loop error spin.** ([#15](https://github.com/LockInTime/headless/issues/15)) All `accept()` errors are swallowed with `continue` (`HP/Transport.swift:180-184`); persistent EMFILE becomes a hot loop. Add backoff + a fatal threshold. -**A5. `@eN` refs silently invalidated by every snapshot.** The `current` ref +**A5. `@eN` refs silently invalidated by every snapshot.** ([#16](https://github.com/LockInTime/headless/issues/16)) The `current` ref map is reset on each `snapshot()` (`HP/AgentRuntime.swift:376`), so a `--context summary` (max 8 elements) invalidates all refs from a prior `full`; the agent later gets a bare `ELEMENT_NOT_FOUND`. Meanwhile @@ -56,7 +62,7 @@ teaching), then (a) make the error say *why* ("ref expired; re-inspect"), (b) reset regions consistently on navigation, (c) document in P1.md. Test: inspect-full → inspect-summary → click stale `@eN` asserts the new error. -**A6. `--json`/`--session` stripped from anywhere in argv.** Global-option +**A6. `--json`/`--session` stripped from anywhere in argv.** ([#17](https://github.com/LockInTime/headless/issues/17)) Global-option stripping (`HP/CLI.swift:46-49`) happens before subcommand parsing, so `headless fill @e1 pass --json to API` silently drops `--json` from typed text; `fill` also joins args with single spaces destroying whitespace @@ -64,17 +70,17 @@ text; `fill` also joins args with single spaces destroying whitespace before it, and pass the fill value as one argument. Test: fill value containing `--json`, tabs, double spaces. -**A7. Client never verifies response `id`.** Failure paths return +**A7. Client never verifies response `id`.** ([#18](https://github.com/LockInTime/headless/issues/18)) Failure paths return `id:"unknown"` (`HP/Transport.swift:201,222`); `LocalSocketClient.send` doesn't check correlation. Echo the request id everywhere and assert client-side. -**A8. CDP O(n²) buffering.** `receiveText` rescans the whole buffer and +**A8. CDP O(n²) buffering.** ([#19](https://github.com/LockInTime/headless/issues/19)) `receiveText` rescans the whole buffer and `removeFirst`s per 8 KiB read (`LinuxHost/CDP.swift:229-256`); a 30 MB base64 screenshot triggers thousands of full scans under a 128 MiB cap. Track a scan offset / use a ring buffer. -**A9. Misc hardening (smaller, same phase).** +**A9. Misc hardening (smaller, same phase).** ([#20](https://github.com/LockInTime/headless/issues/20)) - `ChromiumChildProcess.stop()` can busy-wait forever post-SIGKILL (`LinuxHost/BrowserProcess.swift:38`); bound it. - `SO_PEERCRED` hard-coded as `17` + hand-rolled `ucred` @@ -99,7 +105,7 @@ Track a scan offset / use a ring buffer. ## §B — Structure & contract (Phase 2) -**B1. HostCore extraction** — the headline refactor; full spec in +**B1. HostCore extraction** ([#21](https://github.com/LockInTime/headless/issues/21)) — the headline refactor; full spec in [architecture-decisions §3](architecture-decisions.md). Duplicated pairs to collapse (verified line ranges): dispatch switches (`main.swift:949-1291` / `LinuxHost/main.swift:65-343`), screenshot-series loops (`main.swift:911-945` @@ -111,11 +117,11 @@ collapse (verified line ranges): dispatch switches (`main.swift:949-1291` / gate, target validation (3 copies + JS), screenshot bounds, syscall shims (`Transport.swift:387-407` / `CDP.swift:278-288`). -**B2. Typed errors end-to-end.** Replace `message.contains("ELEMENT_NOT_FOUND")` +**B2. Typed errors end-to-end.** ([#22](https://github.com/LockInTime/headless/issues/22)) Replace `message.contains("ELEMENT_NOT_FOUND")` string matching (both hosts; `Host/AgentBridge.swift:416-418`) with an error enum carrying the protocol code. -**B3. Single-source constants + drift tests.** Blocked/caution extensions +**B3. Single-source constants + drift tests.** ([#23](https://github.com/LockInTime/headless/issues/23)) Blocked/caution extensions exist in Swift (`HP/Protocol.swift:576-591`) and JS (`HP/AgentRuntime.swift:17-21`) with no cross-check; artifact charset written 4×; local-address list 3×; CLI vs validator bounds disagree (scroll amount @@ -123,7 +129,7 @@ exist in Swift (`HP/Protocol.swift:576-591`) and JS unbounded in CLI, `CLI.swift:449` / `Protocol.swift:396-398`). One definition each + a test asserting the JS copy contains the Swift set. -**B4. Dead code removal.** `screenshotSeriesPoints(from:)` +**B4. Dead code removal.** ([#24](https://github.com/LockInTime/headless/issues/24)) `screenshotSeriesPoints(from:)` (`HP/ScreenshotSeries.swift:74-76`), `JSONValue.foundationObject` (`HP/Protocol.swift:670-679`), discarded `timeout` param (`LinuxHost/BrowserProcess.swift:777-778`) — either honor it (tour expects @@ -133,13 +139,13 @@ each + a test asserting the JS copy contains the Swift set. the flag from help, `jsonValue(from:)` duplicate (`AgentBridge.swift:428-441`). -**B5. `pruneToBudget` quality.** Hand-rolled 2-pass fixed point +**B5. `pruneToBudget` quality.** ([#25](https://github.com/LockInTime/headless/issues/25)) Hand-rolled 2-pass fixed point (`HP/AgentRuntime.swift:348-352`), O(n²) re-encoding per trim, pop-largest- *last*-element heuristic misses large mid-array items (`AgentRuntime.swift:367-369`), text-chop fallback untested. Rework with a size-estimating single pass; add unit tests in the jsdom suite. -**B6. Declared capability matrix.** Silent per-platform divergences to either +**B6. Declared capability matrix.** ([#26](https://github.com/LockInTime/headless/issues/26)) Silent per-platform divergences to either fix or promote to declared differences asserted in tests: PDF raster (macOS, `Host/AgentBridge.swift:236-250`) vs vector (`Page.printToPDF`, `BrowserProcess.swift:471-496`); element-screenshot coordinate space viewport @@ -156,13 +162,13 @@ through it; `press` length enforced in bridge only on macOS (`BrowserProcess.swift:521`). Generate `capabilities` from code (`CLI.swift:648-682` is a hand-written literal today) and assert it. -**B7. Runtime injection cost** — cache the isolated world / install runtime +**B7. Runtime injection cost** ([#27](https://github.com/LockInTime/headless/issues/27)) — cache the isolated world / install runtime per-navigation instead of per-call on both engines; extract the JS to a compiled resource. Spec in [architecture-decisions §10](architecture-decisions.md). (`BrowserProcess.swift:777-834`, `AgentBridge.swift:382-420`, `Tests/agent-runtime.test.mjs` regex extraction.) -**B8. QA diagnostics bridge isolation (macOS).** Page-world injection is +**B8. QA diagnostics bridge isolation (macOS).** ([#28](https://github.com/LockInTime/headless/issues/28)) Page-world injection is detectable/forgeable/spammable by a hostile page (`Host/QADiagnosticsBridge.swift:5-93`, `main.swift:233-236`) while P0 claims isolated-world helpers. Move what's possible; mark the rest untrusted. See @@ -170,35 +176,35 @@ architecture decision §7. ## §C — MCP & agent surface (Phases 1/4) -**C1. Timeout parity.** MCP uses flat 30 s except tour/series +**C1. Timeout parity.** ([#29](https://github.com/LockInTime/headless/issues/29)) MCP uses flat 30 s except tour/series (`apps/headless/MCP/main.swift:70-72`); CLI derives from `--timeout` (`HeadlessCLI/main.swift:104-114`). `wait --timeout 90000` works in CLI, dies via MCP. Derive identically. -**C2. Destructive verbs over MCP.** `stop` (shutdown) and `session close` are +**C2. Destructive verbs over MCP.** ([#30](https://github.com/LockInTime/headless/issues/30)) `stop` (shutdown) and `session close` are callable though the tool description says "safe"; decide policy (deny, or annotate) and test it. -**C3. Zero MCP tests** — the only coverage is inside `qa-videos.sh`. Add a +**C3. Zero MCP tests** ([#31](https://github.com/LockInTime/headless/issues/31)) — the only coverage is inside `qa-videos.sh`. Add a stdio harness test: initialize / tools/list / tools/call / malformed line / oversized line / local-command rejection (`MCP/main.swift:64-66`). -**C4. Machine-accurate `capabilities`** — generate from `CommandName.allCases` +**C4. Machine-accurate `capabilities`** ([#32](https://github.com/LockInTime/headless/issues/32)) — generate from `CommandName.allCases` + engine matrix (see B6) so agents can trust it. -**C5. Harness onboarding [exists: skill content].** Root `AGENTS.md` + +**C5. Harness onboarding [exists: skill content].** ([#33](https://github.com/LockInTime/headless/issues/33)) Root `AGENTS.md` + `CLAUDE.md` (added with this doc set); mirror the skill into `.claude/skills/` or symlink so Claude Code auto-discovers; ship `.mcp.json` example + per- client snippets (Claude Code, Cursor, Codex TOML — replacing the site's hardcoded `ssh hermes-vm` config, `apps/web/components/docs-markdown.ts:57-64`); flesh out `agents/openai.yaml` beyond its 4-line stub or remove it. -**C6. Input fidelity (Phase 4).** Real CDP input on Linux +**C6. Input fidelity (Phase 4).** ([#34](https://github.com/LockInTime/headless/issues/34)) Real CDP input on Linux (`Input.dispatchKeyEvent`/`dispatchMouseEvent`) behind the same verbs; today both engines dispatch synthetic DOM events and `press` special-cases only Enter/Space (`HP/AgentRuntime.swift:492-537`). Declared divergence per B6. -**C7. Considered-and-worth-designing (not committed):** hover/drag verbs; +**C7. Considered-and-worth-designing (not committed):** ([#35](https://github.com/LockInTime/headless/issues/35)) hover/drag verbs; `select` for dropdowns; scoped `evaluate` never (see what-is-excellent §3); per-session isolated profiles (`session create --isolated`, architecture §11); response-body inspection stays denied (P1.md:134) unless a gated design lands. @@ -220,7 +226,7 @@ Follow-ups: mark the jobs required in branch protection (repo setting, not code); consider arm64 Linux E2E on PRs (release covers it on tags); revisit whether the macOS E2E can become a per-PR gate once runtime is measured. -**D2. Known test gaps (from code audit).** No tests for: peer-UID rejection +**D2. Known test gaps (from code audit).** ([#36](https://github.com/LockInTime/headless/issues/36)) No tests for: peer-UID rejection (A9), MCP (C3), `Recording.swift` (ffmpeg args per format/quality, discovery rejection, drop-frame abort at `consecutiveFailures`, stop timeout), `VisualComparison`, `Flows.swift` — including the security property that @@ -239,10 +245,10 @@ which currently passes for the wrong reason. **Done** — the `web` job in `ci.yml` runs `pnpm --filter @headless/web lint` and `build`. -**D4. Cross-engine conformance runner** (architecture §14) replacing drifted +**D4. Cross-engine conformance runner** ([#37](https://github.com/LockInTime/headless/issues/37)) (architecture §14) replacing drifted hand-mirrored E2E assertions (e.g. `macos-e2e.sh:231` vs `linux-e2e.sh:157`). -**D5. Benchmark refresh discipline [exists: benchmark.sh].** Emit JSON +**D5. Benchmark refresh discipline [exists: benchmark.sh].** ([#38](https://github.com/LockInTime/headless/issues/38)) Emit JSON results artifact; re-run with the task-aware flow (BENCHMARK.md:29-31 says current numbers predate `--task`); repeat-count medians instead of single samples. @@ -251,50 +257,55 @@ samples. Owner-decided scope: package managers, no hosted service. -- **E1.** macOS Developer ID signing + notarization + stapling (today ad-hoc, +- **E1.** ([#39](https://github.com/LockInTime/headless/issues/39)) macOS Developer ID signing + notarization + stapling (today ad-hoc, `build.sh:96-114`); universal binary (today `uname -m` on an arm64 runner); Homebrew tap/cask. Resolve the `com.headless.app` provisioning question for the passkey entitlement (`.hermes` plan flagged it; `build.sh:97-109`). -- **E2.** Linux `curl | sh` installer wrapping the existing tarball + +- **E2.** ([#40](https://github.com/LockInTime/headless/issues/40)) Linux `curl | sh` installer wrapping the existing tarball + preflight (`install-linux.sh` already does runtime checks — reuse); align its ffmpeg policy with the runtime's allow-list (installer accepts any PATH ffmpeg, `install-linux.sh:118`, runtime doesn't, `HP/Recording.swift:225-227`); single-source the Snap-detection logic currently duplicated in shell (`install-linux.sh:40-67` vs `HP/ChromiumRuntime.swift:167-184`). -- **E3.** Publish the Docker `production` image to GHCR on tag (release notes +- **E3.** ([#41](https://github.com/LockInTime/headless/issues/41)) Publish the Docker `production` image to GHCR on tag (release notes currently say "not published"); this is also the interim Windows story. -- **E4.** `SHA256SUMS` (+ consider cosign) for all release assets — the QA +- **E4.** ([#42](https://github.com/LockInTime/headless/issues/42)) `SHA256SUMS` (+ consider cosign) for all release assets — the QA evidence bundle already ships sums; releases don't. -- **E5.** npm wrapper package (binary download shim) for `npx` reach. -- **E6.** Version unification + `headless --version` + CHANGELOG + release +- **E5.** ([#43](https://github.com/LockInTime/headless/issues/43)) npm wrapper package (binary download shim) for `npx` reach. +- **E6.** ([#44](https://github.com/LockInTime/headless/issues/44)) Version unification + `headless --version` + CHANGELOG + release automation (architecture §12). `package.json` says 0.0.0, tags say 1.0.x, default `HEADLESS_VERSION` is 1.0.0. -- **E7.** Cut a release: everything since v1.0.2 (capture formats, context +- **E7.** ([#45](https://github.com/LockInTime/headless/issues/45)) Cut a release: everything since v1.0.2 (capture formats, context pruning) is unreleased. -- **E8.** `NSAllowsArbitraryLoads` is blanket-true (`build.sh:86-90`); scope +- **E8.** ([#46](https://github.com/LockInTime/headless/issues/46)) `NSAllowsArbitraryLoads` is blanket-true (`build.sh:86-90`); scope it (localhost exception) if WKWebView allows. ## §F — Website & docs (Phase 5) -- **F1. Deploy pipeline** — none exists (no vercel/netlify/Dockerfile/export - config; only good headers/CSP in `next.config.ts` — keep those, consider a - nonce to drop `unsafe-inline`). -- **F2. Content provenance** — benchmark numbers hand-copied in +- **F1. Deploy pipeline is invisible to the repo** ([#47](https://github.com/LockInTime/headless/issues/47)) — the site *is* live at + `https://headless-web-pi.vercel.app` (set as the repo homepage) via Vercel's + GitHub integration, but nothing in the tree records that: no `vercel.json`, + no deploy docs, no preview-URL comment on PRs, and the temporary + `*-pi.vercel.app` hostname suggests no custom domain. Make the deployment + reproducible and reviewable — check in the project config, document the + hosting in `AGENTS.md`, and decide on a domain. Keep the existing headers/CSP + in `next.config.ts`; consider a nonce so `unsafe-inline` can be dropped. +- **F2. Content provenance** ([#48](https://github.com/LockInTime/headless/issues/48)) — benchmark numbers hand-copied in `app/page.tsx:26-38`, `components/efficiency-chart.tsx:26-31`, `components/benchmark-chart.tsx:21-26` (+ date in two places); docs prose triplicated across `app/docs/page.tsx`, `components/docs-markdown.ts`, and `README.md`, already diverging. Import from generated artifacts (D5, B6). -- **F3. Missing pages:** install (README's build/install section is absent +- **F3. Missing pages:** ([#49](https://github.com/LockInTime/headless/issues/49)) install (README's build/install section is absent from the site entirely), security model, MCP setup, full command reference (~30 commands; site lists 4 groups), changelog/version indicator, platform matrix, the README's comparison table (strongest positioning content, not on site). Plus `robots.txt`, `sitemap`, OG metadata, per-page `metadata`, 404 page. -- **F4. Stale-benchmark honesty:** site quotes pre-`--task` numbers while +- **F4. Stale-benchmark honesty:** ([#50](https://github.com/LockInTime/headless/issues/50)) site quotes pre-`--task` numbers while marketing `--task`, and drops BENCHMARK.md's re-run warning; headline says "Measured, not claimed." Fix by refresh (D5) or by carrying the caveat. -- **F5. Dead weight:** `components/ui/side-rays.tsx` + `ogl` dep (unused), +- **F5. Dead weight:** ([#51](https://github.com/LockInTime/headless/issues/51)) `components/ui/side-rays.tsx` + `ogl` dep (unused), `public/scan-dashboard.png` (unreferenced), leftover shadcn `.dark` block in `globals.css`, 8 unused button variants; two WebGL stacks (~700 KB) for decoration — `PixelBlast` still creates GL contexts under @@ -302,14 +313,14 @@ Owner-decided scope: package managers, no hosted service. reduced-motion rule; Recharts (~150 KB) for 4 static bars. Also `scan-frame.tsx` hardcodes pixel bounds tied to a committed github.com screenshot (drift + trademark question). -- **F6. Fix the Cursor config snippet** — hardcodes `ssh hermes-vm` +- **F6. Fix the Cursor config snippet** ([#52](https://github.com/LockInTime/headless/issues/52)) — hardcodes `ssh hermes-vm` (`components/docs-markdown.ts:57-64`), unusable by anyone else. -- **F7. Docs debt in-repo:** README states P1/P2 features but there is no +- **F7. Docs debt in-repo:** ([#53](https://github.com/LockInTime/headless/issues/53)) README states P1/P2 features but there is no single command reference doc; P1.md should document the `@eN` invalidation contract (A5) and the shared-profile session model (architecture §11); `.gitignore:18-23` still references `apps/chromeless/`. -## §G — Feature ideas (informed dump; schedule via roadmap phases) +## §G — Feature ideas (informed dump; schedule via roadmap phases) ([#54](https://github.com/LockInTime/headless/issues/54)) Gathered from the audit and product thinking; none are committed until they get an architecture-decision entry: