<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: AngelBot AI </title>
    <description>The latest articles on DEV Community by AngelBot AI  (@angelbot_ai).</description>
    <link>https://dev.to/angelbot_ai</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4165648%2Feb71f45a-b04d-4cd5-99ed-17f7282ce683.png</url>
      <title>DEV Community: AngelBot AI </title>
      <link>https://dev.to/angelbot_ai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/angelbot_ai"/>
    <language>en</language>
    <item>
      <title>Your PDF Viewer Runs in the Browser. Does That Actually Make It Secure?</title>
      <dc:creator>AngelBot AI </dc:creator>
      <pubDate>Thu, 29 Oct 2026 11:22:02 +0000</pubDate>
      <link>https://dev.to/angelbot_ai/your-pdf-viewer-runs-in-the-browser-does-that-actually-make-it-secure-11pp</link>
      <guid>https://dev.to/angelbot_ai/your-pdf-viewer-runs-in-the-browser-does-that-actually-make-it-secure-11pp</guid>
      <description>&lt;p&gt;Moving document processing into the browser can reduce server-side exposure.&lt;/p&gt;

&lt;p&gt;But there is a difference between processing a document locally and building a genuinely secure document workflow.&lt;/p&gt;

&lt;p&gt;That distinction matters when your application handles contracts, financial records, employee documents, legal evidence, or other sensitive information.&lt;/p&gt;

&lt;p&gt;Let's look at what client-side PDF processing changes, what it does not change, and what developers should verify before calling a document viewer secure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What client-side processing actually means&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In a traditional architecture, a user uploads a PDF to a server, the server processes it, and the application returns a result.&lt;/p&gt;

&lt;p&gt;Depending on the system, that process might involve temporary files, document conversion services, storage systems, logging, or third-party APIs.&lt;/p&gt;

&lt;p&gt;With a client-side architecture, some or all document processing happens inside the user's browser.&lt;/p&gt;

&lt;p&gt;That can eliminate the need to upload a document to your application server for those particular operations.&lt;/p&gt;

&lt;p&gt;However, client-side processing is an architectural choice, not a complete security guarantee.&lt;/p&gt;

&lt;p&gt;The application can still introduce risks through its own code, dependencies, permissions, browser environment, and data flows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Find out where the document actually travels&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not rely on a product description that says processing happens locally.&lt;br&gt;
Inspect the implementation.&lt;/p&gt;

&lt;p&gt;Ask questions such as:&lt;br&gt;
Is the original PDF uploaded to an application server?&lt;/p&gt;

&lt;p&gt;Does the application send document content to an external API?&lt;/p&gt;

&lt;p&gt;Are extracted text, annotations, or search queries transmitted anywhere?&lt;/p&gt;

&lt;p&gt;Are document contents included in error reports or analytics events?&lt;/p&gt;

&lt;p&gt;Does the application use remote conversion or OCR services?&lt;/p&gt;

&lt;p&gt;Are temporary copies written to storage?&lt;/p&gt;

&lt;p&gt;The answers depend on the actual implementation and configuration.&lt;br&gt;
Browser developer tools can help you inspect network requests during document loading, search, annotation, and export. For a serious security review, combine that inspection with source-code review and testing across all relevant workflows.&lt;/p&gt;

&lt;p&gt;A quiet network tab during initial loading is not proof that no data leaves the browser later.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;strong&gt;Remember that the browser is not a trusted vault.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Local processing changes where computation happens, but it does not make the user's environment invulnerable.&lt;/p&gt;

&lt;p&gt;A malicious browser extension, compromised device, injected script, or cross-site scripting vulnerability may expose data accessible to the page.&lt;br&gt;
Third-party JavaScript deserves particular attention. Code executing in the same page context may have access to application data, depending on how the application is designed.&lt;/p&gt;

&lt;p&gt;For sensitive document applications:&lt;br&gt;
Minimise unnecessary third-party scripts.&lt;br&gt;
Apply an appropriate content security policy.&lt;/p&gt;

&lt;p&gt;Keep dependencies updated.&lt;/p&gt;

&lt;p&gt;Validate document inputs and handle malformed files safely.&lt;br&gt;
Avoid placing extracted document content in unnecessary logs.&lt;br&gt;
Review browser storage and caching behavior.&lt;/p&gt;

&lt;p&gt;Test export and download paths, not just viewing.&lt;br&gt;
The threat model should determine which controls are required.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; &lt;strong&gt;Security must follow the document through every operation.&lt;/strong&gt;
Opening a PDF is only the beginning. **&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users may search, highlight, annotate, redact, export, print, or download it.&lt;/p&gt;

&lt;p&gt;Each operation creates different questions.&lt;/p&gt;

&lt;p&gt;For example, drawing a black rectangle over sensitive text does not necessarily remove that text from the underlying PDF. A safe redaction process must address the actual document content, not just its appearance.&lt;/p&gt;

&lt;p&gt;Similarly, a viewer that processes the original file locally could still upload an exported document if its integration sends the result to a server.&lt;/p&gt;

&lt;p&gt;Security reviews should follow the document from input to final output.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Make the architecture testable.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful test plan includes several independent checks.&lt;/p&gt;

&lt;p&gt;Network inspection: Monitor requests during all document operations.&lt;br&gt;
Output inspection: Extract text from exported PDFs and check whether supposedly removed content remains.&lt;/p&gt;

&lt;p&gt;Dependency review: Examine the packages and external resources used by the application.&lt;/p&gt;

&lt;p&gt;Permission testing: Verify that disabled operations cannot be accessed through alternate UI paths or public APIs.&lt;/p&gt;

&lt;p&gt;Failure testing: Try malformed documents, interrupted operations, and unsuccessful exports.&lt;/p&gt;

&lt;p&gt;No single check proves that a viewer is secure. Together, they provide stronger evidence than a marketing claim.&lt;/p&gt;

&lt;p&gt;Exploring a client-side PDF viewer&lt;/p&gt;

&lt;p&gt;We have been developing TeamSync PDF Viewer, an open-source React PDF viewer SDK designed around client-side document processing, with capabilities including search, annotations, and redaction.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Repository: &lt;a href="https://github.com/angelbot-ai/TeamSync-PDF-Viewer" rel="noopener noreferrer"&gt;https://github.com/angelbot-ai/TeamSync-PDF-Viewer&lt;/a&gt;&lt;br&gt;
*&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;If you are evaluating browser-based document processing, the source code gives you a starting point for examining the implementation rather than relying only on a feature list.&lt;/p&gt;

&lt;p&gt;Review the architecture against your own threat model before using it with sensitive documents. No viewer should be treated as automatically secure simply because it runs in a browser.&lt;/p&gt;

&lt;p&gt;What is your minimum security checklist?&lt;/p&gt;

&lt;p&gt;If you were evaluating a PDF viewer for a production application, which would you investigate first: network traffic, third-party dependencies, redaction correctness, or exported-file handling?&lt;/p&gt;

&lt;p&gt;Share your approach in the comments. If this project is relevant to your work, try it and consider starring the repository if you find it useful.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>Your PDF Viewer Runs in the Browser. Does That Actually Make It Secure?</title>
      <dc:creator>AngelBot AI </dc:creator>
      <pubDate>Mon, 26 Oct 2026 08:52:00 +0000</pubDate>
      <link>https://dev.to/angelbot_ai/your-pdf-viewer-runs-in-the-browser-does-that-actually-make-it-secure-5gd2</link>
      <guid>https://dev.to/angelbot_ai/your-pdf-viewer-runs-in-the-browser-does-that-actually-make-it-secure-5gd2</guid>
      <description>&lt;p&gt;Moving document processing into the browser can reduce server-side exposure.&lt;/p&gt;

&lt;p&gt;But there is a difference between processing a document locally and building a genuinely secure document workflow.&lt;/p&gt;

&lt;p&gt;That distinction matters when your application handles contracts, financial records, employee documents, legal evidence, or other sensitive information.&lt;/p&gt;

&lt;p&gt;Let's look at what client-side PDF processing changes, what it does not change, and what developers should verify before calling a document viewer secure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What client-side processing actually means&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In a traditional architecture, a user uploads a PDF to a server, the server processes it, and the application returns a result.&lt;br&gt;
Depending on the system, that process might involve temporary files, document conversion services, storage systems, logging, or third-party APIs.&lt;/p&gt;

&lt;p&gt;With a client-side architecture, some or all document processing happens inside the user's browser.&lt;/p&gt;

&lt;p&gt;That can eliminate the need to upload a document to your application server for those particular operations.&lt;/p&gt;

&lt;p&gt;However, client-side processing is an architectural choice, not a complete security guarantee.&lt;/p&gt;

&lt;p&gt;The application can still introduce risks through its own code, dependencies, permissions, browser environment, and data flows.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Find out where the document actually travels.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not rely on a product description that says processing happens locally.&lt;br&gt;
Inspect the implementation.&lt;/p&gt;

&lt;p&gt;Ask questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the original PDF uploaded to an application server?&lt;/li&gt;
&lt;li&gt;Does the application send document content to an external API?&lt;/li&gt;
&lt;li&gt;Are extracted text, annotations, or search queries transmitted anywhere?&lt;/li&gt;
&lt;li&gt;Are document contents included in error reports or analytics events?&lt;/li&gt;
&lt;li&gt;Does the application use remote conversion or OCR services?&lt;/li&gt;
&lt;li&gt;Are temporary copies written to storage?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The answers depend on the actual implementation and configuration.&lt;/p&gt;

&lt;p&gt;Browser developer tools can help you inspect network requests during document loading, search, annotation, and export. For a serious security review, combine that inspection with source-code review and testing across all relevant workflows.&lt;/p&gt;

&lt;p&gt;A quiet network tab during initial loading is not proof that no data leaves the browser later.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Remember that the browser is not a trusted vault&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Local processing changes where computation happens, but it does not make the user's environment invulnerable.&lt;/p&gt;

&lt;p&gt;A malicious browser extension, compromised device, injected script, or cross-site scripting vulnerability may expose data accessible to the page.&lt;br&gt;
Third-party JavaScript deserves particular attention. Code executing in the same page context may have access to application data, depending on how the application is designed.&lt;/p&gt;

&lt;p&gt;For sensitive document applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Minimise unnecessary third-party scripts.&lt;/li&gt;
&lt;li&gt;Apply an appropriate Content Security Policy.&lt;/li&gt;
&lt;li&gt;Keep dependencies updated.&lt;/li&gt;
&lt;li&gt;Validate document inputs and handle malformed files safely.&lt;/li&gt;
&lt;li&gt;Avoid placing extracted document content in unnecessary logs.&lt;/li&gt;
&lt;li&gt;Review browser storage and caching behaviour.&lt;/li&gt;
&lt;li&gt;Test export and download paths, not just viewing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The threat model should determine which controls are required.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Security must follow the document through every operation&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Opening a PDF is only the beginning.&lt;/p&gt;

&lt;p&gt;Users may search, highlight, annotate, redact, export, print, or download it.&lt;/p&gt;

&lt;p&gt;Each operation creates different questions.&lt;/p&gt;

&lt;p&gt;For example, drawing a black rectangle over sensitive text does not necessarily remove that text from the underlying PDF. A safe redaction process must address the actual document content, not just its appearance.&lt;/p&gt;

&lt;p&gt;Similarly, a viewer that processes the original file locally could still upload an exported document if its integration sends the result to a server.&lt;/p&gt;

&lt;p&gt;Security reviews should follow the document from input to final output.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Make the architecture testable&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A useful test plan includes several independent checks.&lt;/p&gt;

&lt;p&gt;Network inspection: Monitor requests during all document operations.&lt;/p&gt;

&lt;p&gt;Output inspection: Extract text from exported PDFs and check whether supposedly removed content remains.&lt;/p&gt;

&lt;p&gt;Dependency review: Examine the packages and external resources used by the application.&lt;/p&gt;

&lt;p&gt;Permission testing: Verify that disabled operations cannot be accessed through alternate UI paths or public APIs.&lt;/p&gt;

&lt;p&gt;Failure testing: Try malformed documents, interrupted operations, and unsuccessful exports.&lt;/p&gt;

&lt;p&gt;No single check proves that a viewer is secure. Together, they provide stronger evidence than a marketing claim.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Exploring a client-side PDF viewer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We have been developing TeamSync PDF Viewer, an open-source React PDF viewer SDK designed around client-side document processing, with capabilities including search, annotations, and redaction.&lt;/p&gt;

&lt;p&gt;Repository: &lt;a href="https://github.com/angelbot-ai/TeamSync-PDF-Viewer" rel="noopener noreferrer"&gt;https://github.com/angelbot-ai/TeamSync-PDF-Viewer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are evaluating browser-based document processing, the source code gives you a starting point for examining the implementation rather than relying only on a feature list.&lt;/p&gt;

&lt;p&gt;Review the architecture against your own threat model before using it with sensitive documents. No viewer should be treated as automatically secure simply because it runs in a browser.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is your minimum security checklist?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you were evaluating a PDF viewer for a production application, which would you investigate first: network traffic, third-party dependencies, redaction correctness, or exported-file handling?&lt;/p&gt;

