Skip to content

Security model ​

Maintainer HQ keeps secret-bearing operator state local while exposing sanitized projections to its CLI, dashboard, and MCP server. The workspace document is trusted local state, not a safe interchange format.

Workspace document ​

The default document lives at ${XDG_DATA_HOME:-$HOME/.local/share}/maintainer-hq/workspace.json. Maintainer HQ creates the parent directory with mode 0700 and the file with mode 0600. It rejects symbolic links, non-regular files, broad permissions, oversized content, and files that change identity or metadata during a read.

A document outside Git uses private filesystem permissions as its storage boundary. A document inside a Git worktree must also be covered by the git-crypt filter with plaintext Git diffs disabled through -diff. Explicit --require-git-crypt validation is available when this policy must apply regardless of location.

The document can contain provider recovery values, HMACs, private route identifiers, private fallback targets, and application secret values. Do not pass its contents through command arguments, logs, issue reports, browser tools, MCP messages, fixtures, screenshots, CI artifacts, or packages.

Activity ledger ​

The activity sidecar is private operator state, not a publishable event export. It is bound to one workspace ID and can contain non-public repository names, commit and release metadata, operator notes, decisions, blockers, next actions, generated summaries, and cost receipts. Its default directory and file modes, no-follow reads, size bounds, Git-encryption check, adjacent lock, identity validation, and atomic replacement match the workspace boundary.

CLI, dashboard, and MCP consumers receive validated activity records but never the sidecar path or raw file. Provider readers accept only repositories already attached to explicit projects and return allowlisted normalized fields. Source failures become bounded categories without provider bodies. Refresh is idempotent and does not overwrite acknowledgement, pin, archive, or manual audit state.

Security items, coverage, remediation correlations, operator notes, audit records, policies, notification receipts, and value-free secret-distribution operations also live in this sidecar. GitHub security refresh binds selected projects and both source revisions before making a request. The adapter fixes github.com and the supported API version, limits repositories, concurrency, pages, tracked-item rechecks, pull requests, filenames, response bytes, and time, and constructs canonical browser links locally.

Local security-tracker writes have a separate exact plan and confirmation boundary. The service supplies revision, timestamp, actor, audit, and note identities rather than accepting them from a browser or MCP client. Applying a reviewed digest rereads the sidecar and rejects stale or altered requests. Private note text is omitted from plan summaries, all triage changes append bounded audit records, and none can modify GitHub-owned alert or pull-request state. Explicit remediation links remain repository-scoped and symmetric; explicit unlink records prevent provider refresh from silently restoring an inferred link.

GitHub-owned Dependabot alert state has its own narrower provider-action boundary. Only dismissal of an open alert with a fixed supported reason and reopening of a dismissed alert are accepted. Planning reads one normalized live alert and binds workspace, ledger, item, provider, repository, numeric alert, target-state, and reason evidence into a reproducible SHA-256 digest. Apply rereads the local and provider fingerprints, requires explicit provider confirmation, sends a fixed PATCH body to the fixed GitHub host through standard input, ignores the response body, and performs a separate normalized verification read. The ledger changes only after verification. Provider failures are categorical; timeouts are indeterminate; verification and persistence gaps are partial and direct the operator to verify or refresh rather than repeat a possibly successful mutation. Dismissal comments are never accepted or transmitted.

Policy evaluation and notification deduplication occur within the provider-refresh revision. Disabled policies create no receipt, while snoozed items create a suppression receipt instead of disappearing from history; an eligible receipt returns to pending on the first refresh after the snooze expires. Desktop delivery is never automatic. Count-only mode reveals only Maintainer HQ and an aggregate count to the operating system; detailed mode requires a plan that names every disclosed title. The adapter executable, script, selection size, message size, output, and timeout are fixed or bounded, and clients cannot supply a command. Delivery failures retain only a categorical result and do not discard the private inbox record.

