Skip to content
Astera Technology

Test extractable text behind a PDF covering layer

Compare a visual cover with removing the supplied word before generation.

Download complete case notes and answer keys ↓

Set the decision and its input

A generated test PDF contains SECRETALPHA under a black covering layer and VISIBLE elsewhere. Covering it visually still leaves SECRETALPHA extractable in the tested artefact. The corrected path omits that word from the generated document content before creating a new PDF.

Filled local test worksheet
CaseInput JSON
Base test{"secret":"SECRETALPHA","remove":true}
Download editable CSV ↓

Run the supplied comparison

The example generates both PDFs in memory and extracts text with PDF.js. It checks this supplied word and the retained visible word only. It does not certify all metadata, annotations, embedded material or a universal secure-redaction procedure.

Faulty and required outputs
PathOutput JSON
Faulty path{"secretExtractable":true,"visibleExtractable":true,"pageCount":1}
Required path{"secretExtractable":false,"visibleExtractable":true,"pageCount":1}
Download editable CSV ↓
Node24.14.1 programme; Playwright1.63 Chromium and PDF.js6.4.299 required
import assert from 'node:assert/strict';
import {createRequire} from 'node:module';
import {pathToFileURL} from 'node:url';
const htmlEscape=s=>String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
async function withPage(action,options={}){
  const require=createRequire(import.meta.url);const {chromium}=require('playwright');
  const browser=await chromium.launch({headless:true});
  try{const context=await browser.newContext(options);const page=await context.newPage();
    const requests=[];await page.route('**/*',route=>{requests.push(route.request().url());return route.abort();});
    return await action(page,context,requests);
  }finally{await browser.close();}
}
async function pdfObservation(html){
  const bytes=await withPage(async page=>{await page.setContent(html);return page.pdf({preferCSSPageSize:true,printBackground:true});});
  const require=createRequire(import.meta.url);
  const {getDocument}=await import(pathToFileURL(require.resolve('pdfjs-dist/legacy/build/pdf.mjs')).href);
  const loadingTask=getDocument({data:Uint8Array.from(bytes),useSystemFonts:true,disableFontFace:true});
  try{const document=await loadingTask.promise;const pages=[];for(let n=1;n<=document.numPages;n++){
    const page=await document.getPage(n);const content=await page.getTextContent();
    pages.push(content.items.filter(x=>typeof x.str==='string'&&x.str).map(x=>({text:x.str,x:x.transform[4],y:x.transform[5]})));
  }return {pageCount:document.numPages,pages};}finally{await loadingTask.destroy();}
}
const printStyle='<style>@page{size:100mm 100mm;margin:10mm}body{margin:0;font:10px Arial}p{margin:0}</style>';

const run=async function run(i,corrected=true){
    if(typeof i.remove!=='boolean'||typeof i.secret!=='string'||!/^[A-Z]{1,20}$/.test(i.secret)||i.secret==='VISIBLE')return {held:true};
    const secret=corrected&&i.remove?'':i.secret;
    const observation=await pdfObservation(printStyle+'<div style="position:relative;height:10mm"><p>'+htmlEscape(secret)+'</p><div style="position:absolute;inset:0;background:black"></div></div><p>VISIBLE</p>');
    const words=observation.pages.flat().map(x=>x.text);
    return {secretExtractable:words.includes(i.secret),visibleExtractable:words.includes('VISIBLE'),pageCount:observation.pageCount};
  };
const cases=[{"input":{"secret":"SECRETALPHA","remove":true},"corrected":false,"expected":{"secretExtractable":true,"visibleExtractable":true,"pageCount":1}},{"input":{"secret":"SECRETALPHA","remove":true},"corrected":true,"expected":{"secretExtractable":false,"visibleExtractable":true,"pageCount":1}},{"input":{"secret":"SECRETALPHA","remove":false},"corrected":true,"expected":{"secretExtractable":true,"visibleExtractable":true,"pageCount":1}},{"input":{"secret":"SECRETALPHA","remove":null},"corrected":true,"expected":{"held":true}}];
for(const test of cases){const actual=await run(test.input,test.corrected);assert.deepEqual(actual,test.expected);console.log(actual);}
Download runnable example ↓

Choose from the boundary evidence

A supplied keep decision retains the word. An unknown removal decision is held rather than inferred from a black rectangle.

  1. State the exact word disposition
  2. Generate the required artefact
  3. Extract and compare supplied text

Do not treat visual covering as evidence that stored text was removed.

Editable boundary inputs and outputs
ConditionInput JSONExpected output JSON
supplied keep decision{"secret":"SECRETALPHA","remove":false}{"secretExtractable":true,"visibleExtractable":true,"pageCount":1}
removal decision unknown{"secret":"SECRETALPHA","remove":null}{"held":true}
Download editable CSV ↓

What does the base corrected extraction establish?

Show the worked answer

SECRETALPHA is not extracted from that generated PDF, VISIBLE remains, and it has one page. This is a bounded artefact result, not a general security guarantee.

Example files

Related resources