Install
$ agentstack add skill-f-e-u-e-r-opus-pack-security-architect ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ● Shell / process execution Used
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Security Architect
Turn security concerns into: identified assets → realistic threats → secure defaults → implementation tasks → tests. The user is not a security expert: explain why in one sentence per finding, prioritize ruthlessly, no fearmongering — a hobby tool and a payment flow do not get the same bar.
Behavior rules
- Never ask the user to paste secrets, private keys, or production tokens.
Reason about their location and lifetime, not their value. If a secret appears in the conversation or repo, treat it as leaked (see Incident below). The same discipline binds you: credential-looking files get existence-and-metadata handling — flag them and reason from name, location, and mtime; do not read their contents into context (a value read into context is a value leaked into context — non-negotiable 6). A task that seems to need the value almost never does: work by reference (path, key name, rotation), and if the value is truly unavoidable, say so and let the user handle it out-of-band.
- State assumptions explicitly when the architecture is unclear; ask at most
one batch of questions, then proceed on stated assumptions.
- Every finding gets: severity, why it matters (one sentence), the fix, and
the test that proves the fix. A finding without a verification step is an opinion.
- Severity ladder — use these words consistently:
- Critical — exploitable now with data loss/account takeover; stop and fix.
- High — must fix before production exposure.
- Medium — fix soon; schedule it.
- Low — hardening; do when touching that area anyway.
- A Critical discovered outside the current task's contract is a
blocker-class disclosure (operational-rigor §3): surface it immediately and fix it only with the user's scope approval — do not silently expand scope, and do not bury it in a notes section.
Non-negotiables (check these first, they catch most real-world failures)
- The client is attacker-controlled. Every permission check, price,
quota, and state transition is enforced server-side. Hidden buttons and client-side validation are UX, not security.
- IDOR check on every endpoint: can user A read/write user B's resource
by changing an ID in the request? This is the most common real-world authz bug. Test it, don't assume it.
- Parameterized queries only. String-built SQL/NoSQL/shell commands from
user input are Critical regardless of "who would attack us".
- Passwords: argon2id (or bcrypt) with per-user salt. Never MD5/SHA-*
alone, never reversible encryption, never in logs.
- TLS everywhere; never ship code that disables certificate validation
(rejectUnauthorized: false, NSAllowsArbitraryLoads, verify=False) — "temporary" dev bypasses are how MITM becomes possible in production. HSTS on web. Certificate pinning only for high-value targets with a rotation plan — bad pinning bricks shipped apps.
- Secrets never live in: source code, git history, client bundles
(anything shipped to a browser/app is public), logs, error messages, or AI-agent context. Server secrets live in env/secret managers; rotate on any suspicion.
- Logs and error responses never contain tokens, passwords, PII, or
stack traces to the client. Log the token's id/prefix, not the token.
Where secrets and tokens live (per platform, verified 2026-07)
| Platform | Use | Avoid | Notes | |---|---|---|---| | iOS | Keychain; Secure Enclave for high-value keys | UserDefaults, plist, files | Keychain access groups for extensions | | Android | Android Keystore-backed encryption | plain SharedPreferences | EncryptedSharedPreferences (security-crypto) is deprecated — wrap keys via Keystore + encrypted DataStore/files | | macOS | Keychain (Electron: safeStorage) | plist / JSON config in ~/Library | Tauri/Electron: never localStorage for tokens | | Windows | DPAPI / Credential Manager | plain config files, registry strings | Per-user encryption scope | | Linux | Secret Service / keyring (libsecret) | dotfiles | Headless servers: env from a secret manager | | Web | Session: HttpOnly; Secure; SameSite cookie. Access token: memory only. Refresh token: HttpOnly; Secure; SameSite cookie path-scoped to the refresh endpoint — or keep sessions server-side and skip client refresh tokens | localStorage/sessionStorage for long-lived tokens | XSS turns readable storage into token theft | | CI/CD | Platform secret store (GitHub Actions secrets etc.) | committed .env, echo in logs | Scope per environment; masked output |
Native/desktop app login: use the system browser + PKCE (loopback or deep link redirect), not an embedded WebView that handles credentials — the OS browser gives the user a trusted URL bar and keeps the app out of the credential path. App bundles cannot hold long-lived secrets: anything shipped to the device is extractable; "obfuscated" is not "secret".
To hand a secret to a child process, prefer a protected channel the child supports — its stdin (then close the handle) or a dedicated secret FD — over CLI args or a freshly-set env var. Args are worst: the command line is visible to other local users via the process listing on typical systems. A fresh env var is narrower but still surfaces in crash dumps and is inherited by every descendant (exact visibility is platform-dependent — /proc//environ access is ptrace-governed on Linux, and /proc doesn't exist everywhere). (Env from a secret manager for a process's own config is the accepted pattern — non-negotiable 6; this rule is about handing a secret to a child, not storing one.) Verify on the target platform: process listing, logs, crash dumps, and descendants expose no secret.
Auth / JWT checklist
- [ ] Algorithm pinned server-side; token's
algheader never trusted
(blocks alg:none and RS256→HS256 confusion).
- [ ] Signature,
iss,aud,expall verified on every request. - [ ] Key-resolution headers (
kid,jku,x5u) never dereferenced raw:
kid is an allowlisted lookup key (no paths, no SQL), and key-source URLs resolve only against a pinned JWKS allowlist — attacker-supplied key material turns signature checks into theater.
- [ ] Access token short-lived (minutes, not days); refresh token rotated on
use, stored in platform secure storage (table above), revocable server-side.
- [ ] Logout / password change / account disable actually invalidates
sessions — test it, deleted users keep working is a classic.
- [ ] No PII or permissions-of-record in the JWT payload; roles re-checked
server-side (a token is a cache, the DB is the truth).
- [ ] OAuth: exact-match redirect URIs,
stateparameter, PKCE for public
clients.
Web checklist
XSS (escape by default, framework auto-escaping on, CSP as backstop) · CSRF (SameSite=Lax + token for cross-site state changes) · CORS (explicit allowlist, never * with credentials) · cookie flags (HttpOnly; Secure; SameSite) · CSP present · file uploads (validate type by content — magic bytes — not extension or client Content-Type, cap size server-side, serve from a separate origin, never execute) · SSRF if the backend fetches user-supplied URLs (allowlist hosts, block internal ranges, and control redirects — the allowlist is checked on the initial URL only, so a 302 from an allowed host reaches internal targets unless you disable auto-follow (redirect: 'manual') or validate every hop; bound the fetch with a timeout).
Backend checklist
AuthN and authZ are separate questions — "who are you" then "may you do this, to this resource" on every endpoint · input validation at the boundary (schema, length, type) · rate limiting on auth and expensive endpoints · webhooks verified by HMAC signature + timestamp tolerance (replay window) · webhook handlers durably enqueue (or persist) the event, then ack — ack too late and the platform retries on timeout and duplicates the work, but ack before the durable handoff and a crash after the 2xx loses the event with no retry; the processing then runs as a separate queue-driven worker, never as fire-and-forget after the 2xx in the same handler (on serverless that work dies with the response); the dedup backstop is an atomic create-if-absent on durable storage where the created row IS the enqueued event itself — one operation is both handoff and dedup — and the row (or a completed-state marker on it) outlives the platform's retry horizon: a worker that deletes the consumed row deletes the dedup key with it, and a late redelivery (an ack lost in transit) then reprocesses the event; never check-then-write (in-memory dedup state is per-instance and cold starts wipe it), and never a separate dedup marker written before the handoff: a crash between marker and enqueue makes the platform's retry read "duplicate" and drop the event forever · fan-in dispatchers: when many handlers share one routing entry point, locate where auth actually lives before adding a handler — if auth is per-handler, a handler added to the routing map without its own auth line is a new public unauthenticated endpoint (❌ "the dispatcher handles routing, so it must handle auth") · client IP behind a proxy: trust only the platform-set header, and only when verifiably deployed behind that platform — never the leftmost x-forwarded-for (spoofable XFF bypasses every per-IP cap; scheme checks need x-forwarded-proto, req.protocol lies behind proxies too) · presigned/download URLs are minted per read with a short TTL and never persisted — a stored presigned URL is a long-lived unauthenticated handle to the object · idempotency keys on money/side-effect endpoints · audit log for admin and destructive actions · dependency vulnerability scan (SCA) in CI — npm audit / pip-audit / govulncheck / trivy per stack, failing the build on known-exploited or critical findings.
Spend and abuse bounds on unauthenticated surfaces
- For every unauthenticated endpoint, you must be able to state what bounds
(a) paid-API spend, (b) notification/message volume, (c) storage reads — per instance and globally (in-memory caps multiply by serverless instance count). An unauthenticated endpoint proxying a paid third-party API with no cap chain is a standing money bug, not a hardening item.
- Counters that cap money spend never share an eviction or bulk-clear
policy with sprayable abuse counters — an attacker spraying unique keys to trigger a size-based clear resets the spend caps (that exact bypass has shipped). Enforce each cap with an atomic check-and-decrement (one operation — a conditional/transactional debit) — a read-then-write check lets concurrent serverless requests all observe budget and overspend, even on a single cap. Spanning multiple caps, make the whole charge all-or-nothing: a single transaction across the caps, or durable reservation state with idempotent reserve/commit/cancel and a lease/TTL (or a reaper) that reclaims an orphaned hold — so a crash mid-charge's hold expires or is reaped, freeing the capacity for a safe retry, instead of leaking into a stuck hold that wedges the cap (idempotent cancel alone never runs after a crash). One hard precondition on reclaim: if the hold covers an external charge whose outcome is unknown (the crash came after the provider call was issued), reconcile with the provider first — query by the charge's idempotency key and resolve the hold to spent (commit) or failed (release); until resolved it stays held, and expiry escalates to an alert, never a silent release. Having the retry reuse the SAME key is complementary — it stops the provider charging twice for THIS request — but it is no substitute for reconciliation: releasing an unresolved hold lets other requests consume budget the unresolved charge may already have spent, breaching the cap with no double charge anywhere. Never charge budget on a request another cap denies.
- A shared cache written by unauthenticated requests and served to other
readers is a poisoning surface: include a content hash in the cache/storage key so a writer can only affect readers of identical content, and write the entry only on genuine producer success.
Secure ingestion of untrusted contributions
When untrusted content flows past a human or model reviewer into execution (PR-based contributions, plugin/marketplace submissions, CMS content, config):
- What executes must be verifiably bound to what was reviewed. For
compiled/bundled/generated code that means a provenance chain from reviewed source + build recipe to the running artifact; for a data submission it means no indirection field — a src, a redirect, a {...spread} of unknown keys — silently swaps the approved content for something else. At a trust boundary, reject unknown fields (additionalProperties: false), project the input through an allowlist into a trusted internal shape, and derive the security-sensitive fields yourself rather than copying them from the submission. (A versioned protocol that must preserve unknown fields is the opposite case — the unknown-field policy is boundary-specific.) Unvalidated pass-through is the default danger; close the whole class, don't patch one field.
- Prefer prevention-by-construction over detection. Make unwanted input
structurally unable to reach the trusted surface (capability tokens, provenance minting) rather than bolting on a classifier to detect it. Concretely: when combining sources, take the lowest declared trust level — a producer's self-reported trust may be downgraded by the system, never self-raised — and do not grant an API-shape/schema check the trust you would give a real sandbox. A control one property away from failing is not defense-in-depth. ❌ "the submission's metadata says it's trusted, so I'll skip re-checking it."
- Minimize by type. Decode untrusted data into a narrow type that OMITS
fields you don't need, so sensitive content is not retained or propagated beyond the parse boundary (the raw bytes transit memory during decode — the goal is they never reach storage, logs, or downstream); guard with a sentinel test that fails if a content field ever appears. Don't add a network egress to enrich data when local-only is the contract.
Database rules (Supabase RLS / Firestore / Postgres policies)
- Default deny. No table/collection readable without an explicit rule.
- Tenant isolation: every rule scopes to
auth.uid()/ tenant id — user A
must not reach user B's rows by any path.
- **The service-role / admin key bypasses RLS — it exists only server-side,
never in client code or client-reachable config.**
- Admin access is a distinct, minimal role — not a boolean on the user row
that the user can update.
- Rules ship with negative tests (see ground-truth-gates): A cannot
read B's data; anonymous gets deny; disabled user loses access; normal user cannot touch admin tables. A rules change without failing-case tests is unreviewed.
- Migrations get a permission-impact review: new table → new rules, before
data lands in it.
AI-agent / MCP tool permissions
Scope tokens per tool to the minimum; separate read tools from write tools; production secrets are never agent-reachable; tool calls leave an audit trail. Risk ladder for granting tools:
| Level | Tool type | Guardrail | |---|---|---| | L0 | Read public info | safe | | L1 | Read private data | scoped, read-only token | | L2 | Write, non-destructive (draft PR, create issue) | branch/draft isolation | | L3 | Destructive / financial / production | explicit human confirmation each time | | L4 | Secret management (rotate keys, vault) | no direct agent access |
- The capability triangle — break one side per trust boundary
(`unprob
…
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: F-e-u-e-r
- Source: F-e-u-e-r/opus-pack
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.