mirror of
https://github.com/trailofbits/skills
synced 2026-06-21 14:12:00 +00:00
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:
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user