Skip to content

Commit cf9770d

Browse files
committed
feat: Production readiness & security hardening
1 parent 75d828c commit cf9770d

21 files changed

Lines changed: 882 additions & 572 deletions

‎.github/workflows/ci.yml‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@ on:
66
branches: [main]
77
jobs:
88
ci:
9-
runs-on: ubuntu-latest
9+
strategy:
10+
matrix:
11+
os: [macos-latest, ubuntu-latest, windows-latest]
12+
runs-on: ${{ matrix.os }}
1013
steps:
1114
- uses: actions/checkout@v4
1215
- uses: actions/setup-node@v4
1316
with:
1417
node-version: '22'
15-
- run: npm install
18+
- run: npm ci
1619
- run: npm run typecheck
1720
- run: npm run lint
1821
- run: npm run format:check

‎.github/workflows/release.yml‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
name: Release
22
on:
3-
push:
4-
branches: [main]
3+
workflow_dispatch:
54
permissions:
65
contents: write
76
jobs:
@@ -29,7 +28,7 @@ jobs:
2928
- uses: actions/setup-node@v4
3029
with:
3130
node-version: '22'
32-
- run: npm install
31+
- run: npm ci
3332

3433
- name: Sync package.json version
3534
run: npm --no-git-tag-version version ${{ needs.create-tag.outputs.new_version }}
@@ -52,6 +51,8 @@ jobs:
5251
release/*.exe
5352
release/*.AppImage
5453
release/*.deb
54+
release/*.yml
55+
release/*.blockmap
5556
5657
update-homebrew:
5758
needs: [create-tag, build-and-release]

‎CONTRIBUTING.md‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Contributing to PaperCache
2+
3+
First of all, thank you for considering contributing to PaperCache!
4+
5+
## Development Setup
6+
7+
1. **Clone the repository:**
8+
```bash
9+
git clone https://github.com/VariableThe/PaperCache.git
10+
cd PaperCache
11+
```
12+
13+
2. **Install dependencies:**
14+
We strictly use `npm ci` to ensure reproducible builds.
15+
```bash
16+
npm ci
17+
```
18+
19+
3. **Start the development server:**
20+
```bash
21+
npm run dev
22+
```
23+
24+
## Development Guidelines
25+
- **Pull Requests Required**: Never push new features directly to the `main` branch. Always create a new branch and push your changes as a Pull Request (PR) for review.
26+
- **Pre-PR Checks**: Run `npm run lint` and `npm run test` before opening any PR — don't open a PR with failing checks.
27+
- **Performance Reporting**: Performance changes require a before/after bundle size comparison in the PR description (just paste the Vite build output).
28+
29+
Thank you for your contributions!

‎SECURITY.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Security Policy
2+
3+
## Supported Versions
4+
5+
Currently, only the latest version of PaperCache receives security updates. Please ensure you are running the most recent version available.
6+
7+
## Reporting a Vulnerability
8+
9+
We take the security of PaperCache seriously. If you discover a security vulnerability, we would appreciate it if you could report it privately so it can be addressed before being disclosed publicly.
10+
11+
Please report any security issues to: **adityasharma.variable@gmail.com**
12+
13+
When reporting, please include:
14+
- A description of the vulnerability.
15+
- Steps to reproduce the issue.
16+
- Any potential impact you have identified.
17+
18+
You should receive an acknowledgment of your report within 48 hours, along with an estimated timeline for a fix. Thank you for helping keep PaperCache secure!

‎electron/main.ts‎

Lines changed: 96 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import {
1212
dialog,
1313
safeStorage,
1414
powerMonitor,
15+
session,
1516
} from 'electron'
17+
import { autoUpdater } from 'electron-updater'
1618
import path from 'node:path'
1719
import { fileURLToPath } from 'node:url'
1820
import fs from 'node:fs'
@@ -35,8 +37,15 @@ if (!fs.existsSync(COMMANDS_DIR)) {
3537
fs.mkdirSync(COMMANDS_DIR)
3638
}
3739

38-
fs.writeFileSync(
39-
path.join(COMMANDS_DIR, 'basics.md'),
40+
function writeCommandFile(name: string, content: string) {
41+
const filePath = path.join(COMMANDS_DIR, name)
42+
if (!fs.existsSync(filePath)) {
43+
fs.writeFileSync(filePath, content)
44+
}
45+
}
46+
47+
writeCommandFile(
48+
'basics.md',
4049
`# Basics
4150
4251
- **Zoom**: \`Cmd + +\` to zoom in, \`Cmd + -\` to zoom out, \`Cmd + 0\` to reset.
@@ -56,11 +65,11 @@ fs.writeFileSync(
5665
*Example use:* Press \`Cmd+K\` right now, select "Settings", and set your global hotkey!
5766
5867
Next: [Folders](/file commands/folders.md)
59-
`,
68+
`
6069
)
6170

62-
fs.writeFileSync(
63-
path.join(COMMANDS_DIR, 'folders.md'),
71+
writeCommandFile(
72+
'folders.md',
6473
`# Folders
6574
6675
Organize your notes by using a \`/\` in the note title.
@@ -70,11 +79,11 @@ Folders automatically receive a unique color identifier in the Graph View and Se
7079
If you rename this note (click the title at the top left) to \`projects/PaperCache.md\`, it will automatically be placed inside a \`projects\` folder!
7180
7281
Next: [Variables](/file commands/variables.md)
73-
`,
82+
`
7483
)
7584

76-
fs.writeFileSync(
77-
path.join(COMMANDS_DIR, 'variables.md'),
85+
writeCommandFile(
86+
'variables.md',
7887
`# Variables & Math
7988
8089
PaperCache is a smart scratchpad. You can define variables and write math equations that auto-calculate.
@@ -92,11 +101,11 @@ x * 3 = \u200B30
92101
API_KEY
93102
94103
Next: [Markdown & Code](/file commands/markdown.md)
95-
`,
104+
`
96105
)
97106

98-
fs.writeFileSync(
99-
path.join(COMMANDS_DIR, 'markdown.md'),
107+
writeCommandFile(
108+
'markdown.md',
100109
`# Markdown & Code
101110
102111
PaperCache supports full markdown with seamless inline editing.
@@ -127,11 +136,11 @@ Type \`/ai <prompt>\` and press enter to summon an AI assistant directly into yo
127136
\`/ai Write a python function to reverse a string\`
128137
129138
Next: [Formats & Colors](/file commands/formats.md)
130-
`,
139+
`
131140
)
132141

133-
fs.writeFileSync(
134-
path.join(COMMANDS_DIR, 'formats.md'),
142+
writeCommandFile(
143+
'formats.md',
135144
`# Formats & Colors
136145
137146
PaperCache automatically recognizes and highlights common formats so you can easily spot them in your notes.
@@ -146,11 +155,11 @@ Dates and times are also highlighted to help you keep track of your schedule.
146155
Meeting on 31-05-2024 at 14:30.
147156
148157
Next: [Tags](/file commands/tags.md)
149-
`,
158+
`
150159
)
151160

152-
fs.writeFileSync(
153-
path.join(COMMANDS_DIR, 'tags.md'),
161+
writeCommandFile(
162+
'tags.md',
154163
`# Tags
155164
156165
You can tag your notes anywhere by typing an exclamation mark followed by a word (e.g., !important or !work).
@@ -163,11 +172,11 @@ When you open the search menu (\`Cmd+P\`), you'll see all your unique tags at th
163172
Next: [Tasks](/file commands/tasks.md)
164173
165174
[Back to Welcome](/file Welcome.md)
166-
`,
175+
`
167176
)
168177

169-
fs.writeFileSync(
170-
path.join(COMMANDS_DIR, 'tasks.md'),
178+
writeCommandFile(
179+
'tasks.md',
171180
`# Tasks & Reminders
172181
173182
Stay on top of your work by using tasks!
@@ -184,17 +193,17 @@ Overdue tasks will automatically highlight in red.
184193
Next: [Ready](/file commands/ready.md)
185194
186195
[Back to Welcome](/file Welcome.md)
187-
`,
196+
`
188197
)
189198

190-
fs.writeFileSync(
191-
path.join(COMMANDS_DIR, 'ready.md'),
199+
writeCommandFile(
200+
'ready.md',
192201
`# Ready to get started?
193202
194203
You're all set to use PaperCache! Start jotting down your thoughts, creating folders, and exploring the capabilities.
195204
196205
[Back to Welcome](/file Welcome.md)
197-
`,
206+
`
198207
)
199208

200209
const welcomePath = path.join(NOTES_DIR, 'Welcome.md')
@@ -346,6 +355,24 @@ app.on('web-contents-created', (event, contents) => {
346355
})
347356

348357
app.whenReady().then(() => {
358+
// Content Security Policy
359+
const isDev = !!process.env.VITE_DEV_SERVER_URL;
360+
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
361+
callback({
362+
responseHeaders: {
363+
...details.responseHeaders,
364+
// unsafe-eval is required for mathjs dynamic compilation
365+
'Content-Security-Policy': [
366+
isDev
367+
? "default-src 'none'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https: wss:; font-src 'self' data: https:; object-src 'none'; base-uri 'none';"
368+
: "default-src 'none'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https:; font-src 'self' data: https:; object-src 'none'; base-uri 'none';"
369+
]
370+
}
371+
})
372+
})
373+
374+
autoUpdater.checkForUpdatesAndNotify()
375+
349376
createWindow()
350377

351378
powerMonitor.on('suspend', () => {
@@ -379,6 +406,7 @@ app.whenReady().then(() => {
379406

380407
const contextMenu = Menu.buildFromTemplate([
381408
{ label: 'Show/Hide PaperCache', click: toggleWindow },
409+
{ label: 'Check for Updates', click: () => autoUpdater.checkForUpdatesAndNotify() },
382410
{ type: 'separator' },
383411
{
384412
label: 'Quit',
@@ -615,11 +643,53 @@ ipcMain.on('open-settings', () => {
615643
})
616644

617645

618-
ipcMain.handle('openai-chat', async (_, { model, messages, apiKey, baseURL }) => {
646+
let memoryApiKey = ''
647+
try {
648+
const file = fs.readFileSync(path.join(NOTES_DIR, 'config.enc'), 'utf-8')
649+
if (safeStorage.isEncryptionAvailable()) {
650+
memoryApiKey = safeStorage.decryptString(Buffer.from(file, 'base64'))
651+
} else {
652+
memoryApiKey = file
653+
}
654+
} catch (e) {}
655+
656+
ipcMain.handle('set-api-key', (_, key: string) => {
657+
memoryApiKey = key;
658+
try {
659+
const dataToSave = safeStorage.isEncryptionAvailable()
660+
? safeStorage.encryptString(key).toString('base64')
661+
: key;
662+
fs.writeFileSync(path.join(NOTES_DIR, 'config.enc'), dataToSave);
663+
return true;
664+
} catch (e) {
665+
return false;
666+
}
667+
})
668+
669+
ipcMain.handle('get-api-key-status', () => {
670+
return !!memoryApiKey && memoryApiKey.length > 0;
671+
})
672+
673+
ipcMain.on('check-for-updates', () => {
674+
autoUpdater.checkForUpdatesAndNotify()
675+
})
676+
677+
ipcMain.handle('openai-chat', async (_, { model, messages, baseURL }) => {
678+
// Input Validation
679+
if (typeof model !== 'string' || model.trim() === '') {
680+
throw new Error('Invalid model provided')
681+
}
682+
if (!Array.isArray(messages)) {
683+
throw new Error('Messages must be an array')
684+
}
685+
if (baseURL && typeof baseURL !== 'string') {
686+
throw new Error('Invalid baseURL provided')
687+
}
688+
619689
try {
620690
const OpenAI = (await import('openai')).default
621691
const openai = new OpenAI({
622-
apiKey: apiKey || 'dummy',
692+
apiKey: memoryApiKey || 'dummy',
623693
baseURL: baseURL || undefined,
624694
})
625695
const completion = await openai.chat.completions.create({

‎electron/preload.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ contextBridge.exposeInMainWorld('electronAPI', {
66
saveNote: (id: string, content: string) => ipcRenderer.invoke('save-note', { id, content }),
77
deleteNote: (id: string) => ipcRenderer.invoke('delete-note', id),
88
renameNote: (oldId: string, newId: string) => ipcRenderer.invoke('rename-note', { oldId, newId }),
9-
openAIChat: (args: { model: string, messages: { role: string; content: string }[], apiKey: string, baseURL: string }) => ipcRenderer.invoke('openai-chat', args),
9+
openAIChat: (args: { model: string, messages: { role: string; content: string }[], baseURL: string }) => ipcRenderer.invoke('openai-chat', args),
10+
setApiKey: (key: string) => ipcRenderer.invoke('set-api-key', key),
11+
getApiKeyStatus: () => ipcRenderer.invoke('get-api-key-status'),
12+
checkForUpdates: () => ipcRenderer.send('check-for-updates'),
1013
readNote: (id: string) => ipcRenderer.invoke('read-note', id),
1114
exportNote: (filename: string, content: string) =>
1215
ipcRenderer.invoke('export-note', filename, content),

0 commit comments

Comments
 (0)