firm ships as Claude Code plugins (engineering-core, engineering-rails), each carrying its
own plugin version. Security fixes are applied to the latest released version only; there is no
back-port branch. Update with scripts/firm-plugins.sh update (or /plugin update) to receive them.
| Version | Supported |
|---|---|
| latest release | Yes |
| anything older | No |
Please do not open a public issue for security vulnerabilities.
Report privately using GitHub's private vulnerability reporting
on this repository (the Security → Report a vulnerability tab), or email davidslv.london@gmail.com
with the subject line SECURITY: firm.
Please include:
- a description of the issue and its impact,
- the plugin and version (or commit) affected,
- steps to reproduce or a proof of concept,
- any suggested remediation.
- Acknowledgement within 3 working days.
- An initial assessment and severity triage within 10 working days.
- Coordinated disclosure: we will agree a disclosure timeline with you and credit you (unless you prefer to remain anonymous) once a fix is released.
firm is an engineering operating system delivered as Claude Code plugins — skills, agents, prompts, and a set of deterministic shell/Ruby checks that a developer runs on their own machine against their own repository. It is not a network service, and it has no server, no hosted component, and no telemetry. The realistic security surface is therefore about what firm executes on the operator's machine and what it might leave behind in a repository, not about a remote attacker.
firm's gate (.firm/hooks/firm-gate.sh and the checks in checks.d/) is deliberately built so that a
check is a reporter, not an actor:
- Each check reads the repository and prints findings, one per line, tab-separated
(
<rule>\t<file>\t<message>), and always exits 0. The gate — not any individual check — decides whether to block. A check never mutates the tree, stages a commit, or pushes. - The gate runs as a local pre-push hook the operator installs via
firm install. It is opt-in, gitignored, and reversible; nothing runs on a teammate's machine unless they installed it too. - Overrides are explicit and recorded, never silent: a blocking finding is cleared only by the
exceptions ledger (
.firm/conventions-exceptions.yml) or a one-shotFIRM_OVERRIDE="rule@path: reason".
Because of this shape, the correctness risk we take most seriously is a check that produces a wrong verdict — a false "clean" that lets a real problem (an unsafe migration, a leaked secret pattern, a missing tenant scope) through the gate, or a false failure that trains people to bypass it. Report either via the normal bug template unless the report itself is sensitive.
Some seams run operator-supplied commands, and this is the main place to reason about trust:
- External-tools seam (
.firm/tools.yml). firm's one generic adapter (external-tool-evidence.sh) runs theevidence_commandyou register for each tool and routes its stdout into a seam as evidence. firm ships zero such commands and names no vendor — every command is one you put there. Treat.firm/tools.ymlas trusted input: anything you register runs with your shell privileges when a seam fires. An absent or failing command degrades to silence, never a failure. - Orchestrated tools (
brakeman,bullet). These run from the sidecar's own bundle, or are injected into the host's own test run — never into the host's committedGemfile/bundle. - Generators (
.firm/generators/) scaffold convention-correct, test-first Rails code. They write new files in the working tree; they do not run migrations or execute the code they produce.
None of these reach the network on firm's behalf, and none phone home. If you find a path where firm executes something you did not register or expect, that is a vulnerability — please report it.
firm is designed to be invisible to a repository it is not installed in and to keep secrets out of version control:
- The entire persistent footprint is a single gitignored
.firm/sidecar. firm never writes to the host's committed tree, bundle, or history unless the operator explicitly opts into shared mode. Install and uninstall are non-destructive. - Operator-private configuration — issue-tracker URLs (
.firm/ecosystem.yml), external tool names and commands (.firm/tools.yml), the exceptions ledger — lives only in the gitignored sidecar and can never leak into the shared firm distribution or a committed diff. - firm has no credential store and asks for no tokens of its own. It does not read, cache, or transmit secrets. A finding that firm has caused a secret, a tracker URL, or any private configuration to be written to a tracked file is in scope and should be reported.
- A gate check that returns a wrong verdict (false clean or false failure) on a security-relevant seam (migration safety, secrets/PII, tenant scoping, auth, injection).
- Any path by which firm executes code the operator did not register or expect, or escalates beyond the read-only-reporter contract.
- Any path by which firm writes private configuration or secrets into the host's committed tree, or otherwise breaks the leaves-no-trace guarantee.
- Path traversal, command injection, or unsafe interpolation in a shipped check, adapter, generator, or installer that a crafted repository, Profile, or filename could trigger.
- Vulnerabilities in Claude Code itself, or in third-party tools you register via
.firm/tools.yml(report those to their maintainers). - The behaviour of a model acting on firm's prompts — firm's skills are guidance, and the operator remains in control of every action a session takes.
- Findings that require an already-compromised machine or a malicious operator acting on their own repo.
firm reports; the operator decides. firm's checks and reviewers surface findings and draft artifacts. They do not deploy, merge, or mutate anything on their own. Every action stays under the operator's control and Claude Code's permission system.