Skip to content

fix(clearfolio): require explicit artifact origin trust - #504

Closed
seonghobae wants to merge 9 commits into
fix/clearfolio-provider-response-boundaryfrom
fix/clearfolio-artifact-origin-policy-489-reconciled
Closed

fix(clearfolio): require explicit artifact origin trust#504
seonghobae wants to merge 9 commits into
fix/clearfolio-provider-response-boundaryfrom
fix/clearfolio-artifact-origin-policy-489-reconciled

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Buyer and security impact

Clearfolio artifact links must never turn an arbitrary cross-origin HTTPS response into a trusted ScopeWeave redirect target. This bounded stacked slice makes artifact authority explicit: the configured Clearfolio origin is trusted by default, and operators may add only exact, reviewed HTTPS artifact origins.

This is a follow-up slice of #489. It is stacked on #498 and does not close #489.

Exact current stack and scope

Current parent: fix/clearfolio-provider-response-boundary@d4b09cddb61f524efaa6d93e12a006f6a51afd3c (#498).
Current child head: 2954f1a65491aae49e674bbe9a4c8d243ec54608.

Fresh parent→child comparison is ahead-only, zero-behind, and has the current parent as the exact merge base. The semantic child delta is limited to exactly six artifact-origin-policy files:

  • CHANGELOG.md (+5/-0 relative to the current parent);
  • docs/doctoring/clearfolio-artifact-origin-trust.md;
  • package.json;
  • server/clearfolio.mjs;
  • tests/unit/clearfolio-artifact-origin.test.mjs;
  • tests/unit/clearfolio-status-signal.test.mjs.

No database, auth/session, attachment-refresh engine, dependency lockfile, workflow, scanner suppression, protected Microsoft Project XML behavior, contextual-orchestrator contract, or branch-protection artifact is changed by the final parent→child delta.

Reconciliation discipline

The parent advanced materially while retaining the same branch. This reconciliation therefore did not reuse the stale child tree. Commit 2954f1a6... was built from the exact current parent tree and overlaid only the six child-owned semantic paths. The shared CHANGELOG.md was merged explicitly so the parent’s Clearfolio transport entry, protected XML-import entry, and other parent history remain intact while adding only the artifact-origin policy entry.

Fresh comparison proves the current parent as merge base, zero commits behind, and exactly the intended six-file delta. Every later parent move requires the same parent-tree-first reconstruction and semantic shared-file merge before this child may advance.

Implemented contract

  • arbitrary cross-origin HTTPS artifact links fail by default;
  • CLEARFOLIO_ARTIFACT_ORIGINS, when present, accepts only comma-separated exact HTTPS origins without credentials, path, query, fragment, or empty entries;
  • malformed allowlist configuration fails closed with stable code clearfolio_artifact_origins_invalid before provider transport;
  • returned URLs reject credentials, fragments, insecure downgrade, and origins outside the provider origin plus explicit allowlist;
  • scheme, host, and effective port identity are compared through canonical URL.origin values;
  • same-origin artifactToken values may be translated into the trusted Clearfolio viewer route;
  • an explicitly allowlisted cross-origin token remains bound to its returned artifact origin and is never transplanted into the Clearfolio viewer origin;
  • invalid allowlist configuration, credentials, fragments, downgrade, default-deny, exact-port matching, same-origin token translation, and explicitly trusted cross-origin behavior have focused regressions; and
  • the existing downstream-contract regression remains aligned with the same source behavior.

Evidence boundary

All workflow/check/review evidence predating 2954f1a65491aae49e674bbe9a4c8d243ec54608 is historical and non-authorizing. Fresh exact-head repository and organization workflows are required. Pending, queued, skipped-required, neutral, absent, stale, predecessor, status-only, author-only, or model-only evidence is non-passing.

This PR remains Draft until the unchanged current head has terminal-success applicable checks, zero valid unresolved findings, and qualifying independent current-head approval under the live policy.

Remaining #489 work

This slice does not complete the Clearfolio production lifecycle. The dependent stack still owns capability readiness, broader persistence/lifecycle controls, incident and recovery evidence, and protected integration.

Merge gate

Do not integrate this child before #498 and #493. After the parent stack reaches protected develop, retarget or reconcile this bounded semantic diff against the resulting protected head, rerun all then-applicable repository and organization gates, and require qualifying independent current-head approval. No predecessor-head, author-only, status-only, synthetic-only, or model-only evidence transfers.

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0ba25932-c89c-4cf2-962a-c20e022b5706

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae
seonghobae marked this pull request as draft August 16, 2026 06:56

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review (2954f1a65491aae49e674bbe9a4c8d243ec54608)

The origin-trust core is sound. Canonical URL.origin comparison, fail-closed allowlist before fetch, and no cross-origin artifactToken transplant into /viewer/... all hold. I did not find an origin-comparison bypass or a default-open redirect.

This remains a Draft stacked on #498. Do not merge #504. Checks on this head were still queued at review time; pending/skipped/author-only evidence is non-passing.

Residual on this head

  1. Allowlist grammar is looser than the written “origin only” rule. #, empty userinfo, /foo/.., and :443 survive component checks after WHATWG normalization.
  2. Default-deny tests omit protocol-relative, backslash-normalized, and userinfo-as-host shapes.
  3. docs/deploy.md, docs/api.md, and the parent doctoring records still describe the pre-allowlist contract, so an operator cannot configure a reviewed CDN origin from the deploy table.

Landing vehicle

Those residuals are fixed on 0c193aa28e730e5c1a18e7d740637f34efbbd95d (cursor/bc-d8443854-d031-4b84-b085-56f439fcbf05-df45). Prefer that successor over this PR. Do not land both.

Next #489 slice (do not fold in)

Reject private, loopback, link-local, and metadata IP literals in both the allowlist and returned URLs, then constrain the attachment 302 so a trusted origin cannot become an arbitrary in-origin redirector.

I cannot merge from this automation. Independent current-head approval is still required after exact-head checks go green.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

await resolveArtifact('/signed/file.pdf', undefined),
'https://clearfolio.example/signed/file.pdf',
);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Default-deny currently covers only an absolute https://cdn.example URL and a same-origin relative path. The live URL parser also treats //evil.com/file.pdf, /\evil.com/file.pdf, and https://clearfolio.example@evil.com/x?artifactToken=t as foreign origins. Those shapes already fail here because url.origin is compared, but a later “relative means same-origin” shortcut would reopen the 302 without failing this suite.

Add those three cases next to this test. The successor on cursor/bc-d8443854-d031-4b84-b085-56f439fcbf05-df45 already does.

Comment thread server/clearfolio.mjs
'CLEARFOLIO_ARTIFACT_ORIGINS must contain only comma-separated HTTPS origins.',
);
}
trustedOrigins.add(url.origin);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After WHATWG normalization this accepts https://cdn.example#, https://:@cdn.example, https://cdn.example/foo/.., and https://cdn.example:443 even though the doctoring record says “origin only.” Compare the trimmed entry to url.origin or ${url.origin}/ before adding it to the set, and add empty/whitespace env values to the fail-closed-before-transport list. Platforms that inject an empty CLEARFOLIO_ARTIFACT_ORIGINS already fail closed; the suite should prove it.

Copy link
Copy Markdown
Contributor Author

Superseded after fresh exact-head comparison. PR #538 head 0c193aa28e730e5c1a18e7d740637f34efbbd95d is an exact descendant of this PR's head 2954f1a65491aae49e674bbe9a4c8d243ec54608 (ahead_by=1, behind_by=0, merge base = this head) and retains this origin-trust implementation while adding stricter canonical allowlist validation plus operator/deploy/API documentation. Keeping both open would duplicate the same production boundary. Unique work from #504 is preserved in #538; no #504-only semantic delta is being discarded.

@seonghobae seonghobae closed this Aug 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant