arc-1 0.6.10 → 0.7.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/README.md +8 -7
- package/bin/arc1-cli.js +10 -0
- package/bin/arc1.js +1 -1
- package/dist/adt/cds-impact.d.ts +35 -0
- package/dist/adt/cds-impact.d.ts.map +1 -1
- package/dist/adt/cds-impact.js +71 -0
- package/dist/adt/cds-impact.js.map +1 -1
- package/dist/adt/client.d.ts +4 -1
- package/dist/adt/client.d.ts.map +1 -1
- package/dist/adt/client.js +18 -5
- package/dist/adt/client.js.map +1 -1
- package/dist/adt/crud.d.ts.map +1 -1
- package/dist/adt/crud.js +32 -5
- package/dist/adt/crud.js.map +1 -1
- package/dist/adt/devtools.d.ts +39 -3
- package/dist/adt/devtools.d.ts.map +1 -1
- package/dist/adt/devtools.js +237 -25
- package/dist/adt/devtools.js.map +1 -1
- package/dist/adt/diagnostics.d.ts +69 -7
- package/dist/adt/diagnostics.d.ts.map +1 -1
- package/dist/adt/diagnostics.js +694 -36
- package/dist/adt/diagnostics.js.map +1 -1
- package/dist/adt/errors.d.ts +14 -1
- package/dist/adt/errors.d.ts.map +1 -1
- package/dist/adt/errors.js +40 -9
- package/dist/adt/errors.js.map +1 -1
- package/dist/adt/http.d.ts.map +1 -1
- package/dist/adt/http.js +86 -1
- package/dist/adt/http.js.map +1 -1
- package/dist/adt/rap-handlers.d.ts +165 -0
- package/dist/adt/rap-handlers.d.ts.map +1 -0
- package/dist/adt/rap-handlers.js +835 -0
- package/dist/adt/rap-handlers.js.map +1 -0
- package/dist/adt/rap-preflight.d.ts +43 -0
- package/dist/adt/rap-preflight.d.ts.map +1 -0
- package/dist/adt/rap-preflight.js +405 -0
- package/dist/adt/rap-preflight.js.map +1 -0
- package/dist/adt/safety.d.ts +60 -36
- package/dist/adt/safety.d.ts.map +1 -1
- package/dist/adt/safety.js +202 -120
- package/dist/adt/safety.js.map +1 -1
- package/dist/adt/transport.d.ts +1 -1
- package/dist/adt/transport.js +2 -2
- package/dist/adt/transport.js.map +1 -1
- package/dist/adt/types.d.ts +88 -0
- package/dist/adt/types.d.ts.map +1 -1
- package/dist/adt/xml-parser.d.ts +13 -1
- package/dist/adt/xml-parser.d.ts.map +1 -1
- package/dist/adt/xml-parser.js +26 -15
- package/dist/adt/xml-parser.js.map +1 -1
- package/dist/authz/policy.d.ts +53 -0
- package/dist/authz/policy.d.ts.map +1 -0
- package/dist/authz/policy.js +199 -0
- package/dist/authz/policy.js.map +1 -0
- package/dist/cli-args.d.ts +14 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +62 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli.d.ts +13 -7
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +252 -55
- package/dist/cli.js.map +1 -1
- package/dist/extract-sap-cookies.d.ts +24 -0
- package/dist/extract-sap-cookies.d.ts.map +1 -0
- package/dist/extract-sap-cookies.js +317 -0
- package/dist/extract-sap-cookies.js.map +1 -0
- package/dist/handlers/hyperfocused.d.ts +4 -3
- package/dist/handlers/hyperfocused.d.ts.map +1 -1
- package/dist/handlers/hyperfocused.js +25 -16
- package/dist/handlers/hyperfocused.js.map +1 -1
- package/dist/handlers/intent.d.ts +4 -12
- package/dist/handlers/intent.d.ts.map +1 -1
- package/dist/handlers/intent.js +1238 -114
- package/dist/handlers/intent.js.map +1 -1
- package/dist/handlers/schemas.d.ts +38 -10
- package/dist/handlers/schemas.d.ts.map +1 -1
- package/dist/handlers/schemas.js +69 -4
- package/dist/handlers/schemas.js.map +1 -1
- package/dist/handlers/tools.d.ts.map +1 -1
- package/dist/handlers/tools.js +251 -164
- package/dist/handlers/tools.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/server/audit.d.ts +26 -3
- package/dist/server/audit.d.ts.map +1 -1
- package/dist/server/audit.js.map +1 -1
- package/dist/server/config.d.ts +34 -19
- package/dist/server/config.d.ts.map +1 -1
- package/dist/server/config.js +320 -193
- package/dist/server/config.js.map +1 -1
- package/dist/server/deny-actions.d.ts +31 -0
- package/dist/server/deny-actions.d.ts.map +1 -0
- package/dist/server/deny-actions.js +156 -0
- package/dist/server/deny-actions.js.map +1 -0
- package/dist/server/effective-policy-log.d.ts +27 -0
- package/dist/server/effective-policy-log.d.ts.map +1 -0
- package/dist/server/effective-policy-log.js +103 -0
- package/dist/server/effective-policy-log.js.map +1 -0
- package/dist/server/http.d.ts.map +1 -1
- package/dist/server/http.js +15 -16
- package/dist/server/http.js.map +1 -1
- package/dist/server/server.d.ts +37 -3
- package/dist/server/server.d.ts.map +1 -1
- package/dist/server/server.js +231 -30
- package/dist/server/server.js.map +1 -1
- package/dist/server/types.d.ts +29 -13
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server/types.js +10 -11
- package/dist/server/types.js.map +1 -1
- package/dist/server/xsuaa.d.ts +1 -2
- package/dist/server/xsuaa.d.ts.map +1 -1
- package/dist/server/xsuaa.js +13 -14
- package/dist/server/xsuaa.js.map +1 -1
- package/package.json +6 -3
package/dist/server/config.js
CHANGED
|
@@ -4,15 +4,99 @@
|
|
|
4
4
|
* Resolves configuration from CLI flags, environment variables, and defaults.
|
|
5
5
|
* Priority: CLI > env > .env > defaults
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* Post-authz-refactor-v2 (v0.7):
|
|
8
|
+
* - Profile layer (`ARC1_PROFILE`) was removed. Use explicit `SAP_ALLOW_*` env vars.
|
|
9
|
+
* - Op-code allowlist/blocklist env vars (`SAP_ALLOWED_OPS` / `SAP_DISALLOWED_OPS`)
|
|
10
|
+
* were removed. Use `SAP_DENY_ACTIONS` for fine-grained per-action denials.
|
|
11
|
+
* - Single `ARC1_API_KEY` was removed. Use `ARC1_API_KEYS="key:profile"` instead.
|
|
12
|
+
* - Negated safety flags (`SAP_READ_ONLY`, `SAP_BLOCK_DATA`, `SAP_BLOCK_FREE_SQL`,
|
|
13
|
+
* `SAP_ENABLE_TRANSPORTS`, `SAP_ENABLE_GIT`) were replaced with positive opt-ins
|
|
14
|
+
* (`SAP_ALLOW_WRITES`, `SAP_ALLOW_DATA_PREVIEW`, `SAP_ALLOW_FREE_SQL`,
|
|
15
|
+
* `SAP_ALLOW_TRANSPORT_WRITES`, `SAP_ALLOW_GIT_WRITES`).
|
|
16
|
+
* - See docs_page/updating.md for the full migration table.
|
|
9
17
|
*/
|
|
18
|
+
import { parseDenyActions, validateDenyActions } from './deny-actions.js';
|
|
10
19
|
import { logger } from './logger.js';
|
|
11
20
|
import { DEFAULT_CONFIG } from './types.js';
|
|
21
|
+
export const API_KEY_PROFILES = {
|
|
22
|
+
viewer: {
|
|
23
|
+
scopes: ['read'],
|
|
24
|
+
safety: {
|
|
25
|
+
allowWrites: false,
|
|
26
|
+
allowDataPreview: false,
|
|
27
|
+
allowFreeSQL: false,
|
|
28
|
+
allowTransportWrites: false,
|
|
29
|
+
allowGitWrites: false,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
'viewer-data': {
|
|
33
|
+
scopes: ['read', 'data'],
|
|
34
|
+
safety: {
|
|
35
|
+
allowWrites: false,
|
|
36
|
+
allowDataPreview: true,
|
|
37
|
+
allowFreeSQL: false,
|
|
38
|
+
allowTransportWrites: false,
|
|
39
|
+
allowGitWrites: false,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
'viewer-sql': {
|
|
43
|
+
scopes: ['read', 'data', 'sql'],
|
|
44
|
+
safety: {
|
|
45
|
+
allowWrites: false,
|
|
46
|
+
allowDataPreview: true,
|
|
47
|
+
allowFreeSQL: true,
|
|
48
|
+
allowTransportWrites: false,
|
|
49
|
+
allowGitWrites: false,
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
developer: {
|
|
53
|
+
scopes: ['read', 'write', 'transports', 'git'],
|
|
54
|
+
safety: {
|
|
55
|
+
allowWrites: true,
|
|
56
|
+
allowDataPreview: false,
|
|
57
|
+
allowFreeSQL: false,
|
|
58
|
+
allowTransportWrites: true,
|
|
59
|
+
allowGitWrites: true,
|
|
60
|
+
allowedPackages: ['$TMP'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
'developer-data': {
|
|
64
|
+
scopes: ['read', 'write', 'data', 'transports', 'git'],
|
|
65
|
+
safety: {
|
|
66
|
+
allowWrites: true,
|
|
67
|
+
allowDataPreview: true,
|
|
68
|
+
allowFreeSQL: false,
|
|
69
|
+
allowTransportWrites: true,
|
|
70
|
+
allowGitWrites: true,
|
|
71
|
+
allowedPackages: ['$TMP'],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
'developer-sql': {
|
|
75
|
+
scopes: ['read', 'write', 'data', 'sql', 'transports', 'git'],
|
|
76
|
+
safety: {
|
|
77
|
+
allowWrites: true,
|
|
78
|
+
allowDataPreview: true,
|
|
79
|
+
allowFreeSQL: true,
|
|
80
|
+
allowTransportWrites: true,
|
|
81
|
+
allowGitWrites: true,
|
|
82
|
+
allowedPackages: ['$TMP'],
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
admin: {
|
|
86
|
+
scopes: ['read', 'write', 'data', 'sql', 'transports', 'git', 'admin'],
|
|
87
|
+
safety: {
|
|
88
|
+
allowWrites: true,
|
|
89
|
+
allowDataPreview: true,
|
|
90
|
+
allowFreeSQL: true,
|
|
91
|
+
allowTransportWrites: true,
|
|
92
|
+
allowGitWrites: true,
|
|
93
|
+
allowedPackages: [],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
12
97
|
/**
|
|
13
98
|
* Parse API keys string into structured array.
|
|
14
99
|
* Format: "key1:profile1,key2:profile2"
|
|
15
|
-
* Each entry maps an API key to a named profile.
|
|
16
100
|
*/
|
|
17
101
|
export function parseApiKeys(raw) {
|
|
18
102
|
const entries = [];
|
|
@@ -20,20 +104,18 @@ export function parseApiKeys(raw) {
|
|
|
20
104
|
const trimmed = pair.trim();
|
|
21
105
|
if (!trimmed)
|
|
22
106
|
continue;
|
|
23
|
-
// Use LAST colon as separator — keys may contain colons (e.g. base64)
|
|
24
|
-
// but profile names never do
|
|
25
107
|
const colonIdx = trimmed.lastIndexOf(':');
|
|
26
108
|
if (colonIdx === -1) {
|
|
27
109
|
throw new Error(`Invalid API key entry '${trimmed}': expected 'key:profile' format. ` +
|
|
28
|
-
`Valid profiles: ${Object.keys(
|
|
110
|
+
`Valid profiles: ${Object.keys(API_KEY_PROFILES).join(', ')}`);
|
|
29
111
|
}
|
|
30
112
|
const key = trimmed.slice(0, colonIdx);
|
|
31
113
|
const profile = trimmed.slice(colonIdx + 1);
|
|
32
114
|
if (!key) {
|
|
33
115
|
throw new Error('Invalid API key entry: key cannot be empty');
|
|
34
116
|
}
|
|
35
|
-
if (!
|
|
36
|
-
throw new Error(`Invalid profile '${profile}' in API key entry. Valid profiles: ${Object.keys(
|
|
117
|
+
if (!API_KEY_PROFILES[profile]) {
|
|
118
|
+
throw new Error(`Invalid profile '${profile}' in API key entry. Valid profiles: ${Object.keys(API_KEY_PROFILES).join(', ')}`);
|
|
37
119
|
}
|
|
38
120
|
entries.push({ key, profile });
|
|
39
121
|
}
|
|
@@ -42,118 +124,150 @@ export function parseApiKeys(raw) {
|
|
|
42
124
|
}
|
|
43
125
|
return entries;
|
|
44
126
|
}
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
'
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
'
|
|
56
|
-
'developer-sql': ['read', 'write', 'data', 'sql'],
|
|
127
|
+
/** Map of legacy env-var names → human-readable migration hint. */
|
|
128
|
+
const LEGACY_ENV_VARS = {
|
|
129
|
+
SAP_READ_ONLY: 'Replaced by SAP_ALLOW_WRITES (inverted). Set SAP_ALLOW_WRITES=true to enable writes.',
|
|
130
|
+
SAP_BLOCK_DATA: 'Replaced by SAP_ALLOW_DATA_PREVIEW (inverted). Set SAP_ALLOW_DATA_PREVIEW=true to enable table preview.',
|
|
131
|
+
SAP_BLOCK_FREE_SQL: 'Replaced by SAP_ALLOW_FREE_SQL (inverted). Set SAP_ALLOW_FREE_SQL=true to enable freestyle SQL.',
|
|
132
|
+
SAP_ENABLE_TRANSPORTS: 'Replaced by SAP_ALLOW_TRANSPORT_WRITES. Transport reads are always available; writes need SAP_ALLOW_TRANSPORT_WRITES=true + SAP_ALLOW_WRITES=true.',
|
|
133
|
+
SAP_ENABLE_GIT: 'Replaced by SAP_ALLOW_GIT_WRITES. Git reads are always available; writes need SAP_ALLOW_GIT_WRITES=true + SAP_ALLOW_WRITES=true.',
|
|
134
|
+
SAP_ALLOWED_OPS: 'Op-code allowlist was removed. Use SAP_DENY_ACTIONS for fine-grained per-action denials (e.g., SAP_DENY_ACTIONS="SAPWrite.delete,SAPManage.flp_*").',
|
|
135
|
+
SAP_DISALLOWED_OPS: 'Op-code blocklist was removed. Use SAP_DENY_ACTIONS instead.',
|
|
136
|
+
ARC1_PROFILE: 'Server-side profile presets were removed. Set individual SAP_ALLOW_* flags (see .env.example for recipes).',
|
|
137
|
+
ARC1_API_KEY: 'Single API-key mode was removed. Use ARC1_API_KEYS="key:profile" with a profile name (valid: viewer, viewer-data, viewer-sql, developer, developer-data, developer-sql, admin).',
|
|
57
138
|
};
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
enableTransports: false,
|
|
69
|
-
},
|
|
70
|
-
'viewer-data': {
|
|
71
|
-
readOnly: true,
|
|
72
|
-
blockData: false,
|
|
73
|
-
blockFreeSQL: true,
|
|
74
|
-
enableTransports: false,
|
|
75
|
-
},
|
|
76
|
-
'viewer-sql': {
|
|
77
|
-
readOnly: true,
|
|
78
|
-
blockData: false,
|
|
79
|
-
blockFreeSQL: false,
|
|
80
|
-
enableTransports: false,
|
|
81
|
-
},
|
|
82
|
-
developer: {
|
|
83
|
-
readOnly: false,
|
|
84
|
-
blockData: true,
|
|
85
|
-
blockFreeSQL: true,
|
|
86
|
-
enableTransports: true,
|
|
87
|
-
allowedPackages: ['$TMP'],
|
|
88
|
-
},
|
|
89
|
-
'developer-data': {
|
|
90
|
-
readOnly: false,
|
|
91
|
-
blockData: false,
|
|
92
|
-
blockFreeSQL: true,
|
|
93
|
-
enableTransports: true,
|
|
94
|
-
allowedPackages: ['$TMP'],
|
|
95
|
-
},
|
|
96
|
-
'developer-sql': {
|
|
97
|
-
readOnly: false,
|
|
98
|
-
blockData: false,
|
|
99
|
-
blockFreeSQL: false,
|
|
100
|
-
enableTransports: true,
|
|
101
|
-
allowedPackages: ['$TMP'],
|
|
102
|
-
},
|
|
139
|
+
const LEGACY_CLI_FLAGS = {
|
|
140
|
+
'read-only': LEGACY_ENV_VARS.SAP_READ_ONLY,
|
|
141
|
+
'block-data': LEGACY_ENV_VARS.SAP_BLOCK_DATA,
|
|
142
|
+
'block-free-sql': LEGACY_ENV_VARS.SAP_BLOCK_FREE_SQL,
|
|
143
|
+
'enable-transports': LEGACY_ENV_VARS.SAP_ENABLE_TRANSPORTS,
|
|
144
|
+
'enable-git': LEGACY_ENV_VARS.SAP_ENABLE_GIT,
|
|
145
|
+
'allowed-ops': LEGACY_ENV_VARS.SAP_ALLOWED_OPS,
|
|
146
|
+
'disallowed-ops': LEGACY_ENV_VARS.SAP_DISALLOWED_OPS,
|
|
147
|
+
profile: LEGACY_ENV_VARS.ARC1_PROFILE,
|
|
148
|
+
'api-key': LEGACY_ENV_VARS.ARC1_API_KEY,
|
|
103
149
|
};
|
|
150
|
+
/** Migration guard — throws a helpful error if any legacy identifier is set. */
|
|
151
|
+
function detectLegacyConfig(args) {
|
|
152
|
+
const violations = [];
|
|
153
|
+
for (const env of Object.keys(LEGACY_ENV_VARS)) {
|
|
154
|
+
if (process.env[env] !== undefined) {
|
|
155
|
+
violations.push(` ${env}: ${LEGACY_ENV_VARS[env]}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
for (const flag of Object.keys(LEGACY_CLI_FLAGS)) {
|
|
159
|
+
if (args.some((a) => a === `--${flag}` || a.startsWith(`--${flag}=`))) {
|
|
160
|
+
violations.push(` --${flag}: ${LEGACY_CLI_FLAGS[flag]}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
if (violations.length > 0) {
|
|
164
|
+
throw new Error(`Legacy authorization config detected (removed in v0.7):\n${violations.join('\n')}\n\nSee docs_page/updating.md#v07-authorization-refactor-breaking-change for the full migration guide.`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
104
167
|
/**
|
|
105
|
-
* Parse CLI
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
* the MCP server entry point needs to be fast and lightweight.
|
|
109
|
-
* Commander is used for the full CLI (cli.ts), not the server startup.
|
|
168
|
+
* Parse CLI args + env into a `{ config, sources }` pair.
|
|
169
|
+
* `sources` records where each field's value came from (default / env / flag / file).
|
|
170
|
+
* Consumed by the startup effective-policy log and the `arc1 config show` subcommand.
|
|
110
171
|
*/
|
|
111
|
-
export function
|
|
172
|
+
export function resolveConfig(args) {
|
|
173
|
+
detectLegacyConfig(args);
|
|
112
174
|
const config = { ...DEFAULT_CONFIG };
|
|
113
|
-
|
|
175
|
+
const sources = {};
|
|
176
|
+
// ── Resolvers ──────────────────────────────────────────────────────
|
|
114
177
|
const getFlag = (name) => {
|
|
115
178
|
const prefix = `--${name}=`;
|
|
116
179
|
for (let i = 0; i < args.length; i++) {
|
|
117
|
-
if (args[i] === `--${name}` && i + 1 < args.length)
|
|
180
|
+
if (args[i] === `--${name}` && i + 1 < args.length)
|
|
118
181
|
return args[i + 1];
|
|
119
|
-
|
|
120
|
-
if (args[i]?.startsWith(prefix)) {
|
|
182
|
+
if (args[i]?.startsWith(prefix))
|
|
121
183
|
return args[i].slice(prefix.length);
|
|
122
|
-
}
|
|
123
184
|
}
|
|
124
185
|
return undefined;
|
|
125
186
|
};
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
187
|
+
const resolveStr = (flag, envVar, defaultVal, fieldName) => {
|
|
188
|
+
const flagVal = getFlag(flag);
|
|
189
|
+
if (flagVal !== undefined) {
|
|
190
|
+
sources[fieldName] = { flag: `--${flag}` };
|
|
191
|
+
return flagVal;
|
|
192
|
+
}
|
|
193
|
+
if (process.env[envVar] !== undefined) {
|
|
194
|
+
sources[fieldName] = { env: envVar };
|
|
195
|
+
return process.env[envVar];
|
|
196
|
+
}
|
|
197
|
+
sources[fieldName] = 'default';
|
|
198
|
+
return defaultVal;
|
|
129
199
|
};
|
|
130
|
-
const resolveBool = (flag, envVar, defaultVal) => {
|
|
131
|
-
const
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
200
|
+
const resolveBool = (flag, envVar, defaultVal, fieldName) => {
|
|
201
|
+
const flagVal = getFlag(flag);
|
|
202
|
+
if (flagVal !== undefined) {
|
|
203
|
+
sources[fieldName] = { flag: `--${flag}` };
|
|
204
|
+
return flagVal === 'true' || flagVal === '1';
|
|
205
|
+
}
|
|
206
|
+
if (process.env[envVar] !== undefined) {
|
|
207
|
+
sources[fieldName] = { env: envVar };
|
|
208
|
+
return process.env[envVar] === 'true' || process.env[envVar] === '1';
|
|
209
|
+
}
|
|
210
|
+
sources[fieldName] = 'default';
|
|
211
|
+
return defaultVal;
|
|
135
212
|
};
|
|
136
|
-
const resolveFeature = (flag, envVar) => {
|
|
137
|
-
const
|
|
138
|
-
if (
|
|
139
|
-
|
|
213
|
+
const resolveFeature = (flag, envVar, fieldName) => {
|
|
214
|
+
const flagVal = getFlag(flag);
|
|
215
|
+
if (flagVal !== undefined) {
|
|
216
|
+
sources[fieldName] = { flag: `--${flag}` };
|
|
217
|
+
if (flagVal === 'on' || flagVal === 'off')
|
|
218
|
+
return flagVal;
|
|
219
|
+
return 'auto';
|
|
220
|
+
}
|
|
221
|
+
const envVal = process.env[envVar];
|
|
222
|
+
if (envVal !== undefined) {
|
|
223
|
+
sources[fieldName] = { env: envVar };
|
|
224
|
+
if (envVal === 'on' || envVal === 'off')
|
|
225
|
+
return envVal;
|
|
226
|
+
return 'auto';
|
|
227
|
+
}
|
|
228
|
+
sources[fieldName] = 'default';
|
|
140
229
|
return 'auto';
|
|
141
230
|
};
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
231
|
+
const resolveOptionalStr = (flag, envVar, fieldName) => {
|
|
232
|
+
const flagVal = getFlag(flag);
|
|
233
|
+
if (flagVal !== undefined) {
|
|
234
|
+
sources[fieldName] = { flag: `--${flag}` };
|
|
235
|
+
return flagVal;
|
|
236
|
+
}
|
|
237
|
+
if (process.env[envVar] !== undefined) {
|
|
238
|
+
sources[fieldName] = { env: envVar };
|
|
239
|
+
return process.env[envVar];
|
|
240
|
+
}
|
|
241
|
+
sources[fieldName] = 'default';
|
|
242
|
+
return undefined;
|
|
243
|
+
};
|
|
244
|
+
// ── SAP Connection ─────────────────────────────────────────────────
|
|
245
|
+
config.url = resolveStr('url', 'SAP_URL', '', 'url');
|
|
246
|
+
config.username = resolveStr('user', 'SAP_USER', '', 'username');
|
|
247
|
+
config.password = resolveStr('password', 'SAP_PASSWORD', '', 'password');
|
|
248
|
+
config.client = resolveStr('client', 'SAP_CLIENT', '100', 'client');
|
|
249
|
+
config.language = resolveStr('language', 'SAP_LANGUAGE', 'EN', 'language');
|
|
250
|
+
config.insecure = resolveBool('insecure', 'SAP_INSECURE', false, 'insecure');
|
|
251
|
+
// ── Cookie Auth ────────────────────────────────────────────────────
|
|
252
|
+
config.cookieFile = resolveOptionalStr('cookie-file', 'SAP_COOKIE_FILE', 'cookieFile');
|
|
253
|
+
config.cookieString = resolveOptionalStr('cookie-string', 'SAP_COOKIE_STRING', 'cookieString');
|
|
254
|
+
// ── Transport ──────────────────────────────────────────────────────
|
|
255
|
+
const transport = resolveStr('transport', 'SAP_TRANSPORT', 'stdio', 'transport');
|
|
154
256
|
config.transport = (transport === 'http-streamable' ? 'http-streamable' : 'stdio');
|
|
155
|
-
|
|
156
|
-
|
|
257
|
+
const httpAddrFlag = getFlag('http-addr');
|
|
258
|
+
const httpAddrEnv = process.env.ARC1_HTTP_ADDR ?? process.env.SAP_HTTP_ADDR;
|
|
259
|
+
if (httpAddrFlag !== undefined) {
|
|
260
|
+
config.httpAddr = httpAddrFlag;
|
|
261
|
+
sources.httpAddr = { flag: '--http-addr' };
|
|
262
|
+
}
|
|
263
|
+
else if (httpAddrEnv !== undefined) {
|
|
264
|
+
config.httpAddr = httpAddrEnv;
|
|
265
|
+
sources.httpAddr = process.env.ARC1_HTTP_ADDR !== undefined ? { env: 'ARC1_HTTP_ADDR' } : { env: 'SAP_HTTP_ADDR' };
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
config.httpAddr = '0.0.0.0:8080';
|
|
269
|
+
sources.httpAddr = 'default';
|
|
270
|
+
}
|
|
157
271
|
const portOverride = getFlag('port') ?? process.env.ARC1_PORT;
|
|
158
272
|
if (portOverride) {
|
|
159
273
|
const parsedPort = Number.parseInt(portOverride, 10);
|
|
@@ -162,133 +276,147 @@ export function parseArgs(args) {
|
|
|
162
276
|
}
|
|
163
277
|
const addrHost = config.httpAddr.includes(':') ? config.httpAddr.split(':')[0] : '0.0.0.0';
|
|
164
278
|
config.httpAddr = `${addrHost}:${parsedPort}`;
|
|
279
|
+
sources.httpAddr = getFlag('port') !== undefined ? { flag: '--port' } : { env: 'ARC1_PORT' };
|
|
165
280
|
}
|
|
166
|
-
//
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
}
|
|
173
|
-
Object.assign(config, profile);
|
|
174
|
-
}
|
|
175
|
-
// --- Safety (individual flags override profile defaults) ---
|
|
176
|
-
// Only override profile defaults when the flag/env is explicitly set
|
|
177
|
-
const readOnlyExplicit = getFlag('read-only') ?? process.env.SAP_READ_ONLY;
|
|
178
|
-
if (readOnlyExplicit !== undefined)
|
|
179
|
-
config.readOnly = readOnlyExplicit === 'true' || readOnlyExplicit === '1';
|
|
180
|
-
else if (!profileName)
|
|
181
|
-
config.readOnly = true;
|
|
182
|
-
const blockFreeSQLExplicit = getFlag('block-free-sql') ?? process.env.SAP_BLOCK_FREE_SQL;
|
|
183
|
-
if (blockFreeSQLExplicit !== undefined)
|
|
184
|
-
config.blockFreeSQL = blockFreeSQLExplicit === 'true' || blockFreeSQLExplicit === '1';
|
|
185
|
-
else if (!profileName)
|
|
186
|
-
config.blockFreeSQL = true;
|
|
187
|
-
const blockDataExplicit = getFlag('block-data') ?? process.env.SAP_BLOCK_DATA;
|
|
188
|
-
if (blockDataExplicit !== undefined)
|
|
189
|
-
config.blockData = blockDataExplicit === 'true' || blockDataExplicit === '1';
|
|
190
|
-
else if (!profileName)
|
|
191
|
-
config.blockData = true;
|
|
192
|
-
config.allowedOps = resolve('allowed-ops', 'SAP_ALLOWED_OPS', '');
|
|
193
|
-
config.disallowedOps = resolve('disallowed-ops', 'SAP_DISALLOWED_OPS', '');
|
|
281
|
+
// ── Safety (positive opt-ins) ──────────────────────────────────────
|
|
282
|
+
config.allowWrites = resolveBool('allow-writes', 'SAP_ALLOW_WRITES', false, 'allowWrites');
|
|
283
|
+
config.allowDataPreview = resolveBool('allow-data-preview', 'SAP_ALLOW_DATA_PREVIEW', false, 'allowDataPreview');
|
|
284
|
+
config.allowFreeSQL = resolveBool('allow-free-sql', 'SAP_ALLOW_FREE_SQL', false, 'allowFreeSQL');
|
|
285
|
+
config.allowTransportWrites = resolveBool('allow-transport-writes', 'SAP_ALLOW_TRANSPORT_WRITES', false, 'allowTransportWrites');
|
|
286
|
+
config.allowGitWrites = resolveBool('allow-git-writes', 'SAP_ALLOW_GIT_WRITES', false, 'allowGitWrites');
|
|
194
287
|
const pkgs = getFlag('allowed-packages') ?? process.env.SAP_ALLOWED_PACKAGES;
|
|
195
|
-
if (pkgs) {
|
|
288
|
+
if (pkgs !== undefined) {
|
|
196
289
|
const raw = pkgs.split(',').map((p) => p.trim());
|
|
197
290
|
const filtered = raw.filter((p) => p.length > 0);
|
|
198
291
|
if (raw.length !== filtered.length) {
|
|
199
292
|
logger.warn("SAP_ALLOWED_PACKAGES contained empty entries — likely shell expansion of unset $VARs. Use single quotes: SAP_ALLOWED_PACKAGES='$TMP,Z*'", { raw: pkgs, parsed: filtered });
|
|
200
293
|
}
|
|
201
294
|
config.allowedPackages = filtered;
|
|
295
|
+
sources.allowedPackages =
|
|
296
|
+
getFlag('allowed-packages') !== undefined ? { flag: '--allowed-packages' } : { env: 'SAP_ALLOWED_PACKAGES' };
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
sources.allowedPackages = 'default';
|
|
300
|
+
}
|
|
301
|
+
const transports = getFlag('allowed-transports') ?? process.env.SAP_ALLOWED_TRANSPORTS;
|
|
302
|
+
if (transports !== undefined) {
|
|
303
|
+
config.allowedTransports = transports
|
|
304
|
+
.split(',')
|
|
305
|
+
.map((t) => t.trim())
|
|
306
|
+
.filter((t) => t.length > 0);
|
|
307
|
+
sources.allowedTransports =
|
|
308
|
+
getFlag('allowed-transports') !== undefined
|
|
309
|
+
? { flag: '--allowed-transports' }
|
|
310
|
+
: { env: 'SAP_ALLOWED_TRANSPORTS' };
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
sources.allowedTransports = 'default';
|
|
202
314
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
config.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
315
|
+
// ── Deny Actions (parsed + validated; fails fast on error) ─────────
|
|
316
|
+
const denyActionsRaw = getFlag('deny-actions') ?? process.env.SAP_DENY_ACTIONS;
|
|
317
|
+
if (denyActionsRaw) {
|
|
318
|
+
const fromFile = denyActionsRaw.startsWith('/') ||
|
|
319
|
+
denyActionsRaw.startsWith('./') ||
|
|
320
|
+
denyActionsRaw.startsWith('~/') ||
|
|
321
|
+
denyActionsRaw.startsWith('../');
|
|
322
|
+
const parsed = parseDenyActions(denyActionsRaw);
|
|
323
|
+
validateDenyActions(parsed);
|
|
324
|
+
config.denyActions = parsed;
|
|
325
|
+
sources.denyActions = fromFile
|
|
326
|
+
? { file: denyActionsRaw.replace(/^~/, process.env.HOME ?? '~') }
|
|
327
|
+
: getFlag('deny-actions') !== undefined
|
|
328
|
+
? { flag: '--deny-actions' }
|
|
329
|
+
: { env: 'SAP_DENY_ACTIONS' };
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
sources.denyActions = 'default';
|
|
333
|
+
}
|
|
334
|
+
// ── Features ───────────────────────────────────────────────────────
|
|
335
|
+
config.featureAbapGit = resolveFeature('feature-abapgit', 'SAP_FEATURE_ABAPGIT', 'featureAbapGit');
|
|
336
|
+
config.featureGcts = resolveFeature('feature-gcts', 'SAP_FEATURE_GCTS', 'featureGcts');
|
|
337
|
+
config.featureRap = resolveFeature('feature-rap', 'SAP_FEATURE_RAP', 'featureRap');
|
|
338
|
+
config.featureAmdp = resolveFeature('feature-amdp', 'SAP_FEATURE_AMDP', 'featureAmdp');
|
|
339
|
+
config.featureUi5 = resolveFeature('feature-ui5', 'SAP_FEATURE_UI5', 'featureUi5');
|
|
340
|
+
config.featureTransport = resolveFeature('feature-transport', 'SAP_FEATURE_TRANSPORT', 'featureTransport');
|
|
341
|
+
config.featureHana = resolveFeature('feature-hana', 'SAP_FEATURE_HANA', 'featureHana');
|
|
342
|
+
config.featureUi5Repo = resolveFeature('feature-ui5repo', 'SAP_FEATURE_UI5REPO', 'featureUi5Repo');
|
|
343
|
+
config.featureFlp = resolveFeature('feature-flp', 'SAP_FEATURE_FLP', 'featureFlp');
|
|
344
|
+
// ── System Type Detection ──────────────────────────────────────────
|
|
345
|
+
const systemType = resolveStr('system-type', 'SAP_SYSTEM_TYPE', 'auto', 'systemType');
|
|
225
346
|
config.systemType = (['btp', 'onprem'].includes(systemType) ? systemType : 'auto');
|
|
226
|
-
//
|
|
227
|
-
config.apiKey = getFlag('api-key') ?? process.env.ARC1_API_KEY;
|
|
228
|
-
// Multiple API keys with per-key profiles: "key1:viewer,key2:developer"
|
|
347
|
+
// ── Authentication ─────────────────────────────────────────────────
|
|
229
348
|
const apiKeysRaw = getFlag('api-keys') ?? process.env.ARC1_API_KEYS;
|
|
230
349
|
if (apiKeysRaw) {
|
|
231
350
|
config.apiKeys = parseApiKeys(apiKeysRaw);
|
|
351
|
+
sources.apiKeys = getFlag('api-keys') !== undefined ? { flag: '--api-keys' } : { env: 'ARC1_API_KEYS' };
|
|
232
352
|
}
|
|
233
|
-
|
|
234
|
-
|
|
353
|
+
else {
|
|
354
|
+
sources.apiKeys = 'default';
|
|
355
|
+
}
|
|
356
|
+
config.oidcIssuer = resolveOptionalStr('oidc-issuer', 'SAP_OIDC_ISSUER', 'oidcIssuer');
|
|
357
|
+
config.oidcAudience = resolveOptionalStr('oidc-audience', 'SAP_OIDC_AUDIENCE', 'oidcAudience');
|
|
235
358
|
const clockTolerance = getFlag('oidc-clock-tolerance') ?? process.env.SAP_OIDC_CLOCK_TOLERANCE;
|
|
236
359
|
if (clockTolerance) {
|
|
237
360
|
const parsed = Number.parseInt(clockTolerance, 10);
|
|
238
361
|
config.oidcClockTolerance = Number.isNaN(parsed) ? undefined : parsed;
|
|
239
362
|
}
|
|
240
|
-
config.xsuaaAuth = resolveBool('xsuaa-auth', 'SAP_XSUAA_AUTH', false);
|
|
241
|
-
//
|
|
242
|
-
config.btpServiceKey =
|
|
243
|
-
config.btpServiceKeyFile =
|
|
244
|
-
const cbPort =
|
|
363
|
+
config.xsuaaAuth = resolveBool('xsuaa-auth', 'SAP_XSUAA_AUTH', false, 'xsuaaAuth');
|
|
364
|
+
// ── BTP ABAP Environment ───────────────────────────────────────────
|
|
365
|
+
config.btpServiceKey = resolveOptionalStr('btp-service-key', 'SAP_BTP_SERVICE_KEY', 'btpServiceKey');
|
|
366
|
+
config.btpServiceKeyFile = resolveOptionalStr('btp-service-key-file', 'SAP_BTP_SERVICE_KEY_FILE', 'btpServiceKeyFile');
|
|
367
|
+
const cbPort = resolveStr('btp-oauth-callback-port', 'SAP_BTP_OAUTH_CALLBACK_PORT', '0', 'btpOAuthCallbackPort');
|
|
245
368
|
config.btpOAuthCallbackPort = Number.parseInt(cbPort, 10) || 0;
|
|
246
|
-
//
|
|
247
|
-
config.ppEnabled = resolveBool('pp-enabled', 'SAP_PP_ENABLED', false);
|
|
248
|
-
config.ppStrict = resolveBool('pp-strict', 'SAP_PP_STRICT', false);
|
|
249
|
-
config.ppAllowSharedCookies = resolveBool('pp-allow-shared-cookies', 'SAP_PP_ALLOW_SHARED_COOKIES', false);
|
|
250
|
-
//
|
|
251
|
-
config.disableSaml2 = resolveBool('disable-saml', 'SAP_DISABLE_SAML', false);
|
|
252
|
-
//
|
|
253
|
-
const toolMode =
|
|
369
|
+
// ── Principal Propagation ──────────────────────────────────────────
|
|
370
|
+
config.ppEnabled = resolveBool('pp-enabled', 'SAP_PP_ENABLED', false, 'ppEnabled');
|
|
371
|
+
config.ppStrict = resolveBool('pp-strict', 'SAP_PP_STRICT', false, 'ppStrict');
|
|
372
|
+
config.ppAllowSharedCookies = resolveBool('pp-allow-shared-cookies', 'SAP_PP_ALLOW_SHARED_COOKIES', false, 'ppAllowSharedCookies');
|
|
373
|
+
// ── SAML Behavior ──────────────────────────────────────────────────
|
|
374
|
+
config.disableSaml2 = resolveBool('disable-saml', 'SAP_DISABLE_SAML', false, 'disableSaml2');
|
|
375
|
+
// ── Tool Mode ──────────────────────────────────────────────────────
|
|
376
|
+
const toolMode = resolveStr('tool-mode', 'ARC1_TOOL_MODE', 'standard', 'toolMode');
|
|
254
377
|
config.toolMode = (toolMode === 'hyperfocused' ? 'hyperfocused' : 'standard');
|
|
255
|
-
//
|
|
256
|
-
config.abaplintConfig =
|
|
257
|
-
config.lintBeforeWrite = resolveBool('lint-before-write', 'SAP_LINT_BEFORE_WRITE', true);
|
|
258
|
-
|
|
259
|
-
|
|
378
|
+
// ── Lint ───────────────────────────────────────────────────────────
|
|
379
|
+
config.abaplintConfig = resolveOptionalStr('abaplint-config', 'SAP_ABAPLINT_CONFIG', 'abaplintConfig');
|
|
380
|
+
config.lintBeforeWrite = resolveBool('lint-before-write', 'SAP_LINT_BEFORE_WRITE', true, 'lintBeforeWrite');
|
|
381
|
+
config.checkBeforeWrite = resolveBool('check-before-write', 'SAP_CHECK_BEFORE_WRITE', false, 'checkBeforeWrite');
|
|
382
|
+
// ── Cache ──────────────────────────────────────────────────────────
|
|
383
|
+
const cacheMode = resolveStr('cache', 'ARC1_CACHE', 'auto', 'cacheMode');
|
|
260
384
|
config.cacheMode = (['memory', 'sqlite', 'none'].includes(cacheMode) ? cacheMode : 'auto');
|
|
261
|
-
config.cacheFile =
|
|
262
|
-
config.cacheWarmup = resolveBool('cache-warmup', 'ARC1_CACHE_WARMUP', false);
|
|
263
|
-
config.cacheWarmupPackages =
|
|
264
|
-
//
|
|
385
|
+
config.cacheFile = resolveStr('cache-file', 'ARC1_CACHE_FILE', '.arc1-cache.db', 'cacheFile');
|
|
386
|
+
config.cacheWarmup = resolveBool('cache-warmup', 'ARC1_CACHE_WARMUP', false, 'cacheWarmup');
|
|
387
|
+
config.cacheWarmupPackages = resolveStr('cache-warmup-packages', 'ARC1_CACHE_WARMUP_PACKAGES', '', 'cacheWarmupPackages');
|
|
388
|
+
// ── Concurrency ────────────────────────────────────────────────────
|
|
265
389
|
const maxConcurrent = getFlag('max-concurrent') ?? process.env.ARC1_MAX_CONCURRENT;
|
|
266
390
|
if (maxConcurrent) {
|
|
267
391
|
const parsed = Number.parseInt(maxConcurrent, 10);
|
|
268
392
|
config.maxConcurrent = Number.isNaN(parsed) || parsed < 1 ? 1 : parsed;
|
|
269
393
|
}
|
|
270
|
-
//
|
|
271
|
-
config.logFile =
|
|
272
|
-
const logLevel =
|
|
394
|
+
// ── Logging ────────────────────────────────────────────────────────
|
|
395
|
+
config.logFile = resolveOptionalStr('log-file', 'ARC1_LOG_FILE', 'logFile');
|
|
396
|
+
const logLevel = resolveStr('log-level', 'ARC1_LOG_LEVEL', 'info', 'logLevel');
|
|
273
397
|
config.logLevel = (['debug', 'info', 'warn', 'error'].includes(logLevel) ? logLevel : 'info');
|
|
274
|
-
const logFormat =
|
|
398
|
+
const logFormat = resolveStr('log-format', 'ARC1_LOG_FORMAT', 'text', 'logFormat');
|
|
275
399
|
config.logFormat = (logFormat === 'json' ? 'json' : 'text');
|
|
276
|
-
//
|
|
277
|
-
config.verbose = resolveBool('verbose', 'SAP_VERBOSE', false);
|
|
278
|
-
|
|
279
|
-
if (config.verbose) {
|
|
400
|
+
// ── Misc ───────────────────────────────────────────────────────────
|
|
401
|
+
config.verbose = resolveBool('verbose', 'SAP_VERBOSE', false, 'verbose');
|
|
402
|
+
if (config.verbose)
|
|
280
403
|
config.logLevel = 'debug';
|
|
281
|
-
|
|
282
|
-
// --- Startup Validation ---
|
|
404
|
+
// ── Startup Validation ─────────────────────────────────────────────
|
|
283
405
|
validateConfig(config);
|
|
284
|
-
return config;
|
|
406
|
+
return { config, sources };
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Thin wrapper around `resolveConfig` that returns only the config object.
|
|
410
|
+
* Kept for callers that don't need per-field source attribution.
|
|
411
|
+
*/
|
|
412
|
+
export function parseArgs(args) {
|
|
413
|
+
return resolveConfig(args).config;
|
|
285
414
|
}
|
|
286
415
|
/**
|
|
287
416
|
* Validate configuration for internally consistent auth settings.
|
|
288
417
|
* Fails fast at startup for invalid or dangerous config combinations.
|
|
289
418
|
*/
|
|
290
419
|
export function validateConfig(config) {
|
|
291
|
-
// OIDC: audience is required when issuer is set (RFC 9700 §2.3 audience restriction)
|
|
292
420
|
if (config.oidcIssuer && !config.oidcAudience) {
|
|
293
421
|
throw new Error('SAP_OIDC_AUDIENCE is required when SAP_OIDC_ISSUER is set — ' +
|
|
294
422
|
'audience validation prevents token confusion across services (RFC 9700 §2.3)');
|
|
@@ -296,7 +424,6 @@ export function validateConfig(config) {
|
|
|
296
424
|
if (config.oidcAudience && !config.oidcIssuer) {
|
|
297
425
|
throw new Error('SAP_OIDC_ISSUER is required when SAP_OIDC_AUDIENCE is set');
|
|
298
426
|
}
|
|
299
|
-
// PP: ppStrict requires ppEnabled
|
|
300
427
|
if (config.ppStrict && !config.ppEnabled) {
|
|
301
428
|
throw new Error('SAP_PP_STRICT=true requires SAP_PP_ENABLED=true — strict mode has no effect without principal propagation enabled');
|
|
302
429
|
}
|