forgedock 1.21.0 → 1.23.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -183,7 +183,20 @@ if [ "$CHURN" = "true" ] || [ "$DANGER" = "true" ]; then
183
183
  for d in $(top_domains 1); do add_agent "$d"; trig churn-or-danger-zone; done
184
184
  fi
185
185
  hit() { printf '%s\n' "$FILES" | grep -qiE "$1"; }
186
- hit '(^|[/_.-])(auth([^o]|o[^r]|$)|authori[sz]|oauth|session|jwt|login|logout|passw(or)?d|permission|rbac|acl([/_.-]|$)|tokens?([/_.-]|$)|api[-_]?keys?|credential|trust[-_]?filter|trusted[-_a-z]*)|(^|/)deps\.py$' && { add_agent Auth; trig auth-path; }
186
+ # Auth path gate (forge#4071). Docs and prose are never auth by path (the content gate still applies). Strong names match
187
+ # any path segment; weak names (session, login, permission, token) only as a whole word in a code file outside design-system
188
+ # and asset dirs, so design-tokens/, SessionTimeline.tsx, LoginButton.tsx and cursor-token files do not spawn Auth. A CamelCase
189
+ # weak name, leading or after a CamelCase prefix (LoginController.java, RefreshTokenService.java), still counts outside UI
190
+ # component files and dirs.
191
+ AUTH_FILES=$(printf '%s\n' "$FILES" | grep -viE '(^|/)docs?/|\.(md|mdx|markdown|rst|adoc|txt)$' || true)
192
+ AUTH_CODE=$(printf '%s\n' "$AUTH_FILES" | grep -iE '\.(py|pyi|ts|tsx|js|jsx|mjs|cjs|go|rb|java|kt|kts|scala|rs|php|cs|ex|exs|swift|c|cc|cpp|h|hpp|sh|bash|lua|dart|groovy|pl|vue|svelte)$' \
193
+ | grep -viE '(^|/)(design[-_]?(system|tokens?)|assets?|static|images?|icons?|fonts?|styles?|themes?)/' || true)
194
+ if printf '%s\n' "$AUTH_FILES" | grep -qiE '(^|[/_.-])(auth([^o]|o[^r]|$)|authori[sz]|oauth|jwt|passw(or)?d|rbac|acl([/_.-]|$)|api[-_]?keys?|credential|trust[-_]?filter|trusted[-_a-z]*)|(^|/)deps\.py$' \
195
+ || printf '%s\n' "$AUTH_CODE" | grep -qiE '(^|[/_])(sessions?|login|logout|permissions?|tokens?)([/_.-]|$)' \
196
+ || printf '%s\n' "$AUTH_CODE" | grep -viE '\.(tsx|jsx|vue|svelte)$|(^|/)(components?|pages|views|ui|widgets)/' \
197
+ | grep -qE '((^|[/_])([Ss]essions?|[Ll]ogin|[Ll]ogout|[Pp]ermissions?|[Tt]okens?)|[a-z0-9](Sessions?|Login|Logout|Permissions?|Tokens?))([A-Z]|[/_.-]|$)'; then
198
+ add_agent Auth; trig auth-path
199
+ fi
187
200
  hit '^\.github/|(^|/)Dockerfile|docker-compose|(^|/)(docker-)?compose[^/]*\.ya?ml$|(^|/)deploy[^/]*\.sh$|(^|/)docker-entrypoint[^/]*$|(^|/)(deploy|infra|traefik|nginx|k8s|helm|terraform)/|\.tf$' && { add_agent Infra; trig infra-path; }
188
201
  hit 'migration|\.sql$' && { add_agent Database; trig migration-path; }
189
202
  hit '^sdk/|openapi.*\.json$|openapi-versions/' && { add_agent API; trig sdk-path; }
@@ -130,6 +130,19 @@ run "docs/author.md" --scores "$SC_QUIET" --changed-lines 20 --changed-files 1
130
130
  expect_not "no auth path docs/author.md" auth
131
131
  run "svc/tokenizer.py" --scores "$SC_QUIET" --changed-lines 20 --changed-files 1
132
132
  expect_not "no-auth-path-svc/tokenizer.py" auth
