Skip to main content

security

Application security - passkey credentials, Safe multisig, extension runtime, signaling server, receiver HMAC, archive encryption, session keys, permits, agent pipeline, invite codes, CRDT integrity, stealth addresses, ZK proofs. Use for security reviews, pre-release audits, and threat modeling.

Informations de source

Dépôt
greenpill-dev-guild/coop
Dernière activité de la source
1 avril 2026 à 03:51
Langue détectée de SKILL.md
anglais
Étoiles
3
Forks
1

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
security
user-invocable
false
description
Application security - passkey credentials, Safe multisig, extension runtime, signaling server, receiver HMAC, archive encryption, session keys, permits, agent pipeline, invite codes, CRDT integrity, stealth addresses, ZK proofs. Use for security reviews, pre-release audits, and threat modeling.
version
2.0.0
status
active
packages
["shared","extension","app","api"]
dependencies
[]
last_updated
2026-03-31
last_verified
2026-03-31
# Security Skill Comprehensive application security guide covering all trust boundaries in the Coop stack: browser extension runtime, signaling server, peer sync, cross-device pairing, onchain operations, local encryption, and identity. --- ## Activation | Trigger | Action | |---------|--------| | "security audit", "security review" | Full application security review (all parts) | | "check for vulnerabilities" | Targeted vulnerability scan | | "access control review" | Safe/passkey/session-key access control audit | | Pre-release | Mandatory security checklist (Part 12) | --- ## Part 1: Passkey & Authentication Security ### Passkey Credential Safety ```typescript // ALWAYS: Validate passkey responses before trusting async function verifyPasskeyAuth(credential: PublicKeyCredential) { // Verify the credential came from our relying party if (!credential.response) { throw new Error("Invalid credential response"); } // Only store: credential ID, public key, user handle // NEVER store raw passkey private material } // NEVER: Log or transmit passkey credential details // NEVER: Store private key material in localStorage/IndexedDB ``` ### Session Management ```typescript // ALWAYS: Expire sessions after inactivity const SESSION_TIMEOUT = 30 * 60 * 1000; // 30 minutes // ALWAYS: Clear sensitive state on logout function signOut() { authStore.reset(); clearSensitiveData(); // Don't clear local Dexie data (it's the user's local data) } ``` --- ## Part 2: Safe & Onchain Security ### Safe Transaction Safety ```typescript // ALWAYS: Verify Safe address before transactions async function verifySafeOwnership(safeAddress: Address, userAddress: Address) { const owners = await getSafeOwners(safeAddress); if (!owners.includes(userAddress)) { throw new Error("User is not a Safe owner"); } } // ALWAYS: Simulate transactions before execution const simulation = await publicClient.simulateContract({ address: targetContract, abi, functionName, args, account: safeAddress, }); // NEVER: Skip simulation for Safe transactions // NEVER: Hardcode Safe addresses ``` ### ERC-4337 Security | Risk | Mitigation | |------|------------| | Bundler manipulation | Verify bundler responses, check UserOp on-chain | | Gas estimation attacks | Add buffer to gas estimates, set max gas limits | | Paymaster spoofing | Verify paymaster contract address | | Nonce replay | Always use fresh nonces from entrypoint | --- ## Part 3: Local Data Protection ### IndexedDB/Dexie Security ```typescript // ALWAYS: Scope data access to authenticated user async function getUserData(userId: string) { return db.tabs.where("userId").equals(userId).toArray(); } // NEVER: Store unencrypted secrets in IndexedDB // NEVER: Store API keys, private keys, or tokens in Dexie // ALWAYS: Clean up sensitive data async function clearUserSession(userId: string) { await db.sessions.where("userId").equals(userId).delete(); } ``` ### Extension Storage Security ```typescript // ALWAYS: Use chrome.storage.local for extension state (encrypted at rest by browser) // NEVER: Use localStorage in extension context (accessible to content scripts) // NEVER: Store credentials in chrome.storage.sync (synced to Google account) // Extension permissions: request minimum required // manifest.json: only request "activeTab", "storage", "sidePanel" etc. // NEVER: Request "tabs" permission unless absolutely needed (exposes all tab URLs) ``` --- ## Part 4: CRDT / Yjs Integrity ### Peer Trust Model Yjs syncs with any connected peer. The codebase uses `ORIGIN_LOCAL` tagging to distinguish local writes from remote merges. ```typescript // ALWAYS: Tag local writes with origin doc.transact(() => { /* ... */ }, ORIGIN_LOCAL); // ALWAYS: Validate data structure after Yjs merge function validateCoopState(yDoc: Y.Doc): boolean { const yMap = yDoc.getMap("coop"); // Verify required fields present and well-typed if (!yMap.get("profile") || !yMap.get("members")) return false; return true; } // ALWAYS: Use per-member CRDT maps to prevent cross-member overwrites // See: packages/shared/src/modules/storage/ for the origin-tagged pattern ``` ### Room Security ```typescript // ALWAYS: Use unique, unpredictable room names derived from coop state const roomName = `coop-${coopId}-${syncRoomSecret}`; // ALWAYS: Encrypt y-webrtc rooms when possible const provider = new WebrtcProvider(roomName, yDoc, { password: coopEncryptionKey, }); // NEVER: Use predictable or sequential room names // NEVER: Broadcast room names over unencrypted channels ``` --- ## Part 5: Extension Runtime Security ### Message Origin Validation The background service worker validates all incoming runtime messages against the extension's own origin. This is the primary trust boundary for the extension. **Canonical pattern** (`packages/extension/src/background.ts`, lines 328-344): ```typescript chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { const senderUrl = sender.url ?? sender.tab?.url ?? ''; // Only allow messages from our own extension origin if (!senderUrl.startsWith(`chrome-extension://${chrome.runtime.id}`)) { // Exception: receiver bridge content script for ingest messages if (message.type === 'ingest-receiver-capture') { // Allowed — this comes from the isolated content script } else { sendResponse({ ok: false, error: 'Unauthorized sender.' }); return true; } } // ... dispatch to handlers }); ``` ### Rules - **ALWAYS**: Check `sender.url` or `sender.tab.url` against `chrome-extension://${chrome.runtime.id}` - **ALWAYS**: Whitelist specific message types from content scripts, never allow all - **NEVER**: Accept arbitrary messages from web pages without type filtering - **NEVER**: Trust `sender.tab.url` for content-script messages from untrusted pages — only trust the message type whitelist ### Content Script Isolation The receiver bridge (`packages/extension/entrypoints/receiver-bridge.content.ts`) is the only content script and uses strict isolation: ```typescript export default defineContentScript({ matches: ['*://*.coop.town/*', 'http://127.0.0.1:3001/*', 'http://localhost:3001/*'], world: 'ISOLATED', // Prevents page scripts from accessing extension APIs runAt: 'document_idle', }); ``` **Message filtering rules** (lines 36-56): 1. Verify `event.source === window` (same-frame only) 2. Check `data.source === 'coop-receiver-app'` (app identifier) 3. Validate `requestId` is a string 4. Only allow message types: `'ping'` and `'ingest'` 5. Post responses with `window.location.origin` (restricts delivery) ### Rules - **ALWAYS**: Use `world: 'ISOLATED'` for content scripts - **ALWAYS**: Filter by source identifier and enumerate allowed message types - **ALWAYS**: Respond with `window.location.origin`, not `'*'` - **NEVER**: Accept arbitrary message types from page context - **NEVER**: Forward raw page content to the background without validation --- ## Part 6: API & Signaling Server Security The signaling server (`packages/api/`) is the only network-facing service. It handles WebSocket connections for peer discovery and Yjs document sync. ### Current State **Middleware** (`packages/api/src/middleware/index.ts`): Only `logger()` is applied. **WebSocket handler** (`packages/api/src/ws/handler.ts`): Accepts `subscribe`, `unsubscribe`, `publish`, `ping` message types. Topic names are opaque strings. ### Known Gaps (Track These) | Gap | Risk | Status | |-----|------|--------| | No CORS middleware | Any origin can connect | Open | | No WebSocket authentication | Anyone can subscribe/publish to topics | Open — topic name secrecy is the only barrier | | No rate limiting | Connection flooding possible | Open | | No topic authorization | No cryptographic binding of topics to identities | Open | | No payload validation | `publish` accepts any payload | Open | ### Rules For New API Code - **ALWAYS**: Validate JSON payloads with Zod schemas before processing - **ALWAYS**: Add CORS restrictions to known origins when deploying publicly - **ALWAYS**: Log connection metadata (IP, user-agent) for audit - **NEVER**: Trust topic names as an authentication mechanism — they are discoverable - **NEVER**: Accept unbounded payloads — enforce max message size - **NEVER**: Store sensitive state server-side — the server is a relay, not a data store ### When Adding Authentication Prefer signed connection tokens over session cookies for WebSocket auth. The connection should carry a proof of coop membership (e.g., a signed challenge or invite proof) that the server can verify without storing secrets. --- ## Part 7: Receiver & Cross-Device Security ### HMAC Envelope Validation Every receiver sync envelope is signed with HMAC-SHA256 using the pairing secret. This is the trust boundary between the PWA and the extension. **Canonical pattern** (`packages/shared/src/modules/receiver/capture.ts`, lines 36-92): Signed fields: `captureId, deviceId, pairingId, coopId, memberId, kind, title, note, sourceUrl, fileName, mimeType, byteSize, createdAt` plus asset metadata. ```typescript // ALWAYS: Verify HMAC before trusting any receiver envelope const computedSignature = await hmacSign(pairingSecret, canonicalPayload); if (computedSignature !== envelope.auth.signature) { throw new Error('Receiver envelope signature mismatch'); } // ALWAYS: Verify pairing binding — envelope must match pairing ID, coop ID, member ID // ALWAYS: Validate payload structure with Zod before HMAC check (reject malformed early) ``` ### Pairing Security ```typescript // ALWAYS: Generate pairing secrets with crypto.getRandomValues() // ALWAYS: Store pairing records in chrome.storage.local (encrypted at rest) // NEVER: Transmit pairing secrets over unencrypted channels // NEVER: Reuse pairing secrets across devices or coops ``` ### Cross-Origin PostMessage The receiver bridge communicates with the PWA via `postMessage`. Rules: - **ALWAYS**: Specify `targetOrigin` (use `window.location.origin`, never `'*'`) - **ALWAYS**: Validate `event.origin` against known receiver origins - **NEVER**: Accept messages from arbitrary origins --- ## Part 8: Archive & Encryption ### AES-GCM Archive Encryption **Canonical pattern** (`packages/shared/src/modules/archive/crypto.ts`): - **Algorithm**: AES-GCM (authenticated encryption) - **IV**: 12 bytes, cryptographically random per operation (line 58) - **AAD**: Contextual metadata — `coopId`, `bundleId`, `blobId` (lines 63-67) - **Key derivation**: SHA-256 hash of archive config JSON (lines 31-49) ### Known Limitations | Limitation | Risk | Mitigation | |-----------|------|------------| | No salt in key derivation | Deterministic key from config | Acceptable if config includes unique secrets | | No key rotation | Same key for lifetime of archive config | Track for future improvement | | Key material derived from config JSON | Config ordering matters | Use `hashJson()` for stable serialization | ### Rules - **ALWAYS**: Generate a fresh random IV per encryption operation - **ALWAYS**: Include contextual AAD (coopId, blobId) to bind ciphertext to context - **ALWAYS**: Catch and surface decryption failures explicitly — never silently return empty data - **NEVER**: Reuse IVs with the same key - **NEVER**: Log or expose derived key material - **NEVER**: Store archive encryption keys in IndexedDB unencrypted --- ## Part 9: Session Keys & Permits ### Session Key Wrapping **Canonical pattern** (`packages/shared/src/modules/session/session.ts`): - **KDF**: PBKDF2 with 120,000 iterations, SHA-256 - **Salt**: 16 bytes (from stored base64), falls back to `SESSION_WRAPPING_CONTEXT` string if missing - **Wrapping**: AES-GCM 256-bit key, 12-byte IV ### Session Key Scope Enforcement Session keys are constrained by (lines 531-601): 1. **Chain binding** — key only valid on specified chain 2. **Safe address binding** — key only valid for specified Safe 3. **Action class allowlist** — only permitted action types 4. **Target address allowlist** — per-action contract whitelisting 5. **Usage counting** — enforced `maxUses` limit (lines 295-316) 6. **Status lifecycle** — Active -> Expired/Revoked/Exhausted ### Permit Enforcement Chain **Canonical pattern** (`packages/shared/src/modules/permit/enforcement.ts`, lines 22-110): Validation order (all must pass): 1. Revocation check 2. Expiration check 3. Usage limit check 4. Replay ID validation 5. Coop scope check 6. Action allowlist check 7. Executor label binding 8. Executor passkey identity binding 9. Target allowlist check 10. Replay protection via `checkReplayId()` ### Rules - **ALWAYS**: Enforce the full permit validation chain — never skip steps - **ALWAYS**: Use fresh nonces for replay protection - **ALWAYS**: Decrement usage count atomically with execution - **NEVER**: Trust client-supplied scope claims — verify against stored permit - **NEVER**: Fall back to `SESSION_WRAPPING_CONTEXT` salt in production — ensure salt is always stored - **NEVER**: Allow session keys without explicit target allowlists --- ## Part 10: Agent Pipeline Security ### Skill Output Validation The agent pipeline runs 16+ skills that produce structured output. All outputs are validated against Zod schemas before use. **Canonical pattern** (`packages/shared/src/modules/agent/agent.ts`, lines 48-68): ```typescript // Every skill output has a registered Zod schema const skillOutputSchemas: Record<SkillOutputSchemaRef, ZodSchema> = { 'tab-router': tabRouterOutputSchema, 'opportunity-extractor': opportunityExtractorOutputSchema, // ... 16 total skill schemas }; // ALWAYS: Parse skill output through its registered schema before use
Voir sur GitHub
Ce SKILL.md est tres volumineux, SkillsMP affiche donc ici seulement la premiere section. Voir sur GitHub