opencode-supabase 0.1.1 → 0.1.2-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +51 -0
  2. package/package.json +3 -1
  3. package/skills/.upstream.json +13 -0
  4. package/skills/supabase/SKILL.md +112 -0
  5. package/skills/supabase/assets/feedback-issue-template.md +17 -0
  6. package/skills/supabase/references/skill-feedback.md +17 -0
  7. package/skills/supabase-postgres-best-practices/SKILL.md +64 -0
  8. package/skills/supabase-postgres-best-practices/references/_contributing.md +170 -0
  9. package/skills/supabase-postgres-best-practices/references/_sections.md +39 -0
  10. package/skills/supabase-postgres-best-practices/references/_template.md +34 -0
  11. package/skills/supabase-postgres-best-practices/references/advanced-full-text-search.md +55 -0
  12. package/skills/supabase-postgres-best-practices/references/advanced-jsonb-indexing.md +49 -0
  13. package/skills/supabase-postgres-best-practices/references/conn-idle-timeout.md +46 -0
  14. package/skills/supabase-postgres-best-practices/references/conn-limits.md +44 -0
  15. package/skills/supabase-postgres-best-practices/references/conn-pooling.md +41 -0
  16. package/skills/supabase-postgres-best-practices/references/conn-prepared-statements.md +46 -0
  17. package/skills/supabase-postgres-best-practices/references/data-batch-inserts.md +54 -0
  18. package/skills/supabase-postgres-best-practices/references/data-n-plus-one.md +53 -0
  19. package/skills/supabase-postgres-best-practices/references/data-pagination.md +50 -0
  20. package/skills/supabase-postgres-best-practices/references/data-upsert.md +50 -0
  21. package/skills/supabase-postgres-best-practices/references/lock-advisory.md +56 -0
  22. package/skills/supabase-postgres-best-practices/references/lock-deadlock-prevention.md +68 -0
  23. package/skills/supabase-postgres-best-practices/references/lock-short-transactions.md +50 -0
  24. package/skills/supabase-postgres-best-practices/references/lock-skip-locked.md +54 -0
  25. package/skills/supabase-postgres-best-practices/references/monitor-explain-analyze.md +45 -0
  26. package/skills/supabase-postgres-best-practices/references/monitor-pg-stat-statements.md +55 -0
  27. package/skills/supabase-postgres-best-practices/references/monitor-vacuum-analyze.md +55 -0
  28. package/skills/supabase-postgres-best-practices/references/query-composite-indexes.md +44 -0
  29. package/skills/supabase-postgres-best-practices/references/query-covering-indexes.md +40 -0
  30. package/skills/supabase-postgres-best-practices/references/query-index-types.md +48 -0
  31. package/skills/supabase-postgres-best-practices/references/query-missing-indexes.md +43 -0
  32. package/skills/supabase-postgres-best-practices/references/query-partial-indexes.md +45 -0
  33. package/skills/supabase-postgres-best-practices/references/schema-constraints.md +80 -0
  34. package/skills/supabase-postgres-best-practices/references/schema-data-types.md +46 -0
  35. package/skills/supabase-postgres-best-practices/references/schema-foreign-key-indexes.md +59 -0
  36. package/skills/supabase-postgres-best-practices/references/schema-lowercase-identifiers.md +55 -0
  37. package/skills/supabase-postgres-best-practices/references/schema-partitioning.md +55 -0
  38. package/skills/supabase-postgres-best-practices/references/schema-primary-keys.md +61 -0
  39. package/skills/supabase-postgres-best-practices/references/security-privileges.md +54 -0
  40. package/skills/supabase-postgres-best-practices/references/security-rls-basics.md +50 -0
  41. package/skills/supabase-postgres-best-practices/references/security-rls-performance.md +57 -0
  42. package/src/server/index.ts +6 -0
  43. package/src/server/skills.ts +84 -0
