Skip to content

k9securityio/example-agent-mastra

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

example-mastra-agent

A minimal, security-hardened example of a Mastra agent calling the k9 Security MCP server's score_risk tool (reachability-aware vulnerability triage) over OAuth 2.0. Use it as a starting point for calling score_risk from your own agents.

The recommended path is OAuth 2.0 client-credentials: provision a service client, exchange its client_id / client_secret for a claim-bearing token, and call the tools. No human, no browser, no refresh token. The interactive device flow stays as a fallback for quick evaluation. Both are validated end-to-end against k9 Security.

Requirements

  • Node.js >= 24 (runs the TypeScript directly via native type-stripping; no build step, no tsx/esbuild).
  • An active k9 Reachable Risk entitlement on your k9 account (trial or paid); score_risk is paywalled.
  • A service client for the recommended path: in the k9 app, open your customer → Service Clients (M2M) and copy its client_id + one-time client_secret.

Setup

cp .env.example .env           # set K9_CLIENT_ID + K9_CLIENT_SECRET
npm install --ignore-scripts   # builds the lockfile under the .npmrc before-floor
npm ls easy-day-js             # MUST be empty
npm audit signatures           # verify registry signatures
# commit package-lock.json, then use `npm ci` for all later installs

Pinned to clean pre-incident Mastra releases; do not loosen the pins or .npmrc without re-vetting (see Security posture).

Auth

Two modes; the example picks based on the environment:

  • Client-credentials (recommended, unattended). Set K9_CLIENT_ID + K9_CLIENT_SECRET from your service client. The example mints a claim-bearing token automatically and re-mints on expiry: no login, no browser, no refresh token. Keep the secret out of source and history.
  • Device flow (interactive fallback). With no client-credentials set, run npm run login once to approve in the browser as your k9 account; the example then uses and refreshes that token.

Use

# client-credentials: set K9_CLIENT_ID / K9_CLIENT_SECRET in .env, then:
npm run list-tools   # connect + list k9 tools (no LLM needed)
npm run score        # call score_risk on a sample finding (no LLM needed)
npm run score-batch  # score several findings in ONE call (connect amortized over the batch)

# device-flow fallback only:
npm run login        # one-time browser approval; caches the token

score-batch scores multiple findings in a single score_risk call (the tool takes findings[], up to 50). A representative run:

connect + listTools: 12355 ms   |   score_risk (3 findings, 1 call): 757 ms
  log4shell-prod-reachable   CVE-2021-44228   -> FIX_TODAY   (KEV + reachable + tier-1/confidential)
  requests-not-loaded        CVE-2026-25645   -> DEFER       (not loaded)
  requests-reachable         CVE-2026-25645   -> SCHEDULE    (same CVE, reachable, tier-2)

Two takeaways. The ~12s connect (a per-session cold start the first time the k9 server is hit after idle) is paid once while the 3-finding batch scores in <1s, so batch findings into one call (and reuse the client for a long-running agent). And the same CVE flips DEFER to SCHEDULE purely on reachability and asset context: reachability is the lever, not the CVE alone.

Cached tokens live under ~/.cache/k9-mcp-example/ (mode 0600): <stage>-cc.json for client-credentials (re-minted on expiry), <stage>.json for the device flow (refreshed, with rotation persisted).

Security posture

Mastra's npm scope was compromised on 2026-06-17 (the easy-day-js incident). This project is pinned to the last clean pre-compromise releases and layered with controls so a poisoned version cannot be installed. Do not loosen the pins or the .npmrc without re-vetting. Details and sources: SECURITY.md.

Layout

File Role
src/config.ts per-stage endpoints + device client + client-creds env
src/clientCredentials.ts M2M client-credentials token (recommended)
src/deviceAuth.ts device flow + refresh
src/tokenStore.ts token cache + auto-mint/refresh (both modes)
src/login.ts one-time interactive login (device flow)
src/mcp.ts Mastra MCP client wiring
src/listTools.ts / src/score.ts no-LLM validations (list tools, single score)
src/scoreBatch.ts batch scoring in one call (connect amortized)

Notes (@mastra/mcp@1.10.0)

  • Tools accessor is listTools(), not getTools() (the latter isn't in this version).
  • connectTimeout is required. Mastra's default connect timeout is 3s, too short for the k9 server's first request after it scales up from idle (cold start). mcp.ts sets 30s.
  • One client per run. The bearer header is fixed at client construction and access tokens live ~1h, so the example builds one client per CLI run with a fresh token. A long-running process should rebuild the client (and disconnect() the old one) when the token nears expiry; a static header won't pick up a new token on its own.

Related

  • See the k9 MCP M2M quickstart (provided by k9 Security) for the full score_risk contract and troubleshooting.

About

An example of a Mastra agent using k9 MCP tools

Resources

License

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors