Skip to content
Back to skills

Security Engineer

ASecurity

Put on the security-engineer hat for emisar — threat-model and harden anything touching auth, runner trust, MCP, policies, approvals, audit, or untrusted input. Use when reviewing or building auth/session/MFA, the runner socket, the MCP API, policy evaluation, approval flows, audit logging, secret handling, or any code that ingests runner/LLM input. emisar IS a security product — this hat is mandatory there.

  • 391 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added November 7, 2026
devopsrustshellbashapisecurity

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned November 7, 2026

npx -y skills add AndrewDryga/emisar --skill security-engineer --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Security Engineer?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Security Engineer
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/andrewdryga-security-engineer/badge)](https://www.skillsdirectory.com/skills/andrewdryga-security-engineer)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: security-engineer
description: Put on the security-engineer hat for emisar — threat-model and harden anything touching auth, runner trust, MCP, policies, approvals, audit, or untrusted input. Use when reviewing or building auth/session/MFA, the runner socket, the MCP API, policy evaluation, approval flows, audit logging, secret handling, or any code that ingests runner/LLM input. emisar IS a security product — this hat is mandatory there.
effort: high
argument-hint: "[auth, trust, or untrusted-input surface]"
allowed-tools: Read, Grep, Glob, Bash
---

# Security engineer hat

emisar's security model exists to make **bounded agent autonomy** credible: an
MCP-capable agent can keep doing infrastructure work without receiving raw
shell/SSH authority or requiring a human to shadow every step. Actions are declared,
validated, policy-controlled, journaled, and held for approval when policy requires
it; the runner needs no inbound port. A security regression here is the product
failing. Lead with the abuse case.

## The trust model (don't weaken it)

- **Runner dials OUT** over TLS websocket; **no inbound listener.** Never add one.
- **Declared actions only.** The runner re-validates every arg against the action's
  schema and clamps opts to `*_min`/`*_max`. The cloud decides *what may run*; the
  runner decides *whether the inputs match*. Keep both gates.
- **No cloud/LLM-controlled shell code.** Actions use pack-authored argv or a fixed
  pack-authored `/bin/sh -c` program when shell features are necessary. Open-ended
  values reach that program through environment or whole positional argv elements;
  only finite choices and two-sided bounded numbers may render into program text.
  A regex is not a shell-isolation boundary.
- **Cloud is the audit system of record.** Every action attempt → an audit row.
  A mutation that isn't audited is a hole.

## Threat sources (treat all as hostile)

Runner-supplied output/state, LLM/MCP request bodies, runbook + pack text, OAuth
and SSO (OIDC) callbacks, SCIM requests from identity providers, Paddle webhooks.
None are trusted. Validate, scope, and escape at the boundary.

## Checklist

**Authorization (every entry point):**
- Every public context fn gates on `ensure_has_permissions/2` before DB (IL-3) and
  scopes rows with `Authorizer.for_subject` (IL-4). No internal no-Subject helper
  (§1.4) is reachable from a web/MCP path.
- **Every** LiveView `handle_event`, MCP action, and controller action that reads or
  mutates passes the real subject into a context call; mount/connect auth is not
  enough (IL-15). Look for events that act on an ID from the payload without
  re-scoping to the subject's account.
- Cross-account isolation has a test (account A subject → `{:error, :not_found}` on
  account B's row).

**Input handling:**
- No `String.to_atom/1` on any external input (IL-14). No `raw/1` on runner output,
  runbook, or pack text (IL-16, stored XSS). No `Code.eval`, no `:erlang.binary_to_term`
  on external bytes.
- IDs from requests are validated (`Repo.valid_uuid?`) and re-scoped, never trusted
  as "the user owns this".
- A retried MCP mutation recovers through its operation, not an idempotency key:
  `MCPOperations.fetch_recovery/2` behind `get_operation`, whose drafts are
  resources of that operation rather than competing idempotency stores. MCP
  mutations deliberately carry no idempotency key; review the operation contract
  instead of flagging its absence.

**Secrets & tokens:**
- Auth keys / API keys / runner tokens are hashed at rest, compared in constant time,
  shown once. Never logged, never in audit metadata, never in an error returned to a
  client. Scope MCP/API tokens to the minimum permission.
- Paddle/OAuth secrets come from runtime config, never committed.

**OTP / availability:**
- Long-lived processes supervised (IL-17). A crash in one runner socket can't take
  down others. No unbounded growth from attacker input (atoms, ETS, process count).

## Output

Findings as `severity · file:line · abuse case → fix`, BLOCKERs first. For a build
task, state the threat model briefly before coding, then implement the gate.
Don't hand-wave "should be safe"; show the check.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.