mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-15 02:33:15 -06:00
security(widgets): add sandbox="allow-scripts" to widget iframes
Addresses the primary finding from the May 27 security report (issue #8): the admin widget preview modal (frontend/js/views/widgets.js) and the web player widget renderer (server/player/index.html, 2 sites) loaded user-authored widget HTML into unsandboxed iframes. Same-origin scripts in the widget content could access window.parent.localStorage and exfiltrate the JWT. sandbox="allow-scripts" without allow-same-origin sandboxes the widget into a unique origin: inline scripts (clock, RSS, weather widgets) continue to work, but parent-origin access and same-origin requests are blocked. Verified via Playwright probe against all 6 widget types in the dev DB (clock, rss, social, text, weather, webpage): each renders correctly under the new sandbox and contentDocument access from the parent is blocked (opaque-origin enforcement working). Admin preview unchanged in appearance; player display unchanged. Webpage widget (server/routes/widgets.js) sandbox tightening (drop allow-same-origin) is a separate forthcoming commit - needs test against real embed URLs since some sites rely on same-origin behavior. The sandbox-attribute intersection rule means today's outer-iframe sandbox will cascade and strip allow-same-origin from the webpage widget's inner iframe too; accepted as a narrow cosmetic regression (cookies/localStorage stripped for embedded sites) until the deliberate inner-iframe handling ships. SECURITY.md added with reporting process (GitHub Security Advisories primary, support@bytetinker.net fallback) and scope. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
159a36ed99
commit
fe36c8c4b9
97
SECURITY.md
Normal file
97
SECURITY.md
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# Security Policy
|
||||
|
||||
Thanks for taking the time to look at ScreenTinker's security. The project
|
||||
is a one-person open-source effort, so response times reflect that — but
|
||||
reports are taken seriously and handled in good faith.
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
**Primary channel — GitHub Security Advisories (preferred):**
|
||||
|
||||
[github.com/screentinker/screentinker/security/advisories/new](https://github.com/screentinker/screentinker/security/advisories/new)
|
||||
|
||||
GitHub's private advisory flow keeps the report off public issues, lets us
|
||||
draft a fix collaboratively, and produces a CVE if appropriate. Use this
|
||||
unless you have a reason not to.
|
||||
|
||||
**Fallback — email:**
|
||||
|
||||
`support@bytetinker.net` (the maintainer's consultancy inbox; the domain
|
||||
intentionally differs from `screentinker.com` — it's the actively-monitored
|
||||
business address rather than a project-domain alias that might not have
|
||||
working mail delivery).
|
||||
|
||||
Please include:
|
||||
- A description of the issue and its impact
|
||||
- Steps to reproduce (the more concrete, the better)
|
||||
- The commit SHA or release tag you observed it on
|
||||
- Any proof-of-concept code or payload, if you have one
|
||||
|
||||
## Response timeline
|
||||
|
||||
I aim to acknowledge reports within **3–5 business days** and update with a
|
||||
triage assessment within **10 business days**. If you haven't heard back
|
||||
in that window, please feel free to nudge — life happens, and reports
|
||||
occasionally slip past.
|
||||
|
||||
Fix timelines depend on severity, complexity, and whether the issue is on
|
||||
the hosted instance (screentinker.com) or affects self-hosted deployments
|
||||
too. Critical issues affecting the hosted instance generally get same-week
|
||||
turnaround.
|
||||
|
||||
## In scope
|
||||
|
||||
Reports about the following are welcome and treated as security issues:
|
||||
|
||||
- **Authentication / session bypass** (e.g. JWT forgery, login bypass,
|
||||
privilege escalation)
|
||||
- **Multi-tenancy boundary violations** (one workspace's data leaking into
|
||||
another, organization-level isolation breaks)
|
||||
- **XSS in widget rendering or admin UI** (e.g. unsandboxed widget content,
|
||||
unescaped user input in dashboard surfaces)
|
||||
- **CSRF** on state-changing endpoints
|
||||
- **SQL injection** (deviations from parameterized queries are reportable)
|
||||
- **Server-side request forgery** (SSRF) via widget URLs, content uploads,
|
||||
webhook handlers, or similar
|
||||
- **Insecure direct object reference** (accessing a resource by ID without
|
||||
the proper tenancy gate)
|
||||
|
||||
## Out of scope
|
||||
|
||||
The following are acknowledged but not treated as in-scope security
|
||||
issues for this project:
|
||||
|
||||
- **Denial of service via excessive resource usage** (uploading large
|
||||
files, opening many sockets, etc.) — operational concerns, not security
|
||||
vulnerabilities. Rate limits exist where it matters most.
|
||||
- **Social engineering** of the maintainer or other users
|
||||
- **Misconfigurations of self-hosted instances** (e.g. exposing the server
|
||||
to the internet without TLS, weak JWT secrets, default passwords). The
|
||||
README documents recommended configuration; deviations are the operator's
|
||||
responsibility.
|
||||
- **Vulnerabilities in third-party dependencies** (Express, better-sqlite3,
|
||||
socket.io, etc.) — please report those upstream. If a dependency CVE
|
||||
affects ScreenTinker in a non-obvious way, that's worth flagging here too.
|
||||
- **Reports generated by automated scanners** with no manual triage or
|
||||
proof-of-concept (e.g. "your /robots.txt is missing" — not what this
|
||||
project worries about)
|
||||
|
||||
## Coordinated disclosure
|
||||
|
||||
Please **wait until a fix has shipped to the hosted instance and
|
||||
origin/main before public disclosure**. I'll keep you in the loop on
|
||||
timing and confirm when it's safe to publish. For most issues that
|
||||
window is a few weeks at most; if it stretches longer, that's a signal
|
||||
something is more complex than expected and we'll coordinate.
|
||||
|
||||
If you find a critical issue that's being actively exploited (or you
|
||||
believe might be), please say so in the report — I'll prioritize
|
||||
accordingly.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
If you'd like to be credited for a report, I'm happy to acknowledge you
|
||||
by name in release notes and (when applicable) in the GitHub advisory
|
||||
itself. Let me know in your report whether you'd like credit and how
|
||||
you'd like to be named. Anonymous reports are also welcome — no credit
|
||||
is required.
|
||||
|
|
@ -114,7 +114,7 @@ function showPreviewModal(html) {
|
|||
<strong style="color:var(--text-primary)">${t('widget.preview_title')}</strong>
|
||||
<button class="btn btn-secondary btn-sm" id="pvClose">${t('widget.close')}</button>
|
||||
</div>
|
||||
<iframe id="pvIframe" style="flex:1;width:100%;border:0;background:#000"></iframe>
|
||||
<iframe id="pvIframe" sandbox="allow-scripts" style="flex:1;width:100%;border:0;background:#000"></iframe>
|
||||
</div>`;
|
||||
document.body.appendChild(overlay);
|
||||
// srcdoc resolves relative URLs against about:srcdoc, so inject <base> pointing to our origin
|
||||
|
|
|
|||
|
|
@ -1434,6 +1434,9 @@
|
|||
iframe.src = `${serverUrl}/api/widgets/${item.widget_id}/render`;
|
||||
iframe.style.cssText = 'width:100%;height:100%;border:none;background:#000';
|
||||
iframe.allow = 'autoplay; fullscreen';
|
||||
// Sandbox into a unique origin so widget scripts can't read window.parent
|
||||
// state (localStorage / JWT). allow-scripts keeps inline widget code running.
|
||||
iframe.setAttribute('sandbox', 'allow-scripts');
|
||||
mount.appendChild(iframe);
|
||||
if (!isFollower) advanceTimer = setTimeout(nextItem, (item.duration_sec || 30) * 1000);
|
||||
}
|
||||
|
|
@ -1459,6 +1462,9 @@
|
|||
if (zone.zone_type === 'widget' && assignment.widget_id) {
|
||||
const iframe = document.createElement('iframe');
|
||||
iframe.src = `${config.serverUrl}/api/widgets/${assignment.widget_id}/render`;
|
||||
// Sandbox into a unique origin so widget scripts can't read window.parent
|
||||
// state (localStorage / JWT). allow-scripts keeps inline widget code running.
|
||||
iframe.setAttribute('sandbox', 'allow-scripts');
|
||||
div.appendChild(iframe);
|
||||
} else if (isYoutubeZone) {
|
||||
createYoutubeEmbed(src, assignment, div);
|
||||
|
|
|
|||
Loading…
Reference in a new issue