diff --git a/README.md b/README.md index 05a2bab..552bf6e 100644 --- a/README.md +++ b/README.md @@ -369,6 +369,7 @@ pm-ops-map/ ├── render.yaml One-click Render deploy for the sync server ├── .github/workflows/ci.yml CI: install, test, audit, build (app + sync server as separate jobs) ├── CONTRIBUTING.md +├── SECURITY.md ├── LICENSE.txt └── webpack.config.*.js Optional build configs (not needed to run) ``` @@ -474,6 +475,8 @@ If you're evaluating this for real use today: the core ownership/maintenance/por See [CONTRIBUTING.md](CONTRIBUTING.md). PRs welcome — especially department templates, mobile UX improvements, and visualization options. +Found a security issue? See [SECURITY.md](SECURITY.md) — please don't open a public issue for it. + --- ## License diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..1936301 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,36 @@ +# Security Policy + +## Reporting a vulnerability + +Please use GitHub's [private vulnerability reporting](security/advisories/new) +for this repository rather than opening a public issue. If that's not +available, open an issue that describes the problem in general terms without +a working exploit, and a maintainer will follow up to get details privately. + +There's no bug bounty — this is a free, open-source hobby project — but +reports are genuinely appreciated and will be credited in the fix unless you +ask otherwise. + +## Security model + +**Main app.** PM Ops Map is a zero-backend, client-only app: it runs entirely +in your browser and stores its data in `localStorage` on your device. There is +no server, no account system, and no PM Ops Map–operated service that sees +your data. The main risks are the ones inherent to any browser-storage app: +anyone with access to the browser profile can read the stored data, and it is +only as safe as the device it runs on. + +**Optional sync server** (`server/`). Teams that opt into Team Sync run their +own self-hosted instance to share one workspace across devices. Its security +model is intentionally lightweight — workspace passphrases are hashed with +`scrypt` server-side, but the client keeps its own copy in `localStorage` +unencrypted, there's no per-device access revocation, and it's designed for a +small team that already trusts each other rather than for adversarial +multi-tenant use. Full details are in +[`server/README.md`](server/README.md#security-model-honestly). If you deploy +it, always run it behind HTTPS. + +## Supported versions + +This project doesn't yet follow a formal versioning/release cycle — the +`master` branch is the supported version. Fixes land there. diff --git a/index.html b/index.html index 26b62ac..4d563bf 100644 --- a/index.html +++ b/index.html @@ -6,6 +6,17 @@ PM Ops Map + + + + + + + + + + + diff --git a/og-image.png b/og-image.png new file mode 100644 index 0000000..1c6df2a Binary files /dev/null and b/og-image.png differ diff --git a/webpack.config.prod.js b/webpack.config.prod.js index 01a8961..8ef2603 100644 --- a/webpack.config.prod.js +++ b/webpack.config.prod.js @@ -21,8 +21,16 @@ class InlineConfigForFileProtocolPlugin { 'InlineConfigForFileProtocolPlugin', (data, cb) => { const configJson = fs.readFileSync(path.resolve(__dirname, 'config.json'), 'utf8'); + const originalTag = ''; + if (!data.html.includes(originalTag)) { + return cb(new Error( + `InlineConfigForFileProtocolPlugin: expected to find ${originalTag} in the rendered ` + + 'HTML but did not — index.html\'s script tag changed, so config.json would not be inlined ' + + 'and the built app would silently fail to load config over file://.' + )); + } data.html = data.html.replace( - '', + originalTag, `\n` ); cb(null, data); @@ -48,6 +56,7 @@ module.exports = merge(common, { { from: 'favicon.ico', to: 'favicon.ico' }, { from: 'robots.txt', to: 'robots.txt' }, { from: 'icon.png', to: 'icon.png' }, + { from: 'og-image.png', to: 'og-image.png' }, { from: '404.html', to: '404.html' }, { from: 'site.webmanifest', to: 'site.webmanifest' }, ],