Files
Investidor-V2/tools/portal-audit-reopen-c37.py
T
nala 6ddafc95ba
tests / ci (push) Canceled after 0s
oportunidades 4
2026-10-10 07:03:59 +02:00

118 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Keep the existing audit and historical MAC evidence; reopen browser authentication."""
import csv
import json
from pathlib import Path
root = Path(__file__).resolve().parent.parent
docs = root / 'docs/investmoz/v2/auditoria-portal'
private = root / 'storage/app/private/investmoz-v2/portal-audit'
heading = '## C37 reaberto — ciclo de login da conta QA 18 — P0'
old_heading = heading
body = '''C37 continua aberto e bloqueia a demonstração visual A. O utilizador reporta ciclo login→MFA→redirecionamento na QA18097 com a conta18. A correção anterior do MAC inválido da conta13 e as provas HTTP são históricas específicas; não demonstram a continuidade da sessão neste navegador. Nenhum login foi repetido nesta investigação.
GET/login QA confirmou alvo desk_restore_20261009_174726_test:55441 antes de instrumentar. Configuração efetiva: cookie opportunity_resources_qa, domínio vazio (host-only), caminho/, Secure=false, HttpOnly=true, SameSite=lax; XSRF-TOKEN no mesmo domínio/caminho, HttpOnly=false. Guard padrão e Fortify=web, sessão file120min, cache de configuração não carregado. Os atributos observados não identificam uma causa. Portas partilham o âmbito do cookie no mesmo host; o router antigo18096 usa o mesmo nome/diretório por código, mas seu GET atual devolve erro PHP sem Set-Cookie. Não está demonstrado que sobrescreveu o cookie na tentativa reportada.
O router QA, depois da guarda real de destino, ativa tools/portal-qa-auth-trace.php. auth-session-trace-qa.jsonl guarda método/caminho sem query, horaUTC, estado antes/depois da rota, pseudónimo HMAC da sessão e do cookie aceite, utilizador pendente/autenticado, persistência efetiva no handler, atributos Set-Cookie e destino Location/X-Inertia-Location. Não guarda Cookie, X-XSRF-TOKEN, credenciais, códigos MFA, corpos ou valores de cookies. Identificador não secreto X-Investmoz-QA-Auth-Trace permite correlacionar resposta manual. Instrumentação temporária exclusivamente na QA18097; não altera login, MFA, chave ou dados ativos. auth-trace-readiness-qa.json demonstra continuidade convidada e captura antes da autorização, persistência legível e diretório efetivo correto; não é prova de login. Os bundles servidos usam /login e /two-factor-challenge relativos. A configuração de origem declara APP_NAME=Laravel e não SESSION_COOKIE (default laravel_session), distinta do nomeQA; isto é leitura documental de configuração, não consulta ao servidor ativo. XSRF-TOKEN genérico pode partilhar o âmbito entre portas; nenhum conflito concreto foi observado. tools/portal-qa-auth-diagnosis.py lê as provas sem fazer pedidos de login e destaca a primeira divergência da conta18; divergência não é automaticamente causa.
Primeiro diagnóstico manual: usar o navegador afetado e cookies atuais; abrir http://127.0.0.1:18097/investimentos, entrar com a conta18 e confirmar MFA uma vez. Parar no resultado e comunicar hora aproximada/URL final. Não limpar cookies nem trocar de janela antes desta captura. A anterior instrução de janela privada aplica-se ao ensaio posterior, não à localização desta perda. Comparar primeiro: pending18 após POST/login→mesma sessão GET/MFA→POST/MFA autentica/regenera e grava18→GET destino recebe a nova sessão e carrega18. O primeiro desvio identifica o ponto da perda; sem essa sequência observada, a causa fica indeterminada.
Aceitação ainda pendente: /investimentos visível e autorizado após MFA, reload mantém18, logout retira acesso, novo login+MFA recupera o dossier, sem mistura entre projetos/perfis. Depois retomar A/B completos. Correção somente após causa concreta; não fechar por HTTP/unitários. MFA preservado, sem key:generate; zero escritas ativas, candidate_revision_fixed=false e activation_ready=false.
Ativação: C37 precede revisão candidata, ensaio visual A/B e renovação de backup/restauro. O trace QA não integra instalação ativa, recursos ou importação cartográfica. Reversão da instrumentação: remover o carregamento e os três registos de middleware no router QA; guardar provas sanitizadas privadas, não eliminar histórico. Não repetir instalações existentes nem ativar flags.'''
confirmed = (private / 'c37-manual-2122-2124-qa.json').exists() and (private / 'c37-auth-redirect-qa.json').exists()
if confirmed:
manual = json.loads((private / 'c37-manual-2122-2124-qa.json').read_text(encoding='utf-8'))
http = json.loads((private / 'c37-auth-redirect-qa.json').read_text(encoding='utf-8'))
runtime = json.loads((private / 'investor-path-qa-runtime.json').read_text(encoding='utf-8-sig'))
assert manual['cause_confirmed'] and http['mfa_browser_redirect_protocol_passed'] and not http['visual_acceptance_passed']
heading = '## C37 — causa confirmada; seleção de conta QA corrigida — P0, visual pendente'
body = f'''O pedido de reabertura referia a conta esperada18. A captura manual21:22–21:24UTC demonstra que foi selecionada a conta13 restaurada. O MFA foi alcançado, mas a autenticação nunca se completou. Causa concreta: credenciais MFA da conta13 cifradas pela chave de origem, incompatíveis com a chave estávelQA; a conta18 sintética não foi desafiada nessa tentativa. Nenhuma chave, MFA ou conta13 foi alterada para contornar o problema.
| UTC09/10 | Pedido | Resultado observado |
|---|---|---|
|21:22:10|GET/investimentos|302/login; sessão convidada guardada, destino pretendido /investimentos.|
|21:22:11|GET/login|200; mesmo cookie/sessão, sem autenticação.|
|21:22:44|POST/login|302/two-factor-challenge; login.id=13, não18.|
|21:22:44|GET/two-factor-challenge|200; desafio13 recuperado da mesma sessão; ecrã confirmado pelo utilizador.|
|21:23:04|POST/two-factor-challenge|Falha de desencriptação; warning correlacionado conta13;302/login, invalidação intencional e nova sessão convidada.|
|21:23:04|GET/login|200; cookie aceite corresponde à sessão regenerada; sem utilizador autenticado.|
Primeiro desvio do ensaio: POST/login desafia13. Primeira falha técnica: POST/two-factor-challenge às21:23:04 (trace ec68d040a3291cb8, referência26e15b04-07b8-4211-a2a0-7f70509e03f6). Não se tratou de código MFA rejeitado pelo verificador nem de perda após autenticação. Os dois campos cifrados (secret/recovery) são incompatíveis; como nenhum corpo foi retido, não se afirma qual modalidade de entrada foi usada nesta tentativa. Não foram observados conflitos de cookies, mudança de chave/guard ou redirecionamento para outra origem.
CorreçãoQA, após guarda real desk_restore_20261009_174726_test:55441: login identifica/preenche o email não secreto da conta18; o ecrã MFA identifica a conta realmente desafiada. PortalQaAccountSelection, carregado exclusivamente pelo routerQA, recusa endereços não sintéticos antes do desafio e cancela desafios antigos de contas restauradas, com erro controlado. Os perfis sintéticosQA mantêm o login Fortify normal e respetivo MFA. Contas restauradas e dados ativos preservados. A conta13 continua cifrada como no original; não foi reencryptada nem usa chave ativa como fallback.
Lacuna de feedback encontrada no código: o Form recém-criado no login começa com erros locais vazios e não lia o errors.email devolvido pela página após redirecionamento do MFA. Login agora apresenta também esse erro da página. A mensagem de falha de desencriptação continua controlada e não autentica; sua apresentação visual ainda exige confirmação.
Evidência atual{http['captured_at_utc']} em c37-auth-redirect-qa.json, pelo servidor18097 e protocolo Inertia/HTML com redirects reais: seletor13 recusado antes do MFA; desafio18 identificado; código inválido mantém desafio sem autenticar; MFA válido regenera/grava18 e redireciona /investimentos; GET e reload mantêm18; logout revoga acesso; novo cookie jar+login+MFA recupera o mesmo dossier413ba1f6-bdf6-402a-a561-c4e2d4098d1a; outra conta403. Negativo sintético19 demonstra falha de desencriptação→login com mensagem, sem autenticação. Nenhuma criação extra de dossier. Estes são resultados de resposta/persistência, não um ensaio visual bem-sucedido.
Evidências: c37-manual-2122-2124-qa.json conserva os seis pedidos e warning sanitizados; auth-session-trace-qa.jsonl mantém histórico append-only e pseudónimos HMAC; c37-qa-account-auth.xml verifica políticaQA, preservação, MFA válido/inválido e falhas secret/recovery; c37-installation-auth-fingerprint.xml verifica guardas/importação/reversão. TypeScript e build passaram. mfa-persistence-qa.json reconfirma conta13 e .env byte a byte, MFA18 confirmado/oito códigos, chave não gerada/rodada e zero escritas ativas. Cookies, passwords, OTP e corpos não são exportados.
Estado: causa identificada, preparaçãoQA corrigida e protocolo/persistência demonstrados; C37 permanece aberto para /investimentos visível→reload→logout→novo login da conta18 no navegador. A captura manual demonstrou apenas o MFA da conta13 e retorno ao login; não certifica o percursoA/B nem o feedback atualizado. Próximo teste deve confirmar que o ecrã diz contaQA18 antes de usar o autenticador dessa conta. URL exato http://127.0.0.1:18097/investimentos. Credenciais/segredo permanecem somente no ficheiro privado local investor-mfa-qa-private.json, sem exportação. Workflow/contratos especializados continuam no backlog, sem jornadas educativas novas.
Ativação não autorizada e revisão candidata não fixada. Fingerprints agora incluem também login.tsx e two-factor-challenge.tsx: percurso{runtime['code_hash']}; cartografia{runtime['geometry_code_hash']}; recursos{runtime['resource_code_hash']}. Ready/provas de versões anteriores não demonstram esta revisão. Política/seletor/rastreioQA não integram instalação ativa. Após aceitação visualA/B e correções, renovar backup/restauro/compatibilidade e manter a ordem já documentada de recursos→percurso→flag→importação cartográfica→publicação separada, sem repetir o que estiver instalado. ReversãoQA: retirar registo/configuração de PortalQaAccountSelection e overrides Fortify do router; retirar os registos de rastreio; preservar provas. A correção do feedback é código compatível sem migration, alteração de chave ou modificação de dados.'''
for name in ['relatorio-executivo.md', 'backlog.md', 'casos-reproducao.md', 'roteiro-manual-qa-i0-i3.md', 'plano-ativacao.md', 'execucao-percurso-i0-i3.md']:
path = docs / name
if not path.exists():
continue
text = path.read_text(encoding='utf-8-sig')
start = text.find(heading + '\n')
if start < 0 and confirmed:
start = text.find(old_heading + '\n')
if start >= 0:
end = text.find('\n## ', start + 3)
text = text[:start] + heading + '\n\n' + body + '\n\n' + (text[end+1:] if end >= 0 else '')
else:
first = text.find('\n')
text = text[:first+1] + '\n' + heading + '\n\n' + body + '\n\n' + text[first+1:].lstrip('\n')
path.write_text(text, encoding='utf-8')
matrix = docs / 'matriz-requisitos.csv'
with matrix.open(encoding='utf-8-sig', newline='') as f:
reader = csv.DictReader(f)
fields = reader.fieldnames
rows = list(reader)
ids = ['AUD-UI-01', 'AUD-DASH-01', 'AUD-SIM-01', 'AUD-DESK-02', 'AUD-GUIDE-01', 'AUD-CRM-01']
assert len(rows) == 3354
assert sum(int(row['Quantidade de linhas de origem']) for row in rows) == 5830
suffix = ' | C37 REABERTO/P0: ciclo login da conta18 no navegador18097. auth-cookie-config-qa.json confirma atributos/guard QA; auth-session-trace-qa.jsonl preparado para primeira perda antes/depois MFA. Causa e continuidade visual não demonstradas; provas HTTP anteriores não fecham este caso.'
if confirmed:
suffix = ' | C37 DIAGNÓSTICO CONFIRMADO/P0: manual21:22–21:24UTC desafia conta13, não18; POST/MFA21:23:04 falha na desencriptação e invalida intencionalmente; GET/login aceita nova sessão. Sem autenticação concluída/perda posterior/conflito de cookies. c37-manual-2122-2124-qa.json e c37-auth-redirect-qa.json: QA seleciona18, bloqueia restauradas antesMFA, apresenta erro da página; MFA18→/investimentos→reload→logout→nova sessão/dossier HTTP passou; visual18 pendente.'
for row in rows:
if row['ID'] not in ids:
continue
row['Prioridade'] = 'P0'
if 'C37 REABERTO/P0:' not in row['Evidência funcional']:
if not confirmed:
row['Evidência funcional'] += suffix
row['Lacuna'] += ' | C37 reaberto: localizar perda; corrigir causa e demonstrar /investimentos→reload→logout→novo login/MFA no navegador.'
if confirmed:
row['Evidência funcional'] = row['Evidência funcional'].replace('C37 REABERTO/P0:', 'Histórico C37 REABERTO/P0, antes da captura21:22–21:24UTC:')
row['Lacuna'] = row['Lacuna'].replace('C37 reaberto: localizar perda;', 'Histórico da reaberturaC37, antes da captura: localizar perda;')
if 'C37 DIAGNÓSTICO CONFIRMADO/P0:' not in row['Evidência funcional']:
row['Evidência funcional'] += suffix
row['Lacuna'] += ' | C37 após captura: causa conhecida e seleção/feedbackQA corrigidos; falta entrada visual bem-sucedida da conta18, reload/logout/nova sessão e retomaA/B.'
row['Interface'] = 'Manual: MFA13 alcançado e retorno/login observado21:22–21:24UTC; seleção/feedbackQA preparados. Sucesso visual da conta18/reload/logout/nova sessão pendente.'
row['API'] += '' if 'c37-auth-redirect-qa.json' in row['API'] else ' | c37-auth-redirect-qa.json: redirects Inertia/HTML no servidor18097, MFA18 e negativas diferenciadas.'
row['Recuperação'] += '' if 'C37 nova sessão18' in row['Recuperação'] else ' | C37 nova sessão18 por HTTP recupera o dossier próprio apóslogout; outra conta403. Interface pendente.'
else:
row['Interface'] = 'Pendente/com falha reportada: C37 ciclo de login conta18 QA18097; trace manual preparado, causa por localizar.'
with matrix.open('w', encoding='utf-8-sig', newline='') as f:
writer = csv.DictWriter(f, fieldnames=fields)
writer.writeheader()
writer.writerows(rows)
file = private / 'investor-path-evidence.json'
proof = json.loads(file.read_text(encoding='utf-8-sig'))
proof.update({'mfa_blocking_case': 'C37', 'c37_reopened_browser_login_loop': True,
'c37_browser_session_loss_identified': False, 'c37_browser_acceptance_passed': False,
'integrated_ui_ab_passed': False, 'candidate_revision_fixed': False, 'activation_ready': False})
if confirmed:
proof.update({'c37_cause_identified': True, 'c37_manual_observed_actor': 13,
'c37_session_loss_after_auth_ruled_out_in_manual_attempt': True,
'c37_qa_account_selection_corrected': True, 'c37_auth_redirect_qa_evidence': 'c37-auth-redirect-qa.json',
'c37_manual_interval_evidence': 'c37-manual-2122-2124-qa.json', 'c37_browser_acceptance_passed': False})
file.write_text(json.dumps(proof, ensure_ascii=False, indent=2), encoding='utf-8')
print(json.dumps({'case': 'C37 P0 visual pending', 'cause_confirmed': confirmed, 'requirements': len(rows), 'origins': 5830, 'active_writes': 0}))