@jaguilar87/gaia 5.0.0-rc1

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 (609) hide show
  1. package/.claude-plugin/marketplace.json +33 -0
  2. package/.claude-plugin/plugin.json +26 -0
  3. package/ARCHITECTURE.md +335 -0
  4. package/CHANGELOG.md +1212 -0
  5. package/CODE_OF_CONDUCT.md +11 -0
  6. package/CONTRIBUTING.md +146 -0
  7. package/INSTALL.md +436 -0
  8. package/LICENSE +21 -0
  9. package/README.md +222 -0
  10. package/SECURITY.md +47 -0
  11. package/agents/README.md +78 -0
  12. package/agents/cloud-troubleshooter.md +73 -0
  13. package/agents/developer.md +65 -0
  14. package/agents/gaia-operator.md +64 -0
  15. package/agents/gaia-orchestrator.md +237 -0
  16. package/agents/gaia-planner.md +53 -0
  17. package/agents/gaia-system.md +70 -0
  18. package/agents/gitops-operator.md +61 -0
  19. package/agents/terraform-architect.md +63 -0
  20. package/bin/README.md +106 -0
  21. package/bin/cli/__init__.py +1 -0
  22. package/bin/cli/approvals.py +740 -0
  23. package/bin/cli/cleanup.py +562 -0
  24. package/bin/cli/context.py +283 -0
  25. package/bin/cli/doctor.py +628 -0
  26. package/bin/cli/history.py +305 -0
  27. package/bin/cli/memory.py +464 -0
  28. package/bin/cli/metrics.py +1068 -0
  29. package/bin/cli/plans.py +515 -0
  30. package/bin/cli/status.py +302 -0
  31. package/bin/cli/update.py +382 -0
  32. package/bin/gaia +112 -0
  33. package/bin/gaia-cleanup.js +531 -0
  34. package/bin/gaia-doctor.js +635 -0
  35. package/bin/gaia-evidence +126 -0
  36. package/bin/gaia-history.js +251 -0
  37. package/bin/gaia-metrics.js +1278 -0
  38. package/bin/gaia-review.js +269 -0
  39. package/bin/gaia-scan +44 -0
  40. package/bin/gaia-scan.py +589 -0
  41. package/bin/gaia-skills-diagnose.js +929 -0
  42. package/bin/gaia-status.js +278 -0
  43. package/bin/gaia-uninstall.js +111 -0
  44. package/bin/gaia-update.js +816 -0
  45. package/bin/pre-publish-validate.js +610 -0
  46. package/bin/python-detect.js +60 -0
  47. package/commands/README.md +64 -0
  48. package/commands/gaia.md +37 -0
  49. package/commands/scan-project.md +67 -0
  50. package/config/README.md +71 -0
  51. package/config/cloud/aws.json +134 -0
  52. package/config/cloud/gcp.json +139 -0
  53. package/config/context-contracts.json +158 -0
  54. package/config/crons-schema.md +81 -0
  55. package/config/git_standards.json +72 -0
  56. package/config/surface-routing.json +421 -0
  57. package/config/universal-rules.json +102 -0
  58. package/dist/gaia-ops/.claude-plugin/plugin.json +24 -0
  59. package/dist/gaia-ops/README.md +80 -0
  60. package/dist/gaia-ops/agents/cloud-troubleshooter.md +73 -0
  61. package/dist/gaia-ops/agents/developer.md +65 -0
  62. package/dist/gaia-ops/agents/gaia-operator.md +64 -0
  63. package/dist/gaia-ops/agents/gaia-orchestrator.md +237 -0
  64. package/dist/gaia-ops/agents/gaia-planner.md +53 -0
  65. package/dist/gaia-ops/agents/gaia-system.md +70 -0
  66. package/dist/gaia-ops/agents/gitops-operator.md +61 -0
  67. package/dist/gaia-ops/agents/terraform-architect.md +63 -0
  68. package/dist/gaia-ops/commands/gaia.md +37 -0
  69. package/dist/gaia-ops/config/README.md +71 -0
  70. package/dist/gaia-ops/config/cloud/aws.json +134 -0
  71. package/dist/gaia-ops/config/cloud/gcp.json +139 -0
  72. package/dist/gaia-ops/config/context-contracts.json +158 -0
  73. package/dist/gaia-ops/config/crons-schema.md +81 -0
  74. package/dist/gaia-ops/config/git_standards.json +72 -0
  75. package/dist/gaia-ops/config/surface-routing.json +421 -0
  76. package/dist/gaia-ops/config/universal-rules.json +102 -0
  77. package/dist/gaia-ops/hooks/adapters/__init__.py +52 -0
  78. package/dist/gaia-ops/hooks/adapters/base.py +219 -0
  79. package/dist/gaia-ops/hooks/adapters/channel.py +17 -0
  80. package/dist/gaia-ops/hooks/adapters/claude_code.py +1890 -0
  81. package/dist/gaia-ops/hooks/adapters/types.py +194 -0
  82. package/dist/gaia-ops/hooks/adapters/utils.py +25 -0
  83. package/dist/gaia-ops/hooks/hooks.json +163 -0
  84. package/dist/gaia-ops/hooks/modules/__init__.py +15 -0
  85. package/dist/gaia-ops/hooks/modules/agents/__init__.py +29 -0
  86. package/dist/gaia-ops/hooks/modules/agents/contract_validator.py +647 -0
  87. package/dist/gaia-ops/hooks/modules/agents/response_contract.py +496 -0
  88. package/dist/gaia-ops/hooks/modules/agents/skill_injection_verifier.py +120 -0
  89. package/dist/gaia-ops/hooks/modules/agents/state_tracker.py +267 -0
  90. package/dist/gaia-ops/hooks/modules/agents/task_info_builder.py +74 -0
  91. package/dist/gaia-ops/hooks/modules/agents/transcript_analyzer.py +458 -0
  92. package/dist/gaia-ops/hooks/modules/agents/transcript_reader.py +152 -0
  93. package/dist/gaia-ops/hooks/modules/audit/__init__.py +28 -0
  94. package/dist/gaia-ops/hooks/modules/audit/event_detector.py +168 -0
  95. package/dist/gaia-ops/hooks/modules/audit/logger.py +131 -0
  96. package/dist/gaia-ops/hooks/modules/audit/metrics.py +134 -0
  97. package/dist/gaia-ops/hooks/modules/audit/workflow_auditor.py +611 -0
  98. package/dist/gaia-ops/hooks/modules/audit/workflow_recorder.py +296 -0
  99. package/dist/gaia-ops/hooks/modules/context/__init__.py +11 -0
  100. package/dist/gaia-ops/hooks/modules/context/agentic_loop_detector.py +165 -0
  101. package/dist/gaia-ops/hooks/modules/context/anchor_tracker.py +317 -0
  102. package/dist/gaia-ops/hooks/modules/context/compact_context_builder.py +218 -0
  103. package/dist/gaia-ops/hooks/modules/context/context_freshness.py +145 -0
  104. package/dist/gaia-ops/hooks/modules/context/context_injector.py +558 -0
  105. package/dist/gaia-ops/hooks/modules/context/context_writer.py +530 -0
  106. package/dist/gaia-ops/hooks/modules/context/contracts_loader.py +161 -0
  107. package/dist/gaia-ops/hooks/modules/core/__init__.py +40 -0
  108. package/dist/gaia-ops/hooks/modules/core/hook_entry.py +78 -0
  109. package/dist/gaia-ops/hooks/modules/core/paths.py +160 -0
  110. package/dist/gaia-ops/hooks/modules/core/plugin_mode.py +149 -0
  111. package/dist/gaia-ops/hooks/modules/core/plugin_setup.py +577 -0
  112. package/dist/gaia-ops/hooks/modules/core/state.py +179 -0
  113. package/dist/gaia-ops/hooks/modules/core/stdin.py +24 -0
  114. package/dist/gaia-ops/hooks/modules/events/__init__.py +1 -0
  115. package/dist/gaia-ops/hooks/modules/events/event_writer.py +210 -0
  116. package/dist/gaia-ops/hooks/modules/memory/__init__.py +8 -0
  117. package/dist/gaia-ops/hooks/modules/memory/episode_writer.py +216 -0
  118. package/dist/gaia-ops/hooks/modules/orchestrator/__init__.py +1 -0
  119. package/dist/gaia-ops/hooks/modules/orchestrator/delegate_mode.py +122 -0
  120. package/dist/gaia-ops/hooks/modules/scanning/__init__.py +8 -0
  121. package/dist/gaia-ops/hooks/modules/scanning/scan_trigger.py +84 -0
  122. package/dist/gaia-ops/hooks/modules/security/__init__.py +120 -0
  123. package/dist/gaia-ops/hooks/modules/security/approval_cleanup.py +87 -0
  124. package/dist/gaia-ops/hooks/modules/security/approval_constants.py +23 -0
  125. package/dist/gaia-ops/hooks/modules/security/approval_grants.py +1638 -0
  126. package/dist/gaia-ops/hooks/modules/security/approval_messages.py +71 -0
  127. package/dist/gaia-ops/hooks/modules/security/approval_scopes.py +222 -0
  128. package/dist/gaia-ops/hooks/modules/security/blocked_commands.py +595 -0
  129. package/dist/gaia-ops/hooks/modules/security/blocked_message_formatter.py +87 -0
  130. package/dist/gaia-ops/hooks/modules/security/command_semantics.py +181 -0
  131. package/dist/gaia-ops/hooks/modules/security/composition_rules.py +547 -0
  132. package/dist/gaia-ops/hooks/modules/security/flag_classifiers.py +873 -0
  133. package/dist/gaia-ops/hooks/modules/security/gitops_validator.py +179 -0
  134. package/dist/gaia-ops/hooks/modules/security/mutative_verbs.py +1131 -0
  135. package/dist/gaia-ops/hooks/modules/security/network_hosts.py +481 -0
  136. package/dist/gaia-ops/hooks/modules/security/prompt_validator.py +40 -0
  137. package/dist/gaia-ops/hooks/modules/security/shell_unwrapper.py +165 -0
  138. package/dist/gaia-ops/hooks/modules/security/tiers.py +196 -0
  139. package/dist/gaia-ops/hooks/modules/session/__init__.py +10 -0
  140. package/dist/gaia-ops/hooks/modules/session/pending_scanner.py +174 -0
  141. package/dist/gaia-ops/hooks/modules/session/session_context_writer.py +100 -0
  142. package/dist/gaia-ops/hooks/modules/session/session_event_injector.py +160 -0
  143. package/dist/gaia-ops/hooks/modules/session/session_manager.py +31 -0
  144. package/dist/gaia-ops/hooks/modules/session/session_registry.py +232 -0
  145. package/dist/gaia-ops/hooks/modules/tools/__init__.py +29 -0
  146. package/dist/gaia-ops/hooks/modules/tools/bash_validator.py +1008 -0
  147. package/dist/gaia-ops/hooks/modules/tools/cloud_pipe_validator.py +231 -0
  148. package/dist/gaia-ops/hooks/modules/tools/hook_response.py +55 -0
  149. package/dist/gaia-ops/hooks/modules/tools/shell_parser.py +227 -0
  150. package/dist/gaia-ops/hooks/modules/tools/stage_decomposer.py +315 -0
  151. package/dist/gaia-ops/hooks/modules/tools/task_validator.py +294 -0
  152. package/dist/gaia-ops/hooks/modules/validation/__init__.py +23 -0
  153. package/dist/gaia-ops/hooks/modules/validation/commit_validator.py +380 -0
  154. package/dist/gaia-ops/hooks/post_compact.py +43 -0
  155. package/dist/gaia-ops/hooks/post_tool_use.py +54 -0
  156. package/dist/gaia-ops/hooks/pre_compact.py +60 -0
  157. package/dist/gaia-ops/hooks/pre_tool_use.py +413 -0
  158. package/dist/gaia-ops/hooks/session_start.py +81 -0
  159. package/dist/gaia-ops/hooks/stop_hook.py +82 -0
  160. package/dist/gaia-ops/hooks/subagent_start.py +71 -0
  161. package/dist/gaia-ops/hooks/subagent_stop.py +295 -0
  162. package/dist/gaia-ops/hooks/task_completed.py +70 -0
  163. package/dist/gaia-ops/hooks/user_prompt_submit.py +246 -0
  164. package/dist/gaia-ops/settings.json +72 -0
  165. package/dist/gaia-ops/skills/README.md +154 -0
  166. package/dist/gaia-ops/skills/agent-protocol/SKILL.md +93 -0
  167. package/dist/gaia-ops/skills/agent-protocol/examples.md +223 -0
  168. package/dist/gaia-ops/skills/agent-response/SKILL.md +69 -0
  169. package/dist/gaia-ops/skills/agentic-loop/SKILL.md +80 -0
  170. package/dist/gaia-ops/skills/agentic-loop/reference.md +378 -0
  171. package/dist/gaia-ops/skills/blog-writing/SKILL.md +98 -0
  172. package/dist/gaia-ops/skills/blog-writing/reference.md +130 -0
  173. package/dist/gaia-ops/skills/brief-spec/SKILL.md +182 -0
  174. package/dist/gaia-ops/skills/command-execution/SKILL.md +64 -0
  175. package/dist/gaia-ops/skills/command-execution/reference.md +83 -0
  176. package/dist/gaia-ops/skills/context-updater/SKILL.md +87 -0
  177. package/dist/gaia-ops/skills/context-updater/examples.md +71 -0
  178. package/dist/gaia-ops/skills/developer-patterns/SKILL.md +50 -0
  179. package/dist/gaia-ops/skills/developer-patterns/reference.md +112 -0
  180. package/dist/gaia-ops/skills/execution/SKILL.md +99 -0
  181. package/dist/gaia-ops/skills/fast-queries/SKILL.md +43 -0
  182. package/dist/gaia-ops/skills/gaia-compact/SKILL.md +74 -0
  183. package/dist/gaia-ops/skills/gaia-patterns/SKILL.md +108 -0
  184. package/dist/gaia-ops/skills/gaia-patterns/reference.md +395 -0
  185. package/dist/gaia-ops/skills/gaia-planner/SKILL.md +37 -0
  186. package/dist/gaia-ops/skills/gaia-planner/reference.md +107 -0
  187. package/dist/gaia-ops/skills/gaia-release/SKILL.md +82 -0
  188. package/dist/gaia-ops/skills/gaia-release/reference.md +102 -0
  189. package/dist/gaia-ops/skills/gaia-self-check/SKILL.md +114 -0
  190. package/dist/gaia-ops/skills/gaia-self-check/reference.md +453 -0
  191. package/dist/gaia-ops/skills/gaia-verify/SKILL.md +77 -0
  192. package/dist/gaia-ops/skills/gaia-verify/reference.md +80 -0
  193. package/dist/gaia-ops/skills/git-conventions/SKILL.md +47 -0
  194. package/dist/gaia-ops/skills/gitops-patterns/SKILL.md +60 -0
  195. package/dist/gaia-ops/skills/gitops-patterns/reference.md +183 -0
  196. package/dist/gaia-ops/skills/gmail-policy/SKILL.md +200 -0
  197. package/dist/gaia-ops/skills/gmail-policy/reference.md +150 -0
  198. package/dist/gaia-ops/skills/gmail-triage/SKILL.md +100 -0
  199. package/dist/gaia-ops/skills/gws-setup/SKILL.md +99 -0
  200. package/dist/gaia-ops/skills/gws-setup/reference.md +73 -0
  201. package/dist/gaia-ops/skills/investigation/SKILL.md +100 -0
  202. package/dist/gaia-ops/skills/memory-curation/SKILL.md +83 -0
  203. package/dist/gaia-ops/skills/memory-search/SKILL.md +88 -0
  204. package/dist/gaia-ops/skills/orchestrator-approval/SKILL.md +160 -0
  205. package/dist/gaia-ops/skills/orchestrator-approval/reference.md +174 -0
  206. package/dist/gaia-ops/skills/pending-approvals/SKILL.md +72 -0
  207. package/dist/gaia-ops/skills/pending-approvals/reference.md +214 -0
  208. package/dist/gaia-ops/skills/readme-writing/SKILL.md +71 -0
  209. package/dist/gaia-ops/skills/readme-writing/reference.md +188 -0
  210. package/dist/gaia-ops/skills/reference.md +135 -0
  211. package/dist/gaia-ops/skills/request-approval/SKILL.md +140 -0
  212. package/dist/gaia-ops/skills/request-approval/examples.md +140 -0
  213. package/dist/gaia-ops/skills/request-approval/reference.md +57 -0
  214. package/dist/gaia-ops/skills/schedule-task/SKILL.md +64 -0
  215. package/dist/gaia-ops/skills/schedule-task/reference.md +233 -0
  216. package/dist/gaia-ops/skills/security-tiers/SKILL.md +141 -0
  217. package/dist/gaia-ops/skills/security-tiers/destructive-commands-reference.md +623 -0
  218. package/dist/gaia-ops/skills/security-tiers/reference.md +39 -0
  219. package/dist/gaia-ops/skills/skill-creation/SKILL.md +92 -0
  220. package/dist/gaia-ops/skills/skill-creation/reference.md +29 -0
  221. package/dist/gaia-ops/skills/terraform-patterns/SKILL.md +89 -0
  222. package/dist/gaia-ops/skills/terraform-patterns/reference.md +93 -0
  223. package/dist/gaia-ops/tools/__init__.py +9 -0
  224. package/dist/gaia-ops/tools/agentic-loop/decide-status.py +210 -0
  225. package/dist/gaia-ops/tools/agentic-loop/parse-metric.py +106 -0
  226. package/dist/gaia-ops/tools/agentic-loop/record-iteration.py +221 -0
  227. package/dist/gaia-ops/tools/context/README.md +132 -0
  228. package/dist/gaia-ops/tools/context/__init__.py +42 -0
  229. package/dist/gaia-ops/tools/context/_paths.py +20 -0
  230. package/dist/gaia-ops/tools/context/context_provider.py +721 -0
  231. package/dist/gaia-ops/tools/context/context_section_reader.py +342 -0
  232. package/dist/gaia-ops/tools/context/deep_merge.py +159 -0
  233. package/dist/gaia-ops/tools/context/pending_updates.py +760 -0
  234. package/dist/gaia-ops/tools/context/surface_router.py +278 -0
  235. package/dist/gaia-ops/tools/fast-queries/README.md +65 -0
  236. package/dist/gaia-ops/tools/fast-queries/__init__.py +30 -0
  237. package/dist/gaia-ops/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
  238. package/dist/gaia-ops/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
  239. package/dist/gaia-ops/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
  240. package/dist/gaia-ops/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
  241. package/dist/gaia-ops/tools/fast-queries/run_triage.sh +59 -0
  242. package/dist/gaia-ops/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
  243. package/dist/gaia-ops/tools/gaia_simulator/__init__.py +33 -0
  244. package/dist/gaia-ops/tools/gaia_simulator/cli.py +354 -0
  245. package/dist/gaia-ops/tools/gaia_simulator/extractor.py +457 -0
  246. package/dist/gaia-ops/tools/gaia_simulator/reporter.py +258 -0
  247. package/dist/gaia-ops/tools/gaia_simulator/routing_simulator.py +334 -0
  248. package/dist/gaia-ops/tools/gaia_simulator/runner.py +539 -0
  249. package/dist/gaia-ops/tools/gaia_simulator/skills_mapper.py +264 -0
  250. package/dist/gaia-ops/tools/memory/README.md +0 -0
  251. package/dist/gaia-ops/tools/memory/__init__.py +20 -0
  252. package/dist/gaia-ops/tools/memory/backfill_fts5.py +107 -0
  253. package/dist/gaia-ops/tools/memory/conflict_detector.py +295 -0
  254. package/dist/gaia-ops/tools/memory/episodic.py +1210 -0
  255. package/dist/gaia-ops/tools/memory/git_invalidator.py +262 -0
  256. package/dist/gaia-ops/tools/memory/paths.py +102 -0
  257. package/dist/gaia-ops/tools/memory/scoring.py +193 -0
  258. package/dist/gaia-ops/tools/memory/search_store.py +360 -0
  259. package/dist/gaia-ops/tools/persist_transcript_analysis.py +85 -0
  260. package/dist/gaia-ops/tools/review/__init__.py +1 -0
  261. package/dist/gaia-ops/tools/review/review_engine.py +157 -0
  262. package/dist/gaia-ops/tools/scan/__init__.py +35 -0
  263. package/dist/gaia-ops/tools/scan/config.py +247 -0
  264. package/dist/gaia-ops/tools/scan/merge.py +212 -0
  265. package/dist/gaia-ops/tools/scan/orchestrator.py +549 -0
  266. package/dist/gaia-ops/tools/scan/registry.py +127 -0
  267. package/dist/gaia-ops/tools/scan/scanners/__init__.py +18 -0
  268. package/dist/gaia-ops/tools/scan/scanners/base.py +137 -0
  269. package/dist/gaia-ops/tools/scan/scanners/environment.py +349 -0
  270. package/dist/gaia-ops/tools/scan/scanners/git.py +570 -0
  271. package/dist/gaia-ops/tools/scan/scanners/infrastructure.py +875 -0
  272. package/dist/gaia-ops/tools/scan/scanners/orchestration.py +600 -0
  273. package/dist/gaia-ops/tools/scan/scanners/stack.py +1085 -0
  274. package/dist/gaia-ops/tools/scan/scanners/tools.py +260 -0
  275. package/dist/gaia-ops/tools/scan/setup.py +686 -0
  276. package/dist/gaia-ops/tools/scan/tests/__init__.py +1 -0
  277. package/dist/gaia-ops/tools/scan/tests/conftest.py +796 -0
  278. package/dist/gaia-ops/tools/scan/tests/test_environment.py +323 -0
  279. package/dist/gaia-ops/tools/scan/tests/test_git.py +419 -0
  280. package/dist/gaia-ops/tools/scan/tests/test_infrastructure.py +382 -0
  281. package/dist/gaia-ops/tools/scan/tests/test_integration.py +920 -0
  282. package/dist/gaia-ops/tools/scan/tests/test_merge.py +269 -0
  283. package/dist/gaia-ops/tools/scan/tests/test_orchestration.py +304 -0
  284. package/dist/gaia-ops/tools/scan/tests/test_stack.py +604 -0
  285. package/dist/gaia-ops/tools/scan/tests/test_tools.py +349 -0
  286. package/dist/gaia-ops/tools/scan/ui.py +624 -0
  287. package/dist/gaia-ops/tools/scan/verify.py +270 -0
  288. package/dist/gaia-ops/tools/scan/walk.py +118 -0
  289. package/dist/gaia-ops/tools/scan/workspace.py +85 -0
  290. package/dist/gaia-ops/tools/validation/README.md +244 -0
  291. package/dist/gaia-ops/tools/validation/__init__.py +17 -0
  292. package/dist/gaia-ops/tools/validation/approval_gate.py +321 -0
  293. package/dist/gaia-ops/tools/validation/validate_skills.py +189 -0
  294. package/dist/gaia-security/.claude-plugin/plugin.json +24 -0
  295. package/dist/gaia-security/README.md +90 -0
  296. package/dist/gaia-security/config/universal-rules.json +102 -0
  297. package/dist/gaia-security/hooks/adapters/__init__.py +52 -0
  298. package/dist/gaia-security/hooks/adapters/base.py +219 -0
  299. package/dist/gaia-security/hooks/adapters/channel.py +17 -0
  300. package/dist/gaia-security/hooks/adapters/claude_code.py +1890 -0
  301. package/dist/gaia-security/hooks/adapters/types.py +194 -0
  302. package/dist/gaia-security/hooks/adapters/utils.py +25 -0
  303. package/dist/gaia-security/hooks/hooks.json +84 -0
  304. package/dist/gaia-security/hooks/modules/__init__.py +15 -0
  305. package/dist/gaia-security/hooks/modules/agents/__init__.py +29 -0
  306. package/dist/gaia-security/hooks/modules/agents/contract_validator.py +647 -0
  307. package/dist/gaia-security/hooks/modules/agents/response_contract.py +496 -0
  308. package/dist/gaia-security/hooks/modules/agents/skill_injection_verifier.py +120 -0
  309. package/dist/gaia-security/hooks/modules/agents/state_tracker.py +267 -0
  310. package/dist/gaia-security/hooks/modules/agents/task_info_builder.py +74 -0
  311. package/dist/gaia-security/hooks/modules/agents/transcript_analyzer.py +458 -0
  312. package/dist/gaia-security/hooks/modules/agents/transcript_reader.py +152 -0
  313. package/dist/gaia-security/hooks/modules/audit/__init__.py +28 -0
  314. package/dist/gaia-security/hooks/modules/audit/event_detector.py +168 -0
  315. package/dist/gaia-security/hooks/modules/audit/logger.py +131 -0
  316. package/dist/gaia-security/hooks/modules/audit/metrics.py +134 -0
  317. package/dist/gaia-security/hooks/modules/audit/workflow_auditor.py +611 -0
  318. package/dist/gaia-security/hooks/modules/audit/workflow_recorder.py +296 -0
  319. package/dist/gaia-security/hooks/modules/context/__init__.py +11 -0
  320. package/dist/gaia-security/hooks/modules/context/agentic_loop_detector.py +165 -0
  321. package/dist/gaia-security/hooks/modules/context/anchor_tracker.py +317 -0
  322. package/dist/gaia-security/hooks/modules/context/compact_context_builder.py +218 -0
  323. package/dist/gaia-security/hooks/modules/context/context_freshness.py +145 -0
  324. package/dist/gaia-security/hooks/modules/context/context_injector.py +558 -0
  325. package/dist/gaia-security/hooks/modules/context/context_writer.py +530 -0
  326. package/dist/gaia-security/hooks/modules/context/contracts_loader.py +161 -0
  327. package/dist/gaia-security/hooks/modules/core/__init__.py +40 -0
  328. package/dist/gaia-security/hooks/modules/core/hook_entry.py +78 -0
  329. package/dist/gaia-security/hooks/modules/core/paths.py +160 -0
  330. package/dist/gaia-security/hooks/modules/core/plugin_mode.py +149 -0
  331. package/dist/gaia-security/hooks/modules/core/plugin_setup.py +577 -0
  332. package/dist/gaia-security/hooks/modules/core/state.py +179 -0
  333. package/dist/gaia-security/hooks/modules/core/stdin.py +24 -0
  334. package/dist/gaia-security/hooks/modules/events/__init__.py +1 -0
  335. package/dist/gaia-security/hooks/modules/events/event_writer.py +210 -0
  336. package/dist/gaia-security/hooks/modules/memory/__init__.py +8 -0
  337. package/dist/gaia-security/hooks/modules/memory/episode_writer.py +216 -0
  338. package/dist/gaia-security/hooks/modules/orchestrator/__init__.py +1 -0
  339. package/dist/gaia-security/hooks/modules/orchestrator/delegate_mode.py +122 -0
  340. package/dist/gaia-security/hooks/modules/scanning/__init__.py +8 -0
  341. package/dist/gaia-security/hooks/modules/scanning/scan_trigger.py +84 -0
  342. package/dist/gaia-security/hooks/modules/security/__init__.py +120 -0
  343. package/dist/gaia-security/hooks/modules/security/approval_cleanup.py +87 -0
  344. package/dist/gaia-security/hooks/modules/security/approval_constants.py +23 -0
  345. package/dist/gaia-security/hooks/modules/security/approval_grants.py +1638 -0
  346. package/dist/gaia-security/hooks/modules/security/approval_messages.py +71 -0
  347. package/dist/gaia-security/hooks/modules/security/approval_scopes.py +222 -0
  348. package/dist/gaia-security/hooks/modules/security/blocked_commands.py +595 -0
  349. package/dist/gaia-security/hooks/modules/security/blocked_message_formatter.py +87 -0
  350. package/dist/gaia-security/hooks/modules/security/command_semantics.py +181 -0
  351. package/dist/gaia-security/hooks/modules/security/composition_rules.py +547 -0
  352. package/dist/gaia-security/hooks/modules/security/flag_classifiers.py +873 -0
  353. package/dist/gaia-security/hooks/modules/security/gitops_validator.py +179 -0
  354. package/dist/gaia-security/hooks/modules/security/mutative_verbs.py +1131 -0
  355. package/dist/gaia-security/hooks/modules/security/network_hosts.py +481 -0
  356. package/dist/gaia-security/hooks/modules/security/prompt_validator.py +40 -0
  357. package/dist/gaia-security/hooks/modules/security/shell_unwrapper.py +165 -0
  358. package/dist/gaia-security/hooks/modules/security/tiers.py +196 -0
  359. package/dist/gaia-security/hooks/modules/session/__init__.py +10 -0
  360. package/dist/gaia-security/hooks/modules/session/pending_scanner.py +174 -0
  361. package/dist/gaia-security/hooks/modules/session/session_context_writer.py +100 -0
  362. package/dist/gaia-security/hooks/modules/session/session_event_injector.py +160 -0
  363. package/dist/gaia-security/hooks/modules/session/session_manager.py +31 -0
  364. package/dist/gaia-security/hooks/modules/session/session_registry.py +232 -0
  365. package/dist/gaia-security/hooks/modules/tools/__init__.py +29 -0
  366. package/dist/gaia-security/hooks/modules/tools/bash_validator.py +1008 -0
  367. package/dist/gaia-security/hooks/modules/tools/cloud_pipe_validator.py +231 -0
  368. package/dist/gaia-security/hooks/modules/tools/hook_response.py +55 -0
  369. package/dist/gaia-security/hooks/modules/tools/shell_parser.py +227 -0
  370. package/dist/gaia-security/hooks/modules/tools/stage_decomposer.py +315 -0
  371. package/dist/gaia-security/hooks/modules/tools/task_validator.py +294 -0
  372. package/dist/gaia-security/hooks/modules/validation/__init__.py +23 -0
  373. package/dist/gaia-security/hooks/modules/validation/commit_validator.py +380 -0
  374. package/dist/gaia-security/hooks/post_tool_use.py +54 -0
  375. package/dist/gaia-security/hooks/pre_tool_use.py +413 -0
  376. package/dist/gaia-security/hooks/session_start.py +81 -0
  377. package/dist/gaia-security/hooks/stop_hook.py +82 -0
  378. package/dist/gaia-security/hooks/user_prompt_submit.py +246 -0
  379. package/dist/gaia-security/settings.json +58 -0
  380. package/git-hooks/commit-msg +41 -0
  381. package/hooks/README.md +100 -0
  382. package/hooks/adapters/__init__.py +52 -0
  383. package/hooks/adapters/base.py +219 -0
  384. package/hooks/adapters/channel.py +17 -0
  385. package/hooks/adapters/claude_code.py +1890 -0
  386. package/hooks/adapters/types.py +194 -0
  387. package/hooks/adapters/utils.py +25 -0
  388. package/hooks/elicitation_result.py +179 -0
  389. package/hooks/hooks.json +84 -0
  390. package/hooks/modules/README.md +189 -0
  391. package/hooks/modules/__init__.py +15 -0
  392. package/hooks/modules/agents/__init__.py +29 -0
  393. package/hooks/modules/agents/contract_validator.py +647 -0
  394. package/hooks/modules/agents/response_contract.py +496 -0
  395. package/hooks/modules/agents/skill_injection_verifier.py +120 -0
  396. package/hooks/modules/agents/state_tracker.py +267 -0
  397. package/hooks/modules/agents/task_info_builder.py +74 -0
  398. package/hooks/modules/agents/transcript_analyzer.py +458 -0
  399. package/hooks/modules/agents/transcript_reader.py +152 -0
  400. package/hooks/modules/audit/__init__.py +28 -0
  401. package/hooks/modules/audit/event_detector.py +168 -0
  402. package/hooks/modules/audit/logger.py +131 -0
  403. package/hooks/modules/audit/metrics.py +134 -0
  404. package/hooks/modules/audit/workflow_auditor.py +611 -0
  405. package/hooks/modules/audit/workflow_recorder.py +296 -0
  406. package/hooks/modules/context/__init__.py +11 -0
  407. package/hooks/modules/context/agentic_loop_detector.py +165 -0
  408. package/hooks/modules/context/anchor_tracker.py +317 -0
  409. package/hooks/modules/context/compact_context_builder.py +218 -0
  410. package/hooks/modules/context/context_freshness.py +145 -0
  411. package/hooks/modules/context/context_injector.py +558 -0
  412. package/hooks/modules/context/context_writer.py +530 -0
  413. package/hooks/modules/context/contracts_loader.py +161 -0
  414. package/hooks/modules/core/__init__.py +40 -0
  415. package/hooks/modules/core/hook_entry.py +78 -0
  416. package/hooks/modules/core/paths.py +160 -0
  417. package/hooks/modules/core/plugin_mode.py +149 -0
  418. package/hooks/modules/core/plugin_setup.py +577 -0
  419. package/hooks/modules/core/state.py +179 -0
  420. package/hooks/modules/core/stdin.py +24 -0
  421. package/hooks/modules/events/__init__.py +1 -0
  422. package/hooks/modules/events/event_writer.py +210 -0
  423. package/hooks/modules/evidence/__init__.py +34 -0
  424. package/hooks/modules/evidence/assertions.py +137 -0
  425. package/hooks/modules/evidence/index_writer.py +57 -0
  426. package/hooks/modules/evidence/loader.py +126 -0
  427. package/hooks/modules/evidence/runner.py +241 -0
  428. package/hooks/modules/memory/__init__.py +8 -0
  429. package/hooks/modules/memory/episode_writer.py +216 -0
  430. package/hooks/modules/orchestrator/__init__.py +1 -0
  431. package/hooks/modules/orchestrator/delegate_mode.py +122 -0
  432. package/hooks/modules/scanning/__init__.py +8 -0
  433. package/hooks/modules/scanning/scan_trigger.py +84 -0
  434. package/hooks/modules/security/__init__.py +120 -0
  435. package/hooks/modules/security/approval_cleanup.py +87 -0
  436. package/hooks/modules/security/approval_constants.py +23 -0
  437. package/hooks/modules/security/approval_grants.py +1638 -0
  438. package/hooks/modules/security/approval_messages.py +71 -0
  439. package/hooks/modules/security/approval_scopes.py +222 -0
  440. package/hooks/modules/security/blocked_commands.py +595 -0
  441. package/hooks/modules/security/blocked_message_formatter.py +87 -0
  442. package/hooks/modules/security/command_semantics.py +181 -0
  443. package/hooks/modules/security/composition_rules.py +547 -0
  444. package/hooks/modules/security/flag_classifiers.py +873 -0
  445. package/hooks/modules/security/gitops_validator.py +179 -0
  446. package/hooks/modules/security/mutative_verbs.py +1131 -0
  447. package/hooks/modules/security/network_hosts.py +481 -0
  448. package/hooks/modules/security/prompt_validator.py +40 -0
  449. package/hooks/modules/security/shell_unwrapper.py +165 -0
  450. package/hooks/modules/security/tiers.py +196 -0
  451. package/hooks/modules/session/__init__.py +10 -0
  452. package/hooks/modules/session/pending_scanner.py +174 -0
  453. package/hooks/modules/session/session_context_writer.py +100 -0
  454. package/hooks/modules/session/session_event_injector.py +160 -0
  455. package/hooks/modules/session/session_manager.py +31 -0
  456. package/hooks/modules/session/session_registry.py +232 -0
  457. package/hooks/modules/tools/__init__.py +29 -0
  458. package/hooks/modules/tools/bash_validator.py +1008 -0
  459. package/hooks/modules/tools/cloud_pipe_validator.py +231 -0
  460. package/hooks/modules/tools/hook_response.py +55 -0
  461. package/hooks/modules/tools/shell_parser.py +227 -0
  462. package/hooks/modules/tools/stage_decomposer.py +315 -0
  463. package/hooks/modules/tools/task_validator.py +294 -0
  464. package/hooks/modules/validation/__init__.py +23 -0
  465. package/hooks/modules/validation/commit_validator.py +380 -0
  466. package/hooks/post_compact.py +43 -0
  467. package/hooks/post_tool_use.py +54 -0
  468. package/hooks/pre_compact.py +60 -0
  469. package/hooks/pre_tool_use.py +413 -0
  470. package/hooks/session_start.py +81 -0
  471. package/hooks/stop_hook.py +82 -0
  472. package/hooks/subagent_start.py +71 -0
  473. package/hooks/subagent_stop.py +295 -0
  474. package/hooks/task_completed.py +70 -0
  475. package/hooks/user_prompt_submit.py +246 -0
  476. package/index.js +83 -0
  477. package/package.json +99 -0
  478. package/pyproject.toml +32 -0
  479. package/skills/README.md +154 -0
  480. package/skills/agent-protocol/SKILL.md +93 -0
  481. package/skills/agent-protocol/examples.md +223 -0
  482. package/skills/agent-response/SKILL.md +69 -0
  483. package/skills/agentic-loop/SKILL.md +80 -0
  484. package/skills/agentic-loop/reference.md +378 -0
  485. package/skills/blog-writing/SKILL.md +98 -0
  486. package/skills/blog-writing/reference.md +130 -0
  487. package/skills/brief-spec/SKILL.md +182 -0
  488. package/skills/command-execution/SKILL.md +64 -0
  489. package/skills/command-execution/reference.md +83 -0
  490. package/skills/context-updater/SKILL.md +87 -0
  491. package/skills/context-updater/examples.md +71 -0
  492. package/skills/developer-patterns/SKILL.md +50 -0
  493. package/skills/developer-patterns/reference.md +112 -0
  494. package/skills/execution/SKILL.md +99 -0
  495. package/skills/fast-queries/SKILL.md +43 -0
  496. package/skills/gaia-compact/SKILL.md +74 -0
  497. package/skills/gaia-patterns/SKILL.md +108 -0
  498. package/skills/gaia-patterns/reference.md +395 -0
  499. package/skills/gaia-planner/SKILL.md +37 -0
  500. package/skills/gaia-planner/reference.md +107 -0
  501. package/skills/gaia-release/SKILL.md +82 -0
  502. package/skills/gaia-release/reference.md +102 -0
  503. package/skills/gaia-self-check/SKILL.md +114 -0
  504. package/skills/gaia-self-check/reference.md +453 -0
  505. package/skills/gaia-verify/SKILL.md +77 -0
  506. package/skills/gaia-verify/reference.md +80 -0
  507. package/skills/git-conventions/SKILL.md +47 -0
  508. package/skills/gitops-patterns/SKILL.md +60 -0
  509. package/skills/gitops-patterns/reference.md +183 -0
  510. package/skills/gmail-policy/SKILL.md +200 -0
  511. package/skills/gmail-policy/reference.md +150 -0
  512. package/skills/gmail-triage/SKILL.md +100 -0
  513. package/skills/gws-setup/SKILL.md +99 -0
  514. package/skills/gws-setup/reference.md +73 -0
  515. package/skills/investigation/SKILL.md +100 -0
  516. package/skills/memory-curation/SKILL.md +83 -0
  517. package/skills/memory-search/SKILL.md +88 -0
  518. package/skills/orchestrator-approval/SKILL.md +160 -0
  519. package/skills/orchestrator-approval/reference.md +174 -0
  520. package/skills/pending-approvals/SKILL.md +72 -0
  521. package/skills/pending-approvals/reference.md +214 -0
  522. package/skills/readme-writing/SKILL.md +71 -0
  523. package/skills/readme-writing/reference.md +188 -0
  524. package/skills/reference.md +135 -0
  525. package/skills/request-approval/SKILL.md +140 -0
  526. package/skills/request-approval/examples.md +140 -0
  527. package/skills/request-approval/reference.md +57 -0
  528. package/skills/schedule-task/SKILL.md +64 -0
  529. package/skills/schedule-task/reference.md +233 -0
  530. package/skills/security-tiers/SKILL.md +141 -0
  531. package/skills/security-tiers/destructive-commands-reference.md +623 -0
  532. package/skills/security-tiers/reference.md +39 -0
  533. package/skills/skill-creation/SKILL.md +92 -0
  534. package/skills/skill-creation/reference.md +29 -0
  535. package/skills/terraform-patterns/SKILL.md +89 -0
  536. package/skills/terraform-patterns/reference.md +93 -0
  537. package/templates/README.md +69 -0
  538. package/templates/managed-settings.template.json +43 -0
  539. package/tools/__init__.py +9 -0
  540. package/tools/agentic-loop/decide-status.py +210 -0
  541. package/tools/agentic-loop/parse-metric.py +106 -0
  542. package/tools/agentic-loop/record-iteration.py +221 -0
  543. package/tools/context/README.md +132 -0
  544. package/tools/context/__init__.py +42 -0
  545. package/tools/context/_paths.py +20 -0
  546. package/tools/context/context_provider.py +721 -0
  547. package/tools/context/context_section_reader.py +342 -0
  548. package/tools/context/deep_merge.py +159 -0
  549. package/tools/context/pending_updates.py +760 -0
  550. package/tools/context/surface_router.py +278 -0
  551. package/tools/fast-queries/README.md +65 -0
  552. package/tools/fast-queries/__init__.py +30 -0
  553. package/tools/fast-queries/appservices/quicktriage_devops_developer.sh +75 -0
  554. package/tools/fast-queries/cloud/aws/quicktriage_aws_troubleshooter.sh +32 -0
  555. package/tools/fast-queries/cloud/gcp/quicktriage_gcp_troubleshooter.sh +88 -0
  556. package/tools/fast-queries/gitops/quicktriage_gitops_operator.sh +48 -0
  557. package/tools/fast-queries/run_triage.sh +59 -0
  558. package/tools/fast-queries/terraform/quicktriage_terraform_architect.sh +80 -0
  559. package/tools/gaia_simulator/__init__.py +33 -0
  560. package/tools/gaia_simulator/cli.py +354 -0
  561. package/tools/gaia_simulator/extractor.py +457 -0
  562. package/tools/gaia_simulator/reporter.py +258 -0
  563. package/tools/gaia_simulator/routing_simulator.py +334 -0
  564. package/tools/gaia_simulator/runner.py +539 -0
  565. package/tools/gaia_simulator/skills_mapper.py +264 -0
  566. package/tools/memory/README.md +0 -0
  567. package/tools/memory/__init__.py +20 -0
  568. package/tools/memory/backfill_fts5.py +107 -0
  569. package/tools/memory/conflict_detector.py +295 -0
  570. package/tools/memory/episodic.py +1210 -0
  571. package/tools/memory/git_invalidator.py +262 -0
  572. package/tools/memory/paths.py +102 -0
  573. package/tools/memory/scoring.py +193 -0
  574. package/tools/memory/search_store.py +360 -0
  575. package/tools/persist_transcript_analysis.py +85 -0
  576. package/tools/review/__init__.py +1 -0
  577. package/tools/review/review_engine.py +157 -0
  578. package/tools/scan/__init__.py +35 -0
  579. package/tools/scan/config.py +247 -0
  580. package/tools/scan/merge.py +212 -0
  581. package/tools/scan/orchestrator.py +549 -0
  582. package/tools/scan/registry.py +127 -0
  583. package/tools/scan/scanners/__init__.py +18 -0
  584. package/tools/scan/scanners/base.py +137 -0
  585. package/tools/scan/scanners/environment.py +349 -0
  586. package/tools/scan/scanners/git.py +570 -0
  587. package/tools/scan/scanners/infrastructure.py +875 -0
  588. package/tools/scan/scanners/orchestration.py +600 -0
  589. package/tools/scan/scanners/stack.py +1085 -0
  590. package/tools/scan/scanners/tools.py +260 -0
  591. package/tools/scan/setup.py +686 -0
  592. package/tools/scan/tests/__init__.py +1 -0
  593. package/tools/scan/tests/conftest.py +796 -0
  594. package/tools/scan/tests/test_environment.py +323 -0
  595. package/tools/scan/tests/test_git.py +419 -0
  596. package/tools/scan/tests/test_infrastructure.py +382 -0
  597. package/tools/scan/tests/test_integration.py +920 -0
  598. package/tools/scan/tests/test_merge.py +269 -0
  599. package/tools/scan/tests/test_orchestration.py +304 -0
  600. package/tools/scan/tests/test_stack.py +604 -0
  601. package/tools/scan/tests/test_tools.py +349 -0
  602. package/tools/scan/ui.py +624 -0
  603. package/tools/scan/verify.py +270 -0
  604. package/tools/scan/walk.py +118 -0
  605. package/tools/scan/workspace.py +85 -0
  606. package/tools/validation/README.md +244 -0
  607. package/tools/validation/__init__.py +17 -0
  608. package/tools/validation/approval_gate.py +321 -0
  609. package/tools/validation/validate_skills.py +189 -0
@@ -0,0 +1,481 @@
1
+ """
2
+ Network host classification for curl/wget/httpie command targets.
3
+
4
+ This module is a helper consumed by flag_classifiers.py (T3). It is NOT called
5
+ directly from bash_validator.py.
6
+
7
+ Classification categories:
8
+ LOCALHOST -- localhost, 127.0.0.1, ::1, 0.0.0.0, private RFC 1918 ranges
9
+ KNOWN_REGISTRY -- well-known development/CI registries (npm, PyPI, GitHub, etc.)
10
+ UNKNOWN -- all other external targets (triggers T3 approval for GET requests)
11
+
12
+ Private IP ranges (RFC 1918):
13
+ 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
14
+ These are treated as LOCALHOST (internal network) for classification purposes.
15
+
16
+ Usage in flag_classifiers (T3):
17
+ - GET + KNOWN_REGISTRY / LOCALHOST => READ_ONLY
18
+ - GET + UNKNOWN => MUTATIVE (T3 approval required)
19
+ - POST/PUT/DELETE/PATCH (any host) => MUTATIVE (always, regardless of host)
20
+
21
+ Dependencies: Python stdlib only (urllib.parse).
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import re
27
+ import urllib.parse
28
+ from dataclasses import dataclass
29
+ from enum import Enum
30
+ from typing import List, Optional
31
+
32
+
33
+ # ---------------------------------------------------------------------------
34
+ # Known-safe registries (GET-only; POST to any host is still MUTATIVE)
35
+ # ---------------------------------------------------------------------------
36
+ # This is a module-level constant. Extend by adding entries to the frozenset.
37
+
38
+ KNOWN_REGISTRIES: frozenset = frozenset({
39
+ # npm
40
+ "registry.npmjs.org",
41
+ "registry.yarnpkg.com",
42
+ "registry.npmmirror.com",
43
+ "www.npmjs.org",
44
+ "npmjs.org",
45
+ # PyPI
46
+ "pypi.org",
47
+ "files.pythonhosted.org",
48
+ # GitHub
49
+ "github.com",
50
+ "api.github.com",
51
+ "raw.githubusercontent.com",
52
+ # Rust / Go / Ruby / PHP
53
+ "crates.io",
54
+ "rubygems.org",
55
+ "packagist.org",
56
+ "pkg.go.dev",
57
+ "proxy.golang.org",
58
+ # Other common CI/build targets
59
+ "dl.google.com",
60
+ "repo.maven.apache.org",
61
+ # Docker registries
62
+ "hub.docker.com",
63
+ "registry.hub.docker.com",
64
+ "ghcr.io",
65
+ })
66
+
67
+ # Localhost addresses (exact match after port stripping)
68
+ _LOCALHOST_EXACT: frozenset = frozenset({
69
+ "localhost",
70
+ "127.0.0.1",
71
+ "0.0.0.0",
72
+ "::1",
73
+ })
74
+
75
+ # RFC 1918 private ranges
76
+ _RFC1918_10 = re.compile(r"^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
77
+ _RFC1918_172 = re.compile(r"^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$")
78
+ _RFC1918_192 = re.compile(r"^192\.168\.\d{1,3}\.\d{1,3}$")
79
+ # Link-local
80
+ _LINK_LOCAL = re.compile(r"^169\.254\.\d{1,3}\.\d{1,3}$")
81
+ # Loopback range beyond 127.0.0.1
82
+ _LOOPBACK_RANGE = re.compile(r"^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
83
+ # *.local mDNS
84
+ _MDNS_LOCAL = re.compile(r"\.local$", re.IGNORECASE)
85
+
86
+
87
+ # ---------------------------------------------------------------------------
88
+ # Result types
89
+ # ---------------------------------------------------------------------------
90
+
91
+ class HostCategory(str, Enum):
92
+ """Classification category for a network host."""
93
+ LOCALHOST = "LOCALHOST"
94
+ KNOWN_REGISTRY = "KNOWN_REGISTRY"
95
+ UNKNOWN = "UNKNOWN"
96
+
97
+
98
+ @dataclass(frozen=True)
99
+ class HostClassification:
100
+ """Structured result of network host classification.
101
+
102
+ Attributes:
103
+ host: Normalized hostname (no port, no scheme).
104
+ category: One of HostCategory.LOCALHOST, KNOWN_REGISTRY, or UNKNOWN.
105
+ reason: Human-readable explanation.
106
+ """
107
+ host: str
108
+ category: HostCategory
109
+ reason: str
110
+
111
+ @property
112
+ def is_local(self) -> bool:
113
+ return self.category == HostCategory.LOCALHOST
114
+
115
+ @property
116
+ def is_known_registry(self) -> bool:
117
+ return self.category == HostCategory.KNOWN_REGISTRY
118
+
119
+ @property
120
+ def is_unknown(self) -> bool:
121
+ return self.category == HostCategory.UNKNOWN
122
+
123
+
124
+ # ---------------------------------------------------------------------------
125
+ # Host extraction helpers
126
+ # ---------------------------------------------------------------------------
127
+
128
+ def _strip_port(host: str) -> str:
129
+ """Remove port suffix from a bare hostname (host:port -> host)."""
130
+ if host.startswith("["):
131
+ # IPv6 bracketed form: [::1]:8080 -> ::1
132
+ bracket_end = host.find("]")
133
+ if bracket_end != -1:
134
+ return host[1:bracket_end]
135
+ return host
136
+ # Bare IPv6 addresses contain multiple colons (e.g. ::1, fe80::1)
137
+ # Only strip port when there is exactly one colon (host:port form)
138
+ if host.count(":") == 1:
139
+ return host.rsplit(":", 1)[0]
140
+ return host
141
+
142
+
143
+ def _extract_host_from_url(url: str) -> Optional[str]:
144
+ """Extract the hostname from a URL string.
145
+
146
+ Handles:
147
+ - Full URLs with scheme: https://host:port/path
148
+ - Scheme-relative URLs: //host/path
149
+ - Bare hostname+path: host/path or host:port/path
150
+ - IPv6 literal: http://[::1]:8080/
151
+
152
+ Returns the hostname string without port, or None if not parseable.
153
+ """
154
+ url = url.strip()
155
+ if not url:
156
+ return None
157
+
158
+ # URLs with explicit scheme
159
+ if "://" in url:
160
+ try:
161
+ parsed = urllib.parse.urlparse(url)
162
+ if parsed.hostname:
163
+ return parsed.hostname.lower()
164
+ except ValueError:
165
+ pass
166
+ return None
167
+
168
+ # Scheme-relative: //host/path
169
+ if url.startswith("//"):
170
+ try:
171
+ parsed = urllib.parse.urlparse("https:" + url)
172
+ if parsed.hostname:
173
+ return parsed.hostname.lower()
174
+ except ValueError:
175
+ pass
176
+ return None
177
+
178
+ # Bare form: host/path, host:port, host:port/path
179
+ # Split off any path component
180
+ host_part = url.split("/")[0]
181
+ if not host_part:
182
+ return None
183
+ return _strip_port(host_part).lower()
184
+
185
+
186
+ # ---------------------------------------------------------------------------
187
+ # Primary classification logic
188
+ # ---------------------------------------------------------------------------
189
+
190
+ def _is_localhost(host: str) -> bool:
191
+ """Return True when the host is a loopback/local address."""
192
+ if host in _LOCALHOST_EXACT:
193
+ return True
194
+ if _LOOPBACK_RANGE.match(host):
195
+ return True
196
+ if _RFC1918_10.match(host):
197
+ return True
198
+ if _RFC1918_172.match(host):
199
+ return True
200
+ if _RFC1918_192.match(host):
201
+ return True
202
+ if _LINK_LOCAL.match(host):
203
+ return True
204
+ if _MDNS_LOCAL.search(host):
205
+ return True
206
+ return False
207
+
208
+
209
+ def classify_host(url_or_host: str) -> HostClassification:
210
+ """Classify a URL or bare hostname into LOCALHOST, KNOWN_REGISTRY, or UNKNOWN.
211
+
212
+ Args:
213
+ url_or_host: A full URL (https://registry.npmjs.org/express),
214
+ a bare hostname (registry.npmjs.org),
215
+ or host:port (localhost:8080).
216
+
217
+ Returns:
218
+ HostClassification with category and reason.
219
+ """
220
+ if not url_or_host or not url_or_host.strip():
221
+ return HostClassification(
222
+ host="",
223
+ category=HostCategory.UNKNOWN,
224
+ reason="empty host/url",
225
+ )
226
+
227
+ raw = url_or_host.strip()
228
+
229
+ # Attempt to extract the hostname component
230
+ host = _extract_host_from_url(raw)
231
+ if not host:
232
+ return HostClassification(
233
+ host=raw,
234
+ category=HostCategory.UNKNOWN,
235
+ reason=f"could not parse host from {raw!r}",
236
+ )
237
+
238
+ # 1. Localhost / loopback / private ranges
239
+ if _is_localhost(host):
240
+ return HostClassification(
241
+ host=host,
242
+ category=HostCategory.LOCALHOST,
243
+ reason=f"localhost/private: {host}",
244
+ )
245
+
246
+ # 2. Known registries
247
+ if host in KNOWN_REGISTRIES:
248
+ return HostClassification(
249
+ host=host,
250
+ category=HostCategory.KNOWN_REGISTRY,
251
+ reason=f"known registry: {host}",
252
+ )
253
+
254
+ # 3. Unknown external target
255
+ return HostClassification(
256
+ host=host,
257
+ category=HostCategory.UNKNOWN,
258
+ reason=f"unknown external host: {host}",
259
+ )
260
+
261
+
262
+ # ---------------------------------------------------------------------------
263
+ # URL extraction from command token lists
264
+ # ---------------------------------------------------------------------------
265
+
266
+ # Flags that consume the NEXT token as their value (not a URL)
267
+ _CURL_VALUE_FLAGS: frozenset = frozenset({
268
+ "-H", "--header",
269
+ "-u", "--user",
270
+ "-A", "--user-agent",
271
+ "-e", "--referer",
272
+ "-o", "--output",
273
+ "-O", "--remote-name",
274
+ "--output-dir",
275
+ "-x", "--proxy",
276
+ "--proxy-user",
277
+ "-T", "--upload-file",
278
+ "-d", "--data",
279
+ "--data-raw", "--data-binary", "--data-urlencode",
280
+ "-F", "--form", "--form-string",
281
+ "-X", "--request",
282
+ "-b", "--cookie",
283
+ "-c", "--cookie-jar",
284
+ "--max-time", "-m",
285
+ "--connect-timeout",
286
+ "-r", "--range",
287
+ "--resolve",
288
+ "--cacert", "--capath",
289
+ "--cert", "--key",
290
+ "--tlspassword",
291
+ "-w", "--write-out",
292
+ "--interface",
293
+ "--dns-servers",
294
+ "--parallel-max",
295
+ "--json",
296
+ "-K", "--config",
297
+ "--limit-rate",
298
+ "--max-redirs",
299
+ "--retry",
300
+ "--retry-delay",
301
+ "--retry-max-time",
302
+ "--socks4", "--socks4a", "--socks5", "--socks5-hostname",
303
+ })
304
+
305
+ _WGET_VALUE_FLAGS: frozenset = frozenset({
306
+ "-O", "--output-document",
307
+ "-o", "--output-file",
308
+ "--append-output",
309
+ "-P", "--directory-prefix",
310
+ "-U", "--user-agent",
311
+ "--referer",
312
+ "--header",
313
+ "--http-user", "--http-password",
314
+ "--proxy-user", "--proxy-password",
315
+ "--post-data", "--post-file",
316
+ "--method",
317
+ "--body-data", "--body-file",
318
+ "--ca-certificate", "--ca-directory",
319
+ "--certificate", "--private-key",
320
+ "--password",
321
+ "-e", "--execute",
322
+ "--bind-address",
323
+ "--connect-timeout",
324
+ "--dns-timeout",
325
+ "--read-timeout",
326
+ "--timeout",
327
+ "--tries", "-t",
328
+ "--waitretry",
329
+ "--wait", "-w",
330
+ "--random-wait",
331
+ "--limit-rate",
332
+ "--quota", "-Q",
333
+ "--input-file", "-i",
334
+ "--base",
335
+ "--domains", "-D",
336
+ "--exclude-domains",
337
+ "--follow-tags",
338
+ "--ignore-tags",
339
+ "--accept", "-A",
340
+ "--reject", "-R",
341
+ "--accept-regex",
342
+ "--reject-regex",
343
+ "--include-directories", "-I",
344
+ "--exclude-directories", "-X",
345
+ "--cut-dirs",
346
+ "--level", "-l",
347
+ })
348
+
349
+
350
+ def extract_url_from_tokens(tokens: List[str]) -> Optional[str]:
351
+ """Parse a curl/wget/httpie token list and return the first URL argument.
352
+
353
+ Skips flags and their value arguments to find the bare URL positional arg.
354
+
355
+ Args:
356
+ tokens: Tokenized command list (tokens[0] is the command name).
357
+
358
+ Returns:
359
+ The URL string if found, else None.
360
+ """
361
+ if not tokens:
362
+ return None
363
+
364
+ cmd = tokens[0].lower()
365
+ args = tokens[1:]
366
+
367
+ if cmd == "curl":
368
+ return _extract_url_curl(args)
369
+ if cmd == "wget":
370
+ return _extract_url_wget(args)
371
+ if cmd in ("http", "https"):
372
+ return _extract_url_httpie(args)
373
+
374
+ # Generic fallback: first non-flag argument
375
+ return _extract_url_generic(args)
376
+
377
+
378
+ def _extract_url_curl(args: List[str]) -> Optional[str]:
379
+ """Extract URL from curl arguments, skipping flags and their values."""
380
+ i = 0
381
+ while i < len(args):
382
+ a = args[i]
383
+ # Skip -- end of flags marker
384
+ if a == "--":
385
+ i += 1
386
+ # Everything after -- is treated as positional
387
+ if i < len(args):
388
+ return args[i]
389
+ return None
390
+ # Flags that eat the next token as value
391
+ if a in _CURL_VALUE_FLAGS:
392
+ i += 2
393
+ continue
394
+ # Flags with inline value (--flag=value)
395
+ if a.startswith("--") and "=" in a:
396
+ i += 1
397
+ continue
398
+ # Single-char flags that may or may not eat the next token:
399
+ # if the flag is a single char standalone (e.g. -v, -s, -L, -k) it
400
+ # consumes no value; if it's in our known value set, already handled.
401
+ if a.startswith("-") and len(a) == 2:
402
+ i += 1
403
+ continue
404
+ # Bundled short flags (e.g. -sLk) -- no values consumed; skip
405
+ if a.startswith("-") and len(a) > 2 and a[1] != "-":
406
+ i += 1
407
+ continue
408
+ # Non-flag argument: this is the URL
409
+ if not a.startswith("-"):
410
+ return a
411
+ i += 1
412
+ return None
413
+
414
+
415
+ def _extract_url_wget(args: List[str]) -> Optional[str]:
416
+ """Extract URL from wget arguments, skipping flags and their values."""
417
+ i = 0
418
+ while i < len(args):
419
+ a = args[i]
420
+ if a == "--":
421
+ i += 1
422
+ if i < len(args):
423
+ return args[i]
424
+ return None
425
+ if a in _WGET_VALUE_FLAGS:
426
+ i += 2
427
+ continue
428
+ if a.startswith("--") and "=" in a:
429
+ i += 1
430
+ continue
431
+ if a.startswith("-") and len(a) == 2:
432
+ i += 1
433
+ continue
434
+ if a.startswith("-") and len(a) > 2 and a[1] != "-":
435
+ i += 1
436
+ continue
437
+ if not a.startswith("-"):
438
+ return a
439
+ i += 1
440
+ return None
441
+
442
+
443
+ _HTTPIE_METHODS = frozenset({
444
+ "POST", "PUT", "DELETE", "PATCH", "GET", "HEAD", "OPTIONS",
445
+ })
446
+ _HTTPIE_DATA_ITEM = re.compile(r"^[A-Za-z_][A-Za-z0-9_\-]*(:=|==|=@|:@|@|:=@|=)")
447
+
448
+
449
+ def _extract_url_httpie(args: List[str]) -> Optional[str]:
450
+ """Extract URL from httpie (http/https) arguments.
451
+
452
+ httpie positional syntax: http [METHOD] URL [ITEM ...]
453
+ Skip flags, optional METHOD token, then return the URL.
454
+ """
455
+ for a in args:
456
+ if a.startswith("-"):
457
+ continue
458
+ # Skip explicit method token
459
+ if a.upper() in _HTTPIE_METHODS:
460
+ continue
461
+ # Skip data items (key=value, key:=json, key@file)
462
+ if _HTTPIE_DATA_ITEM.match(a):
463
+ continue
464
+ # First remaining positional arg is the URL
465
+ return a
466
+ return None
467
+
468
+
469
+ def _extract_url_generic(args: List[str]) -> Optional[str]:
470
+ """Generic URL extraction: return first non-flag argument."""
471
+ for a in args:
472
+ if a == "--":
473
+ break
474
+ # Long flags with = carry their value inline
475
+ if a.startswith("--") and "=" in a:
476
+ continue
477
+ # Short/long flags without value: skip
478
+ if a.startswith("-"):
479
+ continue
480
+ return a
481
+ return None
@@ -0,0 +1,40 @@
1
+ """Validate and classify resume prompts for security decision-making.
2
+
3
+ Subsystem 1 of the pre_tool_use Task/Agent path.
4
+ Runs FIRST -- if invalid, nothing else loads.
5
+
6
+ Responsibilities:
7
+ - Validates prompt is not empty, not malformed
8
+ - Detects deprecated approval phrases
9
+ - Detects nonce patterns
10
+ - Returns: classification (nonce/malformed_nonce/deprecated/standard)
11
+ """
12
+
13
+ from .approval_constants import (
14
+ NONCE_APPROVAL_PREFIX,
15
+ NONCE_APPROVAL_PATTERN,
16
+ DEPRECATED_APPROVAL_PHRASES,
17
+ )
18
+
19
+
20
+ def classify_resume_prompt(prompt: str) -> str:
21
+ """Classify a resume prompt into one of four categories.
22
+
23
+ Args:
24
+ prompt: The resume prompt string.
25
+
26
+ Returns:
27
+ 'nonce' -- valid nonce approval token present
28
+ 'malformed_nonce' -- APPROVE: prefix present but invalid nonce
29
+ 'deprecated' -- deprecated approval phrase detected
30
+ 'standard' -- normal resume prompt (no approval indicators)
31
+ """
32
+ stripped_prompt = prompt.strip()
33
+ if NONCE_APPROVAL_PATTERN.search(prompt):
34
+ return "nonce"
35
+ if stripped_prompt.startswith(NONCE_APPROVAL_PREFIX):
36
+ return "malformed_nonce"
37
+ prompt_lower = prompt.lower()
38
+ if any(phrase in prompt_lower for phrase in DEPRECATED_APPROVAL_PHRASES):
39
+ return "deprecated"
40
+ return "standard"
@@ -0,0 +1,165 @@
1
+ """
2
+ Shell Unwrapper - Detect and strip wrapper shells from commands.
3
+
4
+ Many commands arrive wrapped in a shell invocation:
5
+ bash -c "actual command"
6
+ sh -c 'rm -rf /tmp/build'
7
+ env bash -c "sh -c 'inner command'"
8
+
9
+ The wrapped inner command is what matters for classification, not the
10
+ wrapper itself. ShellUnwrapper recursively peels wrapper shells until
11
+ it reaches the actual payload.
12
+
13
+ Handles:
14
+ - bash -c, sh -c, zsh -c, dash -c
15
+ - /bin/bash -c, /usr/bin/env bash -c
16
+ - exec bash -c
17
+ - env bash -c (with optional env vars like VAR=val)
18
+ - Nested wrappers: bash -c "sh -c 'inner'"
19
+ - Single-quoted, double-quoted, and unquoted payloads
20
+
21
+ Dependencies: Python stdlib only.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import re
27
+ from dataclasses import dataclass
28
+ from typing import Optional
29
+
30
+
31
+ @dataclass
32
+ class UnwrapResult:
33
+ """Result of unwrapping a shell command."""
34
+
35
+ # The innermost command after all wrappers are stripped.
36
+ inner: str
37
+ # Number of wrapper layers removed (0 = no wrapper detected).
38
+ depth: int
39
+ # True if any wrapper was detected and stripped.
40
+ was_wrapped: bool
41
+
42
+ def __str__(self) -> str:
43
+ return self.inner
44
+
45
+
46
+ # ---------------------------------------------------------------------------
47
+ # Wrapper detection patterns
48
+ # ---------------------------------------------------------------------------
49
+ # Optional path prefix: /bin/, /usr/bin/, /usr/local/bin/
50
+ _OPT_PATH = r"(?:/(?:usr/(?:local/)?)?s?bin/)?"
51
+
52
+ # Optional prefix commands that can precede the shell: env, exec, nohup,
53
+ # sudo, nice, etc. env can carry VAR=val assignments before the shell.
54
+ _PREFIX = (
55
+ r"(?:(?:exec|nohup|sudo|nice|ionice|setsid|time)\s+)*"
56
+ r"(?:(?:" + _OPT_PATH + r")?env\s+(?:[A-Za-z_][A-Za-z_0-9]*=[^\s]*\s+)*)?"
57
+ )
58
+
59
+ # Shell interpreters that accept -c.
60
+ _SHELLS = r"(?:bash|sh|zsh|dash|ksh)"
61
+
62
+ # Combined regex: optional_prefix + optional_path + shell + -c + payload
63
+ # Three capture groups after -c for the three quoting styles:
64
+ # group 1: double-quoted payload
65
+ # group 2: single-quoted payload
66
+ # group 3: unquoted payload (rest of string)
67
+ _WRAPPER_RE = re.compile(
68
+ r"^\s*"
69
+ + _PREFIX
70
+ + _OPT_PATH
71
+ + _SHELLS
72
+ + r"""\s+-c\s+(?:"((?:[^"\\]|\\.)*)"|'([^']*)'|(\S.*))""",
73
+ re.DOTALL,
74
+ )
75
+
76
+ # Maximum recursion depth to prevent infinite loops on pathological input.
77
+ _MAX_DEPTH = 10
78
+
79
+
80
+ class ShellUnwrapper:
81
+ """
82
+ Detect and recursively strip shell wrapper invocations.
83
+
84
+ Zero external dependencies -- Python stdlib only.
85
+
86
+ Usage::
87
+
88
+ unwrapper = ShellUnwrapper()
89
+ result = unwrapper.unwrap('bash -c "ls -la"')
90
+ # result.inner == "ls -la"
91
+ # result.depth == 1
92
+ # result.was_wrapped == True
93
+
94
+ result = unwrapper.unwrap("ls -la")
95
+ # result.inner == "ls -la"
96
+ # result.depth == 0
97
+ # result.was_wrapped == False
98
+ """
99
+
100
+ def unwrap(self, command: str) -> UnwrapResult:
101
+ """
102
+ Recursively strip shell wrappers from *command*.
103
+
104
+ Args:
105
+ command: Raw shell command string.
106
+
107
+ Returns:
108
+ UnwrapResult with the innermost command, depth, and whether
109
+ any wrapper was detected.
110
+ """
111
+ if not command or not command.strip():
112
+ return UnwrapResult(inner=command or "", depth=0, was_wrapped=False)
113
+
114
+ current = command.strip()
115
+ depth = 0
116
+
117
+ while depth < _MAX_DEPTH:
118
+ inner = self._try_unwrap_once(current)
119
+ if inner is None:
120
+ break
121
+ current = inner.strip()
122
+ depth += 1
123
+
124
+ return UnwrapResult(
125
+ inner=current,
126
+ depth=depth,
127
+ was_wrapped=depth > 0,
128
+ )
129
+
130
+ def is_wrapped(self, command: str) -> bool:
131
+ """Return True if *command* has a shell wrapper layer."""
132
+ if not command or not command.strip():
133
+ return False
134
+ return self._try_unwrap_once(command.strip()) is not None
135
+
136
+ # ------------------------------------------------------------------
137
+ # Internal
138
+ # ------------------------------------------------------------------
139
+
140
+ def _try_unwrap_once(self, command: str) -> Optional[str]:
141
+ """
142
+ Attempt to strip one wrapper layer.
143
+
144
+ Returns the inner payload string, or None if no wrapper detected.
145
+ """
146
+ m = _WRAPPER_RE.match(command)
147
+ if m is None:
148
+ return None
149
+
150
+ # Exactly one of the three groups will be non-None.
151
+ payload = m.group(1) # double-quoted
152
+ if payload is not None:
153
+ # Unescape \" and \\ inside double-quoted payload
154
+ payload = payload.replace('\\"', '"').replace("\\\\", "\\")
155
+ return payload.strip()
156
+
157
+ payload = m.group(2) # single-quoted
158
+ if payload is not None:
159
+ return payload.strip()
160
+
161
+ payload = m.group(3) # unquoted
162
+ if payload is not None:
163
+ return payload.strip()
164
+
165
+ return None