&lt;p&gt;Share your approach in the comments. If this project is relevant to your work, try it and consider starring the repository if you find it useful.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>PDF rendering and performance</title>
      <dc:creator>AngelBot AI </dc:creator>
      <pubDate>Thu, 22 Oct 2026 05:57:23 +0000</pubDate>
      <link>https://dev.to/angelbot_ai/pdf-rendering-and-performance-3oj4</link>
      <guid>https://dev.to/angelbot_ai/pdf-rendering-and-performance-3oj4</guid>
      <description>&lt;p&gt;&lt;strong&gt;Your PDF Viewer Works Until Someone Opens a 300-Page File&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A PDF viewer can look perfectly fine during development.&lt;/p&gt;

&lt;p&gt;You open a five-page document. The pages render correctly. Zoom works. Scrolling feels smooth.&lt;/p&gt;

&lt;p&gt;Then someone uploads a 300-page technical manual, a large contract, or a scanned report with high-resolution images. Suddenly, the experience changes.&lt;/p&gt;

&lt;p&gt;The browser becomes sluggish. Pages take too long to appear. Zooming feels delayed. Sometimes the interface becomes almost unusable.&lt;/p&gt;

&lt;p&gt;The problem is not always the PDF itself. Sometimes, it is how the viewer handles rendering, memory, and the browser viewport.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why large PDFs are different&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A PDF is not simply a collection of images waiting to be displayed.&lt;/p&gt;

&lt;p&gt;A document may contain text instructions, vector graphics, embedded fonts, images, annotations, and other objects. Rendering those elements requires work, and the cost varies significantly from one page to another.&lt;/p&gt;

&lt;p&gt;A 300-page text document may behave very differently from a 30-page architectural drawing containing enormous images.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;This is why page count alone is not a reliable measure of rendering cost. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do not render every page at full resolution immediately&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One common mistake is trying to render the entire document as soon as it loads.&lt;/p&gt;

&lt;p&gt;If each page creates a canvas, that can quickly increase memory consumption. High-resolution canvases are particularly expensive.&lt;/p&gt;

&lt;p&gt;A rough estimate for an RGBA canvas is:&lt;br&gt;
width × height × 4 bytes&lt;/p&gt;

&lt;p&gt;For example, a canvas measuring 1,500 × 2,000 pixels requires approximately 11.4 MiB of pixel memory before accounting for additional browser and rendering overhead.&lt;/p&gt;

&lt;p&gt;Multiply that by dozens or hundreds of pages, and the cost becomes significant.&lt;/p&gt;

&lt;p&gt;A better approach: Render visible pages first, then render nearby pages as the user scrolls. Release or reuse resources when appropriate.&lt;br&gt;
Virtualisation and lazy rendering can help, but they should be designed around the viewer's actual interaction model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Treat Zoom as a rendering problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Zooming is more complicated than changing the CSS width of a canvas.&lt;br&gt;
If the display size changes but the underlying rendering resolution does not, text and graphics may become blurry.&lt;/p&gt;

&lt;p&gt;If every tiny zoom movement triggers an expensive rendering operation, the interface can feel slow.&lt;/p&gt;

&lt;p&gt;A practical strategy is to separate the immediate visual response from the more expensive rendering work:&lt;/p&gt;

&lt;p&gt;Update the viewport responsively.&lt;/p&gt;

&lt;p&gt;Avoid triggering a new render for every intermediate input event.&lt;/p&gt;

&lt;p&gt;Cancel obsolete rendering tasks where the rendering library supports it.&lt;/p&gt;

&lt;p&gt;Render at the appropriate resolution after the zoom interaction settles.&lt;/p&gt;

&lt;p&gt;The goal is to keep the interface responsive without sacrificing visual quality.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the layers aligned.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A modern PDF viewer often needs more than a canvas.&lt;br&gt;
It may also have a text layer for selection and search, an annotation layer for interactive elements, and controls for navigation.&lt;/p&gt;

&lt;p&gt;These layers must agree on page dimensions, scale, rotation, and position.&lt;br&gt;
If they do not, users may see text that is difficult to select, highlights that miss their targets, or annotations that drift after zooming.&lt;/p&gt;

&lt;p&gt;Whenever the viewport changes, check that every interactive layer uses consistent coordinate transformations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measure the right things&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of testing only whether a document opens, measure the following:&lt;br&gt;
Time until the first page becomes visible.&lt;/p&gt;

&lt;p&gt;Time required to render a typical page. Responsiveness during rapid zooming and scrolling. Memory usage with large documents. Behaviour when users jump directly to a distant page. Recovery after switching between documents.&lt;/p&gt;

&lt;p&gt;Test with different document types, not just different page counts.&lt;/p&gt;

