Skip to content

v0.3.1 — The Locksmith Update#73

Merged
michaelmcnees merged 17 commits into
mainfrom
feature/v031-locksmith
Mar 26, 2026
Merged

v0.3.1 — The Locksmith Update#73
michaelmcnees merged 17 commits into
mainfrom
feature/v031-locksmith

Conversation

@michaelmcnees

@michaelmcnees michaelmcnees commented Mar 26, 2026

Copy link
Copy Markdown
Collaborator

Summary

Security hardening, code quality fixes, and documentation improvements from the v0.3.0 code review.

Code changes:

Documentation:

Cleanup:

Closes

Closes #34, closes #35, closes #36, closes #37, closes #38, closes #39, closes #40, closes #41, closes #42, closes #43, closes #44, closes #72

Test plan

  • All 23 engine test packages pass (go test ./... -count=1 -short)
  • go build ./... and go vet ./... clean
  • CodeRabbit review — 3 findings addressed, 1 pre-existing (non-regression) noted
  • CI workflows pass
  • Manual: verify email/send fails with non-TLS SMTP server
  • Manual: verify browser/run containers have tmpfs at /tmp

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Centralized IMAP utilities improving IMAP-based email connectors and triggers.
    • Enhanced email send connector with stricter TLS behavior (implicit TLS on 465, STARTTLS elsewhere).
  • Bug Fixes

    • Docker connector now blocks mounts to /tmp and mounts a secure in-container tmpfs for /tmp.
  • Documentation

    • Added security guidance on script injection and error-string leakage; TypeScript and email TLS callouts; updated Examples page source.
  • Chores

    • Removed site CI workflow.

michaelmcnees and others added 12 commits March 26, 2026 11:57
…arch logic

Closes #37, closes #38

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FindBodySection uses exact struct comparison. The fetch used Peek: true but
the lookup used the default Peek: false, causing a mismatch on multipart
messages.

Closes #40
Replace smtp.SendMail (which silently falls back to plaintext) with
explicit TLS: port 465 uses implicit TLS, all other ports use STARTTLS
and fail if the server does not support it.

Closes #34
Adds a tmpfs mount for /tmp with noexec,nosuid flags. ReadOnlyRootfs is
not enabled by default as it breaks browser/run containers that need
npm/pip install.

Closes #41

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes #43

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add /tmp to blockedMountTargets to prevent user mount conflicts with tmpfs
- Use context-aware dialing (DialContext) in SMTP connector for timeout respect
- Fix markdown blank line before closing admonition fence

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Mar 26, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@michaelmcnees has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 14 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d26f57bc-9bb5-461d-a1e3-84ad6f07fc18

📥 Commits

Reviewing files that changed from the base of the PR and between 57b641b and bb49d2f.

📒 Files selected for processing (5)
  • packages/engine/internal/connector/email_receive.go
  • packages/engine/internal/connector/imap_test.go
  • packages/engine/internal/imap/imap.go
  • packages/engine/internal/imap/imap_test.go
  • packages/site/src/pages/docs/examples.astro
📝 Walkthrough

Walkthrough

This PR centralizes IMAP credential parsing/dialing into a new internal imap package, refactors IMAP-using connectors/tests to consume it, replaces simple SMTP send with explicit TLS/STARTTLS session control, mounts /tmp as an in-container tmpfs and blocks /tmp mounts, and adds security documentation about script injection and error-string leakage; it also removes an examples path and a site CI workflow and edits several tests to use the new IMAP types.

Changes

