softr-vibe-coding 2.14.1 → 2.14.3

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ All notable changes to this skill are documented here. Versions follow [Semantic
4
4
 
5
5
  Entries from 1.3.1 onward are generated automatically from git commit subjects between version bumps (see `.github/workflows/publish.yml`). Entries before 1.3.1 were backfilled by hand from the existing commit history.
6
6
 
7
+ ## [2.14.3] - 2026-10-07
8
+ - Release 2.14.3
9
+ - Document that the REST API proxy is not access control
10
+ - Run the publish workflow one at a time and pass a duplicate release run
11
+
12
+ ## [2.14.2] - 2026-10-06
13
+ - Release 2.14.2
14
+ - Check every relative Markdown link in CI before publishing
15
+
7
16
  ## [2.14.1] - 2026-10-06
8
17
  - Release 2.14.1
9
18
  - Add nine more verified Softr facts from the 2026-09 production build
package/README.md CHANGED
@@ -380,6 +380,8 @@ This is an **UNOFFICIAL**, community-maintained Claude skill. It is provided **a
380
380
 
381
381
  Pull requests are what make open source great, and we appreciate the spirit behind them. That said, this skill is maintained for a specific personal workflow, so PRs won't be merged here. We highly recommend forking this repo and making it your own — customize it for your team, your data sources, your design system. That's the beauty of open source.
382
382
 
383
+ If you fork it: the publish workflow first runs `python3 .github/scripts/check-links.py`, which fails the run when any relative Markdown link points at a missing file or a missing `#anchor`. Run the same command before pushing.
384
+
383
385
  ---
384
386
 
385
387
  ## References
@@ -287,7 +287,9 @@ to set a client's links server-side.
287
287
 
288
288
  - **The step:** a **Run custom code** step (`CUSTOM_CODE` v1.2.0) with the HubSpot integration
289
289
  attached. `fetch()` calls to `api.hubapi.com` then carry that integration's credentials, with no
290
- token in the code.
290
+ token in the code. Those credentials carry only the scopes granted when HubSpot was connected.
291
+ A call to an object Softr's connector doesn't support yet may get HubSpot's 403 (Softr engineer,
292
+ 2026-10-07; the scope list is not published).
291
293
  - **Plan and testing:** the step needs a paid Softr plan, and it is `REAL_ONLY`, so a test run
292
294
  writes for real.
293
295
  - **Limits:** about 2 minutes per run, and up to 20 fetches per second.
@@ -310,7 +312,9 @@ to set a client's links server-side.
310
312
  HubSpot itself. The cost is trigger latency (unknown, see below) and a run for every new ticket.
311
313
  - **Why the block can't do it itself:** `useProxyFetch` is documented for REST API sources only.
312
314
  Call API authenticates only REST_API integrations and needs Professional or higher, so it would
