Use secrets module instead of manual os.urandom() in let-fate-decide

Replace the hand-rolled secure_randbelow() rejection sampling with
secrets.randbelow(), which provides the same guarantees. Switch the
coin flip from os.urandom(1) to secrets.randbits(1).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Scott Arciszewski
2026-03-18 10:53:42 -04:00
parent 5c15f4f564
commit 0f8f29ad21
3 changed files with 10 additions and 31 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
# let-fate-decide
A Claude Code skill that draws Tarot cards using `os.urandom()` to inject
A Claude Code skill that draws Tarot cards using `secrets` to inject
entropy into vague or underspecified planning decisions.
## What It Does
@@ -21,7 +21,7 @@ spread and uses the reading to inform its approach.
## How It Works
1. A Python script uses `os.urandom()` to perform a Fisher-Yates shuffle
1. A Python script uses `secrets` to perform a Fisher-Yates shuffle
2. 4 cards are drawn from the top of the shuffled deck
3. Each card has an independent 50% chance of being reversed
4. Claude reads the drawn cards' meaning files and interprets the spread
@@ -1,6 +1,6 @@
---
name: let-fate-decide
description: "Draws 4 Tarot cards using os.urandom() to inject entropy into planning when prompts are vague or underspecified. Interprets the spread to guide next steps. Use when the user is nonchalant, feeling lucky, says 'let fate decide', makes Yu-Gi-Oh references ('heart of the cards'), demonstrates indifference about approach, or says 'try again' on a system with no changes. Also triggers on sufficiently ambiguous prompts where multiple approaches are equally valid."
description: "Draws 4 Tarot cards using secrets to inject entropy into planning when prompts are vague or underspecified. Interprets the spread to guide next steps. Use when the user is nonchalant, feeling lucky, says 'let fate decide', makes Yu-Gi-Oh references ('heart of the cards'), demonstrates indifference about approach, or says 'try again' on a system with no changes. Also triggers on sufficiently ambiguous prompts where multiple approaches are equally valid."
allowed-tools:
- Bash
- Read
@@ -48,7 +48,7 @@ When the path forward is unclear, let the cards speak.
### The Draw
The script uses `os.urandom()` for cryptographic randomness:
The script uses `secrets` for cryptographic randomness:
1. Builds a standard 78-card Tarot deck (22 Major Arcana + 56 Minor Arcana)
2. Performs a Fisher-Yates shuffle using rejection sampling (no modulo bias)
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Draw Tarot cards using os.urandom() for cryptographic randomness.
"""Draw Tarot cards using the secrets module for cryptographic randomness.
Shuffles a full 78-card deck via Fisher-Yates and draws 4 from the top.
Each card has an independent 50/50 chance of being reversed.
@@ -10,7 +10,7 @@ Each card has an independent 50/50 chance of being reversed.
# ///
import json
import os
import secrets
import sys
MAJOR_ARCANA = [
@@ -67,38 +67,17 @@ def build_deck():
return deck
def secure_randbelow(n):
"""Return a cryptographically random integer in [0, n).
Uses os.urandom() with rejection sampling to avoid modulo bias.
"""
if n <= 0:
raise ValueError("n must be positive")
if n == 1:
return 0
# Number of bytes needed to cover range
k = (n - 1).bit_length()
num_bytes = (k + 7) // 8
# Rejection sampling: discard values >= n to avoid bias
while True:
raw = int.from_bytes(os.urandom(num_bytes), "big")
# Mask to k bits to reduce rejection rate
raw = raw & ((1 << k) - 1)
if raw < n:
return raw
def fisher_yates_shuffle(deck):
"""Shuffle deck in-place using Fisher-Yates with os.urandom()."""
"""Shuffle deck in-place using Fisher-Yates with secrets.randbelow()."""
for i in range(len(deck) - 1, 0, -1):
j = secure_randbelow(i + 1)
j = secrets.randbelow(i + 1)
deck[i], deck[j] = deck[j], deck[i]
return deck
def is_reversed():
"""Return True with 50% probability using os.urandom()."""
return os.urandom(1)[0] & 1 == 1
"""Return True with 50% probability using secrets.randbits()."""
return secrets.randbits(1) == 1
def draw(n=4):