Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 37 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Bug report
description: Something in FlexDoc does not behave as documented.
title: "fix: "
labels: []
body:
- type: markdown
attributes:
value: |
Security vulnerabilities belong in a [private advisory](https://github.com/Prauga/flexdoc/security/advisories/new), not in this form.
- type: textarea
id: summary
attributes:
label: What happened
description: What you did, what you expected, and what you observed.
validations:
required: true
- type: input
id: version
attributes:
label: Package and version
description: For example, @prauga/flexdoc-client 3.4.0, or prauga-flexdoc 0.9.0 on Python.
placeholder: "@prauga/flexdoc-client 3.4.0"
validations:
required: true
- type: input
id: runtime
attributes:
label: Adapter and runtime
description: The framework integration and runtime version, if the bug is not in the standalone renderer.
placeholder: "Express on Node 22, or Spring Boot 3.4"
- type: textarea
id: reproduce
attributes:
label: Smallest reproduction
description: A spec fragment, configuration, or steps that show the failure. Leave out secrets, tokens, and cookie values.
validations:
required: true
5 changes: 5 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Security vulnerability
url: https://github.com/Prauga/flexdoc/security/advisories/new
about: Report a vulnerability in private. Do not open a public issue for it.
32 changes: 32 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Feature request
description: A capability FlexDoc should have, and who it is for.
title: "feat: "
body:
- type: textarea
id: problem
attributes:
label: Problem
description: The job you cannot do today, and why the current behavior is not enough.
validations:
required: true
- type: textarea
id: proposal
attributes:
label: Proposal
description: What should change, and which adapter or surface it belongs on.
validations:
required: true
- type: dropdown
id: surface
attributes:
label: Surface
options:
- Renderer / API reference
- API Client
- Host execution
- Framework adapter
- CLI / CI
- Documentation
- Other
validations:
required: true
9 changes: 9 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Summary

## Test plan

- [ ] Tests covering the change pass
- [ ] Client changes include a rebuilt standalone renderer and synchronized adapter assets
- [ ] No secrets, tokens, or cookie values are added to docs, tests, or commit messages

Commits include a `Signed-off-by` line (`git commit -s`).
84 changes: 84 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@

# Contributor Covenant Code of Conduct

## Our Pledge

We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.

We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.

## Our Standards

Examples of behavior that contributes to a positive environment for our community include:

* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall community

Examples of unacceptable behavior include:

* The use of sexualized language or imagery, and sexual attention or advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting

## Enforcement Responsibilities

Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.

Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.

## Scope

This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.

## Enforcement

Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at the repository owner through <https://github.com/bluejeans117>. Security vulnerabilities are not conduct reports; report those through the repository's private security process. All complaints will be reviewed and investigated promptly and fairly.

All community leaders are obligated to respect the privacy and security of the reporter of any incident.

## Enforcement Guidelines

Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:

### 1. Correction

**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.

**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.

### 2. Warning

**Community Impact**: A violation through a single incident or series of actions.

**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.

### 3. Temporary Ban

**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.

**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.

### 4. Permanent Ban

**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.

**Consequence**: A permanent ban from any sort of public interaction within the community.

## Attribution

This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].

Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].

For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].