313
- need a HubSpot private-app token stored as a REST integration (inferred).
315
+ need a HubSpot private-app token stored as a REST integration (inferred). Any user who can call
316
+ the proxy could then use that token for any path on HubSpot's API
317
+ ([why](rest-api.md#the-proxy-is-not-access-control)).
314
318
  - **HubSpot-side route:** a HubSpot workflow's "Create associations" action needs Pro or
315
319
  Enterprise, and ticket-based workflows need Service Hub Pro or Enterprise (documented). It
316
320
  matches records by exact, case-sensitive property value.
@@ -322,6 +326,12 @@ request parameter the caller controls, not access control. See
322
326
  [../references/softr-mcp.md](../references/softr-mcp.md#what-the-server-enforces-on-a-blocks-data-endpoints)
323
327
  (verified 2026-09-18 on Softr Database; on HubSpot 2026-10-05, below).
324
328
 
329
+ A **REST API source pointed at HubSpot** (`useProxyFetch` with a private-app token) has no row gate
330
+ at all. The proxy forwards any path on `api.hubapi.com` that the browser sends (Softr engineers,
331
+ 2026-10-07); see [rest-api.md](rest-api.md#the-proxy-is-not-access-control). Serve client-facing
332
+ rows from the native connection. Objects it lacks, such as conversations and feedback submissions,
333
+ need one of the server-side routes listed there.
334
+
325
335
  The **block's Visibility** is enforced on the same endpoints, all or nothing (verified 2026-10-05 on
326
336
  HubSpot). A block gated to an "Account managers" condition group returned every deal and ticket to
327
337
  members from connections with no Source condition, and 403 on list and by-id to clients, a
@@ -98,10 +98,47 @@ export default function Block() {
98
98
 
99
99
  - `useProxyFetch` returns a fetch function that routes requests through Softr's proxy
100
100
  - Softr injects the authentication headers configured in the data source automatically
101
- - API keys are **never exposed** in client-side code
101
+ - API keys are **never exposed** in client-side code. That protects the key, not the data: see [The proxy is not access control](#the-proxy-is-not-access-control)
102
102
  - The response is the raw API JSON -- access fields directly (e.g., `item.name`, not `record.fields.name`)
103
103
  - **The proxy only supports text payloads** -- streams, `FormData`, and file uploads won't work. Serialize request bodies as JSON/text.
104
104
 
105
+ ### The proxy is not access control
106
+
107
+ *Softr engineers, asked 2026-10-07; not tested here.* The proxy does two things:
108
+
109
+ - It sends requests only to the **origin** set when the REST API data source was created, e.g.
110
+ `https://api.hubapi.com`, so they can't be redirected to another host.
111
+ - It adds the data source's stored secrets on Softr's server, so the token never reaches the browser.
112
+
113
+ It checks nothing else. The path, query, method and body come from the browser. A user can copy a
114
+ proxy request from DevTools' Network tab, change the endpoint or record id, and resend it. Softr
115
+ forwards it as is, and its engineers advise against relying on the proxy for security. So:
116
+
117
+ - **Anyone who can call the proxy can read everything the token can read on that origin.**
118
+ Filtering in block code, or a URL built from `useCurrentUser().email`, decides what the block
119
+ shows, not what a user can fetch.
120
+ - **Source conditions don't apply.** The connection's record filters cover the record hooks
121
+ (`useRecords` and the rest), not `proxyFetch`. This came from a less certain answer in the same
122
+ thread.
123
+ - **Block Visibility and page permissions may not apply either.** That same answer said "only
124
+ the record hooks" for them too, and it is unconfirmed. Until it is tested, assume any logged-in
125
+ user can call the proxy.
126
+ - **`{LOGGED_IN_USER: …}` placeholders don't help.** They are reportedly filled in on the server,
127
+ but a replayed request can simply leave the placeholder out.
128
+ - **The token's scopes set the blast radius.** Grant only the narrowest scopes the block needs.
129
+
130
+ Per-user data needs a gate on the server instead:
131
+
132
+ 1. **A native connector with a logged-in-user Source condition.** This is verified on Softr
133
+ Database and HubSpot; see
134
+ [softr-mcp.md](../references/softr-mcp.md#what-the-server-enforces-on-a-blocks-data-endpoints).
135
+ Softr's engineers asked first why a builder would pick REST over the native integration.
136
+ 2. **For data no native connector covers**, copy it with a workflow into a table that a native
137
+ connector reads, such as a Softr Database table keyed by the user's email, and gate that table
138
+ with a Source condition. This design is inferred, not built.
139
+ 3. **Otherwise keep REST proxy blocks to data that everyone who can reach the page may see**, or
140
+ to internal tools whose users are trusted with everything the token reads.
141
+
105
142
  ### Multiple datasources
106
143
 
107
144
  When the block has more than one datasource, `useProxyFetch` needs to know which source to route through. Unlike the record hooks (which take a `from:` option), it takes the alias as its **function argument**:
@@ -184,6 +221,7 @@ fetch("https://workflows-api.softr.io/v1/workflows/WORKFLOW_ID/executions/EXECUT
184
221
  | Pagination | Built-in `fetchNextPage` | Manual via URL params + cursor |
185
222
  | Filtering | `q.text()`, `q.number()`, etc. | API query params or client-side |
186
223
  | Auth | Handled by Softr | Proxied through Softr (key hidden) |
224
+ | Per-user rows | Source conditions, enforced on the server | None: the browser picks the URL ([why](#the-proxy-is-not-access-control)) |
187
225
  | Mutations | `useRecordCreate/Update/Delete` | Direct `fetch()` or `proxyFetch()` |
188
226
 
189
227
  ## Limitations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "softr-vibe-coding",
3
- "version": "2.14.1",
3
+ "version": "2.14.3",
4
4
  "description": "Claude Code skill for generating production-ready Softr Vibe Coding blocks (JSX). Installs into ~/.claude/skills/ and auto-updates on each Claude Code session.",
5
5
  "bin": {
6
6
  "softr-vibe-coding": "bin/cli.js"
@@ -14,6 +14,7 @@ Run through this catalog before delivering any block. Every row is a violation o
14
14
  | `useRecords` with REST API source | Use `useProxyFetch` + `useQuery` |
15
15
  | `q.select()` for REST API fields | Access raw API response directly |
16
16
  | Hardcoding API keys for connected API | Use `useProxyFetch` -- key stays server-side |
17
+ | Limiting a REST API block to the user's own records by building the `proxyFetch` URL from `useCurrentUser()` or by filtering the response | **This is not access control.** The proxy forwards any path on the source's origin with the stored token, and a user can replay the request from DevTools with another endpoint or record id (Softr engineers, 2026-10-07). Per-user rows need a native connector with a Source condition. See [datasources/rest-api.md](../datasources/rest-api.md#the-proxy-is-not-access-control) |
17
18
  | Using `q.select({})` to dump all fields on Softr Database | Returns record IDs with empty `fields: {}`. Look up field IDs in Studio's Data tab, or use the Softr DB REST API with `fieldNames=true` |
18
19
  | Building an invisible helper block + `window` globals just to read a second table | A block can connect to **multiple data sources**. Declare them with `datasource.define({ alias: "uuid" })` and pass `from: ds.alias` on every hook. One block instead of two, no page-order dependency, no mount-timing race. See [datasources/multi-datasource.md](../datasources/multi-datasource.md). Helper blocks remain correct for genuinely cross-*block* jobs (triggering another block, sharing computed state) — just not for plain multi-table reads |
19
20
  | Omitting `from:` on a hook when the block has more than one datasource | Throws at runtime. `from:` is optional ONLY when exactly one source is connected — then hooks default to it. Applies to `useRecords`, `useRecord`, `useLinkedRecords`, `useFieldOptions`, `useMetric`, `useChartData`, `useRecordCreate`, `useRecordUpdate`, `useRecordDelete`. NOT to `useUpload` / `useCurrentRecordId`, which are app-level. `useProxyFetch` has the same multi-datasource requirement but takes the alias as its **argument** — `useProxyFetch(ds.store)` — not as `from:` |