Skip to content

add frame-ancestors CSP header to export-and-sign iframe#120

Merged
ethankonk merged 2 commits intomainfrom
leeland/eng-3770-sb-2026-3-frames-based-signing-workflow-vulnerable-to-cross
Apr 28, 2026
Merged

add frame-ancestors CSP header to export-and-sign iframe#120
ethankonk merged 2 commits intomainfrom
leeland/eng-3770-sb-2026-3-frames-based-signing-workflow-vulnerable-to-cross

Conversation

@leeland-turnkey
Copy link
Copy Markdown
Contributor

@leeland-turnkey leeland-turnkey commented Apr 20, 2026

Closes ENG-3770 /
https://linear.app/turnkey/issue/ENG-3770/sb-2026-3-frames-based-signing-workflow-vulnerable-to-cross-origin

Background

The export-and-sign iframe stores an embedded key in localStorage scoped to its origin. Because localStorage is
shared across all iframes from the same origin regardless of parent, a malicious site can embed the iframe and supply a
valid encrypted bundle — the iframe has no way to distinguish a legitimate parent from an attacker.

Change

Adds a Content-Security-Policy: frame-ancestors HTTP response header to the port 8086 (export-and-sign) nginx server
block. This is enforced by the browser before the iframe loads, preventing unauthorized origins from embedding it in the
first place.

The header value is configurable via the TURNKEY_FRAME_ANCESTORS environment variable, defaulting to 'none' (blocks
all embedding) if unset. The Dockerfile CMD is updated to run envsubst at container startup, substituting only
TURNKEY_FRAME_ANCESTORS so nginx's own $variable references are left untouched.

Deployment

Set TURNKEY_FRAME_ANCESTORS in the container environment to the origin(s) allowed to embed the iframe:

# Single origin
TURNKEY_FRAME_ANCESTORS="https://app.turnkey.com"

# Multiple origins
TURNKEY_FRAME_ANCESTORS="https://app.turnkey.com https://staging.turnkey.com"

If the variable is not set, the header defaults to frame-ancestors 'none', which blocks all embedding until explicitly
configured.

Testing

  1. Build and run the container

With the env var set

docker build -t frames-test .
docker run -p 8086:8086 -e TURNKEY_FRAME_ANCESTORS="https://app.turnkey.com" frames-test

  1. Verify the header is present

curl -sI http://localhost:8086/ | grep -i content-security-policy

Should return:
content-security-policy: frame-ancestors https://app.turnkey.com

Then test the default (unset) case:
docker run -p 8086:8086 frames-test
curl -sI http://localhost:8086/ | grep -i content-security-policy

Should return:
content-security-policy: frame-ancestors 'none'

Adds a Content-Security-Policy frame-ancestors header to the port 8086
(export-and-sign) nginx server block, configurable via the
TURNKEY_FRAME_ANCESTORS environment variable. This prevents unauthorized
origins from embedding the iframe and exploiting the shared embedded key
stored in localStorage.

The Dockerfile CMD is updated to run envsubst at startup, substituting
only TURNKEY_FRAME_ANCESTORS so nginx's own $variable references are
left untouched.
@ethankonk ethankonk merged commit 02b741e into main Apr 28, 2026
26 checks passed
@ethankonk ethankonk deleted the leeland/eng-3770-sb-2026-3-frames-based-signing-workflow-vulnerable-to-cross branch April 28, 2026 20:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants