Skip to content

Add comprehensive security scanning and hardening#39

Merged
jeremy merged 10 commits intomainfrom
security-scanning-setup
Jan 25, 2026
Merged

Add comprehensive security scanning and hardening#39
jeremy merged 10 commits intomainfrom
security-scanning-setup

Conversation

@jeremy
Copy link
Copy Markdown
Member

@jeremy jeremy commented Jan 25, 2026

Summary

Sets up modern security scanning, vulnerability detection, and supply chain security tooling for the bcq CLI.

Security Tooling Added

Tool Purpose Integration
gosec Go security linter Enabled in golangci-lint
gitleaks Secret scanning CI workflow + pre-commit
govulncheck Dependency vulnerabilities Makefile target
Cosign Release signing (keyless) GoReleaser + Sigstore
SBOM Supply chain transparency GoReleaser (CycloneDX)
Go fuzzing Parser robustness Native fuzz tests

gosec Issues Fixed

All findings addressed across 24 files:

  • G101: Public OAuth client secrets marked intentional (native apps embed these; security relies on PKCE)
  • G104: Unhandled errors fixed with _ = for best-effort ops
  • G112: ReadHeaderTimeout added to HTTP server (Slowloris mitigation)
  • G204: Subprocess execution documented as hardcoded per-platform
  • G301/G306: File permissions tightened (0755→0750, 0644→0600)
  • G304: Config/cache file reads documented as trusted paths
  • G404: math/rand acknowledged as appropriate for jitter

New Makefile Targets

make security    # Full security check (lint + vuln + secrets)
make vuln        # Run govulncheck
make secrets     # Run gitleaks
make fuzz        # Run fuzz tests (30s each)
make fuzz-quick  # Quick fuzz run (10s each)

Test plan

  • make lint passes (0 gosec issues)
  • make vuln passes (no vulnerabilities)
  • make secrets passes (no leaks detected)
  • make fuzz-quick passes (dateparse + URL fuzzing)
  • go test ./... passes (all tests green)

Add gosec to the enabled linters list in golangci-lint configuration.
This catches common Go security issues including injection vulnerabilities,
weak crypto, file permission issues, and more.
- Update pre-commit to golangci-lint v2.1.6 (match CI)
- Add gitleaks hook to pre-commit config
- Add GitHub Actions workflow for secret scanning on push/PR
- Add .gitleaks.toml to allowlist public OAuth client secrets
  (native CLI apps cannot hide client secrets; security relies on PKCE)
- Configure GoReleaser to generate CycloneDX SBOMs for release archives
- Add Cosign keyless signing for checksums via Sigstore
- Install cosign in release workflow before GoReleaser runs
Add Go native fuzz tests for:
- dateparse.ParseFrom: handles relative dates, weekdays, offsets
- URL path parsing: extracts IDs from Basecamp URLs

These help find edge cases and panics in user-facing input parsing.
New targets:
- make security: runs lint + vuln + secrets
- make vuln: runs govulncheck for dependency vulnerabilities
- make secrets: runs gitleaks for secret detection
- make fuzz: runs fuzz tests (30s each)
- make fuzz-quick: quick fuzz run (10s each)
Address all gosec findings across the codebase:

G101 (hardcoded credentials):
- Mark public OAuth client secret as intentional with nolint comment
  (native CLI apps embed client secrets; security relies on PKCE)

G104 (unhandled errors):
- Add _ = for best-effort cleanup operations (file removal, cache writes)
- Add _ = for operations where errors don't affect correctness
  (summary display, flag marking, etc.)
- Handle crypto/rand.Read errors with panic (should never fail)

G112 (Slowloris):
- Add ReadHeaderTimeout to OAuth callback HTTP server

G204 (subprocess):
- Document that browser open command is hardcoded per-platform

G301/G306 (file permissions):
- Tighten directory permissions: 0755 → 0750
- Tighten file permissions: 0644 → 0600

G304 (file inclusion):
- Add nolint comments for file reads from trusted config/cache paths

G404 (weak random):
- Document that math/rand is appropriate for jitter (not security-sensitive)
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ae06490e2f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

