Agent skill

Security Stance

by fjrevoredo in fjrevoredo/mini-diarium

Mini Diarium's opinionated security, encryption, and privacy stance.

MITAuto-check passedDatabases

Install Security Stance

skills CLI
$ npx skills add fjrevoredo/mini-diarium --skill security-stance -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install fjrevoredo/mini-diarium security-stance --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/fjrevoredo/mini-diarium.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/security-stance .claude/skills/security-stance && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
security-stance
GitHub stars
308
Token cost
~11k tokens
SKILL.md length
4,867 words
Files
1
Skills in repo
41
Repo updated
First seen
Licence
MIT

At a glance

Mini Diarium's opinionated security, encryption, and privacy stance.

  • Works in 12 steps: When to load this skill → The five non-negotiables (absolute… → Threat model at a glance → …
  • Tasks that involve Backup and disaster recovery
  • SKILL.md covers 1. When to load this skill, 2. The five non-negotiables…, 3. Threat model at a glance and 4. Crypto invariants (concrete…, plus 14 more sections
  • Calls cargo, bun and git

What it does

Security Stance is an agent skill from fjrevoredo/mini-diarium. Mini Diarium's opinionated security, encryption, and privacy stance. Load when touching crypto (crates/mini-diarium-crypto/src/crypto/), auth (crates/mini-diarium-core/src/auth/, commands/auth/), IPC boundaries (src/lib/errors.ts, tauri.ts), auto-lock (screenlock.rs, App.tsx idle timer), database schema/migrations (db/schema.rs), backups (backup.rs), config.json / JournalConfig, import/export, plugin sandbox (Rhai), search (intentionally stubbed), debug dump, Tauri capabilities (src-tauri/capabilities/) or CSP…

Its SKILL.md is about 11k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts. Compatibility notes: Designed for Claude Code. Applies to Mini Diarium only.

It sits in Databases, covering Backup and disaster recovery, React components and Database schema design. It works with Tauri. The repository describes itself as: A local-only journal with serious encryption. Free, open source, and never touches the internet. The licence is MIT.

When your agent uses it

  • Tasks that involve Backup and disaster recovery
  • Tasks that involve React components
  • Tasks that involve Database schema design

Example prompts

  • “/security-stance”

Requirements

  • Compatibility (from SKILL.md): Designed for Claude Code. Applies to Mini Diarium only.

Workflow steps

12 steps, taken from the step headings in SKILL.md.

  1. When to load this skill
  2. The five non-negotiables (absolute invariants)
  3. Threat model at a glance
  4. Crypto invariants (concrete numbers, not principles)
  5. Auth architecture
  6. Auto-lock: all three paths must fire
  7. The IPC / error sanitization boundary
  8. Data-at-rest rules
  9. Search: in-memory scan (no plaintext on disk)
  10. Plugin / Rhai sandbox
  11. Import / export safety
  12. Runtime boundaries (Tauri capabilities, CSP, E2E isolation)

What it can do on your machine

Read from SKILL.md and the folder at commit eed457f. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • cargo
    • bun
    • git

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md. Its commands use git, which can reach the network depending on how they are called.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

  • Compatibility

    Designed for Claude Code. Applies to Mini Diarium only.

    From compatibility in the SKILL.md frontmatter.

Context cost

Security Stance loads about 11k tokens when it runs. Until then it costs about 196 tokens; SKILL.md has 4,867 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~196
When it runs · the whole SKILL.md, loaded when a task matches
~11k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from fjrevoredo/mini-diarium at commit eed457f, republished under its MIT licence (© fjrevoredo). 4,867 words, ~10,929 tokens.

Download SKILL.mdSave it as .claude/skills/security-stance/SKILL.md (or your agent's skills folder).
name
security-stance
description
Mini Diarium's opinionated security, encryption, and privacy stance. Load when touching crypto (crates/mini-diarium-crypto/src/crypto/), auth (crates/mini-diarium-core/src/auth/, commands/auth/), IPC boundaries (src/lib/errors.ts, tauri.ts), auto-lock (screen_lock.rs, App.tsx idle timer), database schema/migrations (db/schema.rs), backups (backup.rs), config.json / JournalConfig, import/export, plugin sandbox (Rhai), search (intentionally stubbed), debug dump, Tauri capabilities (src-tauri/capabilities/) or CSP (tauri.conf.json), E2E isolation env vars, or anything involving passwords, keys, nonces, zeroization, or entry persistence. Also load when reviewing or proposing new features to judge against the six PHILOSOPHY.md principles and five non-negotiables.
compatibility
Designed for Claude Code. Applies to Mini Diarium only.

Mini Diarium — Security Stance

The opinionated, repo-specific security/privacy/encryption stance for Mini Diarium. Use this skill alongside (not instead of) /security-review. This document is passive context that loads whenever security-adjacent work appears; /security-review is a one-shot review command.

The authoritative sources are PHILOSOPHY.md and SECURITY.md. This skill distills the operational details: invariants, file:line anchors, footguns, and what-breaks-if-you-change-this. When this document and an authoritative source disagree, the source wins — file an update to this skill.


1. When to load this skill

Load when work touches any of:

  • Crypto: crates/mini-diarium-crypto/src/crypto/cipher.rs, crates/mini-diarium-crypto/src/crypto/password.rs
  • Auth: crates/mini-diarium-core/src/auth/mod.rs (composed slot ops + AuthMethodInfo), crates/mini-diarium-crypto/src/auth/{password,keypair,auto_key}.rs (the pure wrapping methods), src-tauri/src/commands/auth/
  • IPC contract: src/lib/tauri/, src/lib/errors.ts, any new #[tauri::command]
  • Auto-lock paths: src/App.tsx idle timer, src-tauri/src/screen_lock.rs, src/lib/focus-lock.ts, src-tauri/src/window_focus.rs, src/lib/dialog.ts
  • DB schema / migrations: crates/mini-diarium-core/src/db/schema/mod.rs (SCHEMA_VERSION), crates/mini-diarium-core/src/db/schema/compat.rs (forward-compatibility guard), crates/mini-diarium-core/src/db/schema/migrations/
  • Backups & rotation: crates/mini-diarium-core/src/backup.rs
  • Journal config: crates/mini-diarium-core/src/config.rs (JournalConfig, auto_key, require_all_auth)
  • Import / export: src-tauri/src/commands/import.rs, commands/export.rs, import/*.rs, export/*.rs
  • Plugin sandbox: crates/mini-diarium-core/src/plugin/rhai_loader/ (runtime.rs = engine + limits, metadata.rs, mod.rs = loader), plugin/registry.rs, plugin/builtins.rs
  • Search interface (preserved stub): src-tauri/src/commands/search.rs, src/state/search.ts, src/components/search/
  • Debug dump: src-tauri/src/commands/debug.rs
  • File-read commands & allowlists: src-tauri/src/commands/files.rs
  • Tauri capabilities: src-tauri/capabilities/*.json
  • CSP: src-tauri/tauri.conf.json (security.csp, dangerousDisableAssetCspModification)
  • Network isolation: src-tauri/src/lib.rs (init script, on_navigation, on_new_window, Windows COM handler, macOS WKContentRuleList handler); src/lib/network-isolation-script.ts (TS copy — must stay in sync with Rust)
  • E2E env-var isolation: src-tauri/src/lib.rs (MINI_DIARIUM_E2E*)
  • Sanitization of imported HTML/Markdown: src/lib/markdown.ts (DOMPurify)
  • Anything involving passwords, keys, nonces, zeroization, or persistence of entry content

Examples

  • Adding a new Tauri command that touches entries / files / auth / plugins → load
  • Pure CSS-only change, i18n string addition, README typo → do not load
  • Reviewing/proposing new features → load to apply the six principles + five non-negotiables

2. The five non-negotiables (absolute invariants)

From PHILOSOPHY.md:165-169. These are not preferences — they are red lines.

#RuleEnforcement / Where it shows up first
1No network access. Mini Diarium never initiates any network connection. OS-opener links (About screen, Onboarding) hand a URL to the system browser — the app makes no network call itself.None of the three crate manifests — src-tauri/Cargo.toml (app crate), crates/mini-diarium-core/Cargo.toml (business layer), or crates/mini-diarium-crypto/Cargo.toml (crypto crate) — contains reqwest, hyper, socket2, ureq, etc. (PHILOSOPHY.md:225). Capabilities allowlist (src-tauri/capabilities/default.json) has no http:*. CI static check scripts/check-no-network.ps1 (globs and scans all crate manifests) enforces this on every push. Adding a network crate or capability is the visible violation.
2No custom cryptography. Standard algorithms and established libraries only.Code review: any homegrown MAC, KDF, cipher mode, nonce scheme, or "encryption helper" is the violation. We use aes-gcm, argon2, x25519-dalek, hkdf, zeroize.
3No password recovery. If credentials are lost, data is gone. Mitigation = register a second auth method.Refusing to add a "reset password without old password" path. The only re-wrap is change_password (requires current creds). remove_auth_method refuses to delete the last slot.
4No vendor lock-in. Users must be able to export and migrate freely.JSON + Markdown exports remain plaintext, with documented schema (PHILOSOPHY.md:82-87). Removing or proprietizing an export format is the violation.
5Honest threat documentation. Document what IS protected and what IS NOT. Never overstate.SECURITY.md table is the contract. Adding sync, sharing, or "encrypted by your account" without updating the threat model is the violation.

If an agent proposes a change that visibly conflicts with any non-negotiable: STOP. Surface the conflict to the user. Do not implement, even partially.


3. Threat model at a glance

Compact form of SECURITY.md:30-43. This is the "what can I honestly promise?" cheat sheet.

ThreatProtected?By what mechanismWhere documented
Offline file access (stolen device, dumped backup, raw diary.db)YESAES-256-GCM per field, fresh random 12-byte nonce from OsRng per writeSECURITY.md:32, crypto/cipher.rs:70-74
Password guessing at restYESArgon2id m=64 MiB, t=3, p=4 (exceeds OWASP 2023 minimums)SECURITY.md:33, crypto/password.rs:8-10
Weak-credential key-file attackYESX25519 ECDH + HKDF-SHA256 — strength = 256-bit curve regardless of file location/nameSECURITY.md:34, auth/keypair.rs:8
Multi-credential compromise (revoke one, others unaffected)YESEach auth method wraps its own copy of the master key in auth_slotsSECURITY.md:35
In-memory key leakage after lockYESLock drops DB connection, clears keys; SecretBytes/Key use ZeroizeOnDrop; explicit .zeroize() on success and error pathsSECURITY.md:36, crates/mini-diarium-crypto/src/auth/mod.rs:21-53, crypto/cipher.rs:14-22, crypto/password.rs:60,103
Running session on the device (unlocked)NOOut of scope — content lives in process memory; mitigated by lockingSECURITY.md:40
Malicious software on the device (keylogger, screen-cap, ptrace)NOOut of scope — protect the device firstSECURITY.md:41
Cloud-sync provider compromise of the DB file alonePARTIALSees only ciphertext. If the same provider also holds the key file or config.json with auto_key, the journal is compromised.SECURITY.md:42, docs/decisions/2026-04-passwordless-journal.md
Coercion / credential theft from an unlocked deviceNOOut of scope — encryption cannot defend against an attacker who can observe or compelSECURITY.md:43

4. Crypto invariants (concrete numbers, not principles)

InvariantValue / shapeFile:lineWhat breaks if changed
AES-GCM key size32 bytes (AES-256)crates/mini-diarium-crypto/src/crypto/cipher.rs:9Incompatible with all existing ciphertext
AES-GCM nonce size12 bytescrates/mini-diarium-crypto/src/crypto/cipher.rs:12AES-GCM spec violation; existing blobs unparseable
AES-GCM nonce generationRandom per encrypt via OsRng.fill_bytescrates/mini-diarium-crypto/src/crypto/cipher.rs:70-74Nonce reuse → catastrophic keystream-XOR leak
Ciphertext blob layout[nonce(12) ‖ ciphertext ‖ tag(16)]crates/mini-diarium-crypto/src/crypto/cipher.rs:83-104All existing entries unrecoverable
Argon2id memory65536 KiB (64 MB)crates/mini-diarium-crypto/src/crypto/password.rs:8Weakening = faster offline crack; strengthening = slow unlock on older hardware. Either way: schema migration + CHANGELOG Security entry.
Argon2id iterations3crates/mini-diarium-crypto/src/crypto/password.rs:9Same — migration class change
Argon2id parallelism4crates/mini-diarium-crypto/src/crypto/password.rs:10Same — migration class change
X25519 HKDF info stringb"mini-diarium-v1" (constant)crates/mini-diarium-crypto/src/auth/keypair.rs:8All existing keypair slots unrecoverable
Wrapped-key blob (keypair)[eph_pub(32) ‖ nonce(12) ‖ ciphertext(32) ‖ tag(16)] = 92 bytes for a 32-byte master keycrates/mini-diarium-crypto/src/auth/keypair.rs:12-14Existing keypair slots unparseable
SecretBytes#[derive(ZeroizeOnDrop)]; Debug shows SecretBytes([REDACTED; N])crates/mini-diarium-crypto/src/auth/mod.rs:21-53Keys linger in heap; Debug would leak bytes
Key struct#[derive(Zeroize, ZeroizeOnDrop)]; Debug shows [REDACTED]crates/mini-diarium-crypto/src/crypto/cipher.rs:14-22Same leak class
Master-key generation32 random bytes from aes_gcm::aead::OsRng.fill_bytes, raw bytes zeroized after wrapcrates/mini-diarium-core/src/db/schema/create.rsPredictable / reused master key would be catastrophic
Schema versionpub const SCHEMA_VERSION: i32 (current value in source)crates/mini-diarium-core/src/db/schema/mod.rsBump on every schema change; each bump must decide min_reader_version (db/schema/compat.rs)
Max import file size100 MB (MAX_IMPORT_FILE_SIZE)src-tauri/src/commands/import.rs:5Too high → memory DoS; too low → legitimate imports fail
Max text file read1 MiB (MAX_TEXT_FILE_BYTES)src-tauri/src/commands/files.rs:19Same DoS class
Backup retention policytiered: 10 recent + 1/day × 14 d + 1/week × 8 w + 1/month × 12 mo; storage budget max(2 GiB, 3 × journal size)crates/mini-diarium-core/src/backup/policy.rs:25-37Disk-use guarantee, not a crypto invariant; changing it is a user-facing policy change

Rule for any change to a row in this table:

  1. Schema migration with SCHEMA_VERSION bump in db/schema/mod.rs.
  2. Explicit ### Security section in the next CHANGELOG entry, plus user-facing notes.
  3. SECURITY.md update if the threat model shifts.
  4. Stop and confirm with the user before silently "upgrading" parameters.

5. Auth architecture

A random 256-bit master key is generated at journal creation (db/schema/create.rs). It encrypts every entry field with AES-256-GCM. The master key itself is never stored in plaintext — it is wrapped per registered auth slot in the auth_slots table (schema v3+). See SECURITY.md:47-69.

Three wrap methods exist:

MethodFileWrap mechanismStores
PasswordMethodcrates/mini-diarium-crypto/src/auth/password.rsArgon2id-derived key + AES-256-GCMPHC hash + AES-GCM blob
KeypairMethodcrates/mini-diarium-crypto/src/auth/keypair.rsX25519 ECIES + HKDF-SHA256 + AES-256-GCMX25519 public key + 92-byte ECIES blob
AutoKeyMethodcrates/mini-diarium-crypto/src/auth/auto_key.rs32-byte device-bound random key + AES-256-GCM (no KDF — already 32 bytes of entropy)Wrapping key hex lives in config.json, not in the DB

Hard rules:

  • change_password is O(1): re-wraps the master key only. Never re-encrypt entries on password change — that conversation always ends in a production bug. See SECURITY.md:61.
  • remove_auth_method refuses to delete the last slot. That guard lives at src-tauri/src/commands/auth/auth_methods.rs:197+. Never remove or weaken it. Removing the only auth method = unrecoverable journal.
  • require_all_auth (per journal) forces unlock_diary_all_methods. Single-method unlock paths must keep the guard. The guard lives in commands/auth/auth_core.rs:80-90 and again at :136-146 (one per single-method unlock path). When adding a new single-method unlock path, add the guard there too.
  • Passwordless (AutoKeyMethod) is accepted per docs/decisions/2026-04-passwordless-journal.md. Trade-off: OS-account compromise = journal compromise. The UI surfaces this via an explicit acknowledgement checkbox in PasswordCreation.tsx. The auto_key hex must stay in config.json — moving it into the DB creates a decryption circularity (you would need the key to decrypt the key).
  • Master-key returns travel as SecretBytes (crates/mini-diarium-crypto/src/auth/mod.rs:21-53) so they zeroize on drop even if the caller forgets.
  • Both success and error paths zeroize. Wrong-password code paths must also call .zeroize() on derived wrapping keys. See crypto/password.rs:60 (success) and :103 (error).

6. Auto-lock: all three paths must fire

Auto-lock is a triple-path mechanism. A change to one path is implicitly a change to all three — the agent must verify the other two still work.

Path A — Frontend idle timer (src/App.tsx:20-55)
  • Tracks user activity events: mousemove, keydown, click, touchstart, scroll (App.tsx:22).
  • After autoLockTimeout seconds of inactivity, calls lockJournal() (App.tsx:42).
  • Cleanup in onCleanup removes listeners and clears the timer (App.tsx:48-54).
  • Controlled by autoLockEnabled + autoLockTimeout preferences.
Path B — Backend OS events (src-tauri/src/screen_lock.rs)
  • Windows: WM_WTSSESSION_CHANGE (lock/logoff) + WM_POWERBROADCAST (suspend) via Win32 subclass (screen_lock.rs:1-100).
  • macOS: screen-sleep + com.apple.screenIsLocked notifications.
  • Both call commands::auth::auto_lock_diary_if_unlocked(...) (screen_lock.rs:79, :208) and emit 'journal-locked'.
  • Fires even when the app is in the background.
Path C — Frontend focus-loss lock (src/lib/focus-lock.ts + src-tauri/src/window_focus.rs, wired in App.tsx)
  • Not DOM visibilitychange — the original design, disproven by manual testing (TODO-0068): WebView2 does not reliably update document.visibilityState on window minimize (confirmed with the CDP debugger fully detached, ruling out an automation-tooling confound).
  • Detection lives in Rust (window_focus.rs): on_window_event on WindowEvent::Focused, emitting "window-unfocused" (false) / "window-focused" (true). A single push-based event pair covers minimize, Alt+Tab/Cmd+Tab away, clicking another app's window, and (on macOS) Cmd+H "Hide" — all of these resign/regain the window's key/active status at the OS level: WM_KILLFOCUS/WM_NCACTIVATE (Windows), windowDidResignKey (macOS — its own doc comment in tao explicitly calls out the Cmd+Tab case), GTK focus-out-event (Linux, forwarded as a genuine WindowEvent unlike minimize/iconify, which GTK only tracks internally). No polling needed — this superseded an earlier is_minimized()-polling design once source inspection confirmed Focused is reliably forwarded on all three platforms.
  • Debounced, not immediate (FOCUS_LOSS_DEBOUNCE_MS, focus-lock.ts, default 3000ms, parametrized as an options.debounceMs field so it's overridable in tests and easy to retune): user feedback after shipping the immediate-lock version reported that a misclick outside the window locked the journal instantly, which was too aggressive. window-unfocused now starts a setTimeout instead of locking; window-focused clears it. isDialogOpen() and isUnlocked() are (re-)checked when the timer actually fires, not at the initial event, since either can change during the debounce window.
  • Design iteration history: (1) frontend-only visibilitychange/blur — disproven, WebView2 doesn't fire it reliably on minimize; (2) Rust is_minimized() polling + a macOS-specific NSApplication.isHidden poll for Cmd+H — replaced because it missed alt-tab/click-away entirely, which the user confirmed was undesired (intent is "lock on any focus loss"); (3) WindowEvent::Focused(false)-only, immediate lock — replaced after the user reported it was too aggressive (locks on a misclick); (4) current: Focused(false)/Focused(true) pair + debounce.
  • Native dialogs (export/import/key-file save) also resign the main window's focus the same way real focus-loss does. src/lib/dialog.ts wraps open/save/confirm from @tauri-apps/plugin-dialog with a shared open-dialog counter (isDialogOpen()); focus-lock.ts checks it when the debounce fires. Every dialog call site must import from dialog.ts, never the plugin directly — src/CLAUDE.md gotcha #10 — or it reopens the false-positive gap. A dialog that closes within the debounce window also self-cancels via the window-focused event when focus returns to the main window.
  • Reschedule, not abandon, when the debounce fires while isDialogOpen() is true (Task 3.5 of docs/archive/project-support-overlay-plan.md, 2026-08-21): the debounce check used to return outright when isDialogOpen() was true at fire time, which meant a dialog left open longer than the debounce window disabled the lock for the rest of that open period, however long. It now reschedules itself (scheduleCheck) and re-checks every debounceMs until isDialogOpen() returns false, at which point it locks if the window is still unfocused and the journal is still unlocked — the lock is delayed, not skipped. dialog.ts's openUrlSuppressingFocusLoss() (added by the same task, used only by ProjectSupportOverlay's external-link buttons) is a second, time-bounded producer of isDialogOpen() === true: it increments the same counter for a fixed EXTERNAL_HANDOFF_SUPPRESS_MS (3500ms, kept in dialog.ts and hand-synced to stay strictly greater than FOCUS_LOSS_DEBOUNCE_MS to avoid a circular import) rather than for a dialog's actual open/close lifetime, since openUrl() resolves immediately and there is no promise to await for the browser handoff itself.
  • Known gap: the dialog guard only covers the app's own dialogs. It does not cover the native menu bar, OS notifications, or other focus-stealers outside the app's own dialog call sites. Closing that fully would need a "does the newly-focused window belong to our own process" check, which is unreliable on Linux/Wayland — the same class of problem that broke the visibilitychange design. Not attempted; flagged as a known limitation instead.
  • Frontend (focus-lock.ts) listens for "window-unfocused"/"window-focused" via @tauri-apps/api/event's listen() and calls lockJournal() when the debounce timer fires, autoLockOnFocusLoss is enabled (preferences.ts, default false), the journal is still unlocked, and isDialogOpen() is false.
  • Guarded multiple ways: the wiring createEffect only registers both listeners while enabled() && isUnlocked() (cleaning up both unlisten fns and any pending debounce timer — including handling the case where disposal races the pending listen() promise — when either goes false); window-focused cancels a pending debounce as the primary "don't lock" path; isDialogOpen()/isUnlocked() are re-checked at fire time as defense-in-depth.
  • The Rust handler runs for the app's lifetime regardless of the preference — it only emits events; the frontend decides whether to act on them. Keeps Rust free of preference/lock-state/timing awareness, consistent with preferences.ts being frontend/localStorage-only.
Frontend listener (src/state/auth.ts:240-263)
  • setupAuthEventListeners() listens for 'journal-locking' (pre-lock cleanup) and 'journal-locked' (post-lock state reset).
  • Wired in App.tsx on mount.

Checklist for any change here:

  • If you touched App.tsx, did backend OS lock (Path B) and the focus-loss lock (Path C) still result in a clean UI lock?
  • If you touched screen_lock.rs, did the idle-timer path (Path A) and the focus-loss lock (Path C) still call lockJournal cleanly?
  • If you touched focus-lock.ts, did the idle-timer path (Path A) and backend OS lock (Path B) still fire independently?
  • Did the 'journal-locked' event still propagate?
  • Were the platform-specific Win32 / macOS code paths preserved?
  • If you added a new native dialog call site, does it import open/save/confirm from src/lib/dialog.ts (not @tauri-apps/plugin-dialog directly)? A direct import reopens the false-positive-lock gap Path C's dialog guard exists to close.

7. The IPC / error sanitization boundary

  • Every Tauri invoke() failure that reaches user-visible UI must go through mapTauriError(err, t) from src/lib/errors.ts:13-51.
  • What it strips: filesystem paths, OS error codes (os error N), rusqlite/sqlite/argon2 internals.
  • What it passes through verbatim: already-user-friendly strings ("file is too large", wrong-password messages — see the regex matches at errors.ts:17-32).
  • Call sites that may omit the translator argument t: module-level state (e.g., src/state/auth.ts) where no i18n context is available — mapTauriError falls back to defaultT (English) per the JSDoc at errors.ts:7-12. Those errors go to an error signal, not raw display.

Red flag patterns to grep for during review:

catch { setError(err.toString()) }
catch { setError(err.message) }
catch (err) { setError(String(err)) }

Any of these without mapTauriError is a bug. Fix at the call site.

Useful grep:

invoke\(|from '@tauri-apps/api'

For each match, confirm the surrounding catch block routes through mapTauriError. See src/CLAUDE.md "Error Handling" for the canonical pattern.


8. Data-at-rest rules

SurfaceEncrypted?Allowed contentsForbidden contents
diary.dbYES — per-field AES-256-GCMEncrypted entry title/body, auth_slots (wrapped master key copies), db_settings (schema v6: require_all_auth + HKDF-SHA256 MAC), schema metadataPlaintext entry content; FTS index (removed in schema v4); plaintext title/body
config.jsonNO — plaintext by designJournal metadata (id, name, path), optional auto_key hex (32-byte random wrapping key), active_journal_idEntry content; passwords; master key (plaintext); Argon2 PHC hashes; X25519 private keys; relative paths in journal entries (silently rejected — path-traversal guard); require_all_auth flag (moved to diary.db db_settings table in schema v6)
backups/YES — direct file copies of diary.dbUp to 30 timestamped copies (backup.rs:6); inherit encryptionAnything not derived from a verbatim fs::copy of the live DB
Key filesN/A — owned by userThe X25519 private key (hex). Mode 0o600 on Unix; NTFS ACLs on Windows. App never keeps a copy.App-side caching of the file or its contents. There is no "reveal key" UI — do not add one.
Exports (JSON / Markdown)NO — intentionally plaintextThe portability contract from Principle 4 (PHILOSOPHY.md:67-88). UI must warn before write.Encrypted-export formats. Don't add one without an explicit design review — conflicts with Easy In, Easy Out.
localStorageNO'preferences' (autoLockEnabled, autoLockTimeout, hideTitles, enableSpellcheck, etc.); 'theme-preference' (auto/light/dark); 'theme-overrides' (CSS token JSON). See src/CLAUDE.md Gotcha #6 for the three-key fan-out.Entry content; passwords; master key; auth material; decrypted anything
Debug dump (commands/debug.rs)NO — written to user-chosen pathEntry counts, distinct-day count, word totals, date range, plugin counts, schema version, OS info, build type, preferences JSON passed from frontend (debug.rs:8-43)Entry titles or bodies; passwords; keys; auth material; filesystem paths beyond the diary directory root

Reconciliation: auto_key in config.json vs. "no plaintext on disk"

The non-negotiable from PHILOSOPHY.md:166 is about diary content, not wrapping keys. auto_key is a 32-byte random AES wrapping key; the entries it wraps remain AES-256-GCM ciphertext on disk. The ADR (docs/decisions/2026-04-passwordless-journal.md:38-40) makes this explicit. Do not "fix" this by moving plaintext entries somewhere else, or by removing auto_key. The trade-off is documented and acknowledged in the UI.

WAL-mode caveat: SQLite is in default journal mode (DELETE), not WAL. fs::copy is therefore safe (backup.rs:22-28). A backup taken mid-write could theoretically capture a partial transaction; documented in SECURITY.md:87-88. Switching to WAL requires switching backups to the SQLite Online Backup API at the same time.


9. Search: in-memory scan (no plaintext on disk)

FTS was removed in v0.2.0 (schema v4) because the plaintext entries_fts table defeated encryption at rest. Search was reintroduced as an in-memory scan: each query decrypts entries (same decrypt path as export/stats), matches case- and accent-folded terms, builds HTML-escaped <mark> snippets, and discards everything. Nothing searchable is written to disk. Do not delete any of these:

LayerPathPurpose
Rust commandsrc-tauri/src/commands/search.rsSearchResult struct + search_entries (in-memory decrypt + scan)
TS wrappersrc/lib/tauri/search.tsSearchResult interface + searchEntries(query)
Statesrc/state/search.tssearchQuery, searchResults, isSearching signals
UIsrc/components/search/SearchOverlay.tsx (+ SearchBar.tsx, SearchResults.tsx)Palette-style dialog mounted in MainLayout
Reindex anchors// Search index hook: comments in crates/mini-diarium-core/src/db/queries/entries.rs (insert/update/delete) and src-tauri/src/commands/import.rs (bulk)Unused today (in-memory scan needs no index); mark where a future encrypted index would plug in

Constraints for any future implementation (non-negotiable):

  1. No plaintext on disk. Encrypted index, or in-memory rebuilt at unlock, or SQLCipher-style encrypted FTS. Do not reintroduce a plaintext FTS5 table.
  2. Schema migration required. Bump SCHEMA_VERSION in db/schema/mod.rs, add a migration step, and decide min_reader_version (db/schema/compat.rs).
  3. All reindex hooks wired. Every // Search index hook: site must call into the new module.
  4. UI placement is undecided. Wire SearchBar/SearchResults into Sidebar.tsx or a new component; do not assume the old layout. See src-tauri/CLAUDE.md "Implementing Search" for the design constraints.

Show full SKILL.md (1,868 more words)Show less

10. Plugin / Rhai sandbox

  • User scripts live in {diary_dir}/plugins/*.rhai. No filesystem access, no network access, Rhai set_max_operations and other safety limits applied (plugin/rhai_loader/runtime.rs:13-15, inside create_sandboxed_engine).
  • Built-ins and Rhai plugins share the same ImportPlugin/ExportPlugin traits (plugin/mod.rs). There is no privileged built-in path — built-ins implement the same interface as user-provided extensions (see PHILOSOPHY.md:204).
  • The unsafe impl Send for RhaiImportPlugin {} / unsafe impl Sync (and the matching pair on RhaiExportPlugin) at plugin/rhai_loader/runtime.rs:126-127,150-151 is justified because the AST is immutable after compilation and the Engine is created fresh per invocation. Adding cached mutable engines, plugin state, or shared mutable AST invalidates the justification — flag for redesign and update or remove the unsafe blocks accordingly. See src-tauri/CLAUDE.md Gotcha #7.
  • Rhai's export is a reserved keyword → export scripts must define fn format_entries(entries) (not fn export). The RhaiExportPlugin wrapper calls "format_entries" internally. See src-tauri/CLAUDE.md Gotcha #6.
  • The plugin registry is built once at startup; diary-directory changes require app restart. Do not add hot-reload without threat modelling — it opens a path for the plugin dir to swap underneath a running unlock. See src-tauri/CLAUDE.md Gotcha #5.
  • unsafe impl Send + Sync should only ever appear on Rhai wrappers. Any new unsafe impl on a plugin type requires explicit review.

11. Import / export safety

  • Imports always create new entries. There is no date-conflict merge (PHILOSOPHY.md:73). Re-import = duplicates; that is the correct behaviour.
  • File-size caps:
    • Imports: 100 MB (MAX_IMPORT_FILE_SIZE at src-tauri/src/commands/import.rs:5).
    • Markdown single-file read: 1 MiB (MAX_TEXT_FILE_BYTES at src-tauri/src/commands/files.rs:19).
  • File-read allowlists (no wildcards):
    • read_file_bytes accepts only jpg, jpeg, png, gif, webp, bmp (commands/files.rs:7).
    • read_text_file accepts only .md (commands/files.rs:27).
    • Any new file-read command must add an explicit extension allowlist and a size cap. No exceptions.
  • HTML/Markdown sanitization on import: Markdown imports go through DOMPurify in src/lib/markdown.ts:18-21 before TipTap. Do not bypass. TipTap's own sanitization is not sufficient for untrusted markdown.
  • Export side: JSON and Markdown are intentionally plaintext (Principle 4). Adding an "encrypted export" format requires explicit design review — it conflicts with Easy In, Easy Out (Principle 4 in PHILOSOPHY.md:67-88).
  • The reindex hook in commands/import.rs::import_entries (// Search index hook:) must be preserved when refactoring imports — see Section 9.

12. Runtime boundaries (Tauri capabilities, CSP, E2E isolation)

CSP (src-tauri/tauri.conf.json:24)

Current CSP:

default-src 'self' data:; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self' ipc: http://ipc.localhost; worker-src 'none'; child-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'none'; manifest-src 'none'

Any broadening — external script-src, wildcard origins, 'unsafe-eval', remote connect-src — is a breaking security change. Requires non-negotiable review and a ### Security CHANGELOG entry.

dangerousDisableAssetCspModification: ["style-src"] (tauri.conf.json:25)

This flag is intentional. Do not remove because the name looks scary.

Reason: Tauri injects a runtime nonce into every CSP directive. When a nonce is present in style-src, browsers ignore 'unsafe-inline' per the CSP spec. This silently breaks TipTap's inline style="text-align: X" node-attribute rendering — alignment looks broken in production builds with no console error in dev. The flag disables nonce injection only for style-src; script-src remains nonce-protected.

See src/CLAUDE.md Gotcha #7 (issue #63). Do not remove or restructure without testing alignment in a production build (dev mode masks the bug).

Tauri capabilities (src-tauri/capabilities/default.json)

Current allowlist:

core:default
core:window:allow-close
opener:default
dialog:default
window-state:default

No network, no unrestricted fs, no shell, no updater. Adding any of:

  • http:*
  • fs:allow-* without a tight scope
  • shell:*
  • updater:*

…is a security decision. Justify in the PR description and add to SECURITY.md Operational Security.

E2E isolation env vars

MINI_DIARIUM_E2E, MINI_DIARIUM_APP_DIR, MINI_DIARIUM_DATA_DIR are read only in src-tauri/src/lib.rs (lines 40, 54, 92, 113) at setup time. Verified by grep -rn 'MINI_DIARIUM_E2E\|MINI_DIARIUM_APP_DIR\|MINI_DIARIUM_DATA_DIR' src-tauri/src/ returning matches in lib.rs only.

Never read these env vars in commands or business logic. Doing so leaks test-only escape hatches into production builds (anyone setting the env var bypasses the protection). Any new reference outside lib.rs is a bug — move it back to lib.rs setup and pass the result through State<> if needed.


13. Concurrency & state-handling rules

  • DiaryState = Mutex<Option<DatabaseConnection>>. None = locked, Some(db) = unlocked. See src-tauri/CLAUDE.md "Security Rules".
  • Every entry-accessing command must check unlocked state:
    rust
    let db = db_state.as_ref().ok_or("Diary not unlocked")?;
    (This exact string — or one matched by errors.ts:23 journal (must be|is not) unlocked — so mapTauriError can route it to errors.journalNotUnlocked.)
  • Mutex poisoning must not panic. Commands must propagate a string error rather than letting a panic escape the Tauri boundary (a panic in a command aborts the process). For DB-only commands, use with_unlocked_db (canonical errors: "Journal state lock failed" / "Journal must be unlocked"). For commands that must open-code the preamble, use .map_err(|_| "Journal state lock failed".to_string()). Do not use .unwrap() on a Mutex — that converts a poisoned lock into a process abort.
  • Schema migrations (db/schema/migrations/) must be idempotent and wrapped in a transaction. Historical v3→v4 and v4→v5 followed this; new migrations must too. Bump SCHEMA_VERSION (db/schema/mod.rs) and document the migration step inline. Every bump must also decide the forward-compatibility marker db_settings.min_reader_version (set it in both the migration step and create_schema, or delete it) and update the tripwire in db/schema/compat.rs — see the checklist there and docs/decisions/2026-09-schema-forward-compatibility.md. Any new open path must call the compat guard before its first write.
  • New commands must be registered in two places: src-tauri/src/commands/mod.rs (module) and lib.rs generate_handler![]. Missing either causes silent failure or compile error. Add the typed wrapper in the matching command-category sub-file under src/lib/tauri/. See src-tauri/CLAUDE.md "Adding a New Tauri Command".
  • unsafe blocks outside crypto crates appear in four places: screen_lock.rs (Win32 subclass / WTS APIs), the two Rhai wrapper Send + Sync impls in rhai_loader/runtime.rs:126-151, and the two network-isolation platform handlers in lib.rs — install_webresource_requested_handler (Windows COM, lib.rs:373+) and install_content_rule_list (macOS ObjC2, lib.rs:448+). Each unsafe block has a // SAFETY: comment justifying it. Any new unsafe block elsewhere requires explicit security review and a SAFETY: block matching that pattern.

Useful greps (canary searches for review)

Run these before merging anything that touched a Section 1 surface. A surprising result is usually a bug.

GoalCommand
Network crates snuck into Cargo.tomlgrep -nE 'reqwest|hyper|socket2|ureq' src-tauri/Cargo.toml crates/mini-diarium-core/Cargo.toml
Raw error display without sanitization (frontend)grep -rnE "setError\((err\.toString\(\)|err\.message|String\(err\))" src/
invoke() call sites that may need mapTauriError reviewgrep -rn "invoke(" src/lib/tauri/ and audit downstream callers
MINI_DIARIUM_E2E* env vars leaking outside lib.rsgrep -rn "MINI_DIARIUM_E2E|MINI_DIARIUM_APP_DIR|MINI_DIARIUM_DATA_DIR" src-tauri/src/ (must show lib.rs only)
unwrap() on the diary mutex (Mutex-poison panic risk)grep -rn "state.db.lock().unwrap" src-tauri/src/
New unsafe impl blocksgit grep -n "unsafe impl" -- src-tauri/src crates (expect: 4 in plugin/rhai_loader/runtime.rs — Send/Sync for RhaiImportPlugin and RhaiExportPlugin; zero in lib.rs — the platform handlers use unsafe {} blocks, not unsafe impl)
New file-read commands without allowlistgrep -rn "std::fs::read" src-tauri/src/commands/ and confirm extension/size guards
Plaintext logging of secret materialgrep -rnE "info!|debug!|println!|eprintln!" crates/mini-diarium-core/src/auth/ crates/mini-diarium-crypto/src/auth/ crates/mini-diarium-crypto/src/crypto/ and confirm no secrets in format args
FTS reintroductiongrep -rn "fts5|entries_fts" src-tauri/src/ (must return empty)

A. Change-review checklist (use before proposing a diff)

Run through this list when modifying anything in Section 1's load-trigger surfaces.

  • Does this introduce a network dependency (crate, capability, fetch)? STOP if yes. Violates non-negotiable #1.
  • Does this introduce custom crypto (homegrown MAC, KDF, nonce scheme, cipher mode)? STOP if yes. Violates non-negotiable #2.
  • Does this add a code path where passwords / master key / SecretBytes are logged, printed, serialized into telemetry, or copied into an unzeroized buffer? Fix before merging.
  • Does this add a new invoke() call? Is its error piped through mapTauriError(err, t)?
  • Does this change crypto parameters (Argon2id m/t/p, AES key size, nonce length, HKDF info)? Schema migration + CHANGELOG ### Security + SECURITY.md review.
  • Does this persist anything new to disk? Is it ciphertext, or explicitly non-sensitive (prefs, paths, public keys)? Does it stay out of localStorage if it's sensitive?
  • Does this touch auto-lock (App.tsx timer or screen_lock.rs)? Did I verify the other path still fires on Windows + macOS + Linux?
  • Does this add/remove a Tauri command? Does it call db_state.as_ref().ok_or("Diary not unlocked")? where applicable? Does it handle Mutex poisoning without panicking? Is it registered in both commands/mod.rs and generate_handler![]?
  • Does this touch search? Did I preserve the stub interface contract (Section 9)?
  • Does this add a schema migration? Is it idempotent, transactional, and does it bump SCHEMA_VERSION? Did I decide min_reader_version (migration step and create_schema) and update the db/schema/compat.rs tripwire?
  • Does this change CSP or dangerousDisableAssetCspModification? Did I test text alignment in a production build (not dev)?
  • Does this add a Tauri capability? Is it strictly necessary, scoped, and documented in SECURITY.md?
  • Does this read MINI_DIARIUM_E2E* env vars outside src-tauri/src/lib.rs setup? Move the read to lib.rs.
  • Does this add a new file-read command? Explicit extension allowlist + size cap?
  • Does this touch import? Is DOMPurify still on the path for HTML/Markdown ingestion?
  • Does this touch remove_auth_method? Is the last-slot guard still intact?
  • Does this touch require_all_auth enforcement? Are all single-method unlock paths still guarded?
  • Does this expand the threat model (sync, share, cloud feature)? Update SECURITY.md before shipping.

B. New-feature decision framework (from PHILOSOPHY.md:146-157)

Six principle questions, plus two Mini-Diarium-specific ones. Answer all eight honestly.

  1. Core or extension? (Principle 1 — PHILOSOPHY.md:11-26)
  2. Security impact / new crypto assumption? (Principle 2 — PHILOSOPHY.md:28-44)
  3. Test coverage possible, fast, deterministic, offline? (Principle 3 — PHILOSOPHY.md:47-64)
  4. Does it affect import/export or create lock-in? (Principle 4 — PHILOSOPHY.md:67-88)
  5. Scope creep vs. focused journaling? (Principle 5 — PHILOSOPHY.md:92-117)
  6. Simplicity cost vs. benefit? (Principle 6 — PHILOSOPHY.md:121-142)
  7. Does this require a schema migration? Plan it up-front; bump SCHEMA_VERSION; write the migration step.
  8. Does this expose anything to the frontend that was previously backend-only? Audit mapTauriError coverage at every new call site.

If any principle is violated without strong justification → reconsider.


C. Tests that must stay green for security-critical changes

Security-critical changes to the listed surfaces must keep these test suites green. If a test fails, do not delete or weaken the test — it is asserting an invariant. Fix the code instead.

SuiteCommandWhat it asserts
Cipher round-trip + tampercargo test --workspace crypto::cipherEncrypt/decrypt round-trip, ciphertext-uniqueness (per-call random nonce), tag-mismatch on tampered ciphertext, tag-mismatch on tampered nonce, wrong-key rejection (crypto/cipher.rs:148-296).
Password hash + verifycargo test --workspace crypto::passwordArgon2id parameter shape (m=65536, t=3, p=4 literal in PHC string), hash determinism for same salt, salt uniqueness, unicode + empty-string passwords, wrong-password rejection (crypto/password.rs:108-232).
Auth slots round-tripcargo test --workspace authWrap/unwrap of master key for password, keypair, and auto-key methods; last-slot guard; change_password re-wraps without entry re-encryption.
File-read allowlistscargo test --workspace filesread_file_bytes rejects non-image extensions; read_text_file rejects non-.md and oversized files (commands/files.rs:44-110).
Schema + migrationcargo test --workspace db::schemaSchema creation; v3→v4→v5→v6 migrations idempotent.
Frontend error mappingbun run test:run -- errorsmapTauriError strips paths and OS codes; passes through user-friendly "file is too large" (see errors.ts:30-32).
E2E unlock + lock + re-unlockbun run test:e2e:localCritical user flow: create journal, lock, unlock again. End-to-end against the real binary.

If a security change makes one of these assertions impossible to keep, that is a signal to stop and discuss with the user — not to delete the test. The benchmarks in src-tauri/benches/cipher_bench.rs are also a useful sanity check that performance hasn't degraded after a crypto-parameter change.


References (deep-read map)

Compact pointers — do not duplicate content from these.

  • PHILOSOPHY.md — six principles + non-negotiables (authoritative)
  • SECURITY.md — threat model, crypto architecture, known limitations (authoritative)
  • docs/decisions/2026-04-passwordless-journal.md — Option B-prime rationale; threat-model shift; future migration to OS keychain
  • CLAUDE.md (root) — command registry, cross-cutting conventions, dual-path auto-lock note (Gotcha #3)
  • src-tauri/CLAUDE.md — backend security rules; Tauri command pattern; FTS removal (Gotcha #1); auth slots (Gotcha #4); plugin registry init (Gotcha #5); Rhai reserved keyword (Gotcha #6); unsafe impl Send + Sync justification (Gotcha #7); search-implementation constraints
  • src/CLAUDE.md — frontend error handling; mapTauriError pattern; base64 images in encrypted text field (Gotcha #5); three localStorage keys (Gotcha #6); dangerousDisableAssetCspModification (Gotcha #7)
  • docs/diagrams/unlock.mmd, docs/diagrams/architecture.svg — visual walkthroughs of unlock flow and layered architecture
  • CHANGELOG.md — ### Security sections per release (v0.2.0 FTS removal; v0.3.0 zeroization; v0.4.19 mandatory multi-auth; etc.)

© fjrevoredo, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .agents/skills/security-stance of fjrevoredo/mini-diarium.

Open the folder on GitHubat commit eed457f

Compare with similar skills

Security Stance next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Security Stance compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Security Stance this skillfjrevoredo/mini-diarium308—~11kAutomated safety check: PassMIT
Extending Personhog Test HarnessPostHog/posthog40k—~1.2kAutomated safety check: PassCustom licence
Postgresmagnus919/agent-skills116—~4kAutomated safety check: PassMIT
Position Encodingpikax/verter113—~4.6kAutomated safety check: PassMIT
Local Platform E2Ecomputesdk/benchmarks126—~3kAutomated safety check: NotesMIT
Desktop App Driving with Reticlereticlehq/reticle1.2k—~1.3kAutomated safety check: PassApache-2.0

Similar skills

  • Official

    When and how to add scenarios, chaos events, and invariants to the personhog e2e test harness (rust/personhog-test-harness).

    40k GitHub stars~1.2k tokensUpdated today
    Testing & QAAuto-check passed
  • Postgres

    magnus919/agent-skills

    Operate PostgreSQL instances safely: configuration review, index and query-plan analysis, vacuum and bloat management, WAL archiving and point-in-time recovery, replication and failover, extensions…

    116 GitHub stars~4k tokensUpdated today
    DatabasesAuto-check passed
  • Position Encoding

    pikax/verter

    Position encoding, span types, coordinate systems, path normalization tables for Verter's multi-layer architecture (OXC, Rust, LSP, FFI, VS Code)

    113 GitHub stars~4.6k tokensUpdated today
    DatabasesAuto-check passed
  • Local Platform E2E

    computesdk/benchmarks

    Stand up benchmarks-platform locally (Postgres + MinIO + ClickHouse in docker) and run a real @benchsdk/runner benchmark against it, with no cloud or provider credentials.

    126 GitHub stars~3k tokensUpdated today
    DatabasesAuto-check: notes
  • Drives and verifies Electron or Tauri desktop apps through Reticle, which sees the renderer and the IPC calls that a browser-based testing tool cannot observe.

    1.2k GitHub stars~1.3k tokensUpdated today
    Testing & QAAuto-check passed
  • Tauri Dev MCP Bridge

    jerrywu001/cc-sessions-viewer

    Launches a Tauri app's dev build with the MCP bridge compiled in, so an agent can take screenshots, read the live DOM, click real buttons and call real backend commands.

    395 GitHub stars~1.4k tokensUpdated 2 days ago
    Testing & QAAuto-check: warnings

More from fjrevoredo/mini-diarium

All 41 skills in this repo
  • Manual Planning

    fjrevoredo/mini-diarium

    Create, update, review, and execute manual Markdown implementation plans when harness planning mode is not being used.

    308 GitHub stars~3.7k tokensUpdated today
    Auto-check passed
  • M10 Performance

    fjrevoredo/mini-diarium

    CRITICAL: Use for performance optimization. An agent skill from fjrevoredo/mini-diarium.

    308 GitHub starsUsed in 2 repos~1k tokens
    Auto-check passed
  • Solidjs

    fjrevoredo/mini-diarium

    SolidJS framework development skill for building reactive web applications with fine-grained reactivity.

    308 GitHub stars~2.6k tokensUpdated today
    Auto-check passed
  • Tauri V2

    fjrevoredo/mini-diarium

    Tauri v2 cross-platform app development with Rust backend. An agent skill from fjrevoredo/mini-diarium.

    308 GitHub stars~2.5k tokensUpdated today
    Auto-check passed
  • Exploration Mode

    fjrevoredo/mini-diarium

    Enter exploration mode: a thinking partner for researching and thinking through ideas and problems before implementation.

    308 GitHub stars~3.4k tokensUpdated today
    Auto-check passed
  • Coding Guidelines

    fjrevoredo/mini-diarium

    A skill your agent uses when asking about Rust code style or best practices.

    308 GitHub starsUsed in 1 repo~759 tokens
    Auto-check passed

Works with

Questions about Security Stance

What does Security Stance do?

Mini Diarium's opinionated security, encryption, and privacy stance. Security Stance is an agent skill from fjrevoredo/mini-diarium. Mini Diarium's opinionated security, encryption, and privacy stance.

When should I use Security Stance?

Security Stance fits situations like: tasks that involve Backup and disaster recovery; tasks that involve React components; tasks that involve Database schema design.

How do I install Security Stance in Claude Code?

Run `npx skills add fjrevoredo/mini-diarium --skill security-stance -a claude-code`. Or copy the skill folder (.agents/skills/security-stance in fjrevoredo/mini-diarium) into .claude/skills/security-stance in your project. Claude Code loads it when a task matches its description.

How do I install Security Stance in Codex?

Run `npx skills add fjrevoredo/mini-diarium --skill security-stance -a codex`. Or copy the skill folder (.agents/skills/security-stance in fjrevoredo/mini-diarium) into .agents/skills/security-stance in your project. Codex loads it when a task matches its description.

Can I use Security Stance in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add fjrevoredo/mini-diarium --skill security-stance -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/security-stance, .gemini/skills/security-stance, .github/skills/security-stance and .opencode/skills/security-stance in your project.

What does Security Stance need to run?

Going by SKILL.md and its folder, Security Stance needs the command-line tools its instructions call (cargo, bun and git). Compatibility (from SKILL.md): Designed for Claude Code. Applies to Mini Diarium only..

Does Security Stance access the network?

SKILL.md contains no URLs. Its commands use git, which can reach the network depending on how they are called. This is read from the text; nothing was executed.

Is Security Stance safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Security Stance use?

Security Stance is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Security Stance use?

About 11k tokens (SKILL.md is roughly 44k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Security Stance?

Skills that share tags, products or a category with Security Stance: Extending Personhog Test Harness (PostHog/posthog, 40k stars), Postgres (magnus919/agent-skills, 116 stars), Position Encoding (pikax/verter, 113 stars) and Local Platform E2E (computesdk/benchmarks, 126 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Security Stance?

fjrevoredo (a GitHub user) maintains it in fjrevoredo/mini-diarium, which has 308 GitHub stars. The repository holds 41 skills in this directory. The repository was last updated on October 9, 2026.

Source: fjrevoredo/mini-diarium on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.