Skip to content
Open
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
26 changes: 13 additions & 13 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"name": "agnix",
"source": "./plugins/agnix",
"description": "AI アシスタント設定ファイルリンター(agnix)統合",
"version": "0.0.12"
"version": "0.0.13"
},
{
"name": "cc-hooks-ts",
Expand All @@ -20,49 +20,49 @@
"name": "code-review",
"source": "./plugins/code-review",
"description": "PR・コードレビューワークフロー支援",
"version": "0.2.6"
"version": "0.2.7"
},
{
"name": "context",
"source": "./plugins/context",
"description": "プロジェクトコンテキスト管理(AGENTS.md, rules)",
"version": "0.2.5"
"version": "0.2.6"
},
{
"name": "debug",
"source": "./plugins/debug",
"description": "plugin debug 用",
"version": "0.0.14"
"version": "0.0.15"
},
{
"name": "devkit",
"source": "./plugins/devkit",
"description": "Development toolkit — tech stack definitions, project setup, and quality automation",
"version": "0.3.8"
"version": "0.3.9"
},
{
"name": "discord-notify",
"source": "./plugins/discord-notify",
"description": "Discord通知 — idle時にセッションの最新メッセージをDiscordスレッドに投稿",
"version": "0.0.3"
"version": "0.0.4"
},
{
"name": "eslint-lsp",
"source": "./plugins/eslint-lsp",
"description": "ESLint 9 Language Server integration for Claude Code",
"version": "0.1.11"
"version": "0.1.12"
},
{
"name": "github-workflow",
"source": "./plugins/github-workflow",
"description": "Git/GitHub ワークフロー支援 — Stop 時にブランチ状態とコンフリクトを通知",
"version": "0.0.7"
"version": "0.0.8"
},
{
"name": "mutils",
"source": "./plugins/mutils",
"description": "汎用ユーティリティ(フック・スキル)",
"version": "0.18.9"
"version": "0.18.10"
},
{
"name": "ops-harbor",
Expand All @@ -80,19 +80,19 @@
"name": "progress-tracker",
"source": "./plugins/progress-tracker",
"description": "セッション進捗ファイルの自動作成・管理",
"version": "0.1.12"
"version": "0.1.13"
},
{
"name": "research",
"source": "./plugins/research",
"description": "調査・分析ツール(画像解析、ライブラリ調査、Figma連携)",
"version": "0.1.10"
"version": "0.1.11"
},
{
"name": "sdd",
"source": "./plugins/sdd",
"description": "Spec Driven Development (SDD) ワークフロー支援コマンド集",
"version": "0.5.15"
"version": "0.5.16"
},
{
"name": "swarm",
Expand All @@ -104,7 +104,7 @@
"name": "ui-dev",
"source": "./plugins/ui-dev",
"description": "Figma x AI UI implementation workflow",
"version": "0.0.20"
"version": "0.0.21"
},
{
"name": "use-codex",
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
with:
bun-version: "1.3.11"
- run: bun install --frozen-lockfile
- run: bunx turbo run check
- run: bun run check

typecheck:
runs-on: ubuntu-latest
Expand All @@ -25,7 +25,7 @@ jobs:
with:
bun-version: "1.3.11"
- run: bun install --frozen-lockfile
- run: bunx turbo run typecheck
- run: bun run typecheck

test:
runs-on: ubuntu-latest
Expand All @@ -35,7 +35,7 @@ jobs:
with:
bun-version: "1.3.11"
- run: bun install --frozen-lockfile
- run: bunx turbo run test -- --coverage
- run: bun run test:coverage

knip:
runs-on: ubuntu-latest
Expand All @@ -55,4 +55,4 @@ jobs:
with:
bun-version: "1.3.11"
- run: bun install --frozen-lockfile
- run: bunx turbo run build
- run: bun run build
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/sh
set -eu

bunx lint-staged
bun run lint-staged

printf '%s\n' "Running security check..."

Expand Down
4 changes: 2 additions & 2 deletions .husky/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ bun run test
bun run knip

bun run hooks:plugin-list-sync
bunx oxfmt AGENTS.md .claude-plugin/marketplace.json
bun run fmt:generated-plugin-list

if [ -n "$(git status --porcelain -- AGENTS.md .claude-plugin/marketplace.json)" ]; then
git add AGENTS.md .claude-plugin/marketplace.json
Expand All @@ -19,7 +19,7 @@ if [ -n "$(git status --porcelain -- AGENTS.md .claude-plugin/marketplace.json)"
fi

bun run docs
bunx oxfmt docs/api/
bun run fmt:generated-docs

if [ -n "$(git status --porcelain -- docs/api)" ]; then
git add docs/api
Expand Down
6 changes: 5 additions & 1 deletion apps/ops-harbor-control-plane/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,11 @@ export function loadStoredConfig(
return normalizeStoredConfig(
v.parse(StoredConfigSchema, JSON.parse(readFileSync(path, "utf-8"))),
);
} catch {
} catch (e) {
// ENOENT is guarded by existsSync above. Remaining errors are JSON parse
// or schema failures from a corrupt config file. Log and return defaults
// so the control plane can still start and write a fresh config.
console.error("[ops-harbor-control-plane] loadStoredConfig: failed to parse config:", e);
return {};
Comment on lines +97 to 102
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid logging raw parse/validation errors for secret-bearing config.

Line 101 logs the full caught error object, which can expose sensitive config values in logs (especially on schema-validation failures). Log a sanitized error summary instead.

🔧 Suggested safe logging change
-  } catch (e) {
+  } catch (e) {
     // ENOENT is guarded by existsSync above. Remaining errors are JSON parse
     // or schema failures from a corrupt config file. Log and return defaults
     // so the control plane can still start and write a fresh config.
-    console.error("[ops-harbor-control-plane] loadStoredConfig: failed to parse config:", e);
+    const errorType = e instanceof Error ? e.name : typeof e;
+    console.error(
+      `[ops-harbor-control-plane] loadStoredConfig: failed to parse config (${errorType})`,
+    );
     return {};
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ops-harbor-control-plane/src/lib/config.ts` around lines 97 - 102, The
catch block in loadStoredConfig currently logs the full error object which may
contain secret-bearing config values; change the catch to log a sanitized
summary (e.g. a short message plus non-sensitive error type or e.message only)
instead of the entire error object and avoid printing full stack traces—replace
console.error("[ops-harbor-control-plane] loadStoredConfig: failed to parse
config:", e) with a safe log like a single-line summary indicating
parse/validation failure and the error name or message only (or a hardcoded
hint) so that loadStoredConfig still returns {} but no sensitive data is
emitted.

}
}
Expand Down
14 changes: 10 additions & 4 deletions apps/ops-harbor-control-plane/src/lib/github.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { afterEach, describe, expect, test, vi } from "vitest";
import { describe, expect, test as base, vi } from "vitest";
import { generateKeyPairSync } from "node:crypto";
import {
ensureGitHubAppWebhookUrl,
Expand All @@ -10,9 +10,15 @@ import {
const { privateKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });
const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" }).toString();

afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
const test = base.extend<{ _mocks: void }>({
_mocks: [
async ({}, use) => {
await use();
vi.restoreAllMocks();
vi.unstubAllGlobals();
},
{ auto: true },
],
});

describe("listInstallations", () => {
Expand Down
14 changes: 10 additions & 4 deletions apps/ops-harbor-control-plane/src/server.test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { afterEach, describe, expect, test, vi } from "vitest";
import { describe, expect, test as base, vi } from "vitest";
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { openControlPlaneApps } from "./server.js";
import { loadStoredConfig, readProcessEnv } from "./lib/config.js";

afterEach(() => {
vi.restoreAllMocks();
vi.unstubAllGlobals();
const test = base.extend<{ _mocks: void }>({
_mocks: [
async ({}, use) => {
await use();
vi.restoreAllMocks();
vi.unstubAllGlobals();
},
{ auto: true },
],
});

describe("openControlPlaneApps", () => {
Expand Down
20 changes: 20 additions & 0 deletions apps/ops-harbor/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as v from "valibot";

const EnvSchema = v.object({
HOME: v.optional(v.string()),
LANG: v.optional(v.string()),
LC_ALL: v.optional(v.string()),
LOGNAME: v.optional(v.string()),
PATH: v.optional(v.string()),
SHELL: v.optional(v.string()),
SSH_AUTH_SOCK: v.optional(v.string()),
TEMP: v.optional(v.string()),
TERM: v.optional(v.string()),
TMP: v.optional(v.string()),
TMPDIR: v.optional(v.string()),
USER: v.optional(v.string()),
XDG_CONFIG_HOME: v.optional(v.string()),
XDG_STATE_HOME: v.optional(v.string()),
});

export const env = v.parse(EnvSchema, process.env);
23 changes: 2 additions & 21 deletions apps/ops-harbor/src/lib/automation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
import { randomUUID } from "node:crypto";
import { existsSync } from "node:fs";
import { spawn } from "node:child_process";
import * as v from "valibot";
import type { OpsHarborConfig, PullRequestOverride, RepositoryConfig } from "./config";
import { env } from "../env";
import { ControlPlaneClient } from "./control-plane-client";
import { recordAutomationRun } from "./local-db";

Expand All @@ -19,27 +19,8 @@ type CommandResult = {
stderr: string;
};

const RunnerProcessEnvSchema = v.object({
HOME: v.optional(v.string()),
LANG: v.optional(v.string()),
LC_ALL: v.optional(v.string()),
LOGNAME: v.optional(v.string()),
PATH: v.optional(v.string()),
SHELL: v.optional(v.string()),
SSH_AUTH_SOCK: v.optional(v.string()),
TEMP: v.optional(v.string()),
TERM: v.optional(v.string()),
TMP: v.optional(v.string()),
TMPDIR: v.optional(v.string()),
USER: v.optional(v.string()),
XDG_CONFIG_HOME: v.optional(v.string()),
XDG_STATE_HOME: v.optional(v.string()),
});

const validatedProcessEnv = Object.fromEntries(
Object.entries(v.parse(RunnerProcessEnvSchema, process.env)).filter(
(entry): entry is [string, string] => entry[1] !== undefined,
),
Object.entries(env).filter((entry): entry is [string, string] => entry[1] !== undefined),
);

function resolveRepositoryConfig(
Expand Down
10 changes: 8 additions & 2 deletions apps/ops-harbor/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,14 @@ export async function loadConfig(): Promise<OpsHarborConfig> {
normalizeConfig(v.parse(ConfigSchema, JSON.parse(raw))),
detectedAuthorLogin,
);
} catch {
return withDetectedAuthorLogin(defaultConfig(), detectedAuthorLogin);
} catch (e) {
// ENOENT is expected on first run before the config file is created
if (e instanceof Error && "code" in e && (e as NodeJS.ErrnoException).code === "ENOENT") {
return withDetectedAuthorLogin(defaultConfig(), detectedAuthorLogin);
}
// Schema/parse errors indicate a corrupt or incompatible config file — propagate
console.error("[ops-harbor] loadConfig: failed to load or parse config:", e);
throw e;
Comment on lines +120 to +121
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid logging raw thrown objects from config load failures.

Line [120] logs the full caught value. In failure cases, raw error objects can include sensitive config context (for example token-bearing content). Log a sanitized message and still rethrow.

🔒 Suggested safe logging adjustment
-    console.error("[ops-harbor] loadConfig: failed to load or parse config:", e);
+    const errorSummary =
+      e instanceof Error ? { name: e.name } : { type: typeof e };
+    console.error("[ops-harbor] loadConfig: failed to load or parse config", errorSummary);
     throw e;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ops-harbor/src/lib/config.ts` around lines 120 - 121, In loadConfig,
avoid logging the raw caught object (currently console.error("[ops-harbor]
loadConfig: failed to load or parse config:", e)); instead log a sanitized
message and minimal non-sensitive detail (e.g., console.error("[ops-harbor]
loadConfig: failed to load or parse config:", e?.message ?? "unknown error"))
and then rethrow the original error (throw e) so callers still get the full
exception; update the logging in the catch block around loadConfig to output
only the safe message/fields rather than the entire error object.

}
}

Expand Down
4 changes: 3 additions & 1 deletion apps/ops-harbor/src/lib/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export async function syncFromControlPlane(
try {
const result = await client.triggerSync(config.authorLogin);
synchronized = result.synchronized;
} catch {
} catch (e) {
// triggerSync is best-effort; network/control-plane errors should not abort the sync
console.error("[ops-harbor] syncFromControlPlane: triggerSync failed:", e);
synchronized = undefined;
Comment on lines +16 to 19
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Sanitize triggerSync error logging to avoid accidental secret exposure.

Line [18] logs the raw thrown object. Client/network errors can contain sensitive request metadata; prefer sanitized fields.

🔒 Suggested safe logging adjustment
-    } catch (e) {
+    } catch (e) {
       // triggerSync is best-effort; network/control-plane errors should not abort the sync
-      console.error("[ops-harbor] syncFromControlPlane: triggerSync failed:", e);
+      const errorSummary =
+        e instanceof Error ? { name: e.name } : { type: typeof e };
+      console.error("[ops-harbor] syncFromControlPlane: triggerSync failed", errorSummary);
       synchronized = undefined;
     }
📝 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
} catch (e) {
// triggerSync is best-effort; network/control-plane errors should not abort the sync
console.error("[ops-harbor] syncFromControlPlane: triggerSync failed:", e);
synchronized = undefined;
} catch (e) {
// triggerSync is best-effort; network/control-plane errors should not abort the sync
const errorSummary =
e instanceof Error ? { name: e.name } : { type: typeof e };
console.error("[ops-harbor] syncFromControlPlane: triggerSync failed", errorSummary);
synchronized = undefined;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ops-harbor/src/lib/sync.ts` around lines 16 - 19, The catch in
syncFromControlPlane currently logs the raw thrown object (variable e) which can
leak sensitive request data; replace the console.error("[ops-harbor]
syncFromControlPlane: triggerSync failed:", e) with a sanitized log that only
includes non-sensitive fields (e.g., e.name, e.message, e.code or e.status) or a
small sanitizedError object produced by a new helper (e.g., sanitizeError(e))
and avoid logging stacks, headers, bodies or the full error object; keep the
existing synchronized = undefined behavior. Use the symbols
syncFromControlPlane, triggerSync, and the local variable e to locate and change
the logging.

}
}
Expand Down
4 changes: 2 additions & 2 deletions apps/sdd-webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@
"valibot": "^1.2.0"
},
"devDependencies": {
"@repo/ts-config": "*",
"@repo/vitest-config": "*",
"@repo/ts-config": "workspace:*",
"@repo/vitest-config": "workspace:*",
"@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.1.18",
"@types/react": "^19.0.0",
Expand Down
15 changes: 15 additions & 0 deletions apps/sdd-webapp/src/env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as v from "valibot";

const EnvSchema = v.object({
// Default: ~/.config/sdd-webapp (resolved at call-site via homedir())
SDD_WEBAPP_CONFIG_DIR: v.optional(v.string()),
});

// Validate at startup (fail-fast), read at call time (testable)
v.parse(EnvSchema, process.env);

export const env = {
get SDD_WEBAPP_CONFIG_DIR() {
return process.env.SDD_WEBAPP_CONFIG_DIR;
},
};
Loading
Loading