&lt;p&gt;A large scanned document, a vector-heavy engineering drawing, and a text-heavy report can expose very different problems.&lt;/p&gt;

&lt;p&gt;Try it in a real React application&lt;/p&gt;

&lt;p&gt;If you are building a document-heavy application, you do not necessarily need to build the entire viewing interface from scratch.&lt;/p&gt;

&lt;p&gt;We have been working on TeamSync PDF Viewer, an open-source PDF viewer SDK for React applications, with document rendering, search, annotations, and other document interaction features.&lt;/p&gt;

&lt;p&gt;GitHub repository: &lt;a href="https://github.com/angelbot-ai/TeamSync-PDF-Viewer" rel="noopener noreferrer"&gt;https://github.com/angelbot-ai/TeamSync-PDF-Viewer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can inspect the implementation, try the project, and explore the integration options before deciding whether it fits your application.&lt;br&gt;
One question for other developers&lt;/p&gt;

&lt;p&gt;What becomes your biggest bottleneck when a PDF viewer handles large documents: canvas memory, rendering time, text-layer alignment, or zoom responsiveness?&lt;/p&gt;

&lt;p&gt;I would be interested in hearing what you have encountered in production.&lt;/p&gt;

&lt;p&gt;If the project looks useful for your work, consider giving the repository a star. If you try it, feedback and reproducible issues are especially valuable to an open-source project.&lt;/p&gt;

</description>
      <category>react</category>
      <category>javascript</category>
      <category>pdf</category>
      <category>devops</category>
    </item>
    <item>
      <title>A Black Rectangle Is Not a Redaction was published.</title>
      <dc:creator>AngelBot AI </dc:creator>
      <pubDate>Thu, 15 Oct 2026 04:24:50 +0000</pubDate>
      <link>https://dev.to/angelbot_ai/a-black-rectangle-is-not-a-redaction-was-published-1933</link>
      <guid>https://dev.to/angelbot_ai/a-black-rectangle-is-not-a-redaction-was-published-1933</guid>
      <description>&lt;p&gt;&lt;strong&gt;A Black Rectangle Is Not a Redaction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every year the same story pops up in the news. A law firm, a government agency or a company publishes a document with sections blacked out. Someone opens the file, highlights the blacked-out sections, hits copy, and pastes the secret text right out.&lt;/p&gt;

&lt;p&gt;It is embarrassing and it keeps happening. The reason is that most tools make it easy to draw a black box and difficult to actually remove the information underneath.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why does the text survive?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A PDF is closer to a set of drawing instructions than to a "document" in the everyday sense.&lt;/p&gt;

&lt;p&gt;A line of text in a PDF looks roughly like this (simplified):&lt;/p&gt;

&lt;p&gt;BT&lt;br&gt;
/F1 12 Tf&lt;br&gt;
72,700 TD&lt;br&gt;
(Account Number: 4839 2201 5567) Tj&lt;br&gt;
ET&lt;br&gt;
BT begins a text object, Tf picks a font, Td positions it, Tj draws the string, and ET ends the object.&lt;br&gt;
Both sets of instructions live in the same file. The renderer draws the text, then draws an opaque rectangle on top. Visually, the account number is gone. Structurally, nothing was removed.&lt;/p&gt;

&lt;p&gt;That means every mechanism that reads the PDF instead of just looking at it will still find the string:&lt;/p&gt;

&lt;p&gt;Text selection and copy&lt;br&gt;
Search in the viewer and at the OS level.&lt;br&gt;
pdftotext and other extraction libraries&lt;br&gt;
Search indexing if the file is uploaded anywhere&lt;br&gt;
Accessibility tools reading the text layer&lt;br&gt;
The rectangle only defeats human eyes. It defeats nothing else.&lt;/p&gt;

&lt;p&gt;And this isn't a bug. It's the default behavior of the most obvious interaction in many UIs. If your annotation tool has a filled-rectangle shape, a user can create a document that looks redacted but isn't in about four seconds, with no warning.&lt;br&gt;
Now imagine you open that file and draw a black box over the account number. In most tools, you haven't deleted the text. You've just added more instructions: "draw a filled rectangle here."&lt;/p&gt;

&lt;p&gt;BT&lt;br&gt;
/F1 12 Tf&lt;br&gt;
72 700 Td&lt;br&gt;
(Account Number: 4839 2201 5567) Tj&lt;br&gt;
ET&lt;br&gt;
q&lt;br&gt;
0 0 0 rg&lt;br&gt;
70 695 180 18 re&lt;br&gt;
f&lt;br&gt;
Q&lt;/p&gt;

