Threat model
fastpace — STRIDE threat model
Public artifact answering "what could go wrong with fastpace itself." Walks each STRIDE category against each shipped primitive. Updated per-release; cross-linked from /trust. Last updated: 2026-05-08 · current version 0.34.0
Scope
This is a threat model of fastpace as a system the customer installs and operates — not of the AI runtimes fastpace mediates. The runtime threat model (Bedrock, Azure OpenAI, Vertex, Anthropic direct) is the customer's responsibility, governed by their direct contract with that provider. We explicitly do not assume responsibility for those data paths and the /trust page documents that boundary.
In scope:
- The
@fastpace-ai/fpCLI binary and every module it imports - The local UI server (
fastpace ui) and its API surface - Every shipped hook (
assets/hooks/*.js) - Every shipped agent identity manifest and skill
- The on-disk artifacts:
fastpace/audit.log,fastpace/manifests/,fastpace/explanations/,fastpace/aibom/,fastpace/exceptions.log,fastpace/corrections.log,fastpace/coach-feedback.log,fastpace/sast/,fastpace/redteam/,fastpace/control-monitor/,fastpace/data-taps/,fastpace/audit-gate/,fastpace/policy/ - The optional self-hosted org dashboard (F1.13)
- The SAML envelope verifier (F1.14) and Sigstore-style attestation bundle (F1.12)
Out of scope:
- The AI runtime API itself (covered by the customer's runtime contract)
- The customer's git server, npm registry, container registry, etc.
- The customer's identity provider's own security posture (we trust the IdP-signed assertion; the IdP's posture is their concern)
Trust boundaries
Three trust boundaries delineate the system:
- Developer machine ↔ AI runtime. Crossed every time an agent calls the runtime API. fastpace's prompt-redactor (F0.5) sanitizes outbound data; runtime guard (F1.11) enforces approved-runtime policy.
- Repo ↔ org dashboard. Crossed when the org dashboard ingests a repo's signed summary. Each summary is signed with the install's F0.1 identity; the dashboard verifies before persisting.
- Org ↔ external policy/identity authority. Crossed when (a) the org admin publishes a signed policy bundle (F2.11) for repos to pull, and (b) the SAML IdP issues an envelope (F1.14) for fastpace to verify. Each direction is signed with the appropriate authority's private key; fastpace verifies against a configured public key.
STRIDE walkthrough
S — Spoofing identity
What could go wrong: An attacker passes off non-authentic content as having come from a legitimate party — a fake audit entry, a fake AI-BOM, a fake policy bundle, a fake SAML profile.
| Primitive | Mitigation |
|---|---|
| F0.1 cryptographic identity | Every install has an Ed25519 keypair generated locally. The private key never leaves the machine. The fingerprint is stable; identity rotation produces a clean break with the prior key archived. |
| F0.2 hash-chained signed audit log | Every entry is signed with the install's F0.1 private key. Verifying a chain requires the matching public key; rotated/archived keys are tried in order. |
| F2.4 AI-BOM | Each BOM is signed by F0.1 and identifies the install_id + fingerprint of the signer. |
| F1.12 sanctioned-install attestation + Sigstore bundle | Co-signed by F0.1 (proves the install agreed) and the org private key (proves the org issued the license). Sigstore bundle wraps the signature in a verifier-friendly envelope. |
| F2.11 policy bundle | Signed by the org admin's F0.1 key; verifyBundle rejects any bundle whose signature does not match the configured trusted issuer. |
| F1.14 SAML envelope verifier | Verifies the IdP signature over the canonical JSON profile. Issuer + audience checks reject envelopes signed by an authentic-but-wrong-tenant IdP. |
| F2.6 trusted data taps | Each tap is signed by the install identity at registration; listTaps drops invalid signatures from the active set. |
Residual risk. If the developer's machine is compromised at a level that exposes the F0.1 private key, the attacker can sign anything. The key file is stored at ~/.fastpace/identity/ with 0o600 permissions and is never transmitted; this is a host-security problem.
T — Tampering with data
What could go wrong: An attacker (or a careless edit) modifies an artifact after the fact and presents the modified version as authentic.
| Primitive | Mitigation |
|---|---|
| F0.2 hash chain | Each entry's entry_hash is sha256(canonical_json(body)); the next entry binds via prev_hash. Editing any entry breaks every later entry's chain. fastpace verify walks and reports the first break. |
| F1.1 run manifests | Each manifest is signed; verifyManifest recomputes entry_hash and rejects mismatches. |
| F2.4 AI-BOM | Same: signed body. |
| F2.3 provenance trailers | Commit messages carry Fp-Run-Manifest: etc. Tampering with the commit message breaks the trailer-to-manifest binding; verify-provenance reports it. |
| F2.2 explanation bundles | Sixth proof layer in the verify-provenance chain: explanation digest links back to the manifest. |
| F2.6 trusted data taps | Tampered entries fail signature verification and are dropped from listTaps. |
| F2.5 control monitor | Each drift report is signed; controlMonitor.listReports surfaces signature presence. |
| F2.9 audit gate | Each gate report is signed. |
| F2.11 policy bundle | Tampering anywhere in the bundle invalidates the signature; the local pre-bundle backup at fastpace/policy/local-pre-bundle.json lets apply be reverted cleanly. |
| F0.4 agent identity manifests | Frontmatter is part of the manifest; the agent-scope-guard hook re-reads on every call so swapping a manifest at rest immediately changes the operational scope. |
Residual risk. A physically-present attacker with write access to the repo can rewrite both the chain and the matching signatures (the key is on the same machine). Defense-in-depth: replication of the audit log to immutable storage (F3.11 Tier-3 backlog) closes this.
R — Repudiation
What could go wrong: A user denies having performed an action that fastpace recorded — "I never granted that exception."
| Primitive | Mitigation |
|---|---|
| F0.1 identity | Every signed artifact is bound to a specific install_id + fingerprint. The signing machine is identified. |
| F0.2 audit chain | Append-only with hash linkage. Repudiating a recorded action requires breaking the chain (which is detectable). |
| F1.8 exception workflow | Every exception request and revoke is signed into the chain. Exceptions are themselves audit records. |
| F2.3 provenance trailers | Every AI-authored commit carries trailers binding the commit to a specific run manifest, agent, and prompt hash. |
| F1.14 RBAC + SAML | Every privileged action goes through the role-permission check; the action's actor is captured in the audit entry. |
| F2.14 coach feedback loop | Every rating is signed and timestamped; rating-token includes the original block_id so feedback cannot be re-attributed to a different block. |
Residual risk. Same as Tampering: if the F0.1 key is compromised, attribution is unreliable. This is the host-security problem.
I — Information disclosure
What could go wrong: A secret, PII, or sensitive code snippet leaks out of the developer machine through a fastpace-mediated channel.
| Primitive | Mitigation |
|---|---|
| F0.5 prompt redactor | UserPromptSubmit hook strips secrets (regex patterns: AWS keys, GitHub PATs, JWTs, generic API tokens) and PII (email, phone) before the prompt reaches the runtime. Custom patterns extend the built-in set. |
| F1.10 sensitivity-tagged context | Frontmatter on context files declares sensitivity (public, internal, confidential, restricted). Hooks can refuse to include restricted content in prompts that target a runtime not on the approved list. |
| F2.6 trusted data taps | Allow-list of source patterns permitted to feed prompts. A source matching no active tap is blocked + logged. |
| F1.11 approved-runtime registry | Calls to non-approved runtimes are blocked at the runtime-guard hook. Limits the blast radius of an exfil attempt to the approved runtime's data path. |
| F1.7 MCP scope mediation | MCP server calls are filtered by allowed_servers + allowed_actions. A compromised agent cannot call mcpgithubmerge_pull_request if the policy says [read, comment] only. |
| F1.10 sensitivity tagging in audits | audit.log entries record the sensitivity tag of any context passed in; auditors can enumerate every cross-tier flow. |
| F0.5 redaction logs | What was redacted (kind + count) is logged, but the redacted content itself is NEVER persisted. |
| Identity key storage | F0.1 private key is stored at ~/.fastpace/identity/private.pem with 0o600 permissions. Never committed, never transmitted. The .gitignore ships a hard-block on .fastpace/identity/. |
Residual risk. Redaction is regex-based and can miss novel secret formats. The prompt-redactor.js hook accepts custom patterns and the F2.13 SAST hook covers a different layer (post-write code). Future work: a learned redactor that also flags potentially-sensitive prose.
D — Denial of service
What could go wrong: A hook or guard fires on every AI call and slows the developer to a crawl, or a misconfigured policy blocks every action.
| Primitive | Mitigation |
|---|---|
| Hook timeout convention | Every shipped hook reads stdin, performs at most one synchronous file system check, and exits in <50ms on the happy path. The audit-logger, agent-scope-guard, prompt-redactor, and runtime-guard hooks specifically exit on first matching deny rule. |
| F1.3 circuit breaker | Refuses to fire below 3 sample sessions to avoid false positives during onboarding. The fleet-wide baseline (Sprint 15) lets a brand-new install borrow fleet baselines instead of failing open until it has its own samples. |
| F1.8 exception workflow + F1.9 coach | Every block surfaces the exact fastpace exception request command. Developers always have a path forward — they don't have to disable a hook to unblock themselves. |
| F2.13 SAST/lint required | block_on is configurable per repo (default high); require_all_tools defaults to false so a missing tool doesn't fail the push. The hook only fires on git push (or explicit opt-in patterns), not on every Bash call. |
F2.9 audit gate --soft mode | Operators can roll out the gate in soft mode that records reports without blocking; only flip to hard mode after the report is consistently green. |
| F2.11 policy bundle apply backup | Apply writes a local-pre-bundle.json snapshot of the prior config; one command reverts. |
| Worst-case bypass | Setting FASTPACE_NO_HOOKS=1 is documented as the developer-side bypass. The bypass itself is logged to the audit chain (next-sprint hardening). |
Residual risk. A hook with a real bug could fail closed. Mitigation: each hook ships try/catch around its main path with allow() as the default failure mode (fail-open is the right choice when the alternative is the developer being unable to work; the audit chain still reflects the failure for post-hoc review). High-risk hooks (runtime-guard, agent-scope-guard) fail closed by design.
E — Elevation of privilege
What could go wrong: A non-privileged actor performs an action that should require a privileged role.
| Primitive | Mitigation |
|---|---|
| F0.4 agent identity + scope | Every agent declares allowed_tools, allowed_paths, max_files, etc., in frontmatter. The agent-scope-guard hook enforces on every tool call. |
| F1.11 approved runtimes | Calls to a non-approved runtime are blocked even if the agent's scope would otherwise allow them. |
| F1.7 MCP scope mediation | Per-server allowed_actions blocks privileged MCP actions (merge_pull_request, delete_repo) even when the server itself is allow-listed. |
| F1.6 least-privilege bootstrap | fastpace grant is the only path to expand an agent's scope. Each grant is signed into the audit chain with reason + reviewer + TTL. |
| F1.8 risk-tier exceptions | Critical / irreversible / exfil_eligible tiers require a named reviewer, not self-approval. The exception module enforces this at the API layer. |
| F1.14 RBAC roles | Four roles (dev / team_lead / security / admin); each permission is checked at the org-dashboard endpoint level. |
| F1.14 SAML envelope verifier | Real signature + window + audience check. An attacker cannot forge a SAML envelope without the IdP private key. |
| F1.12 sanctioned-install attestation | Distinguishes sanctioned (org-licensed) installs from personal npm i -g installs. The org-dashboard can refuse to ingest summaries from non-sanctioned installs (gate is policy-defined). |
Residual risk. A user with admin role plus repo write access can do anything. Mitigation: every admin action is in the audit chain, and the F2.7 red-team harness includes scenarios for admin-account abuse.
Cross-cutting design choices
These come up repeatedly in the per-category analysis above.
- Plaintext is never persisted in a signed artifact. Only sha256 digests of prompts, responses, and contexts. This is a load-bearing rule across F1.1, F2.2, F2.4, F2.3, F2.6, F2.11.
- Canonical JSON for every signed body. The
src/util/sign.jshelper centralizes the convention (sorted keys, no whitespace). - Append-only logs, never updates. corrections.log, exceptions.log, redaction.log, coach-feedback.log are NDJSON and never rewritten. This eliminates a whole class of race conditions.
- Policy as data, not code. Runtimes, MCP scopes, redaction patterns, blast-radius defaults are YAML config or signed bundles — not JavaScript. Auditors can read the policy without reading code.
- Hooks fail open by default. A hook bug should never block the developer; it should record the failure to the audit chain and let the call through. High-risk hooks (runtime-guard, agent-scope-guard) fail closed by explicit design.
- Pro-tier server surfaces gate behind signed bearer tokens, not phone-home. The org-dashboard's read endpoints (
/api/org/rollup,/installs,/baselines,/maturity,/seats,/roi,/summary) verify a short-lived Ed25519-signed access token offline against the bundled issuer pubkey. There is no per-request call back toapi.fastpace.net; air-gapped enterprise installs and licensing- service outages don't take customer dashboards down. Revocation is "stop minting" — a cancelled customer loses access in ≤ 7 days (online) / ≤ 365 days (air-gap). Same-origin browser calls (the bundled web UI) bypass the gate; CLI / external monitoring callers must present a valid bearer. Gate is opt-in viaFASTPACE_ORG_REQUIRE_TOKEN=onfor the v0.34 → v0.45 grace window; default flips at v0.45. See ONBOARDING.md §3 for the full design.
Wave-4 (v0.36.0) — security-relevant additions
Three commands shipped in v0.36.0 touch the security perimeter and are worth calling out explicitly.
fastpace key-vaultis a BYOK registry, not a key store. The vault file at~/.fastpace/key-vault.json(mode 0600) stores only a SHA-256 fingerprint of the key plus the name of the env var the raw key lives in. The raw API key is never persisted by fastpace — it stays in the user's environment. Threat model: a host-compromised attacker who reads the vault file still cannot impersonate the customer at the model vendor; they need the env var. Rotation events are timestamped but not signed (no replay-proof rotation log yet — flagged for v0.37).fastpace price-watchreads the bundledassets/model-prices.jsontable and a local snapshot at.fastpace/model-prices.snapshot.json. No network calls. The snapshot is unsigned; an attacker with write access to the install could falsify a "price went down" report. Mitigation: the bundled prices table is shipped in the npm package and verifiable by reinstall. Real-time pricing pulls (which would require a network call to model-vendor pricing APIs) are deliberately out of scope.fastpace burnout-checkis the only Wave-4 command that reads private timing data about a single developer. Default state is OFF; requires explicit--enableto even compute. Stores its state at~/.fastpace/burnout.json(per-machine, not synced to the org dashboard, ever). The help text explicitly tells the user "if you feel surveilled by this feature, opt out." Threat model: a hostile manager could ask the dev for the file, but cannot pull it from the install without root on the dev's machine. The fastpace shipper does not include this data in the org-dashboard payload. Design rule: never add a fleet-rollup endpoint for burnout-check signals.
Four gateway-dependent Wave-4 commands (model-route, key-vault, local-model, token-budget) ship their config surfaces only. Real call-time enforcement requires the model gateway, which remains parked. Until then, these commands are advisory and a host-compromised attacker can edit the config files freely — the policies serve as documentation and warning surfaces, not enforcement. Be explicit with customers that buying Platform today does not buy gateway-level enforcement.
The platform_metrics rollup added to the install→org-dashboard shipper carries: bus-factor scores, AI-share %, vendor-HHI, coverage-gap counts, token-budget %, redteam findings counts, drift configured-yes/no. No prompt text, no manifest payloads, no developer-identifying data — only counts and percentages. Same posture as the existing roi, maturity, and audit_coverage payloads on the same shipper.
v0.40.0 (Wave 5c.A) — ship-layer additions
The audit chain now carries a new event type deploy.recorded written by fastpace deploy record. Schema-extends the chain; fastpace verify walks the new event type identically to existing hook.* entries. No new trust boundary introduced — the deploy event lives entirely on the customer machine until the existing audit-replication shipper sends it onward. Same posture as roi, maturity, platform_metrics payloads on the same shipper.
Provenance trailers can now optionally carry X-fastpace-Deploy: <tag>. Threat-model implication: the trailer is set by the customer's own commit-author tooling (post-tag hook), not by fastpace; an attacker with commit-author access can spoof it. Mitigation: trailers are verifiable against the corresponding signed deploy attestation in fastpace/deploys/<id>.json and the audit-chain deploy.recorded entry. If the trailer claims a deploy that no attestation backs, the claim fails verification — soft-fail with a clear "no matching attestation" signal in fastpace verify.
v0.41.0 (Wave 5c.B) — metrics-source connector trust boundary (forecast)
When the metrics-source connector ships in v0.41.0, a new trust boundary opens: fastpace → external observability vendor (Datadog / Prometheus / Sentry / CloudWatch / New Relic / Splunk). Per the original threat model's design rule — every new trust boundary gets a STRIDE walkthrough — here it is.
STRIDE — fastpace ↔ external metrics source
| Threat | Risk | Mitigation |
|---|---|---|
| Spoofing. The connector is tricked into hitting a fake vendor endpoint (DNS poisoning, MITM, vendor-look-alike domain). | Attacker harvests the customer's metrics-API token + receives queries that reveal which services/metrics the customer monitors. | TLS verification required on all vendor calls (no --insecure flag, no certificate pinning bypass). Per-provider hostname allowlist baked into the adapter (e.g. Datadog adapter only talks to api.datadoghq.{com,eu}). |
| Tampering. Vendor (or someone on-path between fastpace and the vendor) returns falsified metrics. | A canary deploy looks healthy when it actually isn't, or vice-versa — leading to bad rollback decisions. | (a) Metrics are advisory inputs to a human decision (fastpace rollback assess is a recommendation engine, not an actuator — never auto-rolls-back). (b) Caching writes results to fastpace/metrics-cache/<provider>/<hash>.json with the query hash; cache poisoning requires file-system write access on the dev's machine, which means the threat is already past the trust boundary. (c) Multi-source cross-check: customers can declare two providers (e.g. Datadog + Sentry) and the verify step warns when they disagree. |
| Repudiation. Vendor denies sending data, or fastpace denies sending a query. | Customer can't trace "why did fastpace recommend rollback at 14:23?" | Every query + response gets logged to fastpace/audit.log as a new event type metrics-source.queried (signed, chained). The query hash + response hash + provider name + timestamp are all in the log; vendor logs are out-of-scope but the customer-side record is forensic-grade. |
| Information disclosure. Fastpace sends customer's prompt text or commit content to the vendor. | Customer's source-code intent leaks into the metrics provider's logs. | fastpace never sends prompt text or source code in metrics queries. Queries are pre-canonicalised against the vendor's query DSL (e.g. PromQL, Datadog metrics-query) and verified to contain only: service names, metric names, time ranges, threshold values. The connector enforces this at the adapter layer — any query string containing identifiers outside an allowlist of metric/service/env names is rejected before transmission. |
| Denial of service. Vendor is rate-limiting or down. | fastpace deploy verify blocks indefinitely waiting on response; CI gate times out. | Configurable per-query timeout (default 30s) + async-with-polling fallback for long windows + cached results with TTL (default 5 min). If all three fail, verification reports metrics-source: unavailable rather than hanging — and the rollback-assess step downgrades to "human judgment required, no metrics signal." |
| Elevation of privilege. Compromised metrics-API token grants attacker access beyond just reading metrics. | Customer's metrics-API token is exposed via fastpace; attacker uses it to write metrics, modify alerts, or pivot inside the vendor account. | (a) Tokens are stored env-var-only via fastpace key-vault (never on disk in fastpace's tree). (b) The connector's request library rejects all non-GET HTTP methods (read-only by construction) so a token that has write-scope can't be used to write through fastpace. (c) Customer must mint a read-only API token in the vendor dashboard and grant only the metric scopes the connector needs — documented in the per-adapter setup guide. |
Threats out of scope (deliberately)
- The metrics provider's own infrastructure security. We trust Datadog / Prometheus / Sentry / etc. to be SOC 2 or equivalent; we don't audit them. Customer is responsible for their vendor-relationship trust posture.
- Network-layer attacks against the customer's local Prometheus instance (when running air-gapped). Covered by the customer's own network security.
Audit-trail additions
- New event type
metrics-source.queriedinfastpace/audit.logwith{ provider, query_hash, response_hash, timestamp, latency_ms, cache_hit }. - The Wave 5c.B
deploy verifyflow writes a derived eventdeploy.verifiedlinking the deploy attestation, the metrics-source events, and the verification verdict.
What changes in the next-sprint horizon
These are documented in future-work.md and will update this threat model as they ship:
- F2.1 correction-detector mtime → blame upgrade (Sprint 17). Closes the overcounting branch when AI edits and humans later rewrite unrelated lines.
- F3.10 webhook event bus. Outbound events to Slack / PagerDuty / SIEM. Will introduce a new trust boundary (fastpace → external SIEM) that needs its own STRIDE walkthrough.
- F3.11 audit log replication to S3 / GCS. Closes the residual risk on Tampering — even a host-compromised attacker can't rewrite history if a copy is in immutable storage.
- F1.13 standalone org dashboard binary. Splits the dashboard out of the CLI process. New trust boundary (CLI → dashboard) needs its own signature-verified mTLS or signed-summary handoff. Shipped: the dashboard now ships with a bearer-token gate on all read endpoints (default off; flips to
onat v0.45). Tokens are Ed25519-signed by the same issuer key as licenses; verification is offline against the bundled pubkey. See cross-cutting design choices above. - Public docs site. Will host this threat model; current location at the repo root is interim.
Reporting
To report a security issue or a missing item in this threat model: security@fastpace.dev. Acknowledgement within 48 hours; initial triage within 5 business days.