openvibers/openvibe.locker Commits
CSP: allow avatars from openvibe.media in img-src (#40)
Changes
@@@ -31,7 +31,7 @@ async function createApp(options = {}) {app.disable('x-powered-by'); app.set('trust proxy', config.trustProxy);app.use(helmet({ contentSecurityPolicy: { directives: {defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'", config.networkUrl, 'https://static.cloudflareinsights.com'], styleSrc: ["'self'", "'unsafe-inline'", config.networkUrl, 'https://fonts.googleapis.com'],-- fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'], imgSrc: ["'self'", 'data:', 'blob:', config.networkUrl],++ fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'], imgSrc: ["'self'", 'data:', 'blob:', config.networkUrl, 'https://openvibe.media'],connectSrc: ["'self'", config.networkUrl, 'https://openvibe.chat', 'https://billing.openvibe.network', 'https://openvibe.events', 'https://cloudflareinsights.com', 'https://api.pwnedpasswords.com'],workerSrc: ["'self'"], frameSrc: [config.networkUrl], frameAncestors: ["'none'"], objectSrc: ["'none'"], baseUri: ["'self'"],formAction: ["'self'", config.networkUrl], upgradeInsecureRequests: config.production ? [] : null,@@@ -27,6 +27,11 @@ test('llms.txt and llms-full.txt describe the public pages', async () => {assert.ok(!text.includes('/passwords'), 'no signed-in page is in the full text');});++test('the security policy lets an avatar from openvibe.media show in img-src', async () => {++ const csp = (await t.request('GET', '/', { user: null })).headers.get('content-security-policy') || '';++ assert.match(csp, /img-src[^;]*https:\/\/openvibe\.media(?:[ ;]|$)/, csp);++});++test('password login items use people locks and remain opaque in storage', async () => {const secret = JSON.stringify({ version: 1, site: 'https://example.test', username: 'alice', password: 'secret-password', totp: '', notes: '' });const encrypted = await locker.seal(secret, { lock: 'people', keys: [{ subject: { type: 'user', id: SUBJECT }, publicKey: t.people.get(SUBJECT).ecdhPublic }] });
@@ -31,7 +31,7 @@ async function createApp(options = {}) {
@@ -31,7 +31,7 @@ async function createApp(options = {}) {
app.disable('x-powered-by'); app.set('trust proxy', config.trustProxy);
app.disable('x-powered-by'); app.set('trust proxy', config.trustProxy);
app.use(helmet({ contentSecurityPolicy: { directives: {
app.use(helmet({ contentSecurityPolicy: { directives: {
defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'", config.networkUrl, 'https://static.cloudflareinsights.com'], styleSrc: ["'self'", "'unsafe-inline'", config.networkUrl, 'https://fonts.googleapis.com'],
defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'", config.networkUrl, 'https://static.cloudflareinsights.com'], styleSrc: ["'self'", "'unsafe-inline'", config.networkUrl, 'https://fonts.googleapis.com'],
- fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'], imgSrc: ["'self'", 'data:', 'blob:', config.networkUrl],
+ fontSrc: ["'self'", 'data:', 'https://fonts.gstatic.com'], imgSrc: ["'self'", 'data:', 'blob:', config.networkUrl, 'https://openvibe.media'],
connectSrc: ["'self'", config.networkUrl, 'https://openvibe.chat', 'https://billing.openvibe.network', 'https://openvibe.events', 'https://cloudflareinsights.com', 'https://api.pwnedpasswords.com'],
connectSrc: ["'self'", config.networkUrl, 'https://openvibe.chat', 'https://billing.openvibe.network', 'https://openvibe.events', 'https://cloudflareinsights.com', 'https://api.pwnedpasswords.com'],
workerSrc: ["'self'"], frameSrc: [config.networkUrl], frameAncestors: ["'none'"], objectSrc: ["'none'"], baseUri: ["'self'"],
workerSrc: ["'self'"], frameSrc: [config.networkUrl], frameAncestors: ["'none'"], objectSrc: ["'none'"], baseUri: ["'self'"],
formAction: ["'self'", config.networkUrl], upgradeInsecureRequests: config.production ? [] : null,
formAction: ["'self'", config.networkUrl], upgradeInsecureRequests: config.production ? [] : null,
@@ -27,6 +27,11 @@ test('llms.txt and llms-full.txt describe the public pages', async () => {
@@ -27,6 +27,11 @@ test('llms.txt and llms-full.txt describe the public pages', async () => {
assert.ok(!text.includes('/passwords'), 'no signed-in page is in the full text');
assert.ok(!text.includes('/passwords'), 'no signed-in page is in the full text');
});
});
+test('the security policy lets an avatar from openvibe.media show in img-src', async () => {
+ const csp = (await t.request('GET', '/', { user: null })).headers.get('content-security-policy') || '';
+ assert.match(csp, /img-src[^;]*https:\/\/openvibe\.media(?:[ ;]|$)/, csp);
+});
+
test('password login items use people locks and remain opaque in storage', async () => {
test('password login items use people locks and remain opaque in storage', async () => {
const secret = JSON.stringify({ version: 1, site: 'https://example.test', username: 'alice', password: 'secret-password', totp: '', notes: '' });
const secret = JSON.stringify({ version: 1, site: 'https://example.test', username: 'alice', password: 'secret-password', totp: '', notes: '' });
const encrypted = await locker.seal(secret, { lock: 'people', keys: [{ subject: { type: 'user', id: SUBJECT }, publicKey: t.people.get(SUBJECT).ecdhPublic }] });
const encrypted = await locker.seal(secret, { lock: 'people', keys: [{ subject: { type: 'user', id: SUBJECT }, publicKey: t.people.get(SUBJECT).ecdhPublic }] });