&lt;p&gt;Both sets of instructions live in the same file. The renderer draws the text, then draws an opaque rectangle on top. Visually, the account number is gone. Structurally, nothing was removed.&lt;/p&gt;

&lt;p&gt;That means every mechanism that reads the PDF instead of just looking at it will still find the string:&lt;/p&gt;

&lt;p&gt;Text selection and copy&lt;br&gt;
Search in the viewer and at the OS level&lt;br&gt;
pdftotext and other extraction libraries&lt;br&gt;
Search indexing if the file is uploaded anywhere&lt;br&gt;
Accessibility tools reading the text layer&lt;br&gt;
The rectangle only defeats human eyes. It defeats nothing else.&lt;/p&gt;

&lt;p&gt;And this isn't a bug. It's the default behaviour of the most obvious interaction in many UIs. If your annotation tool has a filled-rectangle shape, a user can create a document that looks redacted but isn't in about four seconds, with no warning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The three things that have to be true&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you stop thinking of redaction as "drawing a black box" it becomes clearer what a safe redaction actually needs to do. A tool can get one or two of these right and still be unsafe, because all three have to work together.&lt;/p&gt;

&lt;p&gt;The visual content has to be destroyed, not covered. On export, the affected region has to be rewritten so that the original marks are no longer present in the output — not present-but-hidden. We do this by rasterising and baking the redacted region into the exported PDF, so the sensitive content becomes part of a flattened result rather than sitting underneath something opaque.&lt;/p&gt;

&lt;p&gt;Clean up the text layer too. Even with the visuals handled, a PDF usually carries a separate text layer used for search, selection and accessibility. If the sensitive string is still in that layer, nothing has been protected. So a proper redaction has to do two things: remove the text from the visible page, and remove it from the selectable and searchable text layer as well. Only then do copy-paste, search and extraction stop reaching it.&lt;/p&gt;

&lt;p&gt;Keep "marked" and "applied" redactions separate. This is where a lot of tools go wrong, and it is the more subtle failure of the three. There are two states:&lt;/p&gt;

&lt;p&gt;Marked — the user has drawn boxes over sensitive areas, but nothing has been permanently removed yet. This is editor state, and the user should be able to move, edit or delete these marks.&lt;br&gt;
Applied — the document has been transformed so the original content is actually gone. This is a deliberate, destructive action.&lt;br&gt;
The dangerous bug happens when these two states get blurred. A user sees black boxes, assumes the document is safe, and exports it — but the export path never ran the apply step. The file looks correct and is not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The coordinate problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is a detail underneath all of this that took us longer than expected.&lt;/p&gt;

&lt;p&gt;To remove text from the visible layer you need to know which extracted characters correspond to the region the user marked. That is not free. Text extraction gives you strings and geometry; a marked redaction gives you a rectangle. Mapping between them means maintaining a correspondence between extracted characters and their positions on the page.&lt;/p&gt;

&lt;p&gt;We render and extract with PDF.js, and we ended up with a shared text builder and a single bounds-for-range function used by both document search and by regex-driven redaction. Which sounds like ordinary refactoring, and it was — but the reason it mattered is that search and redaction were answering the same question, where on the page is this text, and had drifted into two answers.&lt;/p&gt;

&lt;p&gt;Two answers to that question are a correctness bug waiting to happen. If search can find a string that redaction cannot locate, you have a tool that will tell a user the sensitive text exists and then fail to remove it.&lt;/p&gt;

&lt;p&gt;The regex path is worth mentioning because it addresses a real gap in manual redaction. A person marking boxes by eye will redact the account number they noticed. They will miss the second occurrence on page 9. Pattern-driven redaction over the extracted text catches what visual inspection misses, and it only works if the coordinate mapping is shared and trustworthy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this does not solve&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I would rather say this plainly than have someone discover it.&lt;/p&gt;

&lt;p&gt;The original file is untouched. All of this happens on export. If a user redacts a document and then sends the original instead of the exported copy, nothing has been protected. The workflow has to make the exported file the obvious thing to hand over.&lt;/p&gt;

&lt;p&gt;Rasterising has costs. The redacted region stops being vector text. That means it is no longer selectable or machine-readable in that area, which is usually the point, but it also affects file size and accessibility. It is a deliberate trade, not a free win.&lt;/p&gt;

&lt;p&gt;Visual redaction is not full document cleaning. Metadata, XMP, embedded attachments, form field values, annotations elsewhere in the file, and revision history are separate surfaces. Blacking out a name on page 3 does nothing about that name sitting in the document title. If you are redacting for a legal or compliance obligation, the page content is one item on a longer checklist.&lt;/p&gt;