@@ -0,0 +1,54 @@
1
+ ---
2
+ title: Apply Principle of Least Privilege
3
+ impact: MEDIUM
4
+ impactDescription: Reduced attack surface, better audit trail
5
+ tags: privileges, security, roles, permissions
6
+ ---
7
+
8
+ ## Apply Principle of Least Privilege
9
+
10
+ Grant only the minimum permissions required. Never use superuser for application queries.
11
+
12
+ **Incorrect (overly broad permissions):**
13
+
14
+ ```sql
15
+ -- Application uses superuser connection
16
+ -- Or grants ALL to application role
17
+ grant all privileges on all tables in schema public to app_user;
18
+ grant all privileges on all sequences in schema public to app_user;
19
+
20
+ -- Any SQL injection becomes catastrophic
21
+ -- drop table users; cascades to everything
22
+ ```
23
+
24
+ **Correct (minimal, specific grants):**
25
+
26
+ ```sql
27
+ -- Create role with no default privileges
28
+ create role app_readonly nologin;
29
+
30
+ -- Grant only SELECT on specific tables
31
+ grant usage on schema public to app_readonly;
32
+ grant select on public.products, public.categories to app_readonly;
33
+
34
+ -- Create role for writes with limited scope
35
+ create role app_writer nologin;
36
+ grant usage on schema public to app_writer;
37
+ grant select, insert, update on public.orders to app_writer;
38
+ grant usage on sequence orders_id_seq to app_writer;
39
+ -- No DELETE permission
40
+
41
+ -- Login role inherits from these
42
+ create role app_user login password 'xxx';
43
+ grant app_writer to app_user;
44
+ ```
45
+
46
+ Revoke public defaults:
47
+
48
+ ```sql
49
+ -- Revoke default public access
50
+ revoke all on schema public from public;
51
+ revoke all on all tables in schema public from public;
52
+ ```
53
+
54
+ Reference: [Roles and Privileges](https://supabase.com/blog/postgres-roles-and-privileges)
@@ -0,0 +1,50 @@
1
+ ---
2
+ title: Enable Row Level Security for Multi-Tenant Data
3
+ impact: CRITICAL
4
+ impactDescription: Database-enforced tenant isolation, prevent data leaks
5
+ tags: rls, row-level-security, multi-tenant, security
6
+ ---
7
+
8
+ ## Enable Row Level Security for Multi-Tenant Data
9
+
10
+ Row Level Security (RLS) enforces data access at the database level, ensuring users only see their own data.
11
+
12
+ **Incorrect (application-level filtering only):**
13
+
14
+ ```sql
15
+ -- Relying only on application to filter
16
+ select * from orders where user_id = $current_user_id;
17
+
18
+ -- Bug or bypass means all data is exposed!
19
+ select * from orders; -- Returns ALL orders
20
+ ```
21
+
22
+ **Correct (database-enforced RLS):**
23
+
24
+ ```sql
25
+ -- Enable RLS on the table
26
+ alter table orders enable row level security;
27
+
28
+ -- Create policy for users to see only their orders
29
+ create policy orders_user_policy on orders
30
+ for all
31
+ using (user_id = current_setting('app.current_user_id')::bigint);
32
+
33
+ -- Force RLS even for table owners
34
+ alter table orders force row level security;
35
+
36
+ -- Set user context and query
37
+ set app.current_user_id = '123';
38
+ select * from orders; -- Only returns orders for user 123
39
+ ```
40
+
41
+ Policy for authenticated role:
42
+
43
+ ```sql
44
+ create policy orders_user_policy on orders
45
+ for all
46
+ to authenticated
47
+ using (user_id = auth.uid());
48
+ ```
49
+
50
+ Reference: [Row Level Security](https://supabase.com/docs/guides/database/postgres/row-level-security)
@@ -0,0 +1,57 @@
1
+ ---
2
+ title: Optimize RLS Policies for Performance
3
+ impact: HIGH
4
+ impactDescription: 5-10x faster RLS queries with proper patterns
5
+ tags: rls, performance, security, optimization
6
+ ---
7
+
8
+ ## Optimize RLS Policies for Performance
9
+
10
+ Poorly written RLS policies can cause severe performance issues. Use subqueries and indexes strategically.
11
+
12
+ **Incorrect (function called for every row):**
13
+
14
+ ```sql
15
+ create policy orders_policy on orders
16
+ using (auth.uid() = user_id); -- auth.uid() called per row!
17
+
18
+ -- With 1M rows, auth.uid() is called 1M times
19
+ ```
20
+
21
+ **Correct (wrap functions in SELECT):**
22
+
23
+ ```sql
24
+ create policy orders_policy on orders
25
+ using ((select auth.uid()) = user_id); -- Called once, cached
26
+
27
+ -- 100x+ faster on large tables
28
+ ```
29
+
30
+ Use security definer functions for complex checks:
31
+
32
+ ```sql
33
+ -- Create helper function (runs as definer, bypasses RLS)
34
+ create or replace function is_team_member(team_id bigint)
35
+ returns boolean
36
+ language sql
37
+ security definer
38
+ set search_path = ''
39
+ as $$
40
+ select exists (
41
+ select 1 from public.team_members
42
+ where team_id = $1 and user_id = (select auth.uid())
43
+ );
44
+ $$;
45
+
46
+ -- Use in policy (indexed lookup, not per-row check)
47
+ create policy team_orders_policy on orders
48
+ using ((select is_team_member(team_id)));
49
+ ```
50
+
51
+ Always add indexes on columns used in RLS policies:
52
+
53
+ ```sql
54
+ create index orders_user_id_idx on orders (user_id);
55
+ ```
56
+
57
+ Reference: [RLS Performance](https://supabase.com/docs/guides/database/postgres/row-level-security#rls-performance-recommendations)
@@ -2,6 +2,7 @@ import type { Plugin } from "@opencode-ai/plugin";
2
2
 
3
3
  import { createServerLogWriter, createSupabaseLogger } from "../shared/log.ts";
4
4
  import { createSupabaseAuth } from "./auth.ts";
5
+ import { registerSupabaseSkillPaths } from "./skills.ts";
5
6
  import { createSupabaseTools } from "./tools.ts";
6
7
 
7
8
  const server: Plugin = async (input, options) => {
@@ -10,6 +11,11 @@ const server: Plugin = async (input, options) => {
10
11
  });
11
12
 
12
13
  return {
14
+ config: async (config) => {
15
+ registerSupabaseSkillPaths(config, options, {
16
+ warn: (message, data) => logger.warn(message, data as Record<string, unknown>),
17
+ });
18
+ },
13
19
  auth: createSupabaseAuth(input, options, { logger }),
14
20
  tool: createSupabaseTools(input, options, { logger }),
15
21
  };
@@ -0,0 +1,84 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export const BUNDLED_SUPABASE_SKILLS = [
5
+ "supabase",
6
+ "supabase-postgres-best-practices",
7
+ ] as const;
8
+
9
+ export type BundledSupabaseSkill = (typeof BUNDLED_SUPABASE_SKILLS)[number];
10
+
11
+ type Warn = (message: string, data?: unknown) => void;
12
+
13
+ type ResolverDeps = {
14
+ warn?: Warn;
15
+ };
16
+
17
+ type RegisterDeps = ResolverDeps & {
18
+ skillsRoot?: string;
19
+ exists?: (path: string) => boolean;
20
+ };
21
+
22
+ type ConfigWithSkills = object & {
23
+ skills?: {
24
+ paths?: string[];
25
+ };
26
+ };
27
+
28
+ function isRecord(value: unknown): value is Record<string, unknown> {
29
+ return typeof value === "object" && value !== null && !Array.isArray(value);
30
+ }
31
+
32
+ function pluginSkillsOption(options: unknown) {
33
+ if (!isRecord(options) || !("skills" in options)) return true;
34
+ return options.skills;
35
+ }
36
+
37
+ export function resolveEnabledSupabaseSkills(options: unknown, deps: ResolverDeps = {}) {
38
+ const value = pluginSkillsOption(options);
39
+ if (value === false) return [];
40
+ if (value === true || value === undefined) return [...BUNDLED_SUPABASE_SKILLS];
41
+
42
+ if (!isRecord(value)) {
43
+ deps.warn?.("invalid Supabase skills option; loading bundled skills", { value });
44
+ return [...BUNDLED_SUPABASE_SKILLS];
45
+ }
46
+
47
+ const known = new Set<string>(BUNDLED_SUPABASE_SKILLS);
48
+ for (const key of Object.keys(value)) {
49
+ if (!known.has(key)) {
50
+ deps.warn?.("unknown Supabase bundled skill option ignored", { skill: key });
51
+ }
52
+ }
53
+
54
+ return BUNDLED_SUPABASE_SKILLS.filter((skill) => value[skill] !== false);
55
+ }
56
+
57
+ export function defaultSkillsRoot() {
58
+ return path.resolve(path.dirname(new URL(import.meta.url).pathname), "../../skills");
59
+ }
60
+
61
+ export function registerSupabaseSkillPaths(
62
+ config: object,
63
+ options: unknown,
64
+ deps: RegisterDeps = {},
65
+ ) {
66
+ const configWithSkills = config as ConfigWithSkills;
67
+ const skillsRoot = deps.skillsRoot ?? defaultSkillsRoot();
68
+ const exists = deps.exists ?? fs.existsSync;
69
+ const enabled = resolveEnabledSupabaseSkills(options, deps);
70
+
71
+ configWithSkills.skills = configWithSkills.skills ?? {};
72
+ configWithSkills.skills.paths = configWithSkills.skills.paths ?? [];
73
+
74
+ for (const skill of enabled) {
75
+ const skillPath = path.join(skillsRoot, skill);
76
+ if (!exists(skillPath)) {
77
+ deps.warn?.("bundled Supabase skill directory not found", { skill, path: skillPath });
78
+ continue;
79
+ }
80
+ if (!configWithSkills.skills.paths.includes(skillPath)) {
81
+ configWithSkills.skills.paths.push(skillPath);
82
+ }
83
+ }
84
+ }