mirror of
https://github.com/johannesjo/super-productivity.git
synced 2026-07-17 16:37:43 +00:00
* fix(ui): prevent stored XSS in enlarge-img directive
The enlarged-image element was built by interpolating the image URL into an innerHTML string, so a crafted synced/imported note.imgUrl could break out of the src attribute and inject an event handler (stored DOM-XSS). Build the <img> with createElement and property assignment instead, so the URL is never parsed as HTML. Adds a regression spec.
Refs: GHSA-78rv-m663-4fph
* fix(plugins): authorize nodeExecution from main-process state
The Electron main process authorized Node execution from the manifest the renderer passes on each IPC call, and window.ea.pluginExecNodeScript is callable by any renderer code — so injected renderer JS could forge {permissions:['nodeExecution']} and run arbitrary Node (CWE-501).
The main process now keeps its own grantedPlugins set, populated via a dedicated PLUGIN_SET_NODE_CONSENT channel. The renderer registers a grant in _fireOnReady (before the first node call, covering every load path including zip upload) and revokes it on teardown. The executor requires set membership.
Defense in depth, not a hard boundary: the registration channel is itself renderer-reachable, so a fully-compromised renderer could register a grant itself. It blocks the disclosed PoC (forged manifest for a never-loaded plugin) and removes per-call manifest trust. A hard boundary needs a main-process consent dialog.
Refs: GHSA-78rv-m663-4fph
* fix(security): add object-src 'none' and document CSP constraints
Adds object-src 'none' (the app embeds no <object>/<embed>) and replaces the stale TODO with an accurate note on why 'unsafe-eval'/'unsafe-inline' cannot be dropped yet: the plugin runtime and the inline bootstrap script use new Function, and the packaged app loads the renderer over file:// (opaque origin), so tightening script-src to 'self' is unreliable.
Refs: GHSA-78rv-m663-4fph
427 lines
13 KiB
HTML
427 lines
13 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<title>Super Productivity</title>
|
|
|
|
<meta
|
|
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
|
name="viewport"
|
|
/>
|
|
<link
|
|
href="favicon.ico"
|
|
rel="shortcut icon"
|
|
type="image/x-icon"
|
|
/>
|
|
<link
|
|
href="assets/icons/apple-touch-icon.png"
|
|
rel="apple-touch-icon"
|
|
sizes="180x180"
|
|
/>
|
|
<link
|
|
href="assets/icons/favicon-16x16.png"
|
|
rel="icon"
|
|
sizes="16x16"
|
|
type="image/png"
|
|
/>
|
|
<link
|
|
href="assets/icons/favicon-32x32.png"
|
|
rel="icon"
|
|
sizes="32x32"
|
|
type="image/png"
|
|
/>
|
|
<link
|
|
href="assets/icons/favicon-192x192.png"
|
|
rel="icon"
|
|
sizes="192x192"
|
|
type="image/png"
|
|
/>
|
|
<link
|
|
color="#5bbad5"
|
|
href="assets/icons/safari-pinned-tab.svg"
|
|
rel="mask-icon"
|
|
/>
|
|
|
|
<meta
|
|
content="assets/icons/s/mstile-150x150.png"
|
|
name="msapplication-TileImage"
|
|
/>
|
|
<meta
|
|
content="#0b77d2"
|
|
name="msapplication-TileColor"
|
|
/>
|
|
|
|
<link
|
|
href="manifest.json"
|
|
rel="manifest"
|
|
/>
|
|
<meta
|
|
content="#0b77d2"
|
|
name="theme-color"
|
|
/>
|
|
<meta
|
|
content="yes"
|
|
name="mobile-web-app-capable"
|
|
/>
|
|
<meta
|
|
content="#2b5797"
|
|
name="msapplication-TileColor"
|
|
/>
|
|
<meta
|
|
content="assets/icons/browserconfig.xml"
|
|
name="msapplication-config"
|
|
/>
|
|
<!-- Content-Security-Policy. See GHSA-78rv-m663-4fph.
|
|
'unsafe-eval' and 'unsafe-inline' currently CANNOT be dropped:
|
|
- the plugin runtime runs plugin code via `new Function`
|
|
(src/app/plugins/plugin-runner.ts) → needs 'unsafe-eval';
|
|
- the inline browser-compat bootstrap <script> below also uses
|
|
`new Function` and is inline → needs both.
|
|
The packaged app loads the renderer over file:// (electron/main-window.ts),
|
|
where 'self' is an opaque origin, so tightening script-src to 'self' is
|
|
unreliable. A real lockdown (dropping unsafe-inline/unsafe-eval) needs:
|
|
a registered app:// scheme instead of file://, plugin execution moved into
|
|
a sandboxed iframe/worker, and the inline bootstrap externalized.
|
|
object-src 'none' is safe to set now (the app embeds no <object>/<embed>).
|
|
@see https://content-security-policy.com/ -->
|
|
<meta
|
|
content="default-src * uploaded:;
|
|
connect-src * data: blob: filesystem: uploaded:;
|
|
frame-src * data: blob:;
|
|
font-src * data:;
|
|
img-src * data: blob: filesystem: file:;
|
|
style-src * 'unsafe-inline' ;
|
|
object-src 'none';
|
|
script-src * 'self' 'unsafe-inline' 'unsafe-eval' blob:"
|
|
http-equiv="Content-Security-Policy"
|
|
/>
|
|
<base href="./" />
|
|
<style type="text/css">
|
|
html,
|
|
body {
|
|
height: 100%;
|
|
}
|
|
|
|
body {
|
|
height: 100%;
|
|
margin: 0;
|
|
padding: 0;
|
|
background: rgb(18, 18, 18);
|
|
color: #fff;
|
|
}
|
|
|
|
.loading-full-page-wrapper {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
cursor: grab;
|
|
-webkit-app-region: drag;
|
|
}
|
|
|
|
.loading-full-page-wrapper:active {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.app-loading {
|
|
position: fixed;
|
|
left: 50%;
|
|
top: 50%;
|
|
transform: translate(-50%, -50%);
|
|
}
|
|
|
|
.app-loading .logo {
|
|
position: fixed;
|
|
left: 50%;
|
|
top: 50%;
|
|
transform: translate(-50%, -50%);
|
|
width: 90px;
|
|
height: 90px;
|
|
}
|
|
|
|
.app-loading .logo path {
|
|
fill: #fff;
|
|
}
|
|
|
|
@media (prefers-color-scheme: light) {
|
|
body {
|
|
background: transparent;
|
|
background-image: none;
|
|
color: #000000;
|
|
}
|
|
|
|
.app-loading .logo path {
|
|
fill: #000;
|
|
}
|
|
}
|
|
|
|
.app-loading .spinner {
|
|
position: relative;
|
|
margin: 100px auto;
|
|
width: 120px;
|
|
height: 120px;
|
|
}
|
|
|
|
.app-loading .spinner-pulse {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
width: 140px;
|
|
height: 140px;
|
|
margin: -70px 0 0 -70px;
|
|
border-radius: 50%;
|
|
background-color: #6495ed;
|
|
opacity: 0;
|
|
animation: sp-pulse 2s ease-in-out infinite;
|
|
}
|
|
|
|
.productivity-tip {
|
|
text-align: center;
|
|
position: absolute;
|
|
left: 50%;
|
|
bottom: 70%;
|
|
width: 400px;
|
|
max-width: 80vw;
|
|
transform: translate(-50%);
|
|
padding-bottom: 10px;
|
|
}
|
|
|
|
.app-loading .productivity-tip h2#heading {
|
|
font:
|
|
500 20px/32px 'Open Sans',
|
|
'Helvetica Neue',
|
|
sans-serif;
|
|
margin: 0 0 8px !important;
|
|
color: inherit;
|
|
}
|
|
|
|
.app-loading .productivity-tip p {
|
|
font:
|
|
400 14px/20px 'Open Sans',
|
|
'Helvetica Neue',
|
|
sans-serif;
|
|
margin: 0 0 8px !important;
|
|
}
|
|
|
|
@keyframes sp-pulse {
|
|
0%,
|
|
100% {
|
|
opacity: 0;
|
|
transform: scale(0.8);
|
|
}
|
|
50% {
|
|
opacity: 0.15;
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="mat-typography">
|
|
<app-root>
|
|
<div class="loading-full-page-wrapper">
|
|
<div class="app-loading">
|
|
<div class="productivity-tip">
|
|
<h2 id="heading">Heading</h2>
|
|
<p id="text">Text</p>
|
|
</div>
|
|
|
|
<div class="spinner">
|
|
<div class="spinner-pulse"></div>
|
|
</div>
|
|
|
|
<svg
|
|
class="logo"
|
|
viewBox="0 0 64 64"
|
|
>
|
|
<path
|
|
d="M 0.65537046,34.582787 32.452267,63.855301 63.791548,0.94356422 30.222644,47.722023 Z"
|
|
id="path3395"
|
|
style="
|
|
fill-opacity: 1;
|
|
fill-rule: evenodd;
|
|
stroke: none;
|
|
stroke-width: 1.46761858;
|
|
stroke-linecap: butt;
|
|
stroke-linejoin: bevel;
|
|
stroke-miterlimit: 13.80000019;
|
|
stroke-dasharray: none;
|
|
stroke-opacity: 1;
|
|
"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
</app-root>
|
|
<noscript>Please enable JavaScript to use this application.</noscript>
|
|
<script>
|
|
// Browser compatibility check — must use ES5 syntax only.
|
|
// The app targets ES2022+ (Safari 18+, Chrome 107+). On older browsers the
|
|
// compiled JS will fail to parse, leaving the user stuck on the splash screen.
|
|
// This detects unsupported browsers early and shows a helpful message.
|
|
(function () {
|
|
var unsupported = false;
|
|
if (typeof structuredClone !== 'function') {
|
|
unsupported = true;
|
|
}
|
|
if (!unsupported) {
|
|
try {
|
|
new Function('class C { static {} }')();
|
|
} catch (e) {
|
|
unsupported = true;
|
|
}
|
|
}
|
|
if (unsupported) {
|
|
var el = document.querySelector('.app-loading');
|
|
if (el) {
|
|
el.innerHTML =
|
|
'<div style="text-align:center;padding:32px 24px;max-width:400px">' +
|
|
'<h2 style="font:500 20px/28px sans-serif;margin:0 0 16px">Browser Update Required</h2>' +
|
|
'<p style="font:400 14px/22px sans-serif;margin:0 0 12px">' +
|
|
'Super Productivity requires a newer browser version to run.' +
|
|
'</p>' +
|
|
'<p style="font:400 14px/22px sans-serif;margin:0">' +
|
|
'Please update your operating system or browser and try again.' +
|
|
'</p>' +
|
|
'</div>';
|
|
}
|
|
}
|
|
})();
|
|
|
|
var productivityTips = [
|
|
[
|
|
'Be kind',
|
|
'The simple act of paying positive attention to people has a great deal to do with productivity. ― Tom Peters',
|
|
],
|
|
[
|
|
'Achieve something new',
|
|
'Productivity is being able to do things that you were never able to do before. ― Franz Kafka',
|
|
],
|
|
[
|
|
'Focus on output not activity',
|
|
'Stressing output is the key to improving productivity, while looking to increase activity can result in just the opposite. ― Paul Gauguin',
|
|
],
|
|
[
|
|
'There is only one solution to complex problems',
|
|
'Try and correct, try and correct, try and correct, try and correct, try and correct...',
|
|
],
|
|
[
|
|
'Two step super productivity',
|
|
'1. Physically get in your work position. 2. Do one small thing.',
|
|
],
|
|
['Prioritize', 'Do first things first.'],
|
|
[
|
|
'Procrastination',
|
|
'Procrastination is an emotion regulation problem, not a time management problem.',
|
|
],
|
|
[
|
|
"When in doubt, use the magic programmer's estimation factor",
|
|
'Programmers estimate task duration as π times the original estimate.',
|
|
],
|
|
['Be realistic', 'Almost everything will take longer than you think.'],
|
|
[
|
|
'Good is good enough',
|
|
"Don't aim for perfection. Focus on the important things.",
|
|
],
|
|
[
|
|
'Distractions',
|
|
"If something is distracting you, get rid of it. If you can't get it out of your head and you can deal with it later, write it down.",
|
|
],
|
|
['Fun things first', 'The dopamine can get you through the rest!'],
|
|
[
|
|
'Celebrate',
|
|
"After completing a task, don't rush to the next. Give yourself some time to feel good about yourself!",
|
|
],
|
|
[
|
|
'Establish routines',
|
|
'Decisions take time. So establishing routines can be very helpful.',
|
|
],
|
|
[
|
|
'Take breaks',
|
|
'They help to clear your mind and make you more productive in the long run. Go! Take a walk!',
|
|
],
|
|
['Mess creates stress', 'Remove junk from your work area!'],
|
|
['Focus on a single task', 'Multi-tasking is the worst.'],
|
|
[
|
|
'Set your goals',
|
|
'Having a goal to aim for (or even several smaller ones) increases motivation and focus. Remind yourself why you are doing something!',
|
|
],
|
|
[
|
|
'Try to get it right the first time',
|
|
'This can help you avoid dragging a task on forever.',
|
|
],
|
|
[
|
|
'We are what we repeatedly do',
|
|
'Excellence, then, is not an act, but a habit. ― Aristotle',
|
|
],
|
|
[
|
|
"You don't learn to walk by following rules",
|
|
'You learn by doing, and by falling over. ― Richard Branson',
|
|
],
|
|
['Define each task', 'A problem well-put is half-solved ― John Dewey'],
|
|
[
|
|
"Either like what you do or know why you're doing it",
|
|
'If not, consider doing something else.',
|
|
],
|
|
['Focus on what matters', 'Wisdom is learning what to overlook ― William James'],
|
|
[
|
|
'Action overcomes fear',
|
|
'Better to do it than live with the fear of it. ― Logen Ninefingers',
|
|
],
|
|
[
|
|
"It is not that I'm so smart",
|
|
'But I stay with the questions much longer. ― Albert Einstein',
|
|
],
|
|
[
|
|
'You can do it',
|
|
'No problem can withstand the assault of sustained thinking. ― Voltaire',
|
|
],
|
|
[
|
|
'I have not failed',
|
|
"I've just found 10,000 ways that won't work. ― Thomas A. Edison",
|
|
],
|
|
[
|
|
'Review and reflect',
|
|
"At the end of the day or week, review your progress and reflect on what worked and what didn't. Adjust your strategies accordingly.",
|
|
],
|
|
[
|
|
'Set realistic expectations',
|
|
"Don't overburden yourself with an unrealistic workload. Be honest about what you can accomplish in a given time frame.",
|
|
],
|
|
[
|
|
'Prioritize tasks',
|
|
"Use techniques like the Eisenhower Matrix (urgent vs. important) to prioritize your tasks. Focus on what's most important and time-sensitive.",
|
|
],
|
|
[
|
|
'Always be kind to yourself',
|
|
'Productivity is impossible without good self care (at the very least, in the long run).',
|
|
],
|
|
[
|
|
'The path to progress is all about self evaluation',
|
|
'From time to time reflect if your goals and methods serve you well.',
|
|
],
|
|
[
|
|
'Embrace progress over achievement',
|
|
"It's not about massive strides every day, but the accumulation of small, incremental progress that leads to significant achievements over time.",
|
|
],
|
|
[
|
|
'Decide',
|
|
"You don't need more time in your day. You need to decide. ― Seth Godin",
|
|
],
|
|
[
|
|
'Decide to decide what not to do',
|
|
'There is only is so much time in your day (and your life). Decide what you really want to do.',
|
|
],
|
|
];
|
|
var randomIndex = Math.floor(Math.random() * productivityTips.length);
|
|
var headingEl = document.getElementById('heading');
|
|
var textEl = document.getElementById('text');
|
|
if (headingEl && textEl) {
|
|
headingEl.innerText = productivityTips[randomIndex][0];
|
|
textEl.innerText = productivityTips[randomIndex][1];
|
|
}
|
|
|
|
(function () {})();
|
|
</script>
|
|
</body>
|
|
</html>
|