Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c761c52
test(clearfolio): specify bounded provider transport
seonghobae Aug 14, 2026
f38a3d0
fix(clearfolio): bound provider transport and JSON responses
seonghobae Aug 14, 2026
71a8aaf
test(clearfolio): run adapter regressions through real response streams
seonghobae Aug 14, 2026
312466d
test(clearfolio): exercise configuration contracts with streamed resp…
seonghobae Aug 14, 2026
b9c18e6
test(clearfolio): register bounded provider regressions
seonghobae Aug 14, 2026
099cf2f
docs(clearfolio): record bounded provider response boundary
seonghobae Aug 14, 2026
892484b
docs(deploy): document Clearfolio transport limits
seonghobae Aug 14, 2026
a733a2f
docs(changelog): record bounded Clearfolio provider transport
seonghobae Aug 14, 2026
a70ac7d
test(clearfolio): preserve IPv6 loopback parent regression
seonghobae Aug 15, 2026
ad6ca38
fix(clearfolio): carry forward IPv6 loopback parent fix
seonghobae Aug 15, 2026
28cff4a
merge: refresh Clearfolio provider stack on current parent
seonghobae Aug 15, 2026
3adca52
test(clearfolio): require cancellation of rejected provider bodies
seonghobae Aug 15, 2026
88fa42e
test(clearfolio): cover bodyless and failed cancellation paths
seonghobae Aug 15, 2026
4f01714
fix(clearfolio): cancel rejected provider response bodies
seonghobae Aug 15, 2026
11de7b8
merge(clearfolio): reconcile provider boundary with parent hardening
seonghobae Aug 16, 2026
b0e2df8
test(clearfolio): retain parent cross-origin token rejection
seonghobae Aug 16, 2026
c7f0ddd
fix(clearfolio): reject cross-origin artifact tokens
seonghobae Aug 16, 2026
b362077
merge(clearfolio): reconcile current production configuration parent
seonghobae Aug 16, 2026
6d7774d
docs(deploy): inherit orchestrator fail-closed contract
seonghobae Aug 16, 2026
9f4b2b8
merge(clearfolio): reconcile latest production configuration parent
seonghobae Aug 16, 2026
036f916
fix(clearfolio): preserve current parent tree during stack reconcilia…
seonghobae Aug 16, 2026
d4b09cd
fix(changelog): preserve parent XML import entry
seonghobae Aug 16, 2026
89983c4
fix(clearfolio): allowlist attachment-view artifact origins
cursoragent Aug 16, 2026
6eb6929
docs(clearfolio): record artifact-origin allowlist contract
cursoragent Aug 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
app flows.
- `tests/config/`: repository governance and workflow ownership checks.

## Clearfolio adapter

- Clearfolio is an optional MSA document-viewer adapter. Planning stays available when it is unconfigured.
- Production requires a root HTTPS origin and HMAC secret; the in-memory adapter exists only behind `SCOPEWEAVE_DEV=1`.
- Hosted provider calls are non-redirecting, time-bounded, and JSON-bounded before any provider data can change ScopeWeave state.
- Attachment view 302s only to the configured origin or `CLEARFOLIO_ARTIFACT_ORIGINS`. Credentials and fragments are rejected.

## Core decisions

