Fase 0 — descoberta e arquitetura:
- Inventário do projeto, glossário de domínio, arquitetura com bounded
contexts e topologia de containers, threat model inicial.
- 12 ADRs cobrindo modular monolith, topologia de containers (Postgres
isolado + eden-core/parceiros/assinante em containers e portas
distintos), auth/sessões, modelo de permissões, criptografia/segredos,
contrato first-class, stock ledger, separação billing/finance/fiscal,
outbox transacional, adapters SaperX e Focus NFe, e identidade
compartilhada entre as 3 apps.
- 14 subagentes e 7 skills especializados por domínio em .claude/.
- Hooks de segurança (PreToolUse/PostToolUse/Stop) testados via pipe.
Fase 1 — plataforma (em andamento):
- Monorepo pnpm workspaces + Turborepo: apps/{api,worker,core-web,
reseller-web,subscriber-web} + 9 packages compartilhados.
- apps/api: NestJS mínimo com /health/live e /health/ready (checando
Postgres real via @eden/database).
- 3 frontends Vite + React + TypeScript + Tailwind, com o favicon
oficial do EDEN.
- packages/database: migration baseline (node-pg-migrate) criando
roles/role_permissions/applications/users/user_applications/sessions/
audit_log — audit log append-only com hash-chain, testado ao vivo
(UPDATE/DELETE bloqueados pelo trigger).
- compose.yaml implementando a topologia da ADR-0002, validada de ponta
a ponta: os 6 containers sobem e ficam saudáveis com um único
`docker compose up`.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
78 lines
2.2 KiB
Python
Executable File
78 lines
2.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""PreToolUse guard for Read/Write/Edit/Glob/Grep (EDEN, Master Prompt §2.1/§3.4).
|
|
|
|
Blocks access to paths outside the project root and to ~/.ssh or /etc, and
|
|
asks for confirmation before reading a .env/credential-looking file. Fails
|
|
open on any internal error.
|
|
"""
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
PROJECT_ROOT = "/opt/eden"
|
|
HOME = os.path.expanduser("~")
|
|
|
|
SECRET_NAME_RE = re.compile(r"(^|/)(\.env(\..+)?|.*credential.*|.*secret.*)$", re.IGNORECASE)
|
|
|
|
|
|
def extract_path(tool_input: dict) -> str:
|
|
for key in ("file_path", "path", "notebook_path"):
|
|
if key in tool_input:
|
|
return tool_input[key] or ""
|
|
# Grep/Glob use "path" for the search root; pattern itself isn't a filesystem path.
|
|
return ""
|
|
|
|
|
|
def decide(path: str):
|
|
if not path:
|
|
return "allow", None
|
|
abspath = os.path.abspath(path)
|
|
|
|
ssh_dir = os.path.join(HOME, ".ssh")
|
|
if abspath == ssh_dir or abspath.startswith(ssh_dir + os.sep):
|
|
return "deny", "acesso a ~/.ssh"
|
|
if abspath == "/etc" or abspath.startswith("/etc" + os.sep):
|
|
return "deny", "acesso a /etc"
|
|
if abspath == "/root" or abspath.startswith("/root" + os.sep):
|
|
return "deny", "acesso a /root"
|
|
|
|
if abspath.startswith("/opt/") and not (
|
|
abspath == PROJECT_ROOT or abspath.startswith(PROJECT_ROOT + os.sep)
|
|
):
|
|
return "deny", f"caminho fora de {PROJECT_ROOT} (outro projeto no host)"
|
|
|
|
basename = os.path.basename(abspath)
|
|
if SECRET_NAME_RE.match(basename):
|
|
return "ask", f"leitura/escrita de arquivo que parece conter segredo ({basename})"
|
|
|
|
return "allow", None
|
|
|
|
|
|
def main():
|
|
try:
|
|
payload = json.load(sys.stdin)
|
|
tool_input = payload.get("tool_input") or {}
|
|
path = extract_path(tool_input)
|
|
except Exception:
|
|
print(json.dumps({}))
|
|
return
|
|
|
|
decision, reason = decide(path)
|
|
if decision == "allow":
|
|
print(json.dumps({}))
|
|
return
|
|
|
|
output = {
|
|
"hookSpecificOutput": {
|
|
"hookEventName": "PreToolUse",
|
|
"permissionDecision": decision,
|
|
"permissionDecisionReason": f"[eden-guard] {reason} (EDEN_MASTER_PROMPT_CLAUDE.md §2.1/§3.4)",
|
|
}
|
|
}
|
|
print(json.dumps(output))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|