agentleFS
solo-istsolo-ist/prose/.agents/skills/security-audit/skill.md

security-audit

Skill8 starsChanged 5 months agoMITView on GitHub

Systematic security audit for Electron apps. Scans attack surfaces, produces severity-tagged findings, generates implementation plan, and guides hardening.

  • Reads credentials

193 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use create-stories, story-done and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • lilangMaxSkill

    create-stories

    Break a single epic into implementable story files. Reads the epic, its GDD, governing ADRs, and control manifest. Each story embeds its GDD requirement TR-ID, ADR guidance, acceptance criteria, story type, and test evidence path. Run after /create-epics for each epic.

    Also in 11 projects that use this

  • lilangMaxSkill

    story-done

    End-of-story completion review. Reads the story file, verifies each acceptance criterion against the implementation, checks for GDD/ADR deviations, prompts code review, updates story status to Complete, and surfaces the next ready story from the sprint.

    Also in 11 projects that use this

  • nicepkgSkill

    community-led-growth

    Expert in community-led growth (CLG) - leveraging user communities to drive acquisition, retention, and expansion. Covers building developer communities, user groups, ambassador programs, and turning customers into advocates. Knows the difference between community as a feature vs community as a growth engine, and how to measure community ROI. Use when "community-led, CLG, ambassador program, champion program, user community, developer community, word of mouth, user group, " mentioned.

    Also in 5 projects that use this

  • nicepkgSkill

    ph-community-outreach

    Engage Reddit, Indie Hackers, Hacker News and other communities for Product Hunt launches. Use this skill to identify relevant communities, craft appropriate posts, and drive cross-platform engagement without appearing spammy.

    Also in 5 projects that use this

  • +7 more from lilangMax
  • +1 more from nicepkg

The file

Show its rules and text

We split this skill into the rules it gives an agent

13 rules. 0 are common, 10 are distinctive, and 3 are very specific to this project.

  • 0

    Common

    Most of the 174 edited versions we read keep these.

  • 10

    Distinctive

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Prefer deny-by-default over allowlisting

    line 29
  2. Distinctive:

    Don't over-engineer — match security investment to actual threat model

    line 31
  3. Distinctive:

    Pragmatism over paranoia: Not every theoretical attack justifies a fix:

    line 33
  4. Distinctive:

    Read the codebase to understand the architecture before auditing.

    line 42
  5. Distinctive:

    Run through each category systematically.

    line 63
  6. Distinctive:

    Race condition: If a client (e.g., stdio bridge) sends requests before auth handshake completes, the server may reject them.

    line 125
  7. Distinctive:

    After implementation, verify each group:

    line 230
  8. Distinctive:

    Resolving connections before auth completes: When adding auth handshakes to existing socket connections, the promise must resolve AFTER the …

    line 247
  9. Very specific:

    Non-constant-time token comparison: Use crypto.timingSafeEqual for all token/secret comparisons.

    line 249
  10. Very specific:

    Dead code after adding guards: If start() throws without a token, then if (this.token) inside the request handler is dead code.

    line 251
  11. Distinctive:

    Forgetting to git fetch origin before comparing branches: Always fetch before claiming a branch is up to date.

    line 253
  12. Very specific:

    Removing CSP entries without removing the code paths: If you tighten connect-src to remove a provider URL, verify no UI path still offers …

    line 255
  13. Distinctive:

    Credential migration duplication: If the same "read legacy file → store in new system → delete legacy" pattern appears in multiple places, …

    line 257

“Common” is measured: kept by at least 6 in 10 of the 174 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
skill.md

Security Audit

Systematic security audit for Electron desktop applications. Identifies vulnerabilities across the privilege boundary, produces a prioritized findings table, and generates an actionable implementation plan.

Usage

/security-audit [scope]

Scope (optional): Focus the audit on a specific area instead of running the full checklist.

  • full (default) — all categories
  • urls — URL validation surfaces
  • electron — Electron security handlers
  • ipc — IPC and MCP authentication
  • csp — Content Security Policy
  • credentials — Credential storage and OAuth
  • build — Build pipeline hardening (fuses, signing)

Philosophy

Defense in depth. Layer protections so no single bypass is catastrophic:

  • Validate at every trust boundary (main process, preload, renderer)
  • Prefer deny-by-default over allowlisting
  • Fix the highest-impact, lowest-effort issues first
  • Don't over-engineer — match security investment to actual threat model