- One global `tasks` array holds canonical task records.
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
required a canonical signed production origin, rejected ambiguous provider
URL components, and prevented cross-origin artifact tokens from being
transplanted into the trusted Clearfolio viewer URL.
- Bounded hosted Clearfolio calls to non-redirecting 15-second requests and
256 KiB streamed JSON responses, composed caller cancellation with the
provider budget, and validated document metadata/bytes and provider job IDs
before allocation, persistence, or URL construction.
- Restricted Clearfolio attachment-view redirects to the configured provider
origin or an explicit `CLEARFOLIO_ARTIFACT_ORIGINS` allowlist, and rejected
credential-bearing or fragment-bearing artifact links before the 302 target
is returned.
- Made `SCOPEWEAVE_JWT_SECRET` mandatory at startup and rejected weak or
unexpanded placeholder values so production deployments fail closed.
- Neutralized audit-log CSV formulas even when executable prefixes are hidden
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ Docker: set a **persistent** `SCOPEWEAVE_JWT_SECRET` first, then run `docker com
| `STRIPE_SECRET_KEY` | Real checkout (mock URL when unset) |
| `SCOPEWEAVE_RATE_LIMIT_MAX` (+`_WINDOW_MS`) | Opt-in per-IP rate limiting |
| `SCOPEWEAVE_DEV=1` | Dev-only endpoints (activate-pro) |
| `CLEARFOLIO_URL` / `CLEARFOLIO_HMAC_SECRET` | Production document viewer origin and HMAC secret |
| `CLEARFOLIO_ARTIFACT_ORIGINS` | Reviewed CDN origins for attachment-view redirects |

## Verification

Expand Down
9 changes: 6 additions & 3 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,14 @@ credentials never reach the browser. HWP/HWPX are rejected (Clearfolio policy).
| --- | --- | --- |
| `POST` | `/api/projects/:id/attachments` | multipart `file` (+`taskId?`, ≤10MB) → conversion job (write roles) |
| `GET` | `/api/projects/:id/attachments?taskId=` | List (+ refreshes pending statuses) |
| `GET` | `/api/projects/:id/attachments/:aid/view` | 302 → signed artifact URL (`?token=` for new-tab opens) |
| `GET` | `/api/projects/:id/attachments/:aid/view` | 302 → same-origin or allowlisted artifact URL (`?token=` for new-tab opens) |
| `DELETE` | `/api/projects/:id/attachments/:aid` | Uploader or manage |

Env: `CLEARFOLIO_URL` (+ optional `CLEARFOLIO_HMAC_SECRET` for gateway-signed
tenant claims). Unset → a built-in mock converter (dev/test only).
Env: `CLEARFOLIO_URL` and `CLEARFOLIO_HMAC_SECRET` for production conversion.
`CLEARFOLIO_ARTIFACT_ORIGINS` lists reviewed CDN origins for attachment-view
redirects; empty trusts only the Clearfolio origin. Unset `CLEARFOLIO_URL`
fails closed in production and enables the in-memory converter only with
`SCOPEWEAVE_DEV=1`.

## Comments (코멘트)

Expand Down
23 changes: 23 additions & 0 deletions docs/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ persists the database in the `scopeweave-data` volume.
| `ORCHESTRATOR_TOKEN` | with URL | Required bearer token for the configured contextual-orchestrator service (`CONTEXTUAL_ORCHESTRATOR_TOKEN`). |
| `CLEARFOLIO_URL` | for production 산출물 viewer | Root Clearfolio service origin. Production requires HTTPS and rejects credentials, paths, query strings, and fragments. When absent in production, document conversion/viewing is unavailable rather than simulated. |
| `CLEARFOLIO_HMAC_SECRET` | with URL | Required tenant-claim HMAC secret; must contain at least 32 non-whitespace characters and match Clearfolio's configured verifier secret. |
| `CLEARFOLIO_ARTIFACT_ORIGINS` | with URL, when artifacts are served from a CDN | Comma-separated reviewed HTTPS origin list for attachment-view redirects. Empty means only the configured Clearfolio origin is trusted. Entries must be origin URLs without credentials, paths, query strings, or fragments. |
| `SCOPEWEAVE_ATTACHMENT_STATUS_CONCURRENCY` | no (default 8, maximum 32) | Maximum concurrent Clearfolio status lookups during one attachment-list request. Invalid values fall back to 8; values above 32 are clamped. |
| `SCOPEWEAVE_ATTACHMENT_STATUS_TIMEOUT_MS` | no (default 3000, maximum 30000) | Hard caller-side timeout for each Clearfolio status lookup. The AbortSignal is also forwarded downstream. |
| `SCOPEWEAVE_ATTACHMENT_STATUS_BUDGET_MS` | no (default 5000, maximum 60000) | Wall-clock budget for the entire best-effort refresh pass. Work not started before the deadline is deferred to a later list request. |
Expand All @@ -61,6 +62,28 @@ Keep credentials in the dedicated HMAC secret setting rather than URL userinfo,
and do not configure a path, query string, or fragment. The adapter constructs
its own versioned API paths from the validated origin.

Every hosted Clearfolio request is non-redirecting and has a hard 15-second
adapter budget; attachment status lookups compose that budget with the caller's
own cancellation signal. Successful provider responses must be
`application/json`, and both declared and streamed response bytes are capped at
256 KiB before JSON parsing. Provider job identifiers are limited to 256
characters, and upload metadata/bytes are validated before Blob/FormData
allocation. The adapter's document ceiling is 10 MiB, matching the current
ScopeWeave attachment API limit.

These limits are code constants rather than operator-tunable environment
settings. A deployment that needs larger provider responses, longer requests, or
larger documents requires a reviewed application change with corresponding
resource, latency, and security evidence; do not work around these bounds at the
proxy layer.

Attachment view 302s the browser to the provider-returned artifact URL. That
URL is accepted only when it has no credentials or fragment and its origin is
the configured Clearfolio origin or an origin listed in
`CLEARFOLIO_ARTIFACT_ORIGINS`. If planners cannot open a converted document
hosted on a CDN, add that reviewed origin to the allowlist or keep the file on
the Clearfolio host.

## Attachment status refresh operations

The attachment-list API reads `job_id` in its initial project-scoped query and
Expand Down
65 changes: 65 additions & 0 deletions docs/doctoring/clearfolio-artifact-origin-allowlist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Clearfolio artifact-origin allowlist

## Decision

Attachment view is a browser 302 to a provider-returned artifact URL. ScopeWeave therefore treats that URL as an untrusted redirect target even after the Clearfolio origin, HMAC, transport, and JSON-response boundaries have passed.

A returned link is accepted only when:

1. it parses as an absolute URL against the configured Clearfolio origin;
2. its scheme is HTTPS, or HTTP only when both the provider and the link are explicit development-mode loopback HTTP;
3. it has no userinfo and no fragment;
4. its origin is either the configured Clearfolio origin or an origin listed in `CLEARFOLIO_ARTIFACT_ORIGINS`.

Same-origin `artifactToken` values may still be rewritten into the trusted Clearfolio viewer route. Tokens on an allowlisted CDN stay on that CDN and are never copied into the viewer host. An empty allowlist means only the configured Clearfolio origin is trusted, so a planner cannot be sent to an unreviewed host by a confused or compromised provider response.

This slice does not invent a second wire protocol. It only constrains which returned URLs ScopeWeave will hand to the browser. Retry, idempotency, and the remaining Clearfolio lifecycle work stay on issue #489.

```mermaid
flowchart TD
view["GET attachment view"] --> provider["Clearfolio artifact-link JSON"]
provider --> parse["WHATWG URL parse"]
parse --> scheme{"HTTPS or explicit loopback HTTP?"}
scheme -->|no| reject["Fail closed: invalid artifact link"]
scheme -->|yes| secrets{"Userinfo or fragment?"}
secrets -->|yes| reject
secrets -->|no| origin{"Same Clearfolio origin or listed in CLEARFOLIO_ARTIFACT_ORIGINS?"}
origin -->|no| reject
origin -->|yes| token{"Same-origin artifactToken?"}
token -->|yes| viewer["Redirect to Clearfolio viewer"]
token -->|no| host["Redirect to that origin; tokens stay put"]
```

## Operator action

To serve converted documents from a reviewed CDN or object-storage origin, set `CLEARFOLIO_ARTIFACT_ORIGINS` to a comma-separated list of absolute origin URLs such as `https://cdn.example,https://files.example:8443`. Leave the setting empty to keep viewing on the Clearfolio origin only. If a planner sees `clearfolio artifact-link response invalid` after a successful conversion, add the reviewed origin or keep the file on the Clearfolio host; do not disable the check at a proxy.

