@robinmordasiewicz/f5xc-xcsh 1.0.91-2601040044 → 1.0.91-2601040203

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.
Files changed (2) hide show
  1. package/dist/index.js +449 -111
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -47052,8 +47052,8 @@ function getLogoModeFromEnv(envPrefix) {
47052
47052
  var CLI_NAME = "xcsh";
47053
47053
  var CLI_FULL_NAME = "F5 Distributed Cloud Shell";
47054
47054
  function getVersion() {
47055
- if ("v1.0.91-2601040044") {
47056
- return "v1.0.91-2601040044";
47055
+ if ("v1.0.91-2601040203") {
47056
+ return "v1.0.91-2601040203";
47057
47057
  }
47058
47058
  if (process.env.XCSH_VERSION) {
47059
47059
  return process.env.XCSH_VERSION;
@@ -145585,6 +145585,387 @@ function buildConnectionInfo(profileName, apiUrl, hasToken, namespace, isConnect
145585
145585
  return info;
145586
145586
  }
145587
145587
 
145588
+ // src/validation/namespace.ts
145589
+ function validateNamespaceScope(domain, action, currentNamespace, resourceType) {
145590
+ const opInfo = getOperationDescription(domain, action, resourceType);
145591
+ if (!opInfo?.namespaceScope) {
145592
+ return { valid: true };
145593
+ }
145594
+ const scope = opInfo.namespaceScope;
145595
+ const normalizedNamespace = currentNamespace.toLowerCase();
145596
+ switch (scope) {
145597
+ case "system":
145598
+ if (normalizedNamespace !== "system") {
145599
+ return {
145600
+ valid: false,
145601
+ scope,
145602
+ message: `${action} on ${resourceType || domain} requires the 'system' namespace`,
145603
+ suggestion: "system"
145604
+ };
145605
+ }
145606
+ return { valid: true, scope };
145607
+ case "shared":
145608
+ if (normalizedNamespace !== "shared") {
145609
+ return {
145610
+ valid: false,
145611
+ scope,
145612
+ message: `${action} on ${resourceType || domain} requires the 'shared' namespace`,
145613
+ suggestion: "shared"
145614
+ };
145615
+ }
145616
+ return { valid: true, scope };
145617
+ case "any":
145618
+ default:
145619
+ return { valid: true, scope };
145620
+ }
145621
+ }
145622
+
145623
+ // src/validation/safety.ts
145624
+ function checkOperationSafety(domain, action, resourceType) {
145625
+ const opInfo = getOperationDescription(domain, action, resourceType);
145626
+ const dangerLevel = opInfo?.dangerLevel || "low";
145627
+ const requiresConfirmation2 = opInfo?.confirmationRequired ?? dangerLevel === "high";
145628
+ const sideEffects = opInfo?.sideEffects;
145629
+ const result = {
145630
+ proceed: dangerLevel !== "high",
145631
+ // High danger requires explicit confirmation
145632
+ dangerLevel,
145633
+ requiresConfirmation: requiresConfirmation2
145634
+ };
145635
+ if (dangerLevel === "high") {
145636
+ result.warning = formatHighDangerWarning(domain, action, sideEffects);
145637
+ } else if (dangerLevel === "medium") {
145638
+ result.warning = formatMediumDangerWarning(domain, action, sideEffects);
145639
+ }
145640
+ if (sideEffects) {
145641
+ result.sideEffects = sideEffects;
145642
+ }
145643
+ return result;
145644
+ }
145645
+ function formatHighDangerWarning(_domain, _action, sideEffects) {
145646
+ const lines = [
145647
+ colorRed("\u26A0\uFE0F WARNING: This is a HIGH DANGER operation"),
145648
+ ""
145649
+ ];
145650
+ if (sideEffects) {
145651
+ if (sideEffects.deletes && sideEffects.deletes.length > 0) {
145652
+ lines.push(
145653
+ colorRed(` Will DELETE: ${sideEffects.deletes.join(", ")}`)
145654
+ );
145655
+ }
145656
+ if (sideEffects.updates && sideEffects.updates.length > 0) {
145657
+ lines.push(` Will UPDATE: ${sideEffects.updates.join(", ")}`);
145658
+ }
145659
+ if (sideEffects.creates && sideEffects.creates.length > 0) {
145660
+ lines.push(
145661
+ colorDim(` Will CREATE: ${sideEffects.creates.join(", ")}`)
145662
+ );
145663
+ }
145664
+ lines.push("");
145665
+ }
145666
+ lines.push(
145667
+ colorRed("This action may be destructive and cannot be undone.")
145668
+ );
145669
+ return lines.join("\n");
145670
+ }
145671
+ function formatMediumDangerWarning(_domain, _action, sideEffects) {
145672
+ const lines = [
145673
+ colorYellow("\u26A0\uFE0F CAUTION: This operation may have significant effects"),
145674
+ ""
145675
+ ];
145676
+ if (sideEffects) {
145677
+ const effects = [];
145678
+ if (sideEffects.creates && sideEffects.creates.length > 0) {
145679
+ effects.push(`creates: ${sideEffects.creates.join(", ")}`);
145680
+ }
145681
+ if (sideEffects.updates && sideEffects.updates.length > 0) {
145682
+ effects.push(`updates: ${sideEffects.updates.join(", ")}`);
145683
+ }
145684
+ if (sideEffects.deletes && sideEffects.deletes.length > 0) {
145685
+ effects.push(`deletes: ${sideEffects.deletes.join(", ")}`);
145686
+ }
145687
+ if (effects.length > 0) {
145688
+ lines.push(` Side effects: ${effects.join("; ")}`);
145689
+ lines.push("");
145690
+ }
145691
+ }
145692
+ return lines.join("\n");
145693
+ }
145694
+
145695
+ // src/validation/reserved-words.ts
145696
+ var RESERVED_ACTIONS = /* @__PURE__ */ new Set([
145697
+ // CRUD operations
145698
+ "create",
145699
+ "delete",
145700
+ "list",
145701
+ "get",
145702
+ "update",
145703
+ "apply",
145704
+ "patch",
145705
+ // Info operations
145706
+ "describe",
145707
+ "show",
145708
+ "status",
145709
+ "logs",
145710
+ "events",
145711
+ // Modification
145712
+ "edit",
145713
+ "replace",
145714
+ "set",
145715
+ "unset",
145716
+ // Lifecycle
145717
+ "start",
145718
+ "stop",
145719
+ "restart",
145720
+ "rollout",
145721
+ "scale",
145722
+ // Connection
145723
+ "attach",
145724
+ "detach",
145725
+ "connect",
145726
+ "disconnect",
145727
+ // Registration
145728
+ "register",
145729
+ "deregister",
145730
+ "associate",
145731
+ "disassociate",
145732
+ // AWS-specific
145733
+ "put",
145734
+ "modify",
145735
+ // Built-in xcsh commands
145736
+ "help",
145737
+ "quit",
145738
+ "exit",
145739
+ "clear",
145740
+ "history",
145741
+ "refresh",
145742
+ "context",
145743
+ "banner",
145744
+ "profile",
145745
+ "completion",
145746
+ // Common CLI patterns
145747
+ "run",
145748
+ "exec",
145749
+ "wait",
145750
+ "open",
145751
+ "close",
145752
+ "inspect",
145753
+ "validate",
145754
+ "diff",
145755
+ "plan",
145756
+ "import",
145757
+ "export"
145758
+ ]);
145759
+ var SHELL_METACHARACTERS = /[;&|`$()<>\\#!{}[\]*?~\n\r]/;
145760
+ var DANGEROUS_PATTERNS = [
145761
+ {
145762
+ pattern: /^-/,
145763
+ message: "Name cannot start with a hyphen (would be interpreted as a flag)"
145764
+ },
145765
+ {
145766
+ pattern: /^--/,
145767
+ message: "Name cannot start with double-hyphen (would be interpreted as a long flag)"
145768
+ },
145769
+ {
145770
+ pattern: /\.\./,
145771
+ message: "Name cannot contain '..' (path traversal risk)"
145772
+ },
145773
+ {
145774
+ pattern: /^\./,
145775
+ message: "Name cannot start with '.' (hidden file pattern)"
145776
+ },
145777
+ {
145778
+ pattern: /\//,
145779
+ message: "Name cannot contain '/' (path separator)"
145780
+ },
145781
+ {
145782
+ pattern: /\\$/,
145783
+ message: "Name cannot end with backslash (escape sequence risk)"
145784
+ },
145785
+ {
145786
+ pattern: /\s/,
145787
+ message: "Name cannot contain whitespace"
145788
+ },
145789
+ {
145790
+ pattern: /^_/,
145791
+ message: "Name cannot start with underscore (reserved for internal use)"
145792
+ }
145793
+ ];
145794
+ var DANGEROUS_COMMANDS = /* @__PURE__ */ new Set([
145795
+ // File destruction
145796
+ "rm",
145797
+ "rm-rf",
145798
+ "rmdir",
145799
+ "del",
145800
+ "unlink",
145801
+ "shred",
145802
+ // Privilege escalation
145803
+ "sudo",
145804
+ "su",
145805
+ "chmod",
145806
+ "chown",
145807
+ "chattr",
145808
+ "setfacl",
145809
+ // Network tools (payload download)
145810
+ "wget",
145811
+ "curl",
145812
+ "nc",
145813
+ "netcat",
145814
+ "socat",
145815
+ "telnet",
145816
+ "ssh",
145817
+ "scp",
145818
+ "rsync",
145819
+ // Shell execution
145820
+ "bash",
145821
+ "sh",
145822
+ "zsh",
145823
+ "csh",
145824
+ "ksh",
145825
+ "fish",
145826
+ "pwsh",
145827
+ "powershell",
145828
+ // Process execution
145829
+ "exec",
145830
+ "eval",
145831
+ "source",
145832
+ "nohup",
145833
+ "xargs",
145834
+ // Process termination
145835
+ "kill",
145836
+ "pkill",
145837
+ "killall",
145838
+ "killall5",
145839
+ // Disk operations
145840
+ "dd",
145841
+ "mkfs",
145842
+ "fdisk",
145843
+ "parted",
145844
+ "format",
145845
+ // System control
145846
+ "reboot",
145847
+ "shutdown",
145848
+ "halt",
145849
+ "poweroff",
145850
+ "init",
145851
+ "systemctl",
145852
+ // Container escape
145853
+ "docker",
145854
+ "kubectl",
145855
+ "nsenter",
145856
+ "chroot"
145857
+ ]);
145858
+ var CONTROL_CHARS = /[\x00-\x1f\x7f]/;
145859
+ var RFC1035_PATTERN = /^[a-z]([a-z0-9-]{0,61}[a-z0-9])?$/;
145860
+
145861
+ // src/validation/resource-name.ts
145862
+ var DEFAULT_OPTIONS = {
145863
+ maxLength: 63,
145864
+ allowUppercase: false,
145865
+ skipFormatValidation: false
145866
+ };
145867
+ function validateResourceName(name, options = {}) {
145868
+ const opts = { ...DEFAULT_OPTIONS, ...options };
145869
+ const nameLower = name.toLowerCase();
145870
+ if (!name || name.trim().length === 0) {
145871
+ return {
145872
+ valid: false,
145873
+ category: "invalid",
145874
+ message: "Resource name cannot be empty"
145875
+ };
145876
+ }
145877
+ if (name.length > opts.maxLength) {
145878
+ return {
145879
+ valid: false,
145880
+ category: "invalid",
145881
+ message: `Name exceeds maximum length of ${opts.maxLength} characters (got ${name.length})`,
145882
+ suggestion: sanitizeName(name.slice(0, opts.maxLength))
145883
+ };
145884
+ }
145885
+ if (CONTROL_CHARS.test(name)) {
145886
+ return {
145887
+ valid: false,
145888
+ category: "security",
145889
+ message: "Name contains control characters which are not allowed"
145890
+ };
145891
+ }
145892
+ if (SHELL_METACHARACTERS.test(name)) {
145893
+ const match = name.match(SHELL_METACHARACTERS);
145894
+ const char = match?.[0] ?? "unknown";
145895
+ const charDesc = getCharacterDescription(char);
145896
+ return {
145897
+ valid: false,
145898
+ category: "security",
145899
+ message: `Name contains shell metacharacter '${charDesc}' which could enable command injection`
145900
+ };
145901
+ }
145902
+ if (RESERVED_ACTIONS.has(nameLower)) {
145903
+ return {
145904
+ valid: false,
145905
+ category: "reserved",
145906
+ message: `'${name}' is a reserved CLI action word and cannot be used as a resource name`,
145907
+ suggestion: `my-${name}`
145908
+ };
145909
+ }
145910
+ if (DANGEROUS_COMMANDS.has(nameLower)) {
145911
+ return {
145912
+ valid: false,
145913
+ category: "dangerous",
145914
+ message: `'${name}' matches a dangerous system command and cannot be used as a resource name`
145915
+ };
145916
+ }
145917
+ for (const { pattern, message } of DANGEROUS_PATTERNS) {
145918
+ if (pattern.test(name)) {
145919
+ return {
145920
+ valid: false,
145921
+ category: "dangerous",
145922
+ message
145923
+ };
145924
+ }
145925
+ }
145926
+ if (!opts.skipFormatValidation) {
145927
+ const nameToCheck = opts.allowUppercase ? nameLower : name;
145928
+ if (!RFC1035_PATTERN.test(nameToCheck.toLowerCase())) {
145929
+ return {
145930
+ valid: false,
145931
+ category: "invalid",
145932
+ message: "Name must start with a letter, end with alphanumeric, and contain only lowercase letters, numbers, and hyphens",
145933
+ suggestion: sanitizeName(name)
145934
+ };
145935
+ }
145936
+ }
145937
+ return { valid: true };
145938
+ }
145939
+ function getCharacterDescription(char) {
145940
+ const descriptions = {
145941
+ ";": "; (command separator)",
145942
+ "&": "& (background execution)",
145943
+ "|": "| (pipe)",
145944
+ "`": "` (command substitution)",
145945
+ $: "$ (variable/command expansion)",
145946
+ "(": "( (subshell)",
145947
+ ")": ") (subshell)",
145948
+ "<": "< (input redirection)",
145949
+ ">": "> (output redirection)",
145950
+ "\\": "\\ (escape)",
145951
+ "#": "# (comment)",
145952
+ "!": "! (history expansion)",
145953
+ "{": "{ (brace expansion)",
145954
+ "}": "} (brace expansion)",
145955
+ "[": "[ (bracket expansion)",
145956
+ "]": "] (bracket expansion)",
145957
+ "*": "* (glob pattern)",
145958
+ "?": "? (glob pattern)",
145959
+ "~": "~ (home directory)",
145960
+ "\n": "newline",
145961
+ "\r": "carriage return"
145962
+ };
145963
+ return descriptions[char] ?? char;
145964
+ }
145965
+ function sanitizeName(name) {
145966
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^[^a-z]+/, "").replace(/-+$/, "").replace(/-{2,}/g, "-").slice(0, 63) || "resource";
145967
+ }
145968
+
145588
145969
  // src/domains/login/profile/create.ts
145589
145970
  var createCommand2 = {
145590
145971
  name: "create",
@@ -145611,6 +145992,18 @@ var createCommand2 = {
145611
145992
  ].join("\n")
145612
145993
  );
145613
145994
  }
145995
+ const nameValidation = validateResourceName(name, {
145996
+ maxLength: 128,
145997
+ // Profiles can have longer names than API resources
145998
+ resourceType: "profile"
145999
+ });
146000
+ if (!nameValidation.valid) {
146001
+ const lines = [`Invalid profile name: ${nameValidation.message}`];
146002
+ if (nameValidation.suggestion) {
146003
+ lines.push("", `Suggested: ${nameValidation.suggestion}`);
146004
+ }
146005
+ return errorResult(lines.join("\n"));
146006
+ }
145614
146007
  const existing = await manager.get(name);
145615
146008
  if (existing) {
145616
146009
  return errorResult(
@@ -150510,13 +150903,13 @@ var RESERVED_API_ACTIONS = /* @__PURE__ */ new Set([
150510
150903
  "add-labels",
150511
150904
  "remove-labels"
150512
150905
  ]);
150513
- function isReservedAction(name) {
150906
+ function isReservedAction2(name) {
150514
150907
  return RESERVED_API_ACTIONS.has(name.toLowerCase());
150515
150908
  }
150516
150909
  function validateExtension(extension) {
150517
150910
  const conflicts = [];
150518
150911
  for (const [name] of extension.commands) {
150519
- if (isReservedAction(name)) {
150912
+ if (isReservedAction2(name)) {
150520
150913
  conflicts.push(name);
150521
150914
  }
150522
150915
  }
@@ -151861,114 +152254,15 @@ function useGitStatus(options = {}) {
151861
152254
  return { gitInfo, refresh, lastRefresh };
151862
152255
  }
151863
152256
 
151864
- // src/validation/namespace.ts
151865
- function validateNamespaceScope(domain, action, currentNamespace, resourceType) {
151866
- const opInfo = getOperationDescription(domain, action, resourceType);
151867
- if (!opInfo?.namespaceScope) {
151868
- return { valid: true };
151869
- }
151870
- const scope = opInfo.namespaceScope;
151871
- const normalizedNamespace = currentNamespace.toLowerCase();
151872
- switch (scope) {
151873
- case "system":
151874
- if (normalizedNamespace !== "system") {
151875
- return {
151876
- valid: false,
151877
- scope,
151878
- message: `${action} on ${resourceType || domain} requires the 'system' namespace`,
151879
- suggestion: "system"
151880
- };
151881
- }
151882
- return { valid: true, scope };
151883
- case "shared":
151884
- if (normalizedNamespace !== "shared") {
151885
- return {
151886
- valid: false,
151887
- scope,
151888
- message: `${action} on ${resourceType || domain} requires the 'shared' namespace`,
151889
- suggestion: "shared"
151890
- };
151891
- }
151892
- return { valid: true, scope };
151893
- case "any":
151894
- default:
151895
- return { valid: true, scope };
151896
- }
151897
- }
151898
-
151899
- // src/validation/safety.ts
151900
- function checkOperationSafety(domain, action, resourceType) {
151901
- const opInfo = getOperationDescription(domain, action, resourceType);
151902
- const dangerLevel = opInfo?.dangerLevel || "low";
151903
- const requiresConfirmation2 = opInfo?.confirmationRequired ?? dangerLevel === "high";
151904
- const sideEffects = opInfo?.sideEffects;
151905
- const result = {
151906
- proceed: dangerLevel !== "high",
151907
- // High danger requires explicit confirmation
151908
- dangerLevel,
151909
- requiresConfirmation: requiresConfirmation2
151910
- };
151911
- if (dangerLevel === "high") {
151912
- result.warning = formatHighDangerWarning(domain, action, sideEffects);
151913
- } else if (dangerLevel === "medium") {
151914
- result.warning = formatMediumDangerWarning(domain, action, sideEffects);
151915
- }
151916
- if (sideEffects) {
151917
- result.sideEffects = sideEffects;
151918
- }
151919
- return result;
151920
- }
151921
- function formatHighDangerWarning(_domain, _action, sideEffects) {
151922
- const lines = [
151923
- colorRed("\u26A0\uFE0F WARNING: This is a HIGH DANGER operation"),
151924
- ""
151925
- ];
151926
- if (sideEffects) {
151927
- if (sideEffects.deletes && sideEffects.deletes.length > 0) {
151928
- lines.push(
151929
- colorRed(` Will DELETE: ${sideEffects.deletes.join(", ")}`)
151930
- );
151931
- }
151932
- if (sideEffects.updates && sideEffects.updates.length > 0) {
151933
- lines.push(` Will UPDATE: ${sideEffects.updates.join(", ")}`);
151934
- }
151935
- if (sideEffects.creates && sideEffects.creates.length > 0) {
151936
- lines.push(
151937
- colorDim(` Will CREATE: ${sideEffects.creates.join(", ")}`)
151938
- );
151939
- }
151940
- lines.push("");
151941
- }
151942
- lines.push(
151943
- colorRed("This action may be destructive and cannot be undone.")
151944
- );
151945
- return lines.join("\n");
151946
- }
151947
- function formatMediumDangerWarning(_domain, _action, sideEffects) {
151948
- const lines = [
151949
- colorYellow("\u26A0\uFE0F CAUTION: This operation may have significant effects"),
151950
- ""
151951
- ];
151952
- if (sideEffects) {
151953
- const effects = [];
151954
- if (sideEffects.creates && sideEffects.creates.length > 0) {
151955
- effects.push(`creates: ${sideEffects.creates.join(", ")}`);
151956
- }
151957
- if (sideEffects.updates && sideEffects.updates.length > 0) {
151958
- effects.push(`updates: ${sideEffects.updates.join(", ")}`);
151959
- }
151960
- if (sideEffects.deletes && sideEffects.deletes.length > 0) {
151961
- effects.push(`deletes: ${sideEffects.deletes.join(", ")}`);
151962
- }
151963
- if (effects.length > 0) {
151964
- lines.push(` Side effects: ${effects.join("; ")}`);
151965
- lines.push("");
151966
- }
151967
- }
151968
- return lines.join("\n");
151969
- }
151970
-
151971
152257
  // src/repl/executor.ts
152258
+ var WRITE_OPERATIONS = /* @__PURE__ */ new Set([
152259
+ "create",
152260
+ "replace",
152261
+ "apply",
152262
+ "patch",
152263
+ "add-labels",
152264
+ "remove-labels"
152265
+ ]);
151972
152266
  var BUILTIN_COMMANDS = /* @__PURE__ */ new Set([
151973
152267
  "help",
151974
152268
  "--help",
@@ -152653,6 +152947,50 @@ async function executeAPICommand(session, ctx, cmd) {
152653
152947
  );
152654
152948
  const { resourceType, name, namespace, outputFormat, spec, noColor } = parseCommandArgs(args, domainResourceTypes);
152655
152949
  const effectiveNamespace = namespace ?? session.getNamespace();
152950
+ if (effectiveNamespace) {
152951
+ const nsNameValidation = validateResourceName(effectiveNamespace, {
152952
+ resourceType: "namespace"
152953
+ });
152954
+ if (!nsNameValidation.valid) {
152955
+ const lines = [
152956
+ `Error: Invalid namespace name '${effectiveNamespace}'`,
152957
+ "",
152958
+ nsNameValidation.message ?? "Invalid namespace name"
152959
+ ];
152960
+ if (nsNameValidation.suggestion) {
152961
+ lines.push("", `Suggested: ${nsNameValidation.suggestion}`);
152962
+ }
152963
+ return {
152964
+ output: lines,
152965
+ shouldExit: false,
152966
+ shouldClear: false,
152967
+ contextChanged: false,
152968
+ error: nsNameValidation.message ?? "Invalid namespace name"
152969
+ };
152970
+ }
152971
+ }
152972
+ if (WRITE_OPERATIONS.has(action) && name) {
152973
+ const nameValidation = validateResourceName(name, {
152974
+ resourceType: resourceType ?? canonicalDomain
152975
+ });
152976
+ if (!nameValidation.valid) {
152977
+ const lines = [
152978
+ `Error: Invalid resource name '${name}'`,
152979
+ "",
152980
+ nameValidation.message ?? "Invalid resource name"
152981
+ ];
152982
+ if (nameValidation.suggestion) {
152983
+ lines.push("", `Suggested: ${nameValidation.suggestion}`);
152984
+ }
152985
+ return {
152986
+ output: lines,
152987
+ shouldExit: false,
152988
+ shouldClear: false,
152989
+ contextChanged: false,
152990
+ error: nameValidation.message ?? "Invalid resource name"
152991
+ };
152992
+ }
152993
+ }
152656
152994
  const effectiveResource = resourceType ?? canonicalDomain;
152657
152995
  const nsValidation = validateNamespaceScope(
152658
152996
  canonicalDomain,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robinmordasiewicz/f5xc-xcsh",
3
- "version": "1.0.91-2601040044",
3
+ "version": "1.0.91-2601040203",
4
4
  "description": "F5 Distributed Cloud Shell - Interactive CLI for F5 XC",
5
5
  "type": "module",
6
6
  "bin": {