create-workframe 0.1.4 → 0.1.6

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 (182) hide show
  1. package/.dockerignore +22 -22
  2. package/.gitignore +73 -73
  3. package/LICENSE +201 -201
  4. package/NOTICE +12 -12
  5. package/README.md +40 -40
  6. package/SECURITY.md +38 -38
  7. package/bin/create-workframe.js +2816 -2814
  8. package/bin/workframe.js +329 -329
  9. package/docs/workspace-instructions/WORKFRAME_DISCORD.md +20 -20
  10. package/docs/workspace-instructions/WORKFRAME_DOCUMENTS_AND_ARTIFACTS.md +20 -20
  11. package/docs/workspace-instructions/WORKFRAME_KANBAN.md +20 -20
  12. package/docs/workspace-instructions/WORKFRAME_ONBOARDING.md +21 -21
  13. package/docs/workspace-instructions/WORKFRAME_ROUTING.md +29 -29
  14. package/docs/workspace-instructions/WORKFRAME_TELEGRAM.md +19 -19
  15. package/package.json +64 -64
  16. package/profiles/README.md +15 -15
  17. package/profiles/architect/AGENTS.md +29 -29
  18. package/profiles/architect/SOUL.md +44 -44
  19. package/profiles/architect/skills/devops/kanban-worker/SKILL.md +27 -27
  20. package/profiles/designer/AGENTS.md +26 -26
  21. package/profiles/designer/SOUL.md +31 -31
  22. package/profiles/designer/skills/devops/kanban-worker/SKILL.md +27 -27
  23. package/profiles/dev/AGENTS.md +28 -28
  24. package/profiles/dev/SOUL.md +31 -31
  25. package/profiles/dev/skills/devops/kanban-worker/SKILL.md +27 -27
  26. package/profiles/docs/AGENTS.md +27 -27
  27. package/profiles/docs/SOUL.md +30 -30
  28. package/profiles/docs/skills/devops/kanban-worker/SKILL.md +27 -27
  29. package/profiles/research/AGENTS.md +26 -26
  30. package/profiles/research/SOUL.md +31 -31
  31. package/profiles/research/skills/devops/kanban-worker/SKILL.md +27 -27
  32. package/profiles/visionary/AGENTS.md +25 -25
  33. package/profiles/visionary/SOUL.md +31 -31
  34. package/profiles/visionary/skills/devops/kanban-worker/SKILL.md +27 -27
  35. package/profiles/workframe-agent/AGENTS.md +37 -37
  36. package/profiles/workframe-agent/SETUP.md +185 -185
  37. package/profiles/workframe-agent/SOUL.md +61 -61
  38. package/profiles/workframe-agent/skills/devops/botfather/SKILL.md +85 -85
  39. package/profiles/workframe-agent/skills/devops/kanban-handoff-pattern/SKILL.md +58 -58
  40. package/profiles/workframe-agent/skills/devops/workframe-cohort/SKILL.md +54 -54
  41. package/prompts/WORKFRAME_PROMPT_TEMPLATES.md +16 -16
  42. package/rules/.hermes.md +11 -11
  43. package/rules/AGENTS.md +22 -22
  44. package/rules/workspace-README.md +5 -5
  45. package/scripts/apply-update-workframe.sh +91 -21
  46. package/scripts/bundle-workframe-ui.mjs +83 -83
  47. package/scripts/create_workframe_scaffold.py +648 -648
  48. package/scripts/ensure-compose-host-paths.mjs +51 -51
  49. package/scripts/lib/install-identity.mjs +212 -212
  50. package/scripts/lib/workframe-registry.mjs +290 -290
  51. package/scripts/new-project.mjs +68 -68
  52. package/scripts/select_agent_pack.py +31 -31
  53. package/scripts/set-compose-public-url.mjs +92 -92
  54. package/scripts/sync-canonical-to-package.mjs +164 -164
  55. package/shared/WORKFRAME_AGENT_LIBRARY.md +31 -31
  56. package/shared/WORKFRAME_AGENT_OPERATIONS.md +29 -29
  57. package/shared/WORKFRAME_AGENT_PACKS.json +64 -64
  58. package/shared/WORKFRAME_AGENT_PACKS.yaml +20 -20
  59. package/shared/WORKFRAME_CHAT_PERMISSION_MODEL.md +20 -20
  60. package/shared/WORKFRAME_HANDOFF_SCHEMA.md +25 -25
  61. package/shared/WORKFRAME_SKILL_CURATION.md +27 -27
  62. package/workframe-api/Dockerfile +14 -14
  63. package/workframe-api/README.md +24 -24
  64. package/workframe-api/action_proxy.py +131 -131
  65. package/workframe-api/auth_rate_limit.py +49 -49
  66. package/workframe-api/credential_vault.py +445 -445
  67. package/workframe-api/data/avatar-catalog.json +41 -41
  68. package/workframe-api/data/logo-catalog.json +14 -14
  69. package/workframe-api/data/user-avatar-catalog.json +18 -18
  70. package/workframe-api/email_sender.py +220 -220
  71. package/workframe-api/google_auth.py +90 -90
  72. package/workframe-api/install_api.py +359 -359
  73. package/workframe-api/internal_proxy_auth.py +150 -150
  74. package/workframe-api/llm_proxy.py +280 -280
  75. package/workframe-api/oidc_jwt.py +108 -108
  76. package/workframe-api/package.json +12 -12
  77. package/workframe-api/platform_auth.py +194 -194
  78. package/workframe-api/profile_secret_policy.py +86 -86
  79. package/workframe-api/public/assets/index-DPXu_lGn.css +1 -1
  80. package/workframe-api/public/assets/index-DYnLrCZZ.js +8 -8
  81. package/workframe-api/public/assets/index-DglUqFB_.js +8 -8
  82. package/workframe-api/public/index.html +12 -12
  83. package/workframe-api/requirements.txt +2 -2
  84. package/workframe-api/server.py +19866 -19774
  85. package/workframe-api/site_meta.py +271 -271
  86. package/workframe-api/stack_config.py +427 -427
  87. package/workframe-api/test_billing_provider.py +24 -0
  88. package/workframe-api/time-bind-chat.py +99 -99
  89. package/workframe-api/turn_credentials.py +226 -226
  90. package/workframe-api/updates.py +506 -506
  91. package/workframe-api/vault_kek.py +159 -159
  92. package/workframe-api/zk_auth.py +633 -633
  93. package/workframe-supervisor/Dockerfile +11 -11
  94. package/workframe-supervisor/profile_secret_policy.py +76 -76
  95. package/workframe-supervisor/server.py +860 -860
  96. package/workframe-ui/docker/nginx.conf +85 -85
  97. package/workframe-ui/public/assets/{arc-BskqxLmE.js → arc-DoYr2T5c.js} +1 -1
  98. package/workframe-ui/public/assets/architecture-7EHR7CIX-CmDi28hh.js +1 -0
  99. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-B3eGdqvq.js → architectureDiagram-3BPJPVTR-Clher08G.js} +1 -1
  100. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-CzFZG7uX.js → blockDiagram-GPEHLZMM-Bdw9txrZ.js} +1 -1
  101. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BS-gGyCH.js → c4Diagram-AAUBKEIU-DlI_h_uo.js} +1 -1
  102. package/workframe-ui/public/assets/channel-DZYVoEdD.js +1 -0
  103. package/workframe-ui/public/assets/{chunk-2J33WTMH-Cjvqf7c9.js → chunk-2J33WTMH-ol-uwoeq.js} +1 -1
  104. package/workframe-ui/public/assets/{chunk-3OPIFGDE--eUuL8AK.js → chunk-3OPIFGDE-7eDhBen0.js} +1 -1
  105. package/workframe-ui/public/assets/{chunk-4BX2VUAB-s9lHUFKL.js → chunk-4BX2VUAB-xPORD7Js.js} +1 -1
  106. package/workframe-ui/public/assets/{chunk-55IACEB6-D1McA5nz.js → chunk-55IACEB6-Cwd7dr1z.js} +1 -1
  107. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DIstLfU2.js → chunk-5ZQYHXKU-Ckt4Sjy1.js} +1 -1
  108. package/workframe-ui/public/assets/{chunk-727SXJPM-Cd0GD2CO.js → chunk-727SXJPM-sMk1op_G.js} +1 -1
  109. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-gc5Ra6G4.js → chunk-AQP2D5EJ-CMgSvr6H.js} +1 -1
  110. package/workframe-ui/public/assets/{chunk-BSJP7CBP-Cg5QfihJ.js → chunk-BSJP7CBP-Dx7GN0T4.js} +1 -1
  111. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-D_gobbhY.js → chunk-CSCIHK7Q--OCu86u0.js} +1 -1
  112. package/workframe-ui/public/assets/{chunk-FMBD7UC4-B6eLY9_d.js → chunk-FMBD7UC4-CejmwJpz.js} +1 -1
  113. package/workframe-ui/public/assets/{chunk-KSCS5N6A-BGroAeW2.js → chunk-KSCS5N6A-CvOj2Yvp.js} +1 -1
  114. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-34xv7WgT.js → chunk-L5ZTLDWV-B0nGBjLU.js} +1 -1
  115. package/workframe-ui/public/assets/{chunk-LZXEDZCA-XJBctFZw.js → chunk-LZXEDZCA-Bdzvu227.js} +2 -2
  116. package/workframe-ui/public/assets/{chunk-ND2GUHAM-nTOZpln8.js → chunk-ND2GUHAM-CGR07jTY.js} +1 -1
  117. package/workframe-ui/public/assets/{chunk-NZK2D7GU-BBrzcaPH.js → chunk-NZK2D7GU-CBojDPNC.js} +1 -1
  118. package/workframe-ui/public/assets/{chunk-O5CBEL6O-Cntncx6L.js → chunk-O5CBEL6O-DBin5MPF.js} +1 -1
  119. package/workframe-ui/public/assets/chunk-QZHKN3VN-DwiajAaK.js +1 -0
  120. package/workframe-ui/public/assets/chunk-WU5MYG2G-Bz8vA5s7.js +1 -0
  121. package/workframe-ui/public/assets/{chunk-XPW4576I-DSbtd0cg.js → chunk-XPW4576I-TD7I7pcb.js} +1 -1
  122. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-DQiq8od0.js +1 -0
  123. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-DQiq8od0.js +1 -0
  124. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-lmYstEMQ.js → cose-bilkent-S5V4N54A-Bmm5x5xm.js} +1 -1
  125. package/workframe-ui/public/assets/{dagre-BM42HDAG-Bdkbdul2.js → dagre-BM42HDAG-zU2r0aq2.js} +1 -1
  126. package/workframe-ui/public/assets/{diagram-2AECGRRQ-Clb9kSXe.js → diagram-2AECGRRQ-AL01qukm.js} +1 -1
  127. package/workframe-ui/public/assets/{diagram-5GNKFQAL-BApg0yec.js → diagram-5GNKFQAL-DKi1xSMg.js} +1 -1
  128. package/workframe-ui/public/assets/{diagram-KO2AKTUF-peZjDRV6.js → diagram-KO2AKTUF-DQJDQ-f-.js} +1 -1
  129. package/workframe-ui/public/assets/{diagram-LMA3HP47-DZm_1pjc.js → diagram-LMA3HP47-B1oisGUj.js} +1 -1
  130. package/workframe-ui/public/assets/{diagram-OG6HWLK6-DP_N6fYL.js → diagram-OG6HWLK6-DjpJKffk.js} +1 -1
  131. package/workframe-ui/public/assets/{dist-CqP43hXK.js → dist-Jk20AnQG.js} +1 -1
  132. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-Blfs6kLv.js → erDiagram-TEJ5UH35-Vu8RtLUf.js} +1 -1
  133. package/workframe-ui/public/assets/eventmodeling-FCH6USID-BXUnTe0c.js +1 -0
  134. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-CcmMOkwH.js → flowDiagram-I6XJVG4X-CHAXEhbR.js} +1 -1
  135. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-ByoO3dF7.js → ganttDiagram-6RSMTGT7-DhiEkfH8.js} +1 -1
  136. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-DCyTpmPs.js → gitGraph-WXDBUCRP-Cuq44XvU.js} +1 -1
  137. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-CvLYB55b.js → gitGraphDiagram-PVQCEYII-B7kgReRt.js} +1 -1
  138. package/workframe-ui/public/assets/index-72-j0sA5.js +129 -0
  139. package/workframe-ui/public/assets/index-auJ0bLMU.css +1 -0
  140. package/workframe-ui/public/assets/{info-J43DQDTF-CfSRIfOI.js → info-J43DQDTF-oV4b3M4f.js} +1 -1
  141. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-DwS44DS6.js → infoDiagram-5YYISTIA-CthN4b6o.js} +1 -1
  142. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-MRlyodyU.js → ishikawaDiagram-YF4QCWOH-oEGS3TKg.js} +1 -1
  143. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-sq7awk87.js → journeyDiagram-JHISSGLW-CXJsWmKF.js} +1 -1
  144. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-CH6HbcWu.js → kanban-definition-UN3LZRKU-DTPo8yHW.js} +1 -1
  145. package/workframe-ui/public/assets/{line-DRBjFmUW.js → line-Bm9Pmcnz.js} +1 -1
  146. package/workframe-ui/public/assets/{linear-BR0LunjE.js → linear-Cn-uD6eX.js} +1 -1
  147. package/workframe-ui/public/assets/{mermaid-parser.core-Cl1GjHkT.js → mermaid-parser.core-BBVSTQZ4.js} +2 -2
  148. package/workframe-ui/public/assets/{mermaid.core-DsiKK1kE.js → mermaid.core-DE668nUy.js} +3 -3
  149. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-DBvi3OpW.js → mindmap-definition-RKZ34NQL-4qVsiwCf.js} +1 -1
  150. package/workframe-ui/public/assets/{packet-YPE3B663-CPDgtUHg.js → packet-YPE3B663-CqJeoiFG.js} +1 -1
  151. package/workframe-ui/public/assets/{pie-LRSECV5Y-BXJIPPDm.js → pie-LRSECV5Y-DcOqVssg.js} +1 -1
  152. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-CZwLEK-o.js → pieDiagram-4H26LBE5-CosMnpAM.js} +1 -1
  153. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-B65bEUIQ.js → quadrantDiagram-W4KKPZXB-DtsnIK98.js} +1 -1
  154. package/workframe-ui/public/assets/{radar-GUYGQ44K-vXHkZzRK.js → radar-GUYGQ44K-BqLpJuWc.js} +1 -1
  155. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-BGWdvDfG.js → requirementDiagram-4Y6WPE33-Pb7cuX8_.js} +1 -1
  156. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-BT2oC8dV.js → sankeyDiagram-5OEKKPKP-BJBlMM1m.js} +1 -1
  157. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-45yj0Wtx.js → sequenceDiagram-3UESZ5HK-i4HnjP39.js} +1 -1
  158. package/workframe-ui/public/assets/{src-D-P8U2PB.js → src-DIhmc_XG.js} +1 -1
  159. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-SBrBJBZG.js → stateDiagram-AJRCARHV-Ba2iClPT.js} +1 -1
  160. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-aUpqRAhy.js +1 -0
  161. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DegmxXXj.js → timeline-definition-PNZ67QCA-CL4X9vSF.js} +1 -1
  162. package/workframe-ui/public/assets/{treeView-BLDUP644-BVy7CsCj.js → treeView-BLDUP644-BJrjnbMj.js} +1 -1
  163. package/workframe-ui/public/assets/{treemap-LRROVOQU-BD5Kb74J.js → treemap-LRROVOQU-CN6H54RF.js} +1 -1
  164. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-hwAkNbl3.js → vennDiagram-CIIHVFJN-Cee6sbUy.js} +1 -1
  165. package/workframe-ui/public/assets/{wardley-L42UT6IY-CBMPT8Y1.js → wardley-L42UT6IY-D0cXq60c.js} +1 -1
  166. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DXqihVlE.js → wardleyDiagram-YWT4CUSO-CaH9XkCx.js} +1 -1
  167. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-Cbb4LBzj.js → xychartDiagram-2RQKCTM6-BEisgFoI.js} +1 -1
  168. package/workframe-ui/public/favicon.svg +7 -7
  169. package/workframe-ui/public/icons.svg +24 -24
  170. package/workframe-ui/public/index.html +47 -50
  171. package/workframe-ui/public/manifest.webmanifest +18 -18
  172. package/workframe-ui/public/workframe-config.json +3 -3
  173. package/workframe-ui/public/assets/architecture-7EHR7CIX-DOxsUI33.js +0 -1
  174. package/workframe-ui/public/assets/channel-Baa20pIs.js +0 -1
  175. package/workframe-ui/public/assets/chunk-QZHKN3VN-B7tPxs4H.js +0 -1
  176. package/workframe-ui/public/assets/chunk-WU5MYG2G-Dvh2By1o.js +0 -1
  177. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BljHyDzF.js +0 -1
  178. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BljHyDzF.js +0 -1
  179. package/workframe-ui/public/assets/eventmodeling-FCH6USID-C4xwO6dx.js +0 -1
  180. package/workframe-ui/public/assets/index-BIfRGM8p.js +0 -133
  181. package/workframe-ui/public/assets/index-DpoUZAxh.css +0 -1
  182. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B3im3So2.js +0 -1
