Skip to content

Commit 94f94af

Browse files
require JSS 0.0.219; drop all 34 explicit plugin ids (#596 fix consumed)
0.0.219 derives a generic basename's id from its parent directory, so <name>/plugin.js now yields '<name>' with no explicit id — the exact footgun this repo's compose/serve entries and AGENT.md warned about. activitypub/test.js had the old derived id ('plugin') baked into its pluginDir path; it now expects '.plugins/activitypub', which doubles as the motivating-consumer regression test for the fix.
1 parent e165bd7 commit 94f94af

5 files changed

Lines changed: 69 additions & 75 deletions

File tree

activitypub/test.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -198,10 +198,11 @@ describe('activitypub plugin', () => {
198198

199199
// ---- SECURITY REGRESSIONS -------------------------------------------------
200200

201-
// Path to the per-actor state file. No explicit `id` is passed for the entry,
202-
// so the loader derives it from the module basename → 'plugin'; pluginDir is
203-
// <root>/.plugins/<id>/ (see plugins.js), state lives under state/<user>.json.
204-
const statePath = () => path.join(jss.root, '.plugins', 'plugin', 'state', `${USER}.json`);
201+
// Path to the per-actor state file. No explicit `id` is passed for the entry;
202+
// since JSS 0.0.219 (#596 fix) the loader derives the id from the parent dir
203+
// of a generic basename → 'activitypub'; pluginDir is <root>/.plugins/<id>/
204+
// (see plugins.js), state lives under state/<user>.json.
205+
const statePath = () => path.join(jss.root, '.plugins', 'activitypub', 'state', `${USER}.json`);
205206
const readState = () => JSON.parse(fs.readFileSync(statePath(), 'utf8'));
206207

207208
it('SSRF: a Follow with a private/loopback actor URL is refused delivery (default config)', async () => {

compose.test.js

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -64,52 +64,49 @@ describe('composition: every plugin on one server', () => {
6464
// reduce to 'plugin' — the loader's duplicate-id guard requires ids
6565
// here (finding: derive from the parent dir for generic basenames).
6666
plugins: [
67-
{ id: 'relay', module: at('relay/plugin.js'), prefix: '/relay' },
68-
{ id: 'webrtc', module: at('webrtc/plugin.js'), prefix: '/webrtc' },
69-
{ id: 'terminal', module: at('terminal/plugin.js'), prefix: '/terminal', config: { token: 'compose-secret' } },
70-
{ id: 'tunnel', module: at('tunnel/plugin.js'), prefix: '/tunnel' },
67+
{ module: at('relay/plugin.js'), prefix: '/relay' },
68+
{ module: at('webrtc/plugin.js'), prefix: '/webrtc' },
69+
{ module: at('terminal/plugin.js'), prefix: '/terminal', config: { token: 'compose-secret' } },
70+
{ module: at('tunnel/plugin.js'), prefix: '/tunnel' },
7171
{
72-
id: 'notifications',
7372
module: at('notifications/plugin.js'),
7473
prefix: '/.notifications',
7574
config: { podsRoot: root, baseUrl: base },
7675
},
77-
{ id: 'pay', module: at('pay/plugin.js'), prefix: '/paid', config: { cost: 2, address: 'x' } },
78-
{ id: 'nip05', module: at('nip05/plugin.js'), prefix: '/nip05', config: { podsRoot: root } },
79-
{ id: 'corsproxy', module: at('corsproxy/plugin.js'), prefix: '/proxy', config: {} },
80-
{ id: 'capability', module: at('capability/plugin.js'), prefix: '/cap', config: {} },
81-
{ id: 'webdav', module: at('webdav/plugin.js'), prefix: '/webdav', config: { baseUrl: base, loopbackUrl: base } },
82-
{ id: 'gitscratch', module: at('gitscratch/plugin.js'), prefix: '/git', config: {} },
83-
{ id: 'sparql', module: at('sparql/plugin.js'), prefix: '/sparql', config: { baseUrl: base, loopbackUrl: base } },
84-
{ id: 'otp', module: at('otp/plugin.js'), prefix: '/otp', config: {} },
85-
{ id: 'carddav', module: at('carddav/plugin.js'), prefix: '/carddav', config: { baseUrl: base, loopbackUrl: base } },
86-
{ id: 'mastodon', module: at('mastodon/plugin.js'), prefix: '/mastodon', config: { baseUrl: base, loopbackUrl: base } },
87-
{ id: 'bluesky', module: at('bluesky/plugin.js'), prefix: '/bluesky', config: { baseUrl: base, loopbackUrl: base } },
88-
{ id: 'caldav', module: at('caldav/plugin.js'), prefix: '/caldav', config: { baseUrl: base, loopbackUrl: base } },
89-
{ id: 'webfinger', module: at('webfinger/plugin.js'), prefix: '/webfinger', config: { podsRoot: root, baseUrl: base } },
90-
{ id: 'activitypub', module: at('activitypub/plugin.js'), prefix: '/activitypub', config: { baseUrl: base, loopbackUrl: base } },
91-
{ id: 'rss', module: at('rss/plugin.js'), prefix: '/feed', config: { baseUrl: base, loopbackUrl: base } },
92-
{ id: 'matrix', module: at('matrix/plugin.js'), prefix: '/matrix', config: { baseUrl: base, loopbackUrl: base } },
93-
{ id: 'search', module: at('search/plugin.js'), prefix: '/search', config: { baseUrl: base, loopbackUrl: base } },
94-
{ id: 'didweb', module: at('didweb/plugin.js'), prefix: '/didweb', config: { podsRoot: root, baseUrl: base } },
95-
{ id: 's3', module: at('s3/plugin.js'), prefix: '/s3', config: { baseUrl: base, loopbackUrl: base } },
96-
{ id: 'micropub', module: at('micropub/plugin.js'), prefix: '/micropub', config: { baseUrl: base, loopbackUrl: base } },
97-
{ id: 'backup', module: at('backup/plugin.js'), prefix: '/backup', config: { baseUrl: base, loopbackUrl: base } },
98-
{ id: 'shortlink', module: at('shortlink/plugin.js'), prefix: '/short', config: { baseUrl: base } },
99-
{ id: 'oembed', module: at('oembed/plugin.js'), prefix: '/oembed', config: { baseUrl: base, loopbackUrl: base } },
100-
{ id: 'jmap', module: at('jmap/plugin.js'), prefix: '/jmap', config: { baseUrl: base, loopbackUrl: base } },
76+
{ module: at('pay/plugin.js'), prefix: '/paid', config: { cost: 2, address: 'x' } },
77+
{ module: at('nip05/plugin.js'), prefix: '/nip05', config: { podsRoot: root } },
78+
{ module: at('corsproxy/plugin.js'), prefix: '/proxy', config: {} },
79+
{ module: at('capability/plugin.js'), prefix: '/cap', config: {} },
80+
{ module: at('webdav/plugin.js'), prefix: '/webdav', config: { baseUrl: base, loopbackUrl: base } },
81+
{ module: at('gitscratch/plugin.js'), prefix: '/git', config: {} },
82+
{ module: at('sparql/plugin.js'), prefix: '/sparql', config: { baseUrl: base, loopbackUrl: base } },
83+
{ module: at('otp/plugin.js'), prefix: '/otp', config: {} },
84+
{ module: at('carddav/plugin.js'), prefix: '/carddav', config: { baseUrl: base, loopbackUrl: base } },
85+
{ module: at('mastodon/plugin.js'), prefix: '/mastodon', config: { baseUrl: base, loopbackUrl: base } },
86+
{ module: at('bluesky/plugin.js'), prefix: '/bluesky', config: { baseUrl: base, loopbackUrl: base } },
87+
{ module: at('caldav/plugin.js'), prefix: '/caldav', config: { baseUrl: base, loopbackUrl: base } },
88+
{ module: at('webfinger/plugin.js'), prefix: '/webfinger', config: { podsRoot: root, baseUrl: base } },
89+
{ module: at('activitypub/plugin.js'), prefix: '/activitypub', config: { baseUrl: base, loopbackUrl: base } },
90+
{ module: at('rss/plugin.js'), prefix: '/feed', config: { baseUrl: base, loopbackUrl: base } },
91+
{ module: at('matrix/plugin.js'), prefix: '/matrix', config: { baseUrl: base, loopbackUrl: base } },
92+
{ module: at('search/plugin.js'), prefix: '/search', config: { baseUrl: base, loopbackUrl: base } },
93+
{ module: at('didweb/plugin.js'), prefix: '/didweb', config: { podsRoot: root, baseUrl: base } },
94+
{ module: at('s3/plugin.js'), prefix: '/s3', config: { baseUrl: base, loopbackUrl: base } },
95+
{ module: at('micropub/plugin.js'), prefix: '/micropub', config: { baseUrl: base, loopbackUrl: base } },
96+
{ module: at('backup/plugin.js'), prefix: '/backup', config: { baseUrl: base, loopbackUrl: base } },
97+
{ module: at('shortlink/plugin.js'), prefix: '/short', config: { baseUrl: base } },
98+
{ module: at('oembed/plugin.js'), prefix: '/oembed', config: { baseUrl: base, loopbackUrl: base } },
99+
{ module: at('jmap/plugin.js'), prefix: '/jmap', config: { baseUrl: base, loopbackUrl: base } },
101100
{
102-
id: 'remotestorage',
103101
module: at('remotestorage/plugin.js'),
104102
prefix: '/remotestorage',
105103
// webfinger/ already owns /.well-known/webfinger on this server —
106104
// the witnessed collision (see remotestorage/README.md) — so this
107105
// instance stands down and serves only its own-prefix JRD.
108106
config: { baseUrl: base, loopbackUrl: base, claimWellKnown: false },
109107
},
110-
{ id: 'metrics', module: at('metrics/plugin.js'), prefix: '/metrics', config: { loopbackUrl: base } },
108+
{ module: at('metrics/plugin.js'), prefix: '/metrics', config: { loopbackUrl: base } },
111109
{
112-
id: 'admin',
113110
module: at('admin/plugin.js'),
114111
prefix: '/admin',
115112
// Auto-discovers via api.plugins (#610), origin via api.serverInfo
@@ -124,7 +121,6 @@ describe('composition: every plugin on one server', () => {
124121
},
125122
},
126123
{
127-
id: 'dashboard',
128124
module: at('dashboard/plugin.js'),
129125
prefix: '/dashboard',
130126
// No hand-fed list and no loopbackUrl: the dashboard auto-discovers

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
"@noble/curves": "^1.4.0"
1414
},
1515
"devDependencies": {
16-
"javascript-solid-server": "^0.0.218",
16+
"javascript-solid-server": "^0.0.219",
1717
"ws": "^8.18.0"
1818
}
1919
}

serve.js

Lines changed: 30 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -71,46 +71,44 @@ const fastify = createServer({
7171
appPaths: ['/api', '/oauth', '/xrpc', '/ap', '/_matrix'],
7272
// Explicit ids — the <name>/plugin.js convention collides on basename.
7373
plugins: [
74-
{ id: 'relay', module: at('relay/plugin.js'), prefix: '/relay' },
75-
{ id: 'webrtc', module: at('webrtc/plugin.js'), prefix: '/webrtc' },
76-
{ id: 'terminal', module: at('terminal/plugin.js'), prefix: '/terminal', config: { token: terminalToken } },
77-
{ id: 'tunnel', module: at('tunnel/plugin.js'), prefix: '/tunnel' },
74+
{ module: at('relay/plugin.js'), prefix: '/relay' },
75+
{ module: at('webrtc/plugin.js'), prefix: '/webrtc' },
76+
{ module: at('terminal/plugin.js'), prefix: '/terminal', config: { token: terminalToken } },
77+
{ module: at('tunnel/plugin.js'), prefix: '/tunnel' },
7878
{
79-
id: 'notifications',
8079
module: at('notifications/plugin.js'),
8180
prefix: '/.notifications',
8281
config: { podsRoot: PODS, baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` },
8382
},
84-
{ id: 'pay', module: at('pay/plugin.js'), prefix: '/paid', config: { cost: 1, address: 'demo' } },
85-
{ id: 'nip05', module: at('nip05/plugin.js'), prefix: '/nip05', config: { podsRoot: PODS, relayUrl: `${PUBLIC_URL.replace(/^http/, 'ws')}/relay` } },
86-
{ id: 'corsproxy', module: at('corsproxy/plugin.js'), prefix: '/proxy', config: {} },
87-
{ id: 'capability', module: at('capability/plugin.js'), prefix: '/cap', config: {} },
88-
{ id: 'webdav', module: at('webdav/plugin.js'), prefix: '/webdav', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
89-
{ id: 'gitscratch', module: at('gitscratch/plugin.js'), prefix: '/git', config: {} },
90-
{ id: 'sparql', module: at('sparql/plugin.js'), prefix: '/sparql', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
91-
{ id: 'otp', module: at('otp/plugin.js'), prefix: '/otp', config: {} },
92-
{ id: 'carddav', module: at('carddav/plugin.js'), prefix: '/carddav', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
93-
{ id: 'mastodon', module: at('mastodon/plugin.js'), prefix: '/mastodon', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
94-
{ id: 'bluesky', module: at('bluesky/plugin.js'), prefix: '/bluesky', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
95-
{ id: 'caldav', module: at('caldav/plugin.js'), prefix: '/caldav', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
96-
{ id: 'webfinger', module: at('webfinger/plugin.js'), prefix: '/webfinger', config: { podsRoot: PODS, baseUrl: PUBLIC_URL } },
97-
{ id: 'activitypub', module: at('activitypub/plugin.js'), prefix: '/activitypub', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
98-
{ id: 'rss', module: at('rss/plugin.js'), prefix: '/feed', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
99-
{ id: 'matrix', module: at('matrix/plugin.js'), prefix: '/matrix', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
100-
{ id: 'search', module: at('search/plugin.js'), prefix: '/search', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
101-
{ id: 'didweb', module: at('didweb/plugin.js'), prefix: '/didweb', config: { podsRoot: PODS, baseUrl: PUBLIC_URL } },
102-
{ id: 's3', module: at('s3/plugin.js'), prefix: '/s3', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
103-
{ id: 'micropub', module: at('micropub/plugin.js'), prefix: '/micropub', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
104-
{ id: 'backup', module: at('backup/plugin.js'), prefix: '/backup', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
105-
{ id: 'shortlink', module: at('shortlink/plugin.js'), prefix: '/short', config: { baseUrl: PUBLIC_URL } },
106-
{ id: 'oembed', module: at('oembed/plugin.js'), prefix: '/oembed', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
107-
{ id: 'jmap', module: at('jmap/plugin.js'), prefix: '/jmap', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
83+
{ module: at('pay/plugin.js'), prefix: '/paid', config: { cost: 1, address: 'demo' } },
84+
{ module: at('nip05/plugin.js'), prefix: '/nip05', config: { podsRoot: PODS, relayUrl: `${PUBLIC_URL.replace(/^http/, 'ws')}/relay` } },
85+
{ module: at('corsproxy/plugin.js'), prefix: '/proxy', config: {} },
86+
{ module: at('capability/plugin.js'), prefix: '/cap', config: {} },
87+
{ module: at('webdav/plugin.js'), prefix: '/webdav', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
88+
{ module: at('gitscratch/plugin.js'), prefix: '/git', config: {} },
89+
{ module: at('sparql/plugin.js'), prefix: '/sparql', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
90+
{ module: at('otp/plugin.js'), prefix: '/otp', config: {} },
91+
{ module: at('carddav/plugin.js'), prefix: '/carddav', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
92+
{ module: at('mastodon/plugin.js'), prefix: '/mastodon', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
93+
{ module: at('bluesky/plugin.js'), prefix: '/bluesky', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
94+
{ module: at('caldav/plugin.js'), prefix: '/caldav', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
95+
{ module: at('webfinger/plugin.js'), prefix: '/webfinger', config: { podsRoot: PODS, baseUrl: PUBLIC_URL } },
96+
{ module: at('activitypub/plugin.js'), prefix: '/activitypub', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
97+
{ module: at('rss/plugin.js'), prefix: '/feed', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
98+
{ module: at('matrix/plugin.js'), prefix: '/matrix', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
99+
{ module: at('search/plugin.js'), prefix: '/search', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
100+
{ module: at('didweb/plugin.js'), prefix: '/didweb', config: { podsRoot: PODS, baseUrl: PUBLIC_URL } },
101+
{ module: at('s3/plugin.js'), prefix: '/s3', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
102+
{ module: at('micropub/plugin.js'), prefix: '/micropub', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
103+
{ module: at('backup/plugin.js'), prefix: '/backup', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
104+
{ module: at('shortlink/plugin.js'), prefix: '/short', config: { baseUrl: PUBLIC_URL } },
105+
{ module: at('oembed/plugin.js'), prefix: '/oembed', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
106+
{ module: at('jmap/plugin.js'), prefix: '/jmap', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}` } },
108107
// webfinger/ above owns /.well-known/webfinger — the witnessed collision
109108
// (remotestorage/README.md) — so remotestorage stands down here.
110-
{ id: 'remotestorage', module: at('remotestorage/plugin.js'), prefix: '/remotestorage', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}`, claimWellKnown: false } },
111-
{ id: 'metrics', module: at('metrics/plugin.js'), prefix: '/metrics', config: { loopbackUrl: `http://127.0.0.1:${PORT}`, ...(process.env.METRICS_TOKEN ? { token: process.env.METRICS_TOKEN } : {}) } },
109+
{ module: at('remotestorage/plugin.js'), prefix: '/remotestorage', config: { baseUrl: PUBLIC_URL, loopbackUrl: `http://127.0.0.1:${PORT}`, claimWellKnown: false } },
110+
{ module: at('metrics/plugin.js'), prefix: '/metrics', config: { loopbackUrl: `http://127.0.0.1:${PORT}`, ...(process.env.METRICS_TOKEN ? { token: process.env.METRICS_TOKEN } : {}) } },
112111
{
113-
id: 'dashboard',
114112
module: at('dashboard/plugin.js'),
115113
prefix: '/dashboard',
116114
// Auto-discovers every plugin via api.plugins (#610); reaches the host
@@ -128,7 +126,6 @@ const fastify = createServer({
128126
},
129127
},
130128
{
131-
id: 'admin',
132129
module: at('admin/plugin.js'),
133130
prefix: '/admin',
134131
// Auto-discovers via api.plugins (#610); origin via api.serverInfo

0 commit comments

Comments
 (0)