@rubytech/create-maxy-code 0.1.219 → 0.1.222

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 (124) hide show
  1. package/dist/index.js +46 -1
  2. package/package.json +1 -1
  3. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js +60 -23
  4. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js.map +1 -1
  5. package/payload/platform/lib/graph-write/dist/index.d.ts +4 -1
  6. package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
  7. package/payload/platform/lib/graph-write/dist/index.js +17 -5
  8. package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
  9. package/payload/platform/lib/graph-write/src/__tests__/action-provenance-gate.test.ts +58 -31
  10. package/payload/platform/lib/graph-write/src/index.ts +16 -7
  11. package/payload/platform/plugins/admin/PLUGIN.md +2 -3
  12. package/payload/platform/plugins/admin/__tests__/admin-can-enumerate.test.sh +129 -0
  13. package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +1 -2
  14. package/payload/platform/plugins/admin/hooks/lib/hook-emit.sh +3 -2
  15. package/payload/platform/plugins/admin/mcp/dist/index.js +41 -9
  16. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  17. package/payload/platform/plugins/admin/references/publish-site-routing.md +44 -0
  18. package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +1 -1
  19. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +11 -4
  20. package/payload/platform/plugins/admin/skills/plugin-management/SKILL.md +2 -2
  21. package/payload/platform/plugins/contacts/PLUGIN.md +2 -2
  22. package/payload/platform/plugins/docs/references/internals.md +1 -1
  23. package/payload/platform/plugins/docs/references/platform.md +2 -2
  24. package/payload/platform/plugins/docs/references/troubleshooting.md +7 -0
  25. package/payload/platform/plugins/memory/PLUGIN.md +1 -1
  26. package/payload/platform/plugins/memory/references/graph-primitives.md +2 -2
  27. package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.d.ts +2 -1
  28. package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.d.ts.map +1 -1
  29. package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.js +27 -21
  30. package/payload/platform/plugins/url-get/mcp/dist/tools/url-get.js.map +1 -1
  31. package/payload/platform/scripts/check-no-esm-require.mjs +3 -0
  32. package/payload/platform/scripts/setup-account.sh +0 -15
  33. package/payload/platform/services/claude-session-manager/dist/auth-snapshot.d.ts +4 -0
  34. package/payload/platform/services/claude-session-manager/dist/auth-snapshot.d.ts.map +1 -0
  35. package/payload/platform/services/claude-session-manager/dist/auth-snapshot.js +50 -0
  36. package/payload/platform/services/claude-session-manager/dist/auth-snapshot.js.map +1 -0
  37. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  38. package/payload/platform/services/claude-session-manager/dist/http-server.js +19 -2
  39. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  40. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  41. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +39 -19
  42. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  43. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +24 -0
  44. package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
  45. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +100 -11
  46. package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
  47. package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts +8 -0
  48. package/payload/platform/services/claude-session-manager/dist/specialist-drift.d.ts.map +1 -1
  49. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js +7 -2
  50. package/payload/platform/services/claude-session-manager/dist/specialist-drift.js.map +1 -1
  51. package/payload/server/public/assets/{AdminShell-BcxEZnpu.js → AdminShell-BiEwDSRL.js} +1 -1
  52. package/payload/server/public/assets/{Checkbox-DL-HdT29.js → Checkbox-COiFXiXL.js} +1 -1
  53. package/payload/server/public/assets/{admin-DMo5DWKH.js → admin-Ca9ckLZx.js} +1 -1
  54. package/payload/server/public/assets/{architectureDiagram-Q4EWVU46-BoUw4oYz.js → architectureDiagram-Q4EWVU46-DI6JZI13.js} +1 -1
  55. package/payload/server/public/assets/{blockDiagram-DXYQGD6D-DOscnO3m.js → blockDiagram-DXYQGD6D-dgDvswLd.js} +1 -1
  56. package/payload/server/public/assets/{brand-SZW-27T7.css → brand-CAMMU5lz.css} +1 -1
  57. package/payload/server/public/assets/{c4Diagram-AHTNJAMY-aZ1IuZ_F.js → c4Diagram-AHTNJAMY-odaGY4GZ.js} +1 -1
  58. package/payload/server/public/assets/channel-DfwLbKck.js +1 -0
  59. package/payload/server/public/assets/{chunk-336JU56O-DS3ksXBG.js → chunk-336JU56O-BpbY8F27.js} +2 -2
  60. package/payload/server/public/assets/{chunk-426QAEUC-BLRn2STO.js → chunk-426QAEUC-BWbVhEVC.js} +1 -1
  61. package/payload/server/public/assets/{chunk-4TB4RGXK-BrhOvJ01.js → chunk-4TB4RGXK-C3IfRdvA.js} +1 -1
  62. package/payload/server/public/assets/{chunk-5FUZZQ4R-BZZwZX5Q.js → chunk-5FUZZQ4R-CSRMBAGr.js} +1 -1
  63. package/payload/server/public/assets/{chunk-5PVQY5BW-YXZKnPCz.js → chunk-5PVQY5BW-CGAe6GJJ.js} +1 -1
  64. package/payload/server/public/assets/{chunk-EDXVE4YY-DAMCJHKQ.js → chunk-EDXVE4YY-BnXC8cYQ.js} +1 -1
  65. package/payload/server/public/assets/{chunk-ENJZ2VHE-DsD95Bqn.js → chunk-ENJZ2VHE-7qfDalAB.js} +1 -1
  66. package/payload/server/public/assets/{chunk-ICPOFSXX-DxtrRClx.js → chunk-ICPOFSXX-TRvVGIDw.js} +1 -1
  67. package/payload/server/public/assets/{chunk-OYMX7WX6-CN4kLK-H.js → chunk-OYMX7WX6-Cfv8vBvI.js} +1 -1
  68. package/payload/server/public/assets/{chunk-U2HBQHQK-BPTv3Oj1.js → chunk-U2HBQHQK-BfLvbEyu.js} +1 -1
  69. package/payload/server/public/assets/{chunk-X2U36JSP-DxBry33v.js → chunk-X2U36JSP-Cj3k-dYC.js} +1 -1
  70. package/payload/server/public/assets/{chunk-YZCP3GAM-BZ4N75oy.js → chunk-YZCP3GAM-CygW6I0h.js} +1 -1
  71. package/payload/server/public/assets/{chunk-ZZ45TVLE-Dqr161Q1.js → chunk-ZZ45TVLE-CBF3hFJH.js} +1 -1
  72. package/payload/server/public/assets/classDiagram-6PBFFD2Q-CE0XZQre.js +1 -0
  73. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-Bj41Y3XD.js +1 -0
  74. package/payload/server/public/assets/clone-DTd5nONj.js +1 -0
  75. package/payload/server/public/assets/{dagre-DtZ0wKkH.js → dagre-BTGABQJX.js} +1 -1
  76. package/payload/server/public/assets/{dagre-KV5264BT-BPKhT-ZX.js → dagre-KV5264BT-B2lbkuGd.js} +1 -1
  77. package/payload/server/public/assets/{data-D6IQdm0R.js → data-CHdaTjgn.js} +1 -1
  78. package/payload/server/public/assets/{diagram-5BDNPKRD-CATnafi7.js → diagram-5BDNPKRD-BUK4LqT8.js} +1 -1
  79. package/payload/server/public/assets/{diagram-G4DWMVQ6-ChZYQfji.js → diagram-G4DWMVQ6-e49HrvxL.js} +1 -1
  80. package/payload/server/public/assets/{diagram-MMDJMWI5-DHW6kYd3.js → diagram-MMDJMWI5-k4Kx_qWe.js} +1 -1
  81. package/payload/server/public/assets/{diagram-TYMM5635-CnS4Zci8.js → diagram-TYMM5635-DzavOUdK.js} +1 -1
  82. package/payload/server/public/assets/{erDiagram-SMLLAGMA-DKYv2YWp.js → erDiagram-SMLLAGMA-CJKecRhi.js} +1 -1
  83. package/payload/server/public/assets/{flowDiagram-DWJPFMVM-BWvoZWlh.js → flowDiagram-DWJPFMVM-GM7H1TDt.js} +1 -1
  84. package/payload/server/public/assets/{ganttDiagram-T4ZO3ILL-CpCb-xKy.js → ganttDiagram-T4ZO3ILL-kDEA1A-3.js} +1 -1
  85. package/payload/server/public/assets/{gitGraphDiagram-UUTBAWPF-CvHrDKQ-.js → gitGraphDiagram-UUTBAWPF-jVZZdeg2.js} +1 -1
  86. package/payload/server/public/assets/{graph-labels-CWJy0O4w.js → graph-labels-CavkO4DG.js} +1 -1
  87. package/payload/server/public/assets/{graph-PkZKVkWD.js → graph-tqCqSngu.js} +1 -1
  88. package/payload/server/public/assets/{graphlib-DjnyHqGy.js → graphlib-DFU2rJiY.js} +1 -1
  89. package/payload/server/public/assets/{infoDiagram-42DDH7IO-D_MywcVn.js → infoDiagram-42DDH7IO-CFxVoRBy.js} +1 -1
  90. package/payload/server/public/assets/{ishikawaDiagram-UXIWVN3A-N9m-f-Eb.js → ishikawaDiagram-UXIWVN3A-CDvZs3Ua.js} +1 -1
  91. package/payload/server/public/assets/{journeyDiagram-VCZTEJTY-DE0q7uSM.js → journeyDiagram-VCZTEJTY-DUI1m9vW.js} +1 -1
  92. package/payload/server/public/assets/{kanban-definition-6JOO6SKY-Dj5hb9Bq.js → kanban-definition-6JOO6SKY-C_jSVK7W.js} +1 -1
  93. package/payload/server/public/assets/{line-C6ph0i4M.js → line-DfwJeo6J.js} +1 -1
  94. package/payload/server/public/assets/{mermaid-parser.core-BrfmEvCA.js → mermaid-parser.core-BN-2kBfU.js} +1 -1
  95. package/payload/server/public/assets/{mermaid.core-DYnHKfDv.js → mermaid.core-C6amhHAQ.js} +3 -3
  96. package/payload/server/public/assets/{mindmap-definition-QFDTVHPH-DaSaZfhR.js → mindmap-definition-QFDTVHPH-eTKDlw3c.js} +1 -1
  97. package/payload/server/public/assets/{pieDiagram-DEJITSTG-BAu9Ezbv.js → pieDiagram-DEJITSTG-DNX5VeMZ.js} +1 -1
  98. package/payload/server/public/assets/{public-UKgkJxKv.js → public-BADBLzOZ.js} +3 -3
  99. package/payload/server/public/assets/{quadrantDiagram-34T5L4WZ-DRsbL1Xd.js → quadrantDiagram-34T5L4WZ-BFnmYiHX.js} +1 -1
  100. package/payload/server/public/assets/{requirementDiagram-MS252O5E-B9QcsXV6.js → requirementDiagram-MS252O5E-DvHvTyzu.js} +1 -1
  101. package/payload/server/public/assets/{sankeyDiagram-XADWPNL6-DsZbvIlD.js → sankeyDiagram-XADWPNL6-CMFnNT1G.js} +1 -1
  102. package/payload/server/public/assets/{sequenceDiagram-FGHM5R23-BlT4O2N8.js → sequenceDiagram-FGHM5R23-BYon1ouU.js} +1 -1
  103. package/payload/server/public/assets/{stateDiagram-FHFEXIEX-JFgk1K3Y.js → stateDiagram-FHFEXIEX-n6l3hcuJ.js} +1 -1
  104. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-CS46-Mhi.js +1 -0
  105. package/payload/server/public/assets/{timeline-definition-GMOUNBTQ-DOsHw1ip.js → timeline-definition-GMOUNBTQ-DFPFHrQF.js} +1 -1
  106. package/payload/server/public/assets/{useSelectionMode-JDGvwvIk.js → useSelectionMode-iqIyL24g.js} +1 -1
  107. package/payload/server/public/assets/{vennDiagram-DHZGUBPP-CQWIhqZY.js → vennDiagram-DHZGUBPP-DZLturYa.js} +1 -1
  108. package/payload/server/public/assets/{wardleyDiagram-NUSXRM2D-D5Fh7YVL.js → wardleyDiagram-NUSXRM2D-BBt2pdjM.js} +1 -1
  109. package/payload/server/public/assets/{xychartDiagram-5P7HB3ND-C-tINQEs.js → xychartDiagram-5P7HB3ND-DOZxLvEi.js} +1 -1
  110. package/payload/server/public/data.html +5 -5
  111. package/payload/server/public/graph.html +6 -6
  112. package/payload/server/public/index.html +6 -6
  113. package/payload/server/public/public.html +5 -5
  114. package/payload/server/server.js +49 -6
  115. package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-admin-tool-gate.test.sh +0 -109
  116. package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-base64-guard.test.sh +0 -204
  117. package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-memory-write-passthrough.test.sh +0 -118
  118. package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +0 -337
  119. package/payload/server/public/assets/channel-CzcSNxDm.js +0 -1
  120. package/payload/server/public/assets/classDiagram-6PBFFD2Q-DFOM3vCn.js +0 -1
  121. package/payload/server/public/assets/classDiagram-v2-HSJHXN6E-HplDfaL1.js +0 -1
  122. package/payload/server/public/assets/clone-G-2rqnSR.js +0 -1
  123. package/payload/server/public/assets/stateDiagram-v2-QKLJ7IA2-NXz27DaV.js +0 -1
  124. /package/payload/server/public/assets/{brand-SzZmKKjz.js → brand-DfykKss4.js} +0 -0
@@ -1,337 +0,0 @@
1
- #!/usr/bin/env bash
2
- # PreToolUse hook — enforces agent tool boundaries and approval gating.
3
- # Usage: pre-tool-use.sh <agent-type>
4
- # Receives tool call JSON on stdin (from Claude Code hook protocol).
5
- # Exit 0 = allow, exit 2 = block.
6
-
7
- set -uo pipefail
8
-
9
- AGENT_TYPE="${1:-public}"
10
-
11
- # Source the propagation emitter (Task 560). The library is fail-open;
12
- # any error path inside it logs to server.log and returns 0, so sourcing
13
- # never blocks the hook's primary allow/block contract.
14
- HOOKS_LIB_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)/lib"
15
- # shellcheck disable=SC1091
16
- [ -f "$HOOKS_LIB_DIR/hook-emit.sh" ] && source "$HOOKS_LIB_DIR/hook-emit.sh"
17
-
18
- # Read stdin — fail closed if unavailable
19
- if [ -t 0 ]; then
20
- # No INPUT yet; propagate with agentId=unknown so the regression is visible.
21
- if declare -F hook_emit_decision >/dev/null 2>&1; then
22
- hook_emit_decision "unknown" "admin-tool-gate" "?" "block" \
23
- "stdin-missing" "Blocked: Cannot determine tool call (no stdin). Failing closed." 2 0
24
- fi
25
- echo "Blocked: Cannot determine tool call (no stdin). Failing closed." >&2
26
- exit 2
27
- fi
28
- INPUT=$(cat)
29
- TOOL_NAME=$(echo "$INPUT" | grep -o '"tool_name":"[^"]*"' | head -1 | cut -d'"' -f4)
30
-
31
- # Session id for propagation. Empty / parse-failure routes to "unknown" —
32
- # the propagator emits the record under agentId=unknown rather than dropping.
33
- SESSION_ID=$(printf '%s' "$INPUT" | python3 -c '
34
- import sys, json
35
- try:
36
- print(json.load(sys.stdin).get("session_id", "") or "unknown")
37
- except Exception:
38
- print("unknown")
39
- ' 2>/dev/null || echo "unknown")
40
-
41
- # ---------------------------------------------------------------------------
42
- # Admin agent — code directory protection + approval gating
43
- # ---------------------------------------------------------------------------
44
- if [ "$AGENT_TYPE" = "admin" ]; then
45
-
46
- # ── Code directory protection ────────────────────────────────────────────
47
- case "$TOOL_NAME" in
48
- Write|Edit)
49
- FILE_PATH=$(echo "$INPUT" | grep -o '"file_path":"[^"]*"' | head -1 | cut -d'"' -f4)
50
- case "$FILE_PATH" in
51
- # Platform UI source (platform/ui/) — source lives here, not on device
52
- */platform/ui/app/*|*/platform/ui/lib/*|*/platform/ui/*.ts|*/platform/ui/*.tsx|*/platform/ui/*.mjs)
53
- hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "${TOOL_NAME}" \
54
- "code-dir-protection-ui" \
55
- "Blocked: Admin agent cannot modify platform UI source at $FILE_PATH"
56
- ;;
57
- # Compiled MCP servers, hooks, and scripts — what actually ships to device
58
- */platform/plugins/*/mcp/dist/*|*/platform/plugins/*/hooks/*|*/platform/scripts/*)
59
- hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "${TOOL_NAME}" \
60
- "code-dir-protection-platform" \
61
- "Blocked: Admin agent cannot modify platform code at $FILE_PATH"
62
- ;;
63
- # Entitlement library — verifier source, compiled output, vendored
64
- # pubkey, hash file, and any signed entitlement.json. Tampering here is the
65
- # agent path to revenue bypass; the legitimate write path is the Rubytech
66
- # issuance endpoint, never the agent's filesystem.
67
- # Patterns intentionally cover both absolute (*/...) and relative
68
- # (no leading slash) paths so an agent can't bypass via cwd-relative writes.
69
- */platform/lib/entitlement/*|platform/lib/entitlement/*|*/entitlement.json|entitlement.json)
70
- echo "[entitlement] tool-deny: tool=${TOOL_NAME} path=${FILE_PATH} field=entitlement-file" >&2
71
- hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "${TOOL_NAME}" \
72
- "entitlement-file" \
73
- "Blocked: Admin agent cannot modify entitlement files at $FILE_PATH. Effective tier and purchasedPlugins derive from a Rubytech-signed payload."
74
- ;;
75
- # account.json — agent must use the account-update
76
- # MCP tool (or plugin-toggle-enabled for enabledPlugins changes), which
77
- # whitelists editable fields server-side. Direct Edit/Write bypasses
78
- # that whitelist; deny unconditionally including cwd-relative paths.
79
- # adds */account.json and *account.json as a backstop so any
80
- # layout the named patterns miss is still caught — the Adam Mackay
81
- # incident showed a tier upgrade via raw Edit on account.json reach
82
- # the disk, exactly the failure mode this hook exists to prevent.
83
- */data/accounts/*/account.json|*/config/accounts/*/account.json|data/accounts/*/account.json|config/accounts/*/account.json|*/account.json|*account.json|account.json)
84
- echo "[entitlement] tool-deny: tool=${TOOL_NAME} path=${FILE_PATH} field=account-json" >&2
85
- hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "${TOOL_NAME}" \
86
- "account-json" \
87
- "Blocked: Admin agent cannot edit account.json directly. Use the account-update or plugin-toggle-enabled MCP tools — they whitelist editable fields server-side and exclude tier and purchasedPlugins by design."
88
- ;;
89
- # Pending action queue — only the hook and MCP tools may write here
90
- */pending-actions/*)
91
- hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "${TOOL_NAME}" \
92
- "pending-actions" \
93
- "Blocked: Admin agent cannot modify pending action files directly"
94
- ;;
95
- esac
96
- ;;
97
- Bash)
98
- # Block shell commands referencing protected code directories or pending actions
99
- case "$INPUT" in
100
- *"platform/ui/app/"*|*"platform/ui/lib/"*|*"platform/plugins/"*"hooks/"*|*"platform/scripts/"*)
101
- hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "Bash" \
102
- "code-dir-protection-bash" \
103
- "Blocked: Admin agent cannot run shell commands against code directories"
104
- ;;
105
- # Entitlement files via shell — same surface, blocked symmetrically
106
- *"platform/lib/entitlement/"*|*"entitlement.json"*)
107
- echo "[entitlement] tool-deny: tool=Bash path=entitlement-file field=entitlement-file" >&2
108
- hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "Bash" \
109
- "entitlement-shell" \
110
- "Blocked: Admin agent cannot reference entitlement files via shell."
111
- ;;
112
- # account.json via shell — same rationale as Edit/Write block
113
- *"data/accounts/"*"account.json"*|*"config/accounts/"*"account.json"*)
114
- echo "[entitlement] tool-deny: tool=Bash path=account-json field=account-json" >&2
115
- hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "Bash" \
116
- "account-json-shell" \
117
- "Blocked: Admin agent cannot edit account.json via shell. Use the account-update MCP tool."
118
- ;;
119
- *"pending-actions/"*)
120
- hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "Bash" \
121
- "pending-actions-shell" \
122
- "Blocked: Admin agent cannot modify pending action files via shell"
123
- ;;
124
- esac
125
- ;;
126
- esac
127
-
128
- # ── Base64 context-overflow guard ─────────────────────────────
129
- # Block inline base64 payloads from reaching the model context. Two paths:
130
- #
131
- # 1. Bash command that ENCODES a binary file to base64/hex (the producer).
132
- # 2. Write/Edit content carrying an inline `data:<mime>;base64,…` blob
133
- # (the consumer — agent quoting bytes from a prior tool_result into a
134
- # HTML/markdown Write).
135
- #
136
- # Either path landed ~33 KB of base64 in the SDK request and the next turn
137
- # tripped `main_stream_stalled` at 180 s. The
138
- # remediation is symmetric: the agent saves bytes to `$ACCOUNT_DIR/tmp/<sha1>.<ext>`
139
- # and references the path (`<img src="./file">` or Read-by-path) instead of
140
- # carrying bytes through the assistant turn.
141
- #
142
- # Parsing uses python3 (already a hook dependency at the action-id site
143
- # below); grep on JSON is unsafe for content with escaped quotes or
144
- # embedded newlines. Parse failure is fail-open (empty extracted string,
145
- # no match, allow) — matches the playwright-file-guard fail-open contract.
146
- # Single python3 invocation parses the JSON, runs the tool-specific
147
- # regex match (avoiding BSD-vs-GNU grep interval-count incompatibilities —
148
- # `grep -E '{4096,}'` errors with "invalid repetition count(s)" on macOS
149
- # BSD grep under some pattern combinations), and prints the rejection
150
- # outcome to stdout as `REJECT:<reason>:<bytes>` or `ALLOW`. The wrapping
151
- # bash logic reads the verdict and emits the rejection log/stderr/exit-2.
152
- # Parse failure prints `ALLOW` (fail-open, matching the playwright-file-
153
- # guard contract for malformed stdin).
154
- GUARD_VERDICT=$(echo "$INPUT" | python3 -c '
155
- import sys, json, re
156
- try:
157
- d = json.load(sys.stdin)
158
- tool = d.get("tool_name", "")
159
- ti = d.get("tool_input", {}) or {}
160
- if tool in ("Write", "Edit"):
161
- # Write.content OR Edit.new_string can carry inline base64.
162
- content = ti.get("content") or ti.get("new_string") or ""
163
- if not isinstance(content, str):
164
- print("ALLOW"); sys.exit(0)
165
- # data:<mime>;base64,<≥4096 base64 chars> — threshold matches the
166
- # doctrine paragraph in .docs/agents.md. The 4096-char body is
167
- # ~3 KB binary, far above any legitimate inline icon.
168
- m = re.search(r"data:[^;]+;base64,[A-Za-z0-9+/]{4096,}={0,2}", content)
169
- if m:
170
- print(f"REJECT:base64-write-content:{len(content)}")
171
- else:
172
- print("ALLOW")
173
- elif tool == "Bash":
174
- command = ti.get("command", "")
175
- if not isinstance(command, str):
176
- print("ALLOW"); sys.exit(0)
177
- # Per-segment scan. A compound command like
178
- # cat in.b64 | base64 -d > out.bin; cat photo.png | base64
179
- # contains both a legitimate decode AND a malicious encoder. A whole-
180
- # command decode-flag check is fooled into allowing the encoder. Split
181
- # on shell separators (;, &&, ||, &) and scan each segment as its own
182
- # command — the encoder rejection fires when ANY segment is a bare
183
- # base64 invocation without a paired decode flag in the SAME segment.
184
- # Pipelines (|) keep the segment together because the encoder direction
185
- # of `cat file | base64` lives across the pipe.
186
- segments = re.split(r";|&&|\|\||(?<![|&])&(?![|&])", command)
187
- rejected = None
188
- for seg in segments:
189
- if re.search(r"(?:^|[\s|;&])xxd[\t ]+-p(?![A-Za-z0-9_-])", seg):
190
- rejected = "xxd-plain-hex"; break
191
- if re.search(r"(?:^|[\s|;&])base64(?![A-Za-z0-9_-])", seg):
192
- if not re.search(r"base64[\t ]+[^|]*(?:-d|-D|--decode)(?![A-Za-z0-9_])", seg):
193
- rejected = "base64-encoder"; break
194
- if rejected:
195
- print(f"REJECT:{rejected}:{len(command)}")
196
- else:
197
- print("ALLOW")
198
- else:
199
- print("ALLOW")
200
- except Exception:
201
- print("ALLOW")
202
- ' 2>/dev/null || echo "ALLOW")
203
- case "$GUARD_VERDICT" in
204
- REJECT:base64-write-content:*)
205
- BYTES="${GUARD_VERDICT##*:}"
206
- echo "[pre-tool-use] guard=base64-write-content bytes=${BYTES} action=reject" >&2
207
- hook_block_with_emit "$SESSION_ID" "base64-guard" "${TOOL_NAME}" \
208
- "base64-write-content" \
209
- "Blocked: ${TOOL_NAME} content carries an inline base64 payload (>4 KB encoded). Inline binary in Write.content overloads the model context — the same path produced a main_stream_stalled at ~33 KB on 2026-05-09.
210
- Save the bytes to \$ACCOUNT_DIR/tmp/<sha1>.<ext> via Bash (e.g. 'base64 -d > out.png'), then reference the file from the document: <img src=\"./<file>\"> or Read-by-path. Do not carry binary bytes through the assistant turn."
211
- ;;
212
- REJECT:base64-encoder:*|REJECT:xxd-plain-hex:*)
213
- REASON="${GUARD_VERDICT#REJECT:}"; REASON="${REASON%:*}"
214
- BYTES="${GUARD_VERDICT##*:}"
215
- echo "[pre-tool-use] guard=base64-tool-result bytes=${BYTES} tool=Bash reason=${REASON} action=reject" >&2
216
- hook_block_with_emit "$SESSION_ID" "base64-guard" "Bash" \
217
- "$REASON" \
218
- "Blocked: Bash command would emit binary as inline base64/hex to stdout, which lands in the assistant turn and overloads the model context (the 2026-05-09 Rubytech-invoice path hit 66% context after a single ~33 KB tool_result).
219
- Instead: save the bytes directly to \$ACCOUNT_DIR/tmp/<sha1>.<ext> and operate on the file via path — Read for inspection, <img src=\"./<file>\"> for HTML embedding, the \`file-presentation\` skill for delivery. Decoding base64 (e.g. 'base64 -d in.b64 > out.bin') is allowed."
220
- ;;
221
- *)
222
- : # ALLOW — fall through to approval gating below
223
- ;;
224
- esac
225
-
226
- # ── Approval gating (EU AI Act Article 14 — human oversight) ─────────────
227
- # Strip the mcp__<plugin>__ prefix to get the short tool name.
228
- # Built-in tools (no prefix) pass through unchanged.
229
- SHORT_NAME=$(echo "$TOOL_NAME" | sed 's/^mcp__[^_]*__//')
230
-
231
- # Derive platform root from this hook's location:
232
- # platform/plugins/admin/hooks/pre-tool-use.sh → platform/
233
- HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
234
- PLATFORM_ROOT="${HOOK_DIR}/../../.."
235
-
236
- # Resolve the single account directory (Phase 0).
237
- ACCOUNTS_DIR="${PLATFORM_ROOT}/../data/accounts"
238
- ACCOUNT_DIR=""
239
- if [ -d "$ACCOUNTS_DIR" ]; then
240
- ACCOUNT_DIR=$(find "$ACCOUNTS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | head -1)
241
- fi
242
-
243
- # Default require-review tools — matches DEFAULT_REQUIRE_REVIEW in claude-agent.ts
244
- DEFAULT_REQUIRE_REVIEW="email-send email-reply whatsapp-send whatsapp-send-document message contact-erase"
245
-
246
- # Check if this tool requires approval review.
247
- # 1. Read per-account override from account.json approvalPolicy (if it exists)
248
- # 2. Fall back to the default require-review list
249
- POLICY=""
250
- if [ -n "$ACCOUNT_DIR" ] && [ -f "${ACCOUNT_DIR}/account.json" ]; then
251
- # Check per-account override first (grep for the tool name in the approvalPolicy block)
252
- OVERRIDE=$(grep -o "\"${SHORT_NAME}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "${ACCOUNT_DIR}/account.json" 2>/dev/null | head -1 | grep -o '"[^"]*"$' | tr -d '"')
253
- if [ -n "$OVERRIDE" ]; then
254
- POLICY="$OVERRIDE"
255
- fi
256
- fi
257
-
258
- # If no per-account override, check the default list
259
- if [ -z "$POLICY" ]; then
260
- for TOOL in $DEFAULT_REQUIRE_REVIEW; do
261
- if [ "$SHORT_NAME" = "$TOOL" ]; then
262
- POLICY="require-review"
263
- break
264
- fi
265
- done
266
- fi
267
-
268
- # If not in any policy, auto-execute (allow)
269
- if [ "$POLICY" != "require-review" ]; then
270
- exit 0
271
- fi
272
-
273
- # ── Special case: contact-erase preview calls pass through ───────────────
274
- # The contact-erase tool has a two-step flow: first call with confirm:false
275
- # returns a preview (data counts), second call with confirm:true executes.
276
- # Only gate the destructive confirm:true call.
277
- if [ "$SHORT_NAME" = "contact-erase" ]; then
278
- if ! echo "$INPUT" | grep -q '"confirm"[[:space:]]*:[[:space:]]*true'; then
279
- exit 0
280
- fi
281
- fi
282
-
283
- # ── Queue the action for approval ───────────────────────────────────────
284
- PENDING_DIR="${ACCOUNT_DIR}/pending-actions"
285
- mkdir -p "$PENDING_DIR" 2>/dev/null
286
-
287
- ACTION_ID="$(cat /proc/sys/kernel/random/uuid 2>/dev/null || python3 -c 'import uuid; print(uuid.uuid4())' 2>/dev/null || date +%s%N)"
288
- CREATED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
289
-
290
- # Write atomically: temp file + mv
291
- TEMP=$(mktemp "${PENDING_DIR}/.tmp.XXXXXX" 2>/dev/null)
292
- if [ -z "$TEMP" ]; then
293
- hook_block_with_emit "$SESSION_ID" "approval-gate" "${TOOL_NAME}" \
294
- "approval-queue-mktemp" \
295
- "Blocked: Action requires approval but failed to create queue file (disk error). Failing closed."
296
- fi
297
-
298
- # The pending action file contains the full hook JSON (tool_name + tool_input)
299
- # plus metadata. The MCP approval tools parse it with Node.js JSON.parse.
300
- cat > "$TEMP" <<ENDJSON
301
- {
302
- "actionId": "${ACTION_ID}",
303
- "toolName": "${SHORT_NAME}",
304
- "pluginName": "$(echo "$TOOL_NAME" | sed -n 's/^mcp__\([^_]*\)__.*/\1/p')",
305
- "hookPayload": ${INPUT},
306
- "state": "pending",
307
- "createdAt": "${CREATED_AT}"
308
- }
309
- ENDJSON
310
-
311
- if ! mv "$TEMP" "${PENDING_DIR}/${ACTION_ID}.json" 2>/dev/null; then
312
- rm -f "$TEMP" 2>/dev/null
313
- hook_block_with_emit "$SESSION_ID" "approval-gate" "${TOOL_NAME}" \
314
- "approval-queue-mv" \
315
- "Blocked: Action requires approval but failed to queue (filesystem error). Failing closed."
316
- fi
317
-
318
- # Record the queued-for-approval block in the propagation buffer; the
319
- # readable summary still goes to stdout (CC attaches it as the agent's
320
- # tool_result). hook_emit_decision is the no-exit variant — the
321
- # subsequent echoes + exit 2 below remain the operator-facing surface.
322
- hook_emit_decision "$SESSION_ID" "approval-gate" "${TOOL_NAME}" \
323
- "block" "approval-queued" \
324
- "Action ${ACTION_ID} queued for review" 2 0
325
-
326
- # Extract a readable summary of the action for the agent to present
327
- # tool_input is the second JSON value in the hook payload
328
- echo "Action requires approval before execution."
329
- echo ""
330
- echo "Action ID: ${ACTION_ID}"
331
- echo "Tool: ${SHORT_NAME}"
332
- echo "Status: Queued for review"
333
- echo ""
334
- echo "The admin must approve this action before it can execute."
335
- echo "Use action-approve to send, action-reject to cancel, or action-edit to modify."
336
- exit 2
337
- fi
@@ -1 +0,0 @@
1
- import{it as e,rt as t}from"./chunk-ICPOFSXX-DxtrRClx.js";var n=(n,r)=>e.lang.round(t.parse(n)[r]);export{n as t};
@@ -1 +0,0 @@
1
- import{h as e}from"./src-CWiyyVfn.js";import"./chunk-ICPOFSXX-DxtrRClx.js";import"./dist-BzAsli7o.js";import"./chunk-5PVQY5BW-YXZKnPCz.js";import"./chunk-U2HBQHQK-BPTv3Oj1.js";import"./chunk-FMBD7UC4-C_E43NFJ.js";import"./chunk-BSJP7CBP-L79XKVcb.js";import"./chunk-ZZ45TVLE-Dqr161Q1.js";import"./chunk-YZCP3GAM-BZ4N75oy.js";import"./chunk-55IACEB6-BwZyF7vR.js";import"./chunk-EDXVE4YY-DAMCJHKQ.js";import"./chunk-X2U36JSP-DxBry33v.js";import"./chunk-5FUZZQ4R-BZZwZX5Q.js";import"./chunk-ENJZ2VHE-DsD95Bqn.js";import"./chunk-336JU56O-DS3ksXBG.js";import{i as t,n,r,t as i}from"./chunk-4TB4RGXK-BrhOvJ01.js";var a={parser:n,get db(){return new i},renderer:r,styles:t,init:e(e=>{e.class||={},e.class.arrowMarkerAbsolute=e.arrowMarkerAbsolute},`init`)};export{a as diagram};
@@ -1 +0,0 @@
1
- import{h as e}from"./src-CWiyyVfn.js";import"./chunk-ICPOFSXX-DxtrRClx.js";import"./dist-BzAsli7o.js";import"./chunk-5PVQY5BW-YXZKnPCz.js";import"./chunk-U2HBQHQK-BPTv3Oj1.js";import"./chunk-FMBD7UC4-C_E43NFJ.js";import"./chunk-BSJP7CBP-L79XKVcb.js";import"./chunk-ZZ45TVLE-Dqr161Q1.js";import"./chunk-YZCP3GAM-BZ4N75oy.js";import"./chunk-55IACEB6-BwZyF7vR.js";import"./chunk-EDXVE4YY-DAMCJHKQ.js";import"./chunk-X2U36JSP-DxBry33v.js";import"./chunk-5FUZZQ4R-BZZwZX5Q.js";import"./chunk-ENJZ2VHE-DsD95Bqn.js";import"./chunk-336JU56O-DS3ksXBG.js";import{i as t,n,r,t as i}from"./chunk-4TB4RGXK-BrhOvJ01.js";var a={parser:n,get db(){return new i},renderer:r,styles:t,init:e(e=>{e.class||={},e.class.arrowMarkerAbsolute=e.arrowMarkerAbsolute},`init`)};export{a as diagram};
@@ -1 +0,0 @@
1
- import{i as e}from"./graphlib-DjnyHqGy.js";var t=4;function n(n){return e(n,t)}export{n as t};
@@ -1 +0,0 @@
1
- import{h as e}from"./src-CWiyyVfn.js";import"./chunk-ICPOFSXX-DxtrRClx.js";import"./dist-BzAsli7o.js";import"./chunk-5PVQY5BW-YXZKnPCz.js";import"./chunk-U2HBQHQK-BPTv3Oj1.js";import"./chunk-BSJP7CBP-L79XKVcb.js";import"./chunk-ZZ45TVLE-Dqr161Q1.js";import"./chunk-55IACEB6-BwZyF7vR.js";import"./chunk-EDXVE4YY-DAMCJHKQ.js";import"./chunk-X2U36JSP-DxBry33v.js";import"./chunk-5FUZZQ4R-BZZwZX5Q.js";import"./chunk-ENJZ2VHE-DsD95Bqn.js";import"./chunk-336JU56O-DS3ksXBG.js";import{i as t,n,r,t as i}from"./chunk-OYMX7WX6-CN4kLK-H.js";var a={parser:n,get db(){return new i(2)},renderer:r,styles:t,init:e(e=>{e.state||={},e.state.arrowMarkerAbsolute=e.arrowMarkerAbsolute},`init`)};export{a as diagram};