Skip to content

feat: add marketing site (landing + docs)#5

Merged
nicholasadamou merged 2 commits into
mainfrom
feat/marketing-site
Jun 25, 2026
Merged

feat: add marketing site (landing + docs)#5
nicholasadamou merged 2 commits into
mainfrom
feat/marketing-site

Conversation

@nicholasadamou

@nicholasadamou nicholasadamou commented Jun 25, 2026

Copy link
Copy Markdown
Member

Summary

A standalone marketing site for Airlock under site/, modeled on fugue/site — a Vite + React + TypeScript multi-page app (landing + docs), terminal/dark theme, all copy centralized in src/data.ts.

It is standalone: it lives in site/ and is intentionally outside the Bun monorepo workspace (apps/*, packages/*), so it does not affect make check, typecheck, or build. Use npm inside site/.

Landing page (App.tsx)

  • Hero, Every trace surface, closed table, Two ways in (dashboard vs. extension), allowed/denied grants, a session-lifecycle terminal, the seven-browser grid, Configure to taste, an interactive session-request builder, a deploy matrix, get-started, and a security callout.

Docs page (Docs.tsx)

  • Sidebar nav: Overview, Install, Quickstart, Two ways in, API reference, Browsers, Configuration, Security, Deployment, and links to the repo docs.

Details

  • Browser logos via simple-icons (Chrome, Firefox, Brave, Vivaldi, Tor); Chromium reuses the Chrome mark tinted blue-grey; the Edge mark is bundled (CC0, pre-removal) since Microsoft's icons were pulled upstream.
  • One reusable Dropdown component backs every dropdown in the builder (browser dropdown shows logos; TTL is text-only, same styling/behavior).
  • Cyan "airlock porthole" palette + favicon to give it its own identity vs. fugue's amber.

All facts (endpoints, env defaults, image tags, security posture) are sourced from docs/.

Test plan

  • cd site && npm install && npm run buildtsc + vite build pass clean, both pages emit.
  • npm run preview — both / and /docs serve.

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features

    • Added a polished marketing site with a landing page and a separate documentation page.
    • Introduced an interactive session builder that generates copyable request commands and session summaries.
    • Added reusable UI elements like dropdowns, code blocks with copy support, and browser icons.
  • Documentation

    • Added a full README with setup, build, deployment, and site structure guidance.
  • Chores

    • Added project and deployment configuration for a smoother local build and hosting experience.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@nicholasadamou, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 47 minutes and 14 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 33ea7006-9920-4f6a-b3a2-f54cdb14d4a6

📥 Commits

Reviewing files that changed from the base of the PR and between e840cd5 and dffe2b2.

📒 Files selected for processing (11)
  • site/src/App.tsx
  • site/src/BrowserIcon.tsx
  • site/src/Code.tsx
  • site/src/Docs.tsx
  • site/src/Dropdown.tsx
  • site/src/SessionBuilder.tsx
  • site/src/data.ts
  • site/src/docs-main.tsx
  • site/src/index.css
  • site/src/main.tsx
  • site/vite.config.ts
📝 Walkthrough

Walkthrough

Adds a multi-page Vite React site under site/ with landing and docs entrypoints, shared content data, reusable UI primitives, a session request builder, page layouts, styles, and deployment/build configuration.

Changes

Airlock site buildout

Layer / File(s) Summary
Project scaffold and entrypoints
site/package.json, site/tsconfig.json, site/vite.config.ts, site/index.html, site/docs.html, site/src/main.tsx, site/src/docs-main.tsx, site/vercel.json, site/.gitignore, site/README.md
Adds the site manifest, TypeScript/Vite/Vercel settings, HTML shells, React bootstraps, ignore rules, and README.
Site data model
site/src/data.ts
Centralizes browser, trace, entry, grant, config, install, proof, provider, and TTL data.
Reusable UI primitives
site/src/BrowserIcon.tsx, site/src/Code.tsx, site/src/Dropdown.tsx
Adds BrowserIcon, Code, and Dropdown for browser badges, copyable code blocks, and option selection.
Session builder
site/src/SessionBuilder.tsx
Builds the session request form, copyable curl/output text, and session preview/warning states.
Landing page sections
site/src/App.tsx
Implements the landing page sections and composes SessionBuilder into the page.
Docs page
site/src/Docs.tsx
Implements the docs page navigation, reference sections, and footer.
Site styles
site/src/index.css
Adds the shared visual system for layout, components, docs, and responsive behavior.

Sequence Diagram(s)

sequenceDiagram
  participant SessionBuilder
  participant Dropdown
  participant MiniCopy
  participant ClipboardAPI
  SessionBuilder->>Dropdown: render browser and TTL selectors
  Dropdown-->>SessionBuilder: onChange(value)
  SessionBuilder->>MiniCopy: render copy controls
  MiniCopy->>ClipboardAPI: writeText(text)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I hopped through the site with a CSS twirl,
New docs and a landing page gave me a whirl.
I copied my curl with a nose-twitching grin,
Then bounced to the browser where the sessions begin.
🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a standalone marketing site with landing and docs pages.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/marketing-site

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.

@coderabbitai coderabbitai 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.

Actionable comments posted: 10

🧹 Nitpick comments (1)
site/src/Docs.tsx (1)

18-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move these docs content tables into site/src/data.ts.

This page already consumes shared copy from site/src/data.ts, but these five arrays create a second source of truth for docs content. Keeping them centralized with BROWSERS, ENTRIES, INSTALL, REPO, and USAGE will reduce drift across the landing page and docs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/src/Docs.tsx` around lines 18 - 71, The docs content tables in Docs.tsx
are a duplicate source of truth and should be centralized in site/src/data.ts
alongside BROWSERS, ENTRIES, INSTALL, REPO, and USAGE. Move ENDPOINTS,
BODY_FIELDS, ENV_API, ENV_WORKER, and REPO_DOCS into the shared data module,
export them there, and update Docs.tsx to consume the imported values so all
docs copy comes from one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@site/README.md`:
- Around line 5-7: Update the README app description to reflect the current
implementation in the site entrypoint and BrowserIcon component. Remove or
revise the “single-page” and “no runtime dependencies beyond React” claims in
the README wording, since the docs entrypoint and site/src/BrowserIcon.tsx now
rely on runtime imports such as simple-icons. Keep the description aligned with
the actual app structure and dependencies so the README matches the codebase.
- Around line 27-45: Clarify the local preview behavior for the docs page in
site/README.md by stating that Vercel’s cleanUrls rewrite only applies in
production, while the Vite preview server serves the built file directly. Update
the docs-page instructions near the build/deploy section so users open the docs
locally at /docs.html instead of /docs, referencing the docs page and the Vercel
preview flow to keep the guidance accurate.

In `@site/src/Code.tsx`:
- Around line 12-17: The clipboard click handler in Code should be made safe for
unsupported or rejected writes: replace the chained .then() on
navigator.clipboard?.writeText() with an async onClick handler in the Code
component, await the writeText call only when the Clipboard API exists, and wrap
it in try-catch so missing clipboard support or permission failures do not throw
or create unhandled rejections. Keep the copied state update and reset logic
inside the success path, and use the existing Code symbol to locate the handler.

In `@site/src/docs-main.tsx`:
- Around line 1-10: This file is failing prettier checks, so update the
formatting in the docs entrypoint to match the repo style. Re-run prettier on
the docs-main.tsx module and verify the rendered createRoot/StrictMode/Docs
block and import layout are formatted exactly as expected before merging.

In `@site/src/Docs.tsx`:
- Around line 39-51: The Docs.tsx formatting is out of sync with Prettier,
causing the check to fail. Reformat the affected sections, including the ENV_API
array and the other referenced JSX blocks, by running Prettier so the long
string literals and JSX layout match the project style. Keep the content
unchanged and let the formatter rewrite the file consistently.

In `@site/src/Dropdown.tsx`:
- Around line 65-104: The Dropdown trigger/button is missing the ARIA wiring
needed to expose the listbox state and label association. Update the Dropdown
component to accept an id and/or labelId prop, then use it in the button and
listbox so the trigger sets aria-controls and aria-labelledby, and reflects the
current keyboard focus with a dynamic aria-activedescendant tied to the active
option. Make sure the list items in Dropdown map to stable option ids, and
update the SessionBuilder uses (“Browser”, “Lifetime”) to pass the new label/id
props so the wrapping label is programmatically associated with the button.

In `@site/src/main.tsx`:
- Around line 1-10: The formatting in the main entrypoint is failing Prettier
checks, so update the `createRoot` render block in `main.tsx` to match the
repository’s Prettier style by running the formatter and confirming the result
passes `prettier --check`. Focus on the imports and the
`createRoot(...).render(...)` call so the file’s formatting is fully normalized
before merge.

In `@site/src/SessionBuilder.tsx`:
- Around line 148-155: The Bearer token input in SessionBuilder should not be
rendered as plain text because it exposes AIRLOCK_API_TOKEN on-screen and to
browser helpers. Update the token field in SessionBuilder.tsx to use a
secret-style input appropriate for API credentials, while keeping the existing
state handling via setToken and the current placeholder/label behavior.
- Around line 60-70: The generated curl command in SessionBuilder’s useMemo
block is injecting raw baseUrl, targetUrl, and token directly into shell syntax,
which can break quoting or allow shell injection. Update the curl construction
to shell-escape or safely quote every interpolated value used in the request
string, including the URL, Authorization header value, and JSON payload contents
derived from targetUrl so the output remains valid for arbitrary input.

In `@site/vite.config.ts`:
- Around line 1-16: The Vite config in defineConfig is failing prettier check,
so normalize the formatting in the exported object and imports to match the
repo’s Prettier rules. Update the site/vite.config.ts content around
defineConfig, build, and rollupOptions so it is formatted exactly as prettier
--write would produce, then re-run the formatting validation to confirm the
warning is gone.

---

Nitpick comments:
In `@site/src/Docs.tsx`:
- Around line 18-71: The docs content tables in Docs.tsx are a duplicate source
of truth and should be centralized in site/src/data.ts alongside BROWSERS,
ENTRIES, INSTALL, REPO, and USAGE. Move ENDPOINTS, BODY_FIELDS, ENV_API,
ENV_WORKER, and REPO_DOCS into the shared data module, export them there, and
update Docs.tsx to consume the imported values so all docs copy comes from one
place.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 22479fa4-144c-4cbf-be85-25d717ab7ab3

📥 Commits

Reviewing files that changed from the base of the PR and between 2768a64 and e840cd5.

⛔ Files ignored due to path filters (2)
  • site/package-lock.json is excluded by !**/package-lock.json
  • site/public/favicon.svg is excluded by !**/*.svg
📒 Files selected for processing (18)
  • site/.gitignore
  • site/README.md
  • site/docs.html
  • site/index.html
  • site/package.json
  • site/src/App.tsx
  • site/src/BrowserIcon.tsx
  • site/src/Code.tsx
  • site/src/Docs.tsx
  • site/src/Dropdown.tsx
  • site/src/SessionBuilder.tsx
  • site/src/data.ts
  • site/src/docs-main.tsx
  • site/src/index.css
  • site/src/main.tsx
  • site/tsconfig.json
  • site/vercel.json
  • site/vite.config.ts

Comment thread site/README.md
Comment on lines +5 to +7
The marketing site for [Airlock](https://github.com/dotbrains/airlock) — a
single-page Vite + React + TypeScript app, terminal/dark theme, no runtime
dependencies beyond React.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update the app description to match the implementation.

This README still says the site is “single-page” and has no runtime dependencies beyond React, but this PR adds a docs entrypoint and site/src/BrowserIcon.tsx imports simple-icons at runtime. The onboarding docs will be wrong as soon as this lands.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/README.md` around lines 5 - 7, Update the README app description to
reflect the current implementation in the site entrypoint and BrowserIcon
component. Remove or revise the “single-page” and “no runtime dependencies
beyond React” claims in the README wording, since the docs entrypoint and
site/src/BrowserIcon.tsx now rely on runtime imports such as simple-icons. Keep
the description aligned with the actual app structure and dependencies so the
README matches the codebase.

Comment thread site/README.md
Comment on lines +27 to +45
The build is multi-page: the landing page (`index.html`) and the docs page
(`docs.html`).

## Deploy to Vercel

This app lives in the `site/` subdirectory, so point Vercel at that directory:

1. In Vercel, **New Project** → import `dotbrains/airlock`.
2. Set **Root Directory** to `site`.
3. Framework preset auto-detects **Vite** (build `npm run build`, output `dist`).
4. Deploy.

Or from the CLI:

```sh
cd site
npx vercel # first run links/creates the project
npx vercel --prod # promote to production
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor

Ensure the local preview URL for the docs page is /docs.html.

The vercel.json configuration uses cleanUrls to serve docs.html at /docs in production, but npm run preview serves the static build artifacts without URL rewriting. Access the docs locally via http://localhost:4173/docs.html rather than /docs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/README.md` around lines 27 - 45, Clarify the local preview behavior for
the docs page in site/README.md by stating that Vercel’s cleanUrls rewrite only
applies in production, while the Vite preview server serves the built file
directly. Update the docs-page instructions near the build/deploy section so
users open the docs locally at /docs.html instead of /docs, referencing the docs
page and the Vercel preview flow to keep the guidance accurate.

Comment thread site/src/Code.tsx
Comment on lines +12 to +17
onClick={() => {
void navigator.clipboard?.writeText(children).then(() => {
setCopied(true)
setTimeout(() => setCopied(false), 1400)
})
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major

Guard unsupported or rejected clipboard writes.

The handler calls .then() on the result of navigator.clipboard?.writeText(). If the Clipboard API is unavailable, this evaluates to undefined, causing undefined.then() to throw a TypeError. The code also fails to catch permission rejections, resulting in unhandled promise rejections.

Use an async handler with a try-catch block to safely await the operation:

Suggested fix
-        onClick={() => {
-          void navigator.clipboard?.writeText(children).then(() => {
-            setCopied(true)
-            setTimeout(() => setCopied(false), 1400)
-          })
-        }}
+        onClick={async () => {
+          try {
+            await navigator.clipboard?.writeText(children)
+            setCopied(true)
+            setTimeout(() => setCopied(false), 1400)
+          } catch {
+            // Silently fail or handle unsupported context
+          }
+        }}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/src/Code.tsx` around lines 12 - 17, The clipboard click handler in Code
should be made safe for unsupported or rejected writes: replace the chained
.then() on navigator.clipboard?.writeText() with an async onClick handler in the
Code component, await the writeText call only when the Clipboard API exists, and
wrap it in try-catch so missing clipboard support or permission failures do not
throw or create unhandled rejections. Keep the copied state update and reset
logic inside the success path, and use the existing Code symbol to locate the
handler.

Comment thread site/src/docs-main.tsx Outdated
Comment thread site/src/Docs.tsx Outdated
Comment thread site/src/Dropdown.tsx
Comment on lines +65 to +104
<button
type="button"
className="adrop-btn"
aria-haspopup="listbox"
aria-expanded={open}
onClick={() => setOpen((o) => !o)}
onKeyDown={onKey}
>
{renderIcon?.(selected.value)}
<span className="adrop-name">
{selected.name}
{selected.label ? <span className="adrop-label"> — {selected.label}</span> : null}
</span>
<span className="adrop-caret" aria-hidden="true">
</span>
</button>
{open && (
<ul className="adrop-list" role="listbox" tabIndex={-1}>
{options.map((o, i) => (
<li
key={o.value}
role="option"
aria-selected={o.value === value}
className={
'adrop-opt' +
(o.value === value ? ' sel' : '') +
(i === active ? ' active' : '')
}
onMouseEnter={() => setActive(i)}
onClick={() => choose(o.value)}
>
{renderIcon?.(o.value)}
<span className="adrop-name">
{o.name}
{o.label ? <span className="adrop-label"> — {o.label}</span> : null}
</span>
</li>
))}
</ul>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n site/src/Dropdown.tsx

Repository: dotbrains/airlock

Length of output: 4132


🏁 Script executed:

cat -n site/src/SessionBuilder.tsx

Repository: dotbrains/airlock

Length of output: 9604


🏁 Script executed:

true

Repository: dotbrains/airlock

Length of output: 155


Missing accessibility attributes for custom listbox (button trigger).

The custom Dropdown uses a <button> to trigger a role="listbox", but lacks the necessary ARIA wiring to be accessible:

  • The button is missing aria-controls to point to the listbox.
  • The button lacks aria-activedescendant, so screen readers do not announce the currently "active" option during keyboard navigation.
  • Because the trigger is a <button> (not an <input>), the wrapping <label> text in SessionBuilder.tsx ("Browser", "Lifetime") is not programmatically associated with the control.

Update the component to accept an id or labelId prop to wire up aria-controls, aria-labelledby, and dynamic aria-activedescendant attributes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/src/Dropdown.tsx` around lines 65 - 104, The Dropdown trigger/button is
missing the ARIA wiring needed to expose the listbox state and label
association. Update the Dropdown component to accept an id and/or labelId prop,
then use it in the button and listbox so the trigger sets aria-controls and
aria-labelledby, and reflects the current keyboard focus with a dynamic
aria-activedescendant tied to the active option. Make sure the list items in
Dropdown map to stable option ids, and update the SessionBuilder uses
(“Browser”, “Lifetime”) to pass the new label/id props so the wrapping label is
programmatically associated with the button.

Comment thread site/src/main.tsx Outdated
Comment thread site/src/SessionBuilder.tsx Outdated
Comment on lines +60 to +70
const curl = useMemo(() => {
const base = baseUrl.replace(/\/+$/, '') || 'http://localhost:8787'
const body = JSON.stringify({
targetUrl: targetUrl || 'https://example.com',
browser,
ttlSeconds: ttl,
})
const lines = [`curl -X POST ${base}/api/sessions \\`]
if (token.trim()) lines.push(` -H "Authorization: Bearer ${token.trim()}" \\`)
lines.push(' -H "Content-Type: application/json" \\')
lines.push(` -d '${body}'`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Shell-escape the generated curl arguments.

baseUrl, targetUrl, and token are copied straight into shell syntax here. A valid URL like https://example.com/?q=O'Reilly already breaks the command, and pasted untrusted values can change what the shell executes.

Proposed fix
+const shQuote = (value: string) => `'${value.replaceAll("'", "'\\''")}'`
+
 export default function SessionBuilder() {
@@
   const curl = useMemo(() => {
     const base = baseUrl.replace(/\/+$/, '') || 'http://localhost:8787'
     const body = JSON.stringify({
       targetUrl: targetUrl || 'https://example.com',
       browser,
       ttlSeconds: ttl,
     })
-    const lines = [`curl -X POST ${base}/api/sessions \\`]
-    if (token.trim()) lines.push(`  -H "Authorization: Bearer ${token.trim()}" \\`)
-    lines.push('  -H "Content-Type: application/json" \\')
-    lines.push(`  -d '${body}'`)
+    const lines = [`curl -X POST ${shQuote(`${base}/api/sessions`)} \\`]
+    if (token.trim()) {
+      lines.push(`  -H ${shQuote(`Authorization: Bearer ${token.trim()}`)} \\`)
+    }
+    lines.push(`  -H ${shQuote('Content-Type: application/json')} \\`)
+    lines.push(`  --data-raw ${shQuote(body)}`)
     return lines.join('\n')
   }, [baseUrl, targetUrl, browser, ttl, token])
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const curl = useMemo(() => {
const base = baseUrl.replace(/\/+$/, '') || 'http://localhost:8787'
const body = JSON.stringify({
targetUrl: targetUrl || 'https://example.com',
browser,
ttlSeconds: ttl,
})
const lines = [`curl -X POST ${base}/api/sessions \\`]
if (token.trim()) lines.push(` -H "Authorization: Bearer ${token.trim()}" \\`)
lines.push(' -H "Content-Type: application/json" \\')
lines.push(` -d '${body}'`)
const shQuote = (value: string) => `'${value.replaceAll("'", "'\\''")}'`
export default function SessionBuilder() {
const curl = useMemo(() => {
const base = baseUrl.replace(/\/+$/, '') || 'http://localhost:8787'
const body = JSON.stringify({
targetUrl: targetUrl || 'https://example.com',
browser,
ttlSeconds: ttl,
})
const lines = [`curl -X POST ${shQuote(`${base}/api/sessions`)} \\`]
if (token.trim()) {
lines.push(` -H ${shQuote(`Authorization: Bearer ${token.trim()}`)} \\`)
}
lines.push(` -H ${shQuote('Content-Type: application/json')} \\`)
lines.push(` --data-raw ${shQuote(body)}`)
return lines.join('\n')
}, [baseUrl, targetUrl, browser, ttl, token])
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/src/SessionBuilder.tsx` around lines 60 - 70, The generated curl command
in SessionBuilder’s useMemo block is injecting raw baseUrl, targetUrl, and token
directly into shell syntax, which can break quoting or allow shell injection.
Update the curl construction to shell-escape or safely quote every interpolated
value used in the request string, including the URL, Authorization header value,
and JSON payload contents derived from targetUrl so the output remains valid for
arbitrary input.

Comment on lines +148 to +155
<label className="field">
<span>Bearer token (AIRLOCK_API_TOKEN) — blank for local dev</span>
<input
type="text"
value={token}
onChange={(e) => setToken(e.target.value)}
placeholder="(unset — unauthenticated)"
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not expose AIRLOCK_API_TOKEN as plain text.

This field invites users to paste a real API token, but type="text" leaves it visible on-screen and more likely to be retained by browser input helpers.

Proposed fix
               <label className="field">
                 <span>Bearer token (AIRLOCK_API_TOKEN) — blank for local dev</span>
                 <input
-                  type="text"
+                  type="password"
+                  autoComplete="off"
+                  autoCapitalize="none"
+                  autoCorrect="off"
+                  spellCheck={false}
                   value={token}
                   onChange={(e) => setToken(e.target.value)}
                   placeholder="(unset — unauthenticated)"
                 />
               </label>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<label className="field">
<span>Bearer token (AIRLOCK_API_TOKEN) — blank for local dev</span>
<input
type="text"
value={token}
onChange={(e) => setToken(e.target.value)}
placeholder="(unset — unauthenticated)"
/>
<label className="field">
<span>Bearer token (AIRLOCK_API_TOKEN) — blank for local dev</span>
<input
type="password"
autoComplete="off"
autoCapitalize="none"
autoCorrect="off"
spellCheck={false}
value={token}
onChange={(e) => setToken(e.target.value)}
placeholder="(unset — unauthenticated)"
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/src/SessionBuilder.tsx` around lines 148 - 155, The Bearer token input
in SessionBuilder should not be rendered as plain text because it exposes
AIRLOCK_API_TOKEN on-screen and to browser helpers. Update the token field in
SessionBuilder.tsx to use a secret-style input appropriate for API credentials,
while keeping the existing state handling via setToken and the current
placeholder/label behavior.

Comment thread site/vite.config.ts Outdated
@nicholasadamou nicholasadamou merged commit 85e0390 into main Jun 25, 2026
5 checks passed
@nicholasadamou nicholasadamou deleted the feat/marketing-site branch June 25, 2026 18:42
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