[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6C2FX5T47YYN7JHTS8PRZWD":3,"skill-same:01M6C2FX5T47YYN7JHTS8PRZWD":72},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":40,"linkOnly":41,"markdownContent":42,"markdownBody":43,"frontMatter":44,"refName":48,"commitSha":49,"skillDirectory":50,"forksCount":51,"repositoryHtmlUrl":52,"repositoryHomepageUrl":53,"ownerHtmlUrl":54,"ownerBlogUrl":55,"ownerDescription":56,"updatedAt":57,"install":58,"resources":71},"01M6C2FX5T47YYN7JHTS8PRZWD","secret-handling-runtime",null,"Decision aid for runtime secret hygiene — fd passing, scratch surface, error-path safety, identifier hygiene, and avoiding the SECRETS_ENV aggregation anti-pattern.","jmagly\u002Faiwg","jmagly","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F1159087?v=4","01M6C10AK8N2WCSQN11FYGTJWN","Joseph Magly","01M6C10AQ8A2RMED1Y1BSVY7NF",229,"MIT","https:\u002F\u002Fgithub.com\u002Fjmagly\u002Faiwg\u002Fblob\u002Fmain\u002Fagentic\u002Fcode\u002Fframeworks\u002Fsecurity-engineering\u002Fskills\u002Fsecret-handling-runtime\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39],"secret handling","secret handling runtime","runtime","path safety","aggregation","Safety","secrets","decision","pattern","env","fd","hygiene","error","path","for","scratch","2026-11-10 10:12:30",false,"---\nnamespace: aiwg\nname: secret-handling-runtime\nplatforms: [all]\ndescription: Decision aid for runtime secret hygiene — fd passing, scratch surface, error-path safety, identifier hygiene, and avoiding the SECRETS_ENV aggregation anti-pattern.\n\n---\n\n# secret-handling-runtime\n\nDecision aid for how a system handles secrets **during** operation: in memory, in pipes, in scratch files, in error messages, in logs, in process tables. Use when designing or reviewing any code that touches secret material at runtime.\n\nThis skill complements the existing `addons\u002Fsecurity\u002Fsecure-token-load.md` (which covers tokens at rest — file modes, heredoc patterns, source locations). This skill covers **runtime** — what happens to those secrets between load and use, and what happens when something fails mid-operation.\n\n## Triggers\n\n- \"secret in env\" \u002F \"SECRETS_ENV\"\n- \"shred\" \u002F \"tmpfs\" \u002F \"scratch surface\"\n- \"set -e\" \u002F \"error trap\" \u002F \"ERR EXIT\"\n- \"fd passing\" \u002F \"named pipe for secrets\"\n- \"log scrubbing\" \u002F \"secret in error message\"\n- \"ps aux\" \u002F \"\u002Fproc\u002Fself\u002Fenviron\"\n- \"memory wipe\" \u002F \"zeroize\"\n\n---\n\n## Section 1: File-descriptor \u002F named-pipe passing\n\n### Suggested default — one secret per fd\n\n```bash\n# Caller\nencrypt_program 3\u003C\u003C\u003C\"$SECRET_VALUE\" 4\u003C\u003C\u003C\"$OTHER_SECRET\" arg1 arg2\n\n# Inside encrypt_program\nsecret=$(cat \u003C&3)\nother=$(cat \u003C&4)\n# fd 3 and fd 4 are open for the duration of the program\n# never appear in \u002Fproc\u002Fself\u002Fenviron, never in `ps aux`\n```\n\nWhy this beats env vars:\n\n- `env`, `printenv`, `\u002Fproc\u002F\u003Cpid>\u002Fenviron` are readable by other processes on most systems\n- Env vars persist across `exec` chains (subprocess inherits)\n- `set -x` (bash trace) prints env values for each subprocess invocation\n- Crash dumps include env\n\n### Anti-pattern: SECRETS_ENV aggregation (review M3)\n\n```bash\n# WRONG\nSECRETS_ENV=\"api_key=$KEY1;db_pwd=$KEY2;jwt_secret=$KEY3\"\nsome_program  # all three secrets visible to any process inspecting environ\n```\n\nSingle variable = single leak vector. If `set -x` fires, all three leak. If a child process logs env, all three leak. Use one fd per secret OR one named pipe per secret.\n\n### Named pipes (mkfifo) when fd passing isn't structural\n\n```bash\nmkfifo \u002Ftmp\u002Fsecrets.fifo\nchmod 600 \u002Ftmp\u002Fsecrets.fifo\n{ echo \"$SECRET\" > \u002Ftmp\u002Fsecrets.fifo; } &\nprogram \u002Ftmp\u002Fsecrets.fifo\nrm \u002Ftmp\u002Fsecrets.fifo\n```\n\nCaveats:\n\n- `\u002Ftmp\u002F` should be tmpfs (Section 2)\n- Named pipes are visible to other users via `ls -la \u002Ftmp\u002F`; use a per-process directory in `\u002Frun\u002F\u003Cuid>\u002F` or `\u002Fdev\u002Fshm\u002F` that's mode 700\n\n---\n\n## Section 2: Scratch surface — tmpfs only\n\n### Suggested default — refuse to operate if `\u002Ftmp` is not tmpfs\n\n```bash\nrequire_tmpfs() {\n    local path=\"${1:-\u002Ftmp}\"\n    if ! mountpoint -q \"$path\" || \\\n       [ \"$(findmnt -no FSTYPE \"$path\")\" != \"tmpfs\" ]; then\n        echo \"ERROR: $path is not tmpfs; refusing to operate\" >&2\n        exit 1\n    fi\n}\nrequire_tmpfs \u002Ftmp\n```\n\nWhy tmpfs only:\n\n- tmpfs is RAM-resident; on shutdown, contents are gone\n- Disk-backed `\u002Ftmp` (some servers, some embedded systems) leaves traces in flash wear-leveling, journal, and swap\n- `shred -u` on flash storage is **worse than nothing** — it gives false assurance against wear-leveling reality (review M1)\n\n### `shred` is not a substitute\n\n`shred` overwrites file content multiple times then deletes. On flash storage (SSD, USB, eMMC), the wear-leveling layer remaps writes to fresh blocks; the original data sits in unmapped blocks until eventual reuse. `shred -u` deletes the visible file but the data remains recoverable via filesystem forensics.\n\n`shred` IS appropriate for spinning disks where the OS guarantees in-place rewrites — uncommon in 2026.\n\nFor ephemeral scratch, tmpfs is the answer. For persistent encrypted storage, the encryption (LUKS, FileVault) is the answer; never write secrets to \"ordinary\" disk and try to delete them later.\n\n### Documenting the scratch surface\n\n```markdown\n## Scratch surface\n\n| Path | Backing | Acceptable for secrets? |\n|------|---------|-------------------------|\n| \u002Ftmp | tmpfs (verified at start) | yes |\n| \u002Fvar\u002Ftmp | disk | NO — refuse to write secrets |\n| \u002Fdev\u002Fshm | tmpfs | yes |\n| ~\u002F.cache | disk | NO |\n```\n\n---\n\n## Section 3: Error-path safety in shell\n\n### Suggested default — `set -euo pipefail` + `trap cleanup`\n\n```bash\n#!\u002Fbin\u002Fbash\nset -euo pipefail\n\ncleanup() {\n    # zeroize what we can; unmount tmpfs scratch\n    [ -n \"${SCRATCH_DIR:-}\" ] && [ -d \"$SCRATCH_DIR\" ] && \\\n        shred -u \"$SCRATCH_DIR\"\u002F*.tmp 2>\u002Fdev\u002Fnull\n    [ -n \"${SECRET_VAR:-}\" ] && unset SECRET_VAR\n    [ -n \"${MOUNTED:-}\" ] && umount \"$MOUNTED\" 2>\u002Fdev\u002Fnull\n}\ntrap cleanup ERR EXIT INT TERM\n\n# ... actual script logic\n```\n\nWithout `set -e`, a failing `openssl enc` or `cat` mid-pipeline can produce a partial-state file the next step processes (review M2). Without the `trap`, an interrupted script leaves secret-bearing scratch files intact.\n\n### Specific shell pitfalls\n\n| Issue | Mitigation |\n|---|---|\n| `set -e` doesn't fire inside `if`\u002F`while`\u002F`&&`\u002F`\\|\\|` chains | Test exit codes explicitly in those contexts: `if foo; then ...; else echo \"foo failed\"; exit 1; fi` |\n| `set -e` ignores failures in subshells unless caller checks | `(...)` returns the subshell's exit; check it |\n| `set -x` (debug) prints expanded variables — including secrets | Never enable `set -x` on secret-bearing code paths; use `set +x` defensively before secret handling |\n| `cat \u002Fetc\u002Fsecret \\| openssl enc ...` — pipe failures partial-write | `set -o pipefail` to make the pipeline return any failure |\n\n### Python \u002F Node \u002F Go equivalents\n\n| Language | Pattern |\n|---|---|\n| Python | `try\u002Ffinally` for cleanup; `subprocess.run(..., check=True)`; `secrets.compare_digest` for any constant-time compare |\n| Node | `try\u002Ffinally` + `process.on('exit', cleanup)`; pass-fd via `child_process.spawn(..., { stdio: [...] })` |\n| Go | `defer cleanup()`; `os\u002Fexec.Cmd.ExtraFiles` for fd passing |\n\n---\n\n## Section 4: Memory hygiene\n\n### When to bother\n\nIn long-running processes (daemons, sessions), wipe key material from memory after use:\n\n```c\n\u002F\u002F C with libsodium\nsodium_memzero(key, sizeof(key));\n```\n\n```python\n# Python — limited; CPython doesn't guarantee zeroization but bytearrays help\nimport ctypes\nkey = bytearray(32)\n# ... use key\nctypes.memset(ctypes.addressof(ctypes.c_char.from_buffer(key)), 0, len(key))\n```\n\nFor short-lived programs (one-shot scripts), explicit zeroization is mostly performative — the process exits, OS reclaims memory. The bigger concern is:\n\n- **Don't put secrets in long-lived globals**; make them function-local\n- **Don't pass secrets as positional command-line args** (`ps aux` reveals them)\n- **Don't log secrets, even to debug**\n\n### `mlock` to prevent swap\n\n```c\nmlock(key_buffer, key_size);\n\u002F\u002F ... use\nsodium_memzero(key_buffer, key_size);\nmunlock(key_buffer, key_size);\n```\n\n`mlock` prevents the page from being swapped to disk. Required when the process may run on a system with swap enabled. libsodium's `sodium_mlock`\u002F`sodium_munlock` is portable.\n\n---\n\n## Section 5: Identifier hygiene\n\n### Anti-pattern: serial numbers, IDs in metadata files (review L2)\n\n```json\n{\n  \"yk5_serial\": \"12345678\",\n  \"bio_serial\": \"87654321\"\n}\n```\n\nHardware serials in metadata expose:\n\n- Operator identity (serials are often issued to specific people)\n- Hardware revisions (different firmware = different attack surface)\n- Inventory enumeration (attacker who recovers `.meta.json` learns what other devices the operator carries)\n\n### Remediation: salted hash\n\n```python\nimport hashlib, secrets\nsalt = secrets.token_bytes(16)  # per-USB, stored alongside\nyk5_hash = hashlib.sha256(salt + yk5_serial.encode()).hexdigest()\n# .meta.json stores yk5_hash and salt; serial is never persisted\n```\n\nThe salt is stored *with* the metadata, so loss of the metadata file reveals nothing. The hash lets you verify \"is this the right key?\" without revealing which key.\n\n### Apply broadly\n\nThe pattern applies to:\n\n- Hardware serials (USB, YubiKey, smart card, TPM EK)\n- Email addresses, usernames in stored audit\n- IP addresses in long-term logs\n- Build host fingerprints in artifact manifests\n\nIf you need to identify-without-revealing, salted-hash. If you need to compare-without-revealing, HMAC with a per-context key.\n\n---\n\n## Section 6: LUKS \u002F full-disk header hygiene\n\n### Required pattern (review M5)\n\n```bash\n# Provisioning\ncryptsetup luksHeaderBackup \u002Fdev\u002FsdX --header-backup-file \u002Fsecure\u002Fheaders\u002FsdX.luksheader\ngpg --encrypt --recipient hq-pubkey \u002Fsecure\u002Fheaders\u002FsdX.luksheader\n# Store encrypted backup off-device\n\n# Recovery (when header is corrupted)\ncryptsetup luksHeaderRestore \u002Fdev\u002FsdX --header-backup-file \u002Fsecure\u002Fheaders\u002FsdX.luksheader\n```\n\nA single bad block in the LUKS header bricks the volume. Backups are non-negotiable for production. The backup is encrypted to an HQ key (not the operator's keys) so its loss doesn't compromise active sessions and its theft doesn't compromise active sessions either — restoration requires HQ.\n\n---\n\n## Section 7: Logging policy\n\n### Default\n\n- **Never log secret material**, even truncated, even in debug, even in error\n- **Log secret-bearing operation events** with public metadata only (who, when, where, success\u002Ffail — not what)\n- **Log retention**: secret-bearing systems should keep logs short and rotate aggressively; logs are themselves a secondary attack surface\n\n### Examples\n\n```bash\n# WRONG\nlog \"Attempting to decrypt with key prefix: ${KEY:0:4}...\"\n\n# RIGHT\nlog \"Decrypt attempt: kid=$(hash_id $KEY_NAME) result=$result\"\n```\n\nTruncated keys are still useful to attackers (rainbow table search space reduction; structural fingerprint).\n\n---\n\n## Section 8: Worked examples\n\n### Review M1 — `shred` on flash storage\n\nOriginal: scripts use `shred -u` to delete secret-bearing files on the USB drive.\n\nWhat this skill flags:\n\n- Section 2: `shred` on flash is worse than nothing\n- Section 2 anti-pattern: writing secrets to non-tmpfs scratch\n\nRemediation:\n\n- Verify `\u002Ftmp` is tmpfs at script start; refuse if not\n- Move all scratch to tmpfs; don't write secret-bearing files to USB at all\n- Document that the USB only holds encrypted-at-rest data, never plaintext\n\n### Review M2 — `set -e` not specified\n\nOriginal: scripts use `set -uo pipefail` but not `-e`.\n\nWhat this skill flags:\n\n- Section 3: missing `-e` allows silent partial-state errors\n\nRemediation:\n\n- Add `set -euo pipefail` to all secret-bearing scripts\n- Add `trap cleanup ERR EXIT INT TERM`\n\n### Review M3 — SECRETS_ENV aggregation\n\nOriginal: all credentials concatenated into one `SECRETS_ENV` variable.\n\nWhat this skill flags:\n\n- Section 1 anti-pattern: aggregation = single leak vector\n\nRemediation:\n\n- One fd per secret, OR one named pipe per secret in tmpfs with mode 600\n- Never `export` secret-bearing variables; keep them function-local\n\n### Review L2 — YubiKey serials in `.meta.json`\n\nWhat this skill flags:\n\n- Section 5 anti-pattern: identifier in metadata\n\nRemediation:\n\n- Hash with per-USB salt; store `(salt, hash)` not raw serial\n\n### Review M5 — no LUKS header backup\n\nWhat this skill flags:\n\n- Section 6 missing required pattern\n\nRemediation:\n\n- `cryptsetup luksHeaderBackup`, encrypt to HQ pubkey, store off-device, document recovery procedure, **test it**\n\n---\n\n## Section 9: Output format\n\nWhen invoked as part of a review, produce findings in standard format. When the system is mature, produce a `secret-handling-policy.md` document covering Sections 1–7 with project-specific decisions.\n\n---\n\n## Related\n\n- **At-rest companion**: `addons\u002Fsecurity\u002Fsecure-token-load.md` (token files, modes, heredoc patterns)\n- **Companion skill**: `degraded-mode-design` (cleanup hygiene fires from degraded-mode triggers)\n- **Companion skill**: `physical-threat-modeling` (cold-boot, DMA attacks against in-memory secrets)\n\n## Standards referenced\n\n- OWASP Cryptographic Storage Cheat Sheet\n- OWASP Secure Headers Project\n- NIST SP 800-88 — Media Sanitization (for the \"shred is not enough\" case)\n- libsodium memory-handling documentation\n- `man 7 random` — RNG and memory hygiene\n- LUKS On-Disk Format Specification\n","# secret-handling-runtime\n\nDecision aid for how a system handles secrets **during** operation: in memory, in pipes, in scratch files, in error messages, in logs, in process tables. Use when designing or reviewing any code that touches secret material at runtime.\n\nThis skill complements the existing `addons\u002Fsecurity\u002Fsecure-token-load.md` (which covers tokens at rest — file modes, heredoc patterns, source locations). This skill covers **runtime** — what happens to those secrets between load and use, and what happens when something fails mid-operation.\n\n## Triggers\n\n- \"secret in env\" \u002F \"SECRETS_ENV\"\n- \"shred\" \u002F \"tmpfs\" \u002F \"scratch surface\"\n- \"set -e\" \u002F \"error trap\" \u002F \"ERR EXIT\"\n- \"fd passing\" \u002F \"named pipe for secrets\"\n- \"log scrubbing\" \u002F \"secret in error message\"\n- \"ps aux\" \u002F \"\u002Fproc\u002Fself\u002Fenviron\"\n- \"memory wipe\" \u002F \"zeroize\"\n\n---\n\n## Section 1: File-descriptor \u002F named-pipe passing\n\n### Suggested default — one secret per fd\n\n```bash\n# Caller\nencrypt_program 3\u003C\u003C\u003C\"$SECRET_VALUE\" 4\u003C\u003C\u003C\"$OTHER_SECRET\" arg1 arg2\n\n# Inside encrypt_program\nsecret=$(cat \u003C&3)\nother=$(cat \u003C&4)\n# fd 3 and fd 4 are open for the duration of the program\n# never appear in \u002Fproc\u002Fself\u002Fenviron, never in `ps aux`\n```\n\nWhy this beats env vars:\n\n- `env`, `printenv`, `\u002Fproc\u002F\u003Cpid>\u002Fenviron` are readable by other processes on most systems\n- Env vars persist across `exec` chains (subprocess inherits)\n- `set -x` (bash trace) prints env values for each subprocess invocation\n- Crash dumps include env\n\n### Anti-pattern: SECRETS_ENV aggregation (review M3)\n\n```bash\n# WRONG\nSECRETS_ENV=\"api_key=$KEY1;db_pwd=$KEY2;jwt_secret=$KEY3\"\nsome_program  # all three secrets visible to any process inspecting environ\n```\n\nSingle variable = single leak vector. If `set -x` fires, all three leak. If a child process logs env, all three leak. Use one fd per secret OR one named pipe per secret.\n\n### Named pipes (mkfifo) when fd passing isn't structural\n\n```bash\nmkfifo \u002Ftmp\u002Fsecrets.fifo\nchmod 600 \u002Ftmp\u002Fsecrets.fifo\n{ echo \"$SECRET\" > \u002Ftmp\u002Fsecrets.fifo; } &\nprogram \u002Ftmp\u002Fsecrets.fifo\nrm \u002Ftmp\u002Fsecrets.fifo\n```\n\nCaveats:\n\n- `\u002Ftmp\u002F` should be tmpfs (Section 2)\n- Named pipes are visible to other users via `ls -la \u002Ftmp\u002F`; use a per-process directory in `\u002Frun\u002F\u003Cuid>\u002F` or `\u002Fdev\u002Fshm\u002F` that's mode 700\n\n---\n\n## Section 2: Scratch surface — tmpfs only\n\n### Suggested default — refuse to operate if `\u002Ftmp` is not tmpfs\n\n```bash\nrequire_tmpfs() {\n    local path=\"${1:-\u002Ftmp}\"\n    if ! mountpoint -q \"$path\" || \\\n       [ \"$(findmnt -no FSTYPE \"$path\")\" != \"tmpfs\" ]; then\n        echo \"ERROR: $path is not tmpfs; refusing to operate\" >&2\n        exit 1\n    fi\n}\nrequire_tmpfs \u002Ftmp\n```\n\nWhy tmpfs only:\n\n- tmpfs is RAM-resident; on shutdown, contents are gone\n- Disk-backed `\u002Ftmp` (some servers, some embedded systems) leaves traces in flash wear-leveling, journal, and swap\n- `shred -u` on flash storage is **worse than nothing** — it gives false assurance against wear-leveling reality (review M1)\n\n### `shred` is not a substitute\n\n`shred` overwrites file content multiple times then deletes. On flash storage (SSD, USB, eMMC), the wear-leveling layer remaps writes to fresh blocks; the original data sits in unmapped blocks until eventual reuse. `shred -u` deletes the visible file but the data remains recoverable via filesystem forensics.\n\n`shred` IS appropriate for spinning disks where the OS guarantees in-place rewrites — uncommon in 2026.\n\nFor ephemeral scratch, tmpfs is the answer. For persistent encrypted storage, the encryption (LUKS, FileVault) is the answer; never write secrets to \"ordinary\" disk and try to delete them later.\n\n### Documenting the scratch surface\n\n```markdown\n## Scratch surface\n\n| Path | Backing | Acceptable for secrets? |\n|------|---------|-------------------------|\n| \u002Ftmp | tmpfs (verified at start) | yes |\n| \u002Fvar\u002Ftmp | disk | NO — refuse to write secrets |\n| \u002Fdev\u002Fshm | tmpfs | yes |\n| ~\u002F.cache | disk | NO |\n```\n\n---\n\n## Section 3: Error-path safety in shell\n\n### Suggested default — `set -euo pipefail` + `trap cleanup`\n\n```bash\n#!\u002Fbin\u002Fbash\nset -euo pipefail\n\ncleanup() {\n    # zeroize what we can; unmount tmpfs scratch\n    [ -n \"${SCRATCH_DIR:-}\" ] && [ -d \"$SCRATCH_DIR\" ] && \\\n        shred -u \"$SCRATCH_DIR\"\u002F*.tmp 2>\u002Fdev\u002Fnull\n    [ -n \"${SECRET_VAR:-}\" ] && unset SECRET_VAR\n    [ -n \"${MOUNTED:-}\" ] && umount \"$MOUNTED\" 2>\u002Fdev\u002Fnull\n}\ntrap cleanup ERR EXIT INT TERM\n\n# ... actual script logic\n```\n\nWithout `set -e`, a failing `openssl enc` or `cat` mid-pipeline can produce a partial-state file the next step processes (review M2). Without the `trap`, an interrupted script leaves secret-bearing scratch files intact.\n\n### Specific shell pitfalls\n\n| Issue | Mitigation |\n|---|---|\n| `set -e` doesn't fire inside `if`\u002F`while`\u002F`&&`\u002F`\\|\\|` chains | Test exit codes explicitly in those contexts: `if foo; then ...; else echo \"foo failed\"; exit 1; fi` |\n| `set -e` ignores failures in subshells unless caller checks | `(...)` returns the subshell's exit; check it |\n| `set -x` (debug) prints expanded variables — including secrets | Never enable `set -x` on secret-bearing code paths; use `set +x` defensively before secret handling |\n| `cat \u002Fetc\u002Fsecret \\| openssl enc ...` — pipe failures partial-write | `set -o pipefail` to make the pipeline return any failure |\n\n### Python \u002F Node \u002F Go equivalents\n\n| Language | Pattern |\n|---|---|\n| Python | `try\u002Ffinally` for cleanup; `subprocess.run(..., check=True)`; `secrets.compare_digest` for any constant-time compare |\n| Node | `try\u002Ffinally` + `process.on('exit', cleanup)`; pass-fd via `child_process.spawn(..., { stdio: [...] })` |\n| Go | `defer cleanup()`; `os\u002Fexec.Cmd.ExtraFiles` for fd passing |\n\n---\n\n## Section 4: Memory hygiene\n\n### When to bother\n\nIn long-running processes (daemons, sessions), wipe key material from memory after use:\n\n```c\n\u002F\u002F C with libsodium\nsodium_memzero(key, sizeof(key));\n```\n\n```python\n# Python — limited; CPython doesn't guarantee zeroization but bytearrays help\nimport ctypes\nkey = bytearray(32)\n# ... use key\nctypes.memset(ctypes.addressof(ctypes.c_char.from_buffer(key)), 0, len(key))\n```\n\nFor short-lived programs (one-shot scripts), explicit zeroization is mostly performative — the process exits, OS reclaims memory. The bigger concern is:\n\n- **Don't put secrets in long-lived globals**; make them function-local\n- **Don't pass secrets as positional command-line args** (`ps aux` reveals them)\n- **Don't log secrets, even to debug**\n\n### `mlock` to prevent swap\n\n```c\nmlock(key_buffer, key_size);\n\u002F\u002F ... use\nsodium_memzero(key_buffer, key_size);\nmunlock(key_buffer, key_size);\n```\n\n`mlock` prevents the page from being swapped to disk. Required when the process may run on a system with swap enabled. libsodium's `sodium_mlock`\u002F`sodium_munlock` is portable.\n\n---\n\n## Section 5: Identifier hygiene\n\n### Anti-pattern: serial numbers, IDs in metadata files (review L2)\n\n```json\n{\n  \"yk5_serial\": \"12345678\",\n  \"bio_serial\": \"87654321\"\n}\n```\n\nHardware serials in metadata expose:\n\n- Operator identity (serials are often issued to specific people)\n- Hardware revisions (different firmware = different attack surface)\n- Inventory enumeration (attacker who recovers `.meta.json` learns what other devices the operator carries)\n\n### Remediation: salted hash\n\n```python\nimport hashlib, secrets\nsalt = secrets.token_bytes(16)  # per-USB, stored alongside\nyk5_hash = hashlib.sha256(salt + yk5_serial.encode()).hexdigest()\n# .meta.json stores yk5_hash and salt; serial is never persisted\n```\n\nThe salt is stored *with* the metadata, so loss of the metadata file reveals nothing. The hash lets you verify \"is this the right key?\" without revealing which key.\n\n### Apply broadly\n\nThe pattern applies to:\n\n- Hardware serials (USB, YubiKey, smart card, TPM EK)\n- Email addresses, usernames in stored audit\n- IP addresses in long-term logs\n- Build host fingerprints in artifact manifests\n\nIf you need to identify-without-revealing, salted-hash. If you need to compare-without-revealing, HMAC with a per-context key.\n\n---\n\n## Section 6: LUKS \u002F full-disk header hygiene\n\n### Required pattern (review M5)\n\n```bash\n# Provisioning\ncryptsetup luksHeaderBackup \u002Fdev\u002FsdX --header-backup-file \u002Fsecure\u002Fheaders\u002FsdX.luksheader\ngpg --encrypt --recipient hq-pubkey \u002Fsecure\u002Fheaders\u002FsdX.luksheader\n# Store encrypted backup off-device\n\n# Recovery (when header is corrupted)\ncryptsetup luksHeaderRestore \u002Fdev\u002FsdX --header-backup-file \u002Fsecure\u002Fheaders\u002FsdX.luksheader\n```\n\nA single bad block in the LUKS header bricks the volume. Backups are non-negotiable for production. The backup is encrypted to an HQ key (not the operator's keys) so its loss doesn't compromise active sessions and its theft doesn't compromise active sessions either — restoration requires HQ.\n\n---\n\n## Section 7: Logging policy\n\n### Default\n\n- **Never log secret material**, even truncated, even in debug, even in error\n- **Log secret-bearing operation events** with public metadata only (who, when, where, success\u002Ffail — not what)\n- **Log retention**: secret-bearing systems should keep logs short and rotate aggressively; logs are themselves a secondary attack surface\n\n### Examples\n\n```bash\n# WRONG\nlog \"Attempting to decrypt with key prefix: ${KEY:0:4}...\"\n\n# RIGHT\nlog \"Decrypt attempt: kid=$(hash_id $KEY_NAME) result=$result\"\n```\n\nTruncated keys are still useful to attackers (rainbow table search space reduction; structural fingerprint).\n\n---\n\n## Section 8: Worked examples\n\n### Review M1 — `shred` on flash storage\n\nOriginal: scripts use `shred -u` to delete secret-bearing files on the USB drive.\n\nWhat this skill flags:\n\n- Section 2: `shred` on flash is worse than nothing\n- Section 2 anti-pattern: writing secrets to non-tmpfs scratch\n\nRemediation:\n\n- Verify `\u002Ftmp` is tmpfs at script start; refuse if not\n- Move all scratch to tmpfs; don't write secret-bearing files to USB at all\n- Document that the USB only holds encrypted-at-rest data, never plaintext\n\n### Review M2 — `set -e` not specified\n\nOriginal: scripts use `set -uo pipefail` but not `-e`.\n\nWhat this skill flags:\n\n- Section 3: missing `-e` allows silent partial-state errors\n\nRemediation:\n\n- Add `set -euo pipefail` to all secret-bearing scripts\n- Add `trap cleanup ERR EXIT INT TERM`\n\n### Review M3 — SECRETS_ENV aggregation\n\nOriginal: all credentials concatenated into one `SECRETS_ENV` variable.\n\nWhat this skill flags:\n\n- Section 1 anti-pattern: aggregation = single leak vector\n\nRemediation:\n\n- One fd per secret, OR one named pipe per secret in tmpfs with mode 600\n- Never `export` secret-bearing variables; keep them function-local\n\n### Review L2 — YubiKey serials in `.meta.json`\n\nWhat this skill flags:\n\n- Section 5 anti-pattern: identifier in metadata\n\nRemediation:\n\n- Hash with per-USB salt; store `(salt, hash)` not raw serial\n\n### Review M5 — no LUKS header backup\n\nWhat this skill flags:\n\n- Section 6 missing required pattern\n\nRemediation:\n\n- `cryptsetup luksHeaderBackup`, encrypt to HQ pubkey, store off-device, document recovery procedure, **test it**\n\n---\n\n## Section 9: Output format\n\nWhen invoked as part of a review, produce findings in standard format. When the system is mature, produce a `secret-handling-policy.md` document covering Sections 1–7 with project-specific decisions.\n\n---\n\n## Related\n\n- **At-rest companion**: `addons\u002Fsecurity\u002Fsecure-token-load.md` (token files, modes, heredoc patterns)\n- **Companion skill**: `degraded-mode-design` (cleanup hygiene fires from degraded-mode triggers)\n- **Companion skill**: `physical-threat-modeling` (cold-boot, DMA attacks against in-memory secrets)\n\n## Standards referenced\n\n- OWASP Cryptographic Storage Cheat Sheet\n- OWASP Secure Headers Project\n- NIST SP 800-88 — Media Sanitization (for the \"shred is not enough\" case)\n- libsodium memory-handling documentation\n- `man 7 random` — RNG and memory hygiene\n- LUKS On-Disk Format Specification\n",{"name":5,"namespace":45,"platforms":46,"description":7},"aiwg",[47],"all","main","6754967c344ccb087a8de06972f55a41067b291b","agentic\u002Fcode\u002Fframeworks\u002Fsecurity-engineering\u002Fskills\u002Fsecret-handling-runtime",36,"https:\u002F\u002Fgithub.com\u002Fjmagly\u002Faiwg","https:\u002F\u002Faiwg.io","https:\u002F\u002Fgithub.com\u002Fjmagly","https:\u002F\u002Fintegrolabs.io","ml\u002Fai, industrial automation, safety\u002Fsecurity, ssi\r\n\r\nmagly.net\r\nintegrolabs.io\r\naiwg.io\r\nfortemi.com\r\npagenary.com\r\nbytecask.com","2026-11-12 00:33:10",{"source":8,"skillName":5,"commands":59,"prompt":69,"zipAvailable":70},[60,63,66],{"tool":61,"command":62},"npx","npx skills add jmagly\u002Faiwg --skill secret-handling-runtime",{"tool":64,"command":65},"bunx","bunx skills add jmagly\u002Faiwg --skill secret-handling-runtime",{"tool":67,"command":68},"pnpm","pnpm dlx skills add jmagly\u002Faiwg --skill secret-handling-runtime","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fjmagly\u002Faiwg\u002Fblob\u002Fmain\u002Fagentic\u002Fcode\u002Fframeworks\u002Fsecurity-engineering\u002Fskills\u002Fsecret-handling-runtime\u002FSKILL.md",true,[],[73,100,124,130,137,151],{"skillNo":74,"name":75,"nameZh":6,"description":76,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":77,"categories":78,"keywords":80,"publishedAt":40},"01M70QA0BSYJ49EES9V4RE8QH1","host-hardening","Read-only system hardening baseline and plan-first host hardening recommendations for SSH, Docker bind addresses, firewall persistence, datastores, exporters and audit retention.","https:\u002F\u002Fgithub.com\u002Fjmagly\u002Faiwg\u002Fblob\u002Fmain\u002Fagentic\u002Fcode\u002Fplugins\u002Fsecurity-engineering\u002Fskills\u002Fhost-hardening\u002FSKILL.md",[79],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,30,99],"host","host hardening","hardening","read","re","recommendations","remediation","plan","port","verification","Bash","access","only","retention","audit","never","firewall","bind","request",{"skillNo":101,"name":102,"nameZh":6,"description":103,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":104,"categories":105,"keywords":110,"publishedAt":40},"01M70PNT2Z4HMZPRBTAF6073NN","exposure-analysis","Read-only firewall analysis and effective host exposure reconstruction for Docker published ports, UFW bypass, stale rules, CDN scope, IPv6 asymmetry and external probe allowlists.","https:\u002F\u002Fgithub.com\u002Fjmagly\u002Faiwg\u002Fblob\u002Fmain\u002Fagentic\u002Fcode\u002Fplugins\u002Fforensics\u002Fskills\u002Fexposure-analysis\u002FSKILL.md",[106,109],{"categoryNo":107,"categoryName":108,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000011","运维与部署",{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[111,112,113,84,114,89,115,116,91,117,81,118,93,119,120,96,97,121,122,123],"exposure analysis","analysis","unknown","credentials","collection","ufw","ports","internet","local","scope","reconstruction","operator","write",{"skillNo":125,"name":75,"nameZh":6,"description":76,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":126,"categories":127,"keywords":129,"publishedAt":40},"01M70P8QATMPKA78E4FT5D2TNA","https:\u002F\u002Fgithub.com\u002Fjmagly\u002Faiwg\u002Fblob\u002Fmain\u002Fagentic\u002Fcode\u002Fframeworks\u002Fsecurity-engineering\u002Fskills\u002Fhost-hardening\u002FSKILL.md",[128],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[83,81,82,92,84,99,87,86,95,88,89,90,91,30,93,94,85,96,97,98],{"skillNo":131,"name":102,"nameZh":6,"description":103,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":132,"categories":133,"keywords":136,"publishedAt":40},"01M70NMPVJ5H0JH131P60FW4R8","https:\u002F\u002Fgithub.com\u002Fjmagly\u002Faiwg\u002Fblob\u002Fmain\u002Fagentic\u002Fcode\u002Fframeworks\u002Fforensics-complete\u002Fskills\u002Fexposure-analysis\u002FSKILL.md",[134,135],{"categoryNo":107,"categoryName":108,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[112,111,96,114,89,115,91,113,81,118,93,119,120,84,97,117,116,121,122,123],{"skillNo":138,"name":139,"nameZh":6,"description":140,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":141,"categories":142,"keywords":144,"publishedAt":40},"01M6CB4DDR0B4NNCFK7ZK7449H","fixture-skill","Exercise rule delivery.","https:\u002F\u002Fgithub.com\u002Fjmagly\u002Faiwg\u002Fblob\u002Fmain\u002Ftest\u002Ffixtures\u002Frule-delivery\u002Fskills\u002Ffixture-skill\u002FSKILL.md",[143],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[145,146,147,148,149,150],"fixture skill","fixture","rule","delivery","true","exercise",{"skillNo":152,"name":153,"nameZh":6,"description":154,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":155,"categories":156,"keywords":158,"publishedAt":40},"01M6CB4AVK1W76MFMPP0JP32CJ","pi-local","Pi-local duplicate-precedence fixture.","https:\u002F\u002Fgithub.com\u002Fjmagly\u002Faiwg\u002Fblob\u002Fmain\u002Ftest\u002Ffixtures\u002Fproviders\u002Fpi\u002Fproject\u002F.pi\u002Fskills\u002Fpi-local\u002FSKILL.md",[157],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},[159,160,119,161,146],"pi","pi local","duplicate"]