Cohort / File(s) Summary
IMAP shared package
packages/engine/internal/imap/imap.go, packages/engine/internal/imap/imap_test.go
Added exported Config, ParseCredential, ParseCredentialMap, Dial, and BuildSearchCriteria; implements TLS/plain IMAP dialing and credential parsing with unit tests.
Connectors using IMAP
packages/engine/internal/connector/imap.go, packages/engine/internal/connector/email_receive.go, packages/engine/internal/connector/email_receive_test.go, packages/engine/internal/connector/email_move_test.go, packages/engine/internal/connector/email_delete.go
Refactored to call new imaplib APIs; removed local parsing/helpers; tests updated; logging switched to slog in delete fallback.
Server email trigger
packages/engine/internal/server/email_trigger.go, packages/engine/internal/server/email_trigger_test.go
Replaced local IMAP helpers with imaplib calls; set fetch Peek: true; updated tests and error wrapping.
SMTP send logic
packages/engine/internal/connector/email.go
Replaced smtp.SendMail with explicit SMTP session handling: context-aware dialing, implicit TLS (port 465) or STARTTLS (other ports) with extension check, TLS config, per-step error handling, auth, and deferred client close.
Docker tmpfs & mount blocking
packages/engine/internal/connector/docker.go
Added "/tmp" to blocked mount targets and pre-mounted /tmp as tmpfs (rw,noexec,nosuid,size=256m).
Orchestrator docs
packages/engine/internal/engine/orchestrator.go
Expanded AdvanceExecution comment and added a CAVEAT about continue_on_error being sourced from in-memory steps and hot-reload implications.
Site docs & examples
packages/site/src/content/docs/concepts/security.md, packages/site/src/content/docs/workflow-reference/connectors.md, packages/site/src/content/docs/workflow-reference/index.md, packages/site/src/pages/docs/examples.astro
Added script-injection and error-string leakage cautions, TLS enforcement note for email/send, TypeScript limitations for browser/run; changed examples dir resolution.
Site CI removal & growth doc
.github/workflows/site-ci.yml, marketing/growth-strategy.md
Removed site CI workflow; removed "npm wrapper" option from growth strategy table.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SMTP as SMTP Server
    participant TLS

    Client->>+SMTP: TCP connect (port 465 or other)
    alt Port 465 (implicit TLS)
        SMTP-->>Client: TCP connection ready
        Client->>+TLS: Wrap TCP in TLS (HandshakeContext)
        TLS-->>-Client: TLS session established
        Client->>SMTP: smtp.NewClient over TLS
    else Other ports (STARTTLS)
        SMTP-->>Client: SMTP greeting
        Client->>SMTP: EHLO / check STARTTLS extension
        SMTP-->>Client: STARTTLS supported?
        Client->>+TLS: Send STARTTLS -> perform TLS handshake
        TLS-->>-Client: TLS session established
        Client->>SMTP: smtp.NewClient over upgraded TLS
    end
    Client->>SMTP: AUTH (login), MAIL FROM, RCPT TO, DATA
    Client->>SMTP: Send message body
    Client->>SMTP: End DATA, QUIT
    SMTP-->>-Client: Close connection
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I hopped through configs, stitched IMAP with care,
TLS threads now sparkle in the SMTP air,
/tmp tucked safely in a tiny tmpfs bed,
Docs whisper warnings so no strings leak dread. ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 61.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'v0.3.1 — The Locksmith Update' is vague and does not convey the specific changes in the PR (security hardening, IMAP/SMTP refactoring, Docker hardening, documentation updates). Consider using a more descriptive title that highlights the main changes, such as 'Security hardening: SMTP TLS enforcement, IMAP consolidation, Docker /tmp hardening, and docs' or breaking into multiple PRs with clearer scopes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 feature/v031-locksmith

Comment @coderabbitai help to get the list of available commands and usage tips.

michaelmcnees and others added 2 commits March 26, 2026 12:53
The runner's default Node 20 is no longer supported. Add explicit
setup-node step with Node 24 before setup-bun.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Site deploys via Cloudflare Workers directly. The CI build step is
redundant. Will re-add with lint/test jobs when those are configured.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Mar 26, 2026

Copy link
Copy Markdown

Deploying mantle with  Cloudflare Pages  Cloudflare Pages

Latest commit: bb49d2f
Status: ✅  Deploy successful!
Preview URL: https://d661a020.mantle-cxy.pages.dev
Branch Preview URL: https://feature-v031-locksmith.mantle-cxy.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/engine/internal/connector/email_receive.go (1)

213-214: ⚠️ Potential issue | 🟠 Major

Peek mismatch in FindBodySection causes inefficient fallback in the default case.

The fetch request at lines 106-109 uses Peek: !markSeen (defaulting to true when markSeen is false). However, FindBodySection at line 213-214 constructs a lookup key without setting Peek, which defaults to false. In go-imap v2, the Peek field affects the key matching in FindBodySection, so when markSeen is false (the default), FindBodySection returns nil and the code falls back to using buf.BodySection[0] at line 219—which may not be the TEXT section.