Dependabot responses are projected at the provider boundary. Maintainer HQ retains only normalized package, ecosystem, manifest, severity, advisory identity and summary, vulnerable range, patch version, provider state and timestamps, pull-request state, refs, commit IDs, labels, filenames, conservative inferences, and coverage counts. It does not retain raw responses, advisory descriptions, dismissal comments, actors, assignees, pull-request bodies, patches, blobs, dependency names from the SBOM, or provider diagnostics. A failed or truncated stream remains distinct from a successful empty collection.

Safe reads ​

Maintainer HQ opens private files with no-follow and nonblocking semantics, reads within fixed bounds, and verifies the same file descriptor and path identity after reading. Browser and MCP projections expose repository policy, capability state, secret names, consumer metadata, monitoring target IDs, and environment variable names, but not stored values, HMACs, raw routes, monitoring URLs, or probe configuration.

Environment inventory recognizes live and template dotenv-style files beneath configured roots. It does not follow candidate-file links. The browser receives path, mode, classification, and variable names only. Permission repair changes only eligible discovered live files and never reads their values into a plan.

GitHub secret discovery accepts only active repository identities already enrolled in the workspace. It returns environment names through bounded fixed-host pages. Destination preflight and verification return only whether one exact Actions secret key is present and its provider update timestamp. They do not read a provider secret value because GitHub does not return one.

Atomic writes and recovery ​

Every workspace writer acquires a private adjacent lock, rereads the planned revision, writes a private temporary file, syncs content and metadata, replaces atomically, and retains the lock through completion. Competing or stale writers fail closed. If a process stops while holding a lock, confirm that no writer remains before removing the adjacent hidden lock file.

Local and secret-bearing plans bind the complete mutation to a document revision with a process-local HMAC. Secret-bearing plans remain in a bounded expiring memory store, and only their digest and redacted summary leave the service. The stateless Dependabot action digest is reproducible across CLI processes but is not an authorization token: authenticated session or MCP request state, explicit confirmation, exact action input, and fresh local and provider rereads are all still required. Apply rereads both workspace and relevant local evidence before confirmation and inside the write lock.

Canonical distribution first commits the reviewed canonical record, then commits a pending value-free operation to the sidecar before any GitHub write. A failure to persist that operation stops the workflow before provider mutation. Each attempted destination advances through a durable attempt record and a separate presence verification. Provider timeouts remain indeterminate, and failed verification or post-call persistence remains recoverable rather than being treated as success. Resume is allowed only for unresolved targets while the stored canonical revision matches the operation; a later canonical value supersedes the old operation.

The provider subprocess inherits only an explicit operational allowlist such as GitHub authentication, proxy, certificate, locale, path, and temporary-directory variables. It receives the canonical value solely on standard input to a fixed gh secret set invocation. The value is never placed in an argument, environment projection, captured stdout, diagnostic, provider record, activity entry, or audit summary. Captured provider diagnostics are bounded and collapse to a fixed error category before persistence or response.

Managed Hookrelay retirement retains recovery until provider route, KV, receiver HMAC, declared sender secrets, and final synchronization absence have all been verified. A safe private recovery manifest makes failed stages resumable. Linked, non-regular, or broadly readable recovery output is removed instead of retained.

Dashboard boundary ​

The broker binds only to 127.0.0.1 on a random port. Every page and API request uses a random session route and session header, checks the exact loopback Host and same-origin request, and serves an allowlist of bundled assets through bounded no-follow descriptors. The page loads no remote scripts and receives a restrictive Content Security Policy.

Each launched broker stores its loopback capability URL, stable workspace ID, mode, process ID, protocol versions, and start time in a user-owned 0700 runtime directory with a 0600 no-follow record. It stores no document path, workspace name, provider credential, or canonical value. Session discovery validates the record owner and mode, exact loopback URL shape, process liveness, public authorization bootstrap, and a separate authenticated identity response before printing the capability. Graceful shutdown removes the record, and later discovery prunes records whose process exited.

Write sessions use exact revision-bound plans and reject changed mutations. Scheduled re-review evidence exposes repository identity, due date, and commit ID but not checkout contents or diffs. Endpoint Monitor inventory is reduced to validated target IDs before it reaches a snapshot; adapter failures become fixed unavailable observations rather than leaking output.

AI activity assistance is disabled unless the server inherits an AI Gateway credential, and no feed operation calls a model automatically. The operator selects exact activity records, purpose, and field classes before planning. Safe briefing defaults omit bodies and repository names. Summary-mode generation can include bodies only through the same explicit checklist and complete disclosure. Planning uses current catalog metadata to admit only providers marked for zero retention and no prompt training; apply fixes that provider allowlist in the Gateway request and sends the prompt only after a second confirmation.

The summary-mode prompt uses positional records and omits stable activity IDs. The returned batch must have the exact selected count and order, with each plain headline bounded by words, characters, and control-character policy before private IDs are restored. The ledger stores a SHA-256 prompt hash rather than the prompt itself. It retains the purpose, selected entry IDs and timestamps, field classes, structured generated output, model, eligible-provider and pricing snapshot, estimate, usage, actual Gateway cost, and bounded status so disclosure, freshness, and spending remain auditable. It discards provider error bodies and any output whose receipt exceeds the reviewed cost limit. Because the limit is checked against the returned receipt, it is an approval and output-acceptance boundary rather than a provider-side billing cap.

The Shared secrets page receives only canonical identifiers, descriptions, revisions, intended destination metadata, provider name presence, and value-free operation state. A replacement value exists in the password control only until plan handoff; the frontend clears that control immediately after constructing the request, and no returned plan or response can reconstruct it. URLs contain only safe identifiers, filters, operation IDs, and modal names. Operators should take browser screenshots only after the handoff when a real value has been entered.

MCP boundary ​

The MCP server is a local stdio process and opens no network listener. Protocol frames use stdout and diagnostics use stderr. A read-only server leaves local planning and provider verification available while rejecting workspace, filesystem, migration, projection, recovery, provider-action planning, and provider writes.

Tool callers cannot request the raw workspace document, a secret-bearing projection, an environment value, an arbitrary executable, a provider method, or an API URL. Secret changes and canonical distributions identify an inherited environment variable; the process resolves its value only into short-lived memory and refuses to reuse a variable required by the provider subprocess. Startup and provider diagnostics redact inherited secret-shaped values before returning output.

Confirmation-gated tools mint authenticated method-bound request state, show the complete exact plan, require explicit approval, and reject mismatched or replayed input. Provider writes rerun the adapter plan before and after confirmation, reject failure, suppression, truncation, or changed output, and retain the adapter's own confirmation rules.

Publication boundary ​

npm run check:publication reads every publishable project file through bounded no-follow descriptors and rejects unexpected links, environment files, machine-private paths, secret-shaped material, remote browser assets, inline scripts, non-ASCII content, and unsafe entrypoints. npm run check:package independently inspects the exact npm file manifest. The release package excludes tests, CI, development scripts, local state, and build output.

The documentation build adds a second bounded inspection over the generated artifact. It requires the complete page and routing surface, rejects linked or oversized output, remote browser assets, source maps, secret-shaped material, and machine-private paths, and publishes only synthetic product data. CI stamps the verified artifact with its exact source revision, passes that artifact unchanged to the protected deployment job, and verifies the canonical origin after publication.

Vulnerability reporting ​

Use GitHub private vulnerability reporting when available. Include the affected version, impact, minimal reproduction, and proposed mitigation, but never attach a real workspace document, activity ledger, credential, provider projection, environment value, private repository identity, or browser artifact containing private state.

If private reporting is unavailable, contact the repository owner privately through their GitHub profile. Do not open a public issue for an undisclosed vulnerability.

A Strange Lasers project released under AGPL-3.0-only