Skip to content
Back to skills

Conventions Security

ASecurity

Apply when writing code that touches auth, data access, file handling, or external APIs. Stella handles privileged legal data (attorney-client privilege, litigation holds, personal data).

  • 257 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added May 27, 2026
businessrustgoexpressrailsgitapidatabasesecurity

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned September 20, 2026

npx -y skills add stella/stella --skill conventions-security --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Conventions Security?

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

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

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: conventions-security
description: "Apply when writing code that touches auth, data access, file handling, or external APIs. Stella handles privileged legal data (attorney-client privilege, litigation holds, personal data)."
---

# Security Conventions

Apply when writing code that touches auth, data access, file
handling, or external APIs. Stella handles privileged legal data
(attorney-client privilege, litigation holds, personal data).

## SOC 2 / ISO 27001 Principles

- **Least privilege** — minimum permissions needed. No wildcard
  IAM policies, no admin-by-default.
- **Audit trail** — state-changing operations traceable to an
  actor and timestamp. Never silently mutate data.
- **Encryption in transit and at rest** — TLS everywhere, S3 SSE,
  no plaintext secrets in code or logs.
- **Input validation at boundaries** — all external input
  validated and sanitised before processing.
- **Workspace isolation** — data from one workspace must never
  leak to another.
- **Ethical walls** — workspace (matter) boundaries enforce
  information barriers for the `member` role at the RLS layer (not
  just the UI): a member has zero visibility into workspaces they
  are not assigned to via `workspace_members` — no names, no
  members, no metadata. **Known limitation:** org `owner`/`admin`
  roles carry a deliberate firm-admin override granting access to
  every client matter (`client_id IS NOT NULL`) regardless of
  assignment, and there is no per-matter screening to wall a
  specific admin out of a specific matter. Do not describe admin
  access as "absolute confidentiality." Screening a matter from an
  admin requires a matter-level exclusion consulted by the RLS
  view (`stella_authorized_workspaces`), which is not yet built.
- **Access control** — every endpoint enforces auth and
  authorisation. No "internal-only" endpoints without guards.
- **Dependency hygiene** — minimal, pinned, audited.
- **Logging without leaking** — never log secrets, tokens, PII,
  or document contents.
- **Change management** — all changes go through PR review. No
  direct commits to main.
- **Data retention** — when data is deleted, it is actually
  deleted (not soft-deleted indefinitely).

## Structural Guardrails

Prefer solutions that make security bugs **structurally impossible**
(compile-time, lint-time) over ones that rely on developer discipline.

### Authorized database boundary

Workspace-scoped handlers use `createSafeHandler`, declare permissions beside
the route, derive `workspaceId: SafeId<"workspace">` from the authorized
context, and access tenant data through `scopedDb`. Never accept ownership IDs
from body/query input or fall back to root `db` because a relation is awkward
to express. Root/system handlers require a documented non-tenant purpose and
tables whose RLS posture denies ordinary application access. Enforced by
`require-safe-route-handlers/require-safe-route-handlers` and
`no-body-ownership-ids/no-body-ownership-ids`.

When an identifier comes from a related row, authorize it in the same query or
transaction that uses it. A prior UI filter, cache lookup, or separate
read-then-write check is not an access-control boundary.

### Server-bound audit events

Actors, workspace/organization ownership, request metadata, and before/after
identifiers come from authenticated server context. Clients may supply a user
action or reason where the domain requires it, but never the authoritative
actor or tenant. Required audit writes participate in the same transaction as
the mutation, or use a durable outbox when the sink is external. Enforced by
`require-audit-on-mutation/require-audit-on-mutation`.

### Workspace status filtering

`resolveAccessibleWorkspaces` returns **all** workspaces (including
`deleting`). The auth macro exposes two fields:

- `activeWorkspaceIds` — excludes `deleting` workspaces (includes
  active and archived). Use this for search, chat, MCP, and any
  query that builds a workspace allowlist. This is the default;
  reach for it first.
- `accessibleWorkspaces` — includes all statuses. Only use in
  `workspaceAccessMacro` (which needs the status to return
  appropriate HTTP codes). Never pass these IDs as a search/query
  allowlist.

If you need workspace IDs for a new feature, use `activeWorkspaceIds`
unless you have an explicit reason to include deleting workspaces
and document that reason in a comment.

### CSV and file exports

Use `escapeCSV` from `@/api/lib/csv` for all CSV cell values. Never
hand-roll CSV escaping; the shared utility handles both delimiter
quoting and spreadsheet formula neutralization (=, +, -, @, tab, CR
prefixes). This prevents CSV injection attacks where user-controlled
values starting with formula characters execute in Excel/LibreOffice.

### Multi-entry-point validation

Validate untrusted input at each entry boundary with the owning shared schema.
Generated MCP capabilities validate the live endpoint's body, params, and query
schemas in `apps/api/src/mcp/capability-tools.ts`; do not repeat that parsing
inside the handler. Native MCP tools and chat registry tools normalize input
through `normalizeObjectInputAtBoundary` before dispatch; any other caller that
bypasses the HTTP route (cron jobs, workers) must apply the same normalizer and
schema before calling the operation. Keep business invariants and
related-resource authorization in the owning operation: an ID's valid shape
does not establish its ownership.

### Filename sanitization

All user-supplied filenames must pass through `sanitizeFilename`
(`@/api/lib/sanitize-filename`) before storage or use in file
operations (ZIP entries, Content-Disposition headers, S3 keys).
The sanitizer strips path separators, traversal sequences, and
dangerous characters. Enforced by
`security-guards/no-raw-filename-write`.

### Cross-org user ID validation

When a handler accepts a `userId` from user input (body, query, or
params) and uses it in a query that returns user data (names, emails,
images), validate org membership first using `validateOrgUserId` from
`@/api/lib/validated-org-user-id`. The returned `ValidatedOrgUserId`
proves the check happened at the type level, making cross-org user ID
injection structurally impossible. For read paths that resolve
userIds stored in the database (not from user input), scope the user
query with an `innerJoin` on the `member` table filtered by
`session.activeOrganizationId`; enforced by
`security-guards/no-unscoped-user-query`.

### CI workflow permissions

GitHub Actions workflows must declare the **minimum** permissions
needed. Never use `permissions: write-all`. For PR-triggered
workflows, scope to `contents: read` + `pull-requests: write`.
Pin third-party actions to commit SHAs, not mutable tags. For
SBOM/provenance, use the shared `stella/.github` reusable
workflows which handle pinning, checksums, and PR-based updates.

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.