Skip to content

fix(security): align repository dot-segment contract - #399

Closed
cursor[bot] wants to merge 9 commits into
mainfrom
cursor/bc-8b9a4b3b-b31c-4087-bed6-5571fc641bdf-80be
Closed

fix(security): align repository dot-segment contract#399
cursor[bot] wants to merge 9 commits into
mainfrom
cursor/bc-8b9a4b3b-b31c-4087-bed6-5571fc641bdf-80be

Conversation

@cursor

@cursor cursor Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

Purpose

Supersede draft #397 at 3d4f87b97d77593df0daae6f1b6f6617a1bdd008 with the same credential-helper coverage slice plus the buyer-facing contract that slice omitted.

#397 correctly measures validateRepositoryName and parseExchangeRequestBody, and it rejects ContextualWisdomLab/.. before GitHub App work. That head still published an OpenAPI/docs/api-spec.md pattern that accepts owner/.., left CHANGELOG.md Unreleased silent, stripped the src/index.ts trailing newline, and checked only the name segment.

What to do next

Repair

  • Reject owner and name segments that are exactly . or .. as 400 ERR_VALIDATION_INPUT before GitHub App private-key use. .github remains valid.
  • Publish the same rule in openapi.json, docs/api-spec.md, docs/api-stability-contract.md, and CHANGELOG.md.
  • Restore the src/index.ts trailing newline.
  • Add realistic cases for ContextualWisdomLab/., ../noema, and ./noema with zero api.github.com egress.

Verification

Local Node 24.19.0: npm run typecheck and npm test — 1515 tests, 100% statements/branches/functions/lines.

Related: #84, #397.

Open in Web View Automation 

Cite RFC 3986 path normalization and keep the public API docs pointing
integrators at the fail-closed `.` / `..` rule before they send
target_repository.

Co-authored-by: Seongho Bae <seonghobae@users.noreply.github.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Draft #399 at 8a0abec lands the fail-closed . / .. check before GitHub App private-key use. That slice is correct and preferred over draft #397.

Do not merge this head. The published OpenAPI still uses PCRE lookaheads, the contract test only string-compares the pattern, .github / encoding / slash / Unicode / backslash cases are missing, and doctoring still says URL interpolation happens before key import.

Those buyer-facing gaps are repaired on successor #400 (0caea38). Review and merge #400 after exact-head required checks and an independent human review. Keep #397 closed. Keep #84 open — remaining credential/OIDC/GitHub App exclusions stay uncounted.

CodeRabbit CLI 0.7.3 is installed here but coderabbit auth login --agent timed out on the browser callback, so this assessment is from the designated code-reviewer plus local Node 24.19.0 typecheck / test (1525 tests, 100% coverage) on the successor. That missing CLI login is not a merge blocker for #400.

Open in Web View Automation 

Sent by Cursor Automation: Fix Issues

Comment thread openapi.json
"type": "string",
"pattern": "^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$",
"description": "Authorized owner/repository target."
"pattern": "^(?!\\.{1,2}/)[A-Za-z0-9_.-]+/(?!\\.{1,2}$)[A-Za-z0-9_.-]+$",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This lookahead is not RE2-portable. Buyer tooling that compiles OpenAPI pattern with RE2 will fail or drop the rule, so owner/.. looks legal again.

Use the shared RepositoryLocator allOf / not schema on successor #400. Do not merge this pattern.

expect(
exchange.requestBody.content["application/json"].schema.properties.target_repository.pattern,
).toBe("^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$");
).toBe("^(?!\\.{1,2}/)[A-Za-z0-9_.-]+/(?!\\.{1,2}$)[A-Za-z0-9_.-]+$");

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This only string-compares the published pattern. Doctoring says the contract must reject . / .. and accept .github by executing the schema.

Successor #400 runs the RE2-safe schema against those realistic values. Do not treat this assertion as proof the public contract works.


## Decision

`/exchange` accepts an optional `target_repository` string in `owner/name` form. Noema interpolates that string into GitHub REST paths such as `/repos/${repository}/installation` before any GitHub App private key is imported. A caller who sends `ContextualWisdomLab/..` or `../noema` would otherwise produce a URL whose `.` / `..` segments are removed during generic URI resolution and no longer name the intended repository.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Recorded order is wrong. Runtime validates the locator, then imports the GitHub App private key, then interpolates /repos/${repository}/installation.

Successor #400 corrects this sequence so an integrator does not think a traversal string can reach URL construction before the 400.

["name current segment", "ContextualWisdomLab/."],
["owner parent segment", "../noema"],
["owner current segment", "./noema"],
])("rejects repository URL %s before GitHub App credential work", async (_label, targetRepository) => {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These four cases miss the buyer-facing vectors: ContextualWisdomLab/.github as a legal target, %2e%2e / extra slashes / backslash / Unicode dots, and a PKCS#8 importKey spy.

Successor #400 adds those cases and proves key import happens only after validation. Keep this draft closed once #400 is the carrier.

Copy link
Copy Markdown
Contributor

Superseded by #400. Exact ancestry is preserved: #399 head 8a0abeca906ab91b9bb0c6e86d4139403f2b9bdb#400 head 0caea38148f937dc155a218c909475ac50ddeb31 is ahead 1 / behind 0. #400 carries all #399 fail-closed dot-segment behavior plus the RE2-safe OpenAPI/JSON-Schema contract and executable schema regressions. Closing this predecessor avoids competing ownership; no unique #399 delta is discarded.

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.

2 participants