The GrapheneOS PDF viewer asks for no permissions and locks the WebView down
Simple Android PDF viewer based on pdf.js and content providers. The app doesn't require any permissions. The PDF stream is fed into the sandboxed WebView without giving it access to content or files. CSP is used to enforce that the JavaScript and styling properties within the WebView are entirely static.
At a glance
- What is it?
- GrapheneOS PdfViewer is a small Android PDF viewer built on pdf.js and content providers, and its design is entirely about what the app is not allowed to touch. It requests no permissions, feeds the PDF stream into a sandboxed WebView without giving it access to the network, files, content providers or any other data, and uses a Content Security Policy to keep the JavaScript and styling static, with the PDF rendering itself memory safe and dynamic code evaluation disabled.
- Who is it for?
- PdfViewer is the right choice if you open untrusted PDFs on Android and care about what the document can reach, since the permission-free design, the sandboxed WebView and the strict Content Security Policy are a coherent set of choices rather than a checklist, and the PDF parsing path is memory safe with dynamic code evaluation disabled.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly Kotlin, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
An app whose most important property is what it cannot do
PdfViewer is described simply, a PDF viewer for Android based on pdf.js and content providers, and the README spends most of its words on what it does not have access to. The app does not require any permissions, and the PDF stream is fed into a sandboxed WebView without giving it access to the network, files, content providers or any other data. That is a rare and deliberate shape for an Android application, since a PDF viewer that reads from a content provider normally asks for storage or media permissions and gets a URI grant instead, and one that renders in a WebView normally has a network connection and a file system. The audience is whoever handles documents they did not create, and the design assumption is that a PDF is a hostile input, which is true of most PDFs that arrive by email or messaging. The project is part of GrapheneOS, and the repository is MIT licensed with a separate PDFJS_LICENSE file for the pdf.js code.
The WebView receives bytes, not a file system
The mechanism is the interesting part. The PDF is not opened as a file that the WebView can navigate to, and it is not rendered by the WebView's own PDF plugin, which is where most mobile PDF viewers leak. Instead the stream is fed into the WebView, so the renderer receives the bytes of the document and nothing else. There is no path for the document to cause a request to a server, because the WebView has no network access, and no path for it to read another file, because it has no file access. The README describes the reuse of the hardened Chromium rendering stack while only exposing a tiny subset of the attack surface compared to actual web content. That is the whole argument of the project in one sentence, that the web platform is a heavily reviewed, heavily sandboxed renderer, and the trick is to use it for what it is good at while removing everything it could be abused for. The PDF rendering code itself is stated to be memory safe with dynamic code evaluation disabled, so the parsing layer, which is where a PDF viewer traditionally breaks, is not the weak point.
The Content Security Policy is the enforcement, not boilerplate
A WebView with no network access is still a WebView that will run any script you put in it, and a PDF is a container for a lot of things. The second lock is a Content Security Policy, used to enforce that the JavaScript and styling properties within the WebView are entirely static content from the APK assets, and to block custom fonts, since pdf.js handles rendering those itself. The custom font blocking is a good detail to notice, because it shows the policy was written by someone who knows what a font file can do. The phrase entirely static content from the APK assets is the whole model, because the only code that ever runs in the renderer is the code that shipped inside the application package, and the document contributes data rather than behaviour. The README adds the consequence, that even if an attacker did gain code execution by exploiting the underlying web rendering engine, they would be within the Chromium renderer sandbox with less access than they would have within the browser, which is a defensible argument precisely because it does not claim the exploit will not happen.
Two test suites, one for the app and one for the viewer code
The repository runs tests at two levels, and the split is a good indicator of what the project treats as risky. The Android instrumentation tests need a connected device or a running emulator:
./gradlew connectedAndroidTestand a single test can be selected with:
./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.class=app.grapheneos.pdfviewer.test.PdfViewerLaunchTestThe JavaScript unit tests cover the viewer code, and they need Node.js 24 or newer, with the modules installed first:
npm install
npm testThe runner is vitest, with a test:watch script for the loop. The build files show the rest of the toolchain, with the Gradle build in build.gradle.kts and settings.gradle.kts, an eslint config, and a vitest.config.js at the root, and the package.json is small, with esbuild, pdfjs-dist at 6.2.108, vitest, and the eslint packages as development dependencies and nothing at all in the runtime dependencies. That is consistent with a project where the JavaScript is bundled into the APK rather than shipped as a node package.
How the static assets are produced
The repository layout explains the pipeline. The app/ directory is the Android application, the viewer/ directory holds the web side, and process_static.js at the root is the script that turns the viewer sources into the static assets the Content Security Policy allows. The policy names APK assets as the only permitted source of scripts and styles, and that naming is the output of this build step, so the script and the policy are two ends of the same constraint. The Gradle wrapper and gradlew.bat are present, and gradle.properties and settings.gradle.kts configure the build, while package-lock.json and the package.json at the root drive the JavaScript side. The releases are numbered rather than semver tagged, with version 33 published on 2026-07-30, 32 on 2026-07-08 and 31 on 2025-08-31, and the last push was on 2026-10-15, so the project is still being worked on even though the release numbering looks like an internal counter.
Where a PDF viewer like this is the wrong choice
The limits are the mirror image of the strengths. This is a viewer, and the README describes nothing about annotation, editing, form filling beyond what pdf.js provides, or exporting content, so a workflow that needs to mark up or extract text into another document is not served by it. The second limit is the substrate. The security argument depends on the Chromium renderer being a hardened, sandboxed component, which means the app is only as strong as the WebView implementation on the device, and it means the process carries a browser engine, which is a considerable amount of memory for what is, in the end, a document display. The third limit is scope. An app that requires no permissions and exposes no content provider access is also an app that cannot, for instance, open a document from an arbitrary provider that expects a grant, and the content provider integration is therefore part of the package's behaviour rather than a general capability. The honest summary is that this is a deliberately narrow viewer for a specific threat model, and outside that model it is going to be less capable than the general-purpose app on your phone.
Editorial conclusion
PdfViewer is the right choice if you open untrusted PDFs on Android and care about what the document can reach, since the permission-free design, the sandboxed WebView and the strict Content Security Policy are a coherent set of choices rather than a checklist, and the PDF parsing path is memory safe with dynamic code evaluation disabled. It is the wrong tool if you need annotation, editing, text extraction into a document, or anything that writes back to the file, and it is the wrong tool if you cannot accept a Chromium WebView in the process, because the whole security argument rests on the WebView sandbox. Before adopting it, read the permission list in the manifest yourself rather than taking the README's word, run the JavaScript unit tests on Node 24 or newer, and check that the content provider integration matches how your organisation stores documents.
Frequently asked questions
Does the GrapheneOS PdfViewer need any Android permissions?
No. The README states that the app does not require any permissions, and the PDF stream is fed into the sandboxed WebView without giving it access to the network, files, content providers or any other data.
How does the app keep the WebView from running untrusted code?
A Content Security Policy enforces that the JavaScript and styling within the WebView are entirely static content from the APK assets, and blocks custom fonts since pdf.js handles those itself. The PDF rendering code is stated to be memory safe with dynamic code evaluation disabled.
How do I run the tests for the PdfViewer app?
The Android instrumentation tests need a connected device or an emulator and run with ./gradlew connectedAndroidTest, and a single class can be selected with the android.testInstrumentationRunnerArguments.class property. The JavaScript unit tests need Node.js 24 or newer, an npm install, and then npm test, which runs vitest.
Which rendering engine does the GrapheneOS PdfViewer use?
pdf.js inside a WebView. The project describes itself as reusing the hardened Chromium rendering stack while exposing a tiny subset of the attack surface compared to actual web content, with pdfjs-dist listed as a development dependency in the package.json.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/grapheneos-pdfviewer)Was this useful?