@dozimple/abap-adt 1.0.0 → 1.1.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/CHANGELOG.md +89 -0
- package/README.es.md +8 -6
- package/README.md +8 -6
- package/SECURITY.md +12 -11
- package/dist/core/atc.js +2 -1
- package/dist/core/audit.js +81 -12
- package/dist/core/auditkey.js +85 -0
- package/dist/core/catalog.en.js +1 -0
- package/dist/core/catalog.js +1 -0
- package/dist/core/config.js +89 -1
- package/dist/core/confirm.js +19 -7
- package/dist/core/connection.js +46 -1
- package/dist/core/credentials.js +10 -3
- package/dist/core/datapolicy.js +15 -7
- package/dist/core/errors.js +34 -6
- package/dist/core/feeds.js +11 -1
- package/dist/core/objects.js +10 -0
- package/dist/core/policy.js +10 -1
- package/dist/core/registry.js +97 -15
- package/dist/index.js +8 -2
- package/dist/scripts/audit-verify.js +25 -6
- package/dist/tools/core/api_release_state.js +5 -0
- package/dist/tools/core/atc_quickfix.js +2 -2
- package/dist/tools/core/ddic_type_info.js +2 -0
- package/dist/tools/core/dumps.js +2 -1
- package/dist/tools/core/gateway_errors.js +2 -1
- package/dist/tools/core/get_source.js +2 -2
- package/dist/tools/core/revert_source.js +111 -0
- package/dist/tools/core/run_atc.js +9 -4
- package/dist/tools/core/run_unit_tests.js +6 -1
- package/dist/tools/core/sql_query.js +18 -2
- package/dist/tools/core/support.js +6 -3
- package/dist/tools/core/syntax_check.js +17 -2
- package/dist/tools/core/table_contents.js +43 -7
- package/dist/tools/core/text_elements.js +11 -3
- package/dist/tools/core/transport_diff.js +10 -1
- package/dist/tools/core/where_used.js +6 -1
- package/dist/tools/core/write_source.js +14 -2
- package/dist/tools/docs/docs.js +8 -0
- package/dist/tools/local/sap_systems.js +4 -0
- package/dist/tools/transport-risk/_service.js +7 -2
- package/docs/THREAT_MODEL.md +4 -3
- package/docs/TOOLS.md +44 -2
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,95 @@ Formato basado en [Keep a Changelog](https://keepachangelog.com/es-ES/1.1.0/). V
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.1.0] - 2026-09-22
|
|
8
|
+
|
|
9
|
+
Tool nueva y contrato MCP ampliado (salida estructurada, progreso, cancelación) más resiliencia de conexión: el sprint 1 y parte del 2 del plan de mejoras.
|
|
10
|
+
|
|
11
|
+
### Añadido
|
|
12
|
+
- **Progreso y cancelación** (sprint 2 del plan): las tools largas informan de su avance (`transport_diff` objeto a
|
|
13
|
+
objeto; `run_atc`, `where_used` y `run_unit_tests` por fase) y el cliente lo recibe como notificaciones de progreso
|
|
14
|
+
MCP si las pidió. La cancelación del cliente se respeta **entre pasos**: el paso en curso termina (una llamada ADT
|
|
15
|
+
no se puede abortar) y el siguiente ya no empieza. Una petición cancelada termina como `CANCELLED`, un tipo propio
|
|
16
|
+
que no cuenta como fallo del servidor ni abre el circuito.
|
|
17
|
+
- **Salida estructurada** (`outputSchema` / `structuredContent` de MCP), sprint 2 del plan: una tool puede declarar
|
|
18
|
+
`output` y devolver los mismos datos del texto de forma tipada, para que el cliente no tenga que interpretar la
|
|
19
|
+
prosa. El registro exige que toda respuesta no errónea de esas tools la traiga (si falta es error del servidor,
|
|
20
|
+
no un éxito a medias) y el SDK la valida contra el esquema antes de responder. Primeras tools: `sql_query` (filas,
|
|
21
|
+
columnas, valores hasta 500, `truncated`, avisos) y `syntax_check` (errores, avisos y mensajes con línea y
|
|
22
|
+
severidad). `docs/TOOLS.md` documenta el esquema de salida de cada una.
|
|
23
|
+
- **`revert_source`: volver a una versión anterior con confirmación.** Tras un `write_source` cuya activación falló,
|
|
24
|
+
el objeto queda con un borrador inactivo encima de la versión activa; la tool vuelve a escribir la versión elegida
|
|
25
|
+
(`active`: la última activa; `previous`: la anterior a la activa; `N`: una del historial de `object_versions`)
|
|
26
|
+
pasando por la misma vista previa, huella, bloqueo y orden que cualquier escritura. Nunca revierte por su cuenta:
|
|
27
|
+
un rollback automático que pisara una versión sin preguntar sería peor que dejar el objeto inactivo.
|
|
28
|
+
- **Resiliencia de conexión** (sprint 1 del plan de mejoras):
|
|
29
|
+
- **Sesión caducada renovada en las lecturas.** Si una tool de lectura recibe un token CSRF rechazado o un 401 en
|
|
30
|
+
una sesión que ya había entrado, el servidor descarta el cliente, vuelve a entrar y repite la lectura una vez;
|
|
31
|
+
la respuesta lo anota. Nunca en escrituras ni ejecuciones: el bloqueo se perdió con la sesión y un reintento
|
|
32
|
+
podría escribir dos veces. Una contraseña rechazada sigue fallando a la primera y sigue olvidándose.
|
|
33
|
+
- **Circuit breaker por sistema.** Tres fallos de red en un minuto abren el circuito de ESE sistema durante un
|
|
34
|
+
minuto: toda tool responde al instante «no se vuelve a intentar durante N s» en vez de esperar 120 s por llamada.
|
|
35
|
+
Los demás sistemas no se ven afectados; `sap_systems(check=true)` lo cierra y reintenta. Solo cuentan los fallos
|
|
36
|
+
de red reales de la librería, no los tiempos agotados propios.
|
|
37
|
+
- **Tiempo máximo por tool** (`timeoutMs`, por defecto 60 s; ATC y diff de orden 180 s, where-used y ABAP Unit
|
|
38
|
+
120 s). Al vencer, error `NETWORK` con el tiempo y la sugerencia de acotar; no se reintenta.
|
|
39
|
+
|
|
40
|
+
### Cambiado
|
|
41
|
+
- Insignia de **OpenSSF Best Practices (Passing)** en el README: el proyecto cumple los 67 criterios del nivel
|
|
42
|
+
Passing, incluidas las sugerencias, con la ficha pública en https://www.bestpractices.dev/projects/14759.
|
|
43
|
+
- El job de publicación usa Node 24, que ya trae npm >= 11.5.1: se quita la instalación global de npm, que no se
|
|
44
|
+
puede fijar por hash. Un paso comprueba la versión y falla antes de publicar si no la cumple. El build sigue en
|
|
45
|
+
Node 22, la versión mínima que soporta el servidor.
|
|
46
|
+
|
|
47
|
+
## [1.0.1] - 2026-09-22
|
|
48
|
+
|
|
49
|
+
Versión de mantenimiento: seguimiento de la auditoría de seguridad, procedimiento de commit y pruebas por propiedades.
|
|
50
|
+
|
|
51
|
+
### Añadido
|
|
52
|
+
- **Pruebas por propiedades de la entrada hostil** (`test/property.test.ts`, fast-check): 15 invariantes sobre el
|
|
53
|
+
filtro de `table_contents`, el saneado de errores, el HTML de documentación y feeds, los nombres que acaban en una
|
|
54
|
+
ruta ADT, `SAP_USER_RE` y la huella de los tokens de confirmación. En vez de comprobar casos conocidos, cada prueba
|
|
55
|
+
afirma lo que la función garantiza para **cualquier** entrada y el generador busca el contraejemplo (miles por
|
|
56
|
+
ejecución; `FC_RUNS=5000 npm test` para una pasada profunda). Validadas rompiendo el código a propósito: cada
|
|
57
|
+
propiedad detecta la regresión que le toca.
|
|
58
|
+
|
|
59
|
+
### Cambiado
|
|
60
|
+
- La release de GitHub adjunta el bundle de la atestación también como `<paquete>.intoto.jsonl`, además de
|
|
61
|
+
`.sigstore.json`. Es el mismo bundle in-toto con los dos nombres: uno es el que verifica `gh attestation verify` y el
|
|
62
|
+
otro el que las herramientas de cadena de suministro reconocen como procedencia (OpenSSF Scorecard entre ellas).
|
|
63
|
+
|
|
64
|
+
### Seguridad
|
|
65
|
+
- El veto de datos sensibles incluye `ICF_PASSWD` (contraseña del usuario de inicio de sesión fijo de un servicio ICF)
|
|
66
|
+
y las columnas genéricas `PASSWD` / `PASSWORD`, en cualquier tabla, también cuando llegan por un `SELECT *` (se
|
|
67
|
+
comprueban las columnas del resultado y, si aparece una, no se muestra nada). Detectado al revisar la configuración
|
|
68
|
+
ICF de un servicio.
|
|
69
|
+
- Seguimiento de la auditoría (prioridades 1 y 2 del auditor y DZ-30): **permisos de `systems.json` en Windows**
|
|
70
|
+
comprobados por ACL y SID (antes no se comprobaba nada en esa plataforma), fail-closed si no se pueden leer;
|
|
71
|
+
**registro de auditoría firmado con HMAC-SHA256** con clave en el llavero del SO (quitar firmas se detecta), y
|
|
72
|
+
`audit:verify` comprueba cadena y firmas; el escáner falla si `package.json` declara scripts de instalación.
|
|
73
|
+
- **Auditoría de seguridad del 21-09-2026** (30 hallazgos, ninguno alto ni crítico): corregidos todos los accionables.
|
|
74
|
+
Los más relevantes: la escritura ya no aplica un diff aprobado sobre un objeto que cambió en SAP después de la vista
|
|
75
|
+
previa (huella comprobada bajo el bloqueo); filtro de `table_contents` por lista blanca; usuarios SAP validados en
|
|
76
|
+
filtros de feeds; nombres validados en rutas ADT; `allowSelfSigned` solo en DEV y avisado al arrancar; sin
|
|
77
|
+
redirecciones en el servicio de riesgo; errores saneados antes de llegar al modelo; contraseñas en memoria con
|
|
78
|
+
caducidad; toda tool `exec` decide expresamente si pide confirmación; tope de filas también en `jobs` y
|
|
79
|
+
`application_log`; columnas personales propias por cliente (`piiColumns`); registro de auditoría con bloqueo entre
|
|
80
|
+
procesos y lectura de la cola; marcado neutralizado tras decodificar HTML; ids online de `docs_fetch` validados ya
|
|
81
|
+
decodificados. Documentado lo que cada control garantiza y lo que no (token frente a elicitación, cadena de
|
|
82
|
+
auditoría sin secreto, aviso de «dato» probabilístico). Tests de regresión en `test/audit-2026-09.test.ts`.
|
|
83
|
+
- **Procedimiento de seguridad en cada commit** ([docs/COMMIT_SECURITY.md](docs/COMMIT_SECURITY.md)): el `pre-commit`
|
|
84
|
+
revisa el código completo y el contenido exacto preparado; un `pre-push` nuevo y el CI revisan todo el historial de
|
|
85
|
+
todas las ramas. El escáner detecta además direcciones (IPs reales, hosts con puertos de SAP, dominios internos),
|
|
86
|
+
más tipos de credenciales (Google, JWT, tokens de GitHub de grano fino, GitLab, cadenas de conexión, secretos
|
|
87
|
+
asignados, URLs con contraseña, webhooks) y archivos peligrosos por su nombre (`.env`, certificados, claves SSH, el
|
|
88
|
+
`systems.json` real). 21 tests adversariales.
|
|
89
|
+
|
|
90
|
+
### Corregido
|
|
91
|
+
- Workflow de release: la comprobación de `latest` fallaba porque npm tarda unos segundos en propagar la etiqueta
|
|
92
|
+
(la 1.0.0 se publicó bien, pero la release de GitHub no se creó y se completó a mano con los artefactos firmados del
|
|
93
|
+
mismo run). Ahora reintenta durante 2 minutos, no republica una versión existente y actualiza la release si ya
|
|
94
|
+
existe: reintentar un run es seguro.
|
|
95
|
+
|
|
7
96
|
## [1.0.0] - 2026-09-21
|
|
8
97
|
|
|
9
98
|
Primera versión estable, publicada en npm como `@dozimple/abap-adt` con procedencia.
|
package/README.es.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[English](README.md) · **Español**
|
|
6
6
|
|
|
7
|
-
[](../../actions/workflows/ci.yml)    
|
|
7
|
+
[](../../actions/workflows/ci.yml) [](https://www.bestpractices.dev/projects/14759)    
|
|
8
8
|
|
|
9
9
|
**IA que trabaja en tu SAP con las reglas de un consultor senior.**
|
|
10
10
|
|
|
@@ -61,7 +61,7 @@ El agente elige las tools, las encadena y responde con evidencia. Cada respuesta
|
|
|
61
61
|
| [Consulta de datos](#g-datos) | Preguntar a las tablas con ABAP SQL, de solo lectura y sin tocar material de credenciales. | 2 |
|
|
62
62
|
| [Diagnóstico de incidentes](#g-diagnostico) | Reunir en una conversación lo que antes exigía ST22, SM37, SLG1 y /IWFND/ERROR_LOG. | 4 |
|
|
63
63
|
| [Documentación SAP](#g-documentacion) | Responder con la documentación oficial y comprobar qué sintaxis existe en cada release. | 7 |
|
|
64
|
-
| [Escritura controlada](#g-escritura) | Guardar cambios solo en desarrollo, en la orden correcta y con la sintaxis verificada antes. |
|
|
64
|
+
| [Escritura controlada](#g-escritura) | Guardar cambios solo en desarrollo, en la orden correcta y con la sintaxis verificada antes. | 5 |
|
|
65
65
|
| [DoZimple Transport Risk](#g-transport-risk) | Decidir si un pase entero puede ir a calidad o productivo, con el porqué en lenguaje de negocio. | 7 |
|
|
66
66
|
| [Operación y crecimiento](#g-operacion) | Ver qué funciona en cada sistema y decidir con datos cuál es la siguiente tool. | 4 |
|
|
67
67
|
<!-- groups:end -->
|
|
@@ -187,6 +187,7 @@ la **[referencia completa](docs/TOOLS.md)**.
|
|
|
187
187
|
| Tool | Qué hace | Acceso |
|
|
188
188
|
|---|---|---|
|
|
189
189
|
| [`write_source`](docs/TOOLS.md#escritura) | **Guardar fuente en SAP.** Sustituye la fuente COMPLETA de un objeto existente (o de un include de clase), en la orden indicada. | escribe (DEV autorizado) |
|
|
190
|
+
| [`revert_source`](docs/TOOLS.md#escritura) | **Volver a una versión anterior.** Deshace un cambio escribiendo de nuevo una versión anterior del objeto: la última activa (para limpiar un borrador inactivo tras un write_source cuya activación falló), la anterior a la activa, o una concreta del historial de object_versions. | escribe (DEV autorizado) |
|
|
190
191
|
| [`activate`](docs/TOOLS.md#escritura) | **Activar objeto.** Activa un objeto y devuelve los mensajes de SAP tal cual (errores con línea, avisos, objetos que quedan inactivos). | escribe (DEV autorizado) |
|
|
191
192
|
| [`write_text_elements`](docs/TOOLS.md#escritura) | **Crear o cambiar símbolos de texto.** Añade o modifica símbolos de texto (o textos de selección) de un programa/clase/grupo, fusionando con los existentes: no borra los que no se mencionan. | escribe (DEV autorizado) |
|
|
192
193
|
| [`create_transport`](docs/TOOLS.md#escritura) | **Crear orden de transporte.** Crea una orden workbench para el paquete de un objeto, ANTES de la primera edición, para que el cambio caiga en la orden del ticket y no en una tarea reutilizada. | escribe (DEV autorizado) |
|
|
@@ -291,7 +292,7 @@ Modelo de amenazas con STRIDE y OWASP Top 10 para aplicaciones LLM: **[docs/THRE
|
|
|
291
292
|
Con la versión exacta y sin scripts de instalación, igual que las dependencias del propio proyecto:
|
|
292
293
|
|
|
293
294
|
```sh
|
|
294
|
-
npm install -g --ignore-scripts @dozimple/abap-adt@1.
|
|
295
|
+
npm install -g --ignore-scripts @dozimple/abap-adt@1.1.0
|
|
295
296
|
PKG="$(npm root -g)/@dozimple/abap-adt"
|
|
296
297
|
mkdir -p ~/.config/abap-adt-dozimple && chmod 700 ~/.config/abap-adt-dozimple
|
|
297
298
|
cp "$PKG/config/systems.example.json" ~/.config/abap-adt-dozimple/systems.json # sistemas, roles y permisos
|
|
@@ -307,8 +308,9 @@ Registro en el cliente MCP:
|
|
|
307
308
|
```
|
|
308
309
|
|
|
309
310
|
Cada versión se publica desde el CI con [procedencia de npm](https://docs.npmjs.com/generating-provenance-statements):
|
|
310
|
-
`npm view @dozimple/abap-adt@1.
|
|
311
|
-
paquete, su bundle de Sigstore
|
|
311
|
+
`npm view @dozimple/abap-adt@1.1.0 dist.attestations` muestra la atestación, y la release de GitHub incluye el
|
|
312
|
+
paquete, su bundle de Sigstore (`.sigstore.json`), el mismo bundle como procedencia in-toto (`.intoto.jsonl`)
|
|
313
|
+
y el SBOM. Para comprobarlo: `gh attestation verify dozimple-abap-adt-1.1.0.tgz --repo <owner>/abap-adt-dozimple`.
|
|
312
314
|
|
|
313
315
|
### Desde el código fuente
|
|
314
316
|
|
|
@@ -342,7 +344,7 @@ abap-adt-doZimple se construye sobre el trabajo de otros, y lo reconoce: cada to
|
|
|
342
344
|
<!-- credits:start -->
|
|
343
345
|
| Proyecto | Autor / titular | Licencia | Tipo | Usado en |
|
|
344
346
|
|---|---|---|---|---|
|
|
345
|
-
| [abap-adt-api](https://github.com/marcellourbani/abap-adt-api) | Marcello Urbani | MIT | dependencia | todas (núcleo), `transport_diff`, `transport_contents`, `inactive_objects`, `edit_preflight`, `run_atc` y
|
|
347
|
+
| [abap-adt-api](https://github.com/marcellourbani/abap-adt-api) | Marcello Urbani | MIT | dependencia | todas (núcleo), `transport_diff`, `transport_contents`, `inactive_objects`, `edit_preflight`, `run_atc` y 23 más |
|
|
346
348
|
| [Model Context Protocol TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) | Model Context Protocol | MIT | dependencia | todas (núcleo) |
|
|
347
349
|
| [mcp-sap-docs](https://github.com/marianfoo/mcp-sap-docs) | Marian Zeis (marianfoo) | Apache-2.0 | dependencia | `abap_feature_matrix`, `docs_search`, `docs_fetch`, `clean_core_objects`, `clean_core_object`, `abap_lint` y 1 más |
|
|
348
350
|
| [abaplint](https://github.com/abaplint/abaplint) | Lars Hvam y contribuidores | MIT | dependencia | `abap_lint` |
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**English** · [Español](README.es.md)
|
|
6
6
|
|
|
7
|
-
[](../../actions/workflows/ci.yml)    
|
|
7
|
+
[](../../actions/workflows/ci.yml) [](https://www.bestpractices.dev/projects/14759)    
|
|
8
8
|
|
|
9
9
|
**AI that works on your SAP system with the rules of a senior consultant.**
|
|
10
10
|
|
|
@@ -62,7 +62,7 @@ and **a failure is never presented as an empty result or as success**.
|
|
|
62
62
|
| [Data queries](#g-datos) | Query tables with ABAP SQL, read-only, with sensitive and personal data protected. | 2 |
|
|
63
63
|
| [Incident diagnosis](#g-diagnostico) | One conversation for what used to take ST22, SM37, SLG1 and /IWFND/ERROR_LOG. | 4 |
|
|
64
64
|
| [SAP documentation](#g-documentacion) | Answer from official documentation and check which syntax exists in each release. | 7 |
|
|
65
|
-
| [Controlled writes](#g-escritura) | Save changes only in development, in the right transport, previewed and confirmed by a human. |
|
|
65
|
+
| [Controlled writes](#g-escritura) | Save changes only in development, in the right transport, previewed and confirmed by a human. | 5 |
|
|
66
66
|
| [DoZimple Transport Risk](#g-transport-risk) | Decide whether a whole release can go to QA or production, with the why in business terms. | 7 |
|
|
67
67
|
| [Operations and growth](#g-operacion) | See what works on each system and decide the next tool with data. | 4 |
|
|
68
68
|
<!-- groups:end -->
|
|
@@ -188,6 +188,7 @@ Summary per group; each tool's details — parameters, types, defaults, requirem
|
|
|
188
188
|
| Tool | What it does | Access |
|
|
189
189
|
|---|---|---|
|
|
190
190
|
| [`write_source`](docs/TOOLS.md#escritura) | **Save source to SAP.** Replaces the FULL source of an existing object (or class include) in the given transport, after a preview with syntax check and diff and a human confirmation. | writes (authorized DEV) |
|
|
191
|
+
| [`revert_source`](docs/TOOLS.md#escritura) | **Revert to an earlier version.** Writes back an earlier version of an object (the last active one, the one before it, or a numbered one from object_versions) through the same preview, fingerprint, lock and transport as write_source; never reverts on its own. | writes (authorized DEV) |
|
|
191
192
|
| [`activate`](docs/TOOLS.md#escritura) | **Activate object.** Activates an object and returns SAP's messages as they are (errors with line, warnings, objects left inactive). | writes (authorized DEV) |
|
|
192
193
|
| [`write_text_elements`](docs/TOOLS.md#escritura) | **Create or change text symbols.** Adds or changes text symbols (or selection texts) of a program/class/group, merging with the existing ones: nothing not mentioned is deleted. | writes (authorized DEV) |
|
|
193
194
|
| [`create_transport`](docs/TOOLS.md#escritura) | **Create transport request.** Creates a workbench request for an object's package BEFORE the first edit, so the change lands in the ticket's transport and not in a reused task. | writes (authorized DEV) |
|
|
@@ -291,7 +292,7 @@ Designed to pass a Security and Basis review without exceptions. Details: **[SEC
|
|
|
291
292
|
Pinned to an exact version, without install scripts, like the project's own dependencies:
|
|
292
293
|
|
|
293
294
|
```sh
|
|
294
|
-
npm install -g --ignore-scripts @dozimple/abap-adt@1.
|
|
295
|
+
npm install -g --ignore-scripts @dozimple/abap-adt@1.1.0
|
|
295
296
|
PKG="$(npm root -g)/@dozimple/abap-adt"
|
|
296
297
|
mkdir -p ~/.config/abap-adt-dozimple && chmod 700 ~/.config/abap-adt-dozimple
|
|
297
298
|
cp "$PKG/config/systems.example.json" ~/.config/abap-adt-dozimple/systems.json # systems, roles and permissions
|
|
@@ -307,8 +308,9 @@ MCP client registration:
|
|
|
307
308
|
```
|
|
308
309
|
|
|
309
310
|
Every release is published from CI with [npm provenance](https://docs.npmjs.com/generating-provenance-statements):
|
|
310
|
-
`npm view @dozimple/abap-adt@1.
|
|
311
|
-
tarball, its Sigstore bundle
|
|
311
|
+
`npm view @dozimple/abap-adt@1.1.0 dist.attestations` shows the attestation, and the GitHub release carries the
|
|
312
|
+
tarball, its Sigstore bundle (`.sigstore.json`), the same bundle as in-toto provenance (`.intoto.jsonl`) and the
|
|
313
|
+
SBOM. To check it: `gh attestation verify dozimple-abap-adt-1.1.0.tgz --repo <owner>/abap-adt-dozimple`.
|
|
312
314
|
|
|
313
315
|
### From source
|
|
314
316
|
|
|
@@ -343,7 +345,7 @@ abap-adt-doZimple is built on other people's work, and says so: each tool lists
|
|
|
343
345
|
<!-- credits:start -->
|
|
344
346
|
| Project | Author / holder | License | Type | Used in |
|
|
345
347
|
|---|---|---|---|---|
|
|
346
|
-
| [abap-adt-api](https://github.com/marcellourbani/abap-adt-api) | Marcello Urbani | MIT | dependency | all (core), `transport_diff`, `transport_contents`, `inactive_objects`, `edit_preflight`, `run_atc` and
|
|
348
|
+
| [abap-adt-api](https://github.com/marcellourbani/abap-adt-api) | Marcello Urbani | MIT | dependency | all (core), `transport_diff`, `transport_contents`, `inactive_objects`, `edit_preflight`, `run_atc` and 23 more |
|
|
347
349
|
| [Model Context Protocol TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk) | Model Context Protocol | MIT | dependency | all (core) |
|
|
348
350
|
| [mcp-sap-docs](https://github.com/marianfoo/mcp-sap-docs) | Marian Zeis (marianfoo) | Apache-2.0 | dependency | `abap_feature_matrix`, `docs_search`, `docs_fetch`, `clean_core_objects`, `clean_core_object`, `abap_lint` and 1 more |
|
|
349
351
|
| [abaplint](https://github.com/abaplint/abaplint) | Lars Hvam and contributors | MIT | dependency | `abap_lint` |
|
package/SECURITY.md
CHANGED
|
@@ -8,26 +8,26 @@ aplicaciones LLM, riesgos residuales): **[docs/THREAT_MODEL.md](docs/THREAT_MODE
|
|
|
8
8
|
|
|
9
9
|
| Activo | Amenaza | Control |
|
|
10
10
|
|---|---|---|
|
|
11
|
-
| Credenciales SAP | Filtrado en archivos, logs, respuestas o procesos hijo | Llavero de macOS o Secret Service en Linux (`execFile` sin shell); opción `--strict` para que el llavero pregunte en cada lectura; la configuración no admite contraseñas; nunca en logs ni respuestas; los procesos hijo reciben un entorno explícito mínimo (`PATH`, `HOME`, `TMPDIR`, idioma y terminal) |
|
|
11
|
+
| Credenciales SAP | Filtrado en archivos, logs, respuestas o procesos hijo | Llavero de macOS o Secret Service en Linux (`execFile` sin shell); opción `--strict` para que el llavero pregunte en cada lectura; en memoria, caducan a los 15 min y se borran al cerrar; la configuración no admite contraseñas; nunca en logs ni respuestas; los procesos hijo reciben un entorno explícito mínimo (`PATH`, `HOME`, `TMPDIR`, idioma y terminal) |
|
|
12
12
|
| Sistemas SAP | Cambios no deseados por un agente | `read` / `exec` / `write` por tool; `write` solo en DEV con `allowWrite`; QAS y PRD nunca; tools de escritura ocultas si ningún sistema la permite; sin liberar, importar ni borrar |
|
|
13
|
-
| Escrituras | El agente escribe sin que la persona vea qué | **Confirmación obligatoria
|
|
14
|
-
| Trazabilidad | Una escritura sin rastro, o un rastro retocado | **Registro de auditoría** local encadenado por hash (`audit.jsonl`): intención, resultado y denegaciones de toda escritura y ejecución, con usuario SAP, sistema, forma de confirmación y huella sha256 del contenido (nunca el contenido). Sin registro no se escribe. `npm run audit:verify`
|
|
13
|
+
| Escrituras | El agente escribe sin que la persona vea qué | **Confirmación obligatoria** con vista previa (sintaxis de SAP + diff real + avisos de la orden). Dos niveles de garantía, y no son equivalentes: **elicitación MCP** (fuerte: la pregunta la responde una persona en el cliente) si el cliente la soporta; si no, **token** de un solo uso atado a tool, sistema y argumentos exactos (10 min), que es una garantía **de aviso**: el servidor no puede distinguir si una persona vio la vista previa, por eso el cliente no debe autoaprobar estas tools (`destructiveHint`). `npm run audit:verify` cuenta cuántas escrituras se confirmaron de cada forma. Además, la huella de lo mostrado viaja con la confirmación y se comprueba **bajo el bloqueo** antes de escribir: si el objeto cambió en SAP tras la vista previa, no se escribe |
|
|
14
|
+
| Trazabilidad | Una escritura sin rastro, o un rastro retocado | **Registro de auditoría** local encadenado por hash y **firmado con HMAC-SHA256** (`audit.jsonl`): intención, resultado y denegaciones de toda escritura y ejecución, con usuario SAP, sistema, forma de confirmación y huella sha256 del contenido (nunca el contenido). La clave se genera al primer uso y vive en el llavero del SO (se guarda por stdin, nunca como argumento de un proceso); la fecha de creación va con ella, así que quitar firmas se detecta. Sin registro no se escribe; escrituras de varios servidores a la vez, serializadas con bloqueo. `npm run audit:verify` comprueba cadena y firmas. **Límite:** quien pueda leer tu llavero (tu propia sesión sin `--strict`) podría recalcularlo todo; para evidencia fuera de tu control, anota fuera del equipo el «último hash» que imprime `audit:verify`. Sin almacén de secretos (Windows), la cadena queda sin firma y se avisa |
|
|
15
15
|
| ABAP Unit | Un test con efectos (BD, llamadas externas) | Solo tests `RISK LEVEL HARMLESS` y `DURATION SHORT`, pedido explícitamente; solo en DEV |
|
|
16
16
|
| Orden de transporte | El cambio cae en una orden ajena | `decideTransport`: orden explícita; si hay bloqueo CTS de otra orden, se para sin escribir |
|
|
17
17
|
| Material de seguridad en SAP | Lectura de hashes, almacén seguro, PSE, secretos OAuth o dumps | Veto de USR02/USH02/USRPWDHISTORY, RSECTAB/RSECACTB, SSF_PSE_*, USRACL, SNAP, OA2C_* y columnas de hash, **también a través de vistas (DD26S) y CDS (DDLDEPENDENCY + fuente DDL), hasta 3 niveles** |
|
|
18
18
|
| Datos de personal | Infotipos y nómina | PA\*, PB\*, PCL1-5 y HRPY_\* vetados, también vía vistas y CDS |
|
|
19
|
-
| Datos personales de negocio | Exposición masiva a un proveedor LLM | Clase de datos por sistema (`test` / `masked` / `prod`; por defecto DEV = test, QAS/PRD = prod). En masked/prod: **columnas personales enmascaradas** (nombres, direcciones, teléfonos, correos, identificadores fiscales, cuentas bancarias, fechas de nacimiento), prohibido usarlas en WHERE, alias o expresiones, y **tope de filas** por sistema (200 en prod, configurable) |
|
|
19
|
+
| Datos personales de negocio | Exposición masiva a un proveedor LLM | Clase de datos por sistema (`test` / `masked` / `prod`; por defecto DEV = test, QAS/PRD = prod). En masked/prod: **columnas personales enmascaradas** (nombres, direcciones, teléfonos, correos, identificadores fiscales, cuentas bancarias, fechas de nacimiento, y las columnas propias de cada cliente declaradas en `piiColumns`), prohibido usarlas en WHERE, alias o expresiones, y **tope de filas** por sistema (200 en prod, configurable), también en `jobs` y `application_log`. El enmascarado es por nombre de columna: un campo Z no declarado sale en claro |
|
|
20
20
|
| Parámetros de las tools | Un parámetro mal escrito o inventado que se ignora en silencio (p. ej. la orden de transporte) | Esquemas estrictos: un parámetro no declarado es error y la tool no se ejecuta; el esquema publicado declara `additionalProperties: false` |
|
|
21
|
-
| SQL | Escritura, varias sentencias o inyección en filtros | Solo `SELECT`/`WITH`, sin `;`; vista previa de ADT de solo lectura; literales escapados; en `table_contents` el filtro
|
|
22
|
-
| Configuración | Alguien redirige el servidor
|
|
21
|
+
| SQL | Escritura, varias sentencias o inyección en filtros | Solo `SELECT`/`WITH`, sin `;`; vista previa de ADT de solo lectura; literales escapados; en `table_contents` el filtro se valida por **lista blanca** (columnas, operadores, literales, AND/OR/NOT, LIKE, IN, BETWEEN, IS NULL/INITIAL, paréntesis equilibrados); usuarios SAP en filtros de feeds validados por formato; nombres en rutas ADT validados (sin `..`) |
|
|
22
|
+
| Configuración | Alguien redirige el servidor, habilita la escritura o cambia el comando de un componente | `systems.json` se rechaza si lo pueden modificar otros: en macOS/Linux por permisos y propietario; **en Windows por sus ACL**, leídas por SID (independiente del idioma): solo tu usuario, SYSTEM, Administradores y TrustedInstaller pueden escribirlo. Si la comprobación no puede hacerse, no se arranca (salvo `ABAP_DZ_SKIP_ACL_CHECK=1`, avisado en cada arranque). El sistema por defecto por variable de entorno se identifica en cada respuesta, y si no existe es error |
|
|
23
23
|
| Red | Exposición de un puerto | Solo stdio; no se abre ningún puerto |
|
|
24
|
-
| TLS | Intercepción (MitM) | Verificación por defecto; `caFile` para certificados propios; `allowSelfSigned` marcado con ⚠ |
|
|
24
|
+
| TLS | Intercepción (MitM) | Verificación por defecto; `caFile` para certificados propios; `allowSelfSigned` **solo en DEV** (rechazado en QAS/PRD), marcado con ⚠ y avisado en cada arranque. El servicio de riesgo no sigue redirecciones: las credenciales Basic no viajan a otra URL |
|
|
25
25
|
| Datos de clientes | Salida a internet vía documentación | Online apagado por defecto; si se habilita, `assertPublicQuery` bloquea objetos Z/Y, namespaces, órdenes, sistemas, SID, usuarios, dominios y términos configurados; `docs_fetch` solo acepta ids con forma de documento, y los online pasan el mismo filtro |
|
|
26
|
-
| Inyección de instrucciones | Fuente, textos o datos de SAP o de la web que intentan dirigir al agente | Toda respuesta de SAP y de documentación se marca como dato, nunca instrucciones; ninguna escritura sin confirmación humana con vista previa |
|
|
27
|
-
| Dependencias | Vulnerabilidades o código malicioso | 4 dependencias de producción con versión exacta y lockfile; `ignore-scripts` (sin scripts de instalación); `npm audit` y `npm audit signatures` (firmas y procedencia) en CI; SBOM CycloneDX como artefacto de cada build; acciones de CI fijadas por commit; Dependabot |
|
|
26
|
+
| Inyección de instrucciones | Fuente, textos o datos de SAP o de la web que intentan dirigir al agente | Toda respuesta de SAP y de documentación se marca como dato, nunca instrucciones (control **probabilístico**: reduce, no elimina, que el modelo obedezca contenido hostil); el marcado que revive al decodificar HTML se neutraliza; la barrera efectiva es que ninguna escritura ocurre sin confirmación humana con vista previa. Los errores que llegan al modelo se sanean (sin credenciales en URLs, sin cabeceras, sin marcado, con longitud acotada) |
|
|
27
|
+
| Dependencias | Vulnerabilidades o código malicioso | 4 dependencias de producción con versión exacta y lockfile; `ignore-scripts` (sin scripts de instalación) en el desarrollo, y el paquete publicado **no declara ningún script de instalación** (el escáner falla si aparece uno: el `.npmrc` no protege a quien instala el paquete); `npm audit` y `npm audit signatures` (firmas y procedencia) en CI; SBOM CycloneDX como artefacto de cada build; acciones de CI fijadas por commit; Dependabot |
|
|
28
28
|
| Terceros | Un MCP externo con acceso a credenciales | Solo como proceso hijo aislado, tras revisión, con entorno mínimo; sus tools pasan por la política de este servidor |
|
|
29
29
|
| Estado local | Lectura por otros usuarios del equipo | Carpetas `700`, archivos `600`; el registro de uso no guarda argumentos |
|
|
30
|
-
| Repositorio | Credenciales o datos de clientes en el historial |
|
|
30
|
+
| Repositorio | Credenciales, direcciones (IPs, hosts internos o de SAP) o datos de clientes en el código o en el historial | [Procedimiento en cada commit](docs/COMMIT_SECURITY.md): `pre-commit` revisa el código completo más lo preparado; `pre-push` y CI revisan todo el historial; `main` exige el CI; secret scanning con push protection. Los términos de clientes se leen de la configuración local, nunca se listan en el repo |
|
|
31
31
|
|
|
32
32
|
## Recomendaciones de despliegue
|
|
33
33
|
|
|
@@ -46,7 +46,8 @@ aplicaciones LLM, riesgos residuales): **[docs/THREAT_MODEL.md](docs/THREAT_MODE
|
|
|
46
46
|
|
|
47
47
|
```sh
|
|
48
48
|
npm run security # npm audit + firmas del registro + escáner de credenciales y datos de clientes
|
|
49
|
-
npm test # incluye los tests de política y de seguridad (test/security.test.ts)
|
|
49
|
+
npm test # incluye los tests de política y de seguridad (test/security.test.ts) y las
|
|
50
|
+
# pruebas por propiedades de la entrada hostil (test/property.test.ts, fast-check)
|
|
50
51
|
npm run audit:verify # integridad del registro de auditoría local
|
|
51
52
|
npm run sbom # SBOM CycloneDX de las dependencias de producción
|
|
52
53
|
```
|
package/dist/core/atc.js
CHANGED
|
@@ -31,7 +31,8 @@ export async function defaultVariant(c) {
|
|
|
31
31
|
const v = cust.properties.find((p) => p.name === "systemCheckVariant")?.value;
|
|
32
32
|
return typeof v === "string" && v ? v : "DEFAULT";
|
|
33
33
|
}
|
|
34
|
-
|
|
34
|
+
/** Escapado XML completo (texto y atributos con comillas simples o dobles). */
|
|
35
|
+
const xmlEsc = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
35
36
|
/**
|
|
36
37
|
* Ejecución ATC sobre varios objetos a la vez. La librería solo admite una
|
|
37
38
|
* URI; en NW 7.50 una orden no sirve como conjunto («No URI-Mapping defined
|
package/dist/core/audit.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { appendFileSync, closeSync, existsSync, fstatSync, mkdirSync, openSync, readFileSync, readSync, rmSync, statSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { auditKey, macOf } from "./auditkey.js";
|
|
4
5
|
import { stateDir } from "./telemetry.js";
|
|
5
6
|
/**
|
|
6
7
|
* Registro de auditoría de todo lo que modifica o ejecuta en SAP. Solo
|
|
@@ -15,7 +16,7 @@ export const AUDIT_FILE = "audit.jsonl";
|
|
|
15
16
|
const GENESIS = "0".repeat(64);
|
|
16
17
|
const INLINE_MAX = 120;
|
|
17
18
|
/** E/S del registro, sustituible en tests para simular disco lleno o permisos. */
|
|
18
|
-
export const auditIo = { appendFileSync, existsSync, mkdirSync, readFileSync };
|
|
19
|
+
export const auditIo = { appendFileSync, existsSync, mkdirSync, readFileSync, openSync, closeSync, fstatSync, readSync, statSync, rmSync };
|
|
19
20
|
const sha256 = (s) => createHash("sha256").update(s).digest("hex");
|
|
20
21
|
/** Argumentos aptos para el registro: valores cortos tal cual, el resto como huella. */
|
|
21
22
|
export function auditArgs(args) {
|
|
@@ -39,14 +40,63 @@ export function auditArgs(args) {
|
|
|
39
40
|
function entryHash(e) {
|
|
40
41
|
return sha256(JSON.stringify(e));
|
|
41
42
|
}
|
|
43
|
+
/** Última entrada leyendo solo la cola del archivo (el registro no se relee entero en cada escritura). */
|
|
42
44
|
function lastEntry(path) {
|
|
43
45
|
if (!auditIo.existsSync(path))
|
|
44
46
|
return { seq: 0, hash: GENESIS };
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
const fd = auditIo.openSync(path, "r");
|
|
48
|
+
try {
|
|
49
|
+
const size = auditIo.fstatSync(fd).size;
|
|
50
|
+
if (!size)
|
|
51
|
+
return { seq: 0, hash: GENESIS };
|
|
52
|
+
for (let window = 16 * 1024;; window *= 4) {
|
|
53
|
+
const start = Math.max(0, size - window);
|
|
54
|
+
const buf = Buffer.alloc(size - start);
|
|
55
|
+
auditIo.readSync(fd, buf, 0, buf.length, start);
|
|
56
|
+
const lines = buf.toString("utf8").trimEnd().split("\n");
|
|
57
|
+
// Si la ventana empieza a mitad de una línea, la primera está cortada: vale la última si hay al menos dos.
|
|
58
|
+
if (lines.length >= 2 || start === 0) {
|
|
59
|
+
const last = JSON.parse(lines[lines.length - 1]);
|
|
60
|
+
return { seq: last.seq, hash: last.hash };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
auditIo.closeSync(fd);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Bloqueo entre procesos (dos clientes MCP pueden tener cada uno su servidor): sin él, dos escrituras simultáneas
|
|
70
|
+
* leerían el mismo «prev» y romperían la cadena, que sería indistinguible de una manipulación.
|
|
71
|
+
*/
|
|
72
|
+
function withLock(dir, fn) {
|
|
73
|
+
const lock = join(dir, AUDIT_FILE + ".lock");
|
|
74
|
+
const deadline = Date.now() + 5_000;
|
|
75
|
+
for (;;) {
|
|
76
|
+
try {
|
|
77
|
+
auditIo.closeSync(auditIo.openSync(lock, "wx", 0o600));
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
if (e.code !== "EEXIST")
|
|
82
|
+
throw e;
|
|
83
|
+
// Un bloqueo de más de 30 s es de un proceso que murió a mitad: se recupera.
|
|
84
|
+
try {
|
|
85
|
+
if (Date.now() - auditIo.statSync(lock).mtimeMs > 30_000)
|
|
86
|
+
auditIo.rmSync(lock, { force: true });
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
if (Date.now() > deadline)
|
|
90
|
+
throw new Error("registro de auditoría bloqueado por otro proceso");
|
|
91
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 25);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
return fn();
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
auditIo.rmSync(lock, { force: true });
|
|
99
|
+
}
|
|
50
100
|
}
|
|
51
101
|
/**
|
|
52
102
|
* Añade una entrada. Lanza si no puede escribir: quien llama decide no
|
|
@@ -55,6 +105,12 @@ function lastEntry(path) {
|
|
|
55
105
|
export function appendAudit(input, dir = stateDir()) {
|
|
56
106
|
auditIo.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
57
107
|
const path = join(dir, AUDIT_FILE);
|
|
108
|
+
return withLock(dir, () => appendLocked(path, input));
|
|
109
|
+
}
|
|
110
|
+
function appendLocked(path, input) {
|
|
111
|
+
// La clave se obtiene (o se crea) ANTES de fechar la entrada: así la primera firmada nunca queda con una fecha
|
|
112
|
+
// anterior a la de la clave, y quitarle la firma no la hace pasar por una entrada antigua.
|
|
113
|
+
const key = auditKey(true);
|
|
58
114
|
const prev = lastEntry(path);
|
|
59
115
|
const base = {
|
|
60
116
|
seq: prev.seq + 1,
|
|
@@ -63,14 +119,19 @@ export function appendAudit(input, dir = stateDir()) {
|
|
|
63
119
|
args: auditArgs(input.args),
|
|
64
120
|
prev: prev.hash,
|
|
65
121
|
};
|
|
66
|
-
const
|
|
122
|
+
const hash = entryHash(base);
|
|
123
|
+
const entry = key ? { ...base, hash, mac: macOf(key.key, hash) } : { ...base, hash };
|
|
67
124
|
auditIo.appendFileSync(path, JSON.stringify(entry) + "\n", { mode: 0o600 });
|
|
68
125
|
return entry;
|
|
69
126
|
}
|
|
70
|
-
/**
|
|
71
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Recorre la cadena completa: secuencia, enlace con la anterior y hash propio. Con la clave, además, la firma de cada
|
|
129
|
+
* entrada; y toda entrada posterior a la creación de la clave TIENE que estar firmada (quitar firmas no sirve).
|
|
130
|
+
*/
|
|
131
|
+
export function verifyAudit(text, key) {
|
|
72
132
|
const lines = text.split("\n").filter(Boolean);
|
|
73
133
|
let prev = GENESIS;
|
|
134
|
+
let signed = 0;
|
|
74
135
|
for (let i = 0; i < lines.length; i++) {
|
|
75
136
|
let e;
|
|
76
137
|
try {
|
|
@@ -79,15 +140,23 @@ export function verifyAudit(text) {
|
|
|
79
140
|
catch {
|
|
80
141
|
return { ok: false, entries: lines.length, brokenAt: i + 1, reason: "línea que no es JSON" };
|
|
81
142
|
}
|
|
82
|
-
const { hash, ...rest } = e;
|
|
143
|
+
const { hash, mac, ...rest } = e;
|
|
83
144
|
if (e.seq !== i + 1)
|
|
84
145
|
return { ok: false, entries: lines.length, brokenAt: i + 1, reason: `secuencia ${e.seq}, se esperaba ${i + 1}` };
|
|
85
146
|
if (e.prev !== prev)
|
|
86
147
|
return { ok: false, entries: lines.length, brokenAt: i + 1, reason: "no enlaza con la entrada anterior" };
|
|
87
148
|
if (entryHash(rest) !== hash)
|
|
88
149
|
return { ok: false, entries: lines.length, brokenAt: i + 1, reason: "contenido alterado" };
|
|
150
|
+
if (key) {
|
|
151
|
+
if (mac !== undefined && mac !== macOf(key.key, hash))
|
|
152
|
+
return { ok: false, entries: lines.length, brokenAt: i + 1, reason: "firma HMAC inválida (cadena recalculada sin la clave)" };
|
|
153
|
+
if (mac === undefined && e.ts >= key.created)
|
|
154
|
+
return { ok: false, entries: lines.length, brokenAt: i + 1, reason: "entrada sin firma posterior a la creación de la clave (firma quitada)" };
|
|
155
|
+
if (mac !== undefined)
|
|
156
|
+
signed++;
|
|
157
|
+
}
|
|
89
158
|
prev = hash;
|
|
90
159
|
}
|
|
91
|
-
return { ok: true, entries: lines.length };
|
|
160
|
+
return key ? { ok: true, entries: lines.length, signed } : { ok: true, entries: lines.length };
|
|
92
161
|
}
|
|
93
162
|
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
3
|
+
/**
|
|
4
|
+
* Clave HMAC del registro de auditoría, guardada en el almacén de secretos del sistema operativo (llavero de macOS
|
|
5
|
+
* o Secret Service en Linux). Con ella, reescribir el registro exige además poder leer el llavero del usuario: un
|
|
6
|
+
* proceso sin acceso al llavero (u otro usuario del equipo) ya no puede recalcular la cadena y que pase por buena.
|
|
7
|
+
*
|
|
8
|
+
* Formato guardado: «v1:<fecha ISO de creación>:<64 hex>». La fecha permite detectar que se QUITARON firmas:
|
|
9
|
+
* toda entrada posterior a ella tiene que llevarla.
|
|
10
|
+
*
|
|
11
|
+
* ABAP_DZ_AUDIT_KEYSTORE: «os» (por defecto), «memory» (tests) o «none» (sin firma, se avisa).
|
|
12
|
+
*/
|
|
13
|
+
const SERVICE = "abap-adt-dozimple-audit";
|
|
14
|
+
const ACCOUNT = "hmac";
|
|
15
|
+
let cached;
|
|
16
|
+
let memory;
|
|
17
|
+
let warned = false;
|
|
18
|
+
const parse = (v) => {
|
|
19
|
+
const m = /^v1:([^:]+(?::[^:]+)*?):([0-9a-f]{64})$/.exec((v ?? "").trim());
|
|
20
|
+
return m ? { created: m[1], key: Buffer.from(m[2], "hex") } : null;
|
|
21
|
+
};
|
|
22
|
+
function readOs() {
|
|
23
|
+
try {
|
|
24
|
+
if (process.platform === "darwin") {
|
|
25
|
+
return execFileSync("security", ["find-generic-password", "-s", SERVICE, "-a", ACCOUNT, "-w"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
26
|
+
}
|
|
27
|
+
if (process.platform === "linux") {
|
|
28
|
+
return execFileSync("secret-tool", ["lookup", "service", SERVICE, "account", ACCOUNT], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
/* no existe todavía, o no hay almacén */
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
/** Guarda el valor sin pasarlo nunca como argumento de un proceso (visible en `ps`): siempre por stdin. */
|
|
37
|
+
function writeOs(value) {
|
|
38
|
+
try {
|
|
39
|
+
if (process.platform === "darwin") {
|
|
40
|
+
execFileSync("security", ["-i"], { input: `add-generic-password -U -s ${SERVICE} -a ${ACCOUNT} -w ${value}\n`, stdio: ["pipe", "ignore", "ignore"] });
|
|
41
|
+
return parse(readOs())?.key.toString("hex") === value.split(":").pop();
|
|
42
|
+
}
|
|
43
|
+
if (process.platform === "linux") {
|
|
44
|
+
execFileSync("secret-tool", ["store", `--label=${SERVICE}`, "service", SERVICE, "account", ACCOUNT], { input: value, stdio: ["pipe", "ignore", "ignore"] });
|
|
45
|
+
return !!parse(readOs());
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
/* sin almacén disponible */
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* La clave, creándola la primera vez si `create`. null = no hay almacén de secretos: el registro sigue encadenado
|
|
55
|
+
* pero sin firma (y se avisa una vez por stderr).
|
|
56
|
+
*/
|
|
57
|
+
export function auditKey(create = true) {
|
|
58
|
+
if (cached !== undefined && (cached || !create))
|
|
59
|
+
return cached;
|
|
60
|
+
const mode = process.env.ABAP_DZ_AUDIT_KEYSTORE ?? "os";
|
|
61
|
+
if (mode === "none")
|
|
62
|
+
return (cached = null);
|
|
63
|
+
const read = () => (mode === "memory" ? memory : readOs());
|
|
64
|
+
let k = parse(read());
|
|
65
|
+
if (!k && create) {
|
|
66
|
+
const value = `v1:${new Date().toISOString()}:${randomBytes(32).toString("hex")}`;
|
|
67
|
+
if (mode === "memory")
|
|
68
|
+
memory = value;
|
|
69
|
+
else if (!writeOs(value)) {
|
|
70
|
+
if (!warned)
|
|
71
|
+
console.error("[abap-adt-doZimple] ⚠ Sin almacén de secretos: el registro de auditoría queda encadenado pero sin firma HMAC.");
|
|
72
|
+
warned = true;
|
|
73
|
+
return (cached = null);
|
|
74
|
+
}
|
|
75
|
+
k = parse(read());
|
|
76
|
+
}
|
|
77
|
+
return (cached = k);
|
|
78
|
+
}
|
|
79
|
+
/** Solo tests: olvida la clave en memoria (como si fuera otro equipo o se hubiera borrado). */
|
|
80
|
+
export function resetAuditKeyForTests() {
|
|
81
|
+
cached = undefined;
|
|
82
|
+
memory = undefined;
|
|
83
|
+
}
|
|
84
|
+
export const macOf = (key, hash) => createHmac("sha256", key).update(hash).digest("hex");
|
|
85
|
+
//# sourceMappingURL=auditkey.js.map
|
package/dist/core/catalog.en.js
CHANGED
|
@@ -48,6 +48,7 @@ export const TOOLS_EN = {
|
|
|
48
48
|
abap_lint: "**abaplint on a snippet.** Runs abaplint locally (code never leaves the machine) on an ABAP snippet or source.",
|
|
49
49
|
docs_community_search: "**Search SAP Community.** Searches SAP Community (blogs and questions) by error message, class or concept.",
|
|
50
50
|
write_source: "**Save source to SAP.** Replaces the FULL source of an existing object (or class include) in the given transport, after a preview with syntax check and diff and a human confirmation.",
|
|
51
|
+
revert_source: "**Revert to an earlier version.** Writes back an earlier version of an object (the last active one, the one before it, or a numbered one from object_versions) through the same preview, fingerprint, lock and transport as write_source; never reverts on its own.",
|
|
51
52
|
activate: "**Activate object.** Activates an object and returns SAP's messages as they are (errors with line, warnings, objects left inactive).",
|
|
52
53
|
write_text_elements: "**Create or change text symbols.** Adds or changes text symbols (or selection texts) of a program/class/group, merging with the existing ones: nothing not mentioned is deleted.",
|
|
53
54
|
create_transport: "**Create transport request.** Creates a workbench request for an object's package BEFORE the first edit, so the change lands in the ticket's transport and not in a reused task.",
|
package/dist/core/catalog.js
CHANGED
|
@@ -100,6 +100,7 @@ export const GROUPS = [
|
|
|
100
100
|
pitch: "Guardar cambios solo en desarrollo, en la orden correcta y con la sintaxis verificada antes.",
|
|
101
101
|
tools: [
|
|
102
102
|
{ name: "write_source", credits: ADT },
|
|
103
|
+
{ name: "revert_source", credits: ADT },
|
|
103
104
|
{ name: "activate", credits: ADT },
|
|
104
105
|
{ name: "write_text_elements", credits: ADT },
|
|
105
106
|
{ name: "create_transport", credits: ADT },
|