&lt;p&gt;Scanned documents are a different problem. If the page is an image with no text layer, there is no extraction to work with and no strings to pattern-match. Rasterising the region works; everything that depends on the text layer does not apply.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I would check tonight&lt;/strong&gt;&lt;br&gt;
If you ship anything that lets users redact a PDF, three checks, in order:&lt;/p&gt;

&lt;p&gt;Export a redacted file and run pdftotext on it. If the redacted string comes back, you are drawing rectangles. This takes thirty seconds and it is the only test that matters.&lt;/p&gt;

&lt;p&gt;Try to reach an export with pending, unapplied redactions. If you can, and nothing warns you, that is your most dangerous path.&lt;/p&gt;

&lt;p&gt;Search your exported document for the redacted string inside your own viewer. If your search index and your redaction logic disagree about where text is, this is where it shows up.&lt;/p&gt;

&lt;p&gt;The distinction worth carrying away is the one that took us too long to state clearly: redaction is a document transformation, not an annotation. The black rectangle is a status indicator. The security property comes from the original information no longer being reachable through the normal mechanisms — extraction, search, copy — and those mechanisms do not care what the page looks like.&lt;/p&gt;

&lt;p&gt;We work on &lt;a href="https://github.com/angelbot-ai/TeamSync-PDF-Viewer" rel="noopener noreferrer"&gt;TeamSync PDF Viewer&lt;/a&gt;, an open-source client-side PDF viewer for React.&lt;/p&gt;

&lt;p&gt;If you have hit the copy-paste-recovers-the-text problem from the other side — as the person who found it in someone else's document — I would be interested to know which tool produced it. I suspect the list is short and repetitive.&lt;/p&gt;

</description>
      <category>pdf</category>
      <category>javascript</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Our stylesheet was quietly restyling our users' apps. It took us weeks to notice.</title>
      <dc:creator>AngelBot AI </dc:creator>
      <pubDate>Mon, 12 Oct 2026 06:56:26 +0000</pubDate>
      <link>https://dev.to/angelbot_ai/our-stylesheet-was-quietly-restyling-our-users-apps-it-took-us-weeks-to-notice-542i</link>
      <guid>https://dev.to/angelbot_ai/our-stylesheet-was-quietly-restyling-our-users-apps-it-took-us-weeks-to-notice-542i</guid>
      <description>&lt;p&gt;Our stylesheet was quietly restyling our users' apps. It took us weeks to notice.&lt;/p&gt;

&lt;p&gt;A user opened an issue that made no sense to us.&lt;/p&gt;

&lt;p&gt;Their sidebar had gone wrong. Wrong width, white background where it should have been dark, and it had shifted a few pixels down the page. Nothing to do with PDFs. They had not opened a PDF. They had not even clicked the button that opens one.&lt;/p&gt;

&lt;p&gt;They had installed our viewer package, imported it, and their own sidebar broke.&lt;/p&gt;

&lt;p&gt;We spent longer than I want to admit looking in the wrong place.&lt;/p&gt;

&lt;p&gt;The part that did not add up&lt;/p&gt;

&lt;p&gt;The thing that kept throwing us off was the timing. The breakage happened before any document was loaded. Our renderer had not run. No canvas existed. As far as we were concerned the library had not done anything yet.&lt;/p&gt;

&lt;p&gt;That turned out to be the clue, not the mystery. Because something had run. The import had pulled in our stylesheet.&lt;/p&gt;

&lt;p&gt;What was actually in our CSS&lt;/p&gt;

&lt;p&gt;Our package ships &lt;em&gt;dist/style.css&lt;/em&gt;. We had assumed that file contained our rules. It contained our rules plus pdfjs-dist/web/pdf_viewer.css, which is around &lt;strong&gt;900 selectors&lt;/strong&gt; written for pdf.js's own standalone viewer application.&lt;/p&gt;

&lt;p&gt;That file was never meant to be a dependency of somebody else's design system. It was written for a page where pdf.js owns the whole document, so its class names are as plain as you would expect in that situation:&lt;/p&gt;

&lt;p&gt;.sidebar&lt;br&gt;
.dialog&lt;br&gt;
.toggle-button&lt;br&gt;
.closeButton&lt;br&gt;
.messageBar&lt;/p&gt;

&lt;p&gt;Read that list again as a library author. Those are not namespaced names. Those are the names half the web already uses.&lt;/p&gt;