Pragmatism over paranoia. Not every theoretical attack justifies a fix:

  • Localhost-only timing side-channels are low priority
  • Session timeouts on local Unix sockets add complexity for minimal gain
  • But cross-platform path bugs that silently bypass auth are critical

Workflow

Phase 1: Reconnaissance

Read the codebase to understand the architecture before auditing.

Files to read first:

  • AGENTS.md — project architecture overview
  • src/main/index.ts — main process entry (window creation, protocol handlers, CSP)
  • src/preload/index.ts — context bridge surface
  • src/main/ipc.ts — IPC handler definitions
  • electron-builder.yml — build configuration
  • package.json — dependencies and Electron version

Questions to answer:

  1. What is the Electron version? (Check for known CVEs)
  2. Is sandbox enabled? Is contextIsolation enabled? Is nodeIntegration disabled?
  3. What custom protocols are registered?
  4. What IPC channels exist and what do they accept?
  5. Are there any HTTP/WebSocket servers running in the main process?
  6. How are credentials stored?
  7. What CSP is configured?

Phase 2: Audit Checklist

Run through each category systematically. For each finding, record:

  • ID: Sequential identifier (H1, M1, etc.)
  • Severity: HIGH, MEDIUM, LOW, INFO
  • Finding: What the vulnerability is
  • Location: File and line number
  • Category: Which group it belongs to

Category 1: URL Validation Surfaces

Untrusted URLs can execute arbitrary code via javascript:, data:, or custom schemes.

Check each surface:

SurfaceWhat to look for
setWindowOpenHandlerDoes it call shell.openExternal without scheme validation?
will-navigate handlerDoes one exist? Does it restrict to expected origins?
Link click handlersDo CMD+Click or renderer link handlers validate schemes?
LLM-generated contentAre links from AI responses rendered with href validation?
shell.openExternal IPCIs the IPC handler validating schemes before calling?

Safe pattern:

const parsed = new URL(url)
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
  return // block
}

Pitfall: new URL('file:///etc/passwd').origin returns 'null' (string) per URL spec. If comparing origins for file:// URLs, check parsed.protocol explicitly.

Category 2: Electron Security Handlers

Missing handlers leave implicit defaults that are usually permissive.

