fix: cap yubi candidates, pass password via stdin, explicit dir permissions

ober

4207ac84b52f6020e0995f55e7f9c10dc35381ec

diff --git a/recorder.ss b/recorder.ss
index 3b7ad1e..35bf062 100644
--- a/recorder.ss
+++ b/recorder.ss
@@ -105,10 +105,18 @@
 (def (ensure-jsh-logs-dir!)
   (let ((dir (string-append (or (getenv "HOME" #f) "/tmp") "/.jsh/logs")))
     (unless (file-exists? dir)
-      (let ((parent (string-append (or (getenv "HOME" #f) "/tmp") "/.jsh")))
-        (unless (file-exists? parent)
-          (with-catch (lambda (e) #f) (lambda () (mkdir parent)))))
-      (with-catch (lambda (e) #f) (lambda () (mkdir dir))))
+      (let ((parent (string-append (or (getenv "HOME" #f) "/tmp") "/.jsh"))
+            ;; Recordings are sensitive: create ~/.jsh and ~/.jsh/logs with
+            ;; mode 0700 regardless of the user's umask. Temporarily set
+            ;; umask 077 so mkdir yields 0700 at creation, then restore it.
+            (saved-mask (ffi-umask #o077)))
+        (dynamic-wind
+          (lambda () #f)
+          (lambda ()
+            (unless (file-exists? parent)
+              (with-catch (lambda (e) #f) (lambda () (mkdir parent))))
+            (with-catch (lambda (e) #f) (lambda () (mkdir dir))))
+          (lambda () (ffi-umask saved-mask)))))
     dir))
 
 ;; Generate a default recording filename (encrypted .cast.enc).
diff --git a/yubi.ss b/yubi.ss
index 1b3f1e4..f8240fe 100644
--- a/yubi.ss
+++ b/yubi.ss
@@ -3,6 +3,7 @@
 
 (let* ([account-query "gitlab.tgsre.net"]
        [attempt-timeout-seconds 6]
+       [max-candidates 10000]
        [touch-markers '("touch" "press" "contact")]
        [setup-error-markers
         '("command not found"
@@ -32,16 +33,17 @@
         [else
          (loop (cdr chars) (cons (make-string 1 (car chars)) parts))])))
 
-  (define (ykman-command candidate)
+  (define (ykman-command)
     (string-append
       "exec sh -c "
       (shell-quote
         (string-append
+          "IFS= read -r yubi_pass || exit 1; "
           "if ! command -v ykman >/dev/null 2>&1; then "
           "echo 'ykman: command not found' >&2; exit 127; fi; "
           "tmp=$(mktemp \"${TMPDIR:-/tmp}/yubi.XXXXXX\") || exit 1; "
           "trap 'rm -f \"$tmp\"' EXIT HUP INT TERM; "
-          "ykman oath accounts code -p \"$2\" \"$3\" >\"$tmp\" 2>&1 & "
+          "ykman oath accounts code -p \"$yubi_pass\" \"$2\" >\"$tmp\" 2>&1 & "
           "child=$!; "
           "elapsed=0; "
           "while kill -0 \"$child\" 2>/dev/null; do "
@@ -59,18 +61,20 @@
       " sh "
       (shell-quote (number->string attempt-timeout-seconds))
       " "
-      (shell-quote candidate)
-      " "
       (shell-quote account-query)))
 
   (define (try-candidate candidate)
     (call-with-values
       (lambda ()
         (open-process-ports
-          (ykman-command candidate)
+          (ykman-command)
           (buffer-mode block)
           (make-transcoder (utf-8-codec))))
       (lambda (to-in from-out from-err pid)
+        ;; Pass the candidate password via stdin (not argv) so it never
+        ;; appears in the process table.
+        (display candidate to-in)
+        (newline to-in)
         (close-port to-in)
         (let* ([out (get-string-all from-out)]
                [err (get-string-all from-err)])
@@ -115,13 +119,13 @@
     (let* ([positions (alphabetic-positions password)]
            [n (length positions)])
       (let loop ([k 0] [acc '()])
-        (if (> k n)
+        (if (or (> k n) (>= (length acc) max-candidates))
             (reverse acc)
-            (loop (+ k 1)
-                  (append
-                    (map (lambda (combo) (flip-positions password combo))
-                         (combinations-of positions k))
-                    acc))))))
+            (let* ([new (map (lambda (combo) (flip-positions password combo))
+                             (combinations-of positions k))]
+                   [room (- max-candidates (length acc))]
+                   [batch (if (> (length new) room) (take new room) new)])
+              (loop (+ k 1) (append batch acc)))))))
 
   (define (read-secret prompt)
     (display prompt)