sdlc-agent-4-enterprise-server 1.47.1 → 1.48.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.
- package/.kiro/agents/prompts/sm-agent.md +13 -6
- package/.kiro/agents/sm-agent.md +13 -6
- package/Dockerfile +5 -2
- package/README.md +3 -2
- package/dist/admin/db/config.js +5 -3
- package/dist/admin/db/config.js.map +1 -1
- package/dist/admin/sanitize.d.ts +12 -0
- package/dist/admin/sanitize.js +19 -0
- package/dist/admin/sanitize.js.map +1 -1
- package/dist/database/adapters/PostgresAdapter.d.ts +9 -1
- package/dist/database/adapters/PostgresAdapter.js +40 -41
- package/dist/database/adapters/PostgresAdapter.js.map +1 -1
- package/dist/database/adapters/PostgresAsyncAdapter.d.ts +1 -1
- package/dist/database/adapters/PostgresAsyncAdapter.js +3 -3
- package/dist/database/adapters/PostgresAsyncAdapter.js.map +1 -1
- package/dist/database/adapters/pg-sql-utils.d.ts +31 -0
- package/dist/database/adapters/pg-sql-utils.js +78 -0
- package/dist/database/adapters/pg-sql-utils.js.map +1 -0
- package/dist/database/migration/pg-schema-ensure.js +11 -3
- package/dist/database/migration/pg-schema-ensure.js.map +1 -1
- package/dist/engine/context/token-budget-manager.d.ts +1 -0
- package/dist/engine/context/token-budget-manager.js +4 -0
- package/dist/engine/context/token-budget-manager.js.map +1 -1
- package/dist/engine/enrichment/CodeEnrichmentHandler.js +21 -1
- package/dist/engine/enrichment/CodeEnrichmentHandler.js.map +1 -1
- package/dist/engine/enrichment/CodeEnrichmentPromptBuilder.d.ts +19 -1
- package/dist/engine/enrichment/CodeEnrichmentPromptBuilder.js +66 -6
- package/dist/engine/enrichment/CodeEnrichmentPromptBuilder.js.map +1 -1
- package/dist/engine/enrichment/budget-guard.d.ts +20 -0
- package/dist/engine/enrichment/budget-guard.js +32 -0
- package/dist/engine/enrichment/budget-guard.js.map +1 -0
- package/dist/engine/enrichment/reduction-pipeline.d.ts +39 -0
- package/dist/engine/enrichment/reduction-pipeline.js +39 -0
- package/dist/engine/enrichment/reduction-pipeline.js.map +1 -0
- package/dist/engine/graph/graph-schema-ddl.js +1 -0
- package/dist/engine/graph/graph-schema-ddl.js.map +1 -1
- package/dist/models/pega-schema.models.d.ts +36 -36
- package/dist/modules/memory/dispatchers/analytics.js +8 -2
- package/dist/modules/memory/dispatchers/analytics.js.map +1 -1
- package/dist/modules/memory/dispatchers/crud.js +12 -4
- package/dist/modules/memory/dispatchers/crud.js.map +1 -1
- package/dist/modules/memory/llm/LLMInitializer.js +18 -0
- package/dist/modules/memory/llm/LLMInitializer.js.map +1 -1
- package/dist/modules/memory/llm/chunked-reduce.d.ts +11 -0
- package/dist/modules/memory/llm/chunked-reduce.js +29 -0
- package/dist/modules/memory/llm/chunked-reduce.js.map +1 -0
- package/dist/modules/memory/llm/context-window.d.ts +22 -0
- package/dist/modules/memory/llm/context-window.js +160 -0
- package/dist/modules/memory/llm/context-window.js.map +1 -0
- package/dist/modules/memory/llm/error-classifier.d.ts +23 -0
- package/dist/modules/memory/llm/error-classifier.js +127 -0
- package/dist/modules/memory/llm/error-classifier.js.map +1 -0
- package/dist/modules/memory/llm/ollama-adapter.js +3 -1
- package/dist/modules/memory/llm/ollama-adapter.js.map +1 -1
- package/dist/modules/memory/llm/openai-adapter.js +3 -1
- package/dist/modules/memory/llm/openai-adapter.js.map +1 -1
- package/dist/modules/memory/task-queue/PendingTaskRepository.d.ts +14 -1
- package/dist/modules/memory/task-queue/PendingTaskRepository.js +28 -2
- package/dist/modules/memory/task-queue/PendingTaskRepository.js.map +1 -1
- package/dist/modules/memory/task-queue/TaskWorker.d.ts +25 -0
- package/dist/modules/memory/task-queue/TaskWorker.js +129 -1
- package/dist/modules/memory/task-queue/TaskWorker.js.map +1 -1
- package/dist/modules/pega/AstDigestBuilder.d.ts +11 -0
- package/dist/modules/pega/AstDigestBuilder.js +17 -0
- package/dist/modules/pega/AstDigestBuilder.js.map +1 -0
- package/dist/modules/pega/PegaContentExtractor.d.ts +28 -0
- package/dist/modules/pega/PegaContentExtractor.js +64 -5
- package/dist/modules/pega/PegaContentExtractor.js.map +1 -1
- package/dist/modules/pega/PegaIndexer.js +2 -1
- package/dist/modules/pega/PegaIndexer.js.map +1 -1
- package/dist/modules/pega/PegaSymbolSync.d.ts +1 -1
- package/dist/modules/pega/PegaSymbolSync.js +53 -8
- package/dist/modules/pega/PegaSymbolSync.js.map +1 -1
- package/dist/modules/pega/models.d.ts +8 -0
- package/dist/server/routes/admin/config.js +21 -4
- package/dist/server/routes/admin/config.js.map +1 -1
- package/dist/server/routes/admin/kb-entries.js +7 -2
- package/dist/server/routes/admin/kb-entries.js.map +1 -1
- package/dist/server/routes/api-index-ingest.d.ts +76 -0
- package/dist/server/routes/api-index-ingest.js +165 -0
- package/dist/server/routes/api-index-ingest.js.map +1 -0
- package/dist/server/routes/api-index.js +23 -39
- package/dist/server/routes/api-index.js.map +1 -1
- package/dist/server/routes/enrichment-auth-guard.d.ts +49 -0
- package/dist/server/routes/enrichment-auth-guard.js +68 -0
- package/dist/server/routes/enrichment-auth-guard.js.map +1 -0
- package/dist/server/routes/enrichment-status-routes.js +69 -17
- package/dist/server/routes/enrichment-status-routes.js.map +1 -1
- package/docker-compose.local-kb.yml +99 -0
- package/docker-compose.test.yml +8 -1
- package/knexfile.cjs +8 -6
- package/package.json +2 -2
- package/src/admin/__tests__/sanitize.test.ts +36 -0
- package/src/admin/db/__tests__/config-masking.test.ts +43 -0
- package/src/admin/db/config.ts +5 -3
- package/src/admin/sanitize.ts +21 -0
- package/src/database/adapters/PostgresAdapter.ts +44 -40
- package/src/database/adapters/PostgresAsyncAdapter.ts +3 -3
- package/src/database/adapters/__tests__/PostgresAdapter.test.ts +53 -22
- package/src/database/adapters/__tests__/pg-sql-utils.test.ts +75 -0
- package/src/database/adapters/pg-sql-utils.ts +77 -0
- package/src/database/migration/pg-schema-ensure.ts +12 -3
- package/src/engine/context/token-budget-manager.ts +5 -0
- package/src/engine/enrichment/CodeEnrichmentHandler.ts +24 -1
- package/src/engine/enrichment/CodeEnrichmentPromptBuilder.ts +72 -6
- package/src/engine/enrichment/__tests__/pega-enrichment-prompt.test.ts +24 -1
- package/src/engine/enrichment/budget-guard.ts +49 -0
- package/src/engine/enrichment/reduction-pipeline.ts +60 -0
- package/src/engine/graph/graph-schema-ddl.ts +1 -0
- package/src/modules/memory/dispatchers/analytics.ts +8 -3
- package/src/modules/memory/dispatchers/crud.ts +13 -4
- package/src/modules/memory/ingest/__tests__/enrich-existing-tags-preserve.it.test.ts +175 -0
- package/src/modules/memory/ingest/__tests__/ingest-file-tags-fallback.it.test.ts +194 -0
- package/src/modules/memory/llm/LLMInitializer.ts +21 -0
- package/src/modules/memory/llm/__tests__/error-classifier.test.ts +121 -0
- package/src/modules/memory/llm/chunked-reduce.ts +35 -0
- package/src/modules/memory/llm/context-window.ts +173 -0
- package/src/modules/memory/llm/error-classifier.ts +127 -0
- package/src/modules/memory/llm/ollama-adapter.ts +3 -1
- package/src/modules/memory/llm/openai-adapter.ts +3 -1
- package/src/modules/memory/task-queue/PendingTaskRepository.ts +29 -3
- package/src/modules/memory/task-queue/TaskWorker.ts +132 -1
- package/src/modules/memory/task-queue/__tests__/PendingTaskRepository.retry.test.ts +139 -0
- package/src/modules/memory/task-queue/__tests__/TaskWorker.fallback-tag.test.ts +263 -0
- package/src/modules/pega/AstDigestBuilder.ts +26 -0
- package/src/modules/pega/PegaContentExtractor.ts +79 -5
- package/src/modules/pega/PegaIndexer.ts +2 -1
- package/src/modules/pega/PegaSymbolSync.ts +56 -9
- package/src/modules/pega/__tests__/PegaContentExtractor.test.ts +38 -1
- package/src/modules/pega/__tests__/PegaSymbolSync.test.ts +43 -0
- package/src/modules/pega/models.ts +8 -0
- package/src/server/routes/__tests__/admin-config-mask.test.ts +156 -0
- package/src/server/routes/__tests__/api-index-errors.test.ts +242 -1
- package/src/server/routes/__tests__/enrichment-status-routes.test.ts +209 -31
- package/src/server/routes/admin/config.ts +19 -4
- package/src/server/routes/admin/kb-entries.ts +8 -3
- package/src/server/routes/api-index-ingest.ts +195 -0
- package/src/server/routes/api-index.ts +27 -36
- package/src/server/routes/enrichment-auth-guard.ts +81 -0
- package/src/server/routes/enrichment-status-routes.ts +66 -17
|
@@ -31,7 +31,7 @@ At the very beginning of your execution, use `find_tools` to discover tools. Use
|
|
|
31
31
|
|
|
32
32
|
Fallbacks:
|
|
33
33
|
- **Project tracker unavailable** → Skip transitions, manage status via STATUS.json only
|
|
34
|
-
- **KB unavailable** →
|
|
34
|
+
- **KB unavailable** → Attempt MCP bootstrap first (tool-usage-dynamic Step 0). If still down → report to user explicitly ("⚠️ KB unavailable — fallback to file checks, KB verification skipped"), then rely on file checks
|
|
35
35
|
- **DOCX export unavailable** → Skip DOCX export, attach markdown or skip attachment
|
|
36
36
|
|
|
37
37
|
### Discovery Report
|
|
@@ -506,7 +506,14 @@ invokeSubAgent(
|
|
|
506
506
|
- If exists → resume from `currentPhase`
|
|
507
507
|
- If not exists → scan for existing files to build initial status
|
|
508
508
|
|
|
509
|
-
2. **
|
|
509
|
+
2. **KB-first check** (MANDATORY — before deciding next steps):
|
|
510
|
+
- Use the discovered KB "search" tool (query: "{TICKET} BRD FSD TDD") — cross-check STATUS.json against documents actually in KB (detect stale/corrupt status)
|
|
511
|
+
- Use the discovered KB "search" tool (query: "personalized rules preferences conventions") — load user's personalized rules (agent-self-learning Rule #7)
|
|
512
|
+
- Use the discovered KB "search" tool (query: "{PROJECT} lessons learned") — check prior lessons/error patterns relevant to this ticket
|
|
513
|
+
- If any KB finding contradicts STATUS.json → report the conflict to user before proceeding
|
|
514
|
+
- If KB unavailable → attempt MCP bootstrap first, then report to user before falling back to file checks
|
|
515
|
+
|
|
516
|
+
3. **Scan existing files** (when STATUS.json doesn't exist):
|
|
510
517
|
```
|
|
511
518
|
documents/{TICKET}/BRD.md exists? → requirements: done
|
|
512
519
|
documents/{TICKET}/FSD.md exists? → specification: done
|
|
@@ -516,7 +523,7 @@ invokeSubAgent(
|
|
|
516
523
|
```
|
|
517
524
|
Create STATUS.json from scan results.
|
|
518
525
|
|
|
519
|
-
|
|
526
|
+
4. **Check Jira ticket status** (MANDATORY on every resume):
|
|
520
527
|
```
|
|
521
528
|
issue = the discovered project tracker "get issue" tool (issue_key: "{TICKET}")
|
|
522
529
|
jiraStatus = issue.status // "To Do", "Docs Review", "In Progress", "In Review", "QA Test", "UAT", "Ready For Product", "Done"
|
|
@@ -537,7 +544,7 @@ invokeSubAgent(
|
|
|
537
544
|
|
|
538
545
|
**Quan trọng:** Nếu Jira status đã advance (ví dụ: reviewer đã chuyển DOCS REVIEW → IN PROGRESS), SM tự động tiếp tục phase tương ứng mà KHÔNG cần user nói lại.
|
|
539
546
|
|
|
540
|
-
|
|
547
|
+
5. **Read Jira comments** (MANDATORY on every resume):
|
|
541
548
|
```
|
|
542
549
|
the discovered project tracker "get issue" tool (issue_key: "{TICKET}", comment_limit: 10)
|
|
543
550
|
```
|
|
@@ -575,7 +582,7 @@ invokeSubAgent(
|
|
|
575
582
|
- If comment contains rejection/feedback → SM MUST report to user before taking action
|
|
576
583
|
- Store last processed comment timestamp in STATUS.json: `"lastCommentProcessed": "2026-05-01T18:00:00Z"`
|
|
577
584
|
|
|
578
|
-
|
|
585
|
+
6. **Report current status to user:**
|
|
579
586
|
```
|
|
580
587
|
📋 {TICKET} — Status Report
|
|
581
588
|
|
|
@@ -593,7 +600,7 @@ invokeSubAgent(
|
|
|
593
600
|
➡️ Tiếp tục Phase 3 (Design)?
|
|
594
601
|
```
|
|
595
602
|
|
|
596
|
-
|
|
603
|
+
7. **Wait for user confirmation** before proceeding.
|
|
597
604
|
|
|
598
605
|
### Step 1: Execute Phase — Requirements (BA → BRD)
|
|
599
606
|
|
package/.kiro/agents/sm-agent.md
CHANGED
|
@@ -43,7 +43,7 @@ At the very beginning of your execution, use `find_tools` to discover tools. Use
|
|
|
43
43
|
|
|
44
44
|
Fallbacks:
|
|
45
45
|
- **Project tracker unavailable** → Skip transitions, manage status via STATUS.json only
|
|
46
|
-
- **KB unavailable** →
|
|
46
|
+
- **KB unavailable** → Attempt MCP bootstrap first (tool-usage-dynamic Step 0). If still down → report to user explicitly ("⚠️ KB unavailable — fallback to file checks, KB verification skipped"), then rely on file checks
|
|
47
47
|
- **DOCX export unavailable** → Skip DOCX export, attach markdown or skip attachment
|
|
48
48
|
|
|
49
49
|
### Discovery Report
|
|
@@ -523,7 +523,14 @@ invokeSubAgent(
|
|
|
523
523
|
- If exists → resume from `currentPhase`
|
|
524
524
|
- If not exists → scan for existing files to build initial status
|
|
525
525
|
|
|
526
|
-
2. **
|
|
526
|
+
2. **KB-first check** (MANDATORY — before deciding next steps):
|
|
527
|
+
- Use the discovered KB "search" tool (query: "{TICKET} BRD FSD TDD") — cross-check STATUS.json against documents actually in KB (detect stale/corrupt status)
|
|
528
|
+
- Use the discovered KB "search" tool (query: "personalized rules preferences conventions") — load user's personalized rules (agent-self-learning Rule #7)
|
|
529
|
+
- Use the discovered KB "search" tool (query: "{PROJECT} lessons learned") — check prior lessons/error patterns relevant to this ticket
|
|
530
|
+
- If any KB finding contradicts STATUS.json → report the conflict to user before proceeding
|
|
531
|
+
- If KB unavailable → attempt MCP bootstrap first, then report to user before falling back to file checks
|
|
532
|
+
|
|
533
|
+
3. **Scan existing files** (when STATUS.json doesn't exist):
|
|
527
534
|
```
|
|
528
535
|
documents/{TICKET}/BRD.md exists? → requirements: done
|
|
529
536
|
documents/{TICKET}/FSD.md exists? → specification: done
|
|
@@ -533,7 +540,7 @@ invokeSubAgent(
|
|
|
533
540
|
```
|
|
534
541
|
Create STATUS.json from scan results.
|
|
535
542
|
|
|
536
|
-
|
|
543
|
+
4. **Check Jira ticket status** (MANDATORY on every resume):
|
|
537
544
|
```
|
|
538
545
|
issue = the discovered project tracker "get issue" tool (issue_key: "{TICKET}")
|
|
539
546
|
jiraStatus = issue.status // "To Do", "Docs Review", "In Progress", "In Review", "QA Test", "UAT", "Ready For Product", "Done"
|
|
@@ -554,7 +561,7 @@ invokeSubAgent(
|
|
|
554
561
|
|
|
555
562
|
**Quan trọng:** Nếu Jira status đã advance (ví dụ: reviewer đã chuyển DOCS REVIEW → IN PROGRESS), SM tự động tiếp tục phase tương ứng mà KHÔNG cần user nói lại.
|
|
556
563
|
|
|
557
|
-
|
|
564
|
+
5. **Read Jira comments** (MANDATORY on every resume):
|
|
558
565
|
```
|
|
559
566
|
the discovered project tracker "get issue" tool (issue_key: "{TICKET}", comment_limit: 10)
|
|
560
567
|
```
|
|
@@ -592,7 +599,7 @@ invokeSubAgent(
|
|
|
592
599
|
- If comment contains rejection/feedback → SM MUST report to user before taking action
|
|
593
600
|
- Store last processed comment timestamp in STATUS.json: `"lastCommentProcessed": "2026-05-01T18:00:00Z"`
|
|
594
601
|
|
|
595
|
-
|
|
602
|
+
6. **Report current status to user:**
|
|
596
603
|
```
|
|
597
604
|
📋 {TICKET} — Status Report
|
|
598
605
|
|
|
@@ -610,7 +617,7 @@ invokeSubAgent(
|
|
|
610
617
|
➡️ Tiếp tục Phase 3 (Design)?
|
|
611
618
|
```
|
|
612
619
|
|
|
613
|
-
|
|
620
|
+
7. **Wait for user confirmation** before proceeding.
|
|
614
621
|
|
|
615
622
|
### Step 1: Execute Phase — Requirements (BA → BRD)
|
|
616
623
|
|
package/Dockerfile
CHANGED
|
@@ -94,9 +94,12 @@ RUN npm install -g npm@${NPM_VERSION} --no-audit --no-fund --loglevel=error \
|
|
|
94
94
|
&& rm -rf /tmp/*.tgz \
|
|
95
95
|
&& node -e "require('${NM}/brace-expansion'); require('${NM}/undici')" \
|
|
96
96
|
&& npm --version \
|
|
97
|
-
# SEC-04: upgrade Debian packages to fix CVE-2026-103111 (libpcre2-8-0 HIGH)
|
|
97
|
+
# SEC-04: upgrade Debian packages to fix CVE-2026-103111 (libpcre2-8-0 HIGH)
|
|
98
|
+
# and the perl CVE set found by Trivy on 2026-10-10 (CVE-2026-13221 CRITICAL,
|
|
99
|
+
# CVE-2026-42497 HIGH, CVE-2026-42496, CVE-2026-8376, CVE-2026-48962,
|
|
100
|
+
# CVE-2026-57432, CVE-2026-57433 — perl-base/perl-modules in node:XX-slim)
|
|
98
101
|
&& apt-get update \
|
|
99
|
-
&& apt-get upgrade -y --no-install-recommends libpcre2-8-0 \
|
|
102
|
+
&& apt-get upgrade -y --no-install-recommends libpcre2-8-0 perl-base perl-modules-5.36 \
|
|
100
103
|
&& apt-get clean \
|
|
101
104
|
&& rm -rf /var/lib/apt/lists/*
|
|
102
105
|
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
</p>
|
|
7
7
|
|
|
8
8
|
<p align="center">
|
|
9
|
-
<img src="https://img.shields.io/badge/version-1.
|
|
9
|
+
<img src="https://img.shields.io/badge/version-1.48.0-blue?style=for-the-badge" alt="Version">
|
|
10
10
|
<img src="https://img.shields.io/badge/tools-66+-teal?style=for-the-badge" alt="Tools">
|
|
11
11
|
<img src="https://img.shields.io/badge/node-%3E%3D18-green?style=for-the-badge" alt="Node">
|
|
12
12
|
</p>
|
|
@@ -38,7 +38,7 @@ Server starts at **http://localhost:48721**. Verify it's running:
|
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
curl http://localhost:48721/health
|
|
41
|
-
# → {"status":"healthy","version":"1.
|
|
41
|
+
# → {"status":"healthy","version":"1.48.0","uptime":5,"tools_loaded":52}
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
### Development Mode (auto-reload)
|
|
@@ -233,6 +233,7 @@ The embedding model is expected at `.code-intel/models/model.onnx`. Download it:
|
|
|
233
233
|
|
|
234
234
|
| Version | Date | Ticket | Changes |
|
|
235
235
|
|---------|------|--------|---------|
|
|
236
|
+
| 1.48.0 | 2026-10-10 | SA4E-341 | Version sync (E2E testing framework delivered in standalone `e2e/` package — no backend runtime changes). Also includes SA4E-335 DB-layer 6 bugfixes (memoized id-column check, literal-safe placeholder translation, identifier quoting SQLi guard, safeExec SQLSTATE filter, writable test-backend data dir), SA4E-349 auth 401 fail-loud fixes, SA4E-350 Pega '@baseclass' appliesTo passthrough fix, enrichment retry scoped via X-Project-Id + runtime settings module. |
|
|
236
237
|
| 1.47.1 | 2026-10-04 | — | Fix Docker CI fail (Trivy CVE-2026-103111 libpcre2-8-0 HIGH), defensive JSON parse on `/auth/refresh`, SA4E-100 Pega BELONGS_TO edges. |
|
|
237
238
|
| 1.47.0 | 2026-10-02 | — | New endpoint `GET /api/v1/enrichment/failures?limit=N` (clamp 1–1000, default 200) returns full FAILED enrichment task list from `pending_tasks` with resolved source, error, retry count, timestamp (`PendingTaskRepository.listFailedDetailed`). Surfaces ingest failures to the extension beyond the 10-item status tooltip. |
|
|
238
239
|
| 1.46.2 | 2026-09-28 | — | Version sync (no backend code changes; packaging fix lives in `extension`: missing direct `yargs` dep for vendored devtools barrel). |
|
package/dist/admin/db/config.js
CHANGED
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
* SA4E-50: All functions are async; use getDbAdapter() for multi-DB support.
|
|
4
4
|
*/
|
|
5
5
|
import { getDbAdapter } from './core.js';
|
|
6
|
-
|
|
6
|
+
import { maskSecret, isSecretConfigKey } from '../sanitize.js';
|
|
7
|
+
/** Map raw DB row to typed ConfigChange. SA4E-338 S4 (D-SEC-06): secret rows are masked on read. */
|
|
7
8
|
function rowToConfigChange(r) {
|
|
9
|
+
const secret = isSecretConfigKey(r.section, r.key);
|
|
8
10
|
return {
|
|
9
11
|
id: r.id,
|
|
10
12
|
section: r.section,
|
|
11
13
|
key: r.key,
|
|
12
|
-
oldValue: r.old_value,
|
|
13
|
-
newValue: r.new_value,
|
|
14
|
+
oldValue: secret ? maskSecret(r.old_value) : r.old_value,
|
|
15
|
+
newValue: secret ? maskSecret(r.new_value) : r.new_value,
|
|
14
16
|
changedBy: r.changed_by,
|
|
15
17
|
changedAt: r.changed_at,
|
|
16
18
|
requiresRestart: !!r.requires_restart,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/admin/db/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/admin/db/config.ts"],"names":[],"mappings":"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"}
|
package/dist/admin/sanitize.d.ts
CHANGED
|
@@ -24,3 +24,15 @@ export declare function escapeHtml(value: unknown): string;
|
|
|
24
24
|
* Returns a new object; does not mutate the input.
|
|
25
25
|
*/
|
|
26
26
|
export declare function sanitizeKbEntry(entry: Record<string, unknown>): Record<string, unknown>;
|
|
27
|
+
/** Sentinel written in place of secret values in API responses, history and audit entries. */
|
|
28
|
+
export declare const MASKED_VALUE = "***";
|
|
29
|
+
/**
|
|
30
|
+
* SA4E-338 S4 (D-SEC-06): mask a secret value for any API / history / audit output.
|
|
31
|
+
* @returns `***` when a non-empty value is present, `''` when empty/absent.
|
|
32
|
+
*/
|
|
33
|
+
export declare function maskSecret(value: unknown): string;
|
|
34
|
+
/**
|
|
35
|
+
* SA4E-338 S4 (D-SEC-06): config keys whose values are secrets — never returned,
|
|
36
|
+
* echoed or audited in plaintext.
|
|
37
|
+
*/
|
|
38
|
+
export declare function isSecretConfigKey(section: string, key: string): boolean;
|
package/dist/admin/sanitize.js
CHANGED
|
@@ -58,4 +58,23 @@ export function sanitizeKbEntry(entry) {
|
|
|
58
58
|
}
|
|
59
59
|
return clean;
|
|
60
60
|
}
|
|
61
|
+
/** Sentinel written in place of secret values in API responses, history and audit entries. */
|
|
62
|
+
export const MASKED_VALUE = '***';
|
|
63
|
+
/**
|
|
64
|
+
* SA4E-338 S4 (D-SEC-06): mask a secret value for any API / history / audit output.
|
|
65
|
+
* @returns `***` when a non-empty value is present, `''` when empty/absent.
|
|
66
|
+
*/
|
|
67
|
+
export function maskSecret(value) {
|
|
68
|
+
if (value === undefined || value === null)
|
|
69
|
+
return '';
|
|
70
|
+
const s = String(value);
|
|
71
|
+
return s.length > 0 ? MASKED_VALUE : '';
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* SA4E-338 S4 (D-SEC-06): config keys whose values are secrets — never returned,
|
|
75
|
+
* echoed or audited in plaintext.
|
|
76
|
+
*/
|
|
77
|
+
export function isSecretConfigKey(section, key) {
|
|
78
|
+
return (section === 'llm' && key === 'apiKey') || (section === 'auth' && key === 'entraClientSecret');
|
|
79
|
+
}
|
|
61
80
|
//# sourceMappingURL=sanitize.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../src/admin/sanitize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4EAA4E;AAC5E,MAAM,WAAW,GAAG,yBAAyB,CAAC;AAE9C,yCAAyC;AACzC,MAAM,YAAY,GAAG,gCAAgC,CAAC;AAEtD;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,GAAY;IACrC,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,IAAe;IAC5C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAA8B;IAC5D,MAAM,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;QACvE,IAAI,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;YACrC,KAAK,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,GAAI,KAAK,CAAC,IAAkB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
1
|
+
{"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../src/admin/sanitize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,4EAA4E;AAC5E,MAAM,WAAW,GAAG,yBAAyB,CAAC;AAE9C,yCAAyC;AACzC,MAAM,YAAY,GAAG,gCAAgC,CAAC;AAEtD;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,GAAY;IACrC,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAI,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,IAAe;IAC5C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;YAAE,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IACnE,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,KAA8B;IAC5D,MAAM,KAAK,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;QACvE,IAAI,OAAO,KAAK,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;YACrC,KAAK,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;QAC1C,CAAC;IACH,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,KAAK,CAAC,IAAI,GAAI,KAAK,CAAC,IAAkB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC5D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,8FAA8F;AAC9F,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,CAAC;AAElC;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IACrD,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACxB,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;AAC1C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe,EAAE,GAAW;IAC5D,OAAO,CAAC,OAAO,KAAK,KAAK,IAAI,GAAG,KAAK,QAAQ,CAAC,IAAI,CAAC,OAAO,KAAK,MAAM,IAAI,GAAG,KAAK,mBAAmB,CAAC,CAAC;AACxG,CAAC"}
|
|
@@ -22,6 +22,8 @@ export declare class PostgresAdapter implements DatabaseAdapter {
|
|
|
22
22
|
private pool;
|
|
23
23
|
private connected;
|
|
24
24
|
private serverVersion;
|
|
25
|
+
/** Memoized per-table result of the information_schema `id` column check. */
|
|
26
|
+
private idColumnCache;
|
|
25
27
|
constructor(config: PostgresConfig);
|
|
26
28
|
connect(): Promise<void>;
|
|
27
29
|
disconnect(): Promise<void>;
|
|
@@ -40,6 +42,13 @@ export declare class PostgresAdapter implements DatabaseAdapter {
|
|
|
40
42
|
*/
|
|
41
43
|
private getQueryFn;
|
|
42
44
|
runAsync(sql: string, params?: unknown[]): Promise<RunResult>;
|
|
45
|
+
/**
|
|
46
|
+
* Memoized per-table check: does the target of this INSERT have an `id` column?
|
|
47
|
+
* Runs on the active query fn (tx client inside transactions) so tables created
|
|
48
|
+
* in the same transaction are visible. Check failure → false (plain INSERT is
|
|
49
|
+
* always safe); the cache costs one metadata query per table per lifetime.
|
|
50
|
+
*/
|
|
51
|
+
private tableHasIdColumn;
|
|
43
52
|
getAsync<T = unknown>(sql: string, params?: unknown[]): Promise<T | undefined>;
|
|
44
53
|
allAsync<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
45
54
|
execAsync(sql: string): Promise<void>;
|
|
@@ -53,5 +62,4 @@ export declare class PostgresAdapter implements DatabaseAdapter {
|
|
|
53
62
|
getVersion(): Promise<string>;
|
|
54
63
|
getTableNames(): Promise<string[]>;
|
|
55
64
|
getRowCount(table: string): Promise<number>;
|
|
56
|
-
private translateParams;
|
|
57
65
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* Implements: SA4E-33
|
|
6
6
|
*/
|
|
7
7
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
8
|
+
import { parseInsertTarget, quoteIdentifier, translatePlaceholders } from './pg-sql-utils.js';
|
|
8
9
|
/** AsyncLocalStorage to scope queries to a dedicated client during transactions. */
|
|
9
10
|
const txClientStorage = new AsyncLocalStorage();
|
|
10
11
|
export class PostgresAdapter {
|
|
@@ -12,6 +13,8 @@ export class PostgresAdapter {
|
|
|
12
13
|
pool = null;
|
|
13
14
|
connected = false;
|
|
14
15
|
serverVersion = '';
|
|
16
|
+
/** Memoized per-table result of the information_schema `id` column check. */
|
|
17
|
+
idColumnCache = new Map();
|
|
15
18
|
constructor(config) {
|
|
16
19
|
this.config = config;
|
|
17
20
|
}
|
|
@@ -67,53 +70,53 @@ export class PostgresAdapter {
|
|
|
67
70
|
}
|
|
68
71
|
// Async methods
|
|
69
72
|
async runAsync(sql, params) {
|
|
70
|
-
const translated =
|
|
73
|
+
const translated = translatePlaceholders(sql);
|
|
71
74
|
const queryFn = this.getQueryFn();
|
|
72
|
-
const inTransaction = !!txClientStorage.getStore();
|
|
73
|
-
// SA4E-104: If INSERT without RETURNING, add RETURNING id to get lastInsertRowid
|
|
74
75
|
const isInsert = /^\s*INSERT/i.test(sql);
|
|
75
76
|
const hasReturning = /RETURNING/i.test(sql);
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
// that does NOT actually abort the transaction in all PG versions.
|
|
83
|
-
// If it does abort: the error propagates → transactionAsync() ROLLBACK is correct.
|
|
84
|
-
const r = await queryFn(withReturning, params);
|
|
85
|
-
const insertedId = r.rows?.[0]?.id ?? 0;
|
|
86
|
-
return { changes: r.rowCount ?? 0, lastInsertRowid: insertedId };
|
|
87
|
-
}
|
|
88
|
-
// Outside transaction: attempt RETURNING id with dedicated client fallback.
|
|
89
|
-
// BUG FIX: pool.query() uses implicit transaction — if RETURNING id fails,
|
|
90
|
-
// the connection is "poisoned" (aborted tx). We MUST use a fresh client for fallback.
|
|
91
|
-
try {
|
|
92
|
-
const r = await queryFn(withReturning, params);
|
|
93
|
-
const insertedId = r.rows?.[0]?.id ?? 0;
|
|
94
|
-
return { changes: r.rowCount ?? 0, lastInsertRowid: insertedId };
|
|
95
|
-
}
|
|
96
|
-
catch {
|
|
97
|
-
// Fallback: table may not have 'id' column — use dedicated client to avoid poisoned connection.
|
|
98
|
-
const client = await this.pool.connect();
|
|
99
|
-
try {
|
|
100
|
-
const r = await client.query(translated, params);
|
|
101
|
-
return { changes: r.rowCount ?? 0, lastInsertRowid: 0 };
|
|
102
|
-
}
|
|
103
|
-
finally {
|
|
104
|
-
client.release();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
77
|
+
// SA4E-104: append RETURNING id to get lastInsertRowid — but ONLY when the
|
|
78
|
+
// table actually has an `id` column. A failed RETURNING inside a transaction
|
|
79
|
+
// aborts it (25P02), so we never gamble: the memoized check decides upfront.
|
|
80
|
+
if (isInsert && !hasReturning && (await this.tableHasIdColumn(sql, queryFn))) {
|
|
81
|
+
const r = await queryFn(`${translated} RETURNING id`, params);
|
|
82
|
+
return { changes: r.rowCount ?? 0, lastInsertRowid: r.rows?.[0]?.id ?? 0 };
|
|
107
83
|
}
|
|
84
|
+
// Id-less table / unparsable target / check failed → plain INSERT, which can
|
|
85
|
+
// never poison a transaction or a pooled connection.
|
|
108
86
|
const r = await queryFn(translated, params);
|
|
109
87
|
return { changes: r.rowCount ?? 0, lastInsertRowid: 0 };
|
|
110
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Memoized per-table check: does the target of this INSERT have an `id` column?
|
|
91
|
+
* Runs on the active query fn (tx client inside transactions) so tables created
|
|
92
|
+
* in the same transaction are visible. Check failure → false (plain INSERT is
|
|
93
|
+
* always safe); the cache costs one metadata query per table per lifetime.
|
|
94
|
+
*/
|
|
95
|
+
async tableHasIdColumn(sql, queryFn) {
|
|
96
|
+
const target = parseInsertTarget(sql);
|
|
97
|
+
if (!target)
|
|
98
|
+
return false;
|
|
99
|
+
const cached = this.idColumnCache.get(target.key);
|
|
100
|
+
if (cached !== undefined)
|
|
101
|
+
return cached;
|
|
102
|
+
try {
|
|
103
|
+
const r = await queryFn(`SELECT 1 FROM information_schema.columns WHERE table_schema = $1 AND table_name = $2 AND column_name = 'id' LIMIT 1`, [target.schema, target.table]);
|
|
104
|
+
const exists = (r.rows?.length ?? 0) > 0;
|
|
105
|
+
this.idColumnCache.set(target.key, exists);
|
|
106
|
+
return exists;
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
console.warn('[PostgresAdapter] id-column lookup failed — defaulting to plain INSERT:', err.message);
|
|
110
|
+
this.idColumnCache.set(target.key, false);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
111
114
|
async getAsync(sql, params) {
|
|
112
|
-
const r = await this.getQueryFn()(
|
|
115
|
+
const r = await this.getQueryFn()(translatePlaceholders(sql), params);
|
|
113
116
|
return r.rows[0];
|
|
114
117
|
}
|
|
115
118
|
async allAsync(sql, params) {
|
|
116
|
-
const r = await this.getQueryFn()(
|
|
119
|
+
const r = await this.getQueryFn()(translatePlaceholders(sql), params);
|
|
117
120
|
return r.rows;
|
|
118
121
|
}
|
|
119
122
|
async execAsync(sql) {
|
|
@@ -152,12 +155,8 @@ export class PostgresAdapter {
|
|
|
152
155
|
return r.rows.map((row) => row.tablename);
|
|
153
156
|
}
|
|
154
157
|
async getRowCount(table) {
|
|
155
|
-
const r = await this.getQueryFn()(`SELECT COUNT(*) as cnt FROM
|
|
158
|
+
const r = await this.getQueryFn()(`SELECT COUNT(*) as cnt FROM ${quoteIdentifier(table)}`);
|
|
156
159
|
return parseInt(r.rows[0]?.cnt || '0', 10);
|
|
157
160
|
}
|
|
158
|
-
translateParams(sql) {
|
|
159
|
-
let idx = 0;
|
|
160
|
-
return sql.replace(/\?/g, () => `$${++idx}`);
|
|
161
|
-
}
|
|
162
161
|
}
|
|
163
162
|
//# sourceMappingURL=PostgresAdapter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PostgresAdapter.js","sourceRoot":"","sources":["../../../src/database/adapters/PostgresAdapter.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"PostgresAdapter.js","sourceRoot":"","sources":["../../../src/database/adapters/PostgresAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -31,6 +31,6 @@ export declare class PostgresAsyncAdapter implements AsyncDatabaseAdapter {
|
|
|
31
31
|
all<T = unknown>(sql: string, params?: unknown[]): Promise<T[]>;
|
|
32
32
|
exec(sql: string): Promise<void>;
|
|
33
33
|
transaction<T>(fn: () => Promise<T>): Promise<T>;
|
|
34
|
-
/** Convert ? placeholders to $1, $2, etc. for pg driver. */
|
|
34
|
+
/** Convert ? placeholders to $1, $2, etc. for pg driver — skips `?` in literals. */
|
|
35
35
|
private translateParams;
|
|
36
36
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* SA4E-44: Native async PostgreSQL adapter for MemoryModule and other async-first consumers.
|
|
4
4
|
* Cleaner than PostgresAdapter: no sync stubs that throw, pure async contract.
|
|
5
5
|
*/
|
|
6
|
+
import { translatePlaceholders } from './pg-sql-utils.js';
|
|
6
7
|
export class PostgresAsyncAdapter {
|
|
7
8
|
config;
|
|
8
9
|
pool = null;
|
|
@@ -75,10 +76,9 @@ export class PostgresAsyncAdapter {
|
|
|
75
76
|
client.release();
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
|
-
/** Convert ? placeholders to $1, $2, etc. for pg driver. */
|
|
79
|
+
/** Convert ? placeholders to $1, $2, etc. for pg driver — skips `?` in literals. */
|
|
79
80
|
translateParams(sql) {
|
|
80
|
-
|
|
81
|
-
return sql.replace(/\?/g, () => `$${++idx}`);
|
|
81
|
+
return translatePlaceholders(sql);
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
//# sourceMappingURL=PostgresAsyncAdapter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PostgresAsyncAdapter.js","sourceRoot":"","sources":["../../../src/database/adapters/PostgresAsyncAdapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"PostgresAsyncAdapter.js","sourceRoot":"","sources":["../../../src/database/adapters/PostgresAsyncAdapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure SQL-string helpers shared by the PostgreSQL adapters — SA4E-335 hardening.
|
|
3
|
+
*
|
|
4
|
+
* Why a separate module: PostgresAdapter sits at its 200-line budget, and both
|
|
5
|
+
* PG adapters shared the same naive `?`→$n replacement, which corrupts `?`
|
|
6
|
+
* inside string literals. Pure functions keep the adapters lean and testable.
|
|
7
|
+
*/
|
|
8
|
+
export interface InsertTarget {
|
|
9
|
+
/** Stable cache key (as parsed). */
|
|
10
|
+
key: string;
|
|
11
|
+
/** Schema for the information_schema lookup (default: public). */
|
|
12
|
+
schema: string;
|
|
13
|
+
/** Unqualified table name. */
|
|
14
|
+
table: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Convert ? placeholders to $1, $2, ... for the pg driver — skipping any `?`
|
|
18
|
+
* inside single-quoted string literals ('' doubling is the literal escape).
|
|
19
|
+
*/
|
|
20
|
+
export declare function translatePlaceholders(sql: string): string;
|
|
21
|
+
/**
|
|
22
|
+
* Extract the target table of an INSERT statement (bare or quoted, optional
|
|
23
|
+
* schema). Bare identifiers are lowercased (PG semantics); returns null when
|
|
24
|
+
* the target is unparsable so callers can fall back to plain INSERT.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseInsertTarget(sql: string): InsertTarget | null;
|
|
27
|
+
/**
|
|
28
|
+
* Validate and quote a table identifier — throws on anything unsafe so a
|
|
29
|
+
* caller-controlled name can never reach SQL as raw interpolation (SQLi guard).
|
|
30
|
+
*/
|
|
31
|
+
export declare function quoteIdentifier(name: string): string;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure SQL-string helpers shared by the PostgreSQL adapters — SA4E-335 hardening.
|
|
3
|
+
*
|
|
4
|
+
* Why a separate module: PostgresAdapter sits at its 200-line budget, and both
|
|
5
|
+
* PG adapters shared the same naive `?`→$n replacement, which corrupts `?`
|
|
6
|
+
* inside string literals. Pure functions keep the adapters lean and testable.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Convert ? placeholders to $1, $2, ... for the pg driver — skipping any `?`
|
|
10
|
+
* inside single-quoted string literals ('' doubling is the literal escape).
|
|
11
|
+
*/
|
|
12
|
+
export function translatePlaceholders(sql) {
|
|
13
|
+
let idx = 0;
|
|
14
|
+
let out = '';
|
|
15
|
+
let inString = false;
|
|
16
|
+
for (let i = 0; i < sql.length; i++) {
|
|
17
|
+
const ch = sql[i];
|
|
18
|
+
if (inString) {
|
|
19
|
+
out += ch;
|
|
20
|
+
if (ch === "'") {
|
|
21
|
+
if (sql[i + 1] === "'") {
|
|
22
|
+
out += "'";
|
|
23
|
+
i++;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
inString = false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (ch === "'") {
|
|
32
|
+
inString = true;
|
|
33
|
+
out += ch;
|
|
34
|
+
}
|
|
35
|
+
else if (ch === '?') {
|
|
36
|
+
out += `$${++idx}`;
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
out += ch;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Extract the target table of an INSERT statement (bare or quoted, optional
|
|
46
|
+
* schema). Bare identifiers are lowercased (PG semantics); returns null when
|
|
47
|
+
* the target is unparsable so callers can fall back to plain INSERT.
|
|
48
|
+
*/
|
|
49
|
+
export function parseInsertTarget(sql) {
|
|
50
|
+
const m = /^\s*INSERT\s+INTO\s+(?:"([^"]+)"|([A-Za-z_][A-Za-z0-9_.$]*))/i.exec(sql);
|
|
51
|
+
if (!m)
|
|
52
|
+
return null;
|
|
53
|
+
if (m[1] !== undefined)
|
|
54
|
+
return { key: `"${m[1]}"`, schema: 'public', table: m[1] };
|
|
55
|
+
const name = m[2].toLowerCase();
|
|
56
|
+
const dot = name.lastIndexOf('.');
|
|
57
|
+
if (dot === -1)
|
|
58
|
+
return { key: name, schema: 'public', table: name };
|
|
59
|
+
return { key: name, schema: name.slice(0, dot), table: name.slice(dot + 1) };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Validate and quote a table identifier — throws on anything unsafe so a
|
|
63
|
+
* caller-controlled name can never reach SQL as raw interpolation (SQLi guard).
|
|
64
|
+
*/
|
|
65
|
+
export function quoteIdentifier(name) {
|
|
66
|
+
const t = name.trim();
|
|
67
|
+
if (!t)
|
|
68
|
+
throw new Error('Empty table identifier');
|
|
69
|
+
return t.split('.').map((seg) => {
|
|
70
|
+
const isQuoted = seg.length >= 2 && seg.startsWith('"') && seg.endsWith('"');
|
|
71
|
+
const bare = isQuoted ? seg.slice(1, -1) : seg;
|
|
72
|
+
if (!/^[A-Za-z_][A-Za-z0-9_$]*$/.test(bare) && !isQuoted) {
|
|
73
|
+
throw new Error(`Unsafe table identifier: ${name}`);
|
|
74
|
+
}
|
|
75
|
+
return `"${bare.replace(/"/g, '""')}"`;
|
|
76
|
+
}).join('.');
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=pg-sql-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pg-sql-utils.js","sourceRoot":"","sources":["../../../src/database/adapters/pg-sql-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -168,13 +168,21 @@ export async function ensurePostgresIndexSchema(adapter) {
|
|
|
168
168
|
logger.error({ err }, '[pg-schema-ensure] Schema ensure failed (non-fatal)');
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
|
-
/**
|
|
171
|
+
/** PG SQLSTATE codes meaning "already exists / nothing to do" — safe to ignore. */
|
|
172
|
+
const IDEMPOTENT_SQLSTATES = new Set(['42P07', '42710', '42701', '42P01']);
|
|
173
|
+
/**
|
|
174
|
+
* Execute DDL: redundant statements (already exists) are ignored by SQLSTATE
|
|
175
|
+
* code; real errors (permission, syntax, …) are logged loudly, not swallowed.
|
|
176
|
+
*/
|
|
172
177
|
async function safeExec(adapter, sql) {
|
|
173
178
|
try {
|
|
174
179
|
await adapter.runAsync(sql, []);
|
|
175
180
|
}
|
|
176
|
-
catch {
|
|
177
|
-
|
|
181
|
+
catch (err) {
|
|
182
|
+
const code = err.code;
|
|
183
|
+
if (code && IDEMPOTENT_SQLSTATES.has(code))
|
|
184
|
+
return;
|
|
185
|
+
logger.error({ err, sql: sql.trim().slice(0, 120) }, '[pg-schema-ensure] DDL failed — schema may be incomplete');
|
|
178
186
|
}
|
|
179
187
|
}
|
|
180
188
|
//# sourceMappingURL=pg-schema-ensure.js.map
|