Honest by design: defenses first, known gaps last. Update both when either changes.
SPARDA sits between an AI client and a live application. The attack surfaces, in order of severity:
- Prompt injection through the codebase — hostile docstrings/comments become tool descriptions the AI reads.
- The AI itself as an unsafe operator — writes to a live app, error loops hammering broken routes.
- Local network access to the injected router — anything on localhost
could call
/mcp/invoke. - LLM outputs as stored state — sampling responses (semantic pass, immune diagnoses) are untrusted input that we persist and re-show.
- Supply chain — our own dependency footprint.
| Surface | Defense | Where |
|---|---|---|
| Docstring injection | regex deny-list, flagged → purged to fallback; <>{} stripped; 300-char cap |
security/sanitize.js, applied at init and sync |
| LLM outputs | same sanitizer on every sampling result before storage/display | stdio.js (semantic pass, antibodies) |
| Unsafe writes | write tools enabled: false by default; per-write elicitation confirm; proof-after-write read-back |
generators + stdio.js |
| Error-loop hammering | quarantine: 3 consecutive 5xx → 503 + cooldown, half-open probe | router templates (v0.3) |
| Local access | x-sparda-key (UUID) required on every router endpoint; 401 otherwise |
router templates |
| Key at rest | ADR-022: no plaintext key in sparda.json or generated routes — resolved at runtime from SPARDA_LOCAL_KEY (env) → gitignored .sparda/key; fail-closed 503 if neither is found, so an accidental commit/deploy of /mcp routes is safe by construction |
generator/*, manifest.js |
| Self-reference loops | /mcp* paths blocked at parse time and invoke time |
parser + router |
| Resource abuse | 30s timeouts, 8KB output truncation, events ring buffer (100), antibodies cap (50), stats are O(tools) | router + bridge |
| Host stability | uncaughtExceptionMonitor (observe-only — never alters crash behavior); injection is backed up, re-parsed, reversible byte-for-byte |
templates + generators |
| Supply chain | 4 runtime deps, exact-pinned | package.json |
- Key comparison is
!==, not constant-time. The key guards a loopback interface (not an internet-exposed service), so a timing side-channel is low-impact — but it is a deviation from best practice, stated not hidden. - The
x-sparda-keystill guards weak-auth surface, not secret material. Since ADR-022 the key is never at rest in a committable file and fails closed when absent (see Defenses), but treat the loopback interface as local-trust, not a hardened boundary. - No per-tool/per-person access policies — planned for a future paid tier.
- No signed audit log yet (planned for a paid tier) — until then,
actions are observable only via
/mcp/eventsand client logs. - Sanitizer is a deny-list — it blocks known patterns, not novel ones. Acceptable while descriptions are short and capped; revisit if we ever pass larger code context to clients.
- The semantic/immune sampling prompts include app-derived text (route descriptions, error messages). They are sanitized, but a hostile app could still try to steer its own diagnosis. Impact is bounded: output is re-sanitized and only ever shown as a one-line description.