Skip to content

test(agent-bff): pin the OpenAPI mount point under /agent and cover mode 2 - #1827

Merged
nbouliol merged 1 commit into
mainfrom
feature/prd-686-verify-the-openapi-document-is-never-reachable
Aug 14, 2026
Merged

test(agent-bff): pin the OpenAPI mount point under /agent and cover mode 2#1827
nbouliol merged 1 commit into
mainfrom
feature/prd-686-verify-the-openapi-document-is-never-reachable

Conversation

@nbouliol

@nbouliolnbouliol commented Aug 14, 2026

Copy link
Copy Markdown
Member

Why

The BFF has no deny-by-default gate: auth applies only to paths under /agent, and /health is a hardcoded public exact-match ahead of the chain. GET /agent/openapi.json is therefore gated by construction, not by a mechanism — which means the next route added elsewhere could serve the document unauthenticated, silently. An unauthenticated OpenAPI document hands over the full API surface (collections, fields, filter operators, actions) — OWASP API8:2023.

PRD-886 already covers 401-without-credential, 200-with-a-session-token and 404-at-the-root. This PR adds what unit tests did not yet pin: the invariant itself, and the Mode 2 path.

What

test/openapi/openapi-mount-invariant.test.ts (static, no server boot):

  • OPENAPI_PATH must start with /agent/.
  • No file in src/ outside src/openapi/ may import src/openapi/*, except the known mount points: cli-core.ts -> openapi-routes, unfolded-document, and cli-dispatch.ts -> openapi-document, unfolded-document, unfolding. Asserted as an exact map, so any new importer fails — the first CI run on this branch demonstrated it by catching the importers PRD-684 added on main.
  • src/index.ts exports nothing matching /openapi/i, so a package consumer cannot mount the document itself.

test/openapi/openapi-routes.test.ts: the missing Mode 2 coverage — a valid API key gets 200 and the very same unfolded document a session caller gets, and 404 openapi_disabled with a valid API key when BFF_OPENAPI_ENABLED=false.

Test-only. No production code touched.

Verification

  • yarn workspace @forestadmin/agent-bff test — 1084 tests green; lint clean.
  • Mutation-tested: moving OPENAPI_PATH to the root, adding an openapi-document import in another module, and exporting createOpenApiRoutes from index.ts each fail the suite. The Mode 2 cases fail if key resolution fails, so the 200 does not pass for the wrong reason.
  • Manual, local BFF against a real Forest server: no credential 401, Mode 1 session token 200, Mode 2 API key 200, root /openapi.json 404. Logged on the ticket.

Out of scope

A generic deny-by-default gate for the whole BFF, and pinning its public HTTP surface. Recorded as still-open in a ticket comment.

Fixes PRD-686

🤖 Generated with Claude Code

@linear-code

Copy link
Copy Markdown

PRD-686

@qltysh

qltyshBot commented Aug 14, 2026

Copy link
Copy Markdown

Qlty


Coverage Impact

This PR will not change total coverage.

🚦 See full report on Qlty Cloud »

🛟 Help
  • Diff Coverage: Coverage for added or modified lines of code (excludes deleted files). Learn more.

  • Total Coverage: Coverage for the whole repository, calculated as the sum of all File Coverage. Learn more.

  • File Coverage: Covered Lines divided by Covered Lines plus Missed Lines. (Excludes non-executable lines including blank lines and comments.)

    • Indirect Changes: Changes to File Coverage for files that were not modified in this PR. Learn more.

@nbouliol
nbouliolforce-pushed the feature/prd-686-verify-the-openapi-document-is-never-reachable branch from 2eab2d9 to 1f5e04dCompareAugust 14, 2026 12:46
…ode 2
The BFF has no deny-by-default gate: auth only applies under /agent, so a
route mounted elsewhere would serve the document unauthenticated. Pin the
invariant statically (served path, importers of src/openapi, public
surface) and add the missing api key coverage on the route.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nbouliol
nbouliolforce-pushed the feature/prd-686-verify-the-openapi-document-is-never-reachable branch from 1f5e04d to 413ec62CompareAugust 14, 2026 12:57
function relativeImportsOf(file: string): string[] {
const source = readFileSync(path.join(SRC_DIR, file), 'utf8');

return [...source.matchAll(/from '(\.[^']*)'/g)].map(match =>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The scanner only matches static single-quoted from '...' imports, so await import('../openapi/openapi-document') slips through unflagged and nothing in the lint config forbids it. What about widening the regex to /(?:from|import\s*\(|require\s*\()\s*'(\.[^']*)'/g?


describe('when the package public surface is read', () => {
it('should expose nothing OpenAPI-related, since a consumer could mount it off /agent', () => {
expect(Object.keys(publicApi).filter(name => /openapi/i.test(name))).toEqual([]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This pins the main entry only: with no exports map in package.json and files: dist/**/*.js, a consumer can still deep-import @forestadmin/agent-bff/dist/openapi/openapi-document and serve the document ungated. Adding "exports": { ".": "./dist/index.js" } in a follow-up would close it, or we narrow the test name to the main entry.

@TonoursTonours left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Verified the mutation-testing claim locally: adding an openapi import outside the allowlist fails the invariant and names the file, and moving OPENAPI_PATH to the root breaks 18 tests. Two non-blocking suggestions inline on the scanner regex and the deep-import surface.

@nbouliol
nbouliol merged commit a41fc44 into mainAug 14, 2026
39 of 61 checks passed
@nbouliol
nbouliol deleted the feature/prd-686-verify-the-openapi-document-is-never-reachable branch August 14, 2026 14:21
Sign up for freeto 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

@nbouliol@Tonours