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
169 changes: 169 additions & 0 deletions .github/workflows/release-extension.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
name: Release Extension

on:
push:
branches: ["main"]
paths:
- "apps/extension/package.json"
- ".github/workflows/release-extension.yml"
workflow_dispatch:

permissions:
contents: write
Comment on lines +11 to +12

Copy link
Copy Markdown
Contributor

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== workflow file =="
cat -n .github/workflows/release-extension.yml

echo
echo "== package/config references =="
fd -p 'package.json|bun.lockb|bun.lock|pnpm-lock.yaml|yarn.lock|x86' .github/workflows/release-extension.yml 2>/dev/null || true

echo
echo "== relevant checkouts/build hooks across repo =="
rg -n "bun(inst|run)|build|pre|post|prepare|install|hooks|lint|typecheck|verify|actions/checkout|persist-credentials|contents: write|permissions:" -S .github package.json tools scripts 2>/dev/null || true

Repository: denizlg24/denizlg24.com

Length of output: 19569


🌐 Web query:

actions/checkout persist-credentials default true commit GITHUB_TOKEN contents write permissions

💡 Result:

The actions/checkout action has a persist-credentials input that defaults to true [1][2]. When set to true, the authentication token (such as the GITHUB_TOKEN) used to check out the repository is persisted in the local Git configuration [1][3]. This allows subsequent steps in the same job to run authenticated Git commands without needing to manually re-authenticate [1][4]. The token is removed automatically during the post-job cleanup [1][3]. Regarding GITHUB_TOKEN permissions, while actions/checkout defaults to persisting these credentials [1][2], the actual capabilities available to those credentials depend on the permissions explicitly granted to the GITHUB_TOKEN within your workflow file [5][6]. The default GITHUB_TOKEN permissions for GitHub Actions workflows include only read access for repository contents (contents: read) [6]. To enable write operations (such as pushing code or creating branches), you must explicitly grant write permissions in your workflow configuration [6]: permissions: contents: write If you require a job to perform write actions, it is a recommended security practice to use the principle of least privilege by explicitly setting the required permissions in your workflow file rather than relying on default or broad access [5][6]. If your workflow only needs to clone or fetch the repository, keeping the default contents: read is sufficient [6]. To prevent the GITHUB_TOKEN from being persisted in the local Git config for security reasons, you can set persist-credentials to false in the checkout step [1][7].

Citations:


Security Misconfiguration (CWE-250)

Scope contents: write to the release job and disable credential persistence in build steps.

The workflow-level contents: write grant also reaches prepare and build, whose checkouts persist the token by default. The build job already executes bun commands and repo scripts while that token is available, even without installing extra packages. Set the workflow default to contents: read, give release job-level write permissions, and add persist-credentials: false to the checkouts that only need repository access.

🧰 Tools
🪛 zizmor (1.28.0)

[error] 12-12: overly broad permissions (excessive-permissions): contents: write is overly broad at the workflow level

(excessive-permissions)


[warning] 12-12: permissions without explanatory comments (undocumented-permissions): needs an explanatory comment

(undocumented-permissions)

🤖 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 @.github/workflows/release-extension.yml around lines 11 - 12, Update the
workflow-level permissions to contents: read, add contents: write specifically
to the release job, and set persist-credentials: false on checkout steps in
prepare and build that only require repository access. Keep the release
checkout’s credential behavior unchanged unless it does not need the write
token.

Source: Linters/SAST tools


concurrency:
group: release-extension
cancel-in-progress: false

jobs:
prepare:
name: Prepare release
runs-on: ubuntu-latest
outputs:
changed: ${{ steps.version.outputs.changed }}
tag: ${{ steps.version.outputs.tag }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Check out code
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Detect version change
id: version
shell: bash
run: |
current_version="$(jq -r '.version' apps/extension/package.json)"

if [[ "${{ github.event_name }}" != "push" ]]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "tag=ext-v$current_version" >> "$GITHUB_OUTPUT"
echo "version=$current_version" >> "$GITHUB_OUTPUT"
exit 0
fi

previous_version="$(
git show "${{ github.event.before }}:apps/extension/package.json" 2>/dev/null |
jq -r '.version' ||
true
)"
Comment on lines +36 to +49

Copy link
Copy Markdown
Contributor

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Workflow around detected lines:\n'
sed -n '1,90p' .github/workflows/release-extension.yml

printf '\nSearch for release-extension reference and outputs:\n'
rg -n "release-extension|version|changed|tag=ext-v|previous_version|git show|github\.event\.before|github\.event_name" .github apps/extension -S

Repository: denizlg24/denizlg24.com

Length of output: 18017


🌐 Web query:

GitHub Actions github.event.before push all-zero SHA zero commit

💡 Result:

In GitHub Actions, the github.event.before property is set to an all-zero SHA (0000000000000000000000000000000000000000) when there is no previous commit reference for the event [1]. This most commonly occurs during the first push to a new branch, the creation of a new tag, or following a force push [2][3][1]. Because this all-zero string is not a valid git object, workflows that attempt to use github.event.before directly in commands like git fetch or git diff will encounter fatal errors [4][2][3]. To handle this, developers commonly implement conditional logic to detect the null SHA and provide a fallback [3][5][6]. Common strategies include: 1. Skipping the command: If the operation is intended to compare changes between commits, skip the operation entirely when the before SHA is null, as there is no prior state to compare against [4][7]. 2. Using an empty tree: For git diff commands, substituting the null SHA with the git empty-tree SHA (4b825dc642cb6eb9a060e54bf8d69288fbee4904) can allow the diff to treat all files in the initial commit as newly added [2]. 3. Falling back to an alternative base: Some workflows fall back to a default branch (e.g., origin/main), the parent of the first commit in the push, or a recent commit timestamp to establish a base [3][5][8]. Example of defensive logic in a workflow: if [[ "${{ github.event.before }}" == "0000000000000000000000000000000000000000" ]]; then echo "First push detected, skipping base comparison." else git diff "${{ github.event.before }}" "${{ github.sha }}" fi

Citations:


🌐 Web query:

GitHub Actions context payload github.event.before contains shell metacharacters

💡 Result:

Using GitHub Actions context variables—such as github.event.before—directly within run steps creates a security risk known as script or shell injection [1][2][3]. This occurs because GitHub Actions evaluates the ${{ }} expression and substitutes its raw value into your shell script before the shell executes it [1][4][5]. If an attacker can influence that value to contain shell metacharacters (e.g., quotes, semicolons, backticks, or subshell syntax), they can break out of the intended command string and execute arbitrary code on the runner [1][2][3]. While github.event.before is a SHA reference and therefore less likely to be directly manipulated by an attacker compared to fields like issue titles or pull request bodies, it is still considered a best practice to avoid direct interpolation for any context variable [6][7][5]. Security tools and hardening guides flag this pattern because it violates defensive programming principles [6][8][9]. To prevent shell injection, you should always avoid using the ${{ }} syntax directly in run commands [10][5][3]. Instead, pass the context value into an environment variable using the env: block [2][4][3]. Because environment variables are handled as data rather than script code, the shell will not interpret metacharacters within the variable's value [11][5][3]. Recommended pattern: # ❌ VULNERABLE: Direct interpolation run: git diff --name-only ${{ github.event.before }} ${{ github.sha }} # ✅ SECURE: Using environment variables env: BEFORE_SHA: ${{ github.event.before }} run: git diff --name-only "$BEFORE_SHA" "${{ github.sha }}" By assigning the value to an environment variable, the shell treats the content as a safe string [11][3]. Additionally, quoting the variable (e.g., "$BEFORE_SHA") is a standard shell best practice to prevent word splitting [10][5]. Automated security scanners such as CodeQL and zizmor are designed to detect unsafe inline interpolation and should be used to audit your workflows [2][10][6][7].

Citations:


🌐 Web query:

GitHub Actions github.event.before ref type SHA string

💡 Result:

In GitHub Actions, github.event.before is a property available within the github context for specific event types, most notably the push event [1][2]. It is a string representing the SHA (Secure Hash Algorithm) of the most recent commit on the reference (branch or tag) before the push occurred [3][4][2]. Key technical details regarding github.event.before: 1. Type and Format: It is a string containing a 40-character commit SHA [5][3]. 2. Usage: It is primarily used to determine the range of commits introduced by a push (i.e., the range between before and after [3][4][6]). 3. Null SHA Case: When a workflow is triggered by the creation of a new branch or tag, github.event.before is set to the "null SHA" (0000000000000000000000000000000000000000) [1][7]. Because this is not a valid git reference, attempting to use it directly in commands like git fetch or git diff without a conditional check can cause the workflow to fail [8][7][6]. 4. Best Practice: When using github.event.before in scripts, it is recommended to check if the value is the null SHA or empty to prevent errors during execution [7][6]. The github.event context mirrors the webhook payload for the event that triggered the workflow run [5][9][10]. Therefore, properties like before are specific to the structure of the push webhook payload and are not universal to all GitHub Actions events [5][9][3].

Citations:


🌐 Web query:

GitHub push payload before SHA webhook before ref name

💡 Result:

In a GitHub push event webhook payload, the fields appear in the JSON object at the top level [1][2]. While JSON objects are technically unordered collections of key-value pairs, the standard schema for a push event includes the following core fields [1][3]: ref: The full Git reference that was pushed (e.g., refs/heads/main or refs/tags/v3.14.1) [1][4]. before: The SHA of the most recent commit on the ref before the push [1][5]. after: The SHA of the most recent commit on the ref after the push [1][3]. These fields are essential for identifying the state of the branch or tag before and after the specific push action occurred [1][6]. When receiving the payload, you can use these SHAs to compare the changes included in the push [1][3]. [1][2]

Citations:


Injection (CWE-94): Improper Control of Generation of Code ('Code Injection')

Pass event data through env, and handle the all-zero previous SHA.

  1. ${{ github.event_name }} and ${{ github.event.before }} expand into the shell script before bash parses it. Assign them to env values and reference those shell variables in the run block.
  2. On the first push to a ref or after a force push, github.event.before can be the all-zero SHA. git show then fails, so previous_version becomes empty and the workflow reports changed=true even if current_version did not change. Treat an unresolvable previous commit as no version change.
🧰 Tools
🪛 zizmor (1.28.0)

[warning] 38-38: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 46-46: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 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 @.github/workflows/release-extension.yml around lines 36 - 49, Update the
workflow step containing current_version and previous_version to pass
github.event_name and github.event.before through env, then reference those
shell variables instead of embedding expressions in the run script. Handle an
all-zero or otherwise unresolvable previous SHA by treating the previous version
as the current version, so the comparison reports no version change when no
prior package version is available.

Source: Linters/SAST tools


if [[ "$current_version" == "$previous_version" ]]; then
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "The release workflow changed, but the extension version did not."
exit 0
fi

echo "changed=true" >> "$GITHUB_OUTPUT"
echo "tag=ext-v$current_version" >> "$GITHUB_OUTPUT"
echo "version=$current_version" >> "$GITHUB_OUTPUT"

build:
name: Build and verify
needs: prepare
if: needs.prepare.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Install Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.3

- name: Install dependencies
run: bun install --frozen-lockfile --ignore-scripts

- name: Typecheck
run: bun run typecheck
working-directory: apps/extension

- name: Test
run: bun test
working-directory: apps/extension

- name: Package
run: bun run package
working-directory: apps/extension

- name: Check the manifests carry the released version
shell: bash
run: |
for target in chrome firefox; do
manifest_version="$(jq -r '.version' "apps/extension/dist/$target/manifest.json")"
if [[ "$manifest_version" != "${{ needs.prepare.outputs.version }}" ]]; then
echo "$target manifest is $manifest_version, expected ${{ needs.prepare.outputs.version }}"
exit 1
fi
done

- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: extension-packages
path: apps/extension/release/*.zip
if-no-files-found: error

release:
name: Publish release
needs: [prepare, build]
if: needs.prepare.outputs.changed == 'true'
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: extension-packages
path: artifacts

- name: Build release notes
env:
TAG: ${{ needs.prepare.outputs.tag }}
VERSION: ${{ needs.prepare.outputs.version }}
shell: bash
run: |
previous_tag="$(
git tag --list 'ext-v*' --sort=-v:refname |
grep -Fvx "$TAG" |
head -n 1 ||
true
)"

if [[ -n "$previous_tag" ]]; then
changes="$(git log --format='- %s (`%h`)' "$previous_tag..HEAD" -- apps/extension)"
else
changes="$(git log --format='- %s (`%h`)' HEAD -- apps/extension)"
fi

if [[ -z "$changes" ]]; then
changes="- Release metadata only."
fi

{
echo "## Authenticator extension v$VERSION"
echo
echo "$changes"
echo
echo "Upload \`*-chrome-*.zip\` to the Chrome Web Store and"
echo "\`*-firefox-*.zip\` to AMO, with \`*-source-*.zip\` as the"
echo "required source submission. Submission notes:"
echo "https://github.com/${{ github.repository }}/blob/$TAG/apps/extension/store/listing.md"
} > extension-release-notes.md

- name: Create GitHub release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.prepare.outputs.tag }}
target_commitish: ${{ github.sha }}
name: Authenticator extension v${{ needs.prepare.outputs.version }}
body_path: extension-release-notes.md
make_latest: false
files: artifacts/*.zip
12 changes: 12 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ Turborepo monorepo (bun workspaces, single root `bun.lock`, Biome lint/format at
- `apps/envoy-cli/` — Rust `envy` CLI. This monorepo is the canonical source
and release owner; `.github/workflows/release-envoy-cli.yml` publishes
`envoy-v*` releases when its Cargo package version changes on `main`.
- `apps/extension/` — Vite + React MV3 browser extension (Chrome and Firefox)
holding an offline TOTP vault synced with `/api/admin/authenticator`. Uses
`@repo/ui` for the design system. `.github/workflows/release-extension.yml`
publishes `ext-v*` releases when its package version changes on `main`.
- `apps/terminal/` — compiled Bun web-terminal daemon. Runs on the Pi host under systemd, not in Docker.
- `packages/cloud-core/` — Pi-side cloud logic: drizzle schema, storage/S3, projects, ops, sync, middleware.
- `packages/cloud-ui/`, `packages/cloud-auth-client/` — shared client pieces for the two Vercel cloud apps.
Expand Down Expand Up @@ -228,6 +232,14 @@ Canonical API contract lives in `packages/schemas` (zod schemas; all TS types ar
- `DELETE /resources/{id}/sub-resources/{subId}` → `{ status: "deleted" }` (also deletes health logs)
- Checks run from the backend in the health-check cron (`runAllSubResourceChecks` in `lib/resource-agent.ts`); logs share `HealthCheckLog` keyed by sub-resource id; public `/api/public/resource-status` nests `subResources` per parent

### Authenticator
- `GET /authenticator` → `{ accounts: IAuthenticatorAccount[] }` (no secrets)
- `GET /authenticator/codes` → `{ codes: IAuthenticatorCode[] }` — server-computed, used by the admin and desktop UIs
- `GET /authenticator/export` → `{ accounts: IAuthenticatorExportAccount[], exportedAt }` — **the only route that returns decrypted base32 secrets.** It exists so `apps/extension` can hold an offline vault; a leaked API key here costs every secret, not one code. Do not call it from the web or desktop UIs, and do not persist its response anywhere unencrypted.
- `POST /authenticator` → `{ label, issuer, accountName, secret, algorithm?, digits?, period? }` → `{ account }`
- `PATCH /authenticator/{id}` → label/issuer/accountName only; a secret is never updated in place
- `DELETE /authenticator/{id}` → `{ success: true }`

### Upload
- `POST /upload` → FormData with "file" field → `{ url, hash }`. Stores to the self-hosted cloud S3 via `uploadFileToStorage(file, "image")`, where `"image"` is the bucket name, not a type filter — the route enforces no type or size limit. Pinata is gone — the spreadsheets routes read and write the same self-hosted storage, and only their `pinata*` column names survive.

Expand Down
24 changes: 22 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,40 @@ keeping plaintext secrets and encryption keys on the user's machine.

## Repository map

### Applications

| Path | Description | Audience |
| --- | --- | --- |
| `apps/web` | Public website, writing, projects, and private administration | Personal |
| `apps/desktop` | Native life-dashboard client built with Tauri | Personal |
| `apps/extension` | Chrome and Firefox extension holding an offline authenticator vault | Personal |
| `apps/api` | API for my self-hosted cloud | Personal |
| `apps/cloud` | Administration interface for cloud services | Personal |
| `apps/email-classifier` | Python Logistic Regression Email Classifier API | Personal |
| `apps/markets-relay` | Bun WebSocket relay fanning out Tiingo market quotes | Personal |
| `apps/storage` | Browser-based file manager | Personal |
| `apps/terminal` | Web-terminal daemon for the cloud host | Personal |
| `apps/envoy` | Envoy website and encrypted-storage API | Public |
| `apps/envoy-cli` | Rust command-line client for Envoy | Public |
| `apps/ssh-server` | Go based ssh-server that powers my business card | Personal |
| `packages/*` | Shared contracts, UI, utilities, and application modules | Shared |
| `infra/*` | Deployment definitions for self-hosted services | Personal |

### Packages

| Path | Description |
| --- | --- |
| `packages/schemas` | Canonical wire contracts as Zod schemas; every application type is inferred from here |
| `packages/ui` | Design system and component library shared by every browser interface |
| `packages/admin` | Shared administration feature screens rendered by both the website and the desktop client |
| `packages/markets` | Portfolio engine, pricing logic, and market data contracts |
| `packages/latex-editor` | Reusable multi-file LaTeX editing workspace with compile log and preview |
| `packages/whiteboard-render` | Server-side rendering of whiteboard documents to SVG |
| `packages/cloud-core` | Self-hosted cloud logic: database schema, S3 storage, projects, operations, sync |
| `packages/cloud-ui` | Shared interface pieces for the cloud and storage applications |
| `packages/cloud-auth-client` | Client-side authentication for the cloud applications |
| `packages/utils` | Shared helpers for money, recurrence, and tree structures |
| `packages/typescript-config` | Shared TypeScript configuration presets |

## Architecture

The TypeScript applications are organized as Bun workspaces and coordinated by
Expand All @@ -52,8 +71,9 @@ CLI and shares versioned API fixtures across both implementations.

- Bun, TypeScript, Turborepo
- Next.js, React, Tailwind CSS
- Vite and Manifest V3 for the browser extension
- Rust and Tauri
- Python, FastAPI
- Go, Python, FastAPI
- Hono, PostgreSQL, MongoDB, Redis
- Prisma and Drizzle
- Docker, GitHub Actions, Vercel
Expand Down
5 changes: 5 additions & 0 deletions apps/extension/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dist/
release/
# Regenerated by scripts/generate-icons.ts on every build; SOURCE.md tells
# reviewers no binary assets are checked in, so keep it true.
public/icons/
48 changes: 48 additions & 0 deletions apps/extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Authenticator extension

Offline-first TOTP codes for Chrome and Firefox, synced with
`/api/admin/authenticator` on denizlg24.com.

## How it works

Secrets are pulled once from `GET /api/admin/authenticator/export`, sealed with
AES-256-GCM under a key derived from a passphrase (PBKDF2-SHA256, 600k
iterations), and kept in `storage.local`. Codes are generated locally, so the
extension keeps working when the server does not — that is the point of holding
a copy rather than polling for codes.

The unlocked key lives in `storage.session` (memory only, extension contexts
only), which is what lets the background worker sync without a second unlock and
what makes a browser restart lock the vault again.

Sync runs when the popup opens, after every local change, and on a timer. Local
changes are pushed before the pull, so the merge never has to arbitrate. Accounts
deleted on the server move to a local trash and are purged after the retention
window rather than being dropped on the spot.

## Commands

```bash
bun run dev # chrome, watch mode
bun run dev:firefox
bun run build # both targets into dist/
bun run package # release/*.zip, store-ready
bun run icons # regenerate public/icons from scripts/generate-icons.ts
bun test
bun run typecheck
```

`EXT_API_BASE_URL` (or `--api-base-url=`) overrides the compiled-in default and
the manifest's `host_permissions` entry.

## Loading a development build

- Chrome: `chrome://extensions` → Developer mode → Load unpacked → `dist/chrome`
- Firefox: `about:debugging#/runtime/this-firefox` → Load Temporary Add-on →
`dist/firefox/manifest.json`

## Releasing

Bump `version` in `package.json` and merge to `main`. `release-extension.yml`
detects the change, builds both targets plus the source archive, and publishes a
GitHub release tagged `ext-v<version>`. Uploading to the two stores is manual.
56 changes: 56 additions & 0 deletions apps/extension/SOURCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Build instructions (add-on reviewers)

The submitted package is produced by Vite from the TypeScript sources in this
archive. The archive is the full repository tree at the released commit; only
`apps/extension` and the workspace packages it imports (`packages/ui`,
`packages/schemas`, `packages/typescript-config`) take part in this build.

## Environment

| Tool | Version |
| --- | --- |
| Bun | 1.3.3 |
| Operating system | any; the release is built on `ubuntu-latest` |

Bun is the only prerequisite — it provides the package manager, the runtime for
the build script, and the test runner. Install it from https://bun.sh.

## Steps

```bash
bun install --ignore-scripts
cd apps/extension
bun run build:firefox
```

`--ignore-scripts` skips an unrelated repository postinstall hook (it downloads a
LaTeX toolchain used by a different app in the monorepo) and has no effect on
this extension.

The output appears in `apps/extension/dist/firefox` and is byte-for-byte what
the uploaded package contains:

- `manifest.json` — generated by `scripts/manifest.ts`
- `background.js` — single IIFE bundle of `src/background/index.ts`
- `popup.html`, `options.html`, `assets/*` — Vite build of the two pages
- `icons/*.png` — generated by `scripts/generate-icons.ts`, no binary assets are
checked in

Replace `build:firefox` with `build:chrome` for the Chrome package; the two
differ only in the generated manifest.

## Verifying

```bash
cd apps/extension
bun test # includes the RFC 6238 vectors and the sync merge rules
bun run typecheck
```

## Third-party code

All dependencies are resolved from the public npm registry and pinned by
`bun.lock` at the repository root. The runtime dependencies are: `react`,
`react-dom`, `otpauth` (TOTP), `webextension-polyfill`, `radix-ui`,
`lucide-react`, `clsx`, `tailwind-merge`, `class-variance-authority`, `sonner`.
No code is fetched at runtime; the manifest's CSP is `script-src 'self'`.
2 changes: 2 additions & 0 deletions apps/extension/bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[test]
preload = ["./test-setup.ts"]
12 changes: 12 additions & 0 deletions apps/extension/options.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Authenticator Settings</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/pages/options/main.tsx"></script>
</body>
</html>
Loading