The gitleaks-action@v2 requires a paid license for organizations.
Install and run gitleaks CLI directly, which is free for all use.
Single source of truth for gitleaks configuration and allowlist
handling. Local dev and CI now use the same command.
Keyless signing uses Sigstore's Fulcio CA and Rekor transparency log,
which requires an OIDC identity token from GitHub Actions. The
id-token: write permission enables cosign to obtain this token.
Keyless signing is the default in cosign v2+. The COSIGN_EXPERIMENTAL=1
env var was only needed in cosign v1.x to opt into the feature.
Added a comment noting the OIDC permission requirement.
@jeremy jeremy merged commit 3bd797b into main Jan 25, 2026
3 checks passed
@jeremy jeremy deleted the security-scanning-setup branch January 25, 2026 19:27
@jeremy jeremy mentioned this pull request Jan 28, 2026
3 tasks
jeremy added a commit that referenced this pull request Feb 19, 2026
Add comprehensive security scanning and hardening
jeremy added a commit to brianevanmiller/basecamp-cli that referenced this pull request Mar 18, 2026
extractAttr returned raw HTML-encoded strings, so filenames like
O&basecamp#39;Brien displayed with visible entities. Apply html.UnescapeString
on the read path to match the encoding done by escapeAttr on writes.
jeremy added a commit to brianevanmiller/basecamp-cli that referenced this pull request Mar 18, 2026
extractAttr returned raw HTML-encoded strings, so filenames like
O&basecamp#39;Brien displayed with visible entities. Apply html.UnescapeString
on the read path to match the encoding done by escapeAttr on writes.
jeremy added a commit that referenced this pull request Mar 18, 2026
* Add attachment listing for items

Introduces `basecamp attachments list <id|url>` to enumerate file attachments
embedded in Basecamp item rich text by parsing <bc-attachment> tags. Supports
all recording types with auto-detection from URLs, styled TTY and JSON output,
and context-aware breadcrumbs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Decode HTML entities in attachment attribute values

extractAttr returned raw HTML-encoded strings, so filenames like
O&#39;Brien displayed with visible entities. Apply html.UnescapeString
on the read path to match the encoding done by escapeAttr on writes.

* Prefer comment ID when resolving attachment list URLs

Comment URLs like .../todos/111#__recording_789 were using the parent
recording ID (111) instead of the comment ID (789). Follow the
established convention from comment.go and prefer CommentID when the
URL fragment is present, auto-setting type to "comment".

* Support question_answers type in attachment listing

Check-in answer URLs auto-detect as type "question_answers" but
typeToEndpoint had no case for it, causing "Unknown type" errors.
Add "answer" and "question_answers" aliases and update all hint strings.

* Address remaining PR review feedback

- Guard CommentID preference on compatible recordType to avoid
  type/ID mismatch when --type is explicitly set
- Add \b boundary to reBcAttachmentTag to prevent matching
  similarly-prefixed tag names like bc-attachment-foo
- Use strings.EqualFold for mention content-type filtering and
  attribute name matching in extractAttr

* Normalize breadcrumb types, fix parameter shadowing, case-insensitive IsImage

- Normalize type aliases in show breadcrumb so copied commands work
  (e.g. question_answers → checkin, schedule_entries → schedule-entry)
- Rename ParseAttachments parameter from html to content to avoid
  shadowing the html import
- Make IsImage() case-insensitive with EqualFold prefix check

* Omit --type for answer/question_answers in show breadcrumb

show has no type alias for question_answers, so --type checkin would
fetch the parent question instead of the answer. Return "" to let
generic recording lookup find the correct endpoint.

* Extract resolveAttachmentTarget helper, fix tag boundary, update comment

- Extract URL/type resolution into resolveAttachmentTarget so tests
  exercise the actual function rather than duplicating logic inline
- Replace \b with [\s/>] in reBcAttachmentTag since Go regex treats
  - as non-word, allowing <bc-attachment-custom> to match
- Update typeToEndpoint comment to note it's a superset of show.go

* Update surface snapshot with --in flag for attachments

The --in persistent flag from main was missing from the attachments
surface entries after the merge.

* Fix reBcAttachmentTag to match bare and uppercase tags

The regex required at least one char in [\s/>] after the tag name,
which failed on bare <bc-attachment> with no attributes. Switch to
(\s[^>]*|) to allow empty attrs while still requiring whitespace
before any attribute names (preventing bc-attachment-foo matches).
Add tests for uppercase tag names and bare tags.

* Address review: NBSP normalization, variable shadowing, dedup validation

- Normalize NBSP to regular space in extractAttr after html.UnescapeString
- Rename shadowed recordType to resolvedType in runAttachmentsList
- Remove duplicate type validation from fetchItemContent (caller validates)
- Update SKILL.md to show <id|url> instead of <id>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Jeremy Daer <jeremy@37signals.com>
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