Set Peek: true explicitly in the FindBodySection call at line 213 to match the common fetch pattern (when markSeen=false):

Proposed fix
-	bodySection := &imap.FetchItemBodySection{Specifier: imap.PartSpecifierText}
+	bodySection := &imap.FetchItemBodySection{Specifier: imap.PartSpecifierText, Peek: true}
 	rawText := buf.FindBodySection(bodySection)

If markSeen: true is a supported use case, consider passing the Peek value to messageBufferToMap or always fetching with Peek: true and handling marking separately (as done in lines 127-136).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/engine/internal/connector/email_receive.go` around lines 213 - 214,
The FindBodySection lookup uses an imap.FetchItemBodySection without Peek set,
causing a mismatch with the fetch that used Peek: !markSeen; update the
bodySection construction in messageBufferToMap (the bodySection variable created
before rawText := buf.FindBodySection) to explicitly set Peek: true so the
FindBodySection key matches the common fetch pattern when markSeen is false;
alternatively, if you must support markSeen=true, propagate the original Peek
value into messageBufferToMap and use that value when creating the
imap.FetchItemBodySection.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/engine/internal/imap/imap.go`:
- Around line 37-56: ParseCredentialMap currently enforces host but allows empty
username/password; update ParseCredentialMap to validate that cred["username"]
and cred["password"] are non-empty (returning a descriptive error like "IMAP
credential must include 'username'" / "'password'") before returning the Config,
leaving the existing port/default TLS logic intact; reference the
ParseCredentialMap function and the Config struct when adding these checks so
failures occur early with clear messages.

---

Outside diff comments:
In `@packages/engine/internal/connector/email_receive.go`:
- Around line 213-214: The FindBodySection lookup uses an
imap.FetchItemBodySection without Peek set, causing a mismatch with the fetch
that used Peek: !markSeen; update the bodySection construction in
messageBufferToMap (the bodySection variable created before rawText :=
buf.FindBodySection) to explicitly set Peek: true so the FindBodySection key
matches the common fetch pattern when markSeen is false; alternatively, if you
must support markSeen=true, propagate the original Peek value into
messageBufferToMap and use that value when creating the
imap.FetchItemBodySection.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 89cb8a23-10a4-42b3-b960-aac320e00293

📥 Commits

Reviewing files that changed from the base of the PR and between 746aebd and 9a515cc.

⛔ Files ignored due to path filters (1)
  • go.work.sum is excluded by !**/*.sum
📒 Files selected for processing (17)
  • marketing/growth-strategy.md
  • packages/engine/internal/connector/docker.go
  • packages/engine/internal/connector/email.go
  • packages/engine/internal/connector/email_delete.go
  • packages/engine/internal/connector/email_move_test.go
  • packages/engine/internal/connector/email_receive.go
  • packages/engine/internal/connector/email_receive_test.go
  • packages/engine/internal/connector/imap.go
  • packages/engine/internal/connector/imap_test.go
  • packages/engine/internal/engine/orchestrator.go
  • packages/engine/internal/imap/imap.go
  • packages/engine/internal/imap/imap_test.go
  • packages/engine/internal/server/email_trigger.go
  • packages/engine/internal/server/email_trigger_test.go
  • packages/site/src/content/docs/concepts/security.md
  • packages/site/src/content/docs/workflow-reference/connectors.md
  • packages/site/src/content/docs/workflow-reference/index.md
💤 Files with no reviewable changes (1)
  • marketing/growth-strategy.md

Comment thread packages/engine/internal/imap/imap.go
michaelmcnees and others added 3 commits March 26, 2026 12:59
The examples directory moved from root examples/ to
packages/site/src/content/examples/ during the monorepo conversion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Validate username/password in ParseCredentialMap for early, clear errors
- Fix FindBodySection Peek mismatch in email_receive.go messageBufferToMap
  (same class of bug as #40, propagate Peek value from fetch options)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
import.meta.url resolves relative to the built output directory during
Astro's static build, not the source tree. Use process.cwd() which
is always packages/site/ during build.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@michaelmcnees michaelmcnees merged commit 965916c into main Mar 26, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment