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
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [1.4.1] - 2026-07-27

### Security

- **Brand-owned DNS domains removed from the published artifact.** 1.4.0 shipped
real deployment hostnames in `connectors/zendesk` config templates and docs,
in two other connectors' comments and examples, in `SECURITY.md`, and — via a
bundled dependency literal with no occurrence anywhere in this repo's source —
in the compiled `bin/index.js`. Every occurrence of an owned DNS domain is gone
from the tarball this package publishes. Verified against the packed tarball
rather than the working tree, because the working tree was never where the
whole problem was.

Two limits on that claim, stated because "hostnames removed" would overstate it:

- `@hasna/events` is externalized out of `bin/index.js` (see *Changed*) but is
still a runtime dependency, and its own published package continues to carry
the literal. `npm install` therefore still places an owned domain on disk.
Removing it belongs to that package, and is tracked there.
- Scope here is DNS domains. Deployment **resource identifiers** — the
instance, database and bucket names in `connectors/zendesk`'s docs, `Makefile`
and `.env.example`, together with the naming pattern they follow — are
unchanged from 1.4.0 and still ship. They are a separate class with a
separate fix, tracked separately; this release does not address them and
should not be read as having done so.

### Changed

- **`connect-zendesk` no longer ships a default remote API URL.** The value was
a hardcoded deployment host used as a fallback. It now comes from
`ZENDESK_REMOTE_API_URL` or `connect-zendesk config set-remote-url <url>`.
`config show` and `remote url` report `not set`; `remote status` and
`remote health` exit non-zero with guidance naming both mechanisms. `make`'s
deploy banner reads a new overridable `REMOTE_API_URL`.
- **Vulnerability reports go through GitHub Security Advisories** instead of an
email address. Private vulnerability reporting is enabled on the repository.
- `@hasna/events` is marked external in the CLI bundle, joining the existing
`ink` / `react` / `chalk` / `conf` externals. It is a declared runtime
dependency, so npm resolves it at install time.

### Fixed

- `.test-home/` sandboxes and per-connector lockfiles are no longer swept into
the published tarball by `files: ["connectors/"]`. 1.4.0 shipped a Bun
install-cache blob this way.
- 1.4.0 shipped `.d.ts` files for two modules deleted in 1.4.0 itself, because
`dist/` was not clean at release time.

## [1.4.0] - 2026-07-26

### Removed
Expand Down
17 changes: 12 additions & 5 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,26 @@
If you discover a security vulnerability, please report it responsibly:

