transcript-viewer 0.7.0__tar.gz → 0.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/PKG-INFO +1 -1
  2. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/pyproject.toml +1 -1
  3. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/src/transcript_viewer/page.html +26 -4
  4. transcript_viewer-0.7.1/tests/fixtures/big-command.json +1 -0
  5. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/page.test.js +51 -0
  6. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/uv.lock +1 -1
  7. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/.coverage +0 -0
  8. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/.github/workflows/publish.yml +0 -0
  9. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/.github/workflows/test.yml +0 -0
  10. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/.gitignore +0 -0
  11. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/README.md +0 -0
  12. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/src/transcript_viewer/__init__.py +0 -0
  13. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/src/transcript_viewer/ai.py +0 -0
  14. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/src/transcript_viewer/cli.py +0 -0
  15. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/src/transcript_viewer/config.py +0 -0
  16. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/src/transcript_viewer/corpus.py +0 -0
  17. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/src/transcript_viewer/fetch.py +0 -0
  18. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/src/transcript_viewer/library.py +0 -0
  19. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/src/transcript_viewer/store.py +0 -0
  20. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/src/transcript_viewer/viewer.py +0 -0
  21. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/test_ai.py +0 -0
  22. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/test_cli.py +0 -0
  23. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/test_config.py +0 -0
  24. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/test_corpus.py +0 -0
  25. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/test_fetch.py +0 -0
  26. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/test_library.py +0 -0
  27. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/test_page.py +0 -0
  28. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/test_readme.py +0 -0
  29. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/test_store.py +0 -0
  30. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/test_stream.py +0 -0
  31. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/test_style.py +0 -0
  32. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/test_tidiness.py +0 -0
  33. {transcript_viewer-0.7.0 → transcript_viewer-0.7.1}/tests/test_viewer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: transcript-viewer
3
- Version: 0.7.0
3
+ Version: 0.7.1
4
4
  Summary: Browse agent transcripts in a local, dependency-free web viewer.
5
5
  License: MIT
6
6
  Requires-Python: >=3.12
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "transcript-viewer"
3
- version = "0.7.0"
3
+ version = "0.7.1"
4
4
  description = "Browse agent transcripts in a local, dependency-free web viewer."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -413,10 +413,13 @@ pre{background:var(--sunk);border-radius:7px;padding:10px;overflow:auto;font-siz
413
413
  .j-key{color:var(--j-key)}
414
414
  /* A command reads as a command: one gutter mark, then the script as written. */
415
415
  .sh{background:var(--bg);border-radius:7px;padding:10px 12px 10px 26px;margin:0;
416
- position:relative;overflow-x:auto;white-space:pre;font:12px/1.6 var(--font-mono);
417
- color:var(--ink)}
416
+ position:relative;font:12px/1.6 var(--font-mono);color:var(--ink);
417
+ /* Wrapped rather than scrolled: a command is read, and a line that runs off
418
+ the side is a line nobody reads. A continued line is indented so it stays
419
+ obvious which lines the shell would actually see as separate. */
420
+ white-space:pre-wrap;overflow-wrap:anywhere;text-indent:-1.4em;padding-left:calc(26px + 1.4em)}
418
421
  .sh::before{content:"$";position:absolute;left:11px;top:10px;color:var(--faint);
419
- user-select:none}
422
+ user-select:none;text-indent:0}
420
423
  .sh-cmd{color:var(--sh-cmd)}
421
424
  .sh-str{color:var(--sh-str)}
422
425
  .sh-flag{color:var(--sh-flag)}
@@ -834,8 +837,22 @@ function sh(src) {
834
837
  }
835
838
 
836
839
  if (ch === "$") {
837
- const m = /^\$(?:\{[^}]*\}|[A-Za-z_]\w*|[@*?#$!0-9])/.exec(s.slice(i));
840
+ // $(…) is a command substitution: mark the opening and let what is inside
841
+ // be scanned as the command it is.
842
+ if (s[i + 1] === "(") {
843
+ put("sh-var", "$(");
844
+ i += 2;
845
+ fresh = true;
846
+ continue;
847
+ }
848
+ const m = /^\$(?:\{[^}]*\}|[A-Za-z_]\w*|[@*?#$!0-9])/.exec(s.slice(i, i + 200));
838
849
  if (m) { put("sh-var", m[0]); i += m[0].length; fresh = false; continue; }
850
+ // A lone $ is just a character. Said explicitly, because falling through
851
+ // to the word reader below leaves it unable to move.
852
+ put("", "$");
853
+ i++;
854
+ fresh = false;
855
+ continue;
839
856
  }
840
857
 
841
858
  if (SH_OPS.includes(ch)) {
@@ -852,6 +869,11 @@ function sh(src) {
852
869
 
853
870
  let j = i;
854
871
  while (j < s.length && !" \t\n'\"$".includes(s[j]) && !SH_OPS.includes(s[j])) j++;
872
+ // Every character this reaches must be consumed by something. If a branch
873
+ // above ever stops matching a character it used to, this keeps the loop
874
+ // moving instead of hanging the page — which is what it did, on any command
875
+ // containing $( .
876
+ if (j === i) j = i + 1;
855
877
  const word = s.slice(i, j);
856
878
  put(fresh ? "sh-cmd" : word[0] === "-" ? "sh-flag" : "", word);
857
879
  i = j;
@@ -0,0 +1 @@
1
+ "cat > /Users/girish/Documents/personalwebsite/website/public/check-my-mac.sh <<'SCRIPT_EOF'\n#!/bin/bash\n#\n# check-my-mac.sh \u2014 check the parts of a personal security setup that live on\n# your Mac, and print a report you can paste back into the checklist at\n# https://girishgupta.com/beyond-the-parrot\n#\n# bash check-my-mac.sh readable summary\n# bash check-my-mac.sh --json JSON, for pasting into the checklist\n#\n# WHAT IT DOES AND DOESN'T DO\n# It reads settings and reports them. That's all. It changes nothing,\n# installs nothing, needs no sudo, and sends nothing anywhere \u2014 there is no\n# network call in this file, and you can check that yourself.\n#\n# It can see whether FileVault is on. It cannot see whether you froze your\n# credit, enrolled a hardware key, or turned on a port-out PIN. Those are\n# the items that matter most, and they stay manual on the checklist.\n#\n# Checks that would need root report \"unknown\" rather than asking for your\n# password. Nothing here should ever prompt for it. If something does, stop\n# and work out why.\n#\n# Exit status: 0 if nothing failed, 1 if at least one check failed.\n\nset -uo pipefail\n\nJSON_ONLY=0\n[[ \"${1:-}\" == \"--json\" ]] && JSON_ONLY=1\n\nPASS=0\nFAIL=0\nUNKNOWN=0\nROWS=()\n\nrecord() {\n local key=\"$1\" status=\"$2\" detail=\"$3\"\n case \"$status\" in\n pass) PASS=$((PASS + 1)) ;;\n fail) FAIL=$((FAIL + 1)) ;;\n *) UNKNOWN=$((UNKNOWN + 1)); status=\"unknown\" ;;\n esac\n local esc=${detail//\\\\/\\\\\\\\}\n esc=${esc//\\\"/\\\\\\\"}\n esc=$(printf '%s' \"$esc\" | tr -d '\\000-\\037')\n ROWS+=(\" \\\"$key\\\": { \\\"status\\\": \\\"$status\\\", \\\"detail\\\": \\\"$esc\\\" }\")\n if [[ $JSON_ONLY -eq 0 ]]; then\n local mark\n case \"$status\" in\n pass) mark=\" ok \" ;;\n fail) mark=\"FAIL\" ;;\n unknown) mark=\" ? \" ;;\n esac\n printf '[%s] %-18s %s\\n' \"$mark\" \"$key\" \"$detail\"\n fi\n}\n\nhave() { command -v \"$1\" >/dev/null 2>&1; }\n\nNOW=$(date +%s)\n\n# Days between a unix timestamp and now. Prints nothing if given nothing.\ndays_since() {\n local then=\"${1:-}\"\n [[ -n \"$then\" && \"$then\" =~ ^[0-9]+$ ]] || return 1\n echo $(( (NOW - then) / 86400 ))\n}\n\nif [[ \"$(uname -s)\" != \"Darwin\" ]]; then\n echo \"This one only knows how to check macOS. Detected: $(uname -s)\" >&2\n exit 2\nfi\n\n[[ $JSON_ONLY -eq 0 ]] && {\n echo\n echo \"Checking $(sw_vers -productName) $(sw_vers -productVersion) on $(scutil --get ComputerName 2>/dev/null || hostname -s)\"\n echo\n}\n\n# \u2500\u2500 Disk encryption \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Without this, your laptop is a filing cabinet with the key taped to it.\nfv=\"$(fdesetup status 2>/dev/null)\"\nif grep -qi \"FileVault is On\" <<<\"$fv\"; then\n record \"encryption\" pass \"FileVault is on\"\nelif [[ -n \"$fv\" ]]; then\n record \"encryption\" fail \"$fv \u2014 turn it on in System Settings > Privacy & Security\"\nelse\n record \"encryption\" unknown \"could not read FileVault status\"\nfi\n\n# \u2500\u2500 Screen lock \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Two settings combine: how long until the screen sleeps, and the grace\n# period before a password is demanded. A short screensaver with a ten\n# minute grace period is not a lock.\nidle=$(defaults -currentHost read com.apple.screensaver idleTime 2>/dev/null)\nlock_raw=$(sysadminctl -screenLock status 2>&1 | tail -1)\ngrace=\"\"\ncase \"$lock_raw\" in\n *immediate*) grace=0 ;;\n *\"is off\"*) grace=off ;;\n *[0-9]*seconds*) grace=$(grep -oE \"[0-9]+\" <<<\"$lock_raw\" | head -1) ;;\nesac\nif [[ \"$grace\" == \"off\" ]]; then\n record \"screenlock\" fail \"no password is required after the screen sleeps\"\nelif [[ -n \"$idle\" && \"$idle\" =~ ^[0-9]+$ && \"$idle\" -eq 0 ]]; then\n record \"screenlock\" fail \"the screen never sleeps on its own, so it never locks\"\nelif [[ -n \"$grace\" && -n \"$idle\" && \"$idle\" =~ ^[0-9]+$ ]]; then\n total=$((idle + grace))\n if [[ \"$total\" -le 300 ]]; then\n record \"screenlock\" pass \"locks after ${total}s (${idle}s idle + ${grace}s grace)\"\n else\n record \"screenlock\" fail \"takes ${total}s to lock \u2014 aim for five minutes or less\"\n fi\nelse\n record \"screenlock\" unknown \"could not read the screen lock settings\"\nfi\n\n# \u2500\u2500 Automatic login \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# macOS can be set to log someone in at boot without asking for anything,\n# which turns a stolen laptop into an open laptop. FileVault mostly\n# suppresses it, so this matters most on machines without FileVault.\nautologin=$(defaults read /Library/Preferences/com.apple.loginwindow autoLoginUser 2>/dev/null)\nif [[ -n \"$autologin\" ]]; then\n record \"auto-login\" fail \"this Mac logs in as '$autologin' at boot without a password\"\nelse\n record \"auto-login\" pass \"no automatic login at boot\"\nfi\n\n# \u2500\u2500 Automatic updates \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Read the whole domain: individual keys are often absent, which means\n# \"left at the default\" (on), not \"off\". Then check the setting is actually\n# working \u2014 \"updates are enabled\" and \"updates are happening\" are different\n# claims, and only the second one keeps you patched.\nSU=/Library/Preferences/com.apple.SoftwareUpdate\nsu_dump=$(defaults read \"$SU\" 2>/dev/null)\nsu_key() { grep -oE \"^ *$1 = [0-9]+\" <<<\"$su_dump\" | grep -oE \"[0-9]+$\"; }\nif [[ -z \"$su_dump\" ]]; then\n record \"updates\" unknown \"could not read the software update settings\"\nelse\n auto_check=$(su_key AutomaticCheckEnabled)\n crit=$(su_key CriticalUpdateInstall)\n os_auto=$(su_key AutomaticallyInstallMacOSUpdates)\n\n # Last time a check actually succeeded, as recorded by macOS itself.\n last_su_raw=$(grep -oE 'LastSuccessfulDate = \"[^\"]+\"' <<<\"$su_dump\" | head -1 |\n sed -E 's/.*\"([0-9-]+ [0-9:]+).*/\\1/')\n last_su_epoch=\"\"\n [[ -n \"$last_su_raw\" ]] &&\n last_su_epoch=$(date -j -u -f \"%Y-%m-%d %H:%M:%S\" \"$last_su_raw\" +%s 2>/dev/null)\n su_age=$(days_since \"$last_su_epoch\") || su_age=\"\"\n\n if [[ \"${auto_check:-1}\" == \"0\" ]]; then\n record \"updates\" fail \"automatic update checks are turned off\"\n elif [[ \"${crit:-1}\" == \"0\" ]]; then\n record \"updates\" fail \"security responses are not installed automatically\"\n elif [[ -n \"$su_age\" && \"$su_age\" -gt 30 ]]; then\n record \"updates\" fail \"automatic updates are on, but the last successful check was ${su_age} days ago\"\n elif [[ -n \"$su_age\" ]]; then\n record \"updates\" pass \"automatic updates on and checking (last successful check ${su_age}d ago)\"\n else\n record \"updates\" pass \"automatic updates on (security=${crit:-default}, macOS=${os_auto:-default})\"\n fi\nfi\n\n# \u2500\u2500 Supported macOS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Apple ships security fixes for the current release and the two before it.\n# That list changes every autumn, so it lives here with a date attached: if\n# this script has gone stale the check says so instead of guessing at you.\nOS_TABLE_DATE=\"2026-08-21\"\nSUPPORTED_MAJORS=\"26 25 15 14\"\ntable_epoch=$(date -j -f \"%Y-%m-%d\" \"$OS_TABLE_DATE\" +%s 2>/dev/null)\ntable_age=$(days_since \"$table_epoch\") || table_age=\"\"\nmajor=$(sw_vers -productVersion | cut -d. -f1)\nif [[ -n \"$table_age\" && \"$table_age\" -gt 400 ]]; then\n record \"supported-os\" unknown \"this script's list of supported macOS versions is ${table_age} days old \u2014 too stale to judge macOS $(sw_vers -productVersion)\"\nelif [[ \" $SUPPORTED_MAJORS \" == *\" $major \"* ]]; then\n record \"supported-os\" pass \"macOS $(sw_vers -productVersion) still gets security updates\"\nelse\n record \"supported-os\" fail \"macOS $(sw_vers -productVersion) is past its last security update\"\nfi\n\n# \u2500\u2500 System Integrity Protection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# SIP stops even root from modifying system files or injecting into Apple's\n# own processes. It is on by default, and turning it off takes a deliberate\n# trip to Recovery \u2014 which people make for some dev tool and then forget.\nsip_raw=$(csrutil status 2>/dev/null)\nif grep -qi \"status: enabled\" <<<\"$sip_raw\"; then\n record \"sip\" pass \"System Integrity Protection is on\"\nelif grep -qi \"status: disabled\" <<<\"$sip_raw\"; then\n record \"sip\" fail \"System Integrity Protection is off \u2014 re-enable it from Recovery unless you know why it's off\"\nelif [[ -n \"$sip_raw\" ]]; then\n record \"sip\" fail \"System Integrity Protection is partly disabled: $(tr -d '\\n' <<<\"$sip_raw\" | cut -c1-90)\"\nelse\n record \"sip\" unknown \"could not read System Integrity Protection status\"\nfi\n\n# \u2500\u2500 Secure Boot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Apple Silicon only. Full Security means the Mac will only boot a signed,\n# current OS. Reduced Security is what you get after enabling third-party\n# kernel extensions, and it is often deliberate \u2014 but it should be a choice\n# you remember making rather than one you find out about here.\nbridge=$(system_profiler SPiBridgeDataType 2>/dev/null)\nsb=$(grep -oE \"Secure Boot: .*\" <<<\"$bridge\" | head -1 | sed 's/Secure Boot: //')\nkext=$(grep -oE \"Allow All Kernel Extensions: .*\" <<<\"$bridge\" | head -1 | sed 's/.*: //')\nif [[ -z \"$bridge\" ]]; then\n record \"secure-boot\" unknown \"no boot policy reported \u2014 Intel Macs don't have this\"\nelif [[ \"$sb\" == \"Full Security\" ]]; then\n record \"secure-boot\" pass \"Secure Boot is at Full Security\"\nelif [[ -n \"$sb\" ]]; then\n record \"secure-boot\" fail \"Secure Boot is at ${sb}${kext:+, third-party kernel extensions allowed: $kext}\"\nelse\n record \"secure-boot\" unknown \"could not read the boot policy\"\nfi\n\n# \u2500\u2500 Firewall \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nFW=/usr/libexec/ApplicationFirewall/socketfilterfw\nif [[ -x \"$FW\" ]]; then\n if \"$FW\" --getglobalstate 2>/dev/null | grep -qi \"enabled\"; then\n stealth=\"\"\n \"$FW\" --getstealthmode 2>/dev/null | grep -qi \"on\" && stealth=\", stealth mode on\"\n record \"firewall\" pass \"the built-in firewall is on$stealth\"\n else\n record \"firewall\" fail \"the firewall is off \u2014 System Settings > Network > Firewall\"\n fi\nelse\n record \"firewall\" unknown \"could not find socketfilterfw\"\nfi\n\n# \u2500\u2500 Gatekeeper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nif spctl --status 2>&1 | grep -qi \"assessments enabled\"; then\n xp=$(defaults read /Library/Apple/System/Library/CoreServices/XProtect.bundle/Contents/Info.plist \\\n CFBundleShortVersionString 2>/dev/null)\n record \"gatekeeper\" pass \"Gatekeeper is on${xp:+, XProtect $xp}\"\nelse\n record \"gatekeeper\" fail \"Gatekeeper is disabled \u2014 it is the thing that stops you double-clicking malware\"\nfi\n\n# \u2500\u2500 Services listening to the network \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Anything bound to 127.0.0.1 is talking to your own machine and is nobody\n# else's business; development tools do it constantly. What matters is a\n# service bound to every interface, because that is the one that answers\n# strangers when you join caf\u00e9 wifi.\n#\n# This uses netstat rather than lsof deliberately. lsof run as you only\n# reports your own sockets, and sshd runs as root \u2014 so an lsof check would\n# cheerfully report a clean bill of health on a Mac with Remote Login on.\nlisteners=$(netstat -an -p tcp 2>/dev/null | awk '/LISTEN/ {print $4}')\nif [[ -z \"$listeners\" ]]; then\n record \"sharing\" unknown \"could not read the list of listening sockets\"\nelse\n # Strip loopback, then reduce each address to its port number.\n public_ports=$(grep -vE '^(127\\.0\\.0\\.1|::1|\\*\\.0$)' <<<\"$listeners\" |\n sed -E 's/.*[.:]([0-9]+)$/\\1/' | sort -un)\n named=\"\"\n for p in $public_ports; do\n case \"$p\" in\n 22) named=\"${named:+$named, }Remote Login (SSH)\" ;;\n 445) named=\"${named:+$named, }File Sharing (SMB)\" ;;\n 548) named=\"${named:+$named, }File Sharing (AFP)\" ;;\n 631) named=\"${named:+$named, }Printer Sharing\" ;;\n 3283) named=\"${named:+$named, }Remote Management\" ;;\n 5900) named=\"${named:+$named, }Screen Sharing\" ;;\n 5000|7000) named=\"${named:+$named, }AirPlay Receiver\" ;;\n esac\n done\n others=$(grep -cvE '^(22|445|548|631|3283|5900|5000|7000)$' <<<\"$public_ports\")\n if [[ -n \"$named\" ]]; then\n record \"sharing\" fail \"reachable from the network: $named \u2014 turn off what you don't use in System Settings > General > Sharing\"\n elif [[ \"$others\" -gt 0 ]]; then\n record \"sharing\" unknown \"$others service(s) listening on all interfaces, none of them a standard sharing service: $(tr '\\n' ' ' <<<\"$public_ports\")\"\n else\n record \"sharing\" pass \"nothing is listening to the network beyond your own machine\"\n fi\nfi\n\n# \u2500\u2500 Guest account \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nguest=$(defaults read /Library/Preferences/com.apple.loginwindow GuestEnabled 2>/dev/null)\nif [[ \"${guest:-0}\" == \"1\" ]]; then\n record \"guest-account\" fail \"the guest account is enabled\"\nelse\n record \"guest-account\" pass \"the guest account is off\"\nfi\n\n# \u2500\u2500 Find My \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# The thing that lets you wipe a laptop you are never getting back. Note\n# that the findmydeviced process runs whether or not the feature is on, so\n# it proves nothing; these two signals do.\nfmm=$(defaults read /Library/Preferences/com.apple.FindMyMac FMMEnabled 2>/dev/null)\nfmm_token=$(nvram -p 2>/dev/null | grep -c 'fmm-mobileme-token-FMM')\nif [[ \"${fmm:-0}\" == \"1\" || \"${fmm_token:-0}\" -gt 0 ]]; then\n record \"find-my\" pass \"Find My Mac is on \u2014 a lost machine can be wiped remotely\"\nelif [[ -n \"$fmm\" || \"${fmm_token:-0}\" == \"0\" ]]; then\n record \"find-my\" fail \"Find My Mac is off \u2014 System Settings > Apple Account > iCloud > Find My Mac\"\nelse\n record \"find-my\" unknown \"could not read Find My status\"\nfi\n\n# \u2500\u2500 SSH keys \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Only relevant if you have them, and if you do, an unencrypted private key\n# is a password sitting in a file. ssh-keygen with an empty passphrase\n# succeeds only on a key that has none, and never prompts.\nif [[ -d \"$HOME/.ssh\" ]]; then\n dir_mode=$(stat -f \"%A\" \"$HOME/.ssh\" 2>/dev/null)\n bare=() loose=()\n for k in \"$HOME\"/.ssh/id_* \"$HOME\"/.ssh/*.pem; do\n [[ -f \"$k\" ]] || continue\n [[ \"$k\" == *.pub ]] && continue\n ssh-keygen -y -P \"\" -f \"$k\" >/dev/null 2>&1 </dev/null && bare+=(\"$(basename \"$k\")\")\n mode=$(stat -f \"%A\" \"$k\" 2>/dev/null)\n [[ -n \"$mode\" && \"$mode\" != \"600\" && \"$mode\" != \"400\" ]] && loose+=(\"$(basename \"$k\") is $mode\")\n done\n problems=\"\"\n [[ ${#bare[@]} -gt 0 ]] && problems=\"no passphrase: ${bare[*]}\"\n [[ ${#loose[@]} -gt 0 ]] && problems=\"${problems:+$problems; }readable by others: ${loose[*]}\"\n [[ -n \"$dir_mode\" && \"$dir_mode\" != \"700\" ]] && problems=\"${problems:+$problems; }~/.ssh is $dir_mode, should be 700\"\n if [[ -n \"$problems\" ]]; then\n record \"ssh-keys\" fail \"$problems\"\n else\n record \"ssh-keys\" pass \"SSH keys and permissions look right\"\n fi\nelse\n record \"ssh-keys\" pass \"no ~/.ssh directory, so nothing to get wrong\"\nfi\n\n# \u2500\u2500 Password manager \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\npm=\"\"\nfor app in \"1Password\" \"Bitwarden\" \"Proton Pass\" \"Dashlane\"; do\n [[ -d \"/Applications/$app.app\" ]] && { pm=\"$app\"; break; }\ndone\nif [[ -n \"$pm\" ]]; then\n record \"password-manager\" pass \"$pm is installed\"\nelif [[ -d \"/System/Applications/Passwords.app\" ]]; then\n record \"password-manager\" unknown \"no third-party manager found; Apple Passwords is available but this script can't tell whether you use it\"\nelse\n record \"password-manager\" fail \"no password manager found in /Applications\"\nfi\n\n# \u2500\u2500 Backups \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# A backup you have never restored from is a hypothesis, not a backup \u2014 and\n# an app in /Applications is not even that. What matters is whether\n# something ran recently, so where a tool records its own history in a file\n# you can read, this reads it. Where it doesn't, it says so rather than\n# awarding a pass for owning the software.\nFRESH_DAYS=7\nbest_age=\"\" # smallest number of days since a verified backup\nverified=\"\" # tools with a real, dated last run\npresent=\"\" # tools installed but unverifiable from here\n\nnote_verified() {\n local name=\"$1\" age=\"$2\"\n verified=\"${verified:+$verified; }$name (${age}d ago)\"\n [[ -z \"$best_age\" || \"$age\" -lt \"$best_age\" ]] && best_age=\"$age\"\n}\n\n# Time Machine. A configured destination is not the point; a recent snapshot\n# is. Snapshot paths end in YYYY-MM-DD-HHMMSS.\nif have tmutil && tmutil destinationinfo >/dev/null 2>&1; then\n latest=$(tmutil latestbackup 2>/dev/null | tail -1)\n stamp=$(grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}' <<<\"$latest\" | tail -1)\n if [[ -n \"$stamp\" ]]; then\n tm_epoch=$(date -j -f \"%Y-%m-%d-%H%M%S\" \"$stamp\" +%s 2>/dev/null)\n tm_age=$(days_since \"$tm_epoch\") && note_verified \"Time Machine\" \"$tm_age\"\n else\n present=\"${present:+$present, }Time Machine (destination set, but no snapshot this script can date)\"\n fi\nfi\n\n# Arq. The agent keeps a world-readable log and writes a line on every\n# completed backup record, which is a far better signal than the app existing.\nif [[ -d /Applications/Arq.app ]]; then\n arq_log=$(ls -t /Library/Logs/ArqAgent/*.log 2>/dev/null | head -1)\n arq_done=\"\"\n if [[ -n \"$arq_log\" && -r \"$arq_log\" ]]; then\n arq_done=$(tail -c 2000000 \"$arq_log\" 2>/dev/null |\n grep -oE '[0-9]{2}-[A-Za-z]{3}-[0-9]{4} [0-9]{2}:[0-9]{2}:[0-9]{2}' | tail -1)\n fi\n if [[ -n \"$arq_done\" ]]; then\n arq_epoch=$(date -j -f \"%d-%b-%Y %H:%M:%S\" \"$arq_done\" +%s 2>/dev/null)\n arq_age=$(days_since \"$arq_epoch\") && note_verified \"Arq\" \"$arq_age\"\n else\n present=\"${present:+$present, }Arq (installed, no readable backup record)\"\n fi\nfi\n\n# Everything else: detectable, but without a log this script can honestly\n# read, so they count as \"something is here\" rather than \"you are covered\".\nfor app in \"Backblaze\" \"Carbon Copy Cloner\" \"SuperDuper!\" \"ChronoSync\"; do\n [[ -d \"/Applications/$app.app\" ]] && present=\"${present:+$present, }$app (installed, last run not checkable from here)\"\ndone\nfor tool in restic borg kopia duplicacy rclone; do\n have \"$tool\" && present=\"${present:+$present, }$tool (command-line tool present, schedule unknown)\"\ndone\n\nif [[ -n \"$best_age\" && \"$best_age\" -le \"$FRESH_DAYS\" ]]; then\n record \"backups\" pass \"backed up within the last ${FRESH_DAYS} days \u2014 $verified\"\nelif [[ -n \"$best_age\" ]]; then\n record \"backups\" fail \"the most recent backup was ${best_age} days ago \u2014 $verified\"\nelif [[ -n \"$present\" ]]; then\n record \"backups\" unknown \"backup software found but no run this script can date: $present\"\nelse\n record \"backups\" fail \"no backup software found and no Time Machine destination \u2014 one dead disk from losing everything\"\nfi\n\n# \u2500\u2500 Hardware security key \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n# Presence proves you own one. It says nothing about whether you enrolled it\n# anywhere, which is the part that matters, so this never reports a pass.\nif system_profiler SPUSBDataType 2>/dev/null | grep -qiE \"yubikey|security key\"; then\n record \"hardware-key\" unknown \"a security key is plugged in \u2014 whether it's enrolled on your accounts can't be checked from here\"\nelse\n record \"hardware-key\" unknown \"no security key plugged in right now; enrolment can't be checked from here either way\"\nfi\n\n# \u2500\u2500 Report \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\njson() {\n echo \"{\"\n echo \" \\\"tool\\\": \\\"check-my-mac.sh\\\",\"\n echo \" \\\"version\\\": 2,\"\n echo \" \\\"generated\\\": \\\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\\\",\"\n echo \" \\\"platform\\\": \\\"macOS $(sw_vers -productVersion)\\\",\"\n echo \" \\\"summary\\\": { \\\"pass\\\": $PASS, \\\"fail\\\": $FAIL, \\\"unknown\\\": $UNKNOWN },\"\n echo \" \\\"checks\\\": {\"\n local n=${#ROWS[@]} idx=0\n for row in \"${ROWS[@]}\"; do\n idx=$((idx + 1))\n if [[ $idx -lt $n ]]; then echo \"$row,\"; else echo \"$row\"; fi\n done\n echo \" }\"\n echo \"}\"\n}\n\nif [[ $JSON_ONLY -eq 1 ]]; then\n json\nelse\n echo\n echo \"$PASS passed, $FAIL failed, $UNKNOWN unknown.\"\n echo\n echo \"The things this cannot see \u2014 your credit freeze, your hardware keys,\"\n echo \"your phone carrier's port-out PIN \u2014 are the ones worth doing first.\"\n echo\n echo \"To tick these off in the checklist:\"\n echo \" bash check-my-mac.sh --json | pbcopy\"\n echo \"then paste into the box in the post.\"\n echo\nfi\n\n[[ $FAIL -eq 0 ]]\nSCRIPT_EOF\nbash -n /Users/girish/Documents/personalwebsite/website/public/check-my-mac.sh && echo \"syntax OK\""
@@ -1622,3 +1622,54 @@ test("a file path is titled by its end, where the name is", () => {
1622
1622
  );
1623
1623
  assert.strictEqual(title, "down/ARCHITECTURE.md");
1624
1624
  });
1625
+
1626
+ test("a command substitution does not stop the scanner", () => {
1627
+ // $( matched no branch and the word reader could not move past $, so the page
1628
+ // hung for ever on any command using one. 58 of them in one real session.
1629
+ const html = run(`return sh('echo $(date) and $(basename /a/b)')`);
1630
+ assert.match(html, /sh-var/);
1631
+ assert.ok(html.includes("date"), "what is inside must still be there");
1632
+ });
1633
+
1634
+ test("a lone dollar is just a character", () => {
1635
+ const html = run(`return sh('echo 5$ and $')`);
1636
+ assert.ok(html.includes("5$"), "it should survive");
1637
+ });
1638
+
1639
+ test("every character survives the scanner", () => {
1640
+ // The strongest check there is: what goes in comes out, tags aside. A branch
1641
+ // that stops matching shows up here as mangled text rather than as a hang.
1642
+ const samples = [
1643
+ "echo $(date)",
1644
+ 'for f in $HOME/*; do echo "$f"; done',
1645
+ "awk '{print $1}' file | sort -u > /tmp/o",
1646
+ "x=$((1+2)); echo $x $$ $? $",
1647
+ "cat <<'EOF'\nliteral $(not a call)\nEOF",
1648
+ "curl -s https://x/a#b -o /tmp/x && echo done # a comment",
1649
+ ];
1650
+ for (const src of samples) {
1651
+ const out = run(`return sh(${JSON.stringify(src)})`);
1652
+ const back = out
1653
+ .replace(/<[^>]+>/g, "")
1654
+ .replace(/&quot;/g, '"')
1655
+ .replace(/&#39;/g, "'")
1656
+ .replace(/&lt;/g, "<")
1657
+ .replace(/&gt;/g, ">")
1658
+ .replace(/&amp;/g, "&");
1659
+ assert.strictEqual(back, src, `mangled: ${JSON.stringify(src)}`);
1660
+ }
1661
+ });
1662
+
1663
+ test("a real 20,000-character command draws, and quickly", () => {
1664
+ // This exact command hung the page: 448 lines of heredoc with 58 command
1665
+ // substitutions in it. Kept as a fixture because no invented sample had the
1666
+ // shape that broke it.
1667
+ const command = JSON.parse(
1668
+ readFileSync(join(__dirname, "fixtures", "big-command.json"), "utf8"),
1669
+ );
1670
+ const started = Date.now();
1671
+ const html = run(`return shBlock(${JSON.stringify(command)})`);
1672
+ const took = Date.now() - started;
1673
+ assert.ok(html.includes("class=\"sh\""), "it should render");
1674
+ assert.ok(took < 2000, `took ${took}ms, which is a page nobody waits for`);
1675
+ });
@@ -388,7 +388,7 @@ wheels = [
388
388
 
389
389
  [[package]]
390
390
  name = "transcript-viewer"
391
- version = "0.7.0"
391
+ version = "0.7.1"
392
392
  source = { editable = "." }
393
393
  dependencies = [
394
394
  { name = "atif-make" },