S3 Presign

unlisted

by hczhang

Show images and open links stored in a private S3-compatible bucket by replacing them with presigned URLs when notes are rendered.

Updated 2d agoMIT
View on GitHub

S3 Presign

English | 简体中文

Show images and open links stored in a private S3-compatible bucket (MinIO, Amazon S3, Cloudflare R2 and others) right inside your notes. When a note is rendered, links and embeds that point at endpoint/bucket/<key> are swapped for presigned URLs. The bucket stays private, and your notes keep plain HTTPS URLs that any S3 client can use.

The same note without and with S3 Presign: images from a private bucket fail with 403 until they are signed

Features

  • Works in Reading view, Live Preview embeds, hover previews, Canvas and pop-out windows.
  • Signs images, video, audio and links.
  • Keeps the secret key in Obsidian's keychain on each device, never in the vault.
  • Reuses each presigned URL until shortly before it expires, so the browser cache keeps working.
  • Hides images until they are signed, so the broken-image icon never flashes.
  • Has no dependencies: SigV4 presigning uses the Web Crypto API, so it runs on desktop and mobile.

Usage

Write object URLs in path style, the same way you would for any S3 client:

![Architecture](https://s3.example.com/my-bucket/diagrams/architecture.png)
[Quarterly report](https://s3.example.com/my-bucket/reports/2026-q3.pdf)

Everything that starts with the configured endpoint/bucket/ is signed when it is rendered. Other URLs are left alone.

Markdown source with plain object URLs next to Reading view with the images loaded

Links written as text in Live Preview are not rendered as link elements, so clicking them opens the unsigned URL. Put the cursor on the link and run S3 Presign: Open presigned URL for the link at the current position, or click the link in Reading view.

Tools outside Obsidian, such as scripts or AI agents, can fetch the same URL with a SigV4 request:

curl -f --aws-sigv4 "aws:amz:us-east-1:s3" --user "$ACCESS_KEY:$SECRET_KEY" \
  -o architecture.png "https://s3.example.com/my-bucket/diagrams/architecture.png"

Installation

Requires Obsidian 1.13.2 or later.

  • Community plugins: once the plugin is listed, search for "S3 Presign" in Settings → Community plugins.
  • BRAT: add best/obsidian-s3-presign in the BRAT plugin.
  • Manual: download main.js, manifest.json and styles.css from the latest release, put them in <vault>/<configuration folder>/plugins/s-three-presign/, then enable the plugin in Settings → Community plugins.

Settings

SettingDescription
EndpointS3-compatible endpoint in path style, without the bucket in the host name, e.g. https://s3.example.com.
BucketThe bucket whose URLs should be signed.
RegionSigning region. MinIO uses us-east-1 unless configured otherwise.
Access keyStored in the plugin's data file in your vault configuration folder.
Secret keyPick or create a secret in Obsidian's keychain (Settings → Keychain). Secrets stay on the device and are not synced with the vault, so set it once on each device.
URL lifetime in hoursHow long each presigned URL stays valid, from 0.25 to 168 (7 days). Default 24.
Test connectionSends one signed request for an object that does not exist. A 404 means the credentials work; a 403 means the keys, region or bucket are wrong.

S3 Presign settings after a successful connection test

Use a key that can only read this bucket. For MinIO or AWS, a policy like this is enough:

{
	"Version": "2012-10-17",
	"Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": ["arn:aws:s3:::my-bucket/*"] }]
}

Network use and privacy

  • The plugin only contacts the S3 endpoint you configure. Images and links load from it through presigned URLs, and Test connection sends one signed GET request to it.
  • It uses no other remote service, collects no telemetry, and needs no account or payment.
  • Signing happens locally. The secret key never leaves the device.
  • A presigned URL works for anyone who has it until it expires. If you copy an image address out of Obsidian, the copy carries a valid signature for the configured lifetime.

How it works

  • src/sigv4.ts implements SigV4 query-string presigning (only the host header is signed, payload UNSIGNED-PAYLOAD) with the Web Crypto API. It is checked against the presigning example in the AWS documentation.
  • src/main.ts registers a Markdown post-processor for Reading view and a MutationObserver per window for Live Preview, hover previews and anything rendered later. Matching elements get their src or href replaced with the presigned URL.

Limitations

  • Only path-style URLs (endpoint/bucket/key) are recognized. Virtual-hosted-style URLs (bucket.endpoint/key) are not.
  • One endpoint and bucket per vault.
  • Presigned URLs depend on the device clock being roughly correct.
  • Text links in Live Preview need the command described above.

Compatibility with other plugins

  • Upload plugins: S3 Presign only displays files. Pair it with an uploader that can write to a custom endpoint in path style and insert plain object URLs, then keep the bucket private.
  • Plugins that download remote images into the vault read the URL from the note text, not from the rendered page, so they get 403 for bucket URLs. Add your endpoint to their exclusion list if they have one.
  • Copying an image through plugins that redraw it on a canvas needs the bucket to allow cross-origin requests (CORS) from Obsidian. Viewing works without CORS.
  • Images that are not <img>, <video>, <audio> or link elements are not signed, for example banners set as CSS backgrounds.

Development

npm install
npm run lint    # eslint-plugin-obsidianmd recommended rules
npm test        # offline tests: AWS documented example, key encoding, URL parsing
npm run build   # type check and bundle main.js
npm run dev     # rebuild on change

Set these variables to also run the live tests against a real bucket. They check that an existing object downloads with the expected SHA-256, that a missing key with spaces, CJK and reserved characters returns 404 (the signature was accepted), and that a wrong secret or an unsigned URL returns 403.

S3_TEST_ENDPOINT=https://s3.example.com S3_TEST_BUCKET=my-bucket \
S3_TEST_ACCESS_KEY=... S3_TEST_SECRET_KEY=... \
S3_TEST_KEY=path/to/existing-object.png S3_TEST_SHA256=<sha256 of that object> npm test

Contributing

Bug reports and pull requests are welcome. See CONTRIBUTING.md.

License

MIT

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.