1. Recuperar dados inclui recuperar os controlos
Uma equipa fictícia de suporte recupera uma tabela de posições de fundos antes da distribuição de reporting. O número de linhas coincide com a origem e a consulta do administrador termina sem erro. Estes resultados demonstram apenas partes da recuperação. Ainda falta saber se cada equipa vê as carteiras autorizadas e se os identificadores protegidos continuam inacessíveis a quem não os deve consultar. O cenário não descreve procedimentos internos do BNP Paribas. Antes da operação, inventaria quatro dependências: dados, políticas de acesso, identidades executoras e material necessário à recuperação. Regista também cópias, resultados exportados e versões históricas relevantes. Para cada dependência, define a evidência que vais recolher depois da mudança. Um teste com identidade privilegiada pode confirmar disponibilidade e esconder uma falha de segregação. Inclui uma identidade representativa de cada âmbito e um teste de acesso que deve ser recusado. A responsabilidade do PM é garantir que o plano tem estas verificações, responsáveis e um momento claro de decisão. APS observa o comportamento do serviço, o responsável de dados confirma o âmbito permitido e a equipa técnica demonstra a configuração efetiva. Se a recuperação funcional terminar primeiro, mantém visíveis as verificações de proteção em falta. A urgência do reporting pode justificar uma alternativa operacional aprovada e limitada; não transforma uma contagem correta em prova de autorização. Na passagem de turno, distingue sempre “dados recuperados”, “acessos verificados” e “serviço aceite”.
2. Rever a operação que substitui a tabela
As políticas de uma tabela BigQuery copiada ficam independentes da origem. Se a origem for restringida amanhã, não assumas que a cópia acompanha essa alteração. Identifica as cópias que continuam disponíveis e o responsável por manter os controlos em cada uma. A data da cópia ajuda a localizar o estado inicial, mas não prova a política atual. A revisão deve comparar a configuração no recurso que o consumidor realmente consulta. A operação de substituição também importa. Copiar uma origem sem row access policies sobre um destino protegido pode remover as políticas do destino; a documentação distingue o caso de append. TRUNCATE TABLE conserva o esquema e as políticas, enquanto operações que substituem a tabela com WRITE_TRUNCATE não oferecem a mesma preservação. Estes nomes semelhantes não são razão suficiente para considerar dois runbooks equivalentes. Analisa a semântica da operação que vai ser executada. No caso de reporting, desenha a sequência completa: restringir a utilização durante a recuperação, executar a operação aprovada, confirmar os dados, repor ou rever os controlos necessários e testar os acessos. A consulta do administrador é apenas um dos testes. Junta uma consulta de uma equipa com âmbito limitado e outra que tenta consultar dados fora desse âmbito. Guarda a revisão da configuração e os resultados sem incluir dados sensíveis desnecessários no ticket. Se o destino tiver de permanecer suspenso, comunica qual a evidência em falta e a próxima hora de decisão.
3. Seguir a identidade, a cache e o histórico
Uma exportação usa as permissões da identidade executora. O analista que pede o ficheiro pode ter acesso apenas mascarado, enquanto a conta que executa o job tem Fine-Grained Reader e obtém valores originais. Regista o principal efetivo e testa o conteúdo produzido. A autorização do bucket de destino controla quem abre o ficheiro, mas não demonstra que a coluna foi mascarada durante a exportação. São verificações complementares. Quando um copy job falha para uma identidade com acesso apenas mascarado, conceder acesso integral às colunas altera a fronteira de dados. Revê a necessidade e uma alternativa aprovada para produzir o resultado limitado. Não avalies a correção apenas pelo desaparecimento do erro. A equipa precisa de confirmar que o resultado entregue respeita o âmbito acordado. Usa valores sintéticos distintos para tornar visível a diferença entre original e máscara sem expor informação real. Depois de restringir acesso, considera resultados anteriores em cache e dados históricos. A documentação descreve reutilização de resultados em cache após trocar Fine-Grained Reader por Masked Reader. Um teste sem cache ajuda a observar o controlo atual, sem apagar cópias já obtidas. Se uma coluna foi limpa mantendo o esquema, valores históricos podem continuar acessíveis dentro da janela de time travel segundo as condições documentadas. Planeia quando relaxar o controlo, considerando essa janela. Nem limpar o presente nem alterar uma permissão prova, por si só, que todas as representações anteriores desapareceram.
4. Coordenar versões de secrets sem perder alterações
Um deployment cria a versão 18 de um secret e precisa de confirmar esse conteúdo. O acesso pelo número identifica a versão pretendida e tem a garantia de consistência forte descrita para adicionar e ler. Essa garantia específica não se estende automaticamente a latest ou aliases. Usa a referência adequada ao que estás a verificar e guarda a versão na evidência de implementação. Não registes o valor do secret no ticket ou num log de diagnóstico. A autorização tem uma dimensão temporal separada: alterações IAM podem propagar de forma eventual. Não inventes uma duração universal de revogação nem confundas leitura de versão com distribuição de permissões. Para uma mudança de acesso, identifica quem deixou de precisar dele, quais os recursos abrangidos e como vais observar o resultado. O teste deve usar a identidade pretendida; a conta do operador não substitui esse contexto. ETags ajudam a evitar que um runbook imponha uma intenção antiga sobre uma alteração concorrente. Se outra equipa modificou a versão e a condição falha, lê o estado atual, compara o que mudou e decide novamente. Retirar o ETag para repetir automaticamente elimina precisamente a proteção que revelou o conflito. Distingue o ETag do secret do ETag da versão: são recursos diferentes. Numa passagem de turno internacional, inclui a intenção da alteração e o estado lido, sem credenciais. Assim, quem recebe o incidente consegue decidir se deve continuar, corrigir ou abandonar a operação pendente.
5. Retirar chaves exige conhecer as dependências
Rodar uma chave KMS cria uma versão nova, mas não volta a cifrar automaticamente os arquivos existentes. Se o arquivo depende de v3 e a primária passou a v4, uma nova escrita bem-sucedida não demonstra a recuperação dos dados antigos. Na recifragem direta, ainda precisas do material anterior utilizável para decifrar antes de cifrar com a nova versão. Define a sequência e a evidência de recuperação antes de retirar dependências. O inventário de utilização ajuda a investigar, mas tem limites. Acompanha utilizações CMEK suportadas e não todo o uso direto de uma chave por aplicações. Pode estar limitado ao projeto consultado e agregar utilização de objetos ao bucket. Uma lista vazia, uma lista parcial ou uma entrada agregada não demonstra ausência de dependências. Complementa com os responsáveis das aplicações, registos de versões e testes de recuperação adequados ao arquivo. Se houver incerteza, mantém-na explícita na decisão. Na assinatura assimétrica, coordena a distribuição da nova chave pública e a mudança dos consumidores. Criar material não atualiza os verificadores externos. Considera também mensagens pendentes que ainda precisam da versão anterior de verificação. Se uma versão necessária foi desativada por engano e a recuperação é autorizada, reativá-la pode resolver essa dependência enquanto o material existe; confirma depois o serviço. A aceitação de um comando não é prova de arranque bem-sucedido nem de recuperação de todos os consumidores. O plano deve distinguir disponibilidade, proteção e remoção definitiva de material.
6. Interpretar a inspeção de IA por dimensão
Uma aplicação fictícia resume tickets sintéticos para apoiar uma equipa APS. A política exige determinados filtros e uma transformação antes de encaminhar conteúdo. Na resposta Model Armor, sucesso de invocação e ausência de deteção são dimensões diferentes. invocationResult=SUCCESS não anula MATCH_FOUND. PARTIAL indica filtros ignorados ou falhados. Num filtro específico, EXECUTION_SKIPPED não prova inspeção concluída. Lê os resultados e os detalhes relevantes à política, em vez de converter HTTP de sucesso numa autorização. A transformação é outra dimensão. Receber texto desidentificado não altera automaticamente a string original guardada pela aplicação. Verifica qual o payload efetivamente encaminhado e de que pedido veio. Um teste que mostra apenas o texto apresentado ao utilizador pode não observar o caminho entre aplicação e modelo. Mantém essa verificação no teste da integração, com dados sintéticos que tornem visível uma troca de versões. Confirma ainda as capacidades da integração escolhida. A API direta e um conector não oferecem necessariamente os mesmos comportamentos; a integração Gemini Enterprise descrita na documentação bloqueia deteções SDP em vez de desidentificar. As exclusões de template consultadas estão em Preview. Avaliam texto original sem tradução automática, pelo que precisam de testes nos idiomas usados. Para investigar NO_MATCH_FOUND com exclusões, conserva a configuração e consulta os labels de diagnóstico nos logs de sanitize quando ativados. Um estado agregado não explica sozinho como se chegou à decisão.
7. Exercício: selecionar o conteúdo correspondente
O programa local desta aula recebe um payload sintético, o tenant, o pedido, a fase prompt ou response, a revisão do template e uma janela de validade. A evidência declarada inclui os mesmos identificadores, um hash da entrada e resultados normalizados dos filtros exigidos. Estes campos são do exercício; não constituem um cliente nem uma reprodução integral da API Model Armor. A autenticidade da evidência não é verificada. Executa python3 run.py e compara os casos. O cenário favorável seleciona o hash do texto transformado, diferente do original. Alterar o payload depois da inspeção provoca uma divergência. Trocar tenant, pedido, fase ou revisão também impede satisfazer a política local. O programa conserva os motivos e não apresenta um payload selecionado quando há lacunas. Um hash compara bytes; não é cifragem, anonimização ou prova de autorização. Prevê o resultado antes de alterar uma variável. Um filtro obrigatório ignorado continua em falta mesmo que a transformação esteja completa. Um resultado unknown não é allow. Uma evidência do minuto 90 está na janela quando now=100 e maxAge=10; uma do minuto 89 fica antiga. Se a transformação exigida foi produzida a partir de outro conteúdo, o seu hash de origem denuncia a diferença. Explica também o caso em que um filtro não obrigatório é ignorado pelo modelo local: o programa mostra o nome, mas não decide se a lista de requisitos escolhida pelo autor era adequada.
8. Entregar evidência útil à equipa seguinte
Conclui o exercício com uma nota de decisão em inglês: identifica a operação, o resultado observado, a limitação e o responsável pelo próximo passo. Para reporting, pode faltar uma política no destino apesar da contagem correta. Para chaves, pode faltar confirmação de um consumidor fora do projeto visível. Para IA, pode existir transformação válida e um filtro obrigatório por executar. A nota deve tornar visíveis estas combinações sem reduzir tudo a um único indicador verde. Define o teste seguinte de forma concreta. “Validar segurança” não diz qual a identidade, versão de dados ou caminho a observar. Prefere indicar a consulta limitada que deve funcionar, a consulta fora do âmbito que deve falhar, o arquivo que precisa de recuperar ou o payload sintético que deve chegar ao ponto de encaminhamento. Regista a revisão usada para que uma mudança posterior não herde automaticamente uma evidência antiga. O código não acede a serviços cloud, não lê secrets reais, não executa inspeções e não aprova produção. Demonstra consistência entre declarações fornecidas. Um atacante capaz de fabricar toda a evidência pode torná-la internamente consistente; comparar hashes não autentica quem a produziu. Esta limitação deve aparecer no debrief. No trabalho real, a origem confiável da evidência, a autorização e a integração têm de ser verificadas pelos mecanismos próprios. A aprendizagem termina quando consegues explicar tanto a decisão produzida pelo exercício como a informação adicional necessária para uma decisão operacional.
"""Original offline policy worksheet; not a Model Armor client or security control."""
import copy
import hashlib
import itertools
import json
import re
from pathlib import Path
def digest(text):
return hashlib.sha256(text.encode('utf-8')).hexdigest()
def require(condition, message):
if not condition:
raise ValueError(message)
def text(value):
return isinstance(value, str) and bool(value.strip())
def integer(value):
return type(value) is int and value >= 0
def sha(value):
return isinstance(value, str) and re.fullmatch('[0-9a-f]{64}', value) is not None
def assess(data):
require(isinstance(data, dict), 'input object')
require(all(text(data.get(k)) for k in ('tenant', 'requestId', 'templateRevision', 'payload')), 'context')
require(data.get('phase') in ('prompt', 'response'), 'phase')
require(integer(data.get('now')) and integer(data.get('maxAge')), 'integer minutes')
require(type(data.get('requireTransformation')) is bool, 'transformation flag')
required = data.get('requiredFilters')
require(isinstance(required, list) and required and all(text(x) for x in required), 'required filters')
require(len(set(required)) == len(required), 'unique filters')
e = data.get('evidence')
require(isinstance(e, dict), 'evidence object')
require(all(text(e.get(k)) for k in ('tenant', 'requestId', 'templateRevision')), 'evidence context')
require(e.get('phase') in ('prompt', 'response'), 'evidence phase')
require(integer(e.get('checkedAt')) and e['checkedAt'] <= data['now'], 'evidence time')
require(sha(e.get('inputSha256')), 'input digest')
filters = e.get('filters')
require(isinstance(filters, dict), 'filter map')
for name, value in filters.items():
require(text(name) and isinstance(value, dict), 'filter record')
require(value.get('execution') in ('success', 'skipped', 'error'), 'execution')
require(value.get('decision') in ('allow', 'block', 'unknown'), 'decision')
transformation = e.get('transformation')
if transformation is not None:
require(isinstance(transformation, dict), 'transformation object')
require(transformation.get('status') in ('complete', 'failed'), 'transformation status')
require(sha(transformation.get('inputSha256')), 'transformation input digest')
require(text(transformation.get('text')), 'transformed text')
reasons = []
for field in ('tenant', 'requestId', 'templateRevision', 'phase'):
if data[field] != e[field]: reasons.append('mismatch:' + field)
input_sha = digest(data['payload'])
if e['inputSha256'] != input_sha: reasons.append('mismatch:payload')
if e['checkedAt'] < data['now'] - data['maxAge']: reasons.append('stale-evidence')
for name in sorted(required):
f = filters.get(name)
if f is None: reasons.append('missing-filter:' + name)
elif f['execution'] != 'success': reasons.append('incomplete-filter:' + name)
elif f['decision'] != 'allow': reasons.append('nonpermitting-filter:' + name)
output = data['payload']; selection = 'original'
if data['requireTransformation']:
selection = 'transformed'
if transformation is None: reasons.append('missing-transformation')
else:
if transformation['status'] != 'complete': reasons.append('failed-transformation')
if transformation['inputSha256'] != input_sha: reasons.append('mismatch:transformation-input')
output = transformation['text']
ready = not reasons
return {'readyUnderDeclaredPolicy': ready, 'reasons': reasons,
'selectedPayload': selection if ready else None,
'selectedSha256': digest(output) if ready else None,
'ignoredFilterNames': sorted(set(filters) - set(required)),
'liveInspectionPerformed': False, 'evidenceAuthenticityVerified': False,
'actualAuthorizationEvaluated': False, 'productionUseApproved': False}
def sample():
payload='SYNTHETIC ticket client=FICT-42: batch late'
context=dict(tenant='training-a',requestId='req-17',templateRevision='t4',phase='prompt')
return dict(context,now=100,maxAge=10,payload=payload,requireTransformation=True,
requiredFilters=['injection','uri'],
evidence=dict(context,checkedAt=95,inputSha256=digest(payload),
filters={n:dict(execution='success',decision='allow') for n in ['injection','uri']},
transformation=dict(status='complete',inputSha256=digest(payload),text='SYNTHETIC ticket client=[REDACTED]: batch late')))
def checks():
fixtures=[]
def check(name,data,ready,reason=None):
before=copy.deepcopy(data);actual=assess(data);assert data==before
assert actual['readyUnderDeclaredPolicy']==ready,name
if reason:assert reason in actual['reasons'],name
if not ready:assert actual['selectedSha256'] is None and actual['selectedPayload'] is None
fixtures.append(dict(id=name,**actual));return actual
x=sample();a=check('transformed-selected',x,True)
assert a['selectedSha256']==digest(x['evidence']['transformation']['text'])!=digest(x['payload'])
x=sample();x['requireTransformation']=False;check('original-permitted-by-local-policy',x,True)
for field,value in [('tenant','training-b'),('requestId','req-18'),('templateRevision','t5'),('phase','response')]:
x=sample();x['evidence'][field]=value;check('wrong-'+field,x,False,'mismatch:'+field)
x=sample();x['payload']+=' changed';check('changed-payload',x,False,'mismatch:payload')
x=sample();x['evidence']['checkedAt']=89;check('stale',x,False,'stale-evidence')
x=sample();x['evidence']['checkedAt']=90;check('window-boundary',x,True)
x=sample();x['evidence']['filters']['injection']['execution']='skipped';check('skipped-filter',x,False,'incomplete-filter:injection')
x=sample();del x['evidence']['filters']['uri'];check('missing-filter',x,False,'missing-filter:uri')
x=sample();x['evidence']['filters']['uri']['decision']='block';check('blocked-filter',x,False,'nonpermitting-filter:uri')
x=sample();x['evidence']['filters']['uri']['decision']='unknown';check('unknown-filter',x,False,'nonpermitting-filter:uri')
x=sample();x['evidence']['transformation']=None;check('missing-transformation',x,False,'missing-transformation')
x=sample();x['evidence']['transformation']['status']='failed';check('failed-transformation',x,False,'failed-transformation')
x=sample();x['evidence']['transformation']['inputSha256']=digest('different');check('wrong-transformation-input',x,False,'mismatch:transformation-input')
x=sample();x['evidence']['filters']['optional']=dict(execution='error',decision='unknown')
assert check('optional-outside-declared-policy',x,True)['ignoredFilterNames']==['optional']
combinations=0
for execution,decision,transform,revision in itertools.product(('success','skipped','error'),('allow','block','unknown'),('complete','failed'),('t4','t3')):
x=sample();x['evidence']['filters']['injection'].update(execution=execution,decision=decision)
x['evidence']['transformation']['status']=transform;x['evidence']['templateRevision']=revision
expected=execution=='success' and decision=='allow' and transform=='complete' and revision=='t4'
assert assess(x)['readyUnderDeclaredPolicy']==expected;combinations+=1
x=sample();x['requiredFilters'].append('safety');x['evidence']['filters']['safety']=dict(execution='success',decision='allow');ref=assess(x);permutations=0
for order in itertools.permutations(x['requiredFilters']):
y=copy.deepcopy(x);y['requiredFilters']=list(order);y['evidence']['filters']={n:x['evidence']['filters'][n]for n in order};assert assess(y)==ref;permutations+=1
invalid=[]
for k,v in [('now',True),('maxAge',-1),('payload',''),('phase','request'),('requireTransformation','yes'),('requiredFilters',[]),('requiredFilters',['x','x']),('evidence',None)]:
x=sample();x[k]=v;invalid.append(x)
for k,v in [('checkedAt',101),('checkedAt',1.5),('inputSha256','bad'),('tenant',''),('filters',[])]:
x=sample();x['evidence'][k]=v;invalid.append(x)
for k,v in [('execution','pass'),('decision','success')]:
x=sample();x['evidence']['filters']['injection'][k]=v;invalid.append(x)
for k,v in [('status','ready'),('inputSha256',''),('text',None)]:
x=sample();x['evidence']['transformation'][k]=v;invalid.append(x)
x=sample();del x['evidence']['inputSha256'];invalid.append(x)
x=sample();x['requiredFilters']=[''];invalid.append(x)
for x in invalid:
try:assess(x)
except ValueError:pass
else:raise AssertionError('invalid input accepted')
return {'fixtures':fixtures,'stateCombinations':combinations,'inputPermutations':permutations,'invalidInputs':len(invalid),
'inputPreserved':True,'orderIndependent':True,'network':False,'cloudExecuted':False,'persistentWrites':False,
'scriptSha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest()}
if __name__=='__main__':
print(json.dumps(checks(),ensure_ascii=False,indent=2))
Um reporting recuperado tem a contagem certa mas perdeu políticas de linhas; um assistente tem texto transformado mas encaminha o original com um filtro ignorado. Ambos exigem verificar o controlo que o resultado favorável não demonstra.
Armadilhas comuns
Confundir contagem com proteção, rotação com recifragem, inventário parcial com ausência de dependências ou HTTP de sucesso com inspeção completa.
Tópicos relacionados: Recuperação e resiliência · Identidades e autorização · Segurança de aplicações com IA
A recuperação deve preservar dados utilizáveis e controlos adequados; confirma o recurso, a identidade, a versão e a evidência de cada decisão.
Referência: Using row-level security with other BigQuery features · Current linked guide; edition date unconfirmed (2026-09-30 inspection)