Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/codeql/codeql-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: liquid-potassium CodeQL config

paths-ignore:
- dist/**
- src/generated/**
- tests/generated/**
- coverage/**
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,30 @@ name: CI

on:
pull_request:
branches:
- main
push:
branches:
- main
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
verify:
name: Verify
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Check out repository
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Set up Node.js
uses: actions/setup-node@v4
Expand Down
53 changes: 53 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: CodeQL

on:
pull_request:
branches:
- main
push:
branches:
- main
schedule:
- cron: "27 4 * * 1"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
analyze:
name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
actions: read
contents: read
strategy:
fail-fast: false
matrix:
include:
- language: javascript-typescript
build-mode: none
steps:
- name: Check out repository
uses: actions/checkout@v5
with:
persist-credentials: false

- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
config-file: ./.github/codeql/codeql-config.yml
queries: security-extended

- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:${{ matrix.language }}"
upload: never
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,15 @@ coverage/
.vitest/
.tsbuildinfo
*.tsbuildinfo
*.log
npm-debug.log*

# Local credentials and machine-specific package registry state.
.env
.env.*
!.env.example
.npmrc
*.pem
*.key
*.p12
*.pfx
25 changes: 21 additions & 4 deletions PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ This file is the canonical live tracker for the Infomaniak SDK and OpenClaw inte

## Current State

- Phase: OpenClaw migration readiness.
- Active task: SDK workflow actions and migration documentation for `potassium-openclaw` are complete and validated.
- Repository status: The Phase 1-11 implementation is complete on branch `codex/native-domain-workflows`.
- Phase: Published SDK release and OpenClaw migration readiness.
- Active task: Migrating downstream `potassium-openclaw` usage to the published `liquid-potassium` package.
- Repository status: `liquid-potassium@0.1.0` is published on npm; OpenClaw workflow tooling and kDrive upload file-path support are implemented on branch `codex/liquid-library-openclaw-workflows`.
- Coordination mode: one main agent owns sequencing, verification, integration, and commits.

## Coordination Rules
Expand Down Expand Up @@ -93,10 +93,22 @@ This file is the canonical live tracker for the Infomaniak SDK and OpenClaw inte
- `docs: document OpenClaw migration workflow bridge`
- Documents `client.workflows` as the SDK-first bridge for migrating `potassium-openclaw`.
- Records that OpenClaw-specific orchestration belongs in the OpenClaw adapter/repository, while this library owns reusable network/domain workflows.
- `feat: expose openclaw workflow tools`
- Added OpenClaw workflow list/describe/run tools backed by `client.workflows`, with domain/operation allow/deny policy, mutating blocks, explicit confirmation, and injected fetch.
- Added `tokenEnvName` config defaulting to `INFOMANIAK_TOKEN` so OpenClaw can inject credentials without copying bearer tokens into plugin config.
- Added public exports and the `./openclaw/tools` package subpath for downstream `potassium-openclaw` migration.
- Added kDrive workflow uploads from absolute local `file_path`, including size validation, default remote file names, and binary body preservation in the shared transport.
- Added a package `prepare` script so GitHub-pinned installs can build `dist` without publishing `liquid-potassium` to npm.
- `security: harden public repository readiness`
- Redacted sensitive response headers and secret-shaped JSON/text fields from `InfomaniakOperationError.responseSummary` while preserving status, request id, and non-sensitive diagnostics.
- Added tests for JSON, malformed JSON, and text error redaction while maintaining 100% coverage.
- Tightened local ignore rules for credential files and generated build leftovers.
- Restricted CI workflow permissions to read-only contents access and disabled checkout credential persistence.
- Added `SECURITY.md` and Dependabot updates for npm and GitHub Actions.

## Next Task Queue

- In `/Users/opencow/Software/potassium-openclaw`, migrate existing skills/tools to call `liquid-potassium` through `client.workflows` where a curated action exists.
- In `/Users/opencow/Software/potassium-openclaw`, depend on a specific `OpenCow42/liquidPotassium` commit and migrate existing skills/tools to call `liquid-potassium` through the exported OpenClaw tools and `client.workflows` where a curated action exists.
- Fall back to catalog search/describe plus generated/raw SDK calls for operations that are not yet worth promoting to workflow actions.
- Add new workflow actions in this repository only when migration exposes repeated adapter code or a common user workflow.

Expand Down Expand Up @@ -125,6 +137,11 @@ This file is the canonical live tracker for the Infomaniak SDK and OpenClaw inte
- Open-source release preparation: passing `npm run ci`, `npm audit`, `npm audit --omit=dev`, `npm pack --dry-run`, and `git diff --check`.
- Package preview: `npm pack --dry-run` reports `liquid-potassium@0.1.0`, 160 files, approximately 881.6 kB packed and 16.1 MB unpacked, including `LICENSE`, `README.md`, built SDK output, OpenClaw plugin output, skill docs, Mail application docs, and normalization report.
- Domain workflow actions: passing `npm run typecheck` and `npm run test:coverage` with 100% statements, branches, functions, and lines after the workflow expansion and module split.
- OpenClaw workflow tool migration slice: passing `npm run typecheck`, `npm test`, `npm run test:coverage` with 100% statements/branches/functions/lines, `npm run build`, and `npm pack --dry-run --json`.
- Package preview after workflow tool migration: `npm pack --dry-run --json` reports `liquid-potassium@0.1.0`, 169 files, approximately 898.8 kB packed and 16.2 MB unpacked, including `dist/src/openclaw/infomaniak-tools.*`, `dist/openclaw/plugin.*`, skill docs, package exports, and the `prepare` build flow.
- npm publication: `liquid-potassium@0.1.0` is published to `https://registry.npmjs.org/` with `latest` pointing to `0.1.0`.
- Post-publish install verification: a fresh temporary project installed `liquid-potassium@0.1.0` from npm with 0 vulnerabilities and successfully imported the main SDK, OpenClaw plugin, and OpenClaw tools subpaths.
- Public GitHub security preflight: passing `npm run ci`, `npm audit`, `npm audit --omit=dev`, `npm pack --dry-run --json`, `git diff --check`, and high-confidence secret scans across current tracked files and git history.

## Blockers And Risks

Expand Down
20 changes: 20 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Security Policy

## Supported Versions

Security fixes are prepared for the latest published npm release line.

## Reporting A Vulnerability

Please report suspected vulnerabilities through GitHub's private vulnerability reporting or a private security advisory for this repository.

Do not open a public issue with exploit details, access tokens, cookies, mailbox content, customer data, or production payloads. If a public issue is the only available channel, keep it minimal and ask for a private follow-up path.

Useful reports include:

- affected package version or commit;
- impacted SDK, OpenClaw, generation, or documentation-snapshot surface;
- reproduction steps using mocked data whenever possible;
- whether credentials, authorization headers, cookies, request bodies, response summaries, generated metadata, or package contents are involved.

The project does not require real Infomaniak network calls for tests. Security reproductions should use injected `fetch`, local fixtures, or redacted recordings.
9 changes: 7 additions & 2 deletions openclaw/skills/infomaniak/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@ Use the Infomaniak plugin as a compact API navigator and caller. Prefer domain n
3. Use `infomaniak_describe` on the best operation before calling it. Check path parameters, query parameters, request content types, auth, operation requirements, discovery suggestions, and the `mutating` flag.
4. Use `infomaniak_discover` when an operation needs opaque IDs such as account ids, drive ids, mail access ids, kChat team ids, or product ids. If discovery reports `no_public_discovery`, explain that clearly instead of guessing.
5. Use `infomaniak_mail_application` for mailbox content workflows such as listing the current user's mailboxes, folders, threads, reading message resources, checking quotas, drafts, schedules, cancellation, and moving messages. This is separate from Mail Hosting/admin operations in the generated `mail` domain.
6. Use `infomaniak_call` only after the operation and inputs are clear.
6. Use `infomaniak_workflow_list` and `infomaniak_workflow_describe` for reviewed SDK workflow actions before falling back to raw operations. Prefer `infomaniak_workflow_run` for common tasks such as kDrive browsing/upload, kChat posts, URL shortener links, discovery workflows, and other domain actions.
7. Use `infomaniak_call` only after no reviewed workflow fits and the operation and inputs are clear.

## Safety

- Do not guess operation IDs or required path parameters. Search and describe first.
- Use `infomaniak_discover` before asking the user for opaque resource IDs when a reviewed public discovery recipe exists.
- If discovery says a resource has no public discovery path, ask the user for the ID or explain where it must come from.
- Use Mail application actions for mailbox consumption, not generated Mail Hosting operations.
- Prefer reviewed workflow actions over raw operation calls when they cover the user request.
- Treat `mutating: true` as create/update/delete/send/archive or other state-changing work.
- Call a mutating operation only when the user has explicitly asked for that state change and the request parameters are clear.
- Set `confirm_mutating=true` only for explicit mutating user intent, including mutating Mail application actions. If the plugin blocks mutation, explain that the plugin policy prevents the call.
- Set `confirm_mutating=true` only for explicit mutating user intent, including mutating Mail application and workflow actions. If the plugin blocks mutation, explain that the plugin policy prevents the call.
- Prefer read-only discovery for ambiguous requests.

## Tool Model
Expand All @@ -35,4 +37,7 @@ Use the Infomaniak plugin as a compact API navigator and caller. Prefer domain n
- `infomaniak_describe`: inspect one normalized operation ID, including resource requirements and discovery suggestions.
- `infomaniak_discover`: discover resource IDs or return reviewed no-public-discovery explanations.
- `infomaniak_mail_application`: call reviewed mailbox-consumption actions outside the public OpenAPI catalog.
- `infomaniak_workflow_list`: list reviewed SDK workflow actions available under plugin policy.
- `infomaniak_workflow_describe`: inspect one reviewed workflow action, including input metadata, mutating status, and backing operation IDs.
- `infomaniak_workflow_run`: run one reviewed workflow action with optional `input` and `confirm_mutating`.
- `infomaniak_call`: call one normalized operation ID with optional `path`, `query`, `headers`, `body`, and `confirm_mutating`.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
"./openclaw/plugin": {
"types": "./dist/openclaw/plugin.d.ts",
"default": "./dist/openclaw/plugin.js"
},
"./openclaw/tools": {
"types": "./dist/src/openclaw/infomaniak-tools.d.ts",
"default": "./dist/src/openclaw/infomaniak-tools.js"
}
},
"files": [
Expand All @@ -43,6 +47,7 @@
"openclaw/skills",
"README.md",
"RELEASE.md",
"SECURITY.md",
"spec/normalization-report.json"
],
"engines": {
Expand All @@ -54,6 +59,7 @@
"check:generated": "npm run normalize && npm run generate && git diff --exit-code -- spec/infomaniak.normalized.json spec/normalization-report.json src/generated tests/generated",
"check:docs": "npm run docs:check && git diff --exit-code -- spec/docs-enrichment-report.json",
"check:no-network": "tsx scripts/check-no-network-policy.ts",
"prepare": "npm run build",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
Expand Down
58 changes: 54 additions & 4 deletions src/client/create-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export type InfomaniakClient = GeneratedOperationClient &

const defaultBaseUrl = "https://api.infomaniak.com";
const defaultMailApplicationBaseUrl = "https://mail.infomaniak.com";
const responseSummaryMaxLength = 500;
const redactedValue = "[redacted]";
const sensitiveHeaderNames = new Set(["authorization", "proxy-authorization", "cookie", "set-cookie", "x-api-key", "x-auth-token"]);
const sensitiveFieldNameFragments = ["authorization", "password", "passwd", "secret", "token", "apikey", "privatekey", "session", "cookie", "csrf"];
const operationById = new Map<string, OperationMetadata>(operations.map((operation) => [operation.operationId, operation]));

export function createInfomaniakClient(config: InfomaniakClientConfig = {}): InfomaniakClient {
Expand Down Expand Up @@ -237,7 +241,15 @@ function serializeBody(body: unknown, headers: Headers): BodyInit | undefined {
}

function isJsonSerializableBody(body: unknown): boolean {
return typeof body === "object" && body !== null && !(body instanceof ArrayBuffer) && !(body instanceof Blob) && !(body instanceof FormData) && !(body instanceof URLSearchParams);
return (
typeof body === "object" &&
body !== null &&
!(body instanceof ArrayBuffer) &&
!ArrayBuffer.isView(body) &&
!(body instanceof Blob) &&
!(body instanceof FormData) &&
!(body instanceof URLSearchParams)
);
}

async function parseSuccessResponse(response: Response): Promise<unknown> {
Expand All @@ -256,16 +268,54 @@ async function parseSuccessResponse(response: Response): Promise<unknown> {

async function safeResponseSummary(response: Response): Promise<string> {
const contentType = response.headers.get("content-type") ?? "";
const body = await response.text();
if (!body) {
return "";
}
if (contentType.includes("application/json")) {
return JSON.stringify(await response.json());
try {
return truncateResponseSummary(JSON.stringify(redactJsonValue(JSON.parse(body))));
} catch {
return truncateResponseSummary(redactSensitiveText(body));
}
}
return truncateResponseSummary(redactSensitiveText(body));
}

function redactJsonValue(value: unknown, key?: string): unknown {
if (key && isSensitiveFieldName(key)) {
return redactedValue;
}
if (Array.isArray(value)) {
return value.map((item) => redactJsonValue(item));
}
return (await response.text()).slice(0, 500);
if (isQueryObject(value)) {
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [entryKey, redactJsonValue(entryValue, entryKey)]));
}
return value;
}

function redactSensitiveText(value: string): string {
return value
.replace(/\b(authorization|proxy-authorization)\s*:\s*bearer\s+[^\r\n]+/gi, "$1: Bearer [redacted]")
.replace(/\b((?:set-)?cookie)\s*:\s*[^\r\n]+/gi, "$1: [redacted]")
.replace(/\b(access[_-]?token|refresh[_-]?token|api[_-]?key|password|secret|token)\s*=\s*[^&\s]+/gi, "$1=[redacted]")
.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [redacted]");
}

function truncateResponseSummary(value: string): string {
return value.slice(0, responseSummaryMaxLength);
}

function isSensitiveFieldName(key: string): boolean {
const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
return sensitiveFieldNameFragments.some((fragment) => normalized.includes(fragment));
}

function headersToRecord(headers: Headers): Record<string, string> {
const record: Record<string, string> = {};
headers.forEach((value, key) => {
record[key] = value;
record[key] = sensitiveHeaderNames.has(key.toLowerCase()) ? redactedValue : value;
});
return record;
}
18 changes: 18 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,24 @@ export {
type DomainWorkflowClient,
type DomainWorkflowRuntimeClient,
} from "./workflows/domain-actions.js";
export {
createInfomaniakOpenClawTools,
InfomaniakPluginConfigJsonSchema,
infomaniakCallToolSchema,
infomaniakDiscoverToolSchema,
infomaniakDescribeToolSchema,
infomaniakDomainsToolSchema,
infomaniakMailApplicationToolSchema,
infomaniakSearchToolSchema,
infomaniakWorkflowDescribeToolSchema,
infomaniakWorkflowListToolSchema,
infomaniakWorkflowRunToolSchema,
resolveInfomaniakPluginConfig,
type InfomaniakOpenClawPluginConfig,
type InfomaniakOpenClawToolsOptions,
type OperationDescriptionWithRequirements,
type OperationSummary,
} from "./openclaw/infomaniak-tools.js";
export {
buildDomainMetadata,
buildOperationCatalog,
Expand Down
Loading
Loading