Liga a vulnerabilidade ao ativo e ao processo
Uma equipa APS recebe uma vulnerabilidade fictícia num componente de transferência usado no fecho de fundos. Antes de escolher a janela, confirma versão, configuração afetada, exposição, proprietário e dependências. Um nome semelhante no inventário não prova aplicabilidade, e a ausência de um agente não prova ausência do componente. O NIST SP 800-40 Rev.4 enquadra patching como manutenção contínua, incluindo identificação, resposta, verificação e monitorização. Define quem obtém a correção, quem valida origem e compatibilidade e quem aceita o risco residual. Inclui imagens, templates e mecanismos de recuperação: corrigir as instâncias atuais mantendo um template vulnerável pode reintroduzir o problema no próximo scale-out. O inventário precisa de acompanhar criação, substituição e retirada de ativos.
Combina severidade, exploração e contexto
Uma pontuação Base CVSS informa severidade técnica, mas não decide sozinha a prioridade do serviço. A especificação CVSS 4.0 distingue contexto de ameaça e de ambiente. Exploração conhecida, exposição efetiva, criticidade, controlos existentes e custo de interrupção são entradas adicionais. O catálogo KEV é uma fonte CISA sobre vulnerabilidades exploradas conhecidas; a ausência de uma entrada não prova que uma falha não é explorável ou está ausente no ambiente. Nesta pesquisa, a página principal devolveu 403 e foi consultado o espelho oficial cisagov. Não foram atribuídos CVEs reais aos casos fictícios. Não transportes datas ou obrigações de um programa externo para toda a organização sem confirmar aplicabilidade e política. Documenta a decisão e o motivo de qualquer tratamento temporário.
Define aceitação antes da janela de mudança
Para uma correção de middleware, prepara dependências, compatibilidade, teste representativo, rollback e critérios de paragem. Uma mudança urgente precisa de decisão e coordenação adequadas; urgência não elimina a necessidade de saber quem pode interromper a operação. O instalador terminar com sucesso é apenas uma parte da evidência. Confirma que a versão efetiva mudou, que reinícios necessários ocorreram e que os fluxos de negócio continuam corretos. Se a mitigação for isolamento temporário, verifica o caminho de ataque que deveria bloquear e mantém prazo e responsável. Uma regra de filtragem não é automaticamente equivalente a patch. O rollback também pode restaurar a vulnerabilidade, pelo que deve incluir avaliação do risco e medidas temporárias.
Reconcilia denominadores e validade da evidência
O laboratório Python usa um inventário gerado de 100 ativos e 80 observações fictícias. Dessas 80,72 dizem patched, cinco failed e três pending-reboot. A taxa superficial é 72/80, ou 90%. Quatro das 72 observações patched estão fora da janela fictícia de 72 horas, ficando 68 patched recentes, cinco falhas, três reinícios pendentes, quatro observações antigas e 20 desconhecidos. A cobertura observada é 80%; a parcela do inventário com declaração recente de patched é 68%. Nem essa declaração é prova independente de correção efetiva. O script recusa identidades duplicadas, observações órfãs e tempos sem fuso ou futuros, em vez de sobrescrever conflitos. A janela de evidência foi escolhida para o exercício e não é prazo universal de correção nem prazo de notificação do RGPD.
Entrega um estado acionável a RUN
Executa o código e lê contagens, estados por ativo e verificações, conservando as observações originais. O exercício cria apenas um ficheiro temporário local, calcula o seu hash e remove a própria pasta no fim. Não faz scans, exploits, instalações ou reinícios. Num serviço real, relaciona o relatório com inventário autorizado, data de observação e confirmação da correção em execução. Para cada pendência, identifica impacto, ação seguinte, responsável e prazo; não fecha desconhecidos como conformes para melhorar a percentagem. Verifica também se templates e procedimentos de restauro continuam corrigidos. Resumo: a prioridade depende do contexto e a conclusão depende de evidência com âmbito e idade conhecidos. Relaciona esta aula com incidentes, mudança, obsolescência, observabilidade e gestão de fornecedores.
"""Original offline reconciliation of synthetic patch observations, not a vulnerability scan."""
import copy,datetime,hashlib,json,pathlib,tempfile
UTC=datetime.timezone.utc
NOW=datetime.datetime(2026,10,7,12,tzinfo=UTC)
WINDOW=datetime.timedelta(hours=72)
checks=[]
def check(name,condition):
checks.append({'id':name,'pass':bool(condition)})
def timestamp(value):
t=datetime.datetime.fromisoformat(value)
if t.tzinfo is None:raise ValueError('timezone required')
return t.astimezone(UTC)
def reconcile(inventory,observations):
if len(inventory)!=len(set(inventory)):raise ValueError('duplicate inventory identity')
index={}
for row in observations:
if row['asset'] not in inventory:raise ValueError('unknown inventory identity')
if row['asset'] in index:raise ValueError('duplicate observation identity')
if row['status'] not in ['patched','failed','pending-reboot']:raise ValueError('unsupported status')
t=timestamp(row['observedAt'])
if t>NOW:raise ValueError('future observation')
index[row['asset']]=row
result={}
for asset in inventory:
row=index.get(asset)
if row is None:result[asset]='unknown'
elif NOW-timestamp(row['observedAt'])>WINDOW:result[asset]='stale'
else:result[asset]=row['status']
counts={s:list(result.values()).count(s)for s in ['patched','failed','pending-reboot','stale','unknown']}
return {'states':result,'counts':counts,'inventory':len(inventory),'observed':len(index),'observedCoverage':len(index)/len(inventory)if inventory else None,'verifiedPatchedFraction':counts['patched']/len(inventory)if inventory else None}
def rejects(name,inventory,observations):
try:reconcile(inventory,observations)
except (ValueError,KeyError):check(name,True)
else:check(name,False)
inventory=[f'asset-{i:03}'for i in range(1,101)]
observations=[{'asset':asset,'status':'patched'if i<=72 else 'failed'if i<=77 else 'pending-reboot','observedAt':'2026-10-01T12:00:00Z'if 69<=i<=72 else '2026-10-06T12:00:00Z'}for i,asset in enumerate(inventory[:80],1)]
original=copy.deepcopy(observations)
with tempfile.TemporaryDirectory(prefix='dr-patch-fixture-')as tmp:
directory=pathlib.Path(tmp);source=directory/'observations.json';source.write_text(json.dumps(observations,sort_keys=True)+'\n');raw=source.read_bytes();source_hash=hashlib.sha256(raw).hexdigest();loaded=json.loads(raw);report=reconcile(inventory,loaded)
check('inventory100',report['inventory']==100);check('observed80',report['observed']==80)
check('fresh-patched68',report['counts']['patched']==68);check('failed5',report['counts']['failed']==5)
check('pending-reboot3',report['counts']['pending-reboot']==3);check('stale4',report['counts']['stale']==4)
check('unknown20',report['counts']['unknown']==20);check('counts-cover-inventory',sum(report['counts'].values())==100)
check('coverage80percent',report['observedCoverage']==0.8);check('verified-patched68percent',report['verifiedPatchedFraction']==0.68)
check('observed-claimed-patched90percent',sum(r['status']=='patched'for r in loaded)/len(loaded)==0.9)
check('raw-file-unchanged',source.read_bytes()==raw);check('input-records-unchanged',loaded==original)
check('equivalent-offset',timestamp('2026-10-06T13:00:00+01:00')==timestamp('2026-10-06T12:00:00Z'))
boundary=[{'asset':'x','status':'patched','observedAt':'2026-10-04T12:00:00Z'}]
check('exact72hours-accepted',reconcile(['x'],boundary)['counts']['patched']==1)
older=copy.deepcopy(boundary);older[0]['observedAt']='2026-10-04T11:59:59Z'
check('older-than72hours-stale',reconcile(['x'],older)['counts']['stale']==1)
rejects('duplicate-inventory-rejected',['x','x'],[])
rejects('duplicate-observation-rejected',inventory,loaded+[loaded[0]])
conflict=copy.deepcopy(loaded[0]);conflict['status']='failed';rejects('conflicting-observation-rejected',inventory,loaded+[conflict])
orphan=copy.deepcopy(loaded[0]);orphan['asset']='unmanaged';rejects('orphan-observation-rejected',inventory,[orphan])
naive=copy.deepcopy(loaded[0]);naive['observedAt']='2026-10-06T12:00:00';rejects('naive-time-rejected',inventory,[naive])
future=copy.deepcopy(loaded[0]);future['observedAt']='2026-10-08T12:00:00Z';rejects('future-time-rejected',inventory,[future])
unknown=copy.deepcopy(loaded[0]);unknown['status']='success-ish';rejects('unknown-status-rejected',inventory,[unknown])
missing=copy.deepcopy(loaded[0]);del missing['asset'];rejects('missing-identity-rejected',inventory,[missing])
check('empty-inventory-rate-not-invented',reconcile([],[])['verifiedPatchedFraction'] is None)
check('no-observation-not-patched',reconcile(['x'],[])['states']['x']=='unknown')
check('reordering-does-not-change-report',reconcile(inventory,list(reversed(loaded)))==report)
check('pending-reboot-not-counted-patched',report['states']['asset-080']=='pending-reboot')
check('stale-success-not-counted-patched',report['states']['asset-069']=='stale')
check('owned-temporary-directory-removed',not directory.exists())
result={'runtime':__import__('platform').python_version(),'scriptSHA256':hashlib.sha256(pathlib.Path(__file__).read_bytes()).hexdigest(),'sourceSHA256':source_hash,'passed':sum(c['pass']for c in checks),'failed':sum(not c['pass']for c in checks),'checks':checks,'report':report,'clock':NOW.isoformat(),'freshnessHours':72,'limits':['Generated inventory and status records, not real infrastructure or scanner output.','No vulnerability discovery, exploit, patch installation, restart or provider access.','The72-hour evidence window is a fictional local rule, not a universal remediation deadline.','Patched is an input assertion within this teaching snapshot, not independent proof of effective remediation.','Status arithmetic and identity reconciliation do not demonstrate a service is secure.']}
print(json.dumps(result,indent=2));raise SystemExit(1 if result['failed'] else 0)
72/80 dá90% entre observações, mas só68 dos100 ativos têm uma declaração patched recente;20 permanecem desconhecidos.
Armadilhas comuns
Percentagem sem denominador; sucesso do instalador como efeito confirmado; KEV ausente como segurança; template antigo; desconhecidos como conformes.
Tópicos relacionados: Mudança, inventário e evidência operacional · Dados, fornecedores e responsabilidades
Prioriza vulnerabilidades no contexto do serviço e demonstra cobertura, efeito da correção e trabalho ainda desconhecido.
Referência: Enterprise Patch Management Planning · CCSP examination outline effective 2026-08-01; January2026 V2 PDF