chainlesschain 0.166.89 → 0.166.90
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/README.md +87 -3
- package/package.json +3 -3
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
- package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-C8AkN9R5.js → AIOps-CZzXRe1-.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BrAzSQor.js → ActionButton-_AZfbW1Q.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Cu56X8Oh.js → Analytics-CzaUx_wv.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BP1Riofl.js → AppLayout-xHEeeD-m.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CIW-nNAz.js → Artifacts-DmTbtudE.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CJtDabk-.js → Audit-Cn4ng4fo.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DYIHeQ_P.js → BackgroundAgents-DLUY1VD2.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-B4CsFzj7.js → Backup-B2v--wB6.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-3eoJ5kfb.js → BaseInput-Bgih7yAv.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BBhX2L_E.js → Chat-1MiiA3yA.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CPLynmbb.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-5UvmwHVk.js → Checkbox-DsyBa_NO.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BFy8zsBN.js → Codegen-B7WCFx9H.js} +1 -1
- package/src/assets/web-panel/assets/{Col-4M6bKRRO.js → Col-D2x4GBGm.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D6LC0j7_.js → Community-Rnwg9nea.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Dd48xMcv.js → Compact-BPczlrRh.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Ba85ggmy.js → Compliance-Br1L_wGU.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-D1BXlkuK.js → Cowork-DlsWB_oS.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-Ca2Uy7Ia.js → Cron-BWo_KGpG.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-C2bDRXZg.js → Crosschain-Cug7N8ge.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DySZKAJf.js → DID-CMbyOZJN.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DDpYf-TT.js → Dashboard-C4dl_UaE.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DGjROfPv.js → Dropdown-CBrC7M-9.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DxX9QYiB.js → EmailListRenderer-DTq7tU4b.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-TBCZyrSJ.js → EvolutionSettings-DWP3csxS.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BeqwTXBa.js → FamilyGuardDashboard-DZl7zuv_.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CiCpKJQ1.js → Federation-W-GSlVK5.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CdTmbLkU.js → FormItemContext-DMB7xYFV.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BwbkEY-5.js → GenericCardRenderer-DNv8GEoq.js} +1 -1
- package/src/assets/web-panel/assets/{Git-VnAga2vv.js → Git-BZ0Bbzw6.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-i-4Ebs-H.js → Governance-BNEdcOM5.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CirjTgNw.js → Inference-C8249gIz.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CunS7vhA.js → KnowledgeGraph-ngzebU3e.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DBuTF0c6.js → Logs-CP15th-f.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-B-iE-tt_.js → MarkdownRenderer-CW3B3JDq.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BK6ZivTS.js → Marketplace-CaH59OsY.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CUnCRnd3.js → McpTools-C07vnJev.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CBGGr8Gy.js → Memory-CqAOkVAS.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-reB6iaWD.js → MobileBridge-qp7mZimq.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-o1YdVB7i.js → MobileProjects-DKhbqvTw.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-if5OE3GH.js → Mtc-CRYCnWXv.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-BTZWpq0j.js → MtcAudit-Z08IrKws.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-8cvMAYFL.js → Multisig-BWdatUnH.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BMvUXAWo.js → NLProgramming-BKWiCZ_m.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BWWjColh.js → Notes-Bgpzk3sm.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Ccr4lIhS.js → NotificationSettings-DvtZEktR.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DhksGy6D.js → OrderTableRenderer-CBcy0szc.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DciXQfdY.js → Organization-DRRL6vW1.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-8DR7uMSx.js → Overflow-COmfB8Ns.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DE6euHYl.js → P2P-BqMq_DSa.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-ByuWoauq.js → PdhVaultBrowser-BX7iNvle.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DkjM87ac.js → Permissions-DzYkjmV_.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-2DLp0KTL.js → PersonalDataHub-BBTcxBbN.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-Ci2Qlkfr.js → Pipeline-eRkYFSbO.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DlwDnDVA.js → Privacy-Ch7IFKBh.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CmMifW39.js → ProjectInit-Ci8EcBYg.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DJVFIh-6.js → ProjectSettings-B4lYHFbI.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-P2nWBS70.js → Projects-zkE5cCo_.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-B2cxaGCU.js → Providers-BKDVD3_e.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CpO77kcE.js → QrScannerModal-BromrHlf.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BDQl1hb0.js → QuickAsk-CCgoY1gR.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DTD2f3IN.js → Recommend-DCPZvpq7.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DGZAnwvs.js → RemoteSession-DPnFP8HU.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-B_R4vitb.js → Reputation-l6r8ytAs.js} +1 -1
- package/src/assets/web-panel/assets/{Row-4Jj6JyXN.js → Row-ke9Mk1cx.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-D1t1MCZ-.js → RssFeed-DQOEzUEn.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CyOUlIn7.js → Search-BqgA0aUz.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BmknPT5Q.js → Security-FeGD_jqV.js} +4 -4
- package/src/assets/web-panel/assets/{Services-DkEK8mpa.js → Services-4kiYYAjZ.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Bvo5bpuk.js → Skeleton-BbU8Xegs.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-AMmG8fim.js → Skills-Dl2bUMUL.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Dl7-OKOV.js → Sla-xRGALeaZ.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DtiGKu0E.js → SpeechSettings-DZfd4RDm.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Ch7mp3ES.js → SyncSettings-BRtCV9Ga.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Ck-sEgj-.js → Tasks-DVRQklfG.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BlV22Il8.js → Templates-BbhE5jt5.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CaaAu0mX.js → Tenant-qXPDNKbK.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-lA9g3FtJ.js → Terminal-CV48Ks7E.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CG26IFC3.js → TimelineRenderer-BLGfJIDr.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-iaJUBLBm.js → Tokens-7G9H6Hrt.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DWQJtLK3.js → Trigger--F64N11F.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CTjmUwqp.js → Trust-Cq1r-D_y.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CMnxufgD.js → UkeySign-Dqyu4RVh.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-diCFSWJw.js → VideoEditing-DuFmWH_B.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Ba1oND1K.js → Wallet-C3mg7EWP.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DZDqPS-S.js → WebAuthn-CDK-Aj_X.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CfN8fPMe.js → WorkflowEditor--K7K-T1F.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BiqMsO_i.js → chat-BQDcS6VP.js} +1 -1
- package/src/assets/web-panel/assets/{colors-By8LkjqH.js → colors-BKxJL2-T.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-B0mxvRe3.js → compact-item-CFjQAMbY.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-Li2ub348.js → createContext-DbgCsdQd.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DlIv9Mbc.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DYbHPkbl.js → hasIn-BhkaOBNv.js} +1 -1
- package/src/assets/web-panel/assets/{index-b4O_YBVR.js → index-3YutaXnp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcOJEgBE.js → index-3djsY75k.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5Se6Lnv.js → index-3uR0ThJO.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByqFKMWL.js → index-7BY44UEv.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRzewWeE.js → index-B4LlAJqc.js} +1 -1
- package/src/assets/web-panel/assets/index-BC972Grv.js +1 -0
- package/src/assets/web-panel/assets/{index-Czt7WAtA.js → index-BNjYPJNU.js} +1 -1
- package/src/assets/web-panel/assets/{index-1lVoRUrQ.js → index-BRqlCTrz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGZzexNa.js → index-BfGGsAU7.js} +1 -1
- package/src/assets/web-panel/assets/{index-D6qL3QBU.js → index-BuWPqSGT.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAOpBv3C.js → index-C0DzhUOX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTVPeUh3.js → index-CB4oHynY.js} +1 -1
- package/src/assets/web-panel/assets/{index-KqfqSdAC.js → index-CE506mgj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DPRgnazi.js → index-CJoXgJ2N.js} +1 -1
- package/src/assets/web-panel/assets/{index-beDji-cB.js → index-COGy6wXn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMh3VxgO.js → index-CRtUDiXx.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bm2P2Kgp.js → index-CSUDlsIr.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cpfn4YbT.js → index-CUs7XpUO.js} +1 -1
- package/src/assets/web-panel/assets/index-CVifyQqL.js +1 -0
- package/src/assets/web-panel/assets/{index-CYv3JIzl.js → index-CoOu7sdP.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVZNYRzM.js → index-D-Yh3-1z.js} +1 -1
- package/src/assets/web-panel/assets/{index-KWt8Owo8.js → index-D2Zb-Kd1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgyrklzQ.js → index-D9sVcw6v.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFhksbXp.js → index-DEwHywz9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CEp_3fKJ.js → index-DFt48Z-x.js} +1 -1
- package/src/assets/web-panel/assets/{index-ClsJN8sW.js → index-DJ2OiRvh.js} +3 -3
- package/src/assets/web-panel/assets/{index-DB4DCzqU.js → index-DP1NrM9J.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIImi0cd.js → index-DdcrixaQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bd5znvCV.js → index-De7daEnc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CA0AOURT.js → index-Dhfa_BIl.js} +1 -1
- package/src/assets/web-panel/assets/{index-hw3sCYAz.js → index-DjmVlpap.js} +1 -1
- package/src/assets/web-panel/assets/{index-BSk0xuXP.js → index-DmYK9Tqt.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bc6bxU24.js → index-Dn2QSsuI.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0D0HkkM.js → index-Du_SxxBY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgMFnBOv.js → index-DvWWFJHJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-zSzXsF9b.js → index-T9ka3Xj3.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHgiSV6R.js → index-aPdVzagA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZqTG7fP.js → index-bMX8BBLE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-RE3mFh.js → index-sKfUUldY.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-TgDL_7OY.js → initDefaultProps-wrqabmui.js} +1 -1
- package/src/assets/web-panel/assets/{motion-fHosyV5c.js → motion-BoyB3ilP.js} +1 -1
- package/src/assets/web-panel/assets/{move-C_QfW5qg.js → move-Cq31cSFE.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-u7ywDVOg.js → mtc-parser-BkxNt-8H.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BEJdUgE9.js → omit-DofGni53.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DGkQFAJ5.js → pickAttrs-BnluLL16.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DUyZXAjt.js → placementArrow-C-yegJSP.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-B4xDxO7Q.js → responsiveObserve-CD3ziNhu.js} +1 -1
- package/src/assets/web-panel/assets/{slide-B8WuJtst.js → slide-ToZO4gQ1.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DLUqy7JO.js → statusUtils-DFVmDmVl.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BYOJOyqL.js → styleChecker-De0Y8g1O.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-WChWQURv.js → useFlexGapSupport-wBKIYFKX.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-vLNpm3dv.js → useFs-CP9nmUMJ.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DSZ8unoU.js → usePersonalDataHub-vFGrVDyp.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C4Xf7Kg1.js → vnode-BcBIjvjH.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BulmeAGy.js → zoom-DnXDAQxA.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +4 -4
- package/src/commands/automation.js +35 -8
- package/src/commands/hub.js +147 -0
- package/src/commands/project.js +162 -0
- package/src/data/changelog.json +13 -0
- package/src/gateways/ws/personal-data-hub-protocol.js +54 -8
- package/src/lib/audit-logger.js +16 -29
- package/src/lib/audit-sanitizer.js +279 -0
- package/src/lib/automation-center.js +14 -4
- package/src/lib/automation-engine.js +151 -39
- package/src/lib/knowledge-graph.js +27 -0
- package/src/lib/personal-data-hub-wiring.js +44 -1
- package/src/lib/scheduler-kernel/automation-adapter.js +15 -3
- package/src/lib/scheduler-kernel/automation-event-adapter.js +15 -3
- package/src/assets/web-panel/assets/ChatBubbleRenderer-RN8cbv7w.js +0 -1
- package/src/assets/web-panel/assets/devWarning-D90arbUg.js +0 -1
- package/src/assets/web-panel/assets/index-CeP1oROI.js +0 -1
- package/src/assets/web-panel/assets/index-Cu9gkyRc.js +0 -1
package/src/commands/project.js
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* cc project list [--user <id>] [--status <s>] [--limit <n>] [--json]
|
|
15
15
|
* cc project show <id> [--json]
|
|
16
16
|
* cc project delete <id> [--hard] [--json]
|
|
17
|
+
* cc project risk-evaluate --snapshot <json-file> [--json]
|
|
18
|
+
* cc project task-description-preview --snapshot <json-file> [--json]
|
|
17
19
|
*
|
|
18
20
|
* 设计 (per #21 v1.2 GA 反馈 "目标在手机端做ai项目的交互要像在电脑端那样丝滑"):
|
|
19
21
|
* CLI 直接读写 desktop chainlesschain.db, 立刻在 desktop UI 出现,
|
|
@@ -21,6 +23,8 @@
|
|
|
21
23
|
*/
|
|
22
24
|
|
|
23
25
|
import chalk from "chalk";
|
|
26
|
+
import fs from "node:fs";
|
|
27
|
+
import path from "node:path";
|
|
24
28
|
import {
|
|
25
29
|
openProjectsDb,
|
|
26
30
|
defaultProjectDbPath,
|
|
@@ -30,6 +34,114 @@ import {
|
|
|
30
34
|
} from "../lib/project-runtime.js";
|
|
31
35
|
|
|
32
36
|
const DEFAULT_USER = "default";
|
|
37
|
+
const MAX_PROJECT_SNAPSHOT_BYTES = 2 * 1024 * 1024;
|
|
38
|
+
|
|
39
|
+
function snapshotFailure(code) {
|
|
40
|
+
const error = new Error(code);
|
|
41
|
+
error.code = code;
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Read a user-selected regular snapshot without following links or devices. */
|
|
46
|
+
function readProjectSnapshot(file, io = fs) {
|
|
47
|
+
if (
|
|
48
|
+
typeof file !== "string" ||
|
|
49
|
+
!file ||
|
|
50
|
+
/^[\\/]{2}[?.][\\/]/u.test(file) ||
|
|
51
|
+
(process.platform === "win32" &&
|
|
52
|
+
/^(?:con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\.|$)/iu.test(
|
|
53
|
+
path.basename(file),
|
|
54
|
+
))
|
|
55
|
+
)
|
|
56
|
+
snapshotFailure("SNAPSHOT_INVALID_PATH");
|
|
57
|
+
let fd;
|
|
58
|
+
try {
|
|
59
|
+
// Reject FIFO/device paths before opening. O_NONBLOCK also covers a POSIX
|
|
60
|
+
// file replaced by a FIFO between this check and the descriptor open.
|
|
61
|
+
if (!io.lstatSync(file).isFile())
|
|
62
|
+
snapshotFailure("SNAPSHOT_REGULAR_FILE_REQUIRED");
|
|
63
|
+
fd = io.openSync(
|
|
64
|
+
file,
|
|
65
|
+
fs.constants.O_RDONLY |
|
|
66
|
+
(fs.constants.O_NONBLOCK || 0) |
|
|
67
|
+
(fs.constants.O_NOFOLLOW || 0),
|
|
68
|
+
);
|
|
69
|
+
const before = io.fstatSync(fd);
|
|
70
|
+
if (!before.isFile()) snapshotFailure("SNAPSHOT_REGULAR_FILE_REQUIRED");
|
|
71
|
+
if (
|
|
72
|
+
!Number.isSafeInteger(before.size) ||
|
|
73
|
+
before.size < 0 ||
|
|
74
|
+
before.size > MAX_PROJECT_SNAPSHOT_BYTES
|
|
75
|
+
)
|
|
76
|
+
snapshotFailure("SNAPSHOT_FILE_TOO_LARGE");
|
|
77
|
+
const data = Buffer.alloc(before.size + 1);
|
|
78
|
+
let count = 0;
|
|
79
|
+
while (count < data.length) {
|
|
80
|
+
const read = io.readSync(fd, data, count, data.length - count, count);
|
|
81
|
+
if (read === 0) break;
|
|
82
|
+
count += read;
|
|
83
|
+
}
|
|
84
|
+
const after = io.fstatSync(fd);
|
|
85
|
+
if (
|
|
86
|
+
count !== before.size ||
|
|
87
|
+
after.size !== before.size ||
|
|
88
|
+
after.mtimeMs !== before.mtimeMs ||
|
|
89
|
+
after.ctimeMs !== before.ctimeMs
|
|
90
|
+
)
|
|
91
|
+
snapshotFailure("SNAPSHOT_CHANGED_DURING_READ");
|
|
92
|
+
let text;
|
|
93
|
+
try {
|
|
94
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(
|
|
95
|
+
data.subarray(0, count),
|
|
96
|
+
);
|
|
97
|
+
} catch {
|
|
98
|
+
snapshotFailure("SNAPSHOT_INVALID_UTF8");
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
return JSON.parse(text);
|
|
102
|
+
} catch {
|
|
103
|
+
snapshotFailure("SNAPSHOT_INVALID_JSON");
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (error.code?.startsWith("SNAPSHOT_")) throw error;
|
|
107
|
+
snapshotFailure("SNAPSHOT_READ_FAILED");
|
|
108
|
+
} finally {
|
|
109
|
+
if (fd !== undefined) io.closeSync(fd);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function printSnapshotError(error, options) {
|
|
114
|
+
const code =
|
|
115
|
+
typeof error?.code === "string" &&
|
|
116
|
+
/^(SNAPSHOT|ACTION)_[A-Z_]+$/u.test(error.code)
|
|
117
|
+
? error.code
|
|
118
|
+
: "SNAPSHOT_EVALUATION_FAILED";
|
|
119
|
+
if (options.json)
|
|
120
|
+
console.log(JSON.stringify({ status: "error", error: code }));
|
|
121
|
+
else console.error(chalk.red(code));
|
|
122
|
+
process.exitCode = 2;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function printProjectRisk(result) {
|
|
126
|
+
console.log("Offline project risk evaluation (unverified snapshot)");
|
|
127
|
+
console.log(`Status: ${result.status}`);
|
|
128
|
+
console.log(`Rule: ${result.ruleVersion}`);
|
|
129
|
+
if (result.asOf) console.log(`As of: ${result.asOf}`);
|
|
130
|
+
if (result.inputDigest) console.log(`Input: ${result.inputDigest}`);
|
|
131
|
+
if (result.status !== "evaluated") {
|
|
132
|
+
console.log(`Reasons: ${result.reasonCodes.join(", ")}`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
console.log(
|
|
136
|
+
`Tasks: ${result.summary.taskCount}; matching tasks: ${result.summary.riskTaskCount}; overdue: ${result.summary.overdueTaskCount}; blocked: ${result.summary.blockedTaskCount}`,
|
|
137
|
+
);
|
|
138
|
+
for (const task of result.tasks) {
|
|
139
|
+
const blockers = task.blockingTaskRefs.map((ref) => ref.id).join(", ");
|
|
140
|
+
console.log(
|
|
141
|
+
`${task.taskRef.id}: ${task.reasonCodes.join(", ")}${blockers ? ` (dependencies: ${blockers})` : ""}`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
33
145
|
|
|
34
146
|
function _now() {
|
|
35
147
|
return Date.now();
|
|
@@ -74,6 +186,54 @@ export function registerProjectCommand(program) {
|
|
|
74
186
|
"Manage desktop projects (shared SQLite, syncs to mobile via Phase 3d)",
|
|
75
187
|
);
|
|
76
188
|
|
|
189
|
+
cmd
|
|
190
|
+
.command("risk-evaluate")
|
|
191
|
+
.description(
|
|
192
|
+
"Evaluate overdue tasks and direct blockers in an offline snapshot",
|
|
193
|
+
)
|
|
194
|
+
.requiredOption(
|
|
195
|
+
"--snapshot <json-file>",
|
|
196
|
+
"Complete project risk snapshot (max 2 MiB)",
|
|
197
|
+
)
|
|
198
|
+
.option("--json", "Output JSON")
|
|
199
|
+
.action(async (options) => {
|
|
200
|
+
try {
|
|
201
|
+
const snapshot = readProjectSnapshot(options.snapshot);
|
|
202
|
+
const { evaluateProjectRiskSnapshot } =
|
|
203
|
+
await import("@chainlesschain/session-core/project-risk-evaluation");
|
|
204
|
+
const result = evaluateProjectRiskSnapshot(snapshot);
|
|
205
|
+
if (options.json) console.log(JSON.stringify(result, null, 2));
|
|
206
|
+
else printProjectRisk(result);
|
|
207
|
+
if (result.status === "insufficient-data") process.exitCode = 2;
|
|
208
|
+
} catch (error) {
|
|
209
|
+
printSnapshotError(error, options);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
cmd
|
|
214
|
+
.command("task-description-preview")
|
|
215
|
+
.description(
|
|
216
|
+
"Preview a task description request from an unverified offline snapshot",
|
|
217
|
+
)
|
|
218
|
+
.requiredOption(
|
|
219
|
+
"--snapshot <json-file>",
|
|
220
|
+
"Task/project/description/idempotencyKey JSON (max 2 MiB)",
|
|
221
|
+
)
|
|
222
|
+
.option("--json", "Output JSON")
|
|
223
|
+
.action(async (options) => {
|
|
224
|
+
try {
|
|
225
|
+
const snapshot = readProjectSnapshot(options.snapshot);
|
|
226
|
+
const { createTaskDescriptionPreview } =
|
|
227
|
+
await import("@chainlesschain/session-core/task-description-action-service");
|
|
228
|
+
const result = createTaskDescriptionPreview(snapshot);
|
|
229
|
+
if (!options.json)
|
|
230
|
+
console.log("Offline task description preview (unverified snapshot)");
|
|
231
|
+
console.log(JSON.stringify(result, null, 2));
|
|
232
|
+
} catch (error) {
|
|
233
|
+
printSnapshotError(error, options);
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
77
237
|
// ===== init =====
|
|
78
238
|
cmd
|
|
79
239
|
.command("init <name>")
|
|
@@ -255,4 +415,6 @@ export function registerProjectCommand(program) {
|
|
|
255
415
|
// 暴露给单测
|
|
256
416
|
export const _projectInternals = {
|
|
257
417
|
defaultProjectDbPath,
|
|
418
|
+
readProjectSnapshot,
|
|
419
|
+
MAX_PROJECT_SNAPSHOT_BYTES,
|
|
258
420
|
};
|
package/src/data/changelog.json
CHANGED
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"source": "CHANGELOG.md",
|
|
3
3
|
"releases": [
|
|
4
|
+
{
|
|
5
|
+
"productVersion": null,
|
|
6
|
+
"cliVersion": "0.166.90",
|
|
7
|
+
"cliVersions": ["0.166.90"],
|
|
8
|
+
"date": null,
|
|
9
|
+
"title": "",
|
|
10
|
+
"sections": [
|
|
11
|
+
{
|
|
12
|
+
"heading": "Added - cc CLI 0.166.90: recoverable projections and project risk evidence",
|
|
13
|
+
"body": "- Publish Personal Data Hub `0.4.63` and Session Core `0.3.15` before the CLI,\n with exact dependency pins and public tarball verification. Require the full\n PDH suite with its declared native SQLCipher driver on Linux, Windows and macOS.\n- Publish Agent SDK `0.2.13` with Vitest `4.1.11` test tooling to remove the\n vulnerable Tinypool dependency, and upgrade the workspace proxy-addr to\n `2.0.8`. Require SDK build and tests on the exact Linux, Windows and macOS\n release commit before OIDC publication.\n- Persist PDH projection receipts, recover incomplete derivations and support\n bounded receipt retention. Harden RAG updates, audit redaction and capability\n reporting so unavailable sources do not appear as successful live collection.\n- Add offline project risk evaluation and version-bound task description\n previews. Session Core also supplies native SQLite action/read services and\n saved rule/source reviews for the desktop project workbench; CLI previews\n carry unverified snapshot authority and do not execute those desktop actions.\n- Pair VS Code `0.37.135` and JetBrains `0.4.153` with CLI `0.166.90` after\n exact-commit release checks and public npm readback."
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
4
17
|
{
|
|
5
18
|
"productVersion": null,
|
|
6
19
|
"cliVersion": "0.166.89",
|
|
@@ -193,7 +193,8 @@ async function withHub(fn, loadHub = getHub) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
function governedHubLoader(factory) {
|
|
196
|
-
if (typeof factory === "function")
|
|
196
|
+
if (typeof factory === "function")
|
|
197
|
+
return () => getGovernedAnalysisHub(factory);
|
|
197
198
|
const error = new Error(
|
|
198
199
|
"Personal Data Hub model egress requires an authenticated evolution composition factory",
|
|
199
200
|
);
|
|
@@ -201,16 +202,24 @@ function governedHubLoader(factory) {
|
|
|
201
202
|
throw error;
|
|
202
203
|
}
|
|
203
204
|
|
|
205
|
+
function validateEntityReference(entityType, entityId) {
|
|
206
|
+
if (
|
|
207
|
+
!["event", "person", "place", "item", "topic"].includes(entityType) ||
|
|
208
|
+
typeof entityId !== "string" ||
|
|
209
|
+
!entityId.trim() ||
|
|
210
|
+
entityId.length > 1024
|
|
211
|
+
) {
|
|
212
|
+
throw new Error("A valid entityType and entityId are required");
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
204
216
|
export const PERSONAL_DATA_HUB_HANDLERS = {
|
|
205
217
|
"personal-data-hub.ask": async (msg, context = {}) => {
|
|
206
218
|
const factory = context.server?.evolutionCompositionFactory;
|
|
207
|
-
return withHub(
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
},
|
|
212
|
-
governedHubLoader(factory),
|
|
213
|
-
);
|
|
219
|
+
return withHub(async (hub) => {
|
|
220
|
+
if (!hub.engine) throw new Error("Analysis engine unavailable");
|
|
221
|
+
return await hub.engine.ask(msg.question, msg.options || {});
|
|
222
|
+
}, governedHubLoader(factory));
|
|
214
223
|
},
|
|
215
224
|
|
|
216
225
|
// Path Y: prompt context only, no LLM call. Lets web-shell / mobile host
|
|
@@ -250,6 +259,43 @@ export const PERSONAL_DATA_HUB_HANDLERS = {
|
|
|
250
259
|
"personal-data-hub.list-adapters": async () =>
|
|
251
260
|
withHub((hub) => hub.registry.list()),
|
|
252
261
|
|
|
262
|
+
"personal-data-hub.derivation-status": async (msg) =>
|
|
263
|
+
withHub((hub) =>
|
|
264
|
+
hub.registry.getDerivationStatus({
|
|
265
|
+
adapter: msg.adapter,
|
|
266
|
+
scope: msg.scope,
|
|
267
|
+
}),
|
|
268
|
+
),
|
|
269
|
+
|
|
270
|
+
"personal-data-hub.derivation-state": async (msg) =>
|
|
271
|
+
withHub((hub) => {
|
|
272
|
+
validateEntityReference(msg.entityType, msg.entityId);
|
|
273
|
+
return hub.vault
|
|
274
|
+
.getDerivationStore()
|
|
275
|
+
.getState(msg.entityType, msg.entityId, {
|
|
276
|
+
consumerId: hub.registry.consumerId,
|
|
277
|
+
});
|
|
278
|
+
}),
|
|
279
|
+
|
|
280
|
+
"personal-data-hub.retry-derivations": async (msg) =>
|
|
281
|
+
withHub((hub) =>
|
|
282
|
+
hub.registry.retryDerivations({
|
|
283
|
+
adapter: msg.adapter,
|
|
284
|
+
scope: msg.scope,
|
|
285
|
+
limit: msg.limit,
|
|
286
|
+
}),
|
|
287
|
+
),
|
|
288
|
+
|
|
289
|
+
"personal-data-hub.delete-entity": async (msg) =>
|
|
290
|
+
withHub(async (hub) => {
|
|
291
|
+
if (msg.confirm !== true)
|
|
292
|
+
throw new Error("Entity deletion requires confirm: true");
|
|
293
|
+
validateEntityReference(msg.entityType, msg.entityId);
|
|
294
|
+
const deleted = hub.vault.deleteEntity(msg.entityType, msg.entityId);
|
|
295
|
+
const derivations = await hub.registry.retryDerivations({ limit: 1000 });
|
|
296
|
+
return { deleted, derivations };
|
|
297
|
+
}),
|
|
298
|
+
|
|
253
299
|
// Per-adapter readiness ("能否采集 + 不能的原因"). Probes each adapter's
|
|
254
300
|
// authenticate({ readinessOnly: true }) — see AdapterRegistry.readiness().
|
|
255
301
|
// The web-shell PersonalDataHub view renders this as a 就绪状态 column so
|
package/src/lib/audit-logger.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
import crypto from "crypto";
|
|
7
7
|
import { safeJsonParse } from "./safe-json.js";
|
|
8
8
|
import { escapeLike } from "./sql-like.js";
|
|
9
|
+
import { sanitizeAuditDetails } from "./audit-sanitizer.js";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Event types for audit logging.
|
|
@@ -112,29 +113,15 @@ export function assessRisk(eventType, operation, details) {
|
|
|
112
113
|
* Sanitize sensitive data from log details.
|
|
113
114
|
*/
|
|
114
115
|
export function sanitizeDetails(details) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const sanitized = { ...details };
|
|
118
|
-
const sensitiveKeys = [
|
|
119
|
-
"password",
|
|
120
|
-
"secret",
|
|
121
|
-
"secretKey",
|
|
122
|
-
"secret_key",
|
|
123
|
-
"privateKey",
|
|
124
|
-
"private_key",
|
|
125
|
-
"token",
|
|
126
|
-
"apiKey",
|
|
127
|
-
"api_key",
|
|
128
|
-
"mnemonic",
|
|
129
|
-
];
|
|
130
|
-
|
|
131
|
-
for (const key of sensitiveKeys) {
|
|
132
|
-
if (sanitized[key]) {
|
|
133
|
-
sanitized[key] = "[REDACTED]";
|
|
134
|
-
}
|
|
135
|
-
}
|
|
116
|
+
return sanitizeAuditDetails(details);
|
|
117
|
+
}
|
|
136
118
|
|
|
137
|
-
|
|
119
|
+
function sanitizeErrorMessage(value) {
|
|
120
|
+
const sanitized = sanitizeDetails(value);
|
|
121
|
+
if (sanitized == null) return null;
|
|
122
|
+
return typeof sanitized === "object"
|
|
123
|
+
? JSON.stringify(sanitized)
|
|
124
|
+
: String(sanitized);
|
|
138
125
|
}
|
|
139
126
|
|
|
140
127
|
/**
|
|
@@ -146,8 +133,7 @@ export function logEvent(db, event) {
|
|
|
146
133
|
const id = crypto.randomUUID();
|
|
147
134
|
const sanitized = sanitizeDetails(event.details);
|
|
148
135
|
const risk =
|
|
149
|
-
event.riskLevel ||
|
|
150
|
-
assessRisk(event.eventType, event.operation, event.details);
|
|
136
|
+
event.riskLevel || assessRisk(event.eventType, event.operation, sanitized);
|
|
151
137
|
|
|
152
138
|
db.prepare(
|
|
153
139
|
`INSERT INTO audit_log (id, event_type, operation, actor, target, details, risk_level, ip_address, user_agent, success, error_message)
|
|
@@ -158,12 +144,12 @@ export function logEvent(db, event) {
|
|
|
158
144
|
event.operation || "unknown",
|
|
159
145
|
event.actor || null,
|
|
160
146
|
event.target || null,
|
|
161
|
-
sanitized ? JSON.stringify(sanitized)
|
|
147
|
+
sanitized == null ? null : JSON.stringify(sanitized),
|
|
162
148
|
risk,
|
|
163
149
|
event.ipAddress || null,
|
|
164
150
|
event.userAgent || null,
|
|
165
151
|
event.success !== false ? 1 : 0,
|
|
166
|
-
event.errorMessage
|
|
152
|
+
sanitizeErrorMessage(event.errorMessage),
|
|
167
153
|
);
|
|
168
154
|
|
|
169
155
|
return { id, riskLevel: risk, createdAt: new Date().toISOString() };
|
|
@@ -516,7 +502,8 @@ export function logEventV2(
|
|
|
516
502
|
if (_logStatesV2.has(logId)) {
|
|
517
503
|
throw new Error(`Log already registered: ${logId}`);
|
|
518
504
|
}
|
|
519
|
-
const
|
|
505
|
+
const sanitized = sanitizeDetails(details);
|
|
506
|
+
const risk = riskLevel || assessRisk(eventType, operation, sanitized);
|
|
520
507
|
if (!RISK_LEVELS_V2.includes(risk)) {
|
|
521
508
|
throw new Error(`Invalid riskLevel: ${risk}`);
|
|
522
509
|
}
|
|
@@ -527,12 +514,12 @@ export function logEventV2(
|
|
|
527
514
|
operation,
|
|
528
515
|
actor: actor || null,
|
|
529
516
|
target: target || null,
|
|
530
|
-
details:
|
|
517
|
+
details: sanitized ?? null,
|
|
531
518
|
riskLevel: risk,
|
|
532
519
|
ipAddress: ipAddress || null,
|
|
533
520
|
userAgent: userAgent || null,
|
|
534
521
|
success: success !== false,
|
|
535
|
-
errorMessage: errorMessage
|
|
522
|
+
errorMessage: sanitizeErrorMessage(errorMessage),
|
|
536
523
|
status: LOG_STATUS_V2.ACTIVE,
|
|
537
524
|
integrityStatus: INTEGRITY_STATUS_V2.UNVERIFIED,
|
|
538
525
|
prevHash: _lastChainHash,
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-safe audit details. Limits apply before string processing and while
|
|
3
|
+
* traversing; getters and user-provided toJSON methods are never invoked.
|
|
4
|
+
* This is field/credential redaction, not detection of arbitrary secrets in prose.
|
|
5
|
+
*/
|
|
6
|
+
const REDACTED = "[REDACTED]";
|
|
7
|
+
const TRUNCATED = "[Truncated]";
|
|
8
|
+
const LIMITS = Object.freeze({
|
|
9
|
+
depth: 8,
|
|
10
|
+
entries: 100,
|
|
11
|
+
nodes: 512,
|
|
12
|
+
stringLength: 4096,
|
|
13
|
+
keyLength: 256,
|
|
14
|
+
totalCharacters: 16384,
|
|
15
|
+
serializedBytes: 65536,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const SENSITIVE_KEYS = new Set([
|
|
19
|
+
"password",
|
|
20
|
+
"passwd",
|
|
21
|
+
"pwd",
|
|
22
|
+
"passphrase",
|
|
23
|
+
"secret",
|
|
24
|
+
"secretkey",
|
|
25
|
+
"privatekey",
|
|
26
|
+
"apikey",
|
|
27
|
+
"token",
|
|
28
|
+
"authorization",
|
|
29
|
+
"proxyauthorization",
|
|
30
|
+
"cookie",
|
|
31
|
+
"setcookie",
|
|
32
|
+
"mnemonic",
|
|
33
|
+
"seedphrase",
|
|
34
|
+
"credentials",
|
|
35
|
+
"credential",
|
|
36
|
+
"encryptionkey",
|
|
37
|
+
"signingkey",
|
|
38
|
+
"sessionid",
|
|
39
|
+
"auth",
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
function normalizeKey(key) {
|
|
43
|
+
return key.toLowerCase().replace(/[\s_.-]/g, "");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isSensitiveKey(key) {
|
|
47
|
+
const normalized = normalizeKey(key);
|
|
48
|
+
return (
|
|
49
|
+
SENSITIVE_KEYS.has(normalized) ||
|
|
50
|
+
/(?:password|secret|privatekey|apikey|token)$/.test(normalized)
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function isSensitiveQueryKey(key) {
|
|
55
|
+
return isSensitiveKey(key) || /^(?:key|code|sig|signature)$/i.test(key);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function sanitizeUrl(text) {
|
|
59
|
+
try {
|
|
60
|
+
const url = new URL(text);
|
|
61
|
+
if (url.username || url.password) {
|
|
62
|
+
url.username = REDACTED;
|
|
63
|
+
url.password = "";
|
|
64
|
+
}
|
|
65
|
+
const params = new URLSearchParams();
|
|
66
|
+
for (const [key, value] of url.searchParams) {
|
|
67
|
+
params.append(key, isSensitiveQueryKey(key) ? REDACTED : value);
|
|
68
|
+
}
|
|
69
|
+
url.search = params.toString();
|
|
70
|
+
// OAuth implicit responses put credentials in the fragment instead of query.
|
|
71
|
+
if (url.hash.includes("=")) {
|
|
72
|
+
const fragment = new URLSearchParams(url.hash.slice(1));
|
|
73
|
+
const sanitized = new URLSearchParams();
|
|
74
|
+
for (const [key, value] of fragment) {
|
|
75
|
+
sanitized.append(key, isSensitiveQueryKey(key) ? REDACTED : value);
|
|
76
|
+
}
|
|
77
|
+
url.hash = sanitized.toString();
|
|
78
|
+
}
|
|
79
|
+
return url.toString();
|
|
80
|
+
} catch {
|
|
81
|
+
return "[REDACTED URL]";
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function sanitizeString(value) {
|
|
86
|
+
// Drop oversized strings whole: slicing can expose half of a credential.
|
|
87
|
+
if (value.length > LIMITS.stringLength) return TRUNCATED;
|
|
88
|
+
return value
|
|
89
|
+
.replace(/\b[a-z][a-z0-9+.-]*:\/\/[^\s<>"'`]+/gi, sanitizeUrl)
|
|
90
|
+
.replace(/\b(?:Bearer|Basic)\s+[a-z0-9._~+/=-]+/gi, REDACTED)
|
|
91
|
+
.replace(
|
|
92
|
+
/\b((?:proxy[-_ ]?)?authorization|(?:set[-_ ]?)?cookie)\s*:\s*[^\r\n]+/gi,
|
|
93
|
+
"$1: [REDACTED]",
|
|
94
|
+
)
|
|
95
|
+
.replace(
|
|
96
|
+
/\b((?:[a-z0-9_.-]{0,64})?(?:password|secret|private[-_.]?key|api[-_.]?key|token)|passwd|pwd|passphrase|mnemonic|seed[-_.]?phrase|credentials?|encryption[-_.]?key|signing[-_.]?key|session[-_.]?id|auth|(?:proxy[-_.]?)?authorization|(?:set[-_.]?)?cookie)(["']?\s*[=:]\s*)("(?:\\.|[^"\\\r\n])*"|'(?:\\.|[^'\\\r\n])*'|[^\s&,;"']+)/gi,
|
|
97
|
+
(_match, key, separator, value) => {
|
|
98
|
+
const quote = value[0] === '"' || value[0] === "'" ? value[0] : "";
|
|
99
|
+
return `${key}${separator}${quote}${REDACTED}${quote}`;
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function readDataProperty(value, key) {
|
|
105
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
106
|
+
return descriptor && "value" in descriptor
|
|
107
|
+
? descriptor.value
|
|
108
|
+
: "[Accessor omitted]";
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function errorName(value) {
|
|
112
|
+
let prototype = value;
|
|
113
|
+
for (let i = 0; prototype && i < LIMITS.depth; i += 1) {
|
|
114
|
+
if (Object.hasOwn(prototype, "name")) {
|
|
115
|
+
const name = readDataProperty(prototype, "name");
|
|
116
|
+
return typeof name === "string" ? name : "Error";
|
|
117
|
+
}
|
|
118
|
+
prototype = Object.getPrototypeOf(prototype);
|
|
119
|
+
}
|
|
120
|
+
return "Error";
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function setDataProperty(target, key, value) {
|
|
124
|
+
// Defining __proto__ as data cannot mutate the output object's prototype.
|
|
125
|
+
Object.defineProperty(target, key, {
|
|
126
|
+
value,
|
|
127
|
+
enumerable: true,
|
|
128
|
+
configurable: true,
|
|
129
|
+
writable: true,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function sanitizeAuditDetails(details) {
|
|
134
|
+
const ancestors = new WeakSet();
|
|
135
|
+
let remainingNodes = LIMITS.nodes;
|
|
136
|
+
let remainingCharacters = LIMITS.totalCharacters;
|
|
137
|
+
|
|
138
|
+
function text(value) {
|
|
139
|
+
const sanitized = sanitizeString(value);
|
|
140
|
+
if (sanitized.length > remainingCharacters) return TRUNCATED;
|
|
141
|
+
remainingCharacters -= sanitized.length;
|
|
142
|
+
return sanitized;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function visit(value, depth = 0, context = "") {
|
|
146
|
+
if (remainingNodes-- <= 0) return TRUNCATED;
|
|
147
|
+
if (value === null || value === undefined) return value;
|
|
148
|
+
if (typeof value === "string") return text(value);
|
|
149
|
+
if (typeof value === "bigint") return text(value.toString());
|
|
150
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
151
|
+
if (typeof value === "boolean") return value;
|
|
152
|
+
if (typeof value === "function") return "[Function omitted]";
|
|
153
|
+
if (typeof value === "symbol") return "[Symbol omitted]";
|
|
154
|
+
if (depth >= LIMITS.depth) return TRUNCATED;
|
|
155
|
+
if (ancestors.has(value)) return "[Circular Reference]";
|
|
156
|
+
ancestors.add(value);
|
|
157
|
+
try {
|
|
158
|
+
if (value instanceof Date) {
|
|
159
|
+
return text(Date.prototype.toISOString.call(value));
|
|
160
|
+
}
|
|
161
|
+
if (value instanceof URL) {
|
|
162
|
+
return text(URL.prototype.toString.call(value));
|
|
163
|
+
}
|
|
164
|
+
if (ArrayBuffer.isView(value) || value instanceof ArrayBuffer) {
|
|
165
|
+
return "[Binary omitted]";
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Native fetch headers and query objects have no useful enumerable fields.
|
|
169
|
+
if (value instanceof Headers || value instanceof URLSearchParams) {
|
|
170
|
+
const result = {};
|
|
171
|
+
const iterator =
|
|
172
|
+
value instanceof Headers
|
|
173
|
+
? Headers.prototype.entries.call(value)
|
|
174
|
+
: URLSearchParams.prototype.entries.call(value);
|
|
175
|
+
let count = 0;
|
|
176
|
+
for (const [key, item] of iterator) {
|
|
177
|
+
if (count++ >= LIMITS.entries || remainingNodes <= 0) {
|
|
178
|
+
setDataProperty(result, "[Truncated]", true);
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
if (key.length > LIMITS.keyLength) continue;
|
|
182
|
+
setDataProperty(
|
|
183
|
+
result,
|
|
184
|
+
text(key),
|
|
185
|
+
isSensitiveQueryKey(key) ? REDACTED : visit(item, depth + 1),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (Array.isArray(value)) {
|
|
192
|
+
const result = [];
|
|
193
|
+
const isHeaderList = normalizeKey(context).endsWith("headers");
|
|
194
|
+
// Header tuple arrays are commonly returned by fetch/HTTP clients.
|
|
195
|
+
const first = readDataProperty(value, "0");
|
|
196
|
+
const isSensitivePair =
|
|
197
|
+
value.length === 2 &&
|
|
198
|
+
typeof first === "string" &&
|
|
199
|
+
isSensitiveKey(first);
|
|
200
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
201
|
+
if (i >= LIMITS.entries || remainingNodes <= 0) {
|
|
202
|
+
result.push(TRUNCATED);
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
const previous =
|
|
206
|
+
i % 2 === 1 && isHeaderList
|
|
207
|
+
? readDataProperty(value, String(i - 1))
|
|
208
|
+
: null;
|
|
209
|
+
const redact =
|
|
210
|
+
(isSensitivePair && i === 1) ||
|
|
211
|
+
(typeof previous === "string" && isSensitiveKey(previous));
|
|
212
|
+
result.push(
|
|
213
|
+
redact
|
|
214
|
+
? REDACTED
|
|
215
|
+
: visit(readDataProperty(value, String(i)), depth + 1, context),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const result = {};
|
|
222
|
+
if (value instanceof Error) {
|
|
223
|
+
setDataProperty(result, "name", text(errorName(value)));
|
|
224
|
+
// message/stack/cause are usually non-enumerable. Do not call getters.
|
|
225
|
+
for (const key of ["name", "message", "stack", "cause"]) {
|
|
226
|
+
if (Object.hasOwn(value, key)) {
|
|
227
|
+
setDataProperty(
|
|
228
|
+
result,
|
|
229
|
+
key,
|
|
230
|
+
visit(readDataProperty(value, key), depth + 1),
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
let count = 0;
|
|
236
|
+
const headerName = normalizeKey(context).endsWith("headers")
|
|
237
|
+
? readDataProperty(value, "name")
|
|
238
|
+
: null;
|
|
239
|
+
for (const key in value) {
|
|
240
|
+
if (
|
|
241
|
+
count++ >= LIMITS.entries ||
|
|
242
|
+
remainingNodes <= 0 ||
|
|
243
|
+
remainingCharacters <= 0
|
|
244
|
+
) {
|
|
245
|
+
setDataProperty(result, "[Truncated]", true);
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
249
|
+
if (key.length > LIMITS.keyLength) {
|
|
250
|
+
setDataProperty(result, "[Truncated key]", true);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
setDataProperty(
|
|
254
|
+
result,
|
|
255
|
+
text(key),
|
|
256
|
+
isSensitiveKey(key) ||
|
|
257
|
+
(key === "value" &&
|
|
258
|
+
typeof headerName === "string" &&
|
|
259
|
+
isSensitiveKey(headerName))
|
|
260
|
+
? REDACTED
|
|
261
|
+
: visit(readDataProperty(value, key), depth + 1, key),
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return result;
|
|
265
|
+
} catch {
|
|
266
|
+
// Proxies, invalid native objects, etc. must not break event persistence.
|
|
267
|
+
return "[Unserializable]";
|
|
268
|
+
} finally {
|
|
269
|
+
ancestors.delete(value);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const sanitized = visit(details);
|
|
274
|
+
const serialized = JSON.stringify(sanitized);
|
|
275
|
+
return serialized &&
|
|
276
|
+
Buffer.byteLength(serialized, "utf8") > LIMITS.serializedBytes
|
|
277
|
+
? TRUNCATED
|
|
278
|
+
: sanitized;
|
|
279
|
+
}
|