Unsafe allowlist entries fail closed with `clearfolio_artifact_origins_invalid` and tell the operator to correct the origin list. Credentials, paths, query strings, fragments, and remote HTTP are rejected in the allowlist itself so the setting cannot become an arbitrary request prefix.

## Verification contract

`tests/unit/clearfolio-artifact-origin.test.mjs` proves:

- tokenless cross-origin links fail without an allowlist;
- userinfo and fragments never become redirect targets, even when the host is listed;
- same-origin relative and token-bearing viewer links still work with an empty allowlist;
- listed CDN origins may be returned, and their tokens stay on that origin;
- unlisted CDN origins and malformed allowlist entries fail closed.

`tests/unit/clearfolio-status-signal.test.mjs` now expects the predecessor `https://cdn.example/file.pdf` fixture to fail closed unless that origin is reviewed. The adapter remains an owned c8 production target.

## Security rationale

OWASP's SSRF guidance treats attacker-controlled complete URLs and open redirects as bypass paths around host validation. Attachment view is a user-facing redirect, so the same allowlist discipline applies before the 302 is issued. The WHATWG URL Standard supplies the component model (origin, userinfo, fragment) used for exact comparison instead of string-prefix checks. OWASP API10:2023 continues to classify unvalidated third-party data as unsafe API consumption.