[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ Adapters serve version-matched local renderer assets and do not reimplement sche

See [`examples/`](./examples/README.md), [`docs/host-execution.md`](./docs/host-execution.md), [`docs/releases/3.3.md`](./docs/releases/3.3.md), and [`docs/distribution.md`](./docs/distribution.md).

## Community

FlexDoc follows the [Contributor Covenant](./CODE_OF_CONDUCT.md). Bug reports and feature requests use the issue forms. Report security vulnerabilities through a [private advisory](https://github.com/Prauga/flexdoc/security/advisories/new), not a public issue.

## License

FlexDoc is licensed under **Apache-2.0**. See [LICENSE](./LICENSE).
Expand Down
122 changes: 61 additions & 61 deletions adapters/elixir/assets/flexdoc.standalone.js

Large diffs are not rendered by default.

122 changes: 61 additions & 61 deletions adapters/go/assets/flexdoc.standalone.js

Large diffs are not rendered by default.

122 changes: 61 additions & 61 deletions adapters/php/assets/flexdoc.standalone.js

Large diffs are not rendered by default.

122 changes: 61 additions & 61 deletions adapters/python/src/prauga_flexdoc/_assets/flexdoc.standalone.js

Large diffs are not rendered by default.

122 changes: 61 additions & 61 deletions adapters/ruby/assets/flexdoc.standalone.js

Large diffs are not rendered by default.

122 changes: 61 additions & 61 deletions adapters/rust-actix/assets/flexdoc.standalone.js

Large diffs are not rendered by default.

122 changes: 61 additions & 61 deletions adapters/rust/assets/flexdoc.standalone.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/host-execution-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ flexdoc(app, {

FlexDoc defines the interface; the application supplies the implementation it already runs. No Redis or database dependency enters the package, and the default stays the in-process store, so a single-instance deployment needs no configuration and gains no new failure mode.

A shared jar store contains third-party session cookies and must be operated as credential storage: keep entries short-lived, use the backing store's normal encryption-at-rest controls, and never log serialized cookie values. The application should not copy jar contents into metrics, traces, request logs, or support dumps. When a workspace credential scope moves to `never` or credentials are cleared, call the host cookies clear route so the shared store does not outlive the user's revocation.
A shared jar store contains third-party session cookies and must be operated as credential storage: keep entries short-lived, use the backing store's normal encryption-at-rest controls, and never log serialized cookie values. The application should not copy jar contents into metrics, traces, request logs, or support dumps. Changing the API Client credential-storage scope (`session`, `remember`, or `never`) clears the documentation session's jar through the host cookies route and drops the current request's session-jar selection. The clear sends only the execution marker and same-origin documentation credentials. If it cannot be confirmed, the scope change still stands and the client shows a warning that contains no cookie values.

`sessionSecret` must be at least 32 bytes and is rejected at startup otherwise. A weak shared secret is worse than the random default: it looks like multi-instance support while making session cookies forgeable.

Expand Down
2 changes: 1 addition & 1 deletion docs/multi-instance-deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ Two deliberate choices in that store. It expires jars rather than keeping them f

Treat the shared store as a credential store. Use the application's normal encryption-at-rest controls for the backing database/cache, never log serialized jar values, and keep the TTL short enough for the documentation workflow rather than treating these keys as durable login state.

Client credential scopes (`session` / `remember` / `never`) and the server-side jar are separate stores, but they answer the same user intent. When a workspace moves to `never` or the operator clears credentials, call the host cookies clear route so the shared store does not retain jars the user has asked not to keep. TTL alone is not enough for that case: an hour of retained session cookies can outlive a scope the user just revoked.
Client credential scopes (`session` / `remember` / `never`) and the server-side jar are separate stores, but they answer the same user intent. Any change between those scopes clears the documentation session's jar through the host cookies route, so a shared store does not keep cookies the user just stopped treating as durable. TTL alone is not enough for that case: an hour of retained session cookies can outlive a scope the user just changed.

`FLEXDOC_INSTANCES` has to match the real replica count. If it drifts low the fleet admits more than the budget; if it drifts high each instance admits less than its share. Where the orchestrator can tell the application its replica count, read it from there rather than from a second source of truth.

Expand Down
4 changes: 2 additions & 2 deletions docs/releases/3.3.1.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# FlexDoc 3.3.1

> Unreleased maintenance release.
Published maintenance release. The Java **0.9.0** and ASP.NET Core **0.6.0** artifacts named below are on their registries.

FlexDoc 3.3.1 hardens backend-native host execution without changing the 3.3 transport contract. The primary change is HX-03 protection parity: runtimes that can expose a real host executor now require an explicit acknowledgement that the application already protects the documentation and execute surface with its own authentication/authorization boundary.

Expand All @@ -11,7 +11,7 @@ The following source releases carry the new fail-closed acknowledgement:
- Java JVM / JAX-RS / Spring family: **0.9.0**
- ASP.NET Core: **0.6.0**

The minor bumps are intentional because the adapters add a new required configuration acknowledgement when a real host executor is exposed. These source versions do not imply that registry artifacts are public before their release workflows complete.
The minor bumps were intentional because the adapters add a new required configuration acknowledgement when a real host executor is exposed. Both artifacts published with this release.

### Java

Expand Down
4 changes: 2 additions & 2 deletions docs/releases/3.4.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,6 @@ All in-repository callers already run inside async handlers and await the result

## Release sequencing

The repository version contract intentionally keeps examples and install documentation pinned to the currently published artifacts while this wave is in flight. Source versions are recorded in `pending`; after the corresponding registry artifacts exist, the follow-up repin updates examples, generated lock/checksum metadata and the published baselines, then clears `pending`.
This wave used the version contract's in-flight window. Examples and install documentation stayed pinned to the previously published artifacts while source versions sat in `pending`. After the registry artifacts existed, the follow-up repin updated examples, generated lock and checksum metadata and the published baselines, then cleared `pending`. That window is closed for 3.4.0.

For Rust, publish `rust-host/v0.2.0` before the Axum and Actix releases because both adapter crates depend on that shared crate.
For Rust, `rust-host/v0.2.0` published before the Axum and Actix releases because both adapter crates depend on that shared crate. The same order applies to any later release of those crates.
95 changes: 95 additions & 0 deletions packages/client/src/components/ApiClient.credential-scope.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// The repo-wide react manual mock returns inert state setters.
jest.unmock('react');

import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
import { useState } from 'react';
import { ApiClient } from './ApiClient';
import { API_HOST_COOKIE_JAR_CLEAR_WARNING } from '../utils/api-client-credential-scope';
import type { ApiClientCredentialStorage } from '../utils/api-client-credentials';
import type { FlexDocHostExecutionPublicOptions } from '../types/options';
import type { HttpRequestDraft } from '../utils/http-client';

const fetchMock = jest.fn();

const secretRequest: Partial<HttpRequestDraft> = {
method: 'POST',
url: 'https://internal.example.test/admin',
headers: [{ key: 'Authorization', value: 'Bearer secret-token' }],
body: '{"password":"hunter2"}',
hostExecution: { cookieJar: 'session' },
};

const scopeChanges: ApiClientCredentialStorage[] = [];

function Harness({ hostExecution }: { hostExecution?: FlexDocHostExecutionPublicOptions }) {
const [storage, setStorage] = useState<ApiClientCredentialStorage>('remember');
return (
<ApiClient
initialRequest={secretRequest}
initialRequestTab='authorization'
credentialStorage={storage}
onCredentialStorageChange={(next) => { scopeChanges.push(next); setStorage(next); }}
hostExecution={hostExecution}
/>
);
}

function changeScope(value: ApiClientCredentialStorage) {
return userEvent.selectOptions(screen.getByLabelText('Credential storage'), value);
}

describe('API Client credential-scope cookie jar eviction', () => {
beforeEach(() => {
scopeChanges.length = 0;
fetchMock.mockReset();
Object.defineProperty(globalThis, 'fetch', { value: fetchMock, configurable: true, writable: true });
});

it('sends one marker-only clear and drops the jar selection when the host advertises cookies', async () => {
fetchMock.mockResolvedValue({ ok: true, status: 200, text: async () => '{"cookies":[{"name":"sid"}]}' });
render(<Harness hostExecution={{ available: true, endpoint: '/docs/__flexdoc/execute', cookiesEndpoint: '/docs/__flexdoc/cookies', capabilities: ['cookies'] }} />);

expect(screen.getByLabelText('Use API host cookie jar')).toBeChecked();
await changeScope('never');
expect(scopeChanges).toEqual(['never']);

await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(1));
expect(fetchMock).toHaveBeenCalledWith('/docs/__flexdoc/cookies', {
method: 'DELETE',
credentials: 'same-origin',
headers: { 'X-FlexDoc-Execute': '1' },
});
const init = fetchMock.mock.calls[0][1];
expect(init.body).toBeUndefined();
expect(JSON.stringify(init)).not.toMatch(/secret-token|hunter2|internal\.example/);
expect(screen.getByLabelText('Credential storage')).toHaveValue('never');
expect(screen.getByLabelText('Use API host cookie jar')).not.toBeChecked();
expect(screen.queryByRole('status', { name: 'API-host cookie jar' })).not.toBeInTheDocument();
});

it.each([
['no cookie capability', { available: true, endpoint: '/docs/__flexdoc/execute', cookiesEndpoint: '/docs/__flexdoc/cookies', capabilities: [] as const }],
['no cookie endpoint', { available: true, endpoint: '/docs/__flexdoc/execute', capabilities: ['cookies'] as const }],
])('does not clear when the host has %s', async (_label, hostExecution) => {
render(<Harness hostExecution={hostExecution} />);
await changeScope('session');
await waitFor(() => expect(screen.getByLabelText('Credential storage')).toHaveValue('session'));
expect(fetchMock).not.toHaveBeenCalled();
});

it('keeps the new scope and shows bounded guidance when the clear fails', async () => {
fetchMock.mockRejectedValue(new Error('cookie=sid; secret-token'));
render(<Harness hostExecution={{ available: true, endpoint: '/docs/__flexdoc/execute', cookiesEndpoint: '/docs/__flexdoc/cookies', capabilities: ['cookies'] }} />);

await changeScope('session');

const warning = await screen.findByRole('status', { name: 'API-host cookie jar' });
expect(warning).toHaveTextContent(API_HOST_COOKIE_JAR_CLEAR_WARNING);
expect(warning).not.toHaveTextContent('secret-token');
expect(warning).not.toHaveTextContent('sid');
expect(screen.getByLabelText('Credential storage')).toHaveValue('session');
expect(screen.getByLabelText('Use API host cookie jar')).not.toBeChecked();
});
});
Loading
Loading