@martin4455/redmine-mcp-ro 0.3.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/CONFIG.md ADDED
@@ -0,0 +1,242 @@
1
+ # Local configuration and system credentials
2
+
3
+ Version 0.3.0 stores a complete connection JSON in the operating system credential
4
+ store. The local configuration uses format `3.0` and holds references and optional
5
+ request limits and download settings. The package version, local file format and
6
+ protected JSON version are independent values.
7
+
8
+ ## Select a directory
9
+
10
+ ```bash
11
+ redmine-mcp configure --config-dir /absolute/path/to/project
12
+ redmine-mcp-ro --config-dir /absolute/path/to/project
13
+ ```
14
+
15
+ The directory must already exist. Selection uses `--config-dir`, then
16
+ `REDMINE_CONFIG_DIR`, then the current working directory. There is no parent or
17
+ home search, global fallback or `--global` option. To use a former home configuration,
18
+ select that directory explicitly. Multiple clients can select the same directory.
19
+
20
+ For a source checkout, build first and replace `redmine-mcp` with `node dist/cli.js`
21
+ and `redmine-mcp-ro` with `node dist/index.js`. On Windows use a path such as
22
+ `C:\projects\example`; inside JSON escape it as `C:\\projects\\example`.
23
+
24
+ The application does not read `.env`. `REDMINE_URL` and `REDMINE_API_KEY` are not
25
+ configuration sources. Use `configure` to set or change a connection.
26
+
27
+ ## Local file format
28
+
29
+ ```json
30
+ {
31
+ "version": "3.0",
32
+ "defaultProfile": "default",
33
+ "profiles": {
34
+ "default": {
35
+ "credentialId": "08a93a80-42bc-4c88-a7d9-a10c24985de3"
36
+ }
37
+ },
38
+ "settings": {
39
+ "requestTimeoutMs": 30000,
40
+ "maxAttachmentBytes": 52428800,
41
+ "maxContentBytes": 5242880
42
+ }
43
+ }
44
+ ```
45
+
46
+ `settings` is optional. A profile accepts only `credentialId`. Adding `url`,
47
+ `apiKey` or other unknown fields is rejected. Profile names, UUIDs and limits remain
48
+ visible on disk. The example UUID above is a placeholder, not a working credential.
49
+
50
+ New profile names contain 1-64 ASCII letters, digits, dots, underscores or hyphens,
51
+ starting with a letter or digit; `constructor` and `prototype` are reserved.
52
+
53
+ A UUID identifies an entry; it does not contain or decrypt its secret. The system
54
+ store uses service `@martin4455/redmine-mcp-ro` and the UUID as the entry account.
55
+ The secret value has this shape (example values only):
56
+
57
+ ```json
58
+ {
59
+ "version": 1,
60
+ "url": "https://redmine.example.com",
61
+ "apiKey": "EXAMPLE_ONLY_REPLACE_WITH_YOUR_TOKEN"
62
+ }
63
+ ```
64
+
65
+ Both URL and token are read from that protected value. The URL cannot be overridden
66
+ by local metadata or environment credentials. A reference does not isolate programs
67
+ running under the same OS account; access is governed by the system store.
68
+ Tokens and URLs are present in process memory during use. Redmine itself may return
69
+ URLs and personal data as ordinary API content.
70
+
71
+ ## Platform setup
72
+
73
+ | Platform | Store and requirements |
74
+ | --- | --- |
75
+ | macOS | Login Keychain. Unlock it and allow the Node.js executable when prompted. |
76
+ | Windows | Credential Manager under the account running MCP. |
77
+ | Linux / Ubuntu | Secret Service, such as GNOME Keyring, unlocked in the same user D-Bus session. |
78
+
79
+ The adapter uses pinned `@napi-rs/keyring` 2.1.0 and native APIs, without shell
80
+ commands containing credentials. Linux explicitly selects `secret-service`, so
81
+ there is no kernel-keyring or file fallback. Locked, denied or unavailable stores
82
+ cause an error. Native optional dependencies for the target platform must be installed.
83
+
84
+ Ubuntu Desktop normally provides GNOME Keyring. Where it is absent, install it
85
+ with `sudo apt install gnome-keyring dbus`. SSH, Docker and CI require an explicitly
86
+ provisioned and unlocked Secret Service in the session used by the launcher.
87
+ Preserve `DBUS_SESSION_BUS_ADDRESS` and the relevant `XDG_RUNTIME_DIR`. The isolated
88
+ Ubuntu test harness is an example for synthetic test data, not a persistent vault.
89
+
90
+ Windows Credential Manager limits the credential value to 2560 bytes. This adapter
91
+ checks the UTF-16 size of the complete JSON before writing, including the URL, token,
92
+ field names and escaping. An oversized value fails without a plaintext fallback.
93
+ WSL uses the Linux backend, not Windows Credential Manager.
94
+
95
+ Configure and run under the same OS account. A service account or another machine
96
+ requires its own accessible credential store.
97
+
98
+ ## Configure, inspect and rotate
99
+
100
+ ```bash
101
+ redmine-mcp configure --profile default --config-dir /path/to/project
102
+ redmine-mcp list --config-dir /path/to/project
103
+ redmine-mcp status --profile default --config-dir /path/to/project
104
+ redmine-mcp use default --config-dir /path/to/project
105
+ redmine-mcp remove default --config-dir /path/to/project
106
+ ```
107
+
108
+ `configure` masks token input. The URL is shown in its interactive prompt, including
109
+ as the default when updating an existing profile. The optional connection check
110
+ reports success without printing a user's name. `list` reads metadata without
111
+ accessing the keyring; `status` verifies access but displays neither URL nor token.
112
+
113
+ For rotation, run `configure` again with the same profile and enter the new token.
114
+ It creates a new UUID, writes the complete JSON, verifies exact read-back, commits
115
+ the local file, then deletes the old entry. A failure before the file commit leaves
116
+ the old configuration usable. Restart MCP to reload the credential held in memory.
117
+ `remove` commits removal of the local reference before deleting the entry. Cleanup
118
+ failures return a warning with the UUID and a nonzero exit code for manual removal.
119
+
120
+ A copied file under the same OS account can refer to the same credential. Rotation
121
+ or deletion invalidates that old reference in other copies. Prefer selecting one
122
+ shared configuration directory or configuring independent profiles.
123
+
124
+ Server profile selection uses `--profile`, then `REDMINE_PROFILE`, then
125
+ `defaultProfile`, then the first profile. An unknown explicit selection is rejected.
126
+
127
+ ## Migration
128
+
129
+ ```bash
130
+ redmine-mcp migrate --config-dir /path/to/project
131
+ redmine-mcp status --config-dir /path/to/project
132
+ ```
133
+
134
+ The server and ordinary CLI commands reject older formats until migration. The
135
+ migration command supports:
136
+
137
+ - Format `1.0`: plaintext URLs and tokens are written to new protected entries.
138
+ - Format `2.0` with a URL and UUID: the existing protected JSON is checked against
139
+ the local URL, then the URL is removed from the local file. Existing entries in
140
+ the current service retain their UUIDs.
141
+ - Format `2.0` with only a UUID: the current service is checked first. Only a missing
142
+ entry permits lookup in the historical `redmine-mcp-ro` service. Its complete JSON
143
+ is copied and verified under a new UUID in `@martin4455/redmine-mcp-ro`.
144
+
145
+ A denied, locked or invalid current-store entry does not trigger fallback to another
146
+ namespace. Historical entries are retained because other installations may use them.
147
+ Migrating one copy does not update other files. Remove obsolete historical entries
148
+ separately once no installation needs them.
149
+
150
+ All profiles are validated before replacing the original file with format `3.0`.
151
+ Legacy names outside the new naming rules are converted to unique valid names.
152
+ Existing valid names are preserved; collisions receive numeric suffixes. The
153
+ default profile follows its renamed entry, and the CLI prints each name mapping.
154
+ Update explicit `--profile` or `REDMINE_PROFILE` selections to the reported name.
155
+ Format `1.0` tokens have surrounding whitespace trimmed during migration; embedded
156
+ line breaks and NUL bytes are rejected. URLs with embedded credentials, query
157
+ strings or fragments still require manual correction, with a diagnostic identifying
158
+ the profile by its position without printing the URL or token.
159
+
160
+ Migration does not create a plaintext backup. On failure before the commit, the
161
+ original file is retained and newly created entries are deleted where possible.
162
+ Migration does not erase backups, Git history, copies or previously exposed data;
163
+ rotate tokens that were exposed. Repeating migration on format `3.0` is a no-op.
164
+
165
+ ## File writes and limits
166
+
167
+ Writes use a private temporary metadata file, flush it and rename it over the
168
+ configuration. A lock and an original-content check prevent normal concurrent
169
+ configurators from overwriting one another. Configuration symlinks, files larger
170
+ than 1 MiB, unknown fields and duplicate UUID references are rejected. On POSIX,
171
+ new metadata files have mode `0600`; on Windows, directory ACLs apply.
172
+
173
+ The numeric `settings` keys accept positive safe integers. Environment settings take
174
+ precedence over corresponding local settings; defaults apply if both are absent.
175
+
176
+ | Local setting | Environment variable | Default |
177
+ | --- | --- | --- |
178
+ | `requestTimeoutMs` | `REDMINE_REQUEST_TIMEOUT_MS` | `30000` |
179
+ | `maxAttachmentBytes` | `REDMINE_MAX_ATTACHMENT_BYTES` | `52428800` |
180
+ | `maxContentBytes` | `REDMINE_MAX_CONTENT_BYTES` | `5242880` |
181
+ | `downloadDir` | `REDMINE_DOWNLOAD_DIR` | Unset; local downloads disabled |
182
+
183
+ Timeout cannot exceed `2147483647` ms. JSON API responses have a fixed 10 MiB limit.
184
+ If no configurator is running but a previous process crashed, remove a stale
185
+ `.redmine-mcp.lock` in the selected directory before retrying.
186
+
187
+ ## Local attachment downloads
188
+
189
+ `download_attachment` and `download_issue_attachments` write local files and are
190
+ marked accordingly in MCP tool annotations. They require an existing, dedicated
191
+ download directory selected by the operator. For example, create
192
+ `/absolute/path/to/redmine-downloads` and add `"downloadDir":
193
+ "/absolute/path/to/redmine-downloads"` to `settings`, or set `REDMINE_DOWNLOAD_DIR`
194
+ in the MCP launcher's environment. The environment value takes precedence. Paths
195
+ must be absolute; `~` is not expanded. This setting is non-secret and visible on disk.
196
+
197
+ Use a directory separate from source projects, home configuration and locations
198
+ used to load scripts or other executable code. Downloaded content remains untrusted.
199
+ The tool's optional `output_dir` accepts only a relative subdirectory such as
200
+ `issue-123/documents`, using forward slashes. It defaults to the configured root,
201
+ never the server's working directory. Missing subdirectories are created privately.
202
+ Absolute paths, parent traversal, symlink subdirectories and dot-prefixed filenames
203
+ are rejected. Existing files and symlinks are never overwritten. The root's canonical
204
+ location is trusted; directory checks do not isolate a hostile process running as
205
+ the same OS user and concurrently changing the filesystem.
206
+
207
+ `get_attachment_content` returns bounded base64 content without filesystem writes
208
+ and remains available without this setting.
209
+
210
+ ## Response filtering and diagnostics
211
+
212
+ Both the API client and MCP boundary recursively remove structured API-key fields
213
+ (`api_key`, `apiKey`, `api-key`, `X-Redmine-API-Key`, case-insensitively). Other user
214
+ data, issue text and attachments are returned normally. This filter is not a general
215
+ secret scanner. Earlier client history and tool results are not rewritten.
216
+
217
+ Request errors exclude response bodies and request headers. Native errors are
218
+ replaced with generic platform-specific guidance. API requests and redirects are
219
+ restricted to the configured origin, including every redirect through a proxy.
220
+ Redirect handling avoids `follow-redirects` request logging, so `DEBUG=*` and
221
+ `DEBUG=follow-redirects` do not expose the API-key header. Use HTTPS outside isolated
222
+ local tests.
223
+
224
+ ## Verification
225
+
226
+ ```bash
227
+ npm run typecheck
228
+ npm run lint
229
+ npm test
230
+ npm run test:keyring
231
+ npm run test:keyring:ubuntu
232
+ npm run test:integration
233
+ docker compose -f test/docker-compose.test.yml down --volumes
234
+ ```
235
+
236
+ Unit tests use test doubles for credentials. The native smoke test uses synthetic
237
+ credentials, separate processes and a loopback HTTP fixture, then removes its test
238
+ entries. Run it on each target OS, including Windows. The Ubuntu container also
239
+ checks that an unavailable D-Bus session fails without fallback. The Redmine suite
240
+ uses disposable Docker containers and an in-memory credential-store test double;
241
+ it does not access the host keyring. With images already present, use
242
+ `REDMINE_TEST_OFFLINE=1 npm run test:integration` to prohibit image pulls.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 martin4455
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # Redmine MCP Server (Read-Only)
2
+
3
+ `@martin4455/redmine-mcp-ro` provides MCP tools for reading the Redmine REST API
4
+ through the stdio transport. Version 0.3.0 stores the complete connection JSON,
5
+ including the Redmine URL and API token, in the operating system credential store.
6
+
7
+ ## Requirements
8
+
9
+ - Node.js 24.15 or later in the 24.x line, or Node.js 26 and later.
10
+ - Redmine with its REST API enabled and an API key for the intended account.
11
+ - An accessible macOS Keychain, Windows Credential Manager, or Linux Secret Service.
12
+
13
+ Access to Redmine resources follows the permissions of the configured account.
14
+ Some endpoints require administrator permissions.
15
+
16
+ ## Installation and configuration
17
+
18
+ ```bash
19
+ npm install -g @martin4455/redmine-mcp-ro@0.3.0
20
+ redmine-mcp configure --config-dir /absolute/path/to/project
21
+ redmine-mcp status --config-dir /absolute/path/to/project
22
+ ```
23
+
24
+ The selected directory must already exist. The configurator masks token input and
25
+ can test the connection before saving. It stores `{version, url, apiKey}` together
26
+ in the system credential store and verifies the saved value before committing the
27
+ local reference. The URL appears in the interactive configuration prompt; `list`
28
+ and `status` do not display the URL or token.
29
+
30
+ The local `.redmine-mcp` file uses format `3.0` and contains profile names, random
31
+ credential identifiers, and optional request limits and download settings. It contains neither the URL
32
+ nor the API token. Configure and run MCP under the same OS account.
33
+
34
+ On macOS, unlock the login Keychain and allow access for the Node.js executable
35
+ when prompted. Windows uses Credential Manager for the account running MCP.
36
+ Linux uses an unlocked Secret Service provider, such as GNOME Keyring, in the
37
+ same user D-Bus session. A headless Linux process needs that session set up
38
+ explicitly; see [platform setup](./CONFIG.md#platform-setup).
39
+
40
+ Without `--config-dir`, configuration resolves from `REDMINE_CONFIG_DIR`, then the
41
+ current working directory. There is no parent-directory or home-directory search.
42
+ The server does not load `.env` and does not use `REDMINE_URL` or `REDMINE_API_KEY`.
43
+ An unavailable system store stops the operation; there is no plaintext fallback.
44
+
45
+ ## Upgrade and token rotation
46
+
47
+ Older local configuration formats `1.0` and `2.0` require explicit migration:
48
+
49
+ ```bash
50
+ redmine-mcp migrate --config-dir /absolute/path/to/project
51
+ redmine-mcp status --config-dir /absolute/path/to/project
52
+ ```
53
+
54
+ Migration supports plaintext legacy profiles, profiles with a local URL and UUID,
55
+ and the earlier reference-only format using service `redmine-mcp-ro`. It writes
56
+ format `3.0` and uses service `@martin4455/redmine-mcp-ro`. Historical-service entries
57
+ are retained because another local installation may still use them. No plaintext
58
+ backup is created. Existing backups and copies are not erased.
59
+
60
+ After rotating the token in Redmine, run `configure` for the existing profile and
61
+ enter the replacement token. The command creates and verifies a new credential,
62
+ commits the reference, then deletes the previous entry. Restart the MCP client to
63
+ load the new connection. Copies of the old reference will need reconfiguration.
64
+
65
+ ```bash
66
+ redmine-mcp configure --profile default --config-dir /absolute/path/to/project
67
+ ```
68
+
69
+ See [CONFIG.md](./CONFIG.md) for the file format, platform setup and migration details.
70
+
71
+ ## MCP client configuration
72
+
73
+ After configuring the directory, add a stdio server to your MCP client:
74
+
75
+ ```json
76
+ {
77
+ "mcpServers": {
78
+ "redmine": {
79
+ "command": "redmine-mcp-ro",
80
+ "args": ["--config-dir", "/absolute/path/to/project"]
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ Use an absolute executable path if the client does not inherit your terminal's
87
+ PATH. A source checkout can use `node` with the absolute path to `dist/index.js`
88
+ as the first argument. Windows JSON paths need escaped backslashes, for example
89
+ `C:\\projects\\example`. No URL or token belongs in the MCP client's JSON.
90
+
91
+ This package implements stdio. It does not provide an HTTP MCP server.
92
+
93
+ ## Profiles
94
+
95
+ ```bash
96
+ redmine-mcp configure --profile staging --config-dir /absolute/path/to/project
97
+ redmine-mcp list --config-dir /absolute/path/to/project
98
+ redmine-mcp use staging --config-dir /absolute/path/to/project
99
+ redmine-mcp-ro --profile staging --config-dir /absolute/path/to/project
100
+ redmine-mcp remove staging --config-dir /absolute/path/to/project
101
+ ```
102
+
103
+ Server profile selection uses `--profile`, then `REDMINE_PROFILE`, then the local
104
+ default. An unknown explicitly selected profile fails rather than selecting another.
105
+ `remove` deletes the selected profile and its corresponding credential.
106
+
107
+ ## Available tools
108
+
109
+ | Resources | Tools |
110
+ | --- | --- |
111
+ | Issues | `list_issues`, `get_issue`, `list_issue_relations`, `get_relation` |
112
+ | Projects | `list_projects`, `get_project`, `list_project_versions`, `get_version`, `list_project_memberships`, `get_membership`, `list_project_issue_categories`, `get_issue_category`, `list_project_files` |
113
+ | Users and groups | `list_users`, `get_user`, `get_current_user`, `list_groups`, `get_group` |
114
+ | Time entries | `list_time_entries`, `get_time_entry`, `list_time_entry_activities` |
115
+ | Wiki and news | `list_wiki_pages`, `get_wiki_page`, `list_news` |
116
+ | Metadata | `list_issue_statuses`, `list_trackers`, `list_issue_priorities`, `list_roles`, `get_role`, `list_custom_fields`, `list_queries` |
117
+ | Search | `search` |
118
+ | Attachments | `get_attachment`, `get_attachment_content`, `download_attachment`, `download_issue_attachments` |
119
+
120
+ Local attachment downloads are disabled by default. To enable them, create a
121
+ dedicated directory and set `REDMINE_DOWNLOAD_DIR` or `settings.downloadDir` to its
122
+ absolute path. Use a directory for untrusted downloads, separate from source
123
+ projects, home configuration and executable code. `output_dir` may select only a
124
+ relative subdirectory; omitting it uses the configured root. Absolute paths, parent
125
+ traversal, symlink subdirectories and dot-prefixed filenames are rejected. Existing
126
+ files are never overwritten. `get_attachment_content` does not write files and
127
+ does not require a download directory. See [CONFIG.md](./CONFIG.md#local-attachment-downloads).
128
+
129
+ ## Security behavior
130
+
131
+ Structured API-key fields returned by Redmine are removed recursively in the API
132
+ client and at the MCP output boundary. This includes `api_key`, `apiKey`, `api-key`
133
+ and `X-Redmine-API-Key`, case-insensitively. Other Redmine data, including names,
134
+ URLs, issue text and attachments, remains available as requested. The filter does
135
+ not scan arbitrary text for secrets.
136
+
137
+ Use HTTPS and an account with the permissions required for your use case. URLs
138
+ with embedded credentials, query strings or fragments are rejected. API requests,
139
+ attachments and redirects must remain on the configured origin. Downloaded filenames
140
+ are validated against path traversal. Errors do not serialize request headers or
141
+ response bodies. HTTP redirects use the native transport with explicit origin
142
+ checks, so `DEBUG=*` and `DEBUG=follow-redirects` do not log the API-key header.
143
+
144
+ Embedded attachment discovery ignores links outside the configured origin and
145
+ does not execute document scripts or load remote HTML resources. Parser diagnostics
146
+ containing document text are not forwarded to logs. Downloaded files are saved,
147
+ never executed by this package; their contents remain untrusted.
148
+
149
+ Default limits are 30 seconds per request, 50 MiB per downloaded file, 5 MiB for
150
+ attachment content returned through MCP, and 10 MiB for JSON API responses. The
151
+ first three can be changed through the documented local settings or environment
152
+ variables in [CONFIG.md](./CONFIG.md).
153
+
154
+ ## Development and verification
155
+
156
+ ```bash
157
+ npm ci --ignore-scripts
158
+ npm run typecheck
159
+ npm run lint
160
+ npm test
161
+ npm run build
162
+ npm run check:package
163
+ ```
164
+
165
+ The build cleans generated `dist/`, then compiles production TypeScript without
166
+ source maps or test helpers. The npm archive contains compiled JavaScript, type
167
+ declarations, `README.md`, `CONFIG.md`, `LICENSE` and `package.json`. The `prepack`
168
+ script builds and checks this list automatically. There are no install lifecycle
169
+ scripts in this package. Dependencies are installed according to normal npm rules.
170
+ Direct runtime dependency versions are pinned. The repository lockfile fixes
171
+ development and test installs; consumers can still resolve different transitive
172
+ dependency versions within the dependencies' own ranges.
173
+
174
+ Additional tests use synthetic credentials and disposable test resources:
175
+
176
+ ```bash
177
+ npm run test:keyring
178
+ npm run test:keyring:ubuntu
179
+ npm run test:integration
180
+ docker compose -f test/docker-compose.test.yml down --volumes
181
+ ```
182
+
183
+ The native keyring test verifies persistence across processes, interactive CLI,
184
+ MCP authentication, migration, rotation and cleanup. Run it on each target OS to
185
+ verify native behavior. The Ubuntu test runs GNOME Keyring and D-Bus in Docker.
186
+ The Redmine integration suite requires Docker and an accessible system store; use
187
+ `REDMINE_TEST_PORT=18080` if port 8080 is occupied. Unit tests use test doubles for
188
+ credential storage and local HTTP fixtures.
189
+
190
+ ## License and project links
191
+
192
+ MIT; see [LICENSE](./LICENSE).
193
+
194
+ - [Source repository](https://github.com/luskan/redmine-mcp)
195
+ - [Issue tracker](https://github.com/luskan/redmine-mcp/issues)
196
+ - [npm package](https://www.npmjs.com/package/@martin4455/redmine-mcp-ro)
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Redmine includes api_key in user responses for the account owner and admins.
3
+ * Remove these fields at every depth, including users embedded by extensions.
4
+ * This filters structured fields, not arbitrary secrets in issue text or files.
5
+ */
6
+ export declare function stripApiKeys(value: unknown): unknown;
@@ -0,0 +1,16 @@
1
+ const API_KEY_FIELD = /^(?:api[_-]?key|x-redmine-api-key)$/i;
2
+ /**
3
+ * Redmine includes api_key in user responses for the account owner and admins.
4
+ * Remove these fields at every depth, including users embedded by extensions.
5
+ * This filters structured fields, not arbitrary secrets in issue text or files.
6
+ */
7
+ export function stripApiKeys(value) {
8
+ if (Array.isArray(value))
9
+ return value.map(stripApiKeys);
10
+ if (value !== null && typeof value === 'object') {
11
+ return Object.fromEntries(Object.entries(value)
12
+ .filter(([key]) => !API_KEY_FIELD.test(key))
13
+ .map(([key, entry]) => [key, stripApiKeys(entry)]));
14
+ }
15
+ return value;
16
+ }
@@ -0,0 +1,15 @@
1
+ import type { AttachmentContent, AttachmentDownloadOptions, DownloadedAttachment, DownloadedIssueAttachment, RedmineConfig } from './types.js';
2
+ export declare class AttachmentDownloader {
3
+ private readonly config;
4
+ private readonly client;
5
+ private readonly parser;
6
+ private readonly limits;
7
+ constructor(config: RedmineConfig);
8
+ private metadata;
9
+ private contentUrl;
10
+ downloadAttachment(attachmentId: number, outputDir?: string, options?: AttachmentDownloadOptions): Promise<DownloadedAttachment>;
11
+ private saveFile;
12
+ downloadIssueAttachments(issueId: number, outputDir?: string): Promise<DownloadedIssueAttachment[]>;
13
+ downloadAllIssueAttachments(issueId: number, outputDir?: string, includeEmbedded?: boolean): Promise<DownloadedIssueAttachment[]>;
14
+ getAttachmentContent(attachmentId: number): Promise<AttachmentContent>;
15
+ }