1. **Do not** open a public issue.
2. Email the maintainers at **security@hasna.dev** with:
2. Open a private report through
[GitHub Security Advisories](https://github.com/hasna/connectors/security/advisories/new).
Private vulnerability reporting is enabled on this repository, so any signed-in
GitHub user can file a report and only the maintainers can read it. Include:
- A description of the vulnerability
- Steps to reproduce
- Potential impact
3. You will receive an acknowledgment within 48 hours.
4. We will work with you to understand and address the issue before any public disclosure.

If the advisory form is ever unreachable, open a public issue requesting a private
contact channel — with **no** vulnerability details in it — and a maintainer will
follow up privately.

## Supported Versions

| Version | Supported |
|---------|-----------|
| 0.2.x | Yes |
| < 0.2 | No |
| 1.4.x | Yes |
| < 1.4 | No |

## Security Measures

Expand All @@ -29,8 +36,8 @@ This project implements the following security measures:
- **Security headers** (`X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`)
- **Request body size limits** (1MB max)
- **Fetch timeouts** (10s) on external HTTP requests
- **No secrets in source** — credentials stored in `~/.connectors/` only
- **No secrets in source** — credentials stored in `~/.hasna/connectors/` only

## Credential Storage

Connector credentials are stored locally at `~/.connectors/connect-{name}/`. OAuth tokens are written with `0o600` permissions. No credentials are ever sent to our servers or included in the npm package.
Connector credentials are stored locally at `~/.hasna/connectors/connect-{name}/`. OAuth tokens are written with `0o600` permissions. No credentials are ever sent to our servers or included in the npm package.
2 changes: 1 addition & 1 deletion connectors/googlephotos/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ authCmd
const userInfo = await getUserInfo(result.tokens!.accessToken);
const email = userInfo.email;

// Convert email to profile slug: andrei@hasna.com → andreihasnacom
// Convert email to profile slug: user@example.com → userexamplecom
const profileSlug = email.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();

// Create profile if it doesn't exist
Expand Down
2 changes: 1 addition & 1 deletion connectors/yousearch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export YOUSEARCH_API_KEY=your-api-key
connect-yousearch search "latest AI news" --count 5

# Web search with domain filters (POST)
connect-yousearch search-post "alumia platform" --include-domains "hasna.com,github.com"
connect-yousearch search-post "agent frameworks" --include-domains "example.com,github.com"

# Multi-step research
connect-yousearch research "What are the latest developments in AI agents?" --effort deep
Expand Down
2 changes: 1 addition & 1 deletion connectors/zendesk/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ connect-zendesk/
- **EC2 Instance**: `hasna-prod-connect-zendesk`
- **RDS Database**: `hasna-prod-connect-zendesk`
- **S3 Bucket**: `hasna-prod-connect-zendesk`
- **Remote API**: `https://connect.hasna.com/zendesk`
- **Remote API**: deployment-specific; set `ZENDESK_REMOTE_API_URL` (no shipped default)

## Key Patterns

Expand Down
4 changes: 3 additions & 1 deletion connectors/zendesk/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ EC2_USER ?= ec2-user
DEPLOY_PATH ?= /home/ec2-user/connectors/connect-zendesk
SERVICE_NAME ?= connect-zendesk
PORT ?= 21010
# Public URL the deployed server is reachable at. Deployment-specific — no default.
REMOTE_API_URL ?=

# Default target
help:
Expand Down Expand Up @@ -92,7 +94,7 @@ deploy-ec2: deploy-sync

deploy: build deploy-ec2
@echo "✅ Deployment complete"
@echo "Server running at https://connect.hasna.com/zendesk"
@echo "Server running at $(if $(REMOTE_API_URL),$(REMOTE_API_URL),<REMOTE_API_URL not set>)"

logs:
ssh $(EC2_USER)@$(EC2_HOST) "sudo journalctl -u $(SERVICE_NAME) -f"
Expand Down
5 changes: 3 additions & 2 deletions connectors/zendesk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ This connector provides programmatic access to Zendesk's Support API, including
| EC2 Instance | `hasna-prod-connect-zendesk` |
| RDS Database | `hasna-prod-connect-zendesk` |
| S3 Bucket | `hasna-prod-connect-zendesk` |
| Remote API | `https://connect.hasna.com/zendesk` |
| Remote API | configured per deployment via `ZENDESK_REMOTE_API_URL` (no default) |

## Installation

Expand Down Expand Up @@ -279,7 +279,8 @@ This connector is deployed to:
- **Database**: `hasna-prod-connect-zendesk`
- **S3**: `hasna-prod-connect-zendesk`

The remote API is accessible at `https://connect.hasna.com/zendesk`
The remote API host is deployment-specific and has no built-in default. Point the CLI at your
deployment with `ZENDESK_REMOTE_API_URL` or `connect-zendesk config set-remote-url <url>`.

## License

Expand Down
4 changes: 2 additions & 2 deletions connectors/zendesk/SCAFFOLD.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Each connector follows this naming pattern:
| EC2 Instance | `hasna-prod-connect-{name}` | `hasna-prod-connect-notion` |
| RDS Database | `hasna-prod-connect-{name}` | `hasna-prod-connect-notion` |
| S3 Bucket | `hasna-prod-connect-{name}` | `hasna-prod-connect-notion` |
| Remote API | `https://connect.hasna.com/{name}` | `https://connect.hasna.com/notion` |
| Remote API | `https://<connect-host>/{name}` | `https://connect.example.com/notion` |

## Project Structure

Expand Down Expand Up @@ -175,4 +175,4 @@ The connector is deployed to AWS infrastructure:

1. Build the project: `make build`
2. Deploy to EC2: `make deploy-ec2`
3. Configure the remote API at `https://connect.hasna.com/{name}`
3. Point the CLI at the deployment via `<NAME>_REMOTE_API_URL` (no default is shipped)
16 changes: 8 additions & 8 deletions connectors/zendesk/nginx-connector.conf
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ http {

server {
listen 80;
server_name connect.hasna.com;
server_name connect.example.com;

location / {
root /usr/share/nginx/html;
Expand Down Expand Up @@ -127,7 +127,7 @@ http {

server {
listen 80;
server_name gmail.connect.hasna.com;
server_name gmail.connect.example.com;

location / {
proxy_pass http://127.0.0.1:3001;
Expand All @@ -140,7 +140,7 @@ http {

server {
listen 80;
server_name googlecontacts.connect.hasna.com;
server_name googlecontacts.connect.example.com;

location / {
proxy_pass http://127.0.0.1:3002;
Expand All @@ -153,7 +153,7 @@ http {

server {
listen 80;
server_name googledrive.connect.hasna.com;
server_name googledrive.connect.example.com;

location / {
proxy_pass http://127.0.0.1:3003;
Expand All @@ -166,7 +166,7 @@ http {

server {
listen 80;
server_name linear.connect.hasna.com;
server_name linear.connect.example.com;

location / {
proxy_pass http://127.0.0.1:3004;
Expand All @@ -179,7 +179,7 @@ http {

server {
listen 80;
server_name notion.connect.hasna.com;
server_name notion.connect.example.com;

location / {
proxy_pass http://127.0.0.1:3005;
Expand All @@ -192,7 +192,7 @@ http {

server {
listen 80;
server_name clickbank.connect.hasna.com;
server_name clickbank.connect.example.com;

location / {
proxy_pass http://127.0.0.1:3013;
Expand All @@ -205,7 +205,7 @@ http {

server {
listen 80;
server_name zendesk.connect.hasna.com;
server_name zendesk.connect.example.com;

location / {
proxy_pass http://127.0.0.1:21010;
Expand Down
16 changes: 8 additions & 8 deletions connectors/zendesk/nginx.conf
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ upstream connect_zendesk {
keepalive 32;
}

# Main server block for connect.hasna.com/zendesk
# Add this location block to the existing connect.hasna.com server
# Main server block for connect.example.com/zendesk
# Add this location block to the existing connect.example.com server
#
# If you have a separate server block for connect.hasna.com, add:
# If you have a separate server block for connect.example.com, add:
#
# location /zendesk {
# proxy_pass http://connect_zendesk;
Expand All @@ -30,21 +30,21 @@ upstream connect_zendesk {
# proxy_read_timeout 86400;
# }

# Standalone server configuration (use if connect.hasna.com doesn't exist)
# Standalone server configuration (use if connect.example.com doesn't exist)
# Uncomment and modify as needed:
#
# server {
# listen 80;
# server_name connect.hasna.com;
# server_name connect.example.com;
# return 301 https://$host$request_uri;
# }
#
# server {
# listen 443 ssl http2;
# server_name connect.hasna.com;
# server_name connect.example.com;
#
# ssl_certificate /etc/letsencrypt/live/connect.hasna.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/connect.hasna.com/privkey.pem;
# ssl_certificate /etc/letsencrypt/live/connect.example.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/connect.example.com/privkey.pem;
#
# location /zendesk {
# proxy_pass http://connect_zendesk;
Expand Down
27 changes: 19 additions & 8 deletions connectors/zendesk/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
getConfigDir,
getBaseConfigDir,
getExportsDir,
getRemoteApiUrl,
findRemoteApiUrl,
setRemoteApiUrl,
setProfileOverride,
getCurrentProfile,
Expand Down Expand Up @@ -238,7 +238,7 @@ configCmd

configCmd
.command('set-remote-url <url>')
.description('Set remote API URL (default: https://connect.hasna.com/zendesk)')
.description('Set remote API URL (no default; also settable via ZENDESK_REMOTE_API_URL)')
.action((url: string) => {
setRemoteApiUrl(url);
success(`Remote API URL set to: ${url}`);
Expand All @@ -253,13 +253,13 @@ configCmd
const apiToken = getApiToken();
const baseUrl = getBaseUrl();
const account = getDefaultAccount();
const remoteUrl = getRemoteApiUrl();
const remoteUrl = findRemoteApiUrl();
info(`Profile: ${chalk.cyan(profile)}`);
info(`Email: ${email || chalk.gray('not set')}`);
info(`API Token: ${apiToken ? `${apiToken.substring(0, 6)}...${apiToken.substring(apiToken.length - 4)}` : chalk.gray('not set')}`);
info(`Base URL: ${baseUrl || chalk.gray('not set')}`);
info(`Default Account: ${account || chalk.gray('not set')}`);
info(`Remote API URL: ${remoteUrl}`);
info(`Remote API URL: ${remoteUrl || chalk.gray('not set')}`);
info(`Config Directory: ${getBaseConfigDir()}`);
info(`Profile Config: ${getConfigDir()}`);
info(`Exports Directory: ${getExportsDir()}`);
Expand All @@ -274,17 +274,28 @@ configCmd
});

// ============================================
// Remote API Commands (connect.hasna.com)
// Remote API Commands (host comes from ZENDESK_REMOTE_API_URL / config)
// ============================================
const remoteCmd = program
.command('remote')
.description('Interact with the remote Zendesk connector API');

// The remote host has no shipped default. Commands that need it exit with the
// connector's usual error convention rather than an uncaught throw.
function requireRemoteApiUrl(): string {
const remoteUrl = findRemoteApiUrl();
if (!remoteUrl) {
error('Remote API URL is not configured. Set ZENDESK_REMOTE_API_URL or run: connect-zendesk config set-remote-url <url>');
process.exit(1);
}
return remoteUrl;
}

remoteCmd
.command('status')
.description('Check remote API status')
.action(async () => {
const remoteUrl = getRemoteApiUrl();
const remoteUrl = requireRemoteApiUrl();
logger.command('remote status', { remoteUrl });
try {
const response = await fetch(`${remoteUrl}/status`);
Expand All @@ -301,7 +312,7 @@ remoteCmd
.command('health')
.description('Check remote API health')
.action(async () => {
const remoteUrl = getRemoteApiUrl();
const remoteUrl = requireRemoteApiUrl();
logger.command('remote health', { remoteUrl });
try {
const response = await fetch(`${remoteUrl}/health`);
Expand All @@ -321,7 +332,7 @@ remoteCmd
.command('url')
.description('Show current remote API URL')
.action(() => {
info(`Remote API URL: ${getRemoteApiUrl()}`);
info(`Remote API URL: ${findRemoteApiUrl() || chalk.gray('not set')}`);
});

// ============================================
Expand Down
2 changes: 1 addition & 1 deletion connectors/zendesk/src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/**
* connect-zendesk server
* Remote API server for Zendesk connector
* Deployed at https://connect.hasna.com/zendesk
* Deployment host is environment-specific; see nginx.conf for the reverse-proxy template.
*/

const PORT = parseInt(process.env.PORT || '3000');
Expand Down
Loading
Loading