HandlerPurposeCorrect behavior
will-navigateBlock navigation to untrusted originsAllow only app origin (file:// prod, Vite dev server in dev)
setPermissionRequestHandlerControl permission grantsDeny all (unless app genuinely needs camera/mic/geo)
certificate-errorHandle invalid TLS certsAlways callback(false) — never trust bad certs
setWindowOpenHandlerControl window.open()Deny action, optionally open in external browser

Pitfall: certificate-error requires both event.preventDefault() AND callback(false). Missing either one uses the default behavior.

Category 3: IPC & Local Server Authentication

Any HTTP/WebSocket/Unix socket server in the main process is accessible to all local processes.

Check:

  • Is there an HTTP server? Does it require authentication?
  • Is there a Unix socket? Does it require authentication?
  • How is the auth token generated, stored, and transmitted?
  • Is the token comparison constant-time (crypto.timingSafeEqual)?
  • Is the token file restricted to the owning user (mode: 0o600)?
  • Is token cleanup attempted on quit?

Auth token lifecycle pattern:

startup → randomBytes(32) → write to file (0o600) → pass to servers → cleanup on will-quit

Pitfalls:

  • Race condition: If a client (e.g., stdio bridge) sends requests before auth handshake completes, the server may reject them. Wait for auth response before resolving the connection promise.
  • Cross-platform paths: app.getPath('userData') resolves differently per platform. Hardcoding ~/Library/Application Support/AppName breaks on Linux/Windows and silently bypasses auth.
  • Token-before-start ordering: Set the auth token before calling server.start(), or enforce it structurally with a guard in start().

Cross-platform userData paths:

function getUserDataPath(): string {
  switch (process.platform) {
    case 'darwin':
      return path.join(os.homedir(), 'Library', 'Application Support', 'AppName')
    case 'win32':
      return path.join(process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'), 'AppName')
    default:
      return path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'AppName')
  }
}

Category 4: Custom Protocols & CSP

Custom protocol handlers (protocol.handle) can serve arbitrary filesystem content.

Check:

  • What files can the protocol serve? Is it confined to a safe directory?
  • Does path normalization prevent traversal (../../../etc/passwd)?
  • Is CSP connect-src limited to actually-used API endpoints?
  • Does CSP img-src allow https: wildcard? (Enables tracking pixels but needed for remote images in documents)
  • Is style-src 'unsafe-inline' present? (Often required by editor frameworks like TipTap/ProseMirror)

Safe confinement pattern:

const normalized = normalize(filePath)
if (!normalized.startsWith(homedir() + sep)) {
  return new Response('Access denied', { status: 403 })
}

Category 5: Credential Storage & OAuth

Check:

  • Are credentials stored via safeStorage / a credential store abstraction?
  • Are there any plaintext credentials on disk?
  • Does the OAuth flow include a CSRF state parameter?
  • Are credential migration paths consistent (no duplicated logic)?
  • Is .mcp.json or similar config with secrets in .gitignore?

CSRF state pattern for OAuth:

const csrfState = randomBytes(16).toString('hex')
const authUrl = oauth2Client.generateAuthUrl({ state: csrfState })
// In callback:
if (url.searchParams.get('state') !== csrfState) { reject('CSRF') }

Pitfall: Duplicated migration logic across multiple files. Extract a shared migrateFromLegacyFile(legacyPath, credentialKey) helper.

Category 6: Build Pipeline Hardening

Check:

  • Are Electron fuses configured to disable dangerous runtime features?
  • Is code signing configured? (Distribution concern, may be out of scope)
  • Are devDependencies properly separated from dependencies?

Fuses to disable in production:

// build/afterPack.js
const { flipFuses, FuseV1Options, FuseVersion } = require('@electron/fuses')
await flipFuses(executablePath, {
  version: FuseVersion.V1,
  [FuseV1Options.RunAsNode]: false,
  [FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
  [FuseV1Options.EnableNodeCliInspectArguments]: false,
})

Wire into electron-builder.yml with afterPack: build/afterPack.js.

Phase 3: Findings Report

Produce a findings table sorted by severity:

| ID | Severity | Finding | Location | Category |
|----|----------|---------|----------|----------|
| H1 | HIGH | ... | `src/main/index.ts:147` | URL Validation |

Phase 4: Implementation Plan

Group findings by blast radius and dependency. Prioritize:

  1. Smallest changes, highest attacker value (URL validation)
  2. Missing security handlers (Electron event handlers)
  3. Protocol and CSP fixes (pairs with handlers)
  4. IPC authentication (most complex, touches multiple files)
  5. Credential hygiene (mechanical but touches multiple files)
  6. Build hardening (build pipeline only, no runtime risk)

For each group, specify:

  • Files modified
  • What changes
  • Verification steps

Phase 5: Verification

After implementation, verify each group:

GroupVerification
URL validationCMD+Click javascript: link → nothing happens. https:// link → opens browser.
Electron handlersDevTools: location.href = 'https://evil.com' → blocked with console warning.
IPC authcurl http://localhost:<port>/endpoint without token → 401.
Protocol confinementlocal-file:///etc/hosts in DevTools → 403.
CredentialsExisting credentials survive migration. New store/retrieve works.
FusesELECTRON_RUN_AS_NODE=1 ./App -e "console.log('hi')" → fails.

Common Mistakes

Lessons from past audits — check for these proactively:

  1. Hardcoded macOS paths in cross-platform code. ~/Library/Application Support/ only works on macOS. Use app.getPath('userData') in Electron or replicate the platform logic in non-Electron code.
  2. Resolving connections before auth completes. When adding auth handshakes to existing socket connections, the promise must resolve AFTER the auth response, not after sending the request.
  3. Non-constant-time token comparison. Use crypto.timingSafeEqual for all token/secret comparisons. Requires length pre-check since timingSafeEqual throws on length mismatch.
  4. Dead code after adding guards. If start() throws without a token, then if (this.token) inside the request handler is dead code. Remove it or add a comment explaining it's defense-in-depth.
  5. Forgetting to git fetch origin before comparing branches. Always fetch before claiming a branch is up to date.
  6. Removing CSP entries without removing the code paths. If you tighten connect-src to remove a provider URL, verify no UI path still offers that provider as an option.
  7. Credential migration duplication. If the same "read legacy file → store in new system → delete legacy" pattern appears in multiple places, extract a helper before it diverges.

Output Format

The skill produces three artifacts:

  1. Findings table — all issues found, with severity, location, and category
  2. Implementation plan — grouped, ordered, with file lists and verification
  3. Deferred items — issues intentionally not addressed, with rationale and tracking issue references

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.