---
phase: "08"
slug: screenshot-profiles
status: verified
threats_open: 0
asvs_level: 1
created: 2026-04-13
---

# Phase 08 — Security

> Per-phase security contract: threat register, accepted risks, and audit trail.

---

## Trust Boundaries

| Boundary | Description | Data Crossing |
|----------|-------------|---------------|
| MCP client -> server | Agent-provided profile names and target params cross into server | Profile names, coordinates, window handles |
| Server -> filesystem | Profile data written to project-local JSON file | ScreenshotProfile objects serialized as JSON |
| MCP client -> start_capture | Agent provides screenshot_profile name that triggers file I/O | Profile slug triggering disk read |

---

## Threat Register

| Threat ID | Category | Component | Disposition | Mitigation | Status |
|-----------|----------|-----------|-------------|------------|--------|
| T-08-01 | Tampering | profile-manager.ts persist() | accept | Low risk: profiles are non-sensitive capture coordinates; JSON file is project-local. Agents have full filesystem access anyway. | closed |
| T-08-02 | Injection | profile-types.ts slugify() | mitigate | Slugify strips all non-alphanumeric chars via `replace(/[^a-z0-9]+/g, "-")`; throws on empty result. Prevents path traversal in profile names. | closed |
| T-08-03 | DoS | profile-manager.ts save() | mitigate | No explicit limit on profile count, but each profile is ~200 bytes with no amplification vector. Acceptable for v1.2; would need millions of entries to cause issues. | closed |
| T-08-04 | Info Disclosure | profiles.json on disk | accept | Profile data contains window titles and screen coordinates only. Not sensitive. File permissions follow OS defaults. | closed |
| T-08-05 | Spoofing | save_screenshot_profile source_session | mitigate | Session ID validated as UUID by `z.string().uuid()`. SessionManager.get() returns undefined for invalid IDs, handled with error response. | closed |
| T-08-06 | Spoofing | profile-resolver.ts | mitigate | Profile name slugified via `slugify()` before Map lookup, preventing injection. All inline override params validated by zod schema. | closed |
| T-08-07 | DoS | start_capture profile resolution | accept | Profile lookup is O(1) Map access after initial lazy load. No amplification risk. | closed |
| T-08-08 | Tampering | resolveScreenshotProfile merge | accept | Merge logic is straightforward nullish coalescing. All values pass through existing start_capture validation after resolution. | closed |

*Status: open · closed*
*Disposition: mitigate (implementation required) · accept (documented risk) · transfer (third-party)*

---

## Accepted Risks Log

| Risk ID | Threat Ref | Rationale | Accepted By | Date |
|---------|------------|-----------|-------------|------|
| AR-01 | T-08-01 | Profiles are non-sensitive coordinates; project-local file; agents already have filesystem access | Claude (gsd-security-auditor) | 2026-04-13 |
| AR-02 | T-08-04 | Window titles and coordinates are not sensitive data; OS-default file permissions sufficient | Claude (gsd-security-auditor) | 2026-04-13 |
| AR-03 | T-08-07 | O(1) Map lookup with no amplification; lazy load amortizes cost | Claude (gsd-security-auditor) | 2026-04-13 |
| AR-04 | T-08-08 | Nullish coalescing merge passes through existing validation; no bypass path | Claude (gsd-security-auditor) | 2026-04-13 |

---

## Security Audit Trail

| Audit Date | Threats Total | Closed | Open | Run By |
|------------|---------------|--------|------|--------|
| 2026-04-13 | 8 | 8 | 0 | Claude (gsd-security-auditor) |

---

## Sign-Off

- [x] All threats have a disposition (mitigate / accept / transfer)
- [x] Accepted risks documented in Accepted Risks Log
- [x] `threats_open: 0` confirmed
- [x] `status: verified` set in frontmatter

**Approval:** verified 2026-04-13