## Rollback

Rollback reverts the allowlist parser, artifact-link origin/credential/fragment checks, the new and adapted unit regressions, test registrations, deployment and API guidance, this doctoring record, and the CHANGELOG entry together. No database schema or persisted attachment representation changes in this slice.

## References

OWASP Foundation. (2023). *API10:2023 unsafe consumption of APIs*. OWASP API Security Top 10. https://owasp.org/API-Security/editions/2023/en/0xaa-unsafe-consumption-of-apis/

OWASP Foundation. (n.d.). *Server Side Request Forgery Prevention Cheat Sheet*. OWASP Cheat Sheet Series. https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html

WHATWG. (2026). *URL Standard*. https://url.spec.whatwg.org/
2 changes: 1 addition & 1 deletion docs/doctoring/clearfolio-production-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This boundary prevents configuration text from becoming an arbitrary downstream

If Clearfolio returns an `artifactToken`, ScopeWeave rewrites it into the trusted Clearfolio viewer route only when the returned URL has the same origin as the configured Clearfolio service. A token-bearing link from another origin is rejected rather than transplanted into the trusted viewer or returned directly to an unreviewed host. This closes the token-confusion boundary without claiming that arbitrary cross-origin artifact hosts are approved.

Issue #489 remains open after this slice. A subsequent bounded change must still implement the explicit reviewed artifact-origin allowlist, redirect policy, streaming response-size/media-type limits, provider-wide request budget, and the remaining resource/lifecycle acceptance criteria before the Clearfolio adapter can be described as fully production-complete.
Issue #489 remains open after this slice. Transport, JSON-response, and artifact-origin allowlist rules now live in their successor doctoring records. Remaining #489 work is retry/idempotency, capability readiness, persistence/lifecycle, and protected integration—not another configuration parser.

## Executable evidence

Expand Down
82 changes: 82 additions & 0 deletions docs/doctoring/clearfolio-provider-response-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Clearfolio provider response and request boundary

## Decision

ScopeWeave treats the Clearfolio service as an untrusted external API even after its root origin and tenant HMAC configuration have passed the production configuration boundary. Every hosted submit, status, and artifact-link call therefore uses the same fail-closed transport and response rules before provider data can affect ScopeWeave state or browser-visible behavior.

This record is intentionally narrower than the full Clearfolio production-readiness issue. It extends the configuration boundary introduced by the preceding production-configuration slice and does not claim that arbitrary cross-origin artifact delivery, retry/idempotency policy, or the complete provider lifecycle is finished.

## Request contract

Hosted provider requests:

1. use the configuration-validated provider origin and adapter-owned endpoint path;
2. send tenant claims only to that direct origin request;
3. use Fetch `redirect: "error"` so a redirect is a transport failure rather than a credential-forwarding opportunity;
4. carry a hard 15,000 ms total-request `AbortSignal`;
5. compose a caller cancellation signal with that hard budget for status refreshes; and
6. collapse network, redirect, timeout, and cancellation details into fixed operation-level errors before they can reach browser or diagnostic payloads.

ScopeWeave does not retry provider calls in this slice. Retry eligibility, idempotency keys, backoff, cancellation recovery, and persisted operation lifecycle remain explicit follow-up work rather than being guessed at the transport layer.

## Response contract

Successful provider responses are accepted only when the media type essence is `application/json`. If `Content-Length` is present it must be an exact non-negative decimal integer no greater than 256 KiB. The body stream is independently counted to the same 256 KiB ceiling, so missing or dishonest length metadata cannot bypass the resource limit. Empty bodies, malformed streams, invalid UTF-8, malformed JSON, and incompatible JSON shapes fail closed with fixed operation-specific errors.

The adapter never uses `response.json()` directly for successful hosted provider responses. This prevents an otherwise successful response from being buffered without an application-level byte ceiling before validation.

Provider conversion states remain the exact `PENDING`, `RUNNING`, `SUCCEEDED`, and `FAILED` set. Provider job identifiers are trimmed and limited to 256 characters without control characters before persistence or URL construction.

## Document boundary

Document metadata and bytes are validated before `Blob` or `FormData` construction. The provider adapter accepts only:

- a non-empty document name of at most 512 characters without control characters;
- a MIME string of at most 255 characters without control characters; an empty value retains the existing `application/octet-stream` fallback;
- `Uint8Array`-compatible bytes no larger than 10 MiB.

The 10 MiB limit matches the current ScopeWeave attachment API ceiling, so the downstream adapter cannot accept a document larger than the application path that feeds it.

## Artifact boundary and remaining work

The preceding slice already prevents a cross-origin `artifactToken` from being transplanted into the trusted Clearfolio viewer origin. This slice bounds and media-validates the artifact-link response itself and disables redirects on the request.

The successor artifact-origin slice owns credential, fragment, and reviewed-origin allowlist rules for attachment-view redirects. This transport slice must not be described as approving arbitrary cross-origin artifact URLs. Issue #489 still owns retry/idempotency, capability readiness, and the remaining provider lifecycle.

## Verification contract

Regression evidence covers:

- `redirect: "error"` on submit, status, and artifact-link calls;
- hard request-budget signals and caller-signal composition;
- non-JSON media rejection;
- declared and streamed response-size overflow;
- cancellation-detail sanitization;
- document metadata/byte rejection before provider transport;
- oversized provider job identifiers before URL construction;
- valid streamed JSON compatibility for submission, status, HMAC, loopback-development, and artifact-link behavior;
- the predecessor configuration, HMAC, artifact-token-origin, sanitized-error, status-enum, and attachment-refresh contracts under the same normal unit/coverage paths.

`server/clearfolio.mjs` remains an owned c8 production target. The new provider-boundary regression is registered in both `test:unit` and `test:coverage:cases`; exact statement, branch, function, and line evidence remains a merge gate rather than a documentation claim.

## Security rationale

OWASP API10:2023 identifies unsafe consumption of third-party APIs when applications trust integrated-service data, blindly follow redirects, fail to validate returned data, omit timeouts, or fail to limit resources used to process third-party responses. OWASP API4:2023 separately highlights unbounded memory, bandwidth, and execution-time consumption. The transport, timeout, media-type, streaming-byte, identifier, and document limits in this slice apply those controls at the provider boundary rather than relying on Clearfolio to behave correctly.

The WHATWG Fetch Standard explicitly supports `redirect: "error"` to reject redirect responses. ScopeWeave uses that mode because tenant HMAC claims are provider-origin credentials and there is no reviewed redirect allowlist in the current protocol.

Node.js 22 provides `AbortSignal.timeout()` and `AbortSignal.any()`, allowing the adapter to impose its own total request budget while preserving upstream cancellation without maintaining a second timer/cancellation protocol.

## Rollback

Rollback reverts the provider-boundary implementation, the new and adapted unit regressions, test registrations, deployment guidance, this doctoring record, and the CHANGELOG entry together. The slice adds no database schema or migration. Existing persisted attachment state remains readable by the predecessor implementation.

## References

Node.js contributors. (2026). *Global objects: AbortSignal*. Node.js documentation. Retrieved August 15, 2026, from https://nodejs.org/download/release/v22.18.0/docs/api/globals.html

OWASP Foundation. (2023a). *API4:2023 unrestricted resource consumption*. OWASP API Security Top 10. https://owasp.org/API-Security/editions/2023/en/0xa4-unrestricted-resource-consumption/

OWASP Foundation. (2023b). *API10:2023 unsafe consumption of APIs*. OWASP API Security Top 10. https://owasp.org/API-Security/editions/2023/en/0xaa-unsafe-consumption-of-apis/

WHATWG. (2026). *Fetch Standard* (Living Standard, updated May 8, 2026). https://fetch.spec.whatwg.org/
4 changes: 4 additions & 0 deletions docs/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ ScopeWeave treats the following controls as release-blocking invariants. A chang

Bearer-token middleware and every endpoint that accepts a JWT through another transport must compare the token's `tv` claim with the user's current database `token_version`.

## Clearfolio attachment view

`GET /api/projects/:id/attachments/:aid/view` may 302 only to the configured Clearfolio origin or an origin listed in `CLEARFOLIO_ARTIFACT_ORIGINS`. Returned links with userinfo or fragments are rejected. Cross-origin `artifactToken` values are never copied into the Clearfolio viewer host.

## Spreadsheet exports

Every user-controlled CSV cell is neutralized when, after optional leading whitespace, it begins with `=`, `+`, `-`, `@`, or `|`. Export code must not rely on callers to sanitize values.
Expand Down
Loading