&lt;p&gt;We had scoped our own rules under .tspdf-root. We were reasonably careful about that. The pdf.js rules went out completely unscoped, because we had never actually looked at what our bundled CSS contained after the build ran.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it always won&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here is the bit worth remembering even if you never touch a PDF library.&lt;/p&gt;

&lt;p&gt;Our stylesheet is injected at runtime, when the module is imported. That puts it late in document order. And in the cascade, when two rules have the same specificity, the later one wins.&lt;/p&gt;

&lt;p&gt;So the host application had a perfectly sensible .sidebar rule of its own, written first, loaded normally. Ours arrived afterwards and quietly took the tie. Same specificity, later position, done. Their sidebar picked up position: relative, an inset-block-start they never asked for, a width meant for a PDF thumbnail rail, and a white background.&lt;/p&gt;

&lt;p&gt;No error. No warning. No console output. Just a layout that was subtly wrong and a developer with no reason to suspect the PDF library, because they had not used it yet.&lt;/p&gt;

&lt;p&gt;Silent conflicts are the worst kind. A crash tells you where to look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix that did not work&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The first idea was the obvious one: prefix every selector in the bundled file with.tspdf-root and move on.&lt;/p&gt;

&lt;p&gt;It broke the viewer.&lt;/p&gt;

&lt;p&gt;Two reasons, and both are easy to walk into.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nesting.&lt;/strong&gt; If you run a naive prefix pass over CSS that still has nesting in it, you prefix the outer selector and leave the inner ones alone, or you catch some of them twice. What comes out the other side is a file where some rules match nothing at all. Everything looks fine until you open the viewer and half the toolbar has no styling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;:root&lt;/strong&gt;, &lt;strong&gt;html and body&lt;/strong&gt;. You cannot prefix these. .tspdf-root :root matches nothing, ever. And :root was not decorative — it held the custom properties that the rest of the pdf.js rules read through var(). Prefix it and every one of those lookups fails, so you get a viewer with no colours and no spacing, and a stylesheet that technically contains every value it needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we actually did&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scope after nesting is flattened, not before.&lt;/strong&gt; The scoping pass now runs at build time, once the nesting is already resolved, so every selector in the output is a real flat selector and gets prefixed exactly once.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Re-target :root, html and body instead of prefixing them. *&lt;/em&gt;&lt;br&gt;
They become .tspdf-root. The custom properties then live on our root element rather than the document root, and every descendant that reads them still resolves correctly, because those descendants are &lt;br&gt;
inside .tspdf-root anyway.&lt;/p&gt;

&lt;p&gt;That was the whole fix. Not complicated. It just needed us to stop thinking of the scoping problem as a string replacement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The part I would actually recommend copying&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We added a check to npm run verify:package that fails the build in two cases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Any selector in the shipped CSS is unscoped.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Any selector is prefixed more than once.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second one matters more than it looks. A double-prefixed selector matches nothing, so it does not throw and it does not warn. It just silently stops applying, and you find out when a user tells you the toolbar looks broken. Catching it at build time turns an invisible bug into a failed CI run.&lt;/p&gt;

&lt;p&gt;If you ship CSS in an npm package, that check costs an afternoon, and it will pay for itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you ship a library with styles&lt;/strong&gt;&lt;br&gt;
Three things I would check tonight, in order:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Open your built CSS file.&lt;/strong&gt; Not your source. The file that actually goes in the tarball. Read the selectors. If any of them could plausibly appear in somebody else's application, that is a bug waiting to be filed against you by someone who will have no idea it was your fault.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check every transitive stylesheet.&lt;/strong&gt; Ours came in through a dependency we were glad to have. We inherited its CSS along with its rendering, and we never inspected the result. Anything you bundle, you ship.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Assume you win every tie. *&lt;/em&gt; Runtime injection means late in document order. Late means you take every equal-specificity conflict against your own users' code. That is not a position you want to be in by accident.&lt;/p&gt;

&lt;p&gt;None of this is difficult. It is just not on the checklist, and it should be.&lt;/p&gt;

&lt;p&gt;We hit this while working on &lt;a href="https://github.com/angelbot-ai/TeamSync-PDF-Viewer" rel="noopener noreferrer"&gt;TeamSync PDF Viewer&lt;/a&gt;, an open-source client-side PDF viewer for React. The fix shipped in v1.2.1 if you want to read the changelog entry.&lt;/p&gt;

&lt;p&gt;If you have run into the same thing from a different library, I would be curious which one. I suspect this is far more common than the number of issues filed about it.&lt;/p&gt;

</description>
      <category>css</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>npm</category>
    </item>
  </channel>
</rss>