133
+ # forge#4071: weak names (session, token, permission, login) select Auth only as whole words in code files; docs never
134
+ for f in design-tokens/colors.json src/SessionTimeline.tsx docs/permissions.md src/components/LoginButton.tsx src/editor/cursor-token.ts docs/auth.md \
135
+ src/components/LoginForm.ts svc/tokenizer.py src/JsonTokenizer.java src/components/UserSessionBadge.ts; do
136
+ run "$f" --scores "$SC_QUIET" --changed-lines 20 --changed-files 1
137
+ expect_not "no-auth-path-$f" auth
138
+ done
139
+ for f in src/auth/session.py api/tokens.py middleware/permissions.ts lib/jwt.go src/session/store.ts svc/login_handler.go \
140
+ src/main/java/com/x/LoginController.java src/main/java/com/x/SessionManager.java src/TokenService.kt src/PermissionChecker.cs middleware/PermissionGuard.ts \
141
+ src/main/java/com/x/RefreshTokenService.java src/AccessTokenValidator.kt src/main/java/com/x/UserSessionManager.java \
142
+ src/CsrfTokenFilter.java src/AdminPermissionChecker.cs src/OtpLoginHandler.ts src/main/java/com/x/RefreshToken.java; do
143
+ run "$f" --scores "$SC_QUIET" --changed-lines 20 --changed-files 1
144
+ expect_has "auth-path-$f" auth
145
+ done
133
146
  run "commands/review-pr.md" --scores "AUTH=3 INFRA=0 DATABASE=0 BILLING=0 CONCURRENCY=0" --changed-lines 20 --changed-files 1
134
147
  expect_not "auth-from-prose-score-alone" auth
135
148
  run "commands/review-pr.md" --scores "AUTH=3 INFRA=3 DATABASE=0 BILLING=0 CONCURRENCY=0" --changed-lines 120
package/bin/lean/risk.mjs DELETED
@@ -1,49 +0,0 @@
1
- // SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
2
- // SPDX-License-Identifier: AGPL-3.0-or-later
3
- /** Lean path (#4081): deterministic path/keyword helpers. Pure, codebase-agnostic, no model calls. */
4
-
5
- const RISK_PATH = /(^|[/._-])(auth|authn|authz|oauth|login|session|crypto|cipher|secret|secrets|credential|credentials|token|permission|permissions|acl|rbac|migration|migrations|payment|payments|billing|stripe)([/._-]|$)|^\.github\/workflows\//i;
6
- const RISK_KEYWORD = /\b(auth\w*|crypto\w*|secret\w*|permission\w*|migration\w*|payment\w*)\b/i;
7
-
8
- /** @returns {{tier:"high"|"low", reasons:string[]}} */
9
- export function riskTier(files = [], text = "") {
10
- const reasons = [];
11
- for (const f of files) if (RISK_PATH.test(f)) reasons.push(`path:${f}`);
12
- const kw = RISK_KEYWORD.exec(String(text));
13
- if (kw) reasons.push(`keyword:${kw[1].toLowerCase()}`);
14
- return { tier: reasons.length ? "high" : "low", reasons };
15
- }
16
-
17
- const DOC_FILE = /(\.(md|mdx|rst|txt|adoc)$)|(^|\/)(docs?|documentation)\//i;
18
- export const isDocsOnly = (files) => files.length > 0 && files.every((f) => DOC_FILE.test(f));
19
-
20
- const TEST_FILE = /(^|\/)(tests?|__tests__|spec|specs|e2e)\/|\.(test|spec)\.[a-z0-9]+$|_(test|spec)\.[a-z0-9]+$|(^|\/)test_[^/]+\.[a-z0-9]+$|Tests?\.(java|kt|cs|swift|scala)$/i;
21
- export const isTestFile = (f) => TEST_FILE.test(f);
22
-
23
- export const LARGE_DIFF = { files: 15, lines: 400 };
24
- export const isLargeDiff = ({ files = 0, lines = 0 } = {}) => files >= LARGE_DIFF.files || lines >= LARGE_DIFF.lines;
25
-
26
- /** `auto` goes full only when risk is high AND the diff is large; lean otherwise. */
27
- export function choosePath(requested, risk, stats) {
28
- if (requested === "lean" || requested === "full") return requested;
29
- return risk?.tier === "high" && isLargeDiff(stats) ? "full" : "lean";
30
- }
31
-
32
- /** Blocking = CRITICAL/HIGH, or CONFIRMED MEDIUM in introduced code. */
33
- export function isBlocking(f) {
34
- const sev = String(f?.severity ?? "").toUpperCase();
35
- if (sev === "CRITICAL" || sev === "HIGH") return true;
36
- return sev === "MEDIUM" && String(f?.confidence ?? "").toUpperCase() === "CONFIRMED"
37
- && String(f?.scope ?? "introduced").toLowerCase() === "introduced";
38
- }
39
-
40
- /** Extract the findings array from a reviewer reply (last json fence, else first [...]). null when unparseable. */
41
- export function parseFindings(text) {
42
- const s = String(text ?? "");
43
- const fences = [...s.matchAll(/```(?:json)?\s*([\s\S]*?)```/g)].map((m) => m[1]);
44
- const cands = [...fences.reverse(), s.slice(s.indexOf("["), s.lastIndexOf("]") + 1)];
45
- for (const c of cands) {
46
- try { const v = JSON.parse(c.trim()); if (Array.isArray(v)) return v.filter((x) => x && typeof x === "object"); } catch { /* next */ }
47
- }
48
- return null;
49
- }