diff --git a/.husky/pre-commit b/.husky/pre-commit index 2312dc58..8235c9c9 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1,2 @@ npx lint-staged +uv tool run --from 'git+https://github.com/ibm/detect-secrets.git@076672a9a01abdfc7ecee2e7d14f08cdccb73976' detect-secrets-hook --baseline .secrets.baseline --use-all-plugins --fail-on-unaudited -- $(git diff --cached --name-only --diff-filter=d) diff --git a/.secrets.baseline b/.secrets.baseline new file mode 100644 index 00000000..f26340b1 --- /dev/null +++ b/.secrets.baseline @@ -0,0 +1,380 @@ +{ + "exclude": { + "files": "(?x)(package-lock\\.json$)|^\\.secrets\\.baseline$|src/i18n/locales/*|openapi.json|^.secrets.baseline$", + "lines": null + }, + "generated_at": "2026-09-09T15:28:55Z", + "plugins_used": [ + { + "name": "AWSKeyDetector" + }, + { + "name": "ArtifactoryDetector" + }, + { + "name": "AzureStorageKeyDetector" + }, + { + "base64_limit": 4.5, + "name": "Base64HighEntropyString" + }, + { + "name": "BasicAuthDetector" + }, + { + "name": "BoxDetector" + }, + { + "name": "CloudantDetector" + }, + { + "ghe_instance": "github.ibm.com", + "name": "GheDetector" + }, + { + "name": "GitHubTokenDetector" + }, + { + "hex_limit": 3, + "name": "HexHighEntropyString" + }, + { + "name": "IbmCloudIamDetector" + }, + { + "name": "IbmCosHmacDetector" + }, + { + "name": "JwtTokenDetector" + }, + { + "keyword_exclude": null, + "name": "KeywordDetector" + }, + { + "name": "MailchimpDetector" + }, + { + "name": "NpmDetector" + }, + { + "name": "PrivateKeyDetector" + }, + { + "name": "SlackDetector" + }, + { + "name": "SoftlayerDetector" + }, + { + "name": "SquareOAuthDetector" + }, + { + "name": "StripeDetector" + }, + { + "name": "TwilioKeyDetector" + } + ], + "results": { + "e2e/fixtures/real-login.ts": [ + { + "hashed_secret": "35293d41139aa3c3ee67420240f14c7d3ca824c6", + "is_secret": false, + "is_verified": false, + "line_number": 11, + "type": "Secret Keyword", + "verified_result": null + } + ], + "e2e/seed/seed.ts": [ + { + "hashed_secret": "2465b40695a2c6aa41193e5c0c08a139f5277aa8", + "is_secret": false, + "is_verified": false, + "line_number": 82, + "type": "Secret Keyword", + "verified_result": null + } + ], + "server/src/lib/establish-session.ts": [ + { + "hashed_secret": "9f24a092a796f23564750a17a0c4b12d3023b21d", + "is_secret": false, + "is_verified": false, + "line_number": 49, + "type": "Secret Keyword", + "verified_result": null + } + ], + "server/src/lib/upstream-login.ts": [ + { + "hashed_secret": "45961e859cb28f6215fa1e65cf63e45f9ce908af", + "is_secret": false, + "is_verified": false, + "line_number": 22, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "6c37ecb0f99bc06aee003f37f134ebcdf8f63cd2", + "is_secret": false, + "is_verified": false, + "line_number": 63, + "type": "Secret Keyword", + "verified_result": null + } + ], + "server/src/plugins/session.ts": [ + { + "hashed_secret": "1bcc59b324708cc856541522bd845c53a709d932", + "is_secret": false, + "is_verified": false, + "line_number": 41, + "type": "Secret Keyword", + "verified_result": null + } + ], + "server/src/routes/auth/change-password-required.ts": [ + { + "hashed_secret": "36f7b6843ba1a998aaa3c91657accf409a797259", + "is_secret": false, + "is_verified": false, + "line_number": 131, + "type": "Secret Keyword", + "verified_result": null + } + ], + "server/test/auth.test.ts": [ + { + "hashed_secret": "390e5c31769c2500f14f36c9d49510422d9eeba8", + "is_secret": false, + "is_verified": false, + "line_number": 313, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "6947818ac409551f11fbaa78f0ea6391960aa5b8", + "is_secret": false, + "is_verified": false, + "line_number": 323, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "e7cdf120b5d9fb58528b5180130f00035a4e1728", + "is_secret": false, + "is_verified": false, + "line_number": 343, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "81942c7a0d96dd258e2aa5b35da8bd55dd4db36b", + "is_secret": false, + "is_verified": false, + "line_number": 344, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "e9e4a6d29515c8e53e4df7bc6646a23237b8f862", + "is_secret": false, + "is_verified": false, + "line_number": 518, + "type": "Secret Keyword", + "verified_result": null + } + ], + "server/test/password-reset.test.ts": [ + { + "hashed_secret": "d4675acdaf0a38029067f3003675e43195329e1f", + "is_secret": false, + "is_verified": false, + "line_number": 107, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/api/changePasswordRequiredErrors.ts": [ + { + "hashed_secret": "d506bd5213c46bd49e16c634754ad70113408252", + "is_secret": false, + "is_verified": false, + "line_number": 8, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/api/passwordReset.test.ts": [ + { + "hashed_secret": "fefda504251b73d30a7cf3d047d241388a3c11b3", + "is_secret": false, + "is_verified": false, + "line_number": 39, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/api/passwordReset.ts": [ + { + "hashed_secret": "227a28bc23e315129abcf938441916fcd53d1586", + "is_secret": false, + "is_verified": false, + "line_number": 40, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/api/tools.test.ts": [ + { + "hashed_secret": "dae4772611b2c349a1167c87fb9132ef078c0bce", + "is_secret": false, + "is_verified": false, + "line_number": 147, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/auth/AuthContext.test.tsx": [ + { + "hashed_secret": "c692a7b0b5727d7cad7367faec852a9f1d2f652d", + "is_secret": false, + "is_verified": false, + "line_number": 390, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/components/mcp-servers/AuthComponents.test.tsx": [ + { + "hashed_secret": "1f5e25be9b575e9f5d39c82dfd1d9f4d73f1975c", + "is_secret": false, + "is_verified": false, + "line_number": 114, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/components/server-catalog/CatalogApiKeyDialog.tsx": [ + { + "hashed_secret": "f75b33f87ffeacb3a4f793a09693e672e07449ff", + "is_secret": false, + "is_verified": false, + "line_number": 91, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/components/servers/HandshakeTestPanel.tsx": [ + { + "hashed_secret": "a08f4748b0575ba240be02206f9ab1ac1672e173", + "is_secret": false, + "is_verified": false, + "line_number": 46, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "d04e85d7f14f9f013682085881709a3b648f3ce5", + "is_secret": false, + "is_verified": false, + "line_number": 54, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/components/servers/TestConnectionPanel.tsx": [ + { + "hashed_secret": "d04e85d7f14f9f013682085881709a3b648f3ce5", + "is_secret": false, + "is_verified": false, + "line_number": 36, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/components/tools/ToolAuth.test.tsx": [ + { + "hashed_secret": "1f5e25be9b575e9f5d39c82dfd1d9f4d73f1975c", + "is_secret": false, + "is_verified": false, + "line_number": 67, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/components/tools/ToolForm.test.tsx": [ + { + "hashed_secret": "1f5e25be9b575e9f5d39c82dfd1d9f4d73f1975c", + "is_secret": false, + "is_verified": false, + "line_number": 494, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "d5d4cd07616a542891b7ec2d0257b3a24b69856e", + "is_secret": false, + "is_verified": false, + "line_number": 505, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/components/tools/ToolForm.tsx": [ + { + "hashed_secret": "d8724fc885deb0737089cba0a4365ad613840897", + "is_secret": false, + "is_verified": false, + "line_number": 45, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/hooks/useToolForm.ts": [ + { + "hashed_secret": "b67ebc1b61d5c2b09edf3b99300bef362c05d999", + "is_secret": false, + "is_verified": false, + "line_number": 306, + "type": "Secret Keyword", + "verified_result": null + }, + { + "hashed_secret": "4993f6e43a98e406d93c7afaa68c9c9ac9773538", + "is_secret": false, + "is_verified": false, + "line_number": 568, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/lib/passwordPolicy.ts": [ + { + "hashed_secret": "45c43fe97e3a06ab078b0eeff6fbe622cc417a25", + "is_secret": false, + "is_verified": false, + "line_number": 13, + "type": "Secret Keyword", + "verified_result": null + } + ], + "src/pages/ServerCatalog.test.tsx": [ + { + "hashed_secret": "2e7a7ee14caebf378fc32d6cf6f557f347c96773", + "is_secret": false, + "is_verified": false, + "line_number": 741, + "type": "Secret Keyword", + "verified_result": null + } + ] + }, + "version": "0.13.1+ibm.64.dss", + "word_list": { + "file": null, + "hash": null + } +} diff --git a/README.md b/README.md index 0142f9ff..8aaead31 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,22 @@ npm run format:check - Single quotes: `false` (use double quotes) - Print width: `100` +### Secrets Scanning + +[IBM's hardened detect-secrets fork](https://github.com/ibm/detect-secrets) scans for committed secrets. A `.secrets.baseline` file at the repo root records known false positives so the scanner only alerts on new findings. + +```bash +# Scan the repo and update .secrets.baseline +npm run secrets:scan + +# Interactively audit findings (mark false positives) +npm run secrets:audit +``` + +The scan also runs automatically as a **pre-commit hook** (via Husky). + +**If the pre-commit hook blocks your commit:** a new secret-like pattern was found. Either remove the value, or run `npm run secrets:audit` to review and mark it as a false positive, then re-commit. + ## Testing ### Test Framework @@ -384,6 +400,8 @@ setup. | `npm run e2e:debug` | Playwright Inspector | | `npm run e2e:install` | Install Playwright browsers | | `npm run e2e:report` | Open last Playwright report | +| `npm run secrets:scan` | Scan repo for secrets, update `.secrets.baseline` | +| `npm run secrets:audit` | Interactively audit findings in `.secrets.baseline` | ## Internationalization (i18n) diff --git a/e2e/auth/login-flow.spec.ts b/e2e/auth/login-flow.spec.ts index 49fc457b..890d6b5e 100644 --- a/e2e/auth/login-flow.spec.ts +++ b/e2e/auth/login-flow.spec.ts @@ -4,7 +4,7 @@ import { APP, TOKEN_STORAGE_KEY } from "../utils/paths"; const IS_REAL_API = process.env.E2E_REAL_API === "true"; // Only the "successful login" test needs a user that really exists. const VALID_EMAIL = IS_REAL_API ? (process.env.E2E_TEST_EMAIL ?? "") : "test@example.com"; -const VALID_PASSWORD = IS_REAL_API ? (process.env.E2E_TEST_PASSWORD ?? "") : "password123"; +const VALID_PASSWORD = IS_REAL_API ? (process.env.E2E_TEST_PASSWORD ?? "") : "password123"; //pragma: allowlist secret test.describe("Login flow", () => { test.beforeEach(async ({ page, apiMock }) => { diff --git a/e2e/seed/seed.ts b/e2e/seed/seed.ts index 87c92944..1b320d95 100644 --- a/e2e/seed/seed.ts +++ b/e2e/seed/seed.ts @@ -8,8 +8,8 @@ const GATEWAY_URL = process.env.E2E_GATEWAY_URL ?? "http://localhost:4444"; const AUTH_HEADER_NAME = process.env.CONTEXTFORGE_AUTH_HEADER_NAME ?? "Authorization"; const EMAIL = process.env.E2E_TEST_EMAIL; -const BOOTSTRAP_PASSWORD = process.env.E2E_BOOTSTRAP_PASSWORD ?? "changeme-e2e-bootstrap-pwd1"; -const NEW_PASSWORD = process.env.E2E_TEST_PASSWORD; +const BOOTSTRAP_PASSWORD = process.env.E2E_BOOTSTRAP_PASSWORD ?? "changeme-e2e-bootstrap-pwd1"; //pragma: allowlist secret +const NEW_PASSWORD = process.env.E2E_TEST_PASSWORD; //pragma: allowlist secret const HEALTH_RETRIES = 30; const HEALTH_RETRY_DELAY_MS = 2000; diff --git a/package.json b/package.json index ccde7026..1774d98c 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,8 @@ "i18n:extract": "formatjs extract 'src/**/*.{ts,tsx}' --out-file src/i18n/extracted.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'", "i18n:compile": "formatjs compile-folder --ast src/i18n/locales src/i18n/compiled", "generate": "orval", + "secrets:scan": "uv tool run --from 'git+https://github.com/ibm/detect-secrets.git@076672a9a01abdfc7ecee2e7d14f08cdccb73976' detect-secrets scan --update .secrets.baseline --use-all-plugins --exclude-files '(?x)(package-lock\\.json$)|^\\.secrets\\.baseline$|src/i18n/locales/*|openapi.json'", + "secrets:audit": "uv tool run --from 'git+https://github.com/ibm/detect-secrets.git@076672a9a01abdfc7ecee2e7d14f08cdccb73976' detect-secrets audit .secrets.baseline", "prepare": "husky || true" }, "dependencies": {