@@ -1,11 +1,11 @@
1
- FROM python:3-alpine
2
-
3
- # ponytail: host.setup_public_https and compose restart shell out to docker CLI (socket is mounted).
4
- # set-compose-public-url.mjs needs node on the supervisor image.
5
- RUN apk add --no-cache docker-cli docker-cli-compose bash nodejs npm
6
-
7
- WORKDIR /app
8
- COPY server.py profile_secret_policy.py .
9
-
10
- EXPOSE 8090
11
- CMD ["python3", "server.py"]
1
+ FROM python:3-alpine
2
+
3
+ # ponytail: host.setup_public_https and compose restart shell out to docker CLI (socket is mounted).
4
+ # set-compose-public-url.mjs needs node on the supervisor image.
5
+ RUN apk add --no-cache docker-cli docker-cli-compose bash nodejs npm
6
+
7
+ WORKDIR /app
8
+ COPY server.py profile_secret_policy.py .
9
+
10
+ EXPOSE 8090
11
+ CMD ["python3", "server.py"]
@@ -1,76 +1,76 @@
1
- """Speed bump for cross-profile secret reads — keep in sync with workframe-api/profile_secret_policy.py."""
2
- from __future__ import annotations
3
-
4
- import re
5
-
6
- _PROFILE_SECRET_PATH = re.compile(
7
- r"(?:/opt/data/)?profiles/[^/\s]+/(?:\.env|auth\.json|credentials\.json|\.git-credentials|cookies\.txt)",
8
- re.IGNORECASE,
9
- )
10
- _PROFILE_SLUG_REF = re.compile(
11
- r"(?:/opt/data/)?profiles/([a-z0-9][a-z0-9-]{0,63})(?:/|$|\s)",
12
- re.IGNORECASE,
13
- )
14
- _READ_VERBS = re.compile(
15
- r"\b(?:cat|head|tail|sed|awk|grep|egrep|rg|less|more|od|xxd|nl|sort|wc|strings|dd|python3?|perl|ruby|node)\b",
16
- re.IGNORECASE,
17
- )
18
- _SHELL_BYPASS = re.compile(
19
- r"(?:\*|find\b[^\n]*-exec\b|xargs\b|\btar\b|\bbase64\b|\.e''nv|\.e\"\"nv"
20
- r"|&&\s*(?:cat|head|tail|sed|awk|grep|python3?|perl|dd|od|xxd|nl|sort|wc)\b"
21
- r"|\|\s*(?:cat|head|tail|sed|awk|grep|python3?|perl|dd|od|xxd|nl|sort|wc)\b)",
22
- re.IGNORECASE,
23
- )
24
- _VAR_PROFILE_ASSIGN = re.compile(
25
- r"\b([A-Za-z_][A-Za-z0-9_]*)=(?:[^\s;&|]*\s*)?(?:/opt/data/)?profiles/[a-z0-9][a-z0-9-]{0,63}",
26
- re.IGNORECASE,
27
- )
28
- _VAR_DEREF_SECRET = re.compile(
29
- r"\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?[^\s;&|]*/(?:\.env|auth\.json|credentials\.json|\.git-credentials|cookies\.txt)",
30
- re.IGNORECASE,
31
- )
32
-
33
-
34
- def _cmd_blob(cmd: list[str] | str) -> str:
35
- return " ".join(str(part) for part in cmd) if isinstance(cmd, list) else str(cmd)
36
-
37
-
38
- def referenced_profile_slugs(cmd: list[str] | str) -> set[str]:
39
- return {m.group(1).lower() for m in _PROFILE_SLUG_REF.finditer(_cmd_blob(cmd))}
40
-
41
-
42
- def is_secret_read_attempt(cmd: list[str] | str) -> bool:
43
- blob = _cmd_blob(cmd)
44
- if _PROFILE_SECRET_PATH.search(blob):
45
- return True
46
- if "profiles/" in blob.lower() and _SHELL_BYPASS.search(blob):
47
- return True
48
- if _PROFILE_SLUG_REF.search(blob) and _READ_VERBS.search(blob):
49
- return True
50
- assigned = {m.group(1) for m in _VAR_PROFILE_ASSIGN.finditer(blob)}
51
- if assigned:
52
- for m in _VAR_DEREF_SECRET.finditer(blob):
53
- if m.group(1) in assigned:
54
- return True
55
- return False
56
-
57
-
58
- def touches_foreign_profile_secrets(cmd: list[str] | str, allowed_profile: str) -> bool:
59
- allowed = str(allowed_profile or "").strip().lower()
60
- if not allowed:
61
- return False
62
- slugs = referenced_profile_slugs(cmd)
63
- return bool(slugs - {allowed})
64
-
65
-
66
- def exec_blocked_for_profile(cmd: list[str] | str, acting_profile: str = "") -> bool:
67
- if is_secret_read_attempt(cmd):
68
- return True
69
- if acting_profile and touches_foreign_profile_secrets(cmd, acting_profile):
70
- return True
71
- return False
72
-
73
-
74
- if __name__ == "__main__":
75
- assert is_secret_read_attempt("cd /opt/data/profiles/u-bob && head .env")
76
- print("profile_secret_policy ok")
1
+ """Speed bump for cross-profile secret reads — keep in sync with workframe-api/profile_secret_policy.py."""
2
+ from __future__ import annotations
3
+
4
+ import re
5
+
6
+ _PROFILE_SECRET_PATH = re.compile(
7
+ r"(?:/opt/data/)?profiles/[^/\s]+/(?:\.env|auth\.json|credentials\.json|\.git-credentials|cookies\.txt)",
8
+ re.IGNORECASE,
9
+ )
10
+ _PROFILE_SLUG_REF = re.compile(
11
+ r"(?:/opt/data/)?profiles/([a-z0-9][a-z0-9-]{0,63})(?:/|$|\s)",
12
+ re.IGNORECASE,
13
+ )
14
+ _READ_VERBS = re.compile(
15
+ r"\b(?:cat|head|tail|sed|awk|grep|egrep|rg|less|more|od|xxd|nl|sort|wc|strings|dd|python3?|perl|ruby|node)\b",
16
+ re.IGNORECASE,
17
+ )
18
+ _SHELL_BYPASS = re.compile(
19
+ r"(?:\*|find\b[^\n]*-exec\b|xargs\b|\btar\b|\bbase64\b|\.e''nv|\.e\"\"nv"
20
+ r"|&&\s*(?:cat|head|tail|sed|awk|grep|python3?|perl|dd|od|xxd|nl|sort|wc)\b"
21
+ r"|\|\s*(?:cat|head|tail|sed|awk|grep|python3?|perl|dd|od|xxd|nl|sort|wc)\b)",
22
+ re.IGNORECASE,
23
+ )
24
+ _VAR_PROFILE_ASSIGN = re.compile(
25
+ r"\b([A-Za-z_][A-Za-z0-9_]*)=(?:[^\s;&|]*\s*)?(?:/opt/data/)?profiles/[a-z0-9][a-z0-9-]{0,63}",
26
+ re.IGNORECASE,
27
+ )
28
+ _VAR_DEREF_SECRET = re.compile(
29
+ r"\$\{?([A-Za-z_][A-Za-z0-9_]*)\}?[^\s;&|]*/(?:\.env|auth\.json|credentials\.json|\.git-credentials|cookies\.txt)",
30
+ re.IGNORECASE,
31
+ )
32
+
33
+
34
+ def _cmd_blob(cmd: list[str] | str) -> str:
35
+ return " ".join(str(part) for part in cmd) if isinstance(cmd, list) else str(cmd)
36
+
37
+
38
+ def referenced_profile_slugs(cmd: list[str] | str) -> set[str]:
39
+ return {m.group(1).lower() for m in _PROFILE_SLUG_REF.finditer(_cmd_blob(cmd))}
40
+
41
+
42
+ def is_secret_read_attempt(cmd: list[str] | str) -> bool:
43
+ blob = _cmd_blob(cmd)
44
+ if _PROFILE_SECRET_PATH.search(blob):
45
+ return True
46
+ if "profiles/" in blob.lower() and _SHELL_BYPASS.search(blob):
47
+ return True
48
+ if _PROFILE_SLUG_REF.search(blob) and _READ_VERBS.search(blob):
49
+ return True
50
+ assigned = {m.group(1) for m in _VAR_PROFILE_ASSIGN.finditer(blob)}
51
+ if assigned:
52
+ for m in _VAR_DEREF_SECRET.finditer(blob):
53
+ if m.group(1) in assigned:
54
+ return True
55
+ return False
56
+
57
+
58
+ def touches_foreign_profile_secrets(cmd: list[str] | str, allowed_profile: str) -> bool:
59
+ allowed = str(allowed_profile or "").strip().lower()
60
+ if not allowed:
61
+ return False
62
+ slugs = referenced_profile_slugs(cmd)
63
+ return bool(slugs - {allowed})
64
+
65
+
66
+ def exec_blocked_for_profile(cmd: list[str] | str, acting_profile: str = "") -> bool:
67
+ if is_secret_read_attempt(cmd):
68
+ return True
69
+ if acting_profile and touches_foreign_profile_secrets(cmd, acting_profile):
70
+ return True
71
+ return False
72
+
73
+
74
+ if __name__ == "__main__":
75
+ assert is_secret_read_attempt("cd /opt/data/profiles/u-bob && head .env")
76
+ print